def test_update_port_address_with_binding(self, mock_unp, mock_client): address = 'fe:54:00:77:07:d9' port_id = 'fake-port-id' mock_client.return_value.show_port.return_value = { 'port': { 'binding:host_id': 'host', 'binding:profile': 'foo' } } calls = [ mock.call(port_id, {'port': { 'mac_address': address }}), mock.call(port_id, { 'port': { 'binding:host_id': 'host', 'binding:profile': 'foo' } }) ] neutron.update_port_address(port_id, address) mock_unp.assert_called_once_with(port_id, client=mock_client()) mock_client.return_value.update_port.assert_has_calls(calls)
def test_update_port_address(self, mock_client): address = 'fe:54:00:77:07:d9' port_id = 'fake-port-id' expected = {'port': {'mac_address': address}} mock_client.return_value.show_port.return_value = {} neutron.update_port_address(port_id, address) mock_client.return_value.update_port.assert_called_once_with(port_id, expected)
def test_update_port_address(self, mock_client): address = 'fe:54:00:77:07:d9' port_id = 'fake-port-id' expected = {'port': {'mac_address': address}} mock_client.return_value.show_port.return_value = {} neutron.update_port_address(port_id, address) mock_client.return_value.update_port.assert_called_once_with( port_id, expected)
def vif_attach(self, task, vif_info): """Attach a virtual network interface to a node :param task: A TaskManager instance. :param vif_info: a dictionary of information about a VIF. It must have an 'id' key, whose value is a unique identifier for that VIF. :raises: NetworkError, VifAlreadyAttached, NoFreePhysicalPorts """ vif_id = vif_info['id'] # Sort ports by pxe_enabled to ensure we always bind pxe_enabled ports # first sorted_ports = sorted(task.ports, key=lambda p: p.pxe_enabled, reverse=True) free_ports = [] # Check all ports to ensure this VIF isn't already attached for port in sorted_ports: port_id = port.internal_info.get(TENANT_VIF_KEY, port.extra.get('vif_port_id')) if port_id is None: free_ports.append(port) elif port_id == vif_id: raise exception.VifAlreadyAttached( vif=vif_id, port_uuid=port.uuid) if not free_ports: raise exception.NoFreePhysicalPorts(vif=vif_id) # Get first free port port = free_ports.pop(0) # Check if the requested vif_id is a neutron port. If it is # then attempt to update the port's MAC address. try: client = neutron.get_client(task.context.auth_token) client.show_port(vif_id) except neutron_exceptions.NeutronClientException: # NOTE(sambetts): If a client error occurs this is because either # neutron doesn't exist because we're running in standalone # environment or we can't find a matching neutron port which means # a user might be requesting a non-neutron port. So skip trying to # update the neutron port MAC address in these cases. pass else: try: neutron.update_port_address(vif_id, port.address) except exception.FailedToUpdateMacOnPort: raise exception.NetworkError(_( "Unable to attach VIF %(vif)s because Ironic can not " "update Neutron port %(port)s MAC address to match " "physical MAC address %(mac)s") % { 'vif': vif_id, 'port': vif_id, 'mac': port.address}) int_info = port.internal_info int_info[TENANT_VIF_KEY] = vif_id port.internal_info = int_info port.save()
def test_update_port_address_without_binding(self, mock_unp, mock_client): address = 'fe:54:00:77:07:d9' port_id = 'fake-port-id' expected = {'port': {'mac_address': address}} mock_client.return_value.show_port.return_value = { 'port': {'binding:profile': 'foo'}} neutron.update_port_address(port_id, address, context=self.context) self.assertFalse(mock_unp.called) mock_client.return_value.update_port.assert_any_call(port_id, expected)
def test_update_port_address_without_binding(self, mock_unp, mock_client): address = 'fe:54:00:77:07:d9' port_id = 'fake-port-id' expected = {'port': {'mac_address': address}} mock_client.return_value.show_port.return_value = { 'port': {'binding:profile': 'foo'}} neutron.update_port_address(port_id, address) self.assertFalse(mock_unp.called) mock_client.return_value.update_port.assert_any_call(port_id, expected)
def vif_attach(self, task, vif_info): """Attach a virtual network interface to a node Attach a virtual interface to a node. It will use the first free port group. If there are no free port groups, then the first available port (pxe_enabled preferably) is used. :param task: A TaskManager instance. :param vif_info: a dictionary of information about a VIF. It must have an 'id' key, whose value is a unique identifier for that VIF. :raises: NetworkError, VifAlreadyAttached, NoFreePhysicalPorts """ vif_id = vif_info['id'] port_like_obj = get_free_port_like_object(task, vif_id) client = neutron.get_client() # Address is optional for portgroups if port_like_obj.address: # Check if the requested vif_id is a neutron port. If it is # then attempt to update the port's MAC address. try: client.show_port(vif_id) except neutron_exceptions.NeutronClientException: # NOTE(sambetts): If a client error occurs this is because # either neutron doesn't exist because we're running in # standalone environment or we can't find a matching neutron # port which means a user might be requesting a non-neutron # port. So skip trying to update the neutron port MAC address # in these cases. pass else: try: neutron.update_port_address(vif_id, port_like_obj.address) except exception.FailedToUpdateMacOnPort: raise exception.NetworkError( _("Unable to attach VIF %(vif)s because Ironic can not " "update Neutron port %(port)s MAC address to match " "physical MAC address %(mac)s") % { 'vif': vif_id, 'port': vif_id, 'mac': port_like_obj.address }) int_info = port_like_obj.internal_info int_info[TENANT_VIF_KEY] = vif_id port_like_obj.internal_info = int_info port_like_obj.save() # NOTE(vsaienko) allow to attach VIF to active instance. if task.node.provision_state == states.ACTIVE: plug_port_to_tenant_network(task, port_like_obj, client=client)
def test_update_port_address_with_binding(self, mock_unp, mock_client): address = 'fe:54:00:77:07:d9' port_id = 'fake-port-id' expected = {'port': {'mac_address': address, 'binding:host_id': 'host', 'binding:profile': 'foo'}} mock_client.return_value.show_port.return_value = { 'port': {'binding:host_id': 'host', 'binding:profile': 'foo'}} neutron.update_port_address(port_id, address) mock_unp.assert_called_once_with(port_id, client=mock_client()) mock_client.return_value.update_port.assert_any_call(port_id, expected)
def portgroup_changed(self, task, portgroup_obj): """Handle any actions required when a portgroup changes :param task: a TaskManager instance. :param portgroup_obj: a changed Portgroup object from the API before it is saved to database. :raises: FailedToUpdateDHCPOptOnPort, Conflict """ context = task.context portgroup_uuid = portgroup_obj.uuid # NOTE(vsaienko) address is not mandatory field in portgroup. # Do not touch neutron port if we removed address on portgroup. if ('address' in portgroup_obj.obj_what_changed() and portgroup_obj.address): pg_vif = (portgroup_obj.internal_info.get(TENANT_VIF_KEY) or portgroup_obj.extra.get('vif_port_id')) if pg_vif: neutron.update_port_address(pg_vif, portgroup_obj.address) if 'extra' in portgroup_obj.obj_what_changed(): original_portgroup = objects.Portgroup.get_by_id( context, portgroup_obj.id) if (portgroup_obj.extra.get('vif_port_id') and portgroup_obj.extra['vif_port_id'] != original_portgroup.extra.get('vif_port_id')): utils.warn_about_deprecated_extra_vif_port_id() if ('standalone_ports_supported' in portgroup_obj.obj_what_changed()): if not portgroup_obj.standalone_ports_supported: ports = [ p for p in task.ports if p.portgroup_id == portgroup_obj.id ] for p in ports: vif = p.internal_info.get(TENANT_VIF_KEY, p.extra.get('vif_port_id')) reason = [] if p.pxe_enabled: reason.append("'pxe_enabled' is set to True") if vif: reason.append('VIF %s is attached to this port' % vif) if reason: msg = (_("standalone_ports_supported can not be set " "to False, because the port group %(pg_id)s " "contains port with %(reason)s") % { 'pg_id': portgroup_uuid, 'reason': ', '.join(reason) }) raise exception.Conflict(msg)
def portgroup_changed(self, task, portgroup_obj): """Handle any actions required when a portgroup changes :param task: a TaskManager instance. :param portgroup_obj: a changed Portgroup object from the API before it is saved to database. :raises: FailedToUpdateDHCPOptOnPort, Conflict """ context = task.context portgroup_uuid = portgroup_obj.uuid # NOTE(vsaienko) address is not mandatory field in portgroup. # Do not touch neutron port if we removed address on portgroup. if ('address' in portgroup_obj.obj_what_changed() and portgroup_obj.address): pg_vif = (portgroup_obj.internal_info.get(TENANT_VIF_KEY) or portgroup_obj.extra.get('vif_port_id')) if pg_vif: neutron.update_port_address(pg_vif, portgroup_obj.address) if 'extra' in portgroup_obj.obj_what_changed(): original_portgroup = objects.Portgroup.get_by_id(context, portgroup_obj.id) if (portgroup_obj.extra.get('vif_port_id') and portgroup_obj.extra['vif_port_id'] != original_portgroup.extra.get('vif_port_id')): utils.warn_about_deprecated_extra_vif_port_id() if ('standalone_ports_supported' in portgroup_obj.obj_what_changed()): if not portgroup_obj.standalone_ports_supported: ports = [p for p in task.ports if p.portgroup_id == portgroup_obj.id] for p in ports: vif = p.internal_info.get( TENANT_VIF_KEY, p.extra.get('vif_port_id')) reason = [] if p.pxe_enabled: reason.append("'pxe_enabled' is set to True") if vif: reason.append('VIF %s is attached to this port' % vif) if reason: msg = (_("standalone_ports_supported can not be set " "to False, because the port group %(pg_id)s " "contains port with %(reason)s") % { 'pg_id': portgroup_uuid, 'reason': ', '.join(reason)}) raise exception.Conflict(msg)
def update_port_address(self, port_id, address, token=None): """Update a port's mac address. :param port_id: Neutron port id. :param address: new MAC address. :param token: optional auth token. :raises: FailedToUpdateMacOnPort """ global update_port_address_deprecation if not update_port_address_deprecation: LOG.warning('update_port_address via DHCP provider is ' 'deprecated. The node.network_interface ' 'port_changed() should be used instead.') update_port_address_deprecation = True neutron.update_port_address(port_id, address, token)
def portgroup_changed(self, task, portgroup_obj): """Handle any actions required when a portgroup changes :param task: a TaskManager instance. :param portgroup_obj: a changed Portgroup object from the API before it is saved to database. :raises: FailedToUpdateDHCPOptOnPort, Conflict """ portgroup_uuid = portgroup_obj.uuid # NOTE(vsaienko) address is not mandatory field in portgroup. # Do not touch neutron port if we removed address on portgroup. if ('address' in portgroup_obj.obj_what_changed() and portgroup_obj.address): pg_vif = self._get_vif_id_by_port_like_obj(portgroup_obj) if pg_vif: neutron.update_port_address(pg_vif, portgroup_obj.address, context=task.context) if ('standalone_ports_supported' in portgroup_obj.obj_what_changed()): if not portgroup_obj.standalone_ports_supported: ports = [ p for p in task.ports if p.portgroup_id == portgroup_obj.id ] for p in ports: vif = self._get_vif_id_by_port_like_obj(p) reason = [] if p.pxe_enabled: reason.append("'pxe_enabled' is set to True") if vif: reason.append('VIF %s is attached to this port' % vif) if reason: msg = (_("standalone_ports_supported can not be set " "to False, because the port group %(pg_id)s " "contains port with %(reason)s") % { 'pg_id': portgroup_uuid, 'reason': ', '.join(reason) }) raise exception.Conflict(msg)
def portgroup_changed(self, task, portgroup_obj): """Handle any actions required when a portgroup changes :param task: a TaskManager instance. :param portgroup_obj: a changed Portgroup object from the API before it is saved to database. :raises: FailedToUpdateDHCPOptOnPort, Conflict """ context = task.context portgroup_uuid = portgroup_obj.uuid if 'address' in portgroup_obj.obj_what_changed(): pg_vif = portgroup_obj.extra.get('vif_port_id') if pg_vif: neutron.update_port_address(pg_vif, portgroup_obj.address, token=context.auth_token) if ('standalone_ports_supported' in portgroup_obj.obj_what_changed()): if not portgroup_obj.standalone_ports_supported: ports = [p for p in task.ports if p.portgroup_id == portgroup_obj.id] for p in ports: vif = p.internal_info.get( TENANT_VIF_KEY, p.extra.get('vif_port_id')) reason = [] if p.pxe_enabled: reason.append("'pxe_enabled' is set to True") if vif: reason.append('VIF %s is attached to this port' % vif) if reason: msg = (_("standalone_ports_supported can not be set " "to False, because the port group %(pg_id)s " "contains port with %(reason)s") % { 'pg_id': portgroup_uuid, 'reason': ', '.join(reason)}) raise exception.Conflict(msg)
def portgroup_changed(self, task, portgroup_obj): """Handle any actions required when a portgroup changes :param task: a TaskManager instance. :param portgroup_obj: a changed Portgroup object from the API before it is saved to database. :raises: FailedToUpdateDHCPOptOnPort, Conflict """ portgroup_uuid = portgroup_obj.uuid # NOTE(vsaienko) address is not mandatory field in portgroup. # Do not touch neutron port if we removed address on portgroup. if ('address' in portgroup_obj.obj_what_changed() and portgroup_obj.address): pg_vif = self._get_vif_id_by_port_like_obj(portgroup_obj) if pg_vif: neutron.update_port_address(pg_vif, portgroup_obj.address, context=task.context) if ('standalone_ports_supported' in portgroup_obj.obj_what_changed()): if not portgroup_obj.standalone_ports_supported: ports = [p for p in task.ports if p.portgroup_id == portgroup_obj.id] for p in ports: vif = self._get_vif_id_by_port_like_obj(p) reason = [] if p.pxe_enabled: reason.append("'pxe_enabled' is set to True") if vif: reason.append('VIF %s is attached to this port' % vif) if reason: msg = (_("standalone_ports_supported can not be set " "to False, because the port group %(pg_id)s " "contains port with %(reason)s") % { 'pg_id': portgroup_uuid, 'reason': ', '.join(reason)}) raise exception.Conflict(msg)
def port_changed(self, task, port_obj): """Handle any actions required when a port changes :param task: a TaskManager instance. :param port_obj: a changed Port object from the API before it is saved to database. :raises: FailedToUpdateDHCPOptOnPort, Conflict """ context = task.context node = task.node port_uuid = port_obj.uuid portgroup_obj = None if port_obj.portgroup_id: portgroup_obj = [ pg for pg in task.portgroups if pg.id == port_obj.portgroup_id ][0] vif = (port_obj.internal_info.get(TENANT_VIF_KEY) or port_obj.extra.get('vif_port_id')) if 'address' in port_obj.obj_what_changed(): if vif: neutron.update_port_address(vif, port_obj.address) if 'extra' in port_obj.obj_what_changed(): original_port = objects.Port.get_by_id(context, port_obj.id) updated_client_id = port_obj.extra.get('client-id') if (port_obj.extra.get('vif_port_id') and (port_obj.extra['vif_port_id'] != original_port.extra.get('vif_port_id'))): utils.warn_about_deprecated_extra_vif_port_id() if (original_port.extra.get('client-id') != updated_client_id): # DHCP Option with opt_value=None will remove it # from the neutron port if vif: api = dhcp_factory.DHCPFactory() client_id_opt = { 'opt_name': 'client-id', 'opt_value': updated_client_id } api.provider.update_port_dhcp_opts(vif, [client_id_opt]) # Log warning if there is no VIF and an instance # is associated with the node. elif node.instance_uuid: LOG.warning( _LW("No VIF found for instance %(instance)s " "port %(port)s when attempting to update port " "client-id."), { 'port': port_uuid, 'instance': node.instance_uuid }) if portgroup_obj and ((set(port_obj.obj_what_changed()) & {'pxe_enabled', 'portgroup_id'}) or vif): if not portgroup_obj.standalone_ports_supported: reason = [] if port_obj.pxe_enabled: reason.append("'pxe_enabled' was set to True") if vif: reason.append('VIF %s is attached to the port' % vif) if reason: msg = (_("Port group %(portgroup)s doesn't support " "standalone ports. This port %(port)s cannot be " " a member of that port group because of: " "%(reason)s") % { "reason": ', '.join(reason), "portgroup": portgroup_obj.uuid, "port": port_uuid }) raise exception.Conflict(msg)
def vif_attach(self, task, vif_info): """Attach a virtual network interface to a node Attach a virtual interface to a node. When selecting a port or portgroup to attach the virtual interface to, the following ordered criteria are applied: * Require ports or portgroups to have a physical network that is either None or one of the VIF's allowed physical networks. * Prefer ports or portgroups with a physical network field which is not None. * Prefer portgroups to ports. * Prefer ports with PXE enabled. :param task: A TaskManager instance. :param vif_info: a dictionary of information about a VIF. It must have an 'id' key, whose value is a unique identifier for that VIF. :raises: NetworkError, VifAlreadyAttached, NoFreePhysicalPorts :raises: PortgroupPhysnetInconsistent if one of the node's portgroups has ports which are not all assigned the same physical network. """ vif_id = vif_info['id'] client = neutron.get_client() # Determine whether any of the node's ports have a physical network. If # not, we don't need to check the VIF's network's physical networks as # they will not affect the VIF to port mapping. physnets = set() if any(port.physical_network is not None for port in task.ports): try: physnets = neutron.get_physnets_by_port_uuid(client, vif_id) except (exception.InvalidParameterValue, exception.NetworkError): # TODO(mgoddard): Remove this except clause and handle errors # properly. We can do this once a strategy has been determined # for handling the tempest VIF tests in an environment that # may not support neutron. # NOTE(sambetts): If a client error occurs this is because # either neutron doesn't exist because we're running in # standalone environment or we can't find a matching neutron # port which means a user might be requesting a non-neutron # port. Assume no physical network information exists in these # cases. pass if len(physnets) > 1: # NOTE(mgoddard): Neutron cannot currently handle hosts which # are mapped to multiple segments in the same routed network. node_physnets = network.get_physnets_for_node(task) if len(node_physnets.intersection(physnets)) > 1: reason = _("Node has ports which map to multiple segments " "of the routed network to which the VIF is " "attached. Currently neutron only supports " "hosts which map to one segment of a routed " "network") raise exception.VifInvalidForAttach( node=task.node.uuid, vif=vif_id, reason=reason) port_like_obj = get_free_port_like_object(task, vif_id, physnets) # Address is optional for portgroups if port_like_obj.address: # Check if the requested vif_id is a neutron port. If it is # then attempt to update the port's MAC address. try: client.show_port(vif_id) except neutron_exceptions.NeutronClientException: # TODO(mgoddard): Remove this except clause and handle errors # properly. We can do this once a strategy has been determined # for handling the tempest VIF tests in an environment that # may not support neutron. # NOTE(sambetts): If a client error occurs this is because # either neutron doesn't exist because we're running in # standalone environment or we can't find a matching neutron # port which means a user might be requesting a non-neutron # port. So skip trying to update the neutron port MAC address # in these cases. pass else: try: neutron.update_port_address(vif_id, port_like_obj.address) except exception.FailedToUpdateMacOnPort: raise exception.NetworkError(_( "Unable to attach VIF %(vif)s because Ironic can not " "update Neutron port %(port)s MAC address to match " "physical MAC address %(mac)s") % { 'vif': vif_id, 'port': vif_id, 'mac': port_like_obj.address}) int_info = port_like_obj.internal_info int_info[TENANT_VIF_KEY] = vif_id port_like_obj.internal_info = int_info port_like_obj.save() # NOTE(vsaienko) allow to attach VIF to active instance. if task.node.provision_state == states.ACTIVE: plug_port_to_tenant_network(task, port_like_obj, client=client)
def port_changed(self, task, port_obj): """Handle any actions required when a port changes :param task: a TaskManager instance. :param port_obj: a changed Port object from the API before it is saved to database. :raises: FailedToUpdateDHCPOptOnPort, Conflict """ context = task.context node = task.node port_uuid = port_obj.uuid portgroup_obj = None if port_obj.portgroup_id: portgroup_obj = [pg for pg in task.portgroups if pg.id == port_obj.portgroup_id][0] vif = (port_obj.internal_info.get(TENANT_VIF_KEY) or port_obj.extra.get('vif_port_id')) if 'address' in port_obj.obj_what_changed(): if vif: neutron.update_port_address(vif, port_obj.address) if 'extra' in port_obj.obj_what_changed(): original_port = objects.Port.get_by_id(context, port_obj.id) updated_client_id = port_obj.extra.get('client-id') if (port_obj.extra.get('vif_port_id') and (port_obj.extra['vif_port_id'] != original_port.extra.get('vif_port_id'))): utils.warn_about_deprecated_extra_vif_port_id() if (original_port.extra.get('client-id') != updated_client_id): # DHCP Option with opt_value=None will remove it # from the neutron port if vif: api = dhcp_factory.DHCPFactory() client_id_opt = {'opt_name': 'client-id', 'opt_value': updated_client_id} api.provider.update_port_dhcp_opts( vif, [client_id_opt]) # Log warning if there is no VIF and an instance # is associated with the node. elif node.instance_uuid: LOG.warning( "No VIF found for instance %(instance)s " "port %(port)s when attempting to update port " "client-id.", {'port': port_uuid, 'instance': node.instance_uuid}) if portgroup_obj and ((set(port_obj.obj_what_changed()) & {'pxe_enabled', 'portgroup_id'}) or vif): if not portgroup_obj.standalone_ports_supported: reason = [] if port_obj.pxe_enabled: reason.append("'pxe_enabled' was set to True") if vif: reason.append('VIF %s is attached to the port' % vif) if reason: msg = (_("Port group %(portgroup)s doesn't support " "standalone ports. This port %(port)s cannot be " " a member of that port group because of: " "%(reason)s") % {"reason": ', '.join(reason), "portgroup": portgroup_obj.uuid, "port": port_uuid}) raise exception.Conflict(msg)
def vif_attach(self, task, vif_info): """Attach a virtual network interface to a node Attach a virtual interface to a node. When selecting a port or portgroup to attach the virtual interface to, the following ordered criteria are applied: * Require ports or portgroups to have a physical network that is either None or one of the VIF's allowed physical networks. * Prefer ports or portgroups with a physical network field which is not None. * Prefer portgroups to ports. * Prefer ports with PXE enabled. :param task: A TaskManager instance. :param vif_info: a dictionary of information about a VIF. It must have an 'id' key, whose value is a unique identifier for that VIF. :raises: NetworkError, VifAlreadyAttached, NoFreePhysicalPorts :raises: PortgroupPhysnetInconsistent if one of the node's portgroups has ports which are not all assigned the same physical network. """ vif_id = vif_info['id'] client = neutron.get_client(context=task.context) # Determine whether any of the node's ports have a physical network. If # not, we don't need to check the VIF's network's physical networks as # they will not affect the VIF to port mapping. physnets = set() if any(port.physical_network is not None for port in task.ports): physnets = neutron.get_physnets_by_port_uuid(client, vif_id) if len(physnets) > 1: # NOTE(mgoddard): Neutron cannot currently handle hosts which # are mapped to multiple segments in the same routed network. node_physnets = network.get_physnets_for_node(task) if len(node_physnets.intersection(physnets)) > 1: reason = _("Node has ports which map to multiple segments " "of the routed network to which the VIF is " "attached. Currently neutron only supports " "hosts which map to one segment of a routed " "network") raise exception.VifInvalidForAttach(node=task.node.uuid, vif=vif_id, reason=reason) port_like_obj = get_free_port_like_object(task, vif_id, physnets) # Address is optional for portgroups if port_like_obj.address: try: neutron.update_port_address(vif_id, port_like_obj.address, context=task.context) except exception.FailedToUpdateMacOnPort: raise exception.NetworkError( _("Unable to attach VIF %(vif)s because Ironic can not " "update Neutron port %(port)s MAC address to match " "physical MAC address %(mac)s") % { 'vif': vif_id, 'port': vif_id, 'mac': port_like_obj.address }) self._save_vif_to_port_like_obj(port_like_obj, vif_id) # NOTE(vsaienko) allow to attach VIF to active instance. if task.node.provision_state == states.ACTIVE: plug_port_to_tenant_network(task, port_like_obj, client=client)
def vif_attach(self, task, vif_info): """Attach a virtual network interface to a node Attach a virtual interface to a node. When selecting a port or portgroup to attach the virtual interface to, the following ordered criteria are applied: * Require ports or portgroups to have a physical network that is either None or one of the VIF's allowed physical networks. * Prefer ports or portgroups with a physical network field which is not None. * Prefer portgroups to ports. * Prefer ports with PXE enabled. :param task: A TaskManager instance. :param vif_info: a dictionary of information about a VIF. It must have an 'id' key, whose value is a unique identifier for that VIF. :raises: NetworkError, VifAlreadyAttached, NoFreePhysicalPorts :raises: PortgroupPhysnetInconsistent if one of the node's portgroups has ports which are not all assigned the same physical network. """ vif_id = vif_info['id'] client = neutron.get_client() # Determine whether any of the node's ports have a physical network. If # not, we don't need to check the VIF's network's physical networks as # they will not affect the VIF to port mapping. physnets = set() if any(port.physical_network is not None for port in task.ports): try: physnets = neutron.get_physnets_by_port_uuid(client, vif_id) except (exception.InvalidParameterValue, exception.NetworkError): # TODO(mgoddard): Remove this except clause and handle errors # properly. We can do this once a strategy has been determined # for handling the tempest VIF tests in an environment that # may not support neutron. # NOTE(sambetts): If a client error occurs this is because # either neutron doesn't exist because we're running in # standalone environment or we can't find a matching neutron # port which means a user might be requesting a non-neutron # port. Assume no physical network information exists in these # cases. pass if len(physnets) > 1: # NOTE(mgoddard): Neutron cannot currently handle hosts which # are mapped to multiple segments in the same routed network. node_physnets = network.get_physnets_for_node(task) if len(node_physnets.intersection(physnets)) > 1: reason = _("Node has ports which map to multiple segments " "of the routed network to which the VIF is " "attached. Currently neutron only supports " "hosts which map to one segment of a routed " "network") raise exception.VifInvalidForAttach(node=task.node.uuid, vif=vif_id, reason=reason) port_like_obj = get_free_port_like_object(task, vif_id, physnets) # Address is optional for portgroups if port_like_obj.address: # Check if the requested vif_id is a neutron port. If it is # then attempt to update the port's MAC address. try: client.show_port(vif_id) except neutron_exceptions.NeutronClientException: # TODO(mgoddard): Remove this except clause and handle errors # properly. We can do this once a strategy has been determined # for handling the tempest VIF tests in an environment that # may not support neutron. # NOTE(sambetts): If a client error occurs this is because # either neutron doesn't exist because we're running in # standalone environment or we can't find a matching neutron # port which means a user might be requesting a non-neutron # port. So skip trying to update the neutron port MAC address # in these cases. pass else: try: neutron.update_port_address(vif_id, port_like_obj.address) except exception.FailedToUpdateMacOnPort: raise exception.NetworkError( _("Unable to attach VIF %(vif)s because Ironic can not " "update Neutron port %(port)s MAC address to match " "physical MAC address %(mac)s") % { 'vif': vif_id, 'port': vif_id, 'mac': port_like_obj.address }) int_info = port_like_obj.internal_info int_info[TENANT_VIF_KEY] = vif_id port_like_obj.internal_info = int_info port_like_obj.save() # NOTE(vsaienko) allow to attach VIF to active instance. if task.node.provision_state == states.ACTIVE: plug_port_to_tenant_network(task, port_like_obj, client=client)
def vif_attach(self, task, vif_info): """Attach a virtual network interface to a node Attach a virtual interface to a node. When selecting a port or portgroup to attach the virtual interface to, the following ordered criteria are applied: * Require ports or portgroups to have a physical network that is either None or one of the VIF's allowed physical networks. * Prefer ports or portgroups with a physical network field which is not None. * Prefer portgroups to ports. * Prefer ports with PXE enabled. :param task: A TaskManager instance. :param vif_info: a dictionary of information about a VIF. It must have an 'id' key, whose value is a unique identifier for that VIF. :raises: NetworkError, VifAlreadyAttached, NoFreePhysicalPorts :raises: PortgroupPhysnetInconsistent if one of the node's portgroups has ports which are not all assigned the same physical network. """ vif_id = vif_info['id'] client = neutron.get_client(context=task.context) # Determine whether any of the node's ports have a physical network. If # not, we don't need to check the VIF's network's physical networks as # they will not affect the VIF to port mapping. physnets = set() if any(port.physical_network is not None for port in task.ports): physnets = neutron.get_physnets_by_port_uuid(client, vif_id) if len(physnets) > 1: # NOTE(mgoddard): Neutron cannot currently handle hosts which # are mapped to multiple segments in the same routed network. node_physnets = network.get_physnets_for_node(task) if len(node_physnets.intersection(physnets)) > 1: reason = _("Node has ports which map to multiple segments " "of the routed network to which the VIF is " "attached. Currently neutron only supports " "hosts which map to one segment of a routed " "network") raise exception.VifInvalidForAttach( node=task.node.uuid, vif=vif_id, reason=reason) port_like_obj = get_free_port_like_object(task, vif_id, physnets) # Address is optional for portgroups if port_like_obj.address: try: neutron.update_port_address(vif_id, port_like_obj.address, context=task.context) except exception.FailedToUpdateMacOnPort: raise exception.NetworkError(_( "Unable to attach VIF %(vif)s because Ironic can not " "update Neutron port %(port)s MAC address to match " "physical MAC address %(mac)s") % { 'vif': vif_id, 'port': vif_id, 'mac': port_like_obj.address}) self._save_vif_to_port_like_obj(port_like_obj, vif_id) # NOTE(vsaienko) allow to attach VIF to active instance. if task.node.provision_state == states.ACTIVE: plug_port_to_tenant_network(task, port_like_obj, client=client)