Merge "Add support for the `CompositionService` resource"

This commit is contained in:
Zuul 2019-02-28 11:07:26 +00:00 committed by Gerrit Code Review
commit fb465bc3e0
19 changed files with 811 additions and 0 deletions

View File

@ -0,0 +1,8 @@
---
features:
- |
Adds support for the CompositionService resource to the library.
The `CompositionService` is the top level resource for all things
related to Composability. If a Redfish service supports Composability,
the Service Root resource will contain the `CompositionService` property.

View File

@ -19,6 +19,7 @@ from sushy import connector as sushy_connector
from sushy import exceptions
from sushy.resources import base
from sushy.resources.chassis import chassis
from sushy.resources.compositionservice import compositionservice
from sushy.resources.manager import manager
from sushy.resources.registry import message_registry_file
from sushy.resources.sessionservice import session
@ -65,6 +66,10 @@ class Sushy(base.ResourceBase):
'ProtocolFeaturesSupported')
"""The information about protocol features supported by the service"""
_composition_service_path = base.Field(
['CompositionService', '@odata.id'])
"""CompositionService path"""
_systems_path = base.Field(['Systems', '@odata.id'])
"""SystemCollection path"""
@ -257,3 +262,18 @@ class Sushy(base.ResourceBase):
self._conn,
self._registries_path,
redfish_version=self.redfish_version)
def get_composition_service(self):
"""Get the CompositionService object
:raises: MissingAttributeError, if the composition service
attribute is not found
:returns: The CompositionService object
"""
if not self._composition_service_path:
raise exceptions.MissingAttributeError(
attribute='CompositionService/@odata.id',
resource=self._path)
return compositionservice.CompositionService(
self._conn, self._composition_service_path,
redfish_version=self.redfish_version)

View File

@ -0,0 +1,96 @@
# 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.
# This is referred from Redfish standard schema.
# https://redfish.dmtf.org/schemas/CompositionService.v1_1_0.json
import logging
from sushy import exceptions
from sushy.resources import base
from sushy.resources import common
from sushy.resources.compositionservice import resourceblock
from sushy.resources.compositionservice import resourcezone
from sushy import utils
LOG = logging.getLogger(__name__)
class CompositionService(base.ResourceBase):
allow_overprovisioning = base.Field('AllowOverprovisioning')
"""This indicates whether this service is allowed to overprovision"""
allow_zone_affinity = base.Field('AllowZoneAffinity')
"""This indicates whether a client is allowed to request that given
composition request"""
description = base.Field('Description')
"""The composition service description"""
identity = base.Field('Id', required=True)
"""The composition service identity string"""
name = base.Field('Name', required=True)
"""The composition service name"""
status = common.StatusField('Status')
"""The status of composition service"""
service_enabled = base.Field('ServiceEnabled')
"""The status of composition service is enabled"""
def __init__(self, connector, identity, redfish_version=None):
"""A class representing a CompositionService
:param connector: A connector instance
:param identity: The identity of the CompositionService resource
:param redfish_version: The version of RedFish. Used to construct
the object according to schema of given version
"""
super(CompositionService, self).__init__(
connector,
identity,
redfish_version)
def _get_resource_blocks_collection_path(self):
"""Helper function to find the ResourceBlockCollections path"""
res_block_col = self.json.get('ResourceBlocks')
if not res_block_col:
raise exceptions.MissingAttributeError(
attribute='ResourceBlocks', resource=self._path)
return res_block_col.get('@odata.id')
def _get_resource_zones_collection_path(self):
"""Helper function to find the ResourceZoneCollections path"""
res_zone_col = self.json.get('ResourceZones')
if not res_zone_col:
raise exceptions.MissingAttributeError(
attribute='ResourceZones', resource=self._path)
return res_zone_col.get('@odata.id')
@property
@utils.cache_it
def resource_blocks(self):
"""Property to reference `ResourceBlockCollection` instance"""
return resourceblock.ResourceBlockCollection(
self.conn, self._get_resource_blocks_collection_path,
redfish_version=self.redfish_version)
@property
@utils.cache_it
def resource_zones(self):
"""Property to reference `ResourceZoneCollection` instance"""
return resourcezone.ResourceZoneCollection(
self.conn, self._get_resource_zones_collection_path,
redfish_version=self.redfish_version)

View File

@ -0,0 +1,31 @@
# 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.
# Values come from the Redfish ResourceBlock json-schema.
# https://redfish.dmtf.org/schemas/ResourceBlock.v1_3_0.json
# Composition state related constants
COMPOSITION_STATE_COMPOSING = 'Composing'
COMPOSITION_STATE_COMPOSED_AND_AVAILABLE = 'ComposedAndAvailable'
COMPOSITION_STATE_COMPOSED = 'Composed'
COMPOSITION_STATE_UNUSED = 'Unused'
COMPOSITION_STATE_FAILED = 'Failed'
COMPOSITION_STATE_UNAVAILABLE = 'Unavailable'
# Resource Block type related constants
RESOURCE_BLOCK_TYPE_COMPUTE = 'Compute'
RESOURCE_BLOCK_TYPE_PROCESSOR = 'Processor'
RESOURCE_BLOCK_TYPE_MEMORY = 'Memory'
RESOURCE_BLOCK_TYPE_NETWORK = 'Network'
RESOURCE_BLOCK_TYPE_STORAGE = 'Storage'
RESOURCE_BLOCK_TYPE_COMPUTERSYSTEM = 'ComputerSystem'
RESOURCE_BLOCK_TYPE_EXPANSION = 'Expansion'

View File

@ -0,0 +1,40 @@
# 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 sushy.resources.compositionservice import constants as comp_cons
from sushy import utils
COMPOSITION_STATE_VALUE_MAP = {
'Composing': comp_cons.COMPOSITION_STATE_COMPOSING,
'ComposedAndAvailable': comp_cons.COMPOSITION_STATE_COMPOSED_AND_AVAILABLE,
'Composed': comp_cons.COMPOSITION_STATE_COMPOSED,
'Unused': comp_cons.COMPOSITION_STATE_UNUSED,
'Failed': comp_cons.COMPOSITION_STATE_FAILED,
'Unavailable': comp_cons.COMPOSITION_STATE_UNAVAILABLE
}
COMPOSITION_STATE_VALUE_MAP_REV = (
utils.revert_dictionary(COMPOSITION_STATE_VALUE_MAP))
RESOURCE_BLOCK_TYPE_VALUE_MAP = {
'Compute': comp_cons.RESOURCE_BLOCK_TYPE_COMPUTE,
'Processor': comp_cons.RESOURCE_BLOCK_TYPE_PROCESSOR,
'Memory': comp_cons.RESOURCE_BLOCK_TYPE_MEMORY,
'Network': comp_cons.RESOURCE_BLOCK_TYPE_NETWORK,
'Storage': comp_cons.RESOURCE_BLOCK_TYPE_STORAGE,
'ComputerSystem': comp_cons.RESOURCE_BLOCK_TYPE_COMPUTERSYSTEM,
'Expansion': comp_cons.RESOURCE_BLOCK_TYPE_EXPANSION
}
RESOURCE_BLOCK_TYPE_VALUE_MAP_REV = (
utils.revert_dictionary(RESOURCE_BLOCK_TYPE_VALUE_MAP))

View File

@ -0,0 +1,112 @@
# 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.
# This is referred from Redfish standard schema.
# https://redfish.dmtf.org/schemas/ResourceBlock.v1_1_0.json
import logging
from sushy.resources import base
from sushy.resources import common
from sushy.resources.compositionservice import mappings as res_maps
LOG = logging.getLogger(__name__)
class CompositionStatusField(base.CompositeField):
composition_state = base.MappedField(
'CompositionState',
res_maps.COMPOSITION_STATE_VALUE_MAP,
required=True)
"""Inform the client, state of the resource block"""
max_compositions = base.Field('MaxCompositions')
"""The maximum number of compositions"""
number_of_compositions = base.Field('NumberOfCompositions')
"""The number of compositions"""
reserved_state = base.Field('Reserved')
"""Inform the resource block has been identified by a client"""
sharing_capable = base.Field('SharingCapable')
"""Indicates if this Resource Block is capable of participating in
multiple compositions simultaneously"""
sharing_enabled = base.Field('SharingEnabled')
"""Indicates if this Resource Block is allowed to participate in
multiple compositions simultaneously"""
class ResourceBlock(base.ResourceBase):
composition_status = CompositionStatusField(
'CompositionStatus',
required=True)
"""The composition state of resource block"""
description = base.Field('Description')
"""The resource block description"""
identity = base.Field('Id', required=True)
"""The resource block identity string"""
name = base.Field('Name', required=True)
"""The resource block name"""
resource_block_type = base.MappedField(
'ResourceBlockType',
res_maps.RESOURCE_BLOCK_TYPE_VALUE_MAP,
required=True)
"""The type of resource block"""
status = common.StatusField('Status')
"""The status of resource block"""
def __init__(self, connector, identity, redfish_version=None):
"""A class representing a ResourceBlock
:param connector: A Connector instance
:param identity: The identity of the ResourceBlock resource
:param redfish_version: The version of RedFish. Used to construct
the object according to schema of given version.
"""
super(ResourceBlock, self).__init__(
connector,
identity,
redfish_version)
class ResourceBlockCollection(base.ResourceCollectionBase):
name = base.Field('Name')
"""The resource block collection name"""
description = base.Field('Description')
"""The resource block collection description"""
@property
def _resource_type(self):
return ResourceBlock
def __init__(self, connector, identity, redfish_version=None):
"""A class representing a ResourceBlockCollection
:param connector: A Connector instance
:param identity: A identity of the ResourceBlock resource
:param redfish_version: The version of RedFish. Used to construct
the object according to schema of given version.
"""
super(ResourceBlockCollection, self).__init__(
connector, identity, redfish_version)

View File

@ -0,0 +1,92 @@
# 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.
# This is referred from Redfish standard schema.
# https://redfish.dmtf.org/schemas/Zone.v1_2_0.json
import logging
from sushy.resources import base
from sushy.resources import common
LOG = logging.getLogger(__name__)
class LinksField(base.CompositeField):
endpoints = base.Field('Endpoints')
"""The references to the endpoints that are contained in this zone"""
involved_switches = base.Field('InvolvedSwitches')
"""The references to the switches in this zone"""
resource_blocks = base.Field('ResourceBlocks')
"""The references to the Resource Blocks that are used in this zone"""
class ResourceZone(base.ResourceBase):
# Note(dnuka): This patch doesn't contain 100% of the ResourceZone
description = base.Field('Description')
"""The resources zone description"""
identity = base.Field('Id', required=True)
"""The resource zone identity string"""
links = LinksField('Links')
"""The references to other resources that are related to this
resource"""
name = base.Field('Name', required=True)
"""The resource zone name"""
status = common.StatusField('Status')
"""The resource zone status"""
def __init__(self, connector, identity, redfish_version=None):
"""A class representing a ResourceZone
:param connector: A Connector instance
:param identity: The identity of the ResourceZone resource
:param redfish_version: The version of RedFish. Used to construct
the object according to schema of given version.
"""
super(ResourceZone, self).__init__(
connector,
identity,
redfish_version)
class ResourceZoneCollection(base.ResourceCollectionBase):
name = base.Field('Name')
"""The resource zone collection name"""
description = base.Field('Description')
"""The resource zone collection description"""
@property
def _resource_type(self):
return ResourceZone
def __init__(self, connector, identity, redfish_version=None):
"""A class representing a ResourceZoneCollection
:param connector: A Connector instance
:param identity: The identity of the ResourceZone resource
:param redfish_version: The version of RedFish. Used to construct
the object according to schema of given version.
"""
super(ResourceZoneCollection, self).__init__(
connector, identity, redfish_version)

View File

@ -0,0 +1,21 @@
{
"@odata.context": "/redfish/v1/$metadata#CompositionService.CompositionService",
"@odata.type": "#CompositionService.v1_1_0.CompositionService",
"@odata.id": "/redfish/v1/CompositionService",
"AllowOverprovisioning": false,
"AllowZoneAffinity": true,
"Description": "CompositionService1",
"Id": "CompositionService",
"Name": "Composition Service",
"Status": {
"State": "Enabled",
"Health": "OK"
},
"ServiceEnabled": true,
"ResourceBlocks": {
"@odata.id": "/redfish/v1/CompositionService/ResourceBlocks"
},
"ResourceZones": {
"@odata.id": "/redfish/v1/CompositionService/ResourceZones"
}
}

View File

@ -0,0 +1,48 @@
{
"@odata.context": "/redfish/v1/$metadata#ResourceBlock.ResourceBlock",
"@odata.type": "#ResourceBlock.v1_3_0.ResourceBlock",
"@odata.id": "/redfish/v1/CompositionService/ResourceBlocks/DriveBlock3",
"Id": "DriveBlock3",
"Name": "Drive Block 3",
"Description": "ResourceBlock1",
"ResourceBlockType": "Storage",
"Status": {
"State": "Enabled",
"Health": "OK"
},
"CompositionStatus": {
"Reserved": false,
"CompositionState": "Composed",
"MaxCompositions": 1,
"NumberOfCompositions": 0,
"SharingCapable": true,
"SharingEnabled": false
},
"Processors": [],
"Memory": [],
"Storage": [
{
"@odata.id": "/redfish/v1/CompositionService/ResourceBlocks/DriveBlock3/Storage/Block3NVMe"
}
],
"Links": {
"ComputerSystems": [
{
"@odata.id": "/redfish/v1/Systems/ComposedSystem"
}
],
"Chassis": [
{
"@odata.id": "/redfish/v1/Chassis/ComposableModule3"
}
],
"Zones": [
{
"@odata.id": "/redfish/v1/CompositionService/ResourceZones/1"
},
{
"@odata.id": "/redfish/v1/CompositionService/ResourceZones/2"
}
]
}
}

View File

@ -0,0 +1,11 @@
{
"@odata.type": "#ResourceBlockCollection.ResourceBlockCollection",
"@odata.id": "/redfish/v1/CompositionService/ResourceBlocks",
"Name": "Resource Block Collection",
"Members@odata.count": 1,
"Members": [
{ "@odata.id": "/redfish/v1/CompositionService/ResourceBlocks/ComputeBlock1" }
]
}

View File

@ -0,0 +1,50 @@
{
"@odata.context": "/redfish/v1/$metadata#Zone.Zone",
"@odata.type": "#Zone.v1_2_1.Zone",
"@odata.id": "/redfish/v1/CompositionService/ResourceZones/1",
"Id": "1",
"Name": "Resource Zone 1",
"Description": "ResourceZone1",
"Status": {
"State": "Enabled",
"Health": "OK"
},
"Links": {
"ResourceBlocks": [
{
"@odata.id": "/redfish/v1/CompositionService/ResourceBlocks/ComputeBlock1"
},
{
"@odata.id": "/redfish/v1/CompositionService/ResourceBlocks/DriveBlock3"
},
{
"@odata.id": "/redfish/v1/CompositionService/ResourceBlocks/DriveBlock4"
},
{
"@odata.id": "/redfish/v1/CompositionService/ResourceBlocks/DriveBlock5"
},
{
"@odata.id": "/redfish/v1/CompositionService/ResourceBlocks/DriveBlock6"
},
{
"@odata.id": "/redfish/v1/CompositionService/ResourceBlocks/DriveBlock7"
}
]
},
"@Redfish.CollectionCapabilities": {
"@odata.type": "#CollectionCapabilities.v1_0_0.CollectionCapabilities",
"Capabilities": [
{
"CapabilitiesObject": {
"@odata.id": "/redfish/v1/Systems/Capabilities"
},
"UseCase": "ComputerSystemComposition",
"Links": {
"TargetCollection": {
"@odata.id": "/redfish/v1/Systems"
}
}
}
]
}
}

View File

@ -0,0 +1,13 @@
{
"@odata.type": "#ZoneCollection.ZoneCollection",
"@odata.id": "/redfish/v1/CompositionService/ResourceZones",
"Name": "Resource Zone Collection",
"Members@odata.count": 1,
"Members": [
{
"@odata.id": "/redfish/v1/CompositionService/ResourceZones/1"
}
]
}

View File

@ -33,6 +33,9 @@
"AccountService": {
"@odata.id": "/redfish/v1/AccountService"
},
"CompositionService": {
"@odata.id": "/redfish/v1/CompositionService"
},
"EventService": {
"@odata.id": "/redfish/v1/EventService"
},

View File

@ -0,0 +1,49 @@
# 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.
import json
import mock
from sushy.resources.compositionservice import compositionservice
from sushy.resources import constants as res_cons
from sushy.tests.unit import base
class CompositionServiceTestCase(base.TestCase):
def setUp(self):
super(CompositionServiceTestCase, self).setUp()
self.conn = mock.Mock()
with open(
'sushy/tests/unit/json_samples/compositionservice.json') as f:
self.conn.get.return_value.json.return_value = json.load(f)
self.comp_ser = compositionservice.CompositionService(
self.conn,
'/redfish/v1/CompositionService',
redfish_version='1.0.2')
def test__parse_attributes(self):
self.comp_ser._parse_attributes()
self.assertFalse(self.comp_ser.allow_overprovisioning)
self.assertTrue(self.comp_ser.allow_zone_affinity)
self.assertTrue(self.comp_ser.description, 'CompositionService1')
self.assertEqual(
'CompositionService',
self.comp_ser.identity)
self.assertEqual(
'Composition Service',
self.comp_ser.name)
self.assertEqual(res_cons.STATE_ENABLED, self.comp_ser.status.state)
self.assertEqual(res_cons.HEALTH_OK, self.comp_ser.status.health)
self.assertTrue(self.comp_ser.service_enabled)

View File

@ -0,0 +1,107 @@
# 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.
import json
import mock
from sushy import exceptions
from sushy.resources.compositionservice import constants as res_block_cons
from sushy.resources.compositionservice import resourceblock
from sushy.resources import constants as res_cons
from sushy.tests.unit import base
class ResourceBlockTestCase(base.TestCase):
def setUp(self):
super(ResourceBlockTestCase, self).setUp()
self.conn = mock.Mock()
with open('sushy/tests/unit/json_samples/resourceblock.json') as f:
self.conn.get.return_value.json.return_value = json.load(f)
self.res_block = resourceblock.ResourceBlock(
self.conn,
'/redfish/v1/CompositionService/ResourceBlocks/DriveBlock3',
redfish_version='1.0.2')
def test__parse_attributes(self):
self.res_block._parse_attributes()
self.assertEqual(
res_block_cons.COMPOSITION_STATE_COMPOSED,
self.res_block.composition_status.composition_state)
self.assertEqual(1, self.res_block.composition_status.max_compositions)
self.assertEqual(
0, self.res_block.composition_status.number_of_compositions)
self.assertFalse(self.res_block.composition_status.reserved_state)
self.assertTrue(self.res_block.composition_status.sharing_capable)
self.assertFalse(self.res_block.composition_status.sharing_enabled)
self.assertEqual('ResourceBlock1', self.res_block.description)
self.assertEqual('DriveBlock3', self.res_block.identity)
self.assertEqual('Drive Block 3', self.res_block.name)
self.assertEqual(
res_block_cons.RESOURCE_BLOCK_TYPE_STORAGE,
self.res_block.resource_block_type)
self.assertEqual(
res_cons.STATE_ENABLED,
self.res_block.status.state)
self.assertEqual(res_cons.HEALTH_OK, self.res_block.status.health)
exp_path = '/redfish/v1/CompositionService/ResourceBlocks/DriveBlock3'
self.assertEqual(exp_path, self.res_block.path)
def test__parse_attributes_missing_identity(self):
self.res_block.json.pop('Id')
self.assertRaisesRegex(
exceptions.MissingAttributeError, 'attribute Id',
self.res_block._parse_attributes)
class ResourceBlockCollectionTestCase(base.TestCase):
def setUp(self):
super(ResourceBlockCollectionTestCase, self).setUp()
self.conn = mock.Mock()
with open('sushy/tests/unit/json_samples/'
'resourceblock_collection.json') as f:
self.conn.get.return_value.json.return_value = json.load(f)
self.res_block_col = resourceblock.ResourceBlockCollection(
self.conn, '/redfish/v1/CompositionService/ResourceBlocks',
redfish_version='1.0.2')
def test__parse_attributes(self):
path = '/redfish/v1/CompositionService/ResourceBlocks/ComputeBlock1'
self.res_block_col._parse_attributes()
self.assertEqual('1.0.2', self.res_block_col.redfish_version)
self.assertEqual(
'Resource Block Collection',
self.res_block_col.name)
self.assertEqual((path,), self.res_block_col.members_identities)
@mock.patch.object(resourceblock, 'ResourceBlock', autospec=True)
def test_get_member(self, mock_resourceblock):
path = '/redfish/v1/CompositionService/ResourceBlocks/ComputeBlock1'
self.res_block_col.get_member(path)
mock_resourceblock.assert_called_once_with(
self.res_block_col._conn, path,
redfish_version=self.res_block_col.redfish_version)
@mock.patch.object(resourceblock, 'ResourceBlock', autospec=True)
def test_get_members(self, mock_resourceblock):
path = '/redfish/v1/CompositionService/ResourceBlocks/ComputeBlock1'
members = self.res_block_col.get_members()
mock_resourceblock.assert_called_once_with(
self.res_block_col._conn, path,
redfish_version=self.res_block_col.redfish_version)
self.assertIsInstance(members, list)
self.assertEqual(1, len(members))

View File

@ -0,0 +1,93 @@
# 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.
import json
import mock
from sushy import exceptions
from sushy.resources.compositionservice import resourcezone
from sushy.resources import constants as res_cons
from sushy.tests.unit import base
class ResourceZoneTestCase(base.TestCase):
def setUp(self):
super(ResourceZoneTestCase, self).setUp()
self.conn = mock.Mock()
with open('sushy/tests/unit/json_samples/resourcezone.json') as f:
self.conn.get.return_value.json.return_value = json.load(f)
self.res_zone = resourcezone.ResourceZone(
self.conn,
'/redfish/v1/CompositionService/ResourceZones/1',
redfish_version='1.0.2')
def test__parse_attributes(self):
self.res_zone._parse_attributes()
self.assertEqual('ResourceZone1', self.res_zone.description)
self.assertEqual('1', self.res_zone.identity)
self.assertEqual('Resource Zone 1', self.res_zone.name)
self.assertEqual(
res_cons.STATE_ENABLED,
self.res_zone.status.state)
self.assertEqual(
res_cons.HEALTH_OK,
self.res_zone.status.health)
exp_path = '/redfish/v1/CompositionService/ResourceZones/1'
self.assertEqual(exp_path, self.res_zone.path)
def test__parse_attributes_missing_identity(self):
self.res_zone.json.pop('Id')
self.assertRaisesRegex(
exceptions.MissingAttributeError, 'attribute Id',
self.res_zone._parse_attributes)
class ResourceZoneCollectionTestCase(base.TestCase):
def setUp(self):
super(ResourceZoneCollectionTestCase, self).setUp()
self.conn = mock.Mock()
with open('sushy/tests/unit/json_samples/'
'resourcezone_collection.json') as f:
self.conn.get.return_value.json.return_value = json.load(f)
self.res_zone_col = resourcezone.ResourceZoneCollection(
self.conn, '/redfish/v1/CompositionService/ResourceZones',
redfish_version='1.0.2')
def test__parse_attributes(self):
path = '/redfish/v1/CompositionService/ResourceZones/1'
self.res_zone_col._parse_attributes()
self.assertEqual('1.0.2', self.res_zone_col.redfish_version)
self.assertEqual('Resource Zone Collection', self.res_zone_col.name)
self.assertEqual((path,), self.res_zone_col.members_identities)
@mock.patch.object(resourcezone, 'ResourceZone', autospec=True)
def test_get_member(self, mock_resourcezone):
path = '/redfish/v1/CompositionService/ResourceZones/1'
self.res_zone_col.get_member(path)
mock_resourcezone.assert_called_once_with(
self.res_zone_col._conn, path,
redfish_version=self.res_zone_col.redfish_version)
@mock.patch.object(resourcezone, 'ResourceZone', autospec=True)
def test_get_members(self, mock_resourcezone):
path = '/redfish/v1/CompositionService/ResourceZones/1'
members = self.res_zone_col.get_members()
mock_resourcezone.assert_called_once_with(
self.res_zone_col._conn, path,
redfish_version=self.res_zone_col.redfish_version)
self.assertIsInstance(members, list)
self.assertEqual(1, len(members))

View File

@ -22,6 +22,7 @@ from sushy import connector
from sushy import exceptions
from sushy import main
from sushy.resources.chassis import chassis
from sushy.resources.compositionservice import compositionservice
from sushy.resources.manager import manager
from sushy.resources.registry import message_registry_file
from sushy.resources.sessionservice import session
@ -69,6 +70,8 @@ class MainTestCase(base.TestCase):
self.assertEqual('/redfish/v1/Chassis', self.root._chassis_path)
self.assertEqual('/redfish/v1/SessionService',
self.root._session_service_path)
self.assertEqual('/redfish/v1/CompositionService',
self.root._composition_service_path)
@mock.patch.object(connector, 'Connector', autospec=True)
def test__init_throws_exception(self, mock_Connector):
@ -161,6 +164,14 @@ class MainTestCase(base.TestCase):
self.root._conn, '/redfish/v1/Registries',
redfish_version=self.root.redfish_version)
@mock.patch.object(
compositionservice, 'CompositionService', autospec=True)
def test_get_composition_service(self, mock_comp_ser):
self.root.get_composition_service()
mock_comp_ser.assert_called_once_with(
self.root._conn, '/redfish/v1/CompositionService',
redfish_version=self.root.redfish_version)
class BareMinimumMainTestCase(base.TestCase):
@ -198,5 +209,11 @@ class BareMinimumMainTestCase(base.TestCase):
exceptions.MissingAttributeError,
'UpdateService/@odata.id', self.root.get_update_service)
def test_get_composition_service_when_compositionservice_attr_absent(
self):
self.assertRaisesRegex(
exceptions.MissingAttributeError,
'CompositionService/@odata.id', self.root.get_composition_service)
def test__get_registry_collection_when_registries_attr_absent(self):
self.assertIsNone(self.root._get_registry_collection())