# HG changeset patch # User Sean Wilcox # Date 1468344074 21600 # Node ID 833d05f91424202aeec59013e8d1ba4f557468dc # Parent d9146784951ccb0383310c2f8cd5b5e4b14a144b 23858725 Cannot nova boot by flavor name diff -r d9146784951c -r 833d05f91424 components/python/novaclient/patches/03-launchpad-1446850.patch --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/components/python/novaclient/patches/03-launchpad-1446850.patch Tue Jul 12 11:21:14 2016 -0600 @@ -0,0 +1,216 @@ +This upstream patch has been addressed in Liberty but has not yet been +addressed in Kilo. + +From 098116d6a574b8dede101b257db2ff22d269c6c7 Mon Sep 17 00:00:00 2001 +From: melanie witt +Date: Tue, 21 Apr 2015 21:32:33 +0000 +Subject: Revert "nova flavor-show command is inconsistent" + +This reverts commit 4e79285b45ec1490c8e923f724cbaf4d42fe81c4. + +The aforementioned commit broke flavor-show for mixed case flavorids. +The reason is a bit complex. On the nova api side, there is caching of +db items for flavors, keyed off the flavorid retrieved from the db, +case sensitive, unlike the db query itself. Attempts to flavor-show +a flavor with flavorid composed of letters will fail with a 400 if +the capitalization doesn't match. + +For the flavor names, they work in lowercase because the find_resource +function falls back on a search by "human_id" after failing every other +attempt to find the flavor, because "human_id" is a oslo-slugified +string (all lowercase, non-word characters removed, spaces converted +to hyphens). + +Closes-Bug: #1446850 + +Change-Id: I73247b50f5a6918167c071ccc13cd676aa2c7fec +--- + novaclient/tests/unit/test_base.py | 2 +- + novaclient/tests/unit/v2/fakes.py | 20 ++++++++++---------- + novaclient/tests/unit/v2/test_flavors.py | 4 ++-- + novaclient/tests/unit/v2/test_shell.py | 16 ++++++++-------- + novaclient/v2/shell.py | 11 ----------- + 5 files changed, 21 insertions(+), 32 deletions(-) + +diff --git a/novaclient/tests/unit/test_base.py b/novaclient/tests/unit/test_base.py +index 1ba1d32..b7bceb7 100644 +--- a/novaclient/tests/unit/test_base.py ++++ b/novaclient/tests/unit/test_base.py +@@ -36,7 +36,7 @@ class BaseTest(utils.TestCase): + + def test_resource_lazy_getattr(self): + f = flavors.Flavor(cs.flavors, {'id': 1}) +- self.assertEqual('256 mb server', f.name) ++ self.assertEqual('256 MB Server', f.name) + cs.assert_called('GET', '/flavors/1') + + # Missing stuff still fails after a second get +diff --git a/novaclient/tests/unit/v2/fakes.py b/novaclient/tests/unit/v2/fakes.py +index 343c421..40cd471 100644 +--- a/novaclient/tests/unit/v2/fakes.py ++++ b/novaclient/tests/unit/v2/fakes.py +@@ -258,7 +258,7 @@ class FakeHTTPClient(base_client.HTTPClient): + }, + "flavor": { + "id": 1, +- "name": "256 mb server", ++ "name": "256 MB Server", + }, + "hostId": "e4d909c290d0fb1ca068ffaddf22cbd0", + "status": "BUILD", +@@ -299,7 +299,7 @@ class FakeHTTPClient(base_client.HTTPClient): + }, + "flavor": { + "id": 1, +- "name": "256 mb server", ++ "name": "256 MB Server", + }, + "hostId": "9e107d9d372bb6826bd81d3542a419d6", + "status": "ACTIVE", +@@ -340,7 +340,7 @@ class FakeHTTPClient(base_client.HTTPClient): + "image": "", + "flavor": { + "id": 1, +- "name": "256 mb server", ++ "name": "256 MB Server", + }, + "hostId": "9e107d9d372bb6826bd81d3542a419d6", + "status": "ACTIVE", +@@ -672,19 +672,19 @@ class FakeHTTPClient(base_client.HTTPClient): + + def get_flavors_detail(self, **kw): + flavors = {'flavors': [ +- {'id': 1, 'name': '256 mb server', 'ram': 256, 'disk': 10, ++ {'id': 1, 'name': '256 MB Server', 'ram': 256, 'disk': 10, + 'OS-FLV-EXT-DATA:ephemeral': 10, + 'os-flavor-access:is_public': True, + 'links': {}}, +- {'id': 2, 'name': '512 mb server', 'ram': 512, 'disk': 20, ++ {'id': 2, 'name': '512 MB Server', 'ram': 512, 'disk': 20, + 'OS-FLV-EXT-DATA:ephemeral': 20, + 'os-flavor-access:is_public': False, + 'links': {}}, +- {'id': 4, 'name': '1024 mb server', 'ram': 1024, 'disk': 10, ++ {'id': 4, 'name': '1024 MB Server', 'ram': 1024, 'disk': 10, + 'OS-FLV-EXT-DATA:ephemeral': 10, + 'os-flavor-access:is_public': True, + 'links': {}}, +- {'id': 'aa1', 'name': '128 mb server', 'ram': 128, 'disk': 0, ++ {'id': 'aa1', 'name': '128 MB Server', 'ram': 128, 'disk': 0, + 'OS-FLV-EXT-DATA:ephemeral': 0, + 'os-flavor-access:is_public': True, + 'links': {}} +@@ -736,16 +736,16 @@ class FakeHTTPClient(base_client.HTTPClient): + {}, + {'flavor': { + 'id': 3, +- 'name': '256 mb server', ++ 'name': '256 MB Server', + 'ram': 256, + 'disk': 10, + }}, + ) + +- def get_flavors_512_mb_server(self, **kw): ++ def get_flavors_512_MB_Server(self, **kw): + raise exceptions.NotFound('404') + +- def get_flavors_128_mb_server(self, **kw): ++ def get_flavors_128_MB_Server(self, **kw): + raise exceptions.NotFound('404') + + def get_flavors_aa1(self, **kw): +diff --git a/novaclient/tests/unit/v2/test_flavors.py b/novaclient/tests/unit/v2/test_flavors.py +index 5045902..4fe43c1 100644 +--- a/novaclient/tests/unit/v2/test_flavors.py ++++ b/novaclient/tests/unit/v2/test_flavors.py +@@ -93,10 +93,10 @@ class FlavorsTest(utils.TestCase): + def test_find(self): + f = self.cs.flavors.find(ram=256) + self.cs.assert_called('GET', '/flavors/detail') +- self.assertEqual('256 mb server', f.name) ++ self.assertEqual('256 MB Server', f.name) + + f = self.cs.flavors.find(disk=0) +- self.assertEqual('128 mb server', f.name) ++ self.assertEqual('128 MB Server', f.name) + + self.assertRaises(exceptions.NotFound, self.cs.flavors.find, + disk=12345) +diff --git a/novaclient/tests/unit/v2/test_shell.py b/novaclient/tests/unit/v2/test_shell.py +index b1b9b80..3e7462c 100644 +--- a/novaclient/tests/unit/v2/test_shell.py ++++ b/novaclient/tests/unit/v2/test_shell.py +@@ -668,10 +668,10 @@ class ShellTest(utils.TestCase): + + def test_boot_named_flavor(self): + self.run_command(["boot", "--image", "1", +- "--flavor", "512 mb server", ++ "--flavor", "512 MB Server", + "--max-count", "3", "server"]) + self.assert_called('GET', '/images/1', pos=0) +- self.assert_called('GET', '/flavors/512 mb server', pos=1) ++ self.assert_called('GET', '/flavors/512 MB Server', pos=1) + self.assert_called('GET', '/flavors?is_public=None', pos=2) + self.assert_called('GET', '/flavors/2', pos=3) + self.assert_called( +@@ -708,15 +708,15 @@ class ShellTest(utils.TestCase): + self.assert_called_anytime('GET', '/flavors/aa1') + + def test_flavor_show_by_name(self): +- self.run_command(['flavor-show', '128 mb server']) +- self.assert_called('GET', '/flavors/128 mb server', pos=0) ++ self.run_command(['flavor-show', '128 MB Server']) ++ self.assert_called('GET', '/flavors/128 MB Server', pos=0) + self.assert_called('GET', '/flavors?is_public=None', pos=1) + self.assert_called('GET', '/flavors/aa1', pos=2) + self.assert_called('GET', '/flavors/aa1/os-extra_specs', pos=3) + + def test_flavor_show_by_name_priv(self): +- self.run_command(['flavor-show', '512 mb server']) +- self.assert_called('GET', '/flavors/512 mb server', pos=0) ++ self.run_command(['flavor-show', '512 MB Server']) ++ self.assert_called('GET', '/flavors/512 MB Server', pos=0) + self.assert_called('GET', '/flavors?is_public=None', pos=1) + self.assert_called('GET', '/flavors/2', pos=2) + self.assert_called('GET', '/flavors/2/os-extra_specs', pos=3) +@@ -753,7 +753,7 @@ class ShellTest(utils.TestCase): + {'addTenantAccess': {'tenant': 'proj2'}}) + + def test_flavor_access_add_by_name(self): +- self.run_command(['flavor-access-add', '512 mb server', 'proj2']) ++ self.run_command(['flavor-access-add', '512 MB Server', 'proj2']) + self.assert_called('POST', '/flavors/2/action', + {'addTenantAccess': {'tenant': 'proj2'}}) + +@@ -763,7 +763,7 @@ class ShellTest(utils.TestCase): + {'removeTenantAccess': {'tenant': 'proj2'}}) + + def test_flavor_access_remove_by_name(self): +- self.run_command(['flavor-access-remove', '512 mb server', 'proj2']) ++ self.run_command(['flavor-access-remove', '512 MB Server', 'proj2']) + self.assert_called('POST', '/flavors/2/action', + {'removeTenantAccess': {'tenant': 'proj2'}}) + +diff --git a/novaclient/v2/shell.py b/novaclient/v2/shell.py +index 33653a1..f8b1a10 100644 +--- a/novaclient/v2/shell.py ++++ b/novaclient/v2/shell.py +@@ -1898,17 +1898,6 @@ def _find_image(cs, image): + def _find_flavor(cs, flavor): + """Get a flavor by name, ID, or RAM size.""" + try: +- # isinstance() is being used to check if flavor is an instance of +- # integer. It will help us to check if the user has entered flavor +- # name or flavorid. If flavor name has been entered it is being +- # converted to lowercase using lower(). Incase it is an ID the user +- # has passed it will not go through the "flavor = flavor.lower()" +- # code.The reason for checking if it is a flavor name or flavorid is +- # that int has no lower() so it will give an error. +- if isinstance(flavor, six.integer_types): +- pass +- else: +- flavor = flavor.lower() + return utils.find_resource(cs.flavors, flavor, is_public=None) + except exceptions.NotFound: + return cs.flavors.find(ram=flavor)