1# Licensed under the Apache License, Version 2.0 (the "License"); you may 2# not use this file except in compliance with the License. You may obtain 3# a copy of the License at 4# 5# http://www.apache.org/licenses/LICENSE-2.0 6# 7# Unless required by applicable law or agreed to in writing, software 8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10# License for the specific language governing permissions and limitations 11# under the License. 12# 13 14"""Subnet action implementations""" 15 16import copy 17import logging 18 19from cliff import columns as cliff_columns 20from osc_lib.cli import format_columns 21from osc_lib.cli import parseractions 22from osc_lib.command import command 23from osc_lib import exceptions 24from osc_lib import utils 25from osc_lib.utils import tags as _tag 26 27from openstackclient.i18n import _ 28from openstackclient.identity import common as identity_common 29from openstackclient.network import sdk_utils 30 31 32LOG = logging.getLogger(__name__) 33 34 35def _update_arguments(obj_list, parsed_args_list, option): 36 for item in parsed_args_list: 37 try: 38 obj_list.remove(item) 39 except ValueError: 40 msg = (_("Subnet does not contain %(option)s %(value)s") % 41 {'option': option, 'value': item}) 42 raise exceptions.CommandError(msg) 43 44 45class AllocationPoolsColumn(cliff_columns.FormattableColumn): 46 def human_readable(self): 47 pool_formatted = ['%s-%s' % (pool.get('start', ''), 48 pool.get('end', '')) 49 for pool in self._value] 50 return ','.join(pool_formatted) 51 52 53class HostRoutesColumn(cliff_columns.FormattableColumn): 54 def human_readable(self): 55 # Map the host route keys to match --host-route option. 56 return utils.format_list_of_dicts( 57 convert_entries_to_gateway(self._value)) 58 59 60_formatters = { 61 'allocation_pools': AllocationPoolsColumn, 62 'dns_nameservers': format_columns.ListColumn, 63 'host_routes': HostRoutesColumn, 64 'location': format_columns.DictColumn, 65 'service_types': format_columns.ListColumn, 66 'tags': format_columns.ListColumn, 67} 68 69 70def _get_common_parse_arguments(parser, is_create=True): 71 parser.add_argument( 72 '--allocation-pool', 73 metavar='start=<ip-address>,end=<ip-address>', 74 dest='allocation_pools', 75 action=parseractions.MultiKeyValueAction, 76 required_keys=['start', 'end'], 77 help=_("Allocation pool IP addresses for this subnet " 78 "e.g.: start=192.168.199.2,end=192.168.199.254 " 79 "(repeat option to add multiple IP addresses)") 80 ) 81 if not is_create: 82 parser.add_argument( 83 '--no-allocation-pool', 84 action='store_true', 85 help=_("Clear associated allocation-pools from the subnet. " 86 "Specify both --allocation-pool and --no-allocation-pool " 87 "to overwrite the current allocation pool information.") 88 ) 89 parser.add_argument( 90 '--dns-nameserver', 91 metavar='<dns-nameserver>', 92 action='append', 93 dest='dns_nameservers', 94 help=_("DNS server for this subnet " 95 "(repeat option to set multiple DNS servers)") 96 ) 97 98 if not is_create: 99 parser.add_argument( 100 '--no-dns-nameservers', 101 action='store_true', 102 help=_("Clear existing information of DNS Nameservers. " 103 "Specify both --dns-nameserver and --no-dns-nameserver " 104 "to overwrite the current DNS Nameserver information.") 105 ) 106 parser.add_argument( 107 '--host-route', 108 metavar='destination=<subnet>,gateway=<ip-address>', 109 dest='host_routes', 110 action=parseractions.MultiKeyValueAction, 111 required_keys=['destination', 'gateway'], 112 help=_("Additional route for this subnet " 113 "e.g.: destination=10.10.0.0/16,gateway=192.168.71.254 " 114 "destination: destination subnet (in CIDR notation) " 115 "gateway: nexthop IP address " 116 "(repeat option to add multiple routes)") 117 ) 118 if not is_create: 119 parser.add_argument( 120 '--no-host-route', 121 action='store_true', 122 help=_("Clear associated host-routes from the subnet. " 123 "Specify both --host-route and --no-host-route " 124 "to overwrite the current host route information.") 125 ) 126 parser.add_argument( 127 '--service-type', 128 metavar='<service-type>', 129 action='append', 130 dest='service_types', 131 help=_("Service type for this subnet " 132 "e.g.: network:floatingip_agent_gateway. " 133 "Must be a valid device owner value for a network port " 134 "(repeat option to set multiple service types)") 135 ) 136 137 138def _get_columns(item): 139 column_map = { 140 'is_dhcp_enabled': 'enable_dhcp', 141 'subnet_pool_id': 'subnetpool_id', 142 'tenant_id': 'project_id', 143 } 144 # Do not show this column when displaying a subnet 145 invisible_columns = ['use_default_subnet_pool'] 146 return sdk_utils.get_osc_show_columns_for_sdk_resource( 147 item, 148 column_map, 149 invisible_columns=invisible_columns 150 ) 151 152 153def convert_entries_to_nexthop(entries): 154 # Change 'gateway' entry to 'nexthop' 155 changed_entries = copy.deepcopy(entries) 156 for entry in changed_entries: 157 if 'gateway' in entry: 158 entry['nexthop'] = entry['gateway'] 159 del entry['gateway'] 160 161 return changed_entries 162 163 164def convert_entries_to_gateway(entries): 165 # Change 'nexthop' entry to 'gateway' 166 changed_entries = copy.deepcopy(entries) 167 for entry in changed_entries: 168 if 'nexthop' in entry: 169 entry['gateway'] = entry['nexthop'] 170 del entry['nexthop'] 171 172 return changed_entries 173 174 175def _get_attrs(client_manager, parsed_args, is_create=True): 176 attrs = {} 177 client = client_manager.network 178 if 'name' in parsed_args and parsed_args.name is not None: 179 attrs['name'] = parsed_args.name 180 181 if is_create: 182 if 'project' in parsed_args and parsed_args.project is not None: 183 identity_client = client_manager.identity 184 project_id = identity_common.find_project( 185 identity_client, 186 parsed_args.project, 187 parsed_args.project_domain, 188 ).id 189 attrs['tenant_id'] = project_id 190 attrs['network_id'] = client.find_network(parsed_args.network, 191 ignore_missing=False).id 192 if parsed_args.subnet_pool is not None: 193 subnet_pool = client.find_subnet_pool(parsed_args.subnet_pool, 194 ignore_missing=False) 195 attrs['subnetpool_id'] = subnet_pool.id 196 if parsed_args.use_prefix_delegation: 197 attrs['subnetpool_id'] = "prefix_delegation" 198 if parsed_args.use_default_subnet_pool: 199 attrs['use_default_subnet_pool'] = True 200 if parsed_args.prefix_length is not None: 201 attrs['prefixlen'] = parsed_args.prefix_length 202 if parsed_args.subnet_range is not None: 203 attrs['cidr'] = parsed_args.subnet_range 204 if parsed_args.ip_version is not None: 205 attrs['ip_version'] = parsed_args.ip_version 206 if parsed_args.ipv6_ra_mode is not None: 207 attrs['ipv6_ra_mode'] = parsed_args.ipv6_ra_mode 208 if parsed_args.ipv6_address_mode is not None: 209 attrs['ipv6_address_mode'] = parsed_args.ipv6_address_mode 210 211 if parsed_args.network_segment is not None: 212 attrs['segment_id'] = client.find_segment( 213 parsed_args.network_segment, ignore_missing=False).id 214 if 'gateway' in parsed_args and parsed_args.gateway is not None: 215 gateway = parsed_args.gateway.lower() 216 217 if not is_create and gateway == 'auto': 218 msg = _("Auto option is not available for Subnet Set. " 219 "Valid options are <ip-address> or none") 220 raise exceptions.CommandError(msg) 221 elif gateway != 'auto': 222 if gateway == 'none': 223 attrs['gateway_ip'] = None 224 else: 225 attrs['gateway_ip'] = gateway 226 if ('allocation_pools' in parsed_args and 227 parsed_args.allocation_pools is not None): 228 attrs['allocation_pools'] = parsed_args.allocation_pools 229 if parsed_args.dhcp: 230 attrs['enable_dhcp'] = True 231 if parsed_args.no_dhcp: 232 attrs['enable_dhcp'] = False 233 if parsed_args.dns_publish_fixed_ip: 234 attrs['dns_publish_fixed_ip'] = True 235 if parsed_args.no_dns_publish_fixed_ip: 236 attrs['dns_publish_fixed_ip'] = False 237 if ('dns_nameservers' in parsed_args and 238 parsed_args.dns_nameservers is not None): 239 attrs['dns_nameservers'] = parsed_args.dns_nameservers 240 if 'host_routes' in parsed_args and parsed_args.host_routes is not None: 241 # Change 'gateway' entry to 'nexthop' to match the API 242 attrs['host_routes'] = convert_entries_to_nexthop( 243 parsed_args.host_routes) 244 if ('service_types' in parsed_args and 245 parsed_args.service_types is not None): 246 attrs['service_types'] = parsed_args.service_types 247 if parsed_args.description is not None: 248 attrs['description'] = parsed_args.description 249 return attrs 250 251 252# TODO(abhiraut): Use the SDK resource mapped attribute names once the 253# OSC minimum requirements include SDK 1.0. 254class CreateSubnet(command.ShowOne): 255 _description = _("Create a subnet") 256 257 def get_parser(self, prog_name): 258 parser = super(CreateSubnet, self).get_parser(prog_name) 259 parser.add_argument( 260 'name', 261 metavar='<name>', 262 help=_("New subnet name") 263 ) 264 parser.add_argument( 265 '--project', 266 metavar='<project>', 267 help=_("Owner's project (name or ID)") 268 ) 269 identity_common.add_project_domain_option_to_parser(parser) 270 subnet_pool_group = parser.add_mutually_exclusive_group() 271 subnet_pool_group.add_argument( 272 '--subnet-pool', 273 metavar='<subnet-pool>', 274 help=_("Subnet pool from which this subnet will obtain a CIDR " 275 "(Name or ID)") 276 ) 277 subnet_pool_group.add_argument( 278 '--use-prefix-delegation', 279 help=_("Use 'prefix-delegation' if IP is IPv6 format " 280 "and IP would be delegated externally") 281 ) 282 subnet_pool_group.add_argument( 283 '--use-default-subnet-pool', 284 action='store_true', 285 help=_("Use default subnet pool for --ip-version") 286 ) 287 parser.add_argument( 288 '--prefix-length', 289 metavar='<prefix-length>', 290 help=_("Prefix length for subnet allocation from subnet pool") 291 ) 292 parser.add_argument( 293 '--subnet-range', 294 metavar='<subnet-range>', 295 help=_("Subnet range in CIDR notation " 296 "(required if --subnet-pool is not specified, " 297 "optional otherwise)") 298 ) 299 dhcp_enable_group = parser.add_mutually_exclusive_group() 300 dhcp_enable_group.add_argument( 301 '--dhcp', 302 action='store_true', 303 help=_("Enable DHCP (default)") 304 ) 305 dhcp_enable_group.add_argument( 306 '--no-dhcp', 307 action='store_true', 308 help=_("Disable DHCP") 309 ) 310 dns_publish_fixed_ip_group = parser.add_mutually_exclusive_group() 311 dns_publish_fixed_ip_group.add_argument( 312 '--dns-publish-fixed-ip', 313 action='store_true', 314 help=_("Enable publishing fixed IPs in DNS") 315 ) 316 dns_publish_fixed_ip_group.add_argument( 317 '--no-dns-publish-fixed-ip', 318 action='store_true', 319 help=_("Disable publishing fixed IPs in DNS (default)") 320 ) 321 parser.add_argument( 322 '--gateway', 323 metavar='<gateway>', 324 default='auto', 325 help=_("Specify a gateway for the subnet. The three options are: " 326 "<ip-address>: Specific IP address to use as the gateway, " 327 "'auto': Gateway address should automatically be chosen " 328 "from within the subnet itself, 'none': This subnet will " 329 "not use a gateway, e.g.: --gateway 192.168.9.1, " 330 "--gateway auto, --gateway none (default is 'auto').") 331 ) 332 parser.add_argument( 333 '--ip-version', 334 type=int, 335 default=4, 336 choices=[4, 6], 337 help=_("IP version (default is 4). Note that when subnet pool is " 338 "specified, IP version is determined from the subnet pool " 339 "and this option is ignored.") 340 ) 341 parser.add_argument( 342 '--ipv6-ra-mode', 343 choices=['dhcpv6-stateful', 'dhcpv6-stateless', 'slaac'], 344 help=_("IPv6 RA (Router Advertisement) mode, " 345 "valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac]") 346 ) 347 parser.add_argument( 348 '--ipv6-address-mode', 349 choices=['dhcpv6-stateful', 'dhcpv6-stateless', 'slaac'], 350 help=_("IPv6 address mode, " 351 "valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac]") 352 ) 353 parser.add_argument( 354 '--network-segment', 355 metavar='<network-segment>', 356 help=_("Network segment to associate with this subnet " 357 "(name or ID)") 358 ) 359 parser.add_argument( 360 '--network', 361 required=True, 362 metavar='<network>', 363 help=_("Network this subnet belongs to (name or ID)") 364 ) 365 parser.add_argument( 366 '--description', 367 metavar='<description>', 368 help=_("Set subnet description") 369 ) 370 _get_common_parse_arguments(parser) 371 _tag.add_tag_option_to_parser_for_create(parser, _('subnet')) 372 return parser 373 374 def take_action(self, parsed_args): 375 client = self.app.client_manager.network 376 attrs = _get_attrs(self.app.client_manager, parsed_args) 377 obj = client.create_subnet(**attrs) 378 # tags cannot be set when created, so tags need to be set later. 379 _tag.update_tags_for_set(client, obj, parsed_args) 380 display_columns, columns = _get_columns(obj) 381 data = utils.get_item_properties(obj, columns, formatters=_formatters) 382 return (display_columns, data) 383 384 385class DeleteSubnet(command.Command): 386 _description = _("Delete subnet(s)") 387 388 def get_parser(self, prog_name): 389 parser = super(DeleteSubnet, self).get_parser(prog_name) 390 parser.add_argument( 391 'subnet', 392 metavar="<subnet>", 393 nargs='+', 394 help=_("Subnet(s) to delete (name or ID)") 395 ) 396 return parser 397 398 def take_action(self, parsed_args): 399 client = self.app.client_manager.network 400 result = 0 401 402 for subnet in parsed_args.subnet: 403 try: 404 obj = client.find_subnet(subnet, ignore_missing=False) 405 client.delete_subnet(obj) 406 except Exception as e: 407 result += 1 408 LOG.error(_("Failed to delete subnet with " 409 "name or ID '%(subnet)s': %(e)s"), 410 {'subnet': subnet, 'e': e}) 411 412 if result > 0: 413 total = len(parsed_args.subnet) 414 msg = (_("%(result)s of %(total)s subnets failed " 415 "to delete.") % {'result': result, 'total': total}) 416 raise exceptions.CommandError(msg) 417 418 419# TODO(abhiraut): Use only the SDK resource mapped attribute names once the 420# OSC minimum requirements include SDK 1.0. 421class ListSubnet(command.Lister): 422 _description = _("List subnets") 423 424 def get_parser(self, prog_name): 425 parser = super(ListSubnet, self).get_parser(prog_name) 426 parser.add_argument( 427 '--long', 428 action='store_true', 429 default=False, 430 help=_("List additional fields in output") 431 ) 432 parser.add_argument( 433 '--ip-version', 434 type=int, 435 choices=[4, 6], 436 metavar='<ip-version>', 437 dest='ip_version', 438 help=_("List only subnets of given IP version in output. " 439 "Allowed values for IP version are 4 and 6."), 440 ) 441 dhcp_enable_group = parser.add_mutually_exclusive_group() 442 dhcp_enable_group.add_argument( 443 '--dhcp', 444 action='store_true', 445 help=_("List subnets which have DHCP enabled") 446 ) 447 dhcp_enable_group.add_argument( 448 '--no-dhcp', 449 action='store_true', 450 help=_("List subnets which have DHCP disabled") 451 ) 452 parser.add_argument( 453 '--service-type', 454 metavar='<service-type>', 455 action='append', 456 dest='service_types', 457 help=_("List only subnets of a given service type in output " 458 "e.g.: network:floatingip_agent_gateway. " 459 "Must be a valid device owner value for a network port " 460 "(repeat option to list multiple service types)") 461 ) 462 parser.add_argument( 463 '--project', 464 metavar='<project>', 465 help=_("List only subnets which belong to a given project " 466 "in output (name or ID)") 467 ) 468 identity_common.add_project_domain_option_to_parser(parser) 469 parser.add_argument( 470 '--network', 471 metavar='<network>', 472 help=_("List only subnets which belong to a given network " 473 "in output (name or ID)") 474 ) 475 parser.add_argument( 476 '--gateway', 477 metavar='<gateway>', 478 help=_("List only subnets of given gateway IP in output") 479 ) 480 parser.add_argument( 481 '--name', 482 metavar='<name>', 483 help=_("List only subnets of given name in output") 484 ) 485 parser.add_argument( 486 '--subnet-range', 487 metavar='<subnet-range>', 488 help=_("List only subnets of given subnet range " 489 "(in CIDR notation) in output " 490 "e.g.: --subnet-range 10.10.0.0/16") 491 ) 492 _tag.add_tag_filtering_option_to_parser(parser, _('subnets')) 493 return parser 494 495 def take_action(self, parsed_args): 496 identity_client = self.app.client_manager.identity 497 network_client = self.app.client_manager.network 498 filters = {} 499 if parsed_args.ip_version: 500 filters['ip_version'] = parsed_args.ip_version 501 if parsed_args.dhcp: 502 filters['enable_dhcp'] = True 503 filters['is_dhcp_enabled'] = True 504 elif parsed_args.no_dhcp: 505 filters['enable_dhcp'] = False 506 filters['is_dhcp_enabled'] = False 507 if parsed_args.service_types: 508 filters['service_types'] = parsed_args.service_types 509 if parsed_args.project: 510 project_id = identity_common.find_project( 511 identity_client, 512 parsed_args.project, 513 parsed_args.project_domain, 514 ).id 515 filters['tenant_id'] = project_id 516 filters['project_id'] = project_id 517 if parsed_args.network: 518 network_id = network_client.find_network(parsed_args.network, 519 ignore_missing=False).id 520 filters['network_id'] = network_id 521 if parsed_args.gateway: 522 filters['gateway_ip'] = parsed_args.gateway 523 if parsed_args.name: 524 filters['name'] = parsed_args.name 525 if parsed_args.subnet_range: 526 filters['cidr'] = parsed_args.subnet_range 527 _tag.get_tag_filtering_args(parsed_args, filters) 528 data = network_client.subnets(**filters) 529 530 headers = ('ID', 'Name', 'Network', 'Subnet') 531 columns = ('id', 'name', 'network_id', 'cidr') 532 if parsed_args.long: 533 headers += ('Project', 'DHCP', 'Name Servers', 534 'Allocation Pools', 'Host Routes', 'IP Version', 535 'Gateway', 'Service Types', 'Tags') 536 columns += ('project_id', 'is_dhcp_enabled', 'dns_nameservers', 537 'allocation_pools', 'host_routes', 'ip_version', 538 'gateway_ip', 'service_types', 'tags') 539 540 return (headers, 541 (utils.get_item_properties( 542 s, columns, 543 formatters=_formatters, 544 ) for s in data)) 545 546 547# TODO(abhiraut): Use the SDK resource mapped attribute names once the 548# OSC minimum requirements include SDK 1.0. 549class SetSubnet(command.Command): 550 _description = _("Set subnet properties") 551 552 def get_parser(self, prog_name): 553 parser = super(SetSubnet, self).get_parser(prog_name) 554 parser.add_argument( 555 'subnet', 556 metavar="<subnet>", 557 help=_("Subnet to modify (name or ID)") 558 ) 559 parser.add_argument( 560 '--name', 561 metavar='<name>', 562 help=_("Updated name of the subnet") 563 ) 564 dhcp_enable_group = parser.add_mutually_exclusive_group() 565 dhcp_enable_group.add_argument( 566 '--dhcp', 567 action='store_true', 568 default=None, 569 help=_("Enable DHCP") 570 ) 571 dhcp_enable_group.add_argument( 572 '--no-dhcp', 573 action='store_true', 574 help=_("Disable DHCP") 575 ) 576 dns_publish_fixed_ip_group = parser.add_mutually_exclusive_group() 577 dns_publish_fixed_ip_group.add_argument( 578 '--dns-publish-fixed-ip', 579 action='store_true', 580 help=_("Enable publishing fixed IPs in DNS") 581 ) 582 dns_publish_fixed_ip_group.add_argument( 583 '--no-dns-publish-fixed-ip', 584 action='store_true', 585 help=_("Disable publishing fixed IPs in DNS") 586 ) 587 parser.add_argument( 588 '--gateway', 589 metavar='<gateway>', 590 help=_("Specify a gateway for the subnet. The options are: " 591 "<ip-address>: Specific IP address to use as the gateway, " 592 "'none': This subnet will not use a gateway, " 593 "e.g.: --gateway 192.168.9.1, --gateway none.") 594 ) 595 parser.add_argument( 596 '--network-segment', 597 metavar='<network-segment>', 598 help=_("Network segment to associate with this subnet (name or " 599 "ID). It is only allowed to set the segment if the current " 600 "value is `None`, the network must also have only one " 601 "segment and only one subnet can exist on the network.") 602 ) 603 parser.add_argument( 604 '--description', 605 metavar='<description>', 606 help=_("Set subnet description") 607 ) 608 _tag.add_tag_option_to_parser_for_set(parser, _('subnet')) 609 _get_common_parse_arguments(parser, is_create=False) 610 return parser 611 612 def take_action(self, parsed_args): 613 client = self.app.client_manager.network 614 obj = client.find_subnet(parsed_args.subnet, ignore_missing=False) 615 attrs = _get_attrs(self.app.client_manager, parsed_args, 616 is_create=False) 617 if 'dns_nameservers' in attrs: 618 if not parsed_args.no_dns_nameservers: 619 attrs['dns_nameservers'] += obj.dns_nameservers 620 elif parsed_args.no_dns_nameservers: 621 attrs['dns_nameservers'] = [] 622 if 'host_routes' in attrs: 623 if not parsed_args.no_host_route: 624 attrs['host_routes'] += obj.host_routes 625 elif parsed_args.no_host_route: 626 attrs['host_routes'] = [] 627 if 'allocation_pools' in attrs: 628 if not parsed_args.no_allocation_pool: 629 attrs['allocation_pools'] += obj.allocation_pools 630 elif parsed_args.no_allocation_pool: 631 attrs['allocation_pools'] = [] 632 if 'service_types' in attrs: 633 attrs['service_types'] += obj.service_types 634 if attrs: 635 client.update_subnet(obj, **attrs) 636 # tags is a subresource and it needs to be updated separately. 637 _tag.update_tags_for_set(client, obj, parsed_args) 638 return 639 640 641class ShowSubnet(command.ShowOne): 642 _description = _("Display subnet details") 643 644 def get_parser(self, prog_name): 645 parser = super(ShowSubnet, self).get_parser(prog_name) 646 parser.add_argument( 647 'subnet', 648 metavar="<subnet>", 649 help=_("Subnet to display (name or ID)") 650 ) 651 return parser 652 653 def take_action(self, parsed_args): 654 obj = self.app.client_manager.network.find_subnet(parsed_args.subnet, 655 ignore_missing=False) 656 display_columns, columns = _get_columns(obj) 657 data = utils.get_item_properties(obj, columns, formatters=_formatters) 658 return (display_columns, data) 659 660 661class UnsetSubnet(command.Command): 662 _description = _("Unset subnet properties") 663 664 def get_parser(self, prog_name): 665 parser = super(UnsetSubnet, self).get_parser(prog_name) 666 parser.add_argument( 667 '--allocation-pool', 668 metavar='start=<ip-address>,end=<ip-address>', 669 dest='allocation_pools', 670 action=parseractions.MultiKeyValueAction, 671 required_keys=['start', 'end'], 672 help=_('Allocation pool IP addresses to be removed from this ' 673 'subnet e.g.: start=192.168.199.2,end=192.168.199.254 ' 674 '(repeat option to unset multiple allocation pools)') 675 ) 676 parser.add_argument( 677 '--dns-nameserver', 678 metavar='<dns-nameserver>', 679 action='append', 680 dest='dns_nameservers', 681 help=_('DNS server to be removed from this subnet ' 682 '(repeat option to unset multiple DNS servers)') 683 ) 684 parser.add_argument( 685 '--host-route', 686 metavar='destination=<subnet>,gateway=<ip-address>', 687 dest='host_routes', 688 action=parseractions.MultiKeyValueAction, 689 required_keys=['destination', 'gateway'], 690 help=_('Route to be removed from this subnet ' 691 'e.g.: destination=10.10.0.0/16,gateway=192.168.71.254 ' 692 'destination: destination subnet (in CIDR notation) ' 693 'gateway: nexthop IP address ' 694 '(repeat option to unset multiple host routes)') 695 ) 696 parser.add_argument( 697 '--service-type', 698 metavar='<service-type>', 699 action='append', 700 dest='service_types', 701 help=_('Service type to be removed from this subnet ' 702 'e.g.: network:floatingip_agent_gateway. ' 703 'Must be a valid device owner value for a network port ' 704 '(repeat option to unset multiple service types)') 705 ) 706 _tag.add_tag_option_to_parser_for_unset(parser, _('subnet')) 707 parser.add_argument( 708 'subnet', 709 metavar="<subnet>", 710 help=_("Subnet to modify (name or ID)") 711 ) 712 return parser 713 714 def take_action(self, parsed_args): 715 client = self.app.client_manager.network 716 obj = client.find_subnet(parsed_args.subnet, ignore_missing=False) 717 718 attrs = {} 719 if parsed_args.dns_nameservers: 720 attrs['dns_nameservers'] = copy.deepcopy(obj.dns_nameservers) 721 _update_arguments(attrs['dns_nameservers'], 722 parsed_args.dns_nameservers, 723 'dns-nameserver') 724 if parsed_args.host_routes: 725 attrs['host_routes'] = copy.deepcopy(obj.host_routes) 726 _update_arguments( 727 attrs['host_routes'], 728 convert_entries_to_nexthop(parsed_args.host_routes), 729 'host-route') 730 if parsed_args.allocation_pools: 731 attrs['allocation_pools'] = copy.deepcopy(obj.allocation_pools) 732 _update_arguments(attrs['allocation_pools'], 733 parsed_args.allocation_pools, 734 'allocation-pool') 735 736 if parsed_args.service_types: 737 attrs['service_types'] = copy.deepcopy(obj.service_types) 738 _update_arguments(attrs['service_types'], 739 parsed_args.service_types, 740 'service-type') 741 if attrs: 742 client.update_subnet(obj, **attrs) 743 744 # tags is a subresource and it needs to be updated separately. 745 _tag.update_tags_for_unset(client, obj, parsed_args) 746