Allow to create new endpoint

Change-Id: Ie4024a84a0181bec61186d9ee0f8e436182841a0
This commit is contained in:
Lin Yang 2018-05-08 16:47:14 -07:00
parent 250b7e1c5e
commit 9f097bb1a2
4 changed files with 436 additions and 2 deletions

View File

@ -13,11 +13,13 @@
# License for the specific language governing permissions and limitations
# under the License.
import jsonschema
import logging
from sushy.resources import base
from sushy import utils
from rsd_lib.resources.v2_3.fabric import endpoint_schemas
from rsd_lib import utils as rsd_lib_utils
@ -156,3 +158,76 @@ class EndpointCollection(base.ResourceCollectionBase):
"""
super(EndpointCollection, self).__init__(connector, path,
redfish_version)
def _create_endpoint_request(self, identifiers, connected_entities,
protocol=None, ip_transport_details=None,
interface=None, authentication=None):
request = {}
jsonschema.validate(identifiers,
endpoint_schemas.identifiers_req_schema)
request['Identifiers'] = identifiers
jsonschema.validate(connected_entities,
endpoint_schemas.connected_entities_req_schema)
request['ConnectedEntities'] = connected_entities
if protocol is not None:
jsonschema.validate(protocol, endpoint_schemas.protocol_req_schema)
request['EndpointProtocol'] = protocol
if ip_transport_details is not None:
jsonschema.validate(
ip_transport_details,
endpoint_schemas.ip_transport_details_req_schema)
request['IPTransportDetails'] = ip_transport_details
if interface is not None:
jsonschema.validate(interface,
endpoint_schemas.interface_req_schema)
request['Links'] = {
"Oem": {
"Intel_RackScale": {
"Interfaces": [
{
"@odata.id": interface
}
]
}
}
}
if authentication is not None:
jsonschema.validate(authentication,
endpoint_schemas.authentication_req_schema)
request['Oem'] = {"Intel_RackScale":
{"Authentication": authentication}}
return request
def create_endpoint(self, identifiers, connected_entities, protocol=None,
ip_transport_details=None, interface=None,
authentication=None):
"""Create a new endpoint
:param identifiers: provides iQN or NQN of created entity
:param connected_entities: provides information about entities
connected to the endpoint
:param protocol: the protocol used by the endpoint
:param ip_transport_details: the transport used for accessing the
endpoint
:param interface: the interface that should be used for the endpoint
connectivity
:param authentication: authentication data for target-initiator
authentication. Currently supported only for the
iSCSI protocol.
:returns: The uri of the new endpoint
"""
properties = self._create_endpoint_request(
identifiers, connected_entities, protocol, ip_transport_details,
interface, authentication)
resp = self._conn.post(self._path, data=properties)
LOG.info("Endpoint created at %s", resp.headers['Location'])
endpoint_url = resp.headers['Location']
return endpoint_url[endpoint_url.find(self._path):]

View File

@ -0,0 +1,112 @@
# 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.
identifiers_req_schema = {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'DurableNameFormat': {
'type': 'string',
'enum': ['NQN', 'iQN']
},
'DurableName': {'type': 'string'}
},
"required": ['DurableNameFormat', 'DurableName'],
'additionalProperties': False
}
}
connected_entities_req_schema = {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'EntityLink': {
'type': 'object',
'properties': {
'@odata.id': {'type': 'string'}
},
"required": ['@odata.id'],
'additionalProperties': False
},
'EntityRole': {
'type': 'string',
'enum': ['Initiator', 'Target', 'Both']
},
'Identifiers': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'DurableNameFormat': {
'type': 'string',
'enum': ['NQN', 'iQN', 'FC_WWN', 'UUID', 'EUI',
'NAA', 'NSID', 'SystemPath', 'LUN']
},
'DurableName': {'type': 'string'}
},
"required": ['DurableNameFormat', 'DurableName'],
'additionalProperties': False
}
}
},
"required": ['EntityLink', 'EntityRole'],
'additionalProperties': False
}
}
protocol_req_schema = {
'type': 'string',
'enum': ['NVMeOverFabrics', 'iSCSI']
}
ip_transport_details_req_schema = {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'TransportProtocol': {'type': 'string'},
'IPv4Address': {
'type': 'object',
'properties': {
'Address': {'type': 'string'}
},
'additionalProperties': False
},
'IPv6Address': {
'type': 'object',
'properties': {
'Address': {'type': 'string'}
},
'additionalProperties': False
},
'Port': {'type': 'number'}
},
'additionalProperties': False
}
}
interface_req_schema = {
'type': 'string'
}
authentication_req_schema = {
'type': 'object',
'properties': {
'Username': {'type': 'string'},
'Password': {'type': 'string'}
},
'additionalProperties': False
}

View File

@ -246,7 +246,7 @@ class VolumeCollection(base.ResourceCollectionBase):
def create_volume(self, capacity, access_capabilities=None,
capacity_sources=None, replica_infos=None,
bootable=None):
"""Compose a node from RackScale hardware
"""Create a new volume
:param capacity: Requested volume capacity in bytes
:param access_capabilities: List of volume access capabilities

View File

@ -13,11 +13,14 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
import json
import jsonschema
import mock
import testtools
from rsd_lib.resources.v2_3.fabric import endpoint
from rsd_lib.tests.unit.fakes import request_fakes
class EndpointTestCase(testtools.TestCase):
@ -175,8 +178,13 @@ class EndpointCollectionTestCase(testtools.TestCase):
with open('rsd_lib/tests/unit/json_samples/v2_3/'
'endpoint_collection.json', 'r') as f:
self.conn.get.return_value.json.return_value = json.loads(f.read())
self.conn.post.return_value = request_fakes.fake_request_post(
None, headers={"Location": "https://localhost:8443/redfish/v1/"
"Fabrics/NVMeoE/Endpoints/3"})
self.endpoint_col = endpoint.EndpointCollection(
self.conn, '/redfish/v1/Fabrics/PCIe/Endpoints',
self.conn, '/redfish/v1/Fabrics/NVMeoE/Endpoints',
redfish_version='1.0.2')
def test__parse_attributes(self):
@ -211,3 +219,242 @@ class EndpointCollectionTestCase(testtools.TestCase):
mock_endpoint.assert_has_calls(calls)
self.assertIsInstance(members, list)
self.assertEqual(2, len(members))
def test_create_endpoint(self):
reqs = {
"EndpointProtocol": "NVMeOverFabrics",
"Identifiers": [
{
"DurableNameFormat": "NQN",
"DurableName": "nqn.2014-08.org.nvmexpress:NVMf:"
"uuid:397f9b78-7e94-11e7-9ea4-001e67dfa170"
}
],
"ConnectedEntities": [
{
"EntityLink": {
"@odata.id": "/redfish/v1/StorageServices/1/Volumes/1"
},
"EntityRole": "Target"
}
],
"Links": {
"Oem": {
"Intel_RackScale": {
"Interfaces": [
{
"@odata.id": "/redfish/v1/Systems/Target/"
"EthernetInterfaces/1"
}
]
}
}
}
}
result = self.endpoint_col.create_endpoint(
identifiers=[
{
"DurableNameFormat": "NQN",
"DurableName": "nqn.2014-08.org.nvmexpress:NVMf:"
"uuid:397f9b78-7e94-11e7-9ea4-001e67dfa170"
}
],
connected_entities=[
{
"EntityLink": {
"@odata.id": "/redfish/v1/StorageServices/1/Volumes/1"
},
"EntityRole": "Target"
}
],
protocol="NVMeOverFabrics",
interface="/redfish/v1/Systems/Target/EthernetInterfaces/1")
self.endpoint_col._conn.post.assert_called_once_with(
'/redfish/v1/Fabrics/NVMeoE/Endpoints', data=reqs)
self.assertEqual(result,
'/redfish/v1/Fabrics/NVMeoE/Endpoints/3')
self.endpoint_col._conn.post.reset_mock()
reqs = {
"EndpointProtocol": "iSCSI",
"Identifiers": [
{
"DurableNameFormat": "iQN",
"DurableName": "iqn.1986-03.com.intel:my_storage-uuid:"
"397f9b78-7e94-11e7-9ea4-001e67dfa170"
}
],
"ConnectedEntities": [
{
"EntityLink": {
"@odata.id": "/redfish/v1/StorageServices/1/Volumes/1"
},
"EntityRole": "Target",
"Identifiers": [
{
"DurableNameFormat": "LUN",
"DurableName": "1"
}
]
}
],
"Oem": {
"Intel_RackScale": {
"Authentication": {
"Username": "userA",
"Password": "passB"
}
}
}
}
result = self.endpoint_col.create_endpoint(
identifiers=[
{
"DurableNameFormat": "iQN",
"DurableName": "iqn.1986-03.com.intel:my_storage-uuid:"
"397f9b78-7e94-11e7-9ea4-001e67dfa170"
}
],
connected_entities=[
{
"EntityLink": {
"@odata.id": "/redfish/v1/StorageServices/1/Volumes/1"
},
"EntityRole": "Target",
"Identifiers": [
{
"DurableNameFormat": "LUN",
"DurableName": "1"
}
]
}
],
protocol="iSCSI",
authentication={
"Username": "userA",
"Password": "passB"
})
self.endpoint_col._conn.post.assert_called_once_with(
'/redfish/v1/Fabrics/NVMeoE/Endpoints', data=reqs)
self.assertEqual(result,
'/redfish/v1/Fabrics/NVMeoE/Endpoints/3')
def test_create_endpoint_with_invalid_reqs(self):
identifiers = [
{
"DurableNameFormat": "iQN",
"DurableName": "iqn.1986-03.com.intel:my_storage-uuid:"
"397f9b78-7e94-11e7-9ea4-001e67dfa170"
}
]
connected_entities = [
{
"EntityLink": {
"@odata.id": "/redfish/v1/StorageServices/1/Volumes/1"
},
"EntityRole": "Target",
"Identifiers": [
{
"DurableNameFormat": "LUN",
"DurableName": "1"
}
]
}
]
result = self.endpoint_col.create_endpoint(
identifiers=identifiers, connected_entities=connected_entities)
self.assertEqual(result,
'/redfish/v1/Fabrics/NVMeoE/Endpoints/3')
# Test invalid identifiers argument
invalid_identifiers = copy.deepcopy(identifiers)
invalid_identifiers[0]['DurableNameFormat'] = 'fake-format'
self.assertRaises(jsonschema.exceptions.ValidationError,
self.endpoint_col.create_endpoint,
identifiers=invalid_identifiers,
connected_entities=connected_entities)
invalid_identifiers = copy.deepcopy(identifiers)
invalid_identifiers[0].pop('DurableNameFormat')
self.assertRaises(jsonschema.exceptions.ValidationError,
self.endpoint_col.create_endpoint,
identifiers=invalid_identifiers,
connected_entities=connected_entities)
invalid_identifiers = copy.deepcopy(identifiers)
invalid_identifiers[0].pop('DurableName')
self.assertRaises(jsonschema.exceptions.ValidationError,
self.endpoint_col.create_endpoint,
identifiers=invalid_identifiers,
connected_entities=connected_entities)
invalid_identifiers = copy.deepcopy(identifiers)
invalid_identifiers[0]['invalid_key'] = 'invalid_value'
self.assertRaises(jsonschema.exceptions.ValidationError,
self.endpoint_col.create_endpoint,
identifiers=invalid_identifiers,
connected_entities=connected_entities)
# Test invalid connected_entities argument
invalid_connected_entities = copy.deepcopy(connected_entities)
invalid_connected_entities[0]['EntityRole'] = 'fake-format'
self.assertRaises(jsonschema.exceptions.ValidationError,
self.endpoint_col.create_endpoint,
identifiers=identifiers,
connected_entities=invalid_connected_entities)
invalid_connected_entities = copy.deepcopy(connected_entities)
invalid_connected_entities[0]['EntityLink'].pop('@odata.id')
self.assertRaises(jsonschema.exceptions.ValidationError,
self.endpoint_col.create_endpoint,
identifiers=identifiers,
connected_entities=invalid_connected_entities)
invalid_connected_entities = copy.deepcopy(connected_entities)
invalid_connected_entities[0].pop('EntityLink')
self.assertRaises(jsonschema.exceptions.ValidationError,
self.endpoint_col.create_endpoint,
identifiers=identifiers,
connected_entities=invalid_connected_entities)
invalid_connected_entities = copy.deepcopy(connected_entities)
invalid_connected_entities[0].pop('EntityRole')
self.assertRaises(jsonschema.exceptions.ValidationError,
self.endpoint_col.create_endpoint,
identifiers=identifiers,
connected_entities=invalid_connected_entities)
invalid_connected_entities = copy.deepcopy(connected_entities)
invalid_connected_entities[0]['invalid_key'] = 'invalid_value'
self.assertRaises(jsonschema.exceptions.ValidationError,
self.endpoint_col.create_endpoint,
identifiers=identifiers,
connected_entities=invalid_connected_entities)
# Test invalid protocol argument
self.assertRaises(jsonschema.exceptions.ValidationError,
self.endpoint_col.create_endpoint,
identifiers=identifiers,
connected_entities=connected_entities,
protocol='invalid_potocol')
self.assertRaises(jsonschema.exceptions.ValidationError,
self.endpoint_col.create_endpoint,
identifiers=identifiers,
connected_entities=connected_entities,
protocol=1)
# Test invalid interface argument
self.assertRaises(jsonschema.exceptions.ValidationError,
self.endpoint_col.create_endpoint,
identifiers=identifiers,
connected_entities=connected_entities,
interface=1)
# Test invalid authentication argument
self.assertRaises(jsonschema.exceptions.ValidationError,
self.endpoint_col.create_endpoint,
identifiers=identifiers,
connected_entities=connected_entities,
authentication={'invalid_key': 'invalid_value'})