diff options
Diffstat (limited to 'openstackclient/network')
| -rw-r--r-- | openstackclient/network/common.py | 164 | ||||
| -rw-r--r-- | openstackclient/network/v2/floating_ip.py | 66 | ||||
| -rw-r--r-- | openstackclient/network/v2/network.py | 133 | ||||
| -rw-r--r-- | openstackclient/network/v2/port.py | 61 | ||||
| -rw-r--r-- | openstackclient/network/v2/router.py | 46 | ||||
| -rw-r--r-- | openstackclient/network/v2/security_group.py | 40 | ||||
| -rw-r--r-- | openstackclient/network/v2/subnet.py | 63 |
7 files changed, 483 insertions, 90 deletions
diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py new file mode 100644 index 00000000..cc343c3c --- /dev/null +++ b/openstackclient/network/common.py @@ -0,0 +1,164 @@ +# 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 abc +import six + +from openstackclient.common import command + + +@six.add_metaclass(abc.ABCMeta) +class NetworkAndComputeCommand(command.Command): + """Network and Compute Command""" + + def take_action(self, parsed_args): + if self.app.client_manager.is_network_endpoint_enabled(): + return self.take_action_network(self.app.client_manager.network, + parsed_args) + else: + return self.take_action_compute(self.app.client_manager.compute, + parsed_args) + + def get_parser(self, prog_name): + self.log.debug('get_parser(%s)', prog_name) + parser = super(NetworkAndComputeCommand, self).get_parser(prog_name) + parser = self.update_parser_common(parser) + self.log.debug('common parser: %s', parser) + if self.app.client_manager.is_network_endpoint_enabled(): + return self.update_parser_network(parser) + else: + return self.update_parser_compute(parser) + + def update_parser_common(self, parser): + """Default is no updates to parser.""" + return parser + + def update_parser_network(self, parser): + """Default is no updates to parser.""" + return parser + + def update_parser_compute(self, parser): + """Default is no updates to parser.""" + return parser + + @abc.abstractmethod + def take_action_network(self, client, parsed_args): + """Override to do something useful.""" + pass + + @abc.abstractmethod + def take_action_compute(self, client, parsed_args): + """Override to do something useful.""" + pass + + +@six.add_metaclass(abc.ABCMeta) +class NetworkAndComputeLister(command.Lister): + """Network and Compute Lister + + Lister class for commands that support implementation via + the network or compute endpoint. Such commands have different + implementations for take_action() and may even have different + arguments. + """ + + def take_action(self, parsed_args): + if self.app.client_manager.is_network_endpoint_enabled(): + return self.take_action_network(self.app.client_manager.network, + parsed_args) + else: + return self.take_action_compute(self.app.client_manager.compute, + parsed_args) + + def get_parser(self, prog_name): + self.log.debug('get_parser(%s)', prog_name) + parser = super(NetworkAndComputeLister, self).get_parser(prog_name) + parser = self.update_parser_common(parser) + self.log.debug('common parser: %s', parser) + if self.app.client_manager.is_network_endpoint_enabled(): + return self.update_parser_network(parser) + else: + return self.update_parser_compute(parser) + + def update_parser_common(self, parser): + """Default is no updates to parser.""" + return parser + + def update_parser_network(self, parser): + """Default is no updates to parser.""" + return parser + + def update_parser_compute(self, parser): + """Default is no updates to parser.""" + return parser + + @abc.abstractmethod + def take_action_network(self, client, parsed_args): + """Override to do something useful.""" + pass + + @abc.abstractmethod + def take_action_compute(self, client, parsed_args): + """Override to do something useful.""" + pass + + +@six.add_metaclass(abc.ABCMeta) +class NetworkAndComputeShowOne(command.ShowOne): + """Network and Compute ShowOne + + ShowOne class for commands that support implementation via + the network or compute endpoint. Such commands have different + implementations for take_action() and may even have different + arguments. + """ + + def take_action(self, parsed_args): + if self.app.client_manager.is_network_endpoint_enabled(): + return self.take_action_network(self.app.client_manager.network, + parsed_args) + else: + return self.take_action_compute(self.app.client_manager.compute, + parsed_args) + + def get_parser(self, prog_name): + self.log.debug('get_parser(%s)', prog_name) + parser = super(NetworkAndComputeShowOne, self).get_parser(prog_name) + parser = self.update_parser_common(parser) + self.log.debug('common parser: %s', parser) + if self.app.client_manager.is_network_endpoint_enabled(): + return self.update_parser_network(parser) + else: + return self.update_parser_compute(parser) + + def update_parser_common(self, parser): + """Default is no updates to parser.""" + return parser + + def update_parser_network(self, parser): + """Default is no updates to parser.""" + return parser + + def update_parser_compute(self, parser): + """Default is no updates to parser.""" + return parser + + @abc.abstractmethod + def take_action_network(self, client, parsed_args): + """Override to do something useful.""" + pass + + @abc.abstractmethod + def take_action_compute(self, client, parsed_args): + """Override to do something useful.""" + pass diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py new file mode 100644 index 00000000..48895048 --- /dev/null +++ b/openstackclient/network/v2/floating_ip.py @@ -0,0 +1,66 @@ +# 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. +# + +"""IP Floating action implementations""" + +from openstackclient.common import utils +from openstackclient.network import common + + +class DeleteFloatingIP(common.NetworkAndComputeCommand): + """Delete floating IP""" + + def update_parser_common(self, parser): + parser.add_argument( + 'floating_ip', + metavar="<floating-ip>", + help=("Floating IP to delete (IP address or ID)") + ) + return parser + + def take_action_network(self, client, parsed_args): + obj = client.find_ip(parsed_args.floating_ip) + client.delete_ip(obj) + + def take_action_compute(self, client, parsed_args): + obj = utils.find_resource( + client.floating_ips, + parsed_args.floating_ip, + ) + client.floating_ips.delete(obj.id) + + +class ListFloatingIP(common.NetworkAndComputeLister): + """List floating IP(s)""" + + columns = ('ID', 'IP', 'Fixed IP', 'Instance ID', 'Pool') + column_headers = ('ID', 'Floating IP', 'Fixed IP', 'Server ID', 'Pool') + + def take_action_network(self, client, parsed_args): + query = {} + data = client.ips(**query) + + return (self.column_headers, + (utils.get_item_properties( + s, self.columns, + formatters={}, + ) for s in data)) + + def take_action_compute(self, client, parsed_args): + data = client.floating_ips.list() + + return (self.column_headers, + (utils.get_item_properties( + s, self.columns, + formatters={}, + ) for s in data)) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 38dff8d9..636c333e 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -13,15 +13,11 @@ """Network action implementations""" -import logging - -from cliff import command -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.identity import common as identity_common +from openstackclient.network import common def _format_admin_state(item): @@ -36,6 +32,8 @@ _formatters = { 'subnets': utils.format_list, 'admin_state_up': _format_admin_state, 'router_external': _format_router_external, + 'availability_zones': utils.format_list, + 'availability_zone_hints': utils.format_list, } @@ -50,10 +48,35 @@ def _get_columns(item): return tuple(sorted(columns)) -class CreateNetwork(show.ShowOne): - """Create new network""" +def _get_attrs(client_manager, parsed_args): + attrs = {} + if parsed_args.name is not None: + attrs['name'] = str(parsed_args.name) + if parsed_args.admin_state is not None: + attrs['admin_state_up'] = parsed_args.admin_state + if parsed_args.shared is not None: + attrs['shared'] = parsed_args.shared + + # "network set" command doesn't support setting project. + if 'project' in parsed_args and parsed_args.project is not None: + identity_client = client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + attrs['tenant_id'] = project_id + + # "network set" command doesn't support setting availability zone hints. + if 'availability_zone_hints' in parsed_args and \ + parsed_args.availability_zone_hints is not None: + attrs['availability_zone_hints'] = parsed_args.availability_zone_hints + + return attrs + - log = logging.getLogger(__name__ + '.CreateNetwork') +class CreateNetwork(command.ShowOne): + """Create new network""" def get_parser(self, prog_name): parser = super(CreateNetwork, self).get_parser(prog_name) @@ -93,42 +116,36 @@ class CreateNetwork(show.ShowOne): parser.add_argument( '--project', metavar='<project>', - help="Owner's project (name or ID)") + help="Owner's project (name or ID)" + ) identity_common.add_project_domain_option_to_parser(parser) + + parser.add_argument( + '--availability-zone-hint', + action='append', + dest='availability_zone_hints', + metavar='<availability-zone>', + help='Availability Zone in which to create this network ' + '(requires the Network Availability Zone extension, ' + 'this option can be repeated).', + ) return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.network - body = self.get_body(parsed_args) - obj = client.create_network(**body) + + attrs = _get_attrs(self.app.client_manager, parsed_args) + obj = client.create_network(**attrs) columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (columns, data) - def get_body(self, parsed_args): - body = {'name': str(parsed_args.name), - 'admin_state_up': parsed_args.admin_state} - if parsed_args.shared is not None: - body['shared'] = parsed_args.shared - if parsed_args.project is not None: - identity_client = self.app.client_manager.identity - project_id = identity_common.find_project( - identity_client, - parsed_args.project, - parsed_args.project_domain, - ).id - body['tenant_id'] = project_id - return body - - -class DeleteNetwork(command.Command): - """Delete network(s)""" - log = logging.getLogger(__name__ + '.DeleteNetwork') +class DeleteNetwork(common.NetworkAndComputeCommand): + """Delete network(s)""" - def get_parser(self, prog_name): - parser = super(DeleteNetwork, self).get_parser(prog_name) + def update_parser_common(self, parser): parser.add_argument( 'network', metavar="<network>", @@ -137,19 +154,23 @@ class DeleteNetwork(command.Command): ) return parser - def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) - client = self.app.client_manager.network + def take_action_network(self, client, parsed_args): for network in parsed_args.network: obj = client.find_network(network) client.delete_network(obj) + def take_action_compute(self, client, parsed_args): + for network in parsed_args.network: + network = utils.find_resource( + client.networks, + network, + ) + client.networks.delete(network.id) + -class ListNetwork(lister.Lister): +class ListNetwork(command.Lister): """List networks""" - log = logging.getLogger(__name__ + '.ListNetwork') - def get_parser(self, prog_name): parser = super(ListNetwork, self).get_parser(prog_name) parser.add_argument( @@ -167,7 +188,6 @@ class ListNetwork(lister.Lister): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.network if parsed_args.long: @@ -181,6 +201,7 @@ class ListNetwork(lister.Lister): 'subnets', 'provider_network_type', 'router_external', + 'availability_zones', ) column_headers = ( 'ID', @@ -192,6 +213,7 @@ class ListNetwork(lister.Lister): 'Subnets', 'Network Type', 'Router Type', + 'Availability Zones', ) else: columns = ( @@ -220,12 +242,10 @@ class ListNetwork(lister.Lister): class SetNetwork(command.Command): """Set network properties""" - log = logging.getLogger(__name__ + '.SetNetwork') - def get_parser(self, prog_name): parser = super(SetNetwork, self).get_parser(prog_name) parser.add_argument( - 'identifier', + 'network', metavar="<network>", help=("Network to modify (name or ID)") ) @@ -265,42 +285,33 @@ class SetNetwork(command.Command): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.network - obj = client.find_network(parsed_args.identifier, ignore_missing=False) + obj = client.find_network(parsed_args.network, ignore_missing=False) - if parsed_args.name is not None: - obj.name = str(parsed_args.name) - if parsed_args.admin_state is not None: - obj.admin_state_up = parsed_args.admin_state - if parsed_args.shared is not None: - obj.shared = parsed_args.shared - - if not obj.is_dirty: + attrs = _get_attrs(self.app.client_manager, parsed_args) + if attrs == {}: msg = "Nothing specified to be set" raise exceptions.CommandError(msg) - client.update_network(obj) + client.update_network(obj, **attrs) + return -class ShowNetwork(show.ShowOne): +class ShowNetwork(command.ShowOne): """Show network details""" - log = logging.getLogger(__name__ + '.ShowNetwork') - def get_parser(self, prog_name): parser = super(ShowNetwork, self).get_parser(prog_name) parser.add_argument( - 'identifier', + 'network', metavar="<network>", help=("Network to display (name or ID)") ) return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.network - obj = client.find_network(parsed_args.identifier, ignore_missing=False) + obj = client.find_network(parsed_args.network, ignore_missing=False) columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) return (columns, data) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index ad906a28..46cb031f 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -13,16 +13,48 @@ """Port action implementations""" -import logging +from openstackclient.common import command +from openstackclient.common import utils -from cliff import command + +def _format_admin_state(state): + return 'UP' if state else 'DOWN' + + +_formatters = { + 'admin_state_up': _format_admin_state, + 'allowed_address_pairs': utils.format_list_of_dicts, + 'binding_profile': utils.format_dict, + 'binding_vif_details': utils.format_dict, + 'dns_assignment': utils.format_list_of_dicts, + 'extra_dhcp_opts': utils.format_list_of_dicts, + 'fixed_ips': utils.format_list_of_dicts, + 'security_groups': utils.format_list, +} + + +def _get_columns(item): + columns = item.keys() + if 'tenant_id' in columns: + columns.remove('tenant_id') + columns.append('project_id') + binding_columns = [ + 'binding:host_id', + 'binding:profile', + 'binding:vif_details', + 'binding:vif_type', + 'binding:vnic_type', + ] + for binding_column in binding_columns: + if binding_column in columns: + columns.remove(binding_column) + columns.append(binding_column.replace('binding:', 'binding_', 1)) + return sorted(columns) class DeletePort(command.Command): """Delete port(s)""" - log = logging.getLogger(__name__ + '.DeletePort') - def get_parser(self, prog_name): parser = super(DeletePort, self).get_parser(prog_name) parser.add_argument( @@ -34,9 +66,28 @@ class DeletePort(command.Command): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.network for port in parsed_args.port: res = client.find_port(port) client.delete_port(res) + + +class ShowPort(command.ShowOne): + """Display port details""" + + def get_parser(self, prog_name): + parser = super(ShowPort, self).get_parser(prog_name) + parser.add_argument( + 'port', + metavar="<port>", + help="Port to display (name or ID)" + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_port(parsed_args.port, ignore_missing=False) + columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters=_formatters) + return (tuple(columns), data) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 09e0fe4c..60db816a 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -14,12 +14,8 @@ """Router action implementations""" import json -import logging - -from cliff import command -from cliff import lister -from cliff import show +from openstackclient.common import command from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.identity import common as identity_common @@ -39,6 +35,8 @@ def _format_external_gateway_info(info): _formatters = { 'admin_state_up': _format_admin_state, 'external_gateway_info': _format_external_gateway_info, + 'availability_zones': utils.format_list, + 'availability_zone_hints': utils.format_list, } @@ -50,6 +48,9 @@ def _get_attrs(client_manager, parsed_args): attrs['admin_state_up'] = parsed_args.admin_state_up if parsed_args.distributed is not None: attrs['distributed'] = parsed_args.distributed + if ('availability_zone_hints' in parsed_args + and parsed_args.availability_zone_hints is not None): + attrs['availability_zone_hints'] = parsed_args.availability_zone_hints # "router set" command doesn't support setting project. if 'project' in parsed_args and parsed_args.project is not None: identity_client = client_manager.identity @@ -67,11 +68,9 @@ def _get_attrs(client_manager, parsed_args): return attrs -class CreateRouter(show.ShowOne): +class CreateRouter(command.ShowOne): """Create a new router""" - log = logging.getLogger(__name__ + '.CreateRouter') - def get_parser(self, prog_name): parser = super(CreateRouter, self).get_parser(prog_name) parser.add_argument( @@ -102,14 +101,23 @@ class CreateRouter(show.ShowOne): ) parser.add_argument( '--project', - metavar='<poroject>', + metavar='<project>', help="Owner's project (name or ID)", ) + parser.add_argument( + '--availability-zone-hint', + metavar='<availability-zone>', + action='append', + dest='availability_zone_hints', + help='Availability Zone in which to create this router ' + '(requires the Router Availability Zone extension, ' + 'this option can be repeated).', + ) + identity_common.add_project_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) @@ -128,8 +136,6 @@ class CreateRouter(show.ShowOne): class DeleteRouter(command.Command): """Delete router(s)""" - log = logging.getLogger(__name__ + '.DeleteRouter') - def get_parser(self, prog_name): parser = super(DeleteRouter, self).get_parser(prog_name) parser.add_argument( @@ -141,18 +147,15 @@ class DeleteRouter(command.Command): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.network for router in parsed_args.router: obj = client.find_router(router) client.delete_router(obj) -class ListRouter(lister.Lister): +class ListRouter(command.Lister): """List routers""" - log = logging.getLogger(__name__ + '.ListRouter') - def get_parser(self, prog_name): parser = super(ListRouter, self).get_parser(prog_name) parser.add_argument( @@ -164,7 +167,6 @@ class ListRouter(lister.Lister): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.network columns = ( @@ -189,10 +191,12 @@ class ListRouter(lister.Lister): columns = columns + ( 'routes', 'external_gateway_info', + 'availability_zones' ) column_headers = column_headers + ( 'Routes', 'External gateway info', + 'Availability zones' ) data = client.routers() @@ -206,8 +210,6 @@ class ListRouter(lister.Lister): class SetRouter(command.Command): """Set router properties""" - log = logging.getLogger(__name__ + '.SetRouter') - def get_parser(self, prog_name): parser = super(SetRouter, self).get_parser(prog_name) parser.add_argument( @@ -262,7 +264,6 @@ class SetRouter(command.Command): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.network obj = client.find_router(parsed_args.router, ignore_missing=False) @@ -274,11 +275,9 @@ class SetRouter(command.Command): client.update_router(obj, **attrs) -class ShowRouter(show.ShowOne): +class ShowRouter(command.ShowOne): """Display router details""" - log = logging.getLogger(__name__ + '.ShowRouter') - def get_parser(self, prog_name): parser = super(ShowRouter, self).get_parser(prog_name) parser.add_argument( @@ -289,7 +288,6 @@ class ShowRouter(show.ShowOne): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.network obj = client.find_router(parsed_args.router, ignore_missing=False) columns = sorted(obj.keys()) diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py new file mode 100644 index 00000000..4e122f21 --- /dev/null +++ b/openstackclient/network/v2/security_group.py @@ -0,0 +1,40 @@ +# 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. +# + +"""Security Group action implementations""" + +from openstackclient.common import utils +from openstackclient.network import common + + +class DeleteSecurityGroup(common.NetworkAndComputeCommand): + """Delete a security group""" + + def update_parser_common(self, parser): + parser.add_argument( + 'group', + metavar='<group>', + help='Security group to delete (name or ID)', + ) + return parser + + def take_action_network(self, client, parsed_args): + obj = client.find_security_group(parsed_args.group) + client.delete_security_group(obj) + + def take_action_compute(self, client, parsed_args): + data = utils.find_resource( + client.security_groups, + parsed_args.group, + ) + client.security_groups.delete(data.id) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py new file mode 100644 index 00000000..b948c656 --- /dev/null +++ b/openstackclient/network/v2/subnet.py @@ -0,0 +1,63 @@ +# 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. +# + +"""Subnet action implementations""" + +from openstackclient.common import command +from openstackclient.common import utils + + +def _format_allocation_pools(data): + pool_formatted = ['%s-%s' % (pool.get('start', ''), pool.get('end', '')) + for pool in data] + return ','.join(pool_formatted) + + +_formatters = { + 'allocation_pools': _format_allocation_pools, + 'dns_nameservers': utils.format_list, + 'host_routes': utils.format_list, +} + + +class ListSubnet(command.Lister): + """List subnets""" + + def get_parser(self, prog_name): + parser = super(ListSubnet, self).get_parser(prog_name) + parser.add_argument( + '--long', + action='store_true', + default=False, + help='List additional fields in output', + ) + return parser + + def take_action(self, parsed_args): + data = self.app.client_manager.network.subnets() + + headers = ('ID', 'Name', 'Network', 'Subnet') + columns = ('id', 'name', 'network_id', 'cidr') + if parsed_args.long: + headers += ('Project', 'DHCP', 'Name Servers', + 'Allocation Pools', 'Host Routes', 'IP Version', + 'Gateway') + columns += ('tenant_id', 'enable_dhcp', 'dns_nameservers', + 'allocation_pools', 'host_routes', 'ip_version', + 'gateway_ip') + + return (headers, + (utils.get_item_properties( + s, columns, + formatters=_formatters, + ) for s in data)) |
