diff options
| author | Akihiro Motoki <amotoki@gmail.com> | 2017-04-29 00:32:32 +0000 |
|---|---|---|
| committer | Akihiro Motoki <amotoki@gmail.com> | 2017-07-23 21:54:32 +0000 |
| commit | 57e5840710c3b2b74d31bfd6a0da739e0fc747ed (patch) | |
| tree | 93fbb66cdc78f93062c4be7bddd8c8d8e45ff365 /openstackclient/network | |
| parent | e889ba1524c7e48a86ef41361a17cdb93b9942c2 (diff) | |
| download | python-openstackclient-57e5840710c3b2b74d31bfd6a0da739e0fc747ed.tar.gz | |
Network tag support
Neutron tag mechanism now supports network, subnet, port,
subnetpool and router. Tag support for more resources is planned.
This commit introduces a common mixin class to implement
tag operation and individual resource consumes it.
To support tag remove, network unset command is added.
Implements blueprint neutron-client-tag
Change-Id: Iad59d052f46896d27d73c22d6d4bb3df889f2352
Diffstat (limited to 'openstackclient/network')
| -rw-r--r-- | openstackclient/network/v2/_tag.py | 134 | ||||
| -rw-r--r-- | openstackclient/network/v2/network.py | 43 | ||||
| -rw-r--r-- | openstackclient/network/v2/port.py | 24 | ||||
| -rw-r--r-- | openstackclient/network/v2/router.py | 19 | ||||
| -rw-r--r-- | openstackclient/network/v2/subnet.py | 21 | ||||
| -rw-r--r-- | openstackclient/network/v2/subnet_pool.py | 21 |
6 files changed, 250 insertions, 12 deletions
diff --git a/openstackclient/network/v2/_tag.py b/openstackclient/network/v2/_tag.py new file mode 100644 index 00000000..d1e59937 --- /dev/null +++ b/openstackclient/network/v2/_tag.py @@ -0,0 +1,134 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import argparse + +from openstackclient.i18n import _ + + +class _CommaListAction(argparse.Action): + + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, values.split(',')) + + +def add_tag_filtering_option_to_parser(parser, collection_name): + parser.add_argument( + '--tags', + metavar='<tag>[,<tag>,...]', + action=_CommaListAction, + help=_('List %s which have all given tag(s) ' + '(Comma-separated list of tags)') % collection_name + ) + parser.add_argument( + '--any-tags', + metavar='<tag>[,<tag>,...]', + action=_CommaListAction, + help=_('List %s which have any given tag(s) ' + '(Comma-separated list of tags)') % collection_name + ) + parser.add_argument( + '--not-tags', + metavar='<tag>[,<tag>,...]', + action=_CommaListAction, + help=_('Exclude %s which have all given tag(s) ' + '(Comma-separated list of tags)') % collection_name + ) + parser.add_argument( + '--not-any-tags', + metavar='<tag>[,<tag>,...]', + action=_CommaListAction, + help=_('Exclude %s which have any given tag(s) ' + '(Comma-separated list of tags)') % collection_name + ) + + +def get_tag_filtering_args(parsed_args, args): + if parsed_args.tags: + args['tags'] = ','.join(parsed_args.tags) + if parsed_args.any_tags: + args['any_tags'] = ','.join(parsed_args.any_tags) + if parsed_args.not_tags: + args['not_tags'] = ','.join(parsed_args.not_tags) + if parsed_args.not_any_tags: + args['not_any_tags'] = ','.join(parsed_args.not_any_tags) + + +def add_tag_option_to_parser_for_create(parser, resource_name): + tag_group = parser.add_mutually_exclusive_group() + tag_group.add_argument( + '--tag', + action='append', + dest='tags', + metavar='<tag>', + help=_("Tag to be added to the %s " + "(repeat option to set multiple tags)") % resource_name + ) + tag_group.add_argument( + '--no-tag', + action='store_true', + help=_("No tags associated with the %s") % resource_name + ) + + +def add_tag_option_to_parser_for_set(parser, resource_name): + parser.add_argument( + '--tag', + action='append', + dest='tags', + metavar='<tag>', + help=_("Tag to be added to the %s " + "(repeat option to set multiple tags)") % resource_name + ) + parser.add_argument( + '--no-tag', + action='store_true', + help=_("Clear tags associated with the %s. Specify both " + "--tag and --no-tag to overwrite current tags") % resource_name + ) + + +def update_tags_for_set(client, obj, parsed_args): + if parsed_args.no_tag: + tags = set() + else: + tags = set(obj.tags) + if parsed_args.tags: + tags |= set(parsed_args.tags) + if set(obj.tags) != tags: + client.set_tags(obj, list(tags)) + + +def add_tag_option_to_parser_for_unset(parser, resource_name): + tag_group = parser.add_mutually_exclusive_group() + tag_group.add_argument( + '--tag', + action='append', + dest='tags', + metavar='<tag>', + help=_("Tag to be removed from the %s " + "(repeat option to remove multiple tags)") % resource_name) + tag_group.add_argument( + '--all-tag', + action='store_true', + help=_("Clear all tags associated with the %s") % resource_name) + + +def update_tags_for_unset(client, obj, parsed_args): + tags = set(obj.tags) + if parsed_args.all_tag: + tags = set() + if parsed_args.tags: + tags -= set(parsed_args.tags) + if set(obj.tags) != tags: + client.set_tags(obj, list(tags)) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 33decd82..4c1725c5 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -20,6 +20,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common from openstackclient.network import sdk_utils +from openstackclient.network.v2 import _tag def _format_admin_state(item): @@ -280,6 +281,7 @@ class CreateNetwork(common.NetworkAndComputeShowOne): help=_("Do not make the network VLAN transparent")) _add_additional_network_options(parser) + _tag.add_tag_option_to_parser_for_create(parser, _('network')) return parser def update_parser_compute(self, parser): @@ -299,6 +301,8 @@ class CreateNetwork(common.NetworkAndComputeShowOne): attrs['vlan_transparent'] = False obj = client.create_network(**attrs) + # tags cannot be set when created, so tags need to be set later. + _tag.update_tags_for_set(client, obj, parsed_args) display_columns, columns = _get_columns_network(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) @@ -424,7 +428,9 @@ class ListNetwork(common.NetworkAndComputeLister): '--agent', metavar='<agent-id>', dest='agent_id', - help=_('List networks hosted by agent (ID only)')) + help=_('List networks hosted by agent (ID only)') + ) + _tag.add_tag_filtering_option_to_parser(parser, _('networks')) return parser def take_action_network(self, client, parsed_args): @@ -441,6 +447,7 @@ class ListNetwork(common.NetworkAndComputeLister): 'provider_network_type', 'is_router_external', 'availability_zones', + 'tags', ) column_headers = ( 'ID', @@ -453,6 +460,7 @@ class ListNetwork(common.NetworkAndComputeLister): 'Network Type', 'Router Type', 'Availability Zones', + 'Tags', ) elif parsed_args.agent_id: columns = ( @@ -534,6 +542,8 @@ class ListNetwork(common.NetworkAndComputeLister): args['provider:segmentation_id'] = parsed_args.segmentation_id args['provider_segmentation_id'] = parsed_args.segmentation_id + _tag.get_tag_filtering_args(parsed_args, args) + data = client.networks(**args) return (column_headers, @@ -656,6 +666,7 @@ class SetNetwork(command.Command): action='store_true', help=_("Remove the QoS policy attached to this network") ) + _tag.add_tag_option_to_parser_for_set(parser, _('network')) _add_additional_network_options(parser) return parser @@ -664,7 +675,11 @@ class SetNetwork(command.Command): obj = client.find_network(parsed_args.network, ignore_missing=False) attrs = _get_attrs_network(self.app.client_manager, parsed_args) - client.update_network(obj, **attrs) + if attrs: + client.update_network(obj, **attrs) + + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_set(client, obj, parsed_args) class ShowNetwork(common.NetworkAndComputeShowOne): @@ -689,3 +704,27 @@ class ShowNetwork(common.NetworkAndComputeShowOne): display_columns, columns = _get_columns_compute(obj) data = utils.get_dict_properties(obj, columns) return (display_columns, data) + + +class UnsetNetwork(command.Command): + _description = _("Unset network properties") + + def get_parser(self, prog_name): + parser = super(UnsetNetwork, self).get_parser(prog_name) + parser.add_argument( + 'network', + metavar="<network>", + help=_("Network to modify (name or ID)") + ) + _tag.add_tag_option_to_parser_for_unset(parser, _('network')) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_network(parsed_args.network, ignore_missing=False) + + # NOTE: As of now, UnsetNetwork has no attributes which need + # to be updated by update_network(). + + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_unset(client, obj, parsed_args) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index d7f197e0..9536fe86 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -26,6 +26,7 @@ from osc_lib import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import sdk_utils +from openstackclient.network.v2 import _tag LOG = logging.getLogger(__name__) @@ -47,6 +48,7 @@ _formatters = { 'extra_dhcp_opts': utils.format_list_of_dicts, 'fixed_ips': utils.format_list_of_dicts, 'security_group_ids': utils.format_list, + 'tags': utils.format_list, } @@ -384,6 +386,7 @@ class CreatePort(command.ShowOne): "ip-address=<ip-address>[,mac-address=<mac-address>] " "(repeat option to set multiple allowed-address pairs)") ) + _tag.add_tag_option_to_parser_for_create(parser, _('port')) return parser def take_action(self, parsed_args): @@ -416,6 +419,8 @@ class CreatePort(command.ShowOne): attrs['qos_policy_id'] = client.find_qos_policy( parsed_args.qos_policy, ignore_missing=False).id obj = client.create_port(**attrs) + # tags cannot be set when created, so tags need to be set later. + _tag.update_tags_for_set(client, obj, parsed_args) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) @@ -512,6 +517,7 @@ class ListPort(command.Lister): "(name or ID): subnet=<subnet>,ip-address=<ip-address> " "(repeat option to set multiple fixed IP addresses)"), ) + _tag.add_tag_filtering_option_to_parser(parser, _('ports')) return parser def take_action(self, parsed_args): @@ -535,8 +541,8 @@ class ListPort(command.Lister): filters = {} if parsed_args.long: - columns += ('security_group_ids', 'device_owner',) - column_headers += ('Security Groups', 'Device Owner',) + columns += ('security_group_ids', 'device_owner', 'tags') + column_headers += ('Security Groups', 'Device Owner', 'Tags') if parsed_args.device_owner is not None: filters['device_owner'] = parsed_args.device_owner if parsed_args.router: @@ -566,6 +572,8 @@ class ListPort(command.Lister): filters['fixed_ips'] = _prepare_filter_fixed_ips( self.app.client_manager, parsed_args) + _tag.get_tag_filtering_args(parsed_args, filters) + data = network_client.ports(**filters) return (column_headers, @@ -694,6 +702,7 @@ class SetPort(command.Command): "Unset it to None with the 'port unset' command " "(requires data plane status extension)") ) + _tag.add_tag_option_to_parser_for_set(parser, _('port')) return parser @@ -750,7 +759,11 @@ class SetPort(command.Command): if parsed_args.data_plane_status: attrs['data_plane_status'] = parsed_args.data_plane_status - client.update_port(obj, **attrs) + if attrs: + client.update_port(obj, **attrs) + + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_set(client, obj, parsed_args) class ShowPort(command.ShowOne): @@ -834,6 +847,8 @@ class UnsetPort(command.Command): help=_("Clear existing information of data plane status") ) + _tag.add_tag_option_to_parser_for_unset(parser, _('port')) + return parser def take_action(self, parsed_args): @@ -889,3 +904,6 @@ class UnsetPort(command.Command): if attrs: client.update_port(obj, **attrs) + + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_unset(client, obj, parsed_args) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 8db0c439..4f908537 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -26,6 +26,7 @@ from osc_lib import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import sdk_utils +from openstackclient.network.v2 import _tag LOG = logging.getLogger(__name__) @@ -57,6 +58,7 @@ _formatters = { 'availability_zones': utils.format_list, 'availability_zone_hints': utils.format_list, 'routes': _format_routes, + 'tags': utils.format_list, } @@ -217,6 +219,7 @@ class CreateRouter(command.ShowOne): "(Router Availability Zone extension required, " "repeat option to set multiple availability zones)") ) + _tag.add_tag_option_to_parser_for_create(parser, _('router')) return parser @@ -229,6 +232,8 @@ class CreateRouter(command.ShowOne): if parsed_args.no_ha: attrs['ha'] = False obj = client.create_router(**attrs) + # tags cannot be set when created, so tags need to be set later. + _tag.update_tags_for_set(client, obj, parsed_args) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) @@ -310,6 +315,7 @@ class ListRouter(command.Lister): metavar='<agent-id>', help=_("List routers hosted by an agent (ID only)") ) + _tag.add_tag_filtering_option_to_parser(parser, _('routers')) return parser @@ -357,6 +363,8 @@ class ListRouter(command.Lister): args['tenant_id'] = project_id args['project_id'] = project_id + _tag.get_tag_filtering_args(parsed_args, args) + if parsed_args.agent is not None: agent = client.get_agent(parsed_args.agent) data = client.agent_hosted_routers(agent) @@ -384,6 +392,8 @@ class ListRouter(command.Lister): column_headers = column_headers + ( 'Availability zones', ) + columns = columns + ('tags',) + column_headers = column_headers + ('Tags',) return (column_headers, (utils.get_item_properties( @@ -567,6 +577,7 @@ class SetRouter(command.Command): action='store_true', help=_("Disable Source NAT on external gateway") ) + _tag.add_tag_option_to_parser_for_set(parser, _('router')) return parser def take_action(self, parsed_args): @@ -625,7 +636,10 @@ class SetRouter(command.Command): ips.append(ip_spec) gateway_info['external_fixed_ips'] = ips attrs['external_gateway_info'] = gateway_info - client.update_router(obj, **attrs) + if attrs: + client.update_router(obj, **attrs) + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_set(client, obj, parsed_args) class ShowRouter(command.ShowOne): @@ -675,6 +689,7 @@ class UnsetRouter(command.Command): metavar="<router>", help=_("Router to modify (name or ID)") ) + _tag.add_tag_option_to_parser_for_unset(parser, _('router')) return parser def take_action(self, parsed_args): @@ -695,3 +710,5 @@ class UnsetRouter(command.Command): attrs['external_gateway_info'] = {} if attrs: client.update_router(obj, **attrs) + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_unset(client, obj, parsed_args) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 2fdd11f0..b96dff7f 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -24,6 +24,7 @@ from osc_lib import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import sdk_utils +from openstackclient.network.v2 import _tag LOG = logging.getLogger(__name__) @@ -55,6 +56,7 @@ _formatters = { 'dns_nameservers': utils.format_list, 'host_routes': _format_host_routes, 'service_types': utils.format_list, + 'tags': utils.format_list, } @@ -336,12 +338,15 @@ class CreateSubnet(command.ShowOne): help=_("Set subnet description") ) _get_common_parse_arguments(parser) + _tag.add_tag_option_to_parser_for_create(parser, _('subnet')) return parser def take_action(self, parsed_args): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_subnet(**attrs) + # tags cannot be set when created, so tags need to be set later. + _tag.update_tags_for_set(client, obj, parsed_args) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) @@ -454,6 +459,7 @@ class ListSubnet(command.Lister): "(in CIDR notation) in output " "e.g.: --subnet-range 10.10.0.0/16") ) + _tag.add_tag_filtering_option_to_parser(parser, _('subnets')) return parser def take_action(self, parsed_args): @@ -488,6 +494,7 @@ class ListSubnet(command.Lister): filters['name'] = parsed_args.name if parsed_args.subnet_range: filters['cidr'] = parsed_args.subnet_range + _tag.get_tag_filtering_args(parsed_args, filters) data = network_client.subnets(**filters) headers = ('ID', 'Name', 'Network', 'Subnet') @@ -495,10 +502,10 @@ class ListSubnet(command.Lister): if parsed_args.long: headers += ('Project', 'DHCP', 'Name Servers', 'Allocation Pools', 'Host Routes', 'IP Version', - 'Gateway', 'Service Types') + 'Gateway', 'Service Types', 'Tags') columns += ('project_id', 'is_dhcp_enabled', 'dns_nameservers', 'allocation_pools', 'host_routes', 'ip_version', - 'gateway_ip', 'service_types') + 'gateway_ip', 'service_types', 'tags') return (headers, (utils.get_item_properties( @@ -549,6 +556,7 @@ class SetSubnet(command.Command): metavar='<description>', help=_("Set subnet description") ) + _tag.add_tag_option_to_parser_for_set(parser, _('subnet')) _get_common_parse_arguments(parser, is_create=False) return parser @@ -574,7 +582,10 @@ class SetSubnet(command.Command): attrs['allocation_pools'] = [] if 'service_types' in attrs: attrs['service_types'] += obj.service_types - client.update_subnet(obj, **attrs) + if attrs: + client.update_subnet(obj, **attrs) + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_set(client, obj, parsed_args) return @@ -643,6 +654,7 @@ class UnsetSubnet(command.Command): 'Must be a valid device owner value for a network port ' '(repeat option to unset multiple service types)') ) + _tag.add_tag_option_to_parser_for_unset(parser, _('subnet')) parser.add_argument( 'subnet', metavar="<subnet>", @@ -678,3 +690,6 @@ class UnsetSubnet(command.Command): attrs['service_types'] = tmp_obj.service_types if attrs: client.update_subnet(obj, **attrs) + + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_unset(client, obj, parsed_args) diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index b72a74fc..a5839868 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -24,6 +24,7 @@ from osc_lib import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import sdk_utils +from openstackclient.network.v2 import _tag LOG = logging.getLogger(__name__) @@ -42,6 +43,7 @@ def _get_columns(item): _formatters = { 'prefixes': utils.format_list, + 'tags': utils.format_list, } @@ -191,6 +193,7 @@ class CreateSubnetPool(command.ShowOne): metavar='<num-ip-addresses>', help=_("Set default quota for subnet pool as the number of" "IP addresses allowed in a subnet")), + _tag.add_tag_option_to_parser_for_create(parser, _('subnet pool')) return parser def take_action(self, parsed_args): @@ -200,6 +203,8 @@ class CreateSubnetPool(command.ShowOne): if "prefixes" not in attrs: attrs['prefixes'] = [] obj = client.create_subnet_pool(**attrs) + # tags cannot be set when created, so tags need to be set later. + _tag.update_tags_for_set(client, obj, parsed_args) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) @@ -293,6 +298,7 @@ class ListSubnetPool(command.Lister): help=_("List only subnet pools of given address scope " "in output (name or ID)") ) + _tag.add_tag_filtering_option_to_parser(parser, _('subnet pools')) return parser def take_action(self, parsed_args): @@ -324,15 +330,16 @@ class ListSubnetPool(command.Lister): parsed_args.address_scope, ignore_missing=False) filters['address_scope_id'] = address_scope.id + _tag.get_tag_filtering_args(parsed_args, filters) data = network_client.subnet_pools(**filters) headers = ('ID', 'Name', 'Prefixes') columns = ('id', 'name', 'prefixes') if parsed_args.long: headers += ('Default Prefix Length', 'Address Scope', - 'Default Subnet Pool', 'Shared') + 'Default Subnet Pool', 'Shared', 'Tags') columns += ('default_prefix_length', 'address_scope_id', - 'is_default', 'is_shared') + 'is_default', 'is_shared', 'tags') return (headers, (utils.get_item_properties( @@ -384,6 +391,8 @@ class SetSubnetPool(command.Command): metavar='<num-ip-addresses>', help=_("Set default quota for subnet pool as the number of" "IP addresses allowed in a subnet")), + _tag.add_tag_option_to_parser_for_set(parser, _('subnet pool')) + return parser def take_action(self, parsed_args): @@ -397,7 +406,10 @@ class SetSubnetPool(command.Command): if 'prefixes' in attrs: attrs['prefixes'].extend(obj.prefixes) - client.update_subnet_pool(obj, **attrs) + if attrs: + client.update_subnet_pool(obj, **attrs) + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_set(client, obj, parsed_args) class ShowSubnetPool(command.ShowOne): @@ -441,6 +453,7 @@ class UnsetSubnetPool(command.Command): metavar="<subnet-pool>", help=_("Subnet pool to modify (name or ID)") ) + _tag.add_tag_option_to_parser_for_unset(parser, _('subnet pool')) return parser def take_action(self, parsed_args): @@ -461,3 +474,5 @@ class UnsetSubnetPool(command.Command): attrs['prefixes'] = tmp_prefixes if attrs: client.update_subnet_pool(obj, **attrs) + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_unset(client, obj, parsed_args) |
