Allow to create new endpoint
Change-Id: Ie4024a84a0181bec61186d9ee0f8e436182841a0
This commit is contained in:
parent
250b7e1c5e
commit
9f097bb1a2
@ -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):]
|
||||
|
112
rsd_lib/resources/v2_3/fabric/endpoint_schemas.py
Normal file
112
rsd_lib/resources/v2_3/fabric/endpoint_schemas.py
Normal 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
|
||||
}
|
@ -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
|
||||
|
@ -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'})
|
||||
|
Loading…
x
Reference in New Issue
Block a user