def validate_networks(self, context, requested_networks): """Validate that the tenant can use the requested networks.""" LOG.debug(_('validate_networks() for %s'), requested_networks) if not requested_networks: return net_ids = [] for (net_id, _i, port_id) in requested_networks: if not port_id: net_ids.append(net_id) continue port = quantumv2.get_client(context).show_port(port_id).get('port') if not port: raise exception.PortNotFound(port_id=port_id) if port.get('device_id', None): raise exception.PortInUse(port_id=port_id) net_id = port['network_id'] if net_id in net_ids: raise exception.NetworkDuplicated(network_id=net_id) net_ids.append(net_id) nets = self._get_available_networks(context, context.project_id, net_ids) if len(nets) != len(net_ids): requsted_netid_set = set(net_ids) returned_netid_set = set([net['id'] for net in nets]) lostid_set = requsted_netid_set - returned_netid_set id_str = '' for _id in lostid_set: id_str = id_str and id_str + ', ' + _id or _id raise exception.NetworkNotFound(network_id=id_str)
def _create_port(self, client, tenant_id, network_id, port_req_body, fixed_ip=None, security_group_ids=None, dhcp_opts=None): try: if fixed_ip: port_req_body['port']['fixed_ips'] = [{'ip_address': fixed_ip}] port_req_body['port']['network_id'] = network_id port_req_body['port']['admin_state_up'] = True port_req_body['port']['tenant_id'] = tenant_id if security_group_ids: port_req_body['port']['security_groups'] = security_group_ids if dhcp_opts is not None: port_req_body['port']['extra_dhcp_opts'] = dhcp_opts port_id = client.create_port(port_req_body)['port']['id'] LOG.debug('Successfully created port: %s', port_id) return port_id except neutron_client_exc.OverQuotaClient: LOG.warning(_LW( 'Neutron error: Port quota exceeded in tenant: %s'), port_req_body['port']['tenant_id']) raise exception.PortLimitExceeded() except neutron_client_exc.IpAddressGenerationFailureClient: LOG.warning(_LW('Neutron error: No more fixed IPs in network: %s'), network_id) raise exception.NoMoreFixedIps() except neutron_client_exc.MacAddressInUseClient: LOG.warning(_LW('Neutron error: MAC address %(mac)s is already ' 'in use on network %(network)s.') % {'mac': mac_address, 'network': network_id}) raise exception.PortInUse(port_id=mac_address) except neutron_client_exc.NeutronClientException: with excutils.save_and_reraise_exception(): LOG.exception(_LE('Neutron error creating port on network %s'), network_id)
def _create_port(self, port_client, instance, network_id, port_name, port_req_body, mac_address=None): """Attempts to create a port for the instance on the given network. :param port_client: The client to use to create the port. :param instance: Create the port for the given instance. :param network_id: Create the port on the given network. :param port_req_body: Pre-populated port request. Should have the device_id, device_owner, and any required neutron extension values. :returns: the created port. :raises PortLimitExceeded: If neutron fails with an OverQuota error. :raises NoMoreFixedIps: If neutron fails with IpAddressGenerationFailure error. """ try: port_req_body['port']['network_id'] = network_id port_req_body['port']['admin_state_up'] = True port_req_body['port']['tenant_id'] = instance['project_id'] if port_name: port_req_body['port']['name'] = port_name if mac_address: port_req_body['port']['mac_address'] = mac_address port_info = port_client.create_port(port_req_body)['port'] LOG.debug('Successfully created port: %s', port_info['id'], instance=instance) return port_info except neutron_client_exc.OverQuotaClient: LOG.warning( _LW('Neutron error: Port quota exceeded in tenant: %s'), port_req_body['port']['tenant_id'], instance=instance) raise exception.PortLimitExceeded() except neutron_client_exc.IpAddressGenerationFailureClient: LOG.warning(_LW('Neutron error: No more fixed IPs in network: %s'), network_id, instance=instance) raise exception.NoMoreFixedIps() except neutron_client_exc.MacAddressInUseClient: LOG.warning(_LW('Neutron error: MAC address %(mac)s is already ' 'in use on network %(network)s.') % { 'mac': mac_address, 'network': network_id }, instance=instance) raise exception.PortInUse(port_id=mac_address) except neutron_client_exc.NeutronClientException: with excutils.save_and_reraise_exception(): LOG.exception(_LE('Neutron error creating port on network %s'), network_id, instance=instance)
def test_attach_interface_port_in_use(self, attach_mock, get_mock): fake_instance = objects.Instance(uuid=FAKE_UUID1) get_mock.return_value = fake_instance attach_mock.side_effect = exception.PortInUse(port_id=FAKE_PORT_ID1) body = {} self.assertRaises(self.in_use_exc, self.attachments.create, self.req, FAKE_UUID1, body=body) ctxt = self.req.environ['nova.context'] attach_mock.assert_called_once_with(ctxt, fake_instance, None, None, None) get_mock.assert_called_once_with(ctxt, FAKE_UUID1, expected_attrs=None)
def check_port_usable(self, context, instance, port_id): if port_id is None: return neutron = neutronv2.get_client(context) try: port = neutron.show_port(port_id)['port'] except neutron_client_exc.PortNotFoundClient: raise exception.PortNotFound(port_id=port_id) if port['tenant_id'] != instance.project_id: raise exception.PortNotUsable(port_id=port_id, instance=instance.uuid) if port.get('device_id'): raise exception.PortInUse(port_id=port_id)
def validate_networks(self, context, requested_networks): """Validate that the tenant can use the requested networks.""" LOG.debug(_('validate_networks() for %s'), requested_networks) if not requested_networks: nets = self._get_available_networks(context, context.project_id) if len(nets) > 1: # Attaching to more than one network by default doesn't # make sense, as the order will be arbitrary and the guest OS # won't know which to configure msg = _("Multiple possible networks found, use a Network " "ID to be more specific.") raise exception.NetworkAmbiguous(msg) return net_ids = [] for (net_id, _i, port_id) in requested_networks: if port_id: try: port = (neutronv2.get_client(context) .show_port(port_id) .get('port')) except neutronv2.exceptions.NeutronClientException as e: if e.status_code == 404: port = None if not port: raise exception.PortNotFound(port_id=port_id) if port.get('device_id', None): raise exception.PortInUse(port_id=port_id) net_id = port['network_id'] if net_id in net_ids: raise exception.NetworkDuplicated(network_id=net_id) net_ids.append(net_id) # Now check to see if all requested networks exist nets = self._get_available_networks(context, context.project_id, net_ids) if len(nets) != len(net_ids): requsted_netid_set = set(net_ids) returned_netid_set = set([net['id'] for net in nets]) lostid_set = requsted_netid_set - returned_netid_set id_str = '' for _id in lostid_set: id_str = id_str and id_str + ', ' + _id or _id raise exception.NetworkNotFound(network_id=id_str)
def test_attach_interface_port_in_use(self, attach_mock, get_mock): fake_instance = objects.Instance(uuid=FAKE_UUID1) get_mock.return_value = fake_instance attach_mock.side_effect = exception.PortInUse(port_id=FAKE_PORT_ID1) req = webob.Request.blank(self.url + '/attach') req.method = 'POST' req.body = jsonutils.dumps({}) req.headers['content-type'] = 'application/json' req.environ['nova.context'] = self.context self.assertRaises(self.in_use_exc, self.attachments.create, req, FAKE_UUID1, body=jsonutils.loads(req.body)) attach_mock.assert_called_once_with(self.context, fake_instance, None, None, None) get_mock.assert_called_once_with(self.context, FAKE_UUID1, want_objects=True, expected_attrs=None)
def allocate_for_instance(self, context, instance, **kwargs): """Allocate network resources for the instance. :param requested_networks: optional value containing network_id, fixed_ip, and port_id :param security_groups: security groups to allocate for instance :param macs: None or a set of MAC addresses that the instance should use. macs is supplied by the hypervisor driver (contrast with requested_networks which is user supplied). NB: NeutronV2 currently assigns hypervisor supplied MAC addresses to arbitrary networks, which requires openflow switches to function correctly if more than one network is being used with the bare metal hypervisor (which is the only one known to limit MAC addresses). :param dhcp_options: None or a set of key/value pairs that should determine the DHCP BOOTP response, eg. for PXE booting an instance configured with the baremetal hypervisor. It is expected that these are already formatted for the quantum v2 api. See nova/virt/driver.py:dhcp_options_for_instance for an example. """ hypervisor_macs = kwargs.get('macs', None) available_macs = None dhcp_opts = None if hypervisor_macs is not None: # Make a copy we can mutate: records macs that have not been used # to create a port on a network. If we find a mac with a # pre-allocated port we also remove it from this set. available_macs = set(hypervisor_macs) neutron = neutronv2.get_client(context) LOG.debug(_('allocate_for_instance() for %s'), instance['display_name']) if not instance['project_id']: msg = _('empty project id for instance %s') raise exception.InvalidInput(reason=msg % instance['display_name']) requested_networks = kwargs.get('requested_networks') # Note: (dkehn) this option check should be removed as soon as support # in neutron released, see https://bugs.launchpad.net/nova/+bug/1214162 if CONF.dhcp_options_enabled: dhcp_opts = kwargs.get('dhcp_options', None) ports = {} fixed_ips = {} net_ids = [] if requested_networks: for network_id, fixed_ip, port_id in requested_networks: if port_id: port = neutron.show_port(port_id)['port'] if port.get('device_id'): raise exception.PortInUse(port_id=port_id) if hypervisor_macs is not None: if port['mac_address'] not in hypervisor_macs: raise exception.PortNotUsable( port_id=port_id, instance=instance['display_name']) else: # Don't try to use this MAC if we need to create a # port on the fly later. Identical MACs may be # configured by users into multiple ports so we # discard rather than popping. available_macs.discard(port['mac_address']) network_id = port['network_id'] ports[network_id] = port elif fixed_ip and network_id: fixed_ips[network_id] = fixed_ip if network_id: net_ids.append(network_id) nets = self._get_available_networks(context, instance['project_id'], net_ids) if not nets: LOG.warn(_("No network configured!"), instance=instance) return [] security_groups = kwargs.get('security_groups', []) security_group_ids = [] # TODO(arosen) Should optimize more to do direct query for security # group if len(security_groups) == 1 if len(security_groups): search_opts = {'tenant_id': instance['project_id']} user_security_groups = neutron.list_security_groups( **search_opts).get('security_groups') for security_group in security_groups: name_match = None uuid_match = None for user_security_group in user_security_groups: if user_security_group['name'] == security_group: if name_match: msg = (_("Multiple security groups found matching" " '%s'. Use an ID to be more specific."), security_group) raise exception.NoUniqueMatch(msg) name_match = user_security_group['id'] if user_security_group['id'] == security_group: uuid_match = user_security_group['id'] # If a user names the security group the same as # another's security groups uuid, the name takes priority. if not name_match and not uuid_match: raise exception.SecurityGroupNotFound( security_group_id=security_group) elif name_match: security_group_ids.append(name_match) elif uuid_match: security_group_ids.append(uuid_match) touched_port_ids = [] created_port_ids = [] for network in nets: # If security groups are requested on an instance then the # network must has a subnet associated with it. Some plugins # implement the port-security extension which requires # 'port_security_enabled' to be True for security groups. # That is why True is returned if 'port_security_enabled' # is not found. if (security_groups and not (network['subnets'] and network.get('port_security_enabled', True))): raise exception.SecurityGroupCannotBeApplied() network_id = network['id'] zone = 'compute:%s' % instance['availability_zone'] port_req_body = { 'port': { 'device_id': instance['uuid'], 'device_owner': zone } } try: port = ports.get(network_id) self._populate_neutron_extension_values( instance, port_req_body) # Requires admin creds to set port bindings port_client = (neutron if not self._has_port_binding_extension() else neutronv2.get_client(context, admin=True)) if port: port_client.update_port(port['id'], port_req_body) touched_port_ids.append(port['id']) else: created_port_ids.append( self._create_port(port_client, instance, network_id, port_req_body, fixed_ips.get(network_id), security_group_ids, available_macs, dhcp_opts)) except Exception: with excutils.save_and_reraise_exception(): for port_id in touched_port_ids: try: port_req_body = {'port': {'device_id': None}} # Requires admin creds to set port bindings if self._has_port_binding_extension(): port_req_body['port']['binding:host_id'] = None port_client = neutronv2.get_client(context, admin=True) else: port_client = neutron port_client.update_port(port_id, port_req_body) except Exception: msg = _("Failed to update port %s") LOG.exception(msg, port_id) for port_id in created_port_ids: try: neutron.delete_port(port_id) except Exception: msg = _("Failed to delete port %s") LOG.exception(msg, port_id) nw_info = self._get_instance_nw_info(context, instance, networks=nets) # NOTE(danms): Only return info about ports we created in this run. # In the initial allocation case, this will be everything we created, # and in later runs will only be what was created that time. Thus, # this only affects the attach case, not the original use for this # method. return network_model.NetworkInfo([ port for port in nw_info if port['id'] in created_port_ids + touched_port_ids ])
def validate_networks(self, context, requested_networks, num_instances): """Validate that the tenant can use the requested networks. Return the number of instances than can be successfully allocated with the requested network configuration. """ LOG.debug(_('validate_networks() for %s'), requested_networks) neutron = neutronv2.get_client(context) ports_needed_per_instance = 0 if not requested_networks: nets = self._get_available_networks(context, context.project_id, neutron=neutron) if len(nets) > 1: # Attaching to more than one network by default doesn't # make sense, as the order will be arbitrary and the guest OS # won't know which to configure msg = _("Multiple possible networks found, use a Network " "ID to be more specific.") raise exception.NetworkAmbiguous(msg) else: ports_needed_per_instance = 1 else: net_ids = [] for (net_id, _i, port_id) in requested_networks: if port_id: try: port = neutron.show_port(port_id).get('port') except neutronv2.exceptions.NeutronClientException as e: if e.status_code == 404: port = None else: with excutils.save_and_reraise_exception(): LOG.exception(_("Failed to access port %s"), port_id) if not port: raise exception.PortNotFound(port_id=port_id) if port.get('device_id', None): raise exception.PortInUse(port_id=port_id) if not port.get('fixed_ips'): raise exception.PortRequiresFixedIP(port_id=port_id) net_id = port['network_id'] else: ports_needed_per_instance += 1 if net_id in net_ids: raise exception.NetworkDuplicated(network_id=net_id) net_ids.append(net_id) # Now check to see if all requested networks exist nets = self._get_available_networks(context, context.project_id, net_ids, neutron=neutron) for net in nets: if not net.get('subnets'): raise exception.NetworkRequiresSubnet( network_uuid=net['id']) if len(nets) != len(net_ids): requsted_netid_set = set(net_ids) returned_netid_set = set([net['id'] for net in nets]) lostid_set = requsted_netid_set - returned_netid_set id_str = '' for _id in lostid_set: id_str = id_str and id_str + ', ' + _id or _id raise exception.NetworkNotFound(network_id=id_str) # Note(PhilD): Ideally Nova would create all required ports as part of # network validation, but port creation requires some details # from the hypervisor. So we just check the quota and return # how many of the requested number of instances can be created ports = [] quotas = neutron.show_quota(tenant_id=context.project_id)['quota'] if quotas.get('port', -1) == -1: # Unlimited Port Quota return num_instances else: free_ports = quotas.get('port') - len(ports) ports_needed = ports_needed_per_instance * num_instances if free_ports >= ports_needed: return num_instances else: return free_ports // ports_needed_per_instance
def allocate_for_instance(self, context, instance, **kwargs): """Allocate network resources for the instance. :param context: The request context. :param instance: nova.objects.instance.Instance object. :param requested_networks: optional value containing network_id, fixed_ip, and port_id :param security_groups: security groups to allocate for instance :param macs: None or a set of MAC addresses that the instance should use. macs is supplied by the hypervisor driver (contrast with requested_networks which is user supplied). NB: NeutronV2 currently assigns hypervisor supplied MAC addresses to arbitrary networks, which requires openflow switches to function correctly if more than one network is being used with the bare metal hypervisor (which is the only one known to limit MAC addresses). :param dhcp_options: None or a set of key/value pairs that should determine the DHCP BOOTP response, eg. for PXE booting an instance configured with the baremetal hypervisor. It is expected that these are already formatted for the neutron v2 api. See nova/virt/driver.py:dhcp_options_for_instance for an example. """ hypervisor_macs = kwargs.get('macs', None) available_macs = None if hypervisor_macs is not None: # Make a copy we can mutate: records macs that have not been used # to create a port on a network. If we find a mac with a # pre-allocated port we also remove it from this set. available_macs = set(hypervisor_macs) neutron = neutronv2.get_client(context) LOG.debug('allocate_for_instance()', instance=instance) if not instance.project_id: msg = _('empty project id for instance %s') raise exception.InvalidInput(reason=msg % instance.uuid) requested_networks = kwargs.get('requested_networks') dhcp_opts = kwargs.get('dhcp_options', None) ports = {} net_ids = [] ordered_networks = [] if requested_networks: for request in requested_networks: if request.port_id: try: port = neutron.show_port(request.port_id)['port'] except neutron_client_exc.PortNotFoundClient: raise exception.PortNotFound(port_id=request.port_id) if port['tenant_id'] != instance.project_id: raise exception.PortNotUsable(port_id=request.port_id, instance=instance.uuid) if (port.get('device_id') and port.get('device_id') != instance['uuid']): raise exception.PortInUse(port_id=request.port_id) if hypervisor_macs is not None: if port['mac_address'] not in hypervisor_macs: raise exception.PortNotUsable( port_id=request.port_id, instance=instance.uuid) else: # Don't try to use this MAC if we need to create a # port on the fly later. Identical MACs may be # configured by users into multiple ports so we # discard rather than popping. available_macs.discard(port['mac_address']) request.network_id = port['network_id'] ports[request.port_id] = port if request.network_id: net_ids.append(request.network_id) ordered_networks.append(request) nets = self._get_available_networks(context, instance.project_id, net_ids) if not nets: LOG.warn(_LW("No network configured!"), instance=instance) return network_model.NetworkInfo([]) # if this function is directly called without a requested_network param # or if it is indirectly called through allocate_port_for_instance() # with None params=(network_id=None, requested_ip=None, port_id=None, # pci_request_id=None): if (not requested_networks or requested_networks.is_single_unspecified): # bug/1267723 - if no network is requested and more # than one is available then raise NetworkAmbiguous Exception if len(nets) > 1: msg = _("Multiple possible networks found, use a Network " "ID to be more specific.") raise exception.NetworkAmbiguous(msg) ordered_networks.append( objects.NetworkRequest(network_id=nets[0]['id'])) db_req_networks = list() db_obj = huawei_instance_extra.HuaweiInstanceExtra( instance_uuid=instance.uuid) db_instance = db_obj.get_by_instance_uuid( context, instance_uuid=instance.uuid) if db_instance.request_network: db_req_networks = jsonutils.loads(db_instance.request_network) db_req_networks.append([nets[0]['id'], None, None]) db_obj.request_network = jsonutils.dumps(db_req_networks) db_obj.create(context) # NOTE(): check external net attach permission after the # check for ambiguity, there could be another # available net which is permitted bug/1364344 self._check_external_network_attach(context, nets) security_groups = kwargs.get('security_groups', []) security_group_ids = [] # TODO() Should optimize more to do direct query for security # group if len(security_groups) == 1 if len(security_groups): search_opts = {'tenant_id': instance.project_id} user_security_groups = neutron.list_security_groups( **search_opts).get('security_groups') for security_group in security_groups: name_match = None uuid_match = None for user_security_group in user_security_groups: if user_security_group['name'] == security_group: if name_match: raise exception.NoUniqueMatch( _("Multiple security groups found matching" " '%s'. Use an ID to be more specific.") % security_group) name_match = user_security_group['id'] if user_security_group['id'] == security_group: uuid_match = user_security_group['id'] # If a user names the security group the same as # another's security groups uuid, the name takes priority. if not name_match and not uuid_match: raise exception.SecurityGroupNotFound( security_group_id=security_group) elif name_match: security_group_ids.append(name_match) elif uuid_match: security_group_ids.append(uuid_match) touched_port_ids = [] created_port_ids = [] ports_in_requested_order = [] nets_in_requested_order = [] for request in ordered_networks: # Network lookup for available network_id network = None for net in nets: if net['id'] == request.network_id: network = net break # if network_id did not pass validate_networks() and not available # here then skip it safely not continuing with a None Network else: continue nets_in_requested_order.append(network) # If security groups are requested on an instance then the # network must has a subnet associated with it. Some plugins # implement the port-security extension which requires # 'port_security_enabled' to be True for security groups. # That is why True is returned if 'port_security_enabled' # is not found. if (security_groups and not (network['subnets'] and network.get('port_security_enabled', True))): # add for roll back self._delete_ports(neutron, instance, created_port_ids) raise exception.SecurityGroupCannotBeApplied() request.network_id = network['id'] zone = 'compute:%s' % instance.availability_zone port_req_body = { 'port': { 'device_id': instance.uuid, 'device_owner': zone } } try: self._populate_neutron_extension_values( context, instance, request.pci_request_id, port_req_body) # Requires admin creds to set port bindings port_client = (neutron if not self._has_port_binding_extension(context) else neutronv2.get_client(context, admin=True)) if request.port_id: port = ports[request.port_id] port_client.update_port(port['id'], port_req_body) touched_port_ids.append(port['id']) ports_in_requested_order.append(port['id']) else: created_port = self._create_port(port_client, instance, request.network_id, port_req_body, request.address, security_group_ids, available_macs, dhcp_opts) created_port_ids.append(created_port) ports_in_requested_order.append(created_port) except Exception: with excutils.save_and_reraise_exception(): for port_id in touched_port_ids: try: port_req_body = {'port': {'device_id': ''}} # Requires admin creds to set port bindings if self._has_port_binding_extension(context): port_req_body['port']['binding:host_id'] = None port_client = neutronv2.get_client(context, admin=True) else: port_client = neutron port_client.update_port(port_id, port_req_body) except Exception: msg = _LE("Failed to update port %s") LOG.exception(msg, port_id) self._delete_ports(neutron, instance, created_port_ids) pci_list = kwargs.get('pci_list', []) nw_info = self.get_instance_nw_info(context, instance, networks=nets_in_requested_order, port_ids=ports_in_requested_order, pci_list=pci_list) # NOTE(): Only return info about ports we created in this run. # In the initial allocation case, this will be everything we created, # and in later runs will only be what was created that time. Thus, # this only affects the attach case, not the original use for this # method. return network_model.NetworkInfo([ vif for vif in nw_info if vif['id'] in created_port_ids + touched_port_ids ])
def allocate_for_instance(self, context, instance, **kwargs): """Allocate network resources for the instance. :param context: The request context. :param instance: nova.objects.instance.Instance object. :param requested_networks: optional value containing network_id, fixed_ip, and port_id :param security_groups: security groups to allocate for instance :param macs: None or a set of MAC addresses that the instance should use. macs is supplied by the hypervisor driver (contrast with requested_networks which is user supplied). NB: GluonV2 currently assigns hypervisor supplied MAC addresses to arbitrary networks, which requires openflow switches to function correctly if more than one network is being used with the bare metal hypervisor (which is the only one known to limit MAC addresses). :param dhcp_options: None or a set of key/value pairs that should determine the DHCP BOOTP response, eg. for PXE booting an instance configured with the baremetal hypervisor. It is expected that these are already formatted for the gluon v2 api. TODO and likely this is not true... See nova/virt/driver.py:dhcp_options_for_instance for an example. """ hypervisor_macs = kwargs.get('macs', None) # The gluon client and port_client (either the admin context or # tenant context) are read here. The reason for this is that there are # a number of different calls for the instance allocation. # We do not want to create a new gluon session for each of these # calls. client = self.client LOG.debug('allocate_for_instance()', instance=instance) # Not that this should ever happen, but we can't check permissions if # there is no owning project id on the instance. if not instance.project_id: msg = _('empty project id for instance %s') raise exception.InvalidInput(reason=msg % instance.uuid) # requested NICs, actually (networks for historical reasons) requested_networks = kwargs.get('requested_networks') # We are passed DHCP options, but we don't use them because our ports exist # already. This is only for fresh ports. # dhcp_opts = kwargs.get('dhcp_options', None) # if this function is directly called without a requested_network param # or if it is indirectly called through allocate_port_for_instance() # with None params=(network_id=None, requested_ip=None, port_id=None, # pci_request_id=None): if (not requested_networks or requested_networks.is_single_unspecified): # This used to mean 'attach to all networks', but Gluon # knows not of networks raise NotImplementedError( "NICs must be explicitly bound to ports: %s" % requested_networks) for request in requested_networks: if request.port_id: # Check gluon has heard of this, and something else (another VM under Nova, or another # service than Nova) has not already started using it try: unbound = client.is_unbound(request.port_id) LOG.debug('This port %s is bound? %s' % (request.port_id, 'no' if unbound else 'yes')) if not unbound: raise exception.PortInUse(port_id=request.port_id) except gluon_client_exc.PortNotFoundClient: raise exception.PortNotFound(port_id=request.port_id) # TODO ownership / rights check #if port['tenant_id'] != instance.project_id: # raise exception.PortNotUsable(port_id=request.port_id, # instance=instance.uuid) if hypervisor_macs is not None: if port.get('mac_address') is None or port[ 'mac_address'] not in hypervisor_macs: # Can't use for this if the port is not given a MAC up front or # the port's MAC isn't suitable raise exception.PortNotUsable(port_id=request.port_id, instance=instance.uuid) # yes, two ports can have the same MAC... else: # Only attachment by port works. raise NotImplementedError('Must attach NICs by port-id') if request.network_id: # Just in case the previous didn't pick that up: raise NotImplementedError("Gluon doesn't understand networks") if request.address: raise NotImplementedError( "Gluon doesn't allow address setting") # Note: security groups are provided to the Nova VM, but not used because we never create # fresh ports, only use existing ones (that should already have security set). # Attempt to perform the port binding for all ports being attached to this VM zone = 'compute:%s' % instance.availability_zone bound_ports = [] try: for request in requested_networks: self._bind_port(request.port_id, zone, instance, request.pci_request_id) bound_ports.append(request.port_id) except Exception: with excutils.save_and_reraise_exception(): self._unbind_ports(context, bound_ports) LOG.error( 'attempting to update cached data following a bind: %d ports' % len(bound_ports)) nw_info = self.get_instance_nw_info(context, instance, port_ids=bound_ports) # NOTE(danms): Only return info about ports we created in this run. # In the initial allocation case, this will be everything we created, # and in later runs will only be what was created that time. Thus, # this only affects the attach case, not the original use for this # method. return network_model.NetworkInfo([vif for vif in nw_info])
def create(self, req, server_id, body): """Attach an interface to an instance.""" context = req.environ['nova.context'] authorize(context) network_id = None port_id = None req_ip = None if body: attachment = body['interfaceAttachment'] network_id = attachment.get('net_id', None) port_id = attachment.get('port_id', None) try: req_ip = attachment['fixed_ips'][0]['ip_address'] except Exception: pass if network_id and port_id: msg = _("Must not input both network_id and port_id") raise exc.HTTPBadRequest(explanation=msg) if req_ip and not network_id: msg = _("Must input network_id when request IP address") raise exc.HTTPBadRequest(explanation=msg) #if the port has attached in this instance, raise port_info = {} if utils.is_neutron(): if port_id: try: port_info = self.network_api.show_port(context, port_id) except exception.NotFound as e: raise exc.HTTPNotFound(explanation=e.format_message()) if port_info['port']['device_id']: raise exception.PortInUse(port_id=port_id) #check network is duplicated or not when allow_duplicate_networks is false if utils.is_neutron(): if not cfg.CONF.neutron.allow_duplicate_networks: search_opts = {'device_id': server_id} try: data = self.network_api.list_ports(context, **search_opts) except exception.NotFound as e: raise exc.HTTPNotFound(explanation=e.format_message()) except NotImplementedError: msg = _("Network driver does not support this function.") raise webob.exc.HTTPNotImplemented(explanation=msg) ports = data.get('ports', []) instance_networks = [] for port in ports: instance_networks.append(port.get('network_id')) if port_id: network_id = port_info['port'].get('network_id') if network_id in instance_networks: raise exception.NetworkDuplicated(network_id=network_id) try: instance = common.get_instance(self.compute_api, context, server_id, want_objects=True) LOG.audit(_("Attach interface"), instance=instance) vif = self.compute_api.attach_interface(context, instance, network_id, port_id, req_ip) except (exception.PortNotFound, exception.FixedIpAlreadyInUse, exception.PortInUse, exception.NetworkDuplicated, exception.NetworkAmbiguous, exception.NetworkNotFound, exception.PortNotUsable) as e: raise exc.HTTPBadRequest(explanation=e.format_message()) except exception.InstanceIsLocked as e: raise exc.HTTPConflict(explanation=e.format_message()) except NotImplementedError: msg = _("Network driver does not support this function.") raise webob.exc.HTTPNotImplemented(explanation=msg) except exception.InterfaceAttachFailed as e: LOG.exception(e) msg = _("Failed to attach interface") raise webob.exc.HTTPInternalServerError(explanation=msg) except exception.InstanceInvalidState as state_error: common.raise_http_conflict_for_instance_invalid_state(state_error, 'attach_interface') return self.show(req, server_id, vif['id'])