diff --git a/manilaclient/api_versions.py b/manilaclient/api_versions.py index a42206592..777506da2 100644 --- a/manilaclient/api_versions.py +++ b/manilaclient/api_versions.py @@ -27,7 +27,7 @@ from manilaclient import utils LOG = logging.getLogger(__name__) -MAX_VERSION = '2.77' +MAX_VERSION = '2.78' MIN_VERSION = '2.0' DEPRECATED_VERSION = '1.0' _VERSIONED_METHOD_MAP = {} diff --git a/manilaclient/osc/v2/share_network_subnets.py b/manilaclient/osc/v2/share_network_subnets.py index 8500615cc..c81763461 100644 --- a/manilaclient/osc/v2/share_network_subnets.py +++ b/manilaclient/osc/v2/share_network_subnets.py @@ -13,6 +13,8 @@ import logging from operator import xor +from osc_lib.cli import format_columns +from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils as oscutils @@ -75,6 +77,15 @@ class CreateShareNetworkSubnet(command.ShowOne): "Helpful when check results are stale. " "Available only for microversion >= 2.70.") ) + parser.add_argument( + "--property", + metavar="", + default={}, + action=parseractions.KeyValueAction, + help=_("Set a property to this share network subnet " + "(repeat option to set multiple properties). " + "Available only for microversion >= 2.78."), + ) return parser def take_action(self, parsed_args): @@ -92,6 +103,12 @@ class CreateShareNetworkSubnet(command.ShowOne): "Restart check can be specified only with manila API " "version >= 2.70.") + if (parsed_args.property and + share_client.api_version < api_versions.APIVersion("2.78")): + raise exceptions.CommandError( + "Property can be specified only with manila API " + "version >= 2.78.") + if xor(bool(parsed_args.neutron_net_id), bool(parsed_args.neutron_subnet_id)): raise exceptions.CommandError( @@ -104,6 +121,11 @@ class CreateShareNetworkSubnet(command.ShowOne): parsed_args.share_network).id if parsed_args.check_only or parsed_args.restart_check: + + if parsed_args.property: + raise exceptions.CommandError( + "Property cannot be specified with check operation.") + subnet_create_check = ( share_client.share_networks.share_network_subnet_create_check( neutron_net_id=parsed_args.neutron_net_id, @@ -118,7 +140,8 @@ class CreateShareNetworkSubnet(command.ShowOne): neutron_net_id=parsed_args.neutron_net_id, neutron_subnet_id=parsed_args.neutron_subnet_id, availability_zone=parsed_args.availability_zone, - share_network_id=share_network_id + share_network_id=share_network_id, + metadata=parsed_args.property ) subnet_data = share_network_subnet._info @@ -197,5 +220,123 @@ class ShowShareNetworkSubnet(command.ShowOne): share_network_subnet = share_client.share_network_subnets.get( share_network_id, parsed_args.share_network_subnet) + data = share_network_subnet._info - return self.dict2columns(share_network_subnet._info) + # Special mapping for columns to make the output easier to read: + # 'metadata' --> 'properties' + data.update( + { + 'properties': + format_columns.DictColumn(data.pop('metadata', {})), + }, + ) + + return self.dict2columns(data) + + +class SetShareNetworkSubnet(command.Command): + """Set share network subnet properties.""" + _description = _("Set share network subnet properties") + + def get_parser(self, prog_name): + parser = super(SetShareNetworkSubnet, self).get_parser(prog_name) + parser.add_argument( + "share_network", + metavar="", + help=_("Share network name or ID.") + ) + parser.add_argument( + "share_network_subnet", + metavar="", + help=_("ID of share network subnet to set a property.") + ) + parser.add_argument( + "--property", + metavar="", + default={}, + action=parseractions.KeyValueAction, + help=_("Set a property to this share network subnet " + "(repeat option to set multiple properties). " + "Available only for microversion >= 2.78."), + ) + return parser + + def take_action(self, parsed_args): + share_client = self.app.client_manager.share + + if (parsed_args.property and + share_client.api_version < api_versions.APIVersion("2.78")): + raise exceptions.CommandError( + "Property can be specified only with manila API " + "version >= 2.78.") + + share_network_id = oscutils.find_resource( + share_client.share_networks, + parsed_args.share_network).id + + if parsed_args.property: + try: + share_client.share_network_subnets.set_metadata( + share_network_id, parsed_args.property, + subresource=parsed_args.share_network_subnet) + except Exception as e: + raise exceptions.CommandError(_( + "Failed to set subnet property '%(properties)s': %(e)s") % + {'properties': parsed_args.property, 'e': e}) + + +class UnsetShareNetworkSubnet(command.Command): + """Unset a share network subnet property.""" + _description = _("Unset a share network subnet property") + + def get_parser(self, prog_name): + parser = super(UnsetShareNetworkSubnet, self).get_parser(prog_name) + parser.add_argument( + "share_network", + metavar="", + help=_("Share network name or ID.") + ) + parser.add_argument( + "share_network_subnet", + metavar="", + help=_("ID of share network subnet to set a property.") + ) + parser.add_argument( + '--property', + metavar='', + action='append', + help=_("Remove a property from share network subnet " + "(repeat option to remove multiple properties). " + "Available only for microversion >= 2.78."), + ) + return parser + + def take_action(self, parsed_args): + share_client = self.app.client_manager.share + + if (parsed_args.property and + share_client.api_version < api_versions.APIVersion("2.78")): + raise exceptions.CommandError( + "Property can be specified only with manila API " + "version >= 2.78.") + + share_network_id = oscutils.find_resource( + share_client.share_networks, + parsed_args.share_network).id + + if parsed_args.property: + result = 0 + for key in parsed_args.property: + try: + share_client.share_network_subnets.delete_metadata( + share_network_id, [key], + subresource=parsed_args.share_network_subnet) + except Exception as e: + result += 1 + LOG.error("Failed to unset subnet property " + "'%(key)s': %(e)s", {'key': key, 'e': e}) + if result > 0: + total = len(parsed_args.property) + raise exceptions.CommandError( + f"{result} of {total} subnet properties failed to be " + f"unset.") diff --git a/manilaclient/osc/v2/share_networks.py b/manilaclient/osc/v2/share_networks.py index ef15d1a8c..556f2ac48 100644 --- a/manilaclient/osc/v2/share_networks.py +++ b/manilaclient/osc/v2/share_networks.py @@ -15,6 +15,7 @@ import logging from openstackclient.identity import common as identity_common +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils as oscutils @@ -211,6 +212,16 @@ class ShowShareNetwork(command.ShowOne): data = share_network._info + # Special mapping for columns to make the output easier to read: + # 'metadata' --> 'properties' + for ss in data['share_network_subnets']: + ss.update( + { + 'properties': + format_columns.DictColumn(ss.pop('metadata', {})), + }, + ) + # Add security services information security_services = share_client.security_services.list( search_opts={'share_network_id': share_network.id}, detailed=False) diff --git a/manilaclient/tests/unit/osc/v2/fakes.py b/manilaclient/tests/unit/osc/v2/fakes.py index b436acfae..967105fcc 100644 --- a/manilaclient/tests/unit/osc/v2/fakes.py +++ b/manilaclient/tests/unit/osc/v2/fakes.py @@ -1251,6 +1251,7 @@ class FakeShareNetworkSubnet(object): "share_network_id": str(uuid.uuid4()), "share_network_name": str(uuid.uuid4()), "updated_at": datetime.datetime.now().isoformat(), + "properties": {}, } share_network_subnet.update(attrs) diff --git a/manilaclient/tests/unit/osc/v2/test_share_network_subnets.py b/manilaclient/tests/unit/osc/v2/test_share_network_subnets.py index 2b2062e6e..3fba08e4a 100644 --- a/manilaclient/tests/unit/osc/v2/test_share_network_subnets.py +++ b/manilaclient/tests/unit/osc/v2/test_share_network_subnets.py @@ -86,7 +86,8 @@ class TestShareNetworkSubnetCreate(TestShareNetworkSubnet): neutron_net_id=fake_neutron_net_id, neutron_subnet_id=fake_neutron_subnet_id, availability_zone='nova', - share_network_id=self.share_network.id + share_network_id=self.share_network.id, + metadata={} ) self.assertCountEqual(self.columns, columns) @@ -163,6 +164,51 @@ class TestShareNetworkSubnetCreate(TestShareNetworkSubnet): neutron_subnet_id=None, availability_zone=None, reset_operation=restart_check)) + def test_share_network_subnet_create_metadata(self): + self.app.client_manager.share.api_version = api_versions.APIVersion( + '2.78' + ) + arglist = [ + self.share_network.id, + '--property', 'Manila=zorilla', + '--property', 'Zorilla=manila' + ] + verifylist = [ + ('share_network', self.share_network.id), + ('property', {'Manila': 'zorilla', 'Zorilla': 'manila'}), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.share_subnets_mock.create.assert_called_once_with( + neutron_net_id=None, + neutron_subnet_id=None, + availability_zone=None, + share_network_id=self.share_network.id, + metadata={'Manila': 'zorilla', 'Zorilla': 'manila'}, + ) + self.assertEqual(set(self.columns), set(columns)) + self.assertCountEqual(self.data, data) + + def test_share_network_subnet_create_metadata_api_version_exception(self): + self.app.client_manager.share.api_version = api_versions.APIVersion( + '2.77' + ) + arglist = [ + self.share_network.id, + '--property', 'Manila=zorilla', + ] + verifylist = [ + ('share_network', self.share_network.id), + ('property', {'Manila': 'zorilla'}) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, self.cmd.take_action, parsed_args) + class TestShareNetworkSubnetDelete(TestShareNetworkSubnet): @@ -269,3 +315,142 @@ class TestShareNetworkSubnetShow(TestShareNetworkSubnet): self.assertCountEqual(self.columns, columns) self.assertCountEqual(self.data, data) + + +class TestShareNetworkSubnetSet(TestShareNetworkSubnet): + + def setUp(self): + super(TestShareNetworkSubnetSet, self).setUp() + + self.share_network = ( + manila_fakes.FakeShareNetwork.create_one_share_network()) + self.share_networks_mock.get.return_value = self.share_network + + self.share_network_subnet = ( + manila_fakes.FakeShareNetworkSubnet.create_one_share_subnet()) + + self.cmd = osc_share_subnets.SetShareNetworkSubnet( + self.app, None) + + def test_set_share_network_subnet_property(self): + self.app.client_manager.share.api_version = api_versions.APIVersion( + '2.78' + ) + arglist = [ + self.share_network.id, + self.share_network_subnet.id, + '--property', 'Zorilla=manila', + '--property', 'test=my_test', + ] + verifylist = [ + ('share_network', self.share_network.id), + ('share_network_subnet', self.share_network_subnet.id), + ('property', {'Zorilla': 'manila', 'test': 'my_test'}), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.share_subnets_mock.set_metadata.assert_called_once_with( + self.share_network.id, {'Zorilla': 'manila', 'test': 'my_test'}, + subresource=self.share_network_subnet.id) + + def test_set_share_network_subnet_property_exception(self): + self.app.client_manager.share.api_version = api_versions.APIVersion( + '2.78' + ) + arglist = [ + self.share_network.id, + self.share_network_subnet.id, + '--property', 'key=1', + ] + verifylist = [ + ('share_network', self.share_network.id), + ('share_network_subnet', self.share_network_subnet.id), + ('property', {'key': '1'}), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.share_subnets_mock.set_metadata.assert_called_once_with( + self.share_network.id, {'key': '1'}, + subresource=self.share_network_subnet.id) + + self.share_subnets_mock.set_metadata.side_effect = ( + exceptions.BadRequest) + self.assertRaises( + exceptions.CommandError, self.cmd.take_action, + parsed_args) + + +class TestShareNetworkSubnetUnset(TestShareNetworkSubnet): + + def setUp(self): + super(TestShareNetworkSubnetUnset, self).setUp() + + self.share_network = ( + manila_fakes.FakeShareNetwork.create_one_share_network()) + self.share_networks_mock.get.return_value = self.share_network + + self.share_network_subnet = ( + manila_fakes.FakeShareNetworkSubnet.create_one_share_subnet()) + + self.cmd = osc_share_subnets.UnsetShareNetworkSubnet( + self.app, None) + + def test_unset_share_network_subnet_property(self): + self.app.client_manager.share.api_version = api_versions.APIVersion( + '2.78' + ) + arglist = [ + self.share_network.id, + self.share_network_subnet.id, + '--property', 'Manila', + ] + verifylist = [ + ('share_network', self.share_network.id), + ('share_network_subnet', self.share_network_subnet.id), + ('property', ['Manila']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.share_subnets_mock.delete_metadata.assert_called_once_with( + self.share_network.id, ['Manila'], + subresource=self.share_network_subnet.id) + + def test_unset_share_network_subnet_property_exception(self): + self.app.client_manager.share.api_version = api_versions.APIVersion( + '2.78' + ) + arglist = [ + self.share_network.id, + self.share_network_subnet.id, + '--property', 'Manila', + '--property', 'test', + ] + verifylist = [ + ('share_network', self.share_network.id), + ('share_network_subnet', self.share_network_subnet.id), + ('property', ['Manila', 'test']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.share_subnets_mock.delete_metadata.assert_has_calls([ + mock.call(self.share_network.id, ['Manila'], + subresource=self.share_network_subnet.id), + mock.call(self.share_network.id, ['test'], + subresource=self.share_network_subnet.id)]) + + # 404 Not Found would be raised, if property 'Manila' doesn't exist. + self.share_subnets_mock.delete_metadata.side_effect = ( + exceptions.NotFound) + self.assertRaises( + exceptions.CommandError, self.cmd.take_action, parsed_args) diff --git a/manilaclient/tests/unit/v2/test_share_network_subnets.py b/manilaclient/tests/unit/v2/test_share_network_subnets.py index e0155a1d2..0dae0d5e2 100644 --- a/manilaclient/tests/unit/v2/test_share_network_subnets.py +++ b/manilaclient/tests/unit/v2/test_share_network_subnets.py @@ -39,6 +39,7 @@ class ShareNetworkSubnetTest(utils.TestCase): 'neutron_net_id': 'fake_net_id', 'neutron_subnet_id': 'fake_subnet_id', 'availability_zone': 'fake_availability_zone', + 'metadata': 'fake_metadata' } expected_body = {'share-network-subnet': expected_values} payload = expected_values.copy() diff --git a/manilaclient/v2/share_network_subnets.py b/manilaclient/v2/share_network_subnets.py index b6fe1a5c6..d1679e94e 100644 --- a/manilaclient/v2/share_network_subnets.py +++ b/manilaclient/v2/share_network_subnets.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +from manilaclient import api_versions from manilaclient import base RESOURCES_PATH = '/share-networks/%(share_network_id)s/subnets' @@ -20,7 +21,7 @@ RESOURCE_PATH = RESOURCES_PATH + '/%(share_network_subnet_id)s' RESOURCE_NAME = 'share_network_subnet' -class ShareNetworkSubnet(base.Resource): +class ShareNetworkSubnet(base.MetadataCapableResource): """Network subnet info for Manila share networks.""" def __repr__(self): return "" % self.id @@ -33,18 +34,22 @@ class ShareNetworkSubnet(base.Resource): self.manager.delete(self) -class ShareNetworkSubnetManager(base.ManagerWithFind): +class ShareNetworkSubnetManager(base.MetadataCapableManager): """Manage :class:`ShareNetworkSubnet` resources.""" resource_class = ShareNetworkSubnet + resource_path = '/share-networks' + subresource_path = '/subnets' - def create(self, neutron_net_id=None, neutron_subnet_id=None, - availability_zone=None, share_network_id=None): + def _do_create(self, neutron_net_id=None, neutron_subnet_id=None, + availability_zone=None, share_network_id=None, + metadata=None): """Create share network subnet. :param neutron_net_id: ID of Neutron network :param neutron_subnet_id: ID of Neutron subnet :param availability_zone: Name of the target availability zone + :param metadata: dict - optional metadata to set on share creation :rtype: :class:`ShareNetworkSubnet` """ values = {} @@ -54,6 +59,8 @@ class ShareNetworkSubnetManager(base.ManagerWithFind): values['neutron_subnet_id'] = neutron_subnet_id if availability_zone: values['availability_zone'] = availability_zone + if metadata: + values['metadata'] = metadata body = {'share-network-subnet': values} url = '/share-networks/%(share_network_id)s/subnets' % { @@ -62,6 +69,18 @@ class ShareNetworkSubnetManager(base.ManagerWithFind): return self._create(url, body, RESOURCE_NAME) + @api_versions.wraps("2.0", "2.77") + def create(self, neutron_net_id=None, neutron_subnet_id=None, + availability_zone=None, share_network_id=None): + return self._do_create(neutron_net_id, neutron_subnet_id, + availability_zone, share_network_id) + + @api_versions.wraps("2.78") + def create(self, neutron_net_id=None, neutron_subnet_id=None, # noqa F811 + availability_zone=None, share_network_id=None, metadata=None): + return self._do_create(neutron_net_id, neutron_subnet_id, + availability_zone, share_network_id, metadata) + def get(self, share_network, share_network_subnet): """Get a share network subnet. @@ -89,3 +108,23 @@ class ShareNetworkSubnetManager(base.ManagerWithFind): 'share_network_subnet': share_network_subnet } self._delete(url) + + @api_versions.wraps('2.78') + def get_metadata(self, share_network, share_network_subnet): + return super(ShareNetworkSubnetManager, self).get_metadata( + share_network, subresource=share_network_subnet) + + @api_versions.wraps('2.78') + def set_metadata(self, resource, metadata, subresource=None): + return super(ShareNetworkSubnetManager, self).set_metadata( + resource, metadata, subresource=subresource) + + @api_versions.wraps('2.78') + def delete_metadata(self, resource, keys, subresource=None): + return super(ShareNetworkSubnetManager, self).delete_metadata( + resource, keys, subresource=subresource) + + @api_versions.wraps('2.78') + def update_all_metadata(self, resource, metadata, subresource=None): + return super(ShareNetworkSubnetManager, self).update_all_metadata( + resource, metadata, subresource=subresource) diff --git a/releasenotes/notes/add-subnet-metadata-82426986431b0179.yaml b/releasenotes/notes/add-subnet-metadata-82426986431b0179.yaml new file mode 100644 index 000000000..009f1287e --- /dev/null +++ b/releasenotes/notes/add-subnet-metadata-82426986431b0179.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Adds support to create share network subnet with properties and update + the subnet properties with set and unset command (only with the OpenStackClient). diff --git a/setup.cfg b/setup.cfg index f12afb19d..fb55011d7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -127,6 +127,8 @@ openstack.share.v2 = share_network_subnet_create = manilaclient.osc.v2.share_network_subnets:CreateShareNetworkSubnet share_network_subnet_delete = manilaclient.osc.v2.share_network_subnets:DeleteShareNetworkSubnet share_network_subnet_show = manilaclient.osc.v2.share_network_subnets:ShowShareNetworkSubnet + share_network_subnet_set = manilaclient.osc.v2.share_network_subnets:SetShareNetworkSubnet + share_network_subnet_unset = manilaclient.osc.v2.share_network_subnets:UnsetShareNetworkSubnet share_group_create = manilaclient.osc.v2.share_groups:CreateShareGroup share_group_delete = manilaclient.osc.v2.share_groups:DeleteShareGroup share_group_list = manilaclient.osc.v2.share_groups:ListShareGroup