# 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""" import logging from openstack import exceptions as sdk_exceptions from openstack.network.v2 import floating_ip as _floating_ip from osc_lib import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common from openstackclient.network import sdk_utils def _get_network_columns(item): column_map = { 'tenant_id': 'project_id', } return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) def _get_columns(item): columns = list(item.keys()) if 'tenant_id' in columns: columns.remove('tenant_id') columns.append('project_id') return tuple(sorted(columns)) def _get_attrs(client_manager, parsed_args): attrs = {} network_client = client_manager.network # Name of a network could be empty string. if parsed_args.network is not None: network = network_client.find_network(parsed_args.network, ignore_missing=False) attrs['floating_network_id'] = network.id if parsed_args.subnet: subnet = network_client.find_subnet(parsed_args.subnet, ignore_missing=False) attrs['subnet_id'] = subnet.id if parsed_args.port: port = network_client.find_port(parsed_args.port, ignore_missing=False) attrs['port_id'] = port.id if parsed_args.floating_ip_address: attrs['floating_ip_address'] = parsed_args.floating_ip_address if parsed_args.fixed_ip_address: attrs['fixed_ip_address'] = parsed_args.fixed_ip_address if parsed_args.description is not None: attrs['description'] = parsed_args.description if parsed_args.project: 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 return attrs def _find_floating_ip( session, ip_cache, name_or_id, ignore_missing=True, **params ): """Find a floating IP by IP or ID The SDK's find_ip() can only locate a floating IP by ID so we have to do this ourselves. """ def _get_one_match(name_or_id): """Given a list of results, return the match""" the_result = None for maybe_result in ip_cache: id_value = maybe_result.id ip_value = maybe_result.floating_ip_address if (id_value == name_or_id) or (ip_value == name_or_id): # Only allow one resource to be found. If we already # found a match, raise an exception to show it. if the_result is None: the_result = maybe_result else: msg = "More than one %s exists with the name '%s'." msg = (msg % (_floating_ip.FloatingIP, name_or_id)) raise sdk_exceptions.DuplicateResource(msg) return the_result # Try to short-circuit by looking directly for a matching ID. try: match = _floating_ip.FloatingIP.existing(id=name_or_id, **params) return (match.get(session), ip_cache) except sdk_exceptions.NotFoundException: pass if len(ip_cache) == 0: ip_cache = list(_floating_ip.FloatingIP.list(session, **params)) result = _get_one_match(name_or_id) if result is not None: return (result, ip_cache) if ignore_missing: return (None, ip_cache) raise sdk_exceptions.ResourceNotFound( "No %s found for %s" % (_floating_ip.FloatingIP.__name__, name_or_id)) class CreateFloatingIP(common.NetworkAndComputeShowOne): _description = _("Create floating IP") def update_parser_common(self, parser): # In Compute v2 network, floating IPs could be allocated from floating # IP pools, which are actually external networks. So deprecate the # parameter "pool", and use "network" instead. parser.add_argument( 'network', metavar='', help=_("Network to allocate floating IP from (name or ID)") ) return parser def update_parser_network(self, parser): parser.add_argument( '--subnet', metavar='', help=_("Subnet on which you want to create the floating IP " "(name or ID)") ) parser.add_argument( '--port', metavar='', help=_("Port to be associated with the floating IP " "(name or ID)") ) parser.add_argument( '--floating-ip-address', metavar='', dest='floating_ip_address', help=_("Floating IP address") ) parser.add_argument( '--fixed-ip-address', metavar='', dest='fixed_ip_address', help=_("Fixed IP address mapped to the floating IP") ) parser.add_argument( '--description', metavar='', help=_('Set floating IP description') ) parser.add_argument( '--project', metavar='', help=_("Owner's project (name or ID)") ) identity_common.add_project_domain_option_to_parser(parser) return parser def take_action_network(self, client, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_ip(**attrs) display_columns, columns = _get_network_columns(obj) data = utils.get_item_properties(obj, columns) return (display_columns, data) def take_action_compute(self, client, parsed_args): obj = client.floating_ips.create(parsed_args.network) columns = _get_columns(obj._info) data = utils.get_dict_properties(obj._info, columns) return (columns, data) class CreateIPFloating(CreateFloatingIP): _description = _("Create floating IP") # TODO(tangchen): Remove this class and ``ip floating create`` command # two cycles after Mitaka. # This notifies cliff to not display the help for this command deprecated = True log = logging.getLogger('deprecated') def take_action_network(self, client, parsed_args): self.log.warning(_('This command has been deprecated. ' 'Please use "floating ip create" instead.')) return super(CreateIPFloating, self).take_action_network( client, parsed_args) def take_action_compute(self, client, parsed_args): self.log.warning(_('This command has been deprecated. ' 'Please use "floating ip create" instead.')) return super(CreateIPFloating, self).take_action_compute( client, parsed_args) class DeleteFloatingIP(common.NetworkAndComputeDelete): _description = _("Delete floating IP(s)") # Used by base class to find resources in parsed_args. resource = 'floating_ip' r = None def update_parser_common(self, parser): parser.add_argument( 'floating_ip', metavar="", nargs="+", help=_("Floating IP(s) to delete (IP address or ID)") ) return parser def take_action_network(self, client, parsed_args): (obj, self.ip_cache) = _find_floating_ip( client.session, self.ip_cache, self.r, ignore_missing=False, ) client.delete_ip(obj) def take_action_compute(self, client, parsed_args): obj = utils.find_resource(client.floating_ips, self.r) client.floating_ips.delete(obj.id) def take_action(self, parsed_args): """Implements a naive cache for the list of floating IPs""" # NOTE(dtroyer): This really only prevents multiple list() # calls when performing multiple resource deletes # in a single command. In an interactive session # each delete command will call list(). self.ip_cache = [] super(DeleteFloatingIP, self).take_action(parsed_args) class DeleteIPFloating(DeleteFloatingIP): _description = _("Delete floating IP(s)") # TODO(tangchen): Remove this class and ``ip floating delete`` command # two cycles after Mitaka. # This notifies cliff to not display the help for this command deprecated = True log = logging.getLogger('deprecated') def take_action_network(self, client, parsed_args): self.log.warning(_('This command has been deprecated. ' 'Please use "floating ip delete" instead.')) return super(DeleteIPFloating, self).take_action_network( client, parsed_args) def take_action_compute(self, client, parsed_args): self.log.warning(_('This command has been deprecated. ' 'Please use "floating ip delete" instead.')) return super(DeleteIPFloating, self).take_action_compute( client, parsed_args) class ListFloatingIP(common.NetworkAndComputeLister): # TODO(songminglong): Use SDK resource mapped attribute names once # the OSC minimum requirements include SDK 1.0 _description = _("List floating IP(s)") def update_parser_network(self, parser): parser.add_argument( '--network', metavar='', help=_("List floating IP(s) according to " "given network (name or ID)") ) parser.add_argument( '--port', metavar='', help=_("List floating IP(s) according to " "given port (name or ID)") ) parser.add_argument( '--fixed-ip-address', metavar='', help=_("List floating IP(s) according to " "given fixed IP address") ) parser.add_argument( '--long', action='store_true', default=False, help=_("List additional fields in output") ) parser.add_argument( '--status', metavar='', choices=['ACTIVE', 'DOWN'], help=_("List floating IP(s) according to " "given status ('ACTIVE', 'DOWN')") ) parser.add_argument( '--project', metavar='', help=_("List floating IP(s) according to " "given project (name or ID)") ) identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( '--router', metavar='', help=_("List floating IP(s) according to " "given router (name or ID)") ) return parser def take_action_network(self, client, parsed_args): network_client = self.app.client_manager.network identity_client = self.app.client_manager.identity columns = ( 'id', 'floating_ip_address', 'fixed_ip_address', 'port_id', 'floating_network_id', 'project_id', ) headers = ( 'ID', 'Floating IP Address', 'Fixed IP Address', 'Port', 'Floating Network', 'Project', ) if parsed_args.long: columns = columns + ( 'router_id', 'status', 'description', ) headers = headers + ( 'Router', 'Status', 'Description', ) query = {} if parsed_args.network is not None: network = network_client.find_network(parsed_args.network, ignore_missing=False) query['floating_network_id'] = network.id if parsed_args.port is not None: port = network_client.find_port(parsed_args.port, ignore_missing=False) query['port_id'] = port.id if parsed_args.fixed_ip_address is not None: query['fixed_ip_address'] = parsed_args.fixed_ip_address if parsed_args.status: query['status'] = parsed_args.status if parsed_args.project is not None: project = identity_common.find_project( identity_client, parsed_args.project, parsed_args.project_domain, ) query['tenant_id'] = project.id query['project_id'] = project.id if parsed_args.router is not None: router = network_client.find_router(parsed_args.router, ignore_missing=False) query['router_id'] = router.id data = client.ips(**query) return (headers, (utils.get_item_properties( s, columns, formatters={}, ) for s in data)) def take_action_compute(self, client, parsed_args): columns = ( 'ID', 'IP', 'Fixed IP', 'Instance ID', 'Pool', ) headers = ( 'ID', 'Floating IP Address', 'Fixed IP Address', 'Server', 'Pool', ) data = client.floating_ips.list() return (headers, (utils.get_item_properties( s, columns, formatters={}, ) for s in data)) class ListIPFloating(ListFloatingIP): _description = _("List floating IP(s)") # TODO(tangchen): Remove this class and ``ip floating list`` command # two cycles after Mitaka. # This notifies cliff to not display the help for this command deprecated = True log = logging.getLogger('deprecated') def take_action_network(self, client, parsed_args): self.log.warning(_('This command has been deprecated. ' 'Please use "floating ip list" instead.')) return super(ListIPFloating, self).take_action_network( client, parsed_args) def take_action_compute(self, client, parsed_args): self.log.warning(_('This command has been deprecated. ' 'Please use "floating ip list" instead.')) return super(ListIPFloating, self).take_action_compute( client, parsed_args) class ShowFloatingIP(common.NetworkAndComputeShowOne): _description = _("Display floating IP details") # ip_cache is unused here but is a side effect of _find_floating_ip() ip_cache = [] def update_parser_common(self, parser): parser.add_argument( 'floating_ip', metavar="", help=_("Floating IP to display (IP address or ID)") ) return parser def take_action_network(self, client, parsed_args): (obj, self.ip_cache) = _find_floating_ip( client.session, [], parsed_args.floating_ip, ignore_missing=False, ) display_columns, columns = _get_network_columns(obj) data = utils.get_item_properties(obj, columns) return (display_columns, data) def take_action_compute(self, client, parsed_args): obj = utils.find_resource( client.floating_ips, parsed_args.floating_ip, ) columns = _get_columns(obj._info) data = utils.get_dict_properties(obj._info, columns) return (columns, data) class ShowIPFloating(ShowFloatingIP): _description = _("Display floating IP details") # TODO(tangchen): Remove this class and ``ip floating show`` command # two cycles after Mitaka. # This notifies cliff to not display the help for this command deprecated = True log = logging.getLogger('deprecated') def take_action_network(self, client, parsed_args): self.log.warning(_('This command has been deprecated. ' 'Please use "floating ip show" instead.')) return super(ShowIPFloating, self).take_action_network( client, parsed_args) def take_action_compute(self, client, parsed_args): self.log.warning(_('This command has been deprecated. ' 'Please use "floating ip show" instead.')) return super(ShowIPFloating, self).take_action_compute( client, parsed_args)