diff --git a/releasenotes/notes/v4-fixed-ip-325740fdae85ffa9.yaml b/releasenotes/notes/v4-fixed-ip-325740fdae85ffa9.yaml new file mode 100644 index 000000000..99fe5b8d0 --- /dev/null +++ b/releasenotes/notes/v4-fixed-ip-325740fdae85ffa9.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Re-added support for `v4-fixed-ip` and `v6-fixed-ip` in the `nics` + parameter to `create_server`. These are aliaes for `fixed_ip` provided + by novaclient which shade used to use. The switch to REST didn't include + support for these aliases, resulting in a behavior regression. diff --git a/shade/openstackcloud.py b/shade/openstackcloud.py index 35b2b3500..11413567d 100644 --- a/shade/openstackcloud.py +++ b/shade/openstackcloud.py @@ -6928,13 +6928,19 @@ class OpenStackCloud( "Requested network {net} could not be found.".format( net=nic['net-name'])) net['uuid'] = nic_net['id'] + for ip_key in ('v4-fixed-ip', 'v6-fixed-ip', 'fixed_ip'): + fixed_ip = nic.pop(ip_key, None) + if fixed_ip and net.get('fixed_ip'): + raise exc.OpenStackCloudException( + "Only one of v4-fixed-ip, v6-fixed-ip or fixed_ip" + " may be given") + if fixed_ip: + net['fixed_ip'] = fixed_ip # TODO(mordred) Add support for tag if server supports microversion # 2.32-2.36 or >= 2.42 - for key in ('port', 'fixed_ip'): + for key in ('port', 'port-id'): if key in nic: - net[key] = nic.pop(key) - if 'port-id' in nic: - net['port'] = nic.pop('port-id') + net['port'] = nic.pop(key) if nic: raise exc.OpenStackCloudException( "Additional unsupported keys given for server network" diff --git a/shade/tests/unit/test_create_server.py b/shade/tests/unit/test_create_server.py index d822df7d4..9caf51d7e 100644 --- a/shade/tests/unit/test_create_server.py +++ b/shade/tests/unit/test_create_server.py @@ -631,6 +631,152 @@ class TestCreateServer(base.RequestsMockTestCase): network='network-name', nics=[]) self.assert_calls() + def test_create_server_network_fixed_ip(self): + """ + Verify that if 'fixed_ip' is supplied in nics, we pass it to networks + appropriately. + """ + network = { + 'id': 'network-id', + 'name': 'network-name' + } + fixed_ip = '10.0.0.1' + build_server = fakes.make_fake_server('1234', '', 'BUILD') + self.register_uris([ + dict(method='POST', + uri=self.get_mock_url( + 'compute', 'public', append=['servers']), + json={'server': build_server}, + validate=dict( + json={'server': { + u'flavorRef': u'flavor-id', + u'imageRef': u'image-id', + u'max_count': 1, + u'min_count': 1, + u'networks': [{'fixed_ip': fixed_ip}], + u'name': u'server-name'}})), + dict(method='GET', + uri=self.get_mock_url( + 'compute', 'public', append=['servers', '1234']), + json={'server': build_server}), + dict(method='GET', + uri=self.get_mock_url( + 'network', 'public', append=['v2.0', 'networks.json']), + json={'networks': [network]}), + dict(method='GET', + uri=self.get_mock_url( + 'network', 'public', append=['v2.0', 'subnets.json']), + json={'subnets': []}), + ]) + self.cloud.create_server( + 'server-name', dict(id='image-id'), dict(id='flavor-id'), + nics=[{'fixed_ip': fixed_ip}]) + self.assert_calls() + + def test_create_server_network_v4_fixed_ip(self): + """ + Verify that if 'v4-fixed-ip' is supplied in nics, we pass it to + networks appropriately. + """ + network = { + 'id': 'network-id', + 'name': 'network-name' + } + fixed_ip = '10.0.0.1' + build_server = fakes.make_fake_server('1234', '', 'BUILD') + self.register_uris([ + dict(method='POST', + uri=self.get_mock_url( + 'compute', 'public', append=['servers']), + json={'server': build_server}, + validate=dict( + json={'server': { + u'flavorRef': u'flavor-id', + u'imageRef': u'image-id', + u'max_count': 1, + u'min_count': 1, + u'networks': [{'fixed_ip': fixed_ip}], + u'name': u'server-name'}})), + dict(method='GET', + uri=self.get_mock_url( + 'compute', 'public', append=['servers', '1234']), + json={'server': build_server}), + dict(method='GET', + uri=self.get_mock_url( + 'network', 'public', append=['v2.0', 'networks.json']), + json={'networks': [network]}), + dict(method='GET', + uri=self.get_mock_url( + 'network', 'public', append=['v2.0', 'subnets.json']), + json={'subnets': []}), + ]) + self.cloud.create_server( + 'server-name', dict(id='image-id'), dict(id='flavor-id'), + nics=[{'fixed_ip': fixed_ip}]) + self.assert_calls() + + def test_create_server_network_v6_fixed_ip(self): + """ + Verify that if 'v6-fixed-ip' is supplied in nics, we pass it to + networks appropriately. + """ + network = { + 'id': 'network-id', + 'name': 'network-name' + } + # Note - it doesn't actually have to be a v6 address - it's just + # an alias. + fixed_ip = 'fe80::28da:5fff:fe57:13ed' + build_server = fakes.make_fake_server('1234', '', 'BUILD') + self.register_uris([ + dict(method='POST', + uri=self.get_mock_url( + 'compute', 'public', append=['servers']), + json={'server': build_server}, + validate=dict( + json={'server': { + u'flavorRef': u'flavor-id', + u'imageRef': u'image-id', + u'max_count': 1, + u'min_count': 1, + u'networks': [{'fixed_ip': fixed_ip}], + u'name': u'server-name'}})), + dict(method='GET', + uri=self.get_mock_url( + 'compute', 'public', append=['servers', '1234']), + json={'server': build_server}), + dict(method='GET', + uri=self.get_mock_url( + 'network', 'public', append=['v2.0', 'networks.json']), + json={'networks': [network]}), + dict(method='GET', + uri=self.get_mock_url( + 'network', 'public', append=['v2.0', 'subnets.json']), + json={'subnets': []}), + ]) + self.cloud.create_server( + 'server-name', dict(id='image-id'), dict(id='flavor-id'), + nics=[{'fixed_ip': fixed_ip}]) + self.assert_calls() + + def test_create_server_network_fixed_ip_conflicts(self): + """ + Verify that if 'fixed_ip' and 'v4-fixed-ip' are both supplied in nics, + we throw an exception. + """ + # Note - it doesn't actually have to be a v6 address - it's just + # an alias. + self.use_nothing() + fixed_ip = '10.0.0.1' + self.assertRaises( + exc.OpenStackCloudException, self.cloud.create_server, + 'server-name', dict(id='image-id'), dict(id='flavor-id'), + nics=[{ + 'fixed_ip': fixed_ip, + 'v4-fixed-ip': fixed_ip + }]) + self.assert_calls() + def test_create_server_get_flavor_image(self): self.use_glance() image_id = str(uuid.uuid4())