Update RSD 2.3 remote drive requirment schema

Change-Id: Idfab4aaa079a7627744589fc6c1fff620484912b
This commit is contained in:
Lin Yang 2018-09-06 10:58:09 -07:00
parent f63fb959f3
commit cad78a130b
3 changed files with 585 additions and 8 deletions

View File

@ -13,14 +13,22 @@
# 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',
@ -147,7 +155,7 @@ class Node(v2_1_node.Node):
self._actions.detach_endpoint.action_info = None
class NodeCollection(v2_1_node.NodeCollection):
class NodeCollection(v2_2_node.NodeCollection):
@property
def _resource_type(self):
@ -163,3 +171,107 @@ class NodeCollection(v2_1_node.NodeCollection):
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):]

View File

@ -0,0 +1,220 @@
# Copyright (c) 2018 Intel, Corp.
#
# 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.
processor_req_schema = {
'type': 'array',
'items': [{
'type': 'object',
'properties': {
'Model': {'type': 'string'},
'TotalCores': {'type': 'number'},
'AchievableSpeedMHz': {'type': 'number'},
'InstructionSet': {
'type': 'string',
'enum': ['x86', 'x86-64', 'IA-64', 'ARM-A32',
'ARM-A64', 'MIPS32', 'MIPS64', 'OEM']
},
'Oem': {
'type': 'object',
'properties': {
'Brand': {
'type': 'string',
'enum': ['E3', 'E5', 'E7', 'X3', 'X5', 'X7', 'I3',
'I5', 'I7', 'Silver', 'Gold', 'Platinum',
'Unknown']
},
'Capabilities': {
'type': 'array',
'items': [{'type': 'string'}]
}
}
},
'Resource': {
'type': 'object',
'properties': {
'@odata.id': {'type': 'string'}
}
},
'Chassis': {
'type': 'object',
'properties': {
'@odata.id': {'type': 'string'}
}
},
'ProcessorType': {
'type': 'string',
'enum': ['CPU', 'FPGA', 'GPU', 'DSP', 'Accelerator', 'OEM']
}
},
'additionalProperties': False,
}]
}
memory_req_schema = {
'type': 'array',
'items': [{
'type': 'object',
'properties': {
'CapacityMiB': {'type': 'number'},
'MemoryDeviceType': {
'type': 'string',
'enum': ['DDR', 'DDR2', 'DDR3', 'DDR4', 'DDR4_SDRAM',
'DDR4E_SDRAM', 'LPDDR4_SDRAM', 'DDR3_SDRAM',
'LPDDR3_SDRAM', 'DDR2_SDRAM', 'DDR2_SDRAM_FB_DIMM',
'DDR2_SDRAM_FB_DIMM_PROBE', 'DDR_SGRAM',
'DDR_SDRAM', 'ROM', 'SDRAM', 'EDO',
'FastPageMode', 'PipelinedNibble']
},
'SpeedMHz': {'type': 'number'},
'Manufacturer': {'type': 'string'},
'DataWidthBits': {'type': 'number'},
'Resource': {
'type': 'object',
'properties': {
'@odata.id': {'type': 'string'}
}
},
'Chassis': {
'type': 'object',
'properties': {
'@odata.id': {'type': 'string'}
}
}
},
'additionalProperties': False,
}]
}
remote_drive_req_schema = {
'type': 'array',
'items': [{
'type': 'object',
'properties': {
'CapacityGiB': {'type': 'number'},
'Protocol': {
'type': 'string',
'enum': ['iSCSI', 'NVMeOverFabrics']
},
'Master': {
'type': 'object',
'properties': {
'Type': {
'type': 'string',
'enum': ['Snapshot', 'Clone']
},
'Resource': {
'type': 'object',
'properties': {
'@odata.id': {'type': 'string'}
}
}
}
},
'Resource': {
'type': 'object',
'properties': {
'@odata.id': {'type': 'string'}
}
}
},
'additionalProperties': False,
}]
}
local_drive_req_schema = {
'type': 'array',
'items': [{
'type': 'object',
'properties': {
'CapacityGiB': {'type': 'number'},
'Type': {
'type': 'string',
'enum': ['HDD', 'SSD']
},
'MinRPM': {'type': 'number'},
'SerialNumber': {'type': 'string'},
'Interface': {
'type': 'string',
'enum': ['SAS', 'SATA', 'NVMe']
},
'Resource': {
'type': 'object',
'properties': {
'@odata.id': {'type': 'string'}
}
},
'Chassis': {
'type': 'object',
'properties': {
'@odata.id': {'type': 'string'}
}
},
'FabricSwitch': {'type': 'boolean'}
},
'additionalProperties': False,
}]
}
ethernet_interface_req_schema = {
'type': 'array',
'items': [{
'type': 'object',
'properties': {
'SpeedMbps': {'type': 'number'},
'PrimaryVLAN': {'type': 'number'},
'VLANs': {
'type': 'array',
'additionalItems': {
'type': 'object',
'properties': {
'VLANId': {'type': 'number'},
'Tagged': {'type': 'boolean'}
}
}
},
'Resource': {
'type': 'object',
'properties': {
'@odata.id': {'type': 'string'}
}
},
'Chassis': {
'type': 'object',
'properties': {
'@odata.id': {'type': 'string'}
}
}
},
'additionalProperties': False,
}]
}
security_req_schema = {
'type': 'object',
'properties': {
'TpmPresent': {'type': 'boolean'},
'TpmInterfaceType': {'type': 'string'},
'TxtEnabled': {'type': 'boolean'},
'ClearTPMOnDelete': {'type': 'boolean'}
},
'additionalProperties': False,
}
total_system_core_req_schema = {
'type': 'number'
}
total_system_memory_req_schema = {
'type': 'number'
}

View File

@ -253,30 +253,275 @@ class NodeCollectionTestCase(testtools.TestCase):
'/redfish/v1/Nodes/Actions/Allocate', data={})
self.assertEqual(result, '/redfish/v1/Nodes/1')
def test_compose_node_reqs(self):
def test_compose_node(self):
reqs = {
'Name': 'test',
'Description': 'this is a test node',
'Processors': [{
'TotalCores': 4
'TotalCores': 4,
'ProcessorType': 'FPGA',
'Oem': {
'Brand': 'Platinum',
'Capabilities': ['sse']
}
}],
'Memory': [{
'CapacityMiB': 8000
}],
'RemoteDrives': [{
'CapacityGiB': 80,
'Protocol': 'NVMeOverFabrics',
'Master': {
'Type': 'Snapshot',
'Resource': {
'@odata.id':
'/redfish/v1/StorageServices/NVMeoE1/Volumes/102'
}
},
'Resource': {
'@odata.id':
'/redfish/v1/StorageServices/NVMeoE1/Volumes/102'
}
}],
'Security': {
'TpmPresent': True,
'TpmInterfaceType': 'TPM2_0',
'TxtEnabled': True,
'ClearTPMOnDelete': True
},
'TotalSystemCoreCount': 8,
'TotalSystemMemoryMiB': 16000
}
result = self.node_col.compose_node(
name='test', description='this is a test node',
processor_req=[{'TotalCores': 4}],
processor_req=[{
'TotalCores': 4,
'ProcessorType': 'FPGA',
'Oem': {
'Brand': 'Platinum',
'Capabilities': ['sse']
}
}],
memory_req=[{'CapacityMiB': 8000}],
remote_drive_req=[{
'CapacityGiB': 80,
'Protocol': 'NVMeOverFabrics',
'Master': {
'Type': 'Snapshot',
'Resource': {
'@odata.id':
'/redfish/v1/StorageServices/NVMeoE1/Volumes/102'
}
},
'Resource': {
'@odata.id':
'/redfish/v1/StorageServices/NVMeoE1/Volumes/102'
}
}],
security_req={
'TpmPresent': True,
'TpmInterfaceType': 'TPM2_0',
'TxtEnabled': True,
'ClearTPMOnDelete': True
},
total_system_core_req=8,
total_system_memory_req=16000)
self.node_col._conn.post.assert_called_once_with(
'/redfish/v1/Nodes/Actions/Allocate', data=reqs)
self.assertEqual(result, '/redfish/v1/Nodes/1')
def test_compose_node_invalid_reqs(self):
self.assertRaises(jsonschema.exceptions.ValidationError,
self.node_col.compose_node,
processor_req='invalid')
def test_compose_node_with_invalid_reqs(self):
# Wrong processor type
with self.assertRaisesRegex(
jsonschema.exceptions.ValidationError,
("'invalid' is not one of \['CPU', 'FPGA', 'GPU', 'DSP', "
"'Accelerator', 'OEM'\]")):
self.node_col.compose_node(
name='test', description='this is a test node',
processor_req=[{
'TotalCores': 4,
'ProcessorType': 'invalid'}])
# Wrong processor Oem Brand
with self.assertRaisesRegex(
jsonschema.exceptions.ValidationError,
("'invalid' is not one of \['E3', 'E5'")):
self.node_col.compose_node(
name='test', description='this is a test node',
processor_req=[{
'TotalCores': 4,
'Oem': {
'Brand': 'invalid',
'Capabilities': ['sse']
}
}])
# Wrong processor Oem Capabilities
with self.assertRaisesRegex(
jsonschema.exceptions.ValidationError,
("'sse' is not of type 'array'")):
self.node_col.compose_node(
name='test', description='this is a test node',
processor_req=[{
'TotalCores': 4,
'Oem': {
'Brand': 'E3',
'Capabilities': 'sse'
}
}])
# Wrong processor Oem Capabilities
with self.assertRaisesRegex(
jsonschema.exceptions.ValidationError,
("0 is not of type 'string'")):
self.node_col.compose_node(
name='test', description='this is a test node',
processor_req=[{
'TotalCores': 4,
'Oem': {
'Brand': 'E3',
'Capabilities': [0]
}
}])
# Wrong remote drive CapacityGiB
with self.assertRaisesRegex(
jsonschema.exceptions.ValidationError,
("'invalid' is not of type 'number'")):
self.node_col.compose_node(
name='test', description='this is a test node',
remote_drive_req=[{
'CapacityGiB': 'invalid',
'Protocol': 'NVMeOverFabrics',
'Master': {
'Type': 'Snapshot',
'Resource': {
'@odata.id':
'/redfish/v1/StorageServices/NVMeoE1/Volumes/'
'102'
}
},
'Resource': {
'@odata.id':
'/redfish/v1/StorageServices/NVMeoE1/Volumes/102'
}
}])
# Wrong remote drive Protocol
with self.assertRaisesRegex(
jsonschema.exceptions.ValidationError,
("'invalid' is not one of \['iSCSI', 'NVMeOverFabrics'\]")):
self.node_col.compose_node(
name='test', description='this is a test node',
remote_drive_req=[{
'CapacityGiB': 80,
'Protocol': 'invalid',
'Master': {
'Type': 'Snapshot',
'Resource': {
'@odata.id':
'/redfish/v1/StorageServices/NVMeoE1/Volumes/'
'102'
}
},
'Resource': {
'@odata.id':
'/redfish/v1/StorageServices/NVMeoE1/Volumes/102'
}
}])
# Wrong remote drive Master Type
with self.assertRaisesRegex(
jsonschema.exceptions.ValidationError,
("'invalid' is not one of \['Snapshot', 'Clone'\]")):
self.node_col.compose_node(
name='test', description='this is a test node',
remote_drive_req=[{
'CapacityGiB': 80,
'Protocol': 'iSCSI',
'Master': {
'Type': 'invalid',
'Resource': {
'@odata.id':
'/redfish/v1/StorageServices/NVMeoE1/Volumes/'
'102'
}
},
'Resource': {
'@odata.id':
'/redfish/v1/StorageServices/NVMeoE1/Volumes/102'
}
}])
# Wrong security parameter "TpmPresent"
with self.assertRaisesRegex(
jsonschema.exceptions.ValidationError,
"'invalid' is not of type 'boolean'"):
self.node_col.compose_node(
name='test', description='this is a test node',
security_req={
'TpmPresent': 'invalid',
'TpmInterfaceType': 'TPM2_0',
'TxtEnabled': True,
'ClearTPMOnDelete': True
})
# Wrong security parameter "TpmInterfaceType"
with self.assertRaisesRegex(
jsonschema.exceptions.ValidationError,
"True is not of type 'string'"):
self.node_col.compose_node(
name='test', description='this is a test node',
security_req={
'TpmPresent': False,
'TpmInterfaceType': True,
'TxtEnabled': True,
'ClearTPMOnDelete': True
})
# Wrong security parameter "TxtEnabled"
with self.assertRaisesRegex(
jsonschema.exceptions.ValidationError,
"'invalid' is not of type 'boolean'"):
self.node_col.compose_node(
name='test', description='this is a test node',
security_req={
'TpmPresent': True,
'TpmInterfaceType': 'TPM2_0',
'TxtEnabled': 'invalid',
'ClearTPMOnDelete': True
})
# Wrong security parameter "ClearTPMOnDelete"
with self.assertRaisesRegex(
jsonschema.exceptions.ValidationError,
"'invalid' is not of type 'boolean'"):
self.node_col.compose_node(
name='test', description='this is a test node',
security_req={
'TpmPresent': True,
'TpmInterfaceType': 'TPM2_0',
'TxtEnabled': True,
'ClearTPMOnDelete': 'invalid'
})
# Wrong additional security parameter
with self.assertRaisesRegex(
jsonschema.exceptions.ValidationError,
("Additional properties are not allowed \('invalid-key' was "
"unexpected\)")):
self.node_col.compose_node(
name='test', description='this is a test node',
security_req={
'TpmPresent': True,
'TpmInterfaceType': 'TPM2_0',
'TxtEnabled': False,
'invalid-key': 'invalid-value'
})