302 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			302 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# Copyright 2018 Intel, Inc.
 | 
						|
# All Rights Reserved.
 | 
						|
#
 | 
						|
#    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.
 | 
						|
 | 
						|
from jsonschema import validate
 | 
						|
import logging
 | 
						|
 | 
						|
from sushy import exceptions
 | 
						|
from sushy.resources import base
 | 
						|
 | 
						|
from rsd_lib.resources.v2_1.node import node as v2_1_node
 | 
						|
from rsd_lib.resources.v2_2.node import node as v2_2_node
 | 
						|
from rsd_lib.resources.v2_3.node import attach_action_info
 | 
						|
from rsd_lib.resources.v2_3.node import schemas as node_schemas
 | 
						|
from rsd_lib import utils as rsd_lib_utils
 | 
						|
 | 
						|
 | 
						|
LOG = logging.getLogger(__name__)
 | 
						|
 | 
						|
 | 
						|
class AttachEndpointActionField(base.CompositeField):
 | 
						|
    target_uri = base.Field('target', required=True)
 | 
						|
    action_info_path = base.Field('@Redfish.ActionInfo',
 | 
						|
                                  adapter=rsd_lib_utils.get_resource_identity)
 | 
						|
    action_info = None
 | 
						|
 | 
						|
 | 
						|
class DetachEndpointActionField(base.CompositeField):
 | 
						|
    target_uri = base.Field('target', required=True)
 | 
						|
    action_info_path = base.Field('@Redfish.ActionInfo',
 | 
						|
                                  adapter=rsd_lib_utils.get_resource_identity)
 | 
						|
    action_info = None
 | 
						|
 | 
						|
 | 
						|
class NodeActionsField(v2_1_node.NodeActionsField):
 | 
						|
    attach_endpoint = AttachEndpointActionField('#ComposedNode.AttachResource')
 | 
						|
    detach_endpoint = DetachEndpointActionField('#ComposedNode.DetachResource')
 | 
						|
 | 
						|
 | 
						|
class Node(v2_1_node.Node):
 | 
						|
 | 
						|
    clear_tpm_on_delete = base.Field('ClearTPMOnDelete', adapter=bool)
 | 
						|
    """This is used to specify if TPM module should be cleared on composed node
 | 
						|
       DELETE request
 | 
						|
    """
 | 
						|
 | 
						|
    _actions = NodeActionsField('Actions', required=True)
 | 
						|
 | 
						|
    def update(self, clear_tpm_on_delete):
 | 
						|
        """Update properties of this composed node
 | 
						|
 | 
						|
        :param clear_tpm_on_delete: This is used to specify if TPM module
 | 
						|
            should be cleared on composed node DELETE request.
 | 
						|
        :raises: InvalidParameterValueError, if any information passed is
 | 
						|
            invalid.
 | 
						|
        """
 | 
						|
        if not isinstance(clear_tpm_on_delete, bool):
 | 
						|
            raise exceptions.InvalidParameterValueError(
 | 
						|
                parameter='clear_tpm_on_delete', value=clear_tpm_on_delete,
 | 
						|
                valid_values=[True, False])
 | 
						|
 | 
						|
        data = {
 | 
						|
            'ClearTPMOnDelete': clear_tpm_on_delete
 | 
						|
        }
 | 
						|
 | 
						|
        self._conn.patch(self.path, data=data)
 | 
						|
 | 
						|
    def _get_attach_endpoint_action_element(self):
 | 
						|
        attach_endpoint_action = self._actions.attach_endpoint
 | 
						|
        if not attach_endpoint_action:
 | 
						|
            raise exceptions.MissingActionError(
 | 
						|
                action='#ComposedNode.AttachResource',
 | 
						|
                resource=self._path)
 | 
						|
 | 
						|
        if attach_endpoint_action.action_info is None:
 | 
						|
            attach_endpoint_action.action_info = \
 | 
						|
                attach_action_info.AttachResourceActionInfo(
 | 
						|
                    self._conn, attach_endpoint_action.action_info_path,
 | 
						|
                    redfish_version=self.redfish_version)
 | 
						|
        return attach_endpoint_action
 | 
						|
 | 
						|
    def get_allowed_attach_endpoints(self):
 | 
						|
        """Get the allowed endpoints for attach action.
 | 
						|
 | 
						|
        :returns: A set with the allowed attach endpoints.
 | 
						|
        """
 | 
						|
        attach_action = self._get_attach_endpoint_action_element()
 | 
						|
        for i in attach_action.action_info.parameters:
 | 
						|
            if i['name'] == 'Resource':
 | 
						|
                return i['allowable_values']
 | 
						|
        return ()
 | 
						|
 | 
						|
    def attach_endpoint(self, resource, protocol=None):
 | 
						|
        """Attach endpoint from available pool to composed node
 | 
						|
 | 
						|
        :param resource: Link to endpoint to attach.
 | 
						|
        :param protocol: Protocol of the remote drive.
 | 
						|
        :raises: InvalidParameterValueError
 | 
						|
        """
 | 
						|
        attach_action = self._get_attach_endpoint_action_element()
 | 
						|
        valid_endpoints = self.get_allowed_attach_endpoints()
 | 
						|
        target_uri = attach_action.target_uri
 | 
						|
 | 
						|
        if resource and resource not in valid_endpoints:
 | 
						|
            raise exceptions.InvalidParameterValueError(
 | 
						|
                parameter='resource', value=resource,
 | 
						|
                valid_values=valid_endpoints)
 | 
						|
 | 
						|
        data = {}
 | 
						|
        if resource is not None:
 | 
						|
            data['Resource'] = {'@odata.id': resource}
 | 
						|
        if protocol is not None:
 | 
						|
            data['Protocol'] = protocol
 | 
						|
 | 
						|
        self._conn.post(target_uri, data=data)
 | 
						|
 | 
						|
    def _get_detach_endpoint_action_element(self):
 | 
						|
        detach_endpoint_action = self._actions.detach_endpoint
 | 
						|
        if not detach_endpoint_action:
 | 
						|
            raise exceptions.MissingActionError(
 | 
						|
                action='#ComposedNode.DetachResource',
 | 
						|
                resource=self._path)
 | 
						|
 | 
						|
        if detach_endpoint_action.action_info is None:
 | 
						|
            detach_endpoint_action.action_info = \
 | 
						|
                attach_action_info.AttachResourceActionInfo(
 | 
						|
                    self._conn, detach_endpoint_action.action_info_path,
 | 
						|
                    redfish_version=self.redfish_version)
 | 
						|
        return detach_endpoint_action
 | 
						|
 | 
						|
    def get_allowed_detach_endpoints(self):
 | 
						|
        """Get the allowed endpoints for detach action.
 | 
						|
 | 
						|
        :returns: A set with the allowed detach endpoints.
 | 
						|
        """
 | 
						|
        detach_action = self._get_detach_endpoint_action_element()
 | 
						|
        for i in detach_action.action_info.parameters:
 | 
						|
            if i['name'] == 'Resource':
 | 
						|
                return i['allowable_values']
 | 
						|
        return ()
 | 
						|
 | 
						|
    def detach_endpoint(self, resource):
 | 
						|
        """Detach endpoint from available pool to composed node
 | 
						|
 | 
						|
        :param resource: Link to endpoint to detach.
 | 
						|
        :raises: InvalidParameterValueError
 | 
						|
        """
 | 
						|
        detach_action = self._get_detach_endpoint_action_element()
 | 
						|
        valid_endpoints = self.get_allowed_detach_endpoints()
 | 
						|
        target_uri = detach_action.target_uri
 | 
						|
 | 
						|
        if resource not in valid_endpoints:
 | 
						|
            raise exceptions.InvalidParameterValueError(
 | 
						|
                parameter='resource', value=resource,
 | 
						|
                valid_values=valid_endpoints)
 | 
						|
 | 
						|
        data = {}
 | 
						|
        if resource is not None:
 | 
						|
            data['Resource'] = {'@odata.id': resource}
 | 
						|
 | 
						|
        self._conn.post(target_uri, data=data)
 | 
						|
 | 
						|
    def refresh(self, force=True):
 | 
						|
        super(Node, self).refresh(force)
 | 
						|
        if self._actions.attach_endpoint:
 | 
						|
            self._actions.attach_endpoint.action_info = None
 | 
						|
        if self._actions.detach_endpoint:
 | 
						|
            self._actions.detach_endpoint.action_info = None
 | 
						|
 | 
						|
 | 
						|
class NodeCollection(v2_2_node.NodeCollection):
 | 
						|
 | 
						|
    @property
 | 
						|
    def _resource_type(self):
 | 
						|
        return Node
 | 
						|
 | 
						|
    def __init__(self, connector, path, redfish_version=None):
 | 
						|
        """A class representing a NodeCollection
 | 
						|
 | 
						|
        :param connector: A Connector instance
 | 
						|
        :param path: The canonical path to the Node collection
 | 
						|
            resource
 | 
						|
        :param redfish_version: The version of RedFish. Used to construct
 | 
						|
            the object according to schema of the given version.
 | 
						|
        """
 | 
						|
        super(NodeCollection, self).__init__(connector, path, redfish_version)
 | 
						|
 | 
						|
    def _create_compose_request(self, name=None, description=None,
 | 
						|
                                processor_req=None, memory_req=None,
 | 
						|
                                remote_drive_req=None, local_drive_req=None,
 | 
						|
                                ethernet_interface_req=None,
 | 
						|
                                security_req=None, total_system_core_req=None,
 | 
						|
                                total_system_memory_req=None):
 | 
						|
 | 
						|
        request = {}
 | 
						|
 | 
						|
        if name is not None:
 | 
						|
            request['Name'] = name
 | 
						|
        if description is not None:
 | 
						|
            request['Description'] = description
 | 
						|
 | 
						|
        if processor_req is not None:
 | 
						|
            validate(processor_req,
 | 
						|
                     node_schemas.processor_req_schema)
 | 
						|
            request['Processors'] = processor_req
 | 
						|
 | 
						|
        if memory_req is not None:
 | 
						|
            validate(memory_req,
 | 
						|
                     node_schemas.memory_req_schema)
 | 
						|
            request['Memory'] = memory_req
 | 
						|
 | 
						|
        if remote_drive_req is not None:
 | 
						|
            validate(remote_drive_req,
 | 
						|
                     node_schemas.remote_drive_req_schema)
 | 
						|
            request['RemoteDrives'] = remote_drive_req
 | 
						|
 | 
						|
        if local_drive_req is not None:
 | 
						|
            validate(local_drive_req,
 | 
						|
                     node_schemas.local_drive_req_schema)
 | 
						|
            request['LocalDrives'] = local_drive_req
 | 
						|
 | 
						|
        if ethernet_interface_req is not None:
 | 
						|
            validate(ethernet_interface_req,
 | 
						|
                     node_schemas.ethernet_interface_req_schema)
 | 
						|
            request['EthernetInterfaces'] = ethernet_interface_req
 | 
						|
 | 
						|
        if security_req is not None:
 | 
						|
            validate(security_req,
 | 
						|
                     node_schemas.security_req_schema)
 | 
						|
            request['Security'] = security_req
 | 
						|
 | 
						|
        if total_system_core_req is not None:
 | 
						|
            validate(total_system_core_req,
 | 
						|
                     node_schemas.total_system_core_req_schema)
 | 
						|
            request['TotalSystemCoreCount'] = total_system_core_req
 | 
						|
 | 
						|
        if total_system_memory_req is not None:
 | 
						|
            validate(total_system_memory_req,
 | 
						|
                     node_schemas.total_system_memory_req_schema)
 | 
						|
            request['TotalSystemMemoryMiB'] = total_system_memory_req
 | 
						|
 | 
						|
        return request
 | 
						|
 | 
						|
    def compose_node(self, name=None, description=None,
 | 
						|
                     processor_req=None, memory_req=None,
 | 
						|
                     remote_drive_req=None, local_drive_req=None,
 | 
						|
                     ethernet_interface_req=None, security_req=None,
 | 
						|
                     total_system_core_req=None, total_system_memory_req=None):
 | 
						|
        """Compose a node from RackScale hardware
 | 
						|
 | 
						|
        :param name: Name of node
 | 
						|
        :param description: Description of node
 | 
						|
        :param processor_req: JSON for node processors
 | 
						|
        :param memory_req: JSON for node memory modules
 | 
						|
        :param remote_drive_req: JSON for node remote drives
 | 
						|
        :param local_drive_req: JSON for node local drives
 | 
						|
        :param ethernet_interface_req: JSON for node ethernet ports
 | 
						|
        :param security_req: JSON for node security requirements
 | 
						|
        :param total_system_core_req: Total processor cores available in
 | 
						|
            composed node
 | 
						|
        :param total_system_memory_req: Total memory available in composed node
 | 
						|
        :returns: The location of the composed node
 | 
						|
 | 
						|
        When the 'processor_req' is not none: it need a computer system
 | 
						|
        contains processors whose each processor meet all conditions in the
 | 
						|
        value.
 | 
						|
 | 
						|
        When the 'total_system_core_req' is not none: it need a computer
 | 
						|
        system contains processors whose cores sum up to number equal or
 | 
						|
        greater than 'total_system_core_req'.
 | 
						|
 | 
						|
        When both values are not none: it need meet all conditions.
 | 
						|
 | 
						|
        'memory_req' and 'total_system_memory_req' is the same.
 | 
						|
        """
 | 
						|
        target_uri = self._get_compose_action_element().target_uri
 | 
						|
        properties = self._create_compose_request(
 | 
						|
            name=name, description=description,
 | 
						|
            processor_req=processor_req,
 | 
						|
            memory_req=memory_req,
 | 
						|
            remote_drive_req=remote_drive_req,
 | 
						|
            local_drive_req=local_drive_req,
 | 
						|
            ethernet_interface_req=ethernet_interface_req,
 | 
						|
            security_req=security_req,
 | 
						|
            total_system_core_req=total_system_core_req,
 | 
						|
            total_system_memory_req=total_system_memory_req)
 | 
						|
        resp = self._conn.post(target_uri, data=properties)
 | 
						|
        LOG.info("Node created at %s", resp.headers['Location'])
 | 
						|
        node_url = resp.headers['Location']
 | 
						|
        return node_url[node_url.find(self._path):]
 |