def handle_post_port_like_extra_vif(p_dict): """Handle a Post request that sets .extra['vif_port_id']. This handles attach of VIFs via specifying the VIF port ID in a port or port group's extra['vif_port_id'] field. :param p_dict: a dictionary with field names/values for the port or port group :return: VIF or None """ vif = p_dict.get('extra', {}).get('vif_port_id') if vif: # TODO(rloo): in Stein cycle: if API version >= 1.28, remove # warning and support for extra[]; else (< 1.28) # still support it; continue copying to internal_info # (see bug 1722850). i.e., change the 7 lines of code # below to something like: # if not api_utils.allow_vifs_subcontroller(): # internal_info = {'tenant_vif_port_id': vif} # pg_dict['internal_info'] = internal_info if allow_vifs_subcontroller(): utils.warn_about_deprecated_extra_vif_port_id() # NOTE(rloo): this value should really be in .internal_info[..] # which is what would happen if they had used the # POST /v1/nodes/<node>/vifs API. internal_info = {'tenant_vif_port_id': vif} p_dict['internal_info'] = internal_info return vif
def post(self, port): """Create a new port. :param port: a port within the request body. :raises: NotAcceptable, HTTPNotFound, Conflict """ context = pecan.request.context cdict = context.to_policy_values() policy.authorize('baremetal:port:create', cdict, cdict) if self.parent_node_ident or self.parent_portgroup_ident: raise exception.OperationNotPermitted() pdict = port.as_dict() self._check_allowed_port_fields(pdict) extra = pdict.get('extra') vif = extra.get('vif_port_id') if extra else None if vif: common_utils.warn_about_deprecated_extra_vif_port_id() if (pdict.get('portgroup_uuid') and (pdict.get('pxe_enabled') or vif)): rpc_pg = objects.Portgroup.get_by_uuid(context, pdict['portgroup_uuid']) if not rpc_pg.standalone_ports_supported: msg = _("Port group %s doesn't support standalone ports. " "This port cannot be created as a member of that " "port group because either 'extra/vif_port_id' " "was specified or 'pxe_enabled' was set to True.") raise exception.Conflict( msg % pdict['portgroup_uuid']) # NOTE(yuriyz): UUID is mandatory for notifications payload if not pdict.get('uuid'): pdict['uuid'] = uuidutils.generate_uuid() rpc_port = objects.Port(context, **pdict) rpc_node = objects.Node.get_by_id(context, rpc_port.node_id) notify_extra = {'node_uuid': port.node_uuid, 'portgroup_uuid': port.portgroup_uuid} notify.emit_start_notification(context, rpc_port, 'create', **notify_extra) with notify.handle_error_notification(context, rpc_port, 'create', **notify_extra): # TODO(mgoddard): In RPC API v1.41, port creation was moved to the # conductor service to facilitate validation of the physical # network field of ports in portgroups. Further consideration is # required determine how best to support rolling upgrades from a # release in which ports are created by the API service to one in # which they are created by the conductor service, while ensuring # that all required validation is performed. topic = pecan.request.rpcapi.get_topic_for(rpc_node) new_port = pecan.request.rpcapi.create_port(context, rpc_port, topic) notify.emit_end_notification(context, new_port, 'create', **notify_extra) # Set the HTTP Location Header pecan.response.location = link.build_url('ports', new_port.uuid) return Port.convert_with_links(new_port)
def post(self, port): """Create a new port. :param port: a port within the request body. :raises: NotAcceptable, HTTPNotFound, Conflict """ context = pecan.request.context cdict = context.to_policy_values() policy.authorize('baremetal:port:create', cdict, cdict) if self.parent_node_ident or self.parent_portgroup_ident: raise exception.OperationNotPermitted() pdict = port.as_dict() self._check_allowed_port_fields(pdict) extra = pdict.get('extra') vif = extra.get('vif_port_id') if extra else None if vif: common_utils.warn_about_deprecated_extra_vif_port_id() if (pdict.get('portgroup_uuid') and (pdict.get('pxe_enabled') or vif)): rpc_pg = objects.Portgroup.get_by_uuid(context, pdict['portgroup_uuid']) if not rpc_pg.standalone_ports_supported: msg = _("Port group %s doesn't support standalone ports. " "This port cannot be created as a member of that " "port group because either 'extra/vif_port_id' " "was specified or 'pxe_enabled' was set to True.") raise exception.Conflict(msg % pdict['portgroup_uuid']) # NOTE(yuriyz): UUID is mandatory for notifications payload if not pdict.get('uuid'): pdict['uuid'] = uuidutils.generate_uuid() rpc_port = objects.Port(context, **pdict) rpc_node = objects.Node.get_by_id(context, rpc_port.node_id) notify_extra = { 'node_uuid': port.node_uuid, 'portgroup_uuid': port.portgroup_uuid } notify.emit_start_notification(context, rpc_port, 'create', **notify_extra) with notify.handle_error_notification(context, rpc_port, 'create', **notify_extra): # TODO(mgoddard): In RPC API v1.41, port creation was moved to the # conductor service to facilitate validation of the physical # network field of ports in portgroups. Further consideration is # required determine how best to support rolling upgrades from a # release in which ports are created by the API service to one in # which they are created by the conductor service, while ensuring # that all required validation is performed. topic = pecan.request.rpcapi.get_topic_for(rpc_node) new_port = pecan.request.rpcapi.create_port( context, rpc_port, topic) notify.emit_end_notification(context, new_port, 'create', **notify_extra) # Set the HTTP Location Header pecan.response.location = link.build_url('ports', new_port.uuid) return Port.convert_with_links(new_port)
def test_warn_about_deprecated_extra_vif_port_id(self, mock_log): # Set variable to default value utils.warn_deprecated_extra_vif_port_id = False utils.warn_about_deprecated_extra_vif_port_id() utils.warn_about_deprecated_extra_vif_port_id() self.assertEqual(1, mock_log.warning.call_count) self.assertIn("extra['vif_port_id'] is deprecated and will not", mock_log.warning.call_args[0][0])
def post(self, portgroup): """Create a new portgroup. :param portgroup: a portgroup within the request body. """ if not api_utils.allow_portgroups(): raise exception.NotFound() context = pecan.request.context cdict = context.to_policy_values() policy.authorize('baremetal:portgroup:create', cdict, cdict) if self.parent_node_ident: raise exception.OperationNotPermitted() if (not api_utils.allow_portgroup_mode_properties() and (portgroup.mode is not wtypes.Unset or portgroup.properties is not wtypes.Unset)): raise exception.NotAcceptable() if (portgroup.name and not api_utils.is_valid_logical_name(portgroup.name)): error_msg = _("Cannot create portgroup with invalid name " "'%(name)s'") % { 'name': portgroup.name } raise wsme.exc.ClientSideError(error_msg, status_code=http_client.BAD_REQUEST) pg_dict = portgroup.as_dict() vif = pg_dict.get('extra', {}).get('vif_port_id') if vif: common_utils.warn_about_deprecated_extra_vif_port_id() # NOTE(yuriyz): UUID is mandatory for notifications payload if not pg_dict.get('uuid'): pg_dict['uuid'] = uuidutils.generate_uuid() new_portgroup = objects.Portgroup(context, **pg_dict) notify.emit_start_notification(context, new_portgroup, 'create', node_uuid=portgroup.node_uuid) with notify.handle_error_notification(context, new_portgroup, 'create', node_uuid=portgroup.node_uuid): new_portgroup.create() notify.emit_end_notification(context, new_portgroup, 'create', node_uuid=portgroup.node_uuid) # Set the HTTP Location Header pecan.response.location = link.build_url('portgroups', new_portgroup.uuid) return Portgroup.convert_with_links(new_portgroup)
def post(self, port): """Create a new port. :param port: a port within the request body. :raises: NotAcceptable, HTTPNotFound, Conflict """ context = pecan.request.context cdict = context.to_policy_values() policy.authorize('baremetal:port:create', cdict, cdict) if self.parent_node_ident or self.parent_portgroup_ident: raise exception.OperationNotPermitted() pdict = port.as_dict() if (not api_utils.allow_port_advanced_net_fields() and set(pdict).intersection(self.advanced_net_fields)): raise exception.NotAcceptable() if (not api_utils.allow_portgroups_subcontrollers() and 'portgroup_uuid' in pdict): raise exception.NotAcceptable() extra = pdict.get('extra') vif = extra.get('vif_port_id') if extra else None if vif: common_utils.warn_about_deprecated_extra_vif_port_id() if (pdict.get('portgroup_uuid') and (pdict.get('pxe_enabled') or vif)): rpc_pg = objects.Portgroup.get_by_uuid(context, pdict['portgroup_uuid']) if not rpc_pg.standalone_ports_supported: msg = _("Port group %s doesn't support standalone ports. " "This port cannot be created as a member of that " "port group because either 'extra/vif_port_id' " "was specified or 'pxe_enabled' was set to True.") raise exception.Conflict(msg % pdict['portgroup_uuid']) # NOTE(yuriyz): UUID is mandatory for notifications payload if not pdict.get('uuid'): pdict['uuid'] = uuidutils.generate_uuid() new_port = objects.Port(context, **pdict) notify.emit_start_notification(context, new_port, 'create', node_uuid=port.node_uuid) with notify.handle_error_notification(context, new_port, 'create', node_uuid=port.node_uuid): new_port.create() notify.emit_end_notification(context, new_port, 'create', node_uuid=port.node_uuid) # Set the HTTP Location Header pecan.response.location = link.build_url('ports', new_port.uuid) return Port.convert_with_links(new_port)
def handle_patch_port_like_extra_vif(rpc_object, api_object, patch): """Handle a Patch request that modifies .extra['vif_port_id']. This handles attach/detach of VIFs via the VIF port ID in a port or port group's extra['vif_port_id'] field. :param rpc_object: a Port or Portgroup RPC object :param api_object: the corresponding Port or Portgroup API object :param patch: the JSON patch in the API request """ vif_list = get_patch_values(patch, '/extra/vif_port_id') vif = None if vif_list: # if specified more than once, use the last value vif = vif_list[-1] # TODO(rloo): in Stein cycle: if API version >= 1.28, remove this # warning and don't copy to internal_info; else (<1.28) still # support it; continue copying to internal_info (see bug 1722850). # i.e., change the 8 lines of code below to something like: # if not allow_vifs_subcontroller(): # int_info = rpc_object.internal_info.get('tenant_vif_port_id') # if (not int_info or # int_info == rpc_object.extra.get('vif_port_id')): # api_object.internal_info['tenant_vif_port_id'] = vif if allow_vifs_subcontroller(): utils.warn_about_deprecated_extra_vif_port_id() # NOTE(rloo): if the user isn't also using the REST API # 'POST nodes/<node>/vifs', we are safe to copy the # .extra[] value to the .internal_info location int_info = rpc_object.internal_info.get('tenant_vif_port_id') if (not int_info or int_info == rpc_object.extra.get('vif_port_id')): api_object.internal_info['tenant_vif_port_id'] = vif elif is_path_removed(patch, '/extra/vif_port_id'): # TODO(rloo): in Stein cycle: if API version >= 1.28, remove this # warning and don't remove from internal_info; else (<1.28) still # support it; remove from internal_info (see bug 1722850). # i.e., change the 8 lines of code below to something like: # if not allow_vifs_subcontroller(): # int_info = rpc_object.internal_info.get('tenant_vif...') # if (int_info and int_info==rpc_object.extra.get('vif_port_id')): # api_object.internal_info['tenant_vif_port_id'] = None if allow_vifs_subcontroller(): utils.warn_about_deprecated_extra_vif_port_id() # NOTE(rloo): if the user isn't also using the REST API # 'POST nodes/<node>/vifs', we are safe to remove the # .extra[] value from the .internal_info location int_info = rpc_object.internal_info.get('tenant_vif_port_id') if (int_info and int_info == rpc_object.extra.get('vif_port_id')): api_object.internal_info.pop('tenant_vif_port_id')
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 post(self, portgroup): """Create a new portgroup. :param portgroup: a portgroup within the request body. """ if not api_utils.allow_portgroups(): raise exception.NotFound() context = pecan.request.context cdict = context.to_policy_values() policy.authorize('baremetal:portgroup:create', cdict, cdict) if self.parent_node_ident: raise exception.OperationNotPermitted() if (not api_utils.allow_portgroup_mode_properties() and (portgroup.mode is not wtypes.Unset or portgroup.properties is not wtypes.Unset)): raise exception.NotAcceptable() if (portgroup.name and not api_utils.is_valid_logical_name(portgroup.name)): error_msg = _("Cannot create portgroup with invalid name " "'%(name)s'") % {'name': portgroup.name} raise wsme.exc.ClientSideError( error_msg, status_code=http_client.BAD_REQUEST) pg_dict = portgroup.as_dict() vif = pg_dict.get('extra', {}).get('vif_port_id') if vif: common_utils.warn_about_deprecated_extra_vif_port_id() # NOTE(yuriyz): UUID is mandatory for notifications payload if not pg_dict.get('uuid'): pg_dict['uuid'] = uuidutils.generate_uuid() new_portgroup = objects.Portgroup(context, **pg_dict) notify.emit_start_notification(context, new_portgroup, 'create', node_uuid=portgroup.node_uuid) with notify.handle_error_notification(context, new_portgroup, 'create', node_uuid=portgroup.node_uuid): new_portgroup.create() notify.emit_end_notification(context, new_portgroup, 'create', node_uuid=portgroup.node_uuid) # Set the HTTP Location Header pecan.response.location = link.build_url('portgroups', new_portgroup.uuid) return Portgroup.convert_with_links(new_portgroup)
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 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 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)