diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 1277c34130..9294288305 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -646,6 +646,8 @@ class NICAction(argparse.Action): values = '='.join([self.key, values]) + # We don't include 'tag' here by default since that requires a + # particular microversion info = { 'net-id': '', 'port-id': '', @@ -656,11 +658,11 @@ class NICAction(argparse.Action): for kv_str in values.split(','): k, sep, v = kv_str.partition("=") - if k not in info or not v: + if k not in list(info) + ['tag'] or not v: msg = _( "Invalid argument %s; argument must be of form " - "'net-id=net-uuid,v4-fixed-ip=ip-addr,v6-fixed-ip=ip-addr," - "port-id=port-uuid'" + "'net-id=net-uuid,port-id=port-uuid,v4-fixed-ip=ip-addr," + "v6-fixed-ip=ip-addr,tag=tag'" ) raise argparse.ArgumentTypeError(msg % values) @@ -801,7 +803,7 @@ class CreateServer(command.ShowOne): parser.add_argument( '--nic', metavar="", + "v6-fixed-ip=ip-addr,tag=tag,auto,none>", action=NICAction, dest='nics', default=[], @@ -814,6 +816,8 @@ class CreateServer(command.ShowOne): "\n" "v6-fixed-ip=: IPv6 fixed address for NIC (optional)," "\n" + "tag: interface metadata tag (optional) " + "(supported by --os-compute-api-version 2.43 or above),\n" "none: (v2.37+) no network is attached,\n" "auto: (v2.37+) the compute service will automatically " "allocate a network.\n" @@ -1191,6 +1195,17 @@ class CreateServer(command.ShowOne): nics = nics[0] else: for nic in nics: + if 'tag' in nic: + if ( + compute_client.api_version < + api_versions.APIVersion('2.43') + ): + msg = _( + '--os-compute-api-version 2.43 or greater is ' + 'required to support the --nic tag field' + ) + raise exceptions.CommandError(msg) + if self.app.client_manager.is_network_endpoint_enabled(): network_client = self.app.client_manager.network diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index e8db7f596f..5cf76c887e 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -1423,6 +1423,113 @@ class TestServerCreate(TestServer): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist(), data) + def test_server_create_with_network_tag(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.43') + + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--nic', 'net-id=net1,tag=foo', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('nics', [ + { + 'net-id': 'net1', 'port-id': '', + 'v4-fixed-ip': '', 'v6-fixed-ip': '', + 'tag': 'foo', + }, + ]), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_network = mock.Mock() + network_client = self.app.client_manager.network + network_client.find_network = find_network + network_resource = mock.Mock(id='net1_uuid') + find_network.return_value = network_resource + + # Mock sdk APIs. + _network = mock.Mock(id='net1_uuid') + find_network = mock.Mock() + find_network.return_value = _network + self.app.client_manager.network.find_network = find_network + + # In base command class ShowOne in cliff, abstract method take_action() + # returns a two-part tuple with a tuple of column names and a tuple of + # data to be shown. + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = dict( + meta=None, + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + admin_pass=None, + block_device_mapping_v2=[], + nics=[ + { + 'net-id': 'net1_uuid', + 'v4-fixed-ip': '', + 'v6-fixed-ip': '', + 'port-id': '', + 'tag': 'foo', + }, + ], + scheduler_hints={}, + config_drive=None, + ) + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + + network_client.find_network.assert_called_once() + self.app.client_manager.network.find_network.assert_called_once() + + def test_server_create_with_network_tag_pre_v243(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.42') + + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--nic', 'net-id=net1,tag=foo', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('nics', [ + { + 'net-id': 'net1', 'port-id': '', + 'v4-fixed-ip': '', 'v6-fixed-ip': '', + 'tag': 'foo', + }, + ]), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, self.cmd.take_action, parsed_args) + def test_server_create_with_auto_network(self): arglist = [ '--image', 'image1', diff --git a/releasenotes/notes/add-server-nic-tagging-support-f77b4247e87771d5.yaml b/releasenotes/notes/add-server-nic-tagging-support-f77b4247e87771d5.yaml new file mode 100644 index 0000000000..e5f4fd5255 --- /dev/null +++ b/releasenotes/notes/add-server-nic-tagging-support-f77b4247e87771d5.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + The ``--nic`` option of the ``server create`` command now supports an + optional ``tag=`` key-value pair. This can be used to set a tag for + the interface in server metadata, which can be useful to maintain + persistent references to interfaces during various operations.