rsd-lib/rsd_lib/resources/v2_3/node/node.py

318 lines
11 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 _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):]