Sushy to adhere to the resource identifier portion of the spec
This patch is intended to make the handle of the resources identification more flexible in the library. Prior to this patch the library had some assumptions about the path of a given resource and tried to build such paths within it. Turns out that it's not that simple, the Redfish specification does allow things like resources from a different authority to be registered in a different controller [0]. I quote: "Resources within the same authority as the request URI shall be represented according to the rules of path-absolute defined by that specification. That is, they shall always start with a single forward slash ("/"). Resources within a different authority as the request URI shall start with a double-slash ("//") followed by the authority and path to the resource." That means that, members of a collection could be either: "/redfish/v1/Systems/12345" or "//another-service.com/redfish/v1/Systems/12345" This breaks many of the assumptions we had before of how to handle these resources from the controller. So, this patch is basically making sushy more "dumb" about the location of the resources by not trying to build the URI for it but instead just relying on the URI returned from the members of a collection or other attributes of a specific resource. One example here was the "target" attribute of the #ComputerSystem.Reset reset action, prior to this patch we needed to strip portions of the URI (the /redfish/v1) in order to fit the model that we use to construct the URLs now, that's not needed anymore. Note that, this patch is non-backward compatible and changes the usage of the library but, since we haven't had any release yet (so it's not even alpha or beta, but completely under-development) it seems to be the right time for such a change. [0] http://redfish.dmtf.org/schemas/DSP0266_1.1.html#resource-identifier-property Change-Id: Ia4211ebb3b99f6cc4bd695b5dbea2018d301de33
This commit is contained in:
parent
39f9db1245
commit
392a4a6272
@ -17,14 +17,13 @@ To use sushy in a project:
|
|||||||
LOG.setLevel(logging.DEBUG)
|
LOG.setLevel(logging.DEBUG)
|
||||||
LOG.addHandler(logging.StreamHandler())
|
LOG.addHandler(logging.StreamHandler())
|
||||||
|
|
||||||
s = sushy.Sushy('http://localhost:8000/redfish/v1',
|
s = sushy.Sushy('http://localhost:8000', username='foo', password='bar')
|
||||||
username='foo', password='bar')
|
|
||||||
|
|
||||||
# Get the Redfish version
|
# Get the Redfish version
|
||||||
print(s.redfish_version)
|
print(s.redfish_version)
|
||||||
|
|
||||||
# Instantiate a system object
|
# Instantiate a system object
|
||||||
sys_inst = s.get_system('437XR1138R2')
|
sys_inst = s.get_system('/redfish/v1/Systems/437XR1138R2')
|
||||||
|
|
||||||
|
|
||||||
# Using system collections
|
# Using system collections
|
||||||
@ -124,8 +123,8 @@ setup it do:
|
|||||||
|
|
||||||
sudo dnf install -y libvirt-devel
|
sudo dnf install -y libvirt-devel
|
||||||
|
|
||||||
That's it, now you can test Sushy against the
|
That's it, now you can test Sushy against the ``http://locahost:8000``
|
||||||
``http://locahost:8000/redfish/v1`` endpoint.
|
endpoint.
|
||||||
|
|
||||||
|
|
||||||
Enabling SSL
|
Enabling SSL
|
||||||
@ -150,6 +149,6 @@ pointing to the certificate file when instantiating Sushy, for example:
|
|||||||
import sushy
|
import sushy
|
||||||
|
|
||||||
# Note the HTTP"S"
|
# Note the HTTP"S"
|
||||||
s = sushy.Sushy('https://localhost:8000/redfish/v1', verify='cert.pem', username='foo', password='bar')
|
s = sushy.Sushy('https://localhost:8000', verify='cert.pem', username='foo', password='bar')
|
||||||
|
|
||||||
.. _SSL: https://en.wikipedia.org/wiki/Secure_Sockets_Layer
|
.. _SSL: https://en.wikipedia.org/wiki/Secure_Sockets_Layer
|
||||||
|
@ -15,9 +15,9 @@
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
from six.moves.urllib import parse
|
||||||
|
|
||||||
from sushy import exceptions
|
from sushy import exceptions
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ class Connector(object):
|
|||||||
if data is not None:
|
if data is not None:
|
||||||
data = json.dumps(data)
|
data = json.dumps(data)
|
||||||
|
|
||||||
url = os.path.join(self._url, path)
|
url = parse.urljoin(self._url, path)
|
||||||
# TODO(lucasagomes): We should mask the data to remove sensitive
|
# TODO(lucasagomes): We should mask the data to remove sensitive
|
||||||
# information
|
# information
|
||||||
LOG.debug('Issuing a HTTP %(method)s request at %(url)s with '
|
LOG.debug('Issuing a HTTP %(method)s request at %(url)s with '
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from sushy import connector
|
from sushy import connector
|
||||||
|
from sushy import exceptions
|
||||||
from sushy.resources import base
|
from sushy.resources import base
|
||||||
from sushy.resources.system import system
|
from sushy.resources.system import system
|
||||||
|
|
||||||
@ -29,9 +30,29 @@ class Sushy(base.ResourceBase):
|
|||||||
uuid = None
|
uuid = None
|
||||||
"""The Redfish system UUID"""
|
"""The Redfish system UUID"""
|
||||||
|
|
||||||
def __init__(self, url, username=None, password=None, verify=True):
|
def __init__(self, base_url, username=None, password=None,
|
||||||
|
root_prefix='/redfish/v1/', verify=True):
|
||||||
|
"""A class representing a RootService
|
||||||
|
|
||||||
|
:param base_url: The base URL to the Redfish controller. It
|
||||||
|
should include scheme and authority portion of the URL. For
|
||||||
|
example: https://mgmt.vendor.com
|
||||||
|
:param username: User account with admin/server-profile access
|
||||||
|
privilege
|
||||||
|
:param password: User account password
|
||||||
|
:param root_prefix: The default URL prefix. This part includes
|
||||||
|
the root service and version. Defaults to /redfish/v1
|
||||||
|
:param verify: Either a boolean value, a path to a CA_BUNDLE
|
||||||
|
file or directory with certificates of trusted CAs. If set to
|
||||||
|
True the driver will verify the host certificates; if False
|
||||||
|
the driver will ignore verifying the SSL certificate; if it's
|
||||||
|
a path the driver will use the specified certificate or one of
|
||||||
|
the certificates in the directory. Defaults to True.
|
||||||
|
"""
|
||||||
|
self._root_prefix = root_prefix
|
||||||
super(Sushy, self).__init__(
|
super(Sushy, self).__init__(
|
||||||
connector.Connector(url, username, password, verify))
|
connector.Connector(base_url, username, password, verify),
|
||||||
|
path=self._root_prefix)
|
||||||
|
|
||||||
def _parse_attributes(self):
|
def _parse_attributes(self):
|
||||||
self.identity = self.json.get('Id')
|
self.identity = self.json.get('Id')
|
||||||
@ -39,12 +60,23 @@ class Sushy(base.ResourceBase):
|
|||||||
self.redfish_version = self.json.get('RedfishVersion')
|
self.redfish_version = self.json.get('RedfishVersion')
|
||||||
self.uuid = self.json.get('UUID')
|
self.uuid = self.json.get('UUID')
|
||||||
|
|
||||||
|
def _get_system_collection_path(self):
|
||||||
|
"""Helper function to find the SystemCollection path"""
|
||||||
|
systems_col = self.json.get('Systems')
|
||||||
|
if not systems_col:
|
||||||
|
raise exceptions.MissingAttributeError(attribute='Systems',
|
||||||
|
resource=self._path)
|
||||||
|
return systems_col.get('@odata.id')
|
||||||
|
|
||||||
def get_system_collection(self):
|
def get_system_collection(self):
|
||||||
"""Get the SystemCollection object
|
"""Get the SystemCollection object
|
||||||
|
|
||||||
|
:raises: MissingAttributeError, if the collection attribute is
|
||||||
|
not found
|
||||||
:returns: a SystemCollection object
|
:returns: a SystemCollection object
|
||||||
"""
|
"""
|
||||||
return system.SystemCollection(self._conn,
|
return system.SystemCollection(
|
||||||
|
self._conn, self._get_system_collection_path(),
|
||||||
redfish_version=self.redfish_version)
|
redfish_version=self.redfish_version)
|
||||||
|
|
||||||
def get_system(self, identity):
|
def get_system(self, identity):
|
||||||
|
@ -113,12 +113,12 @@ class ResourceCollectionBase(ResourceBase):
|
|||||||
def _parse_attributes(self):
|
def _parse_attributes(self):
|
||||||
self.name = self.json.get('Name')
|
self.name = self.json.get('Name')
|
||||||
self.members_identities = (
|
self.members_identities = (
|
||||||
utils.get_members_ids(self.json.get('Members', [])))
|
utils.get_members_identities(self.json.get('Members', [])))
|
||||||
|
|
||||||
def get_member(self, identity):
|
def get_member(self, identity):
|
||||||
"""Given the identity return a ``_resource_type`` object
|
"""Given the identity return a ``_resource_type`` object
|
||||||
|
|
||||||
:param identity: The identity of the ``_resource_type`` resource
|
:param identity: The identity of the ``_resource_type``
|
||||||
:returns: The ``_resource_type`` object
|
:returns: The ``_resource_type`` object
|
||||||
:raises: ResourceNotFoundError
|
:raises: ResourceNotFoundError
|
||||||
"""
|
"""
|
||||||
|
@ -19,7 +19,6 @@ from sushy import exceptions
|
|||||||
from sushy.resources import base
|
from sushy.resources import base
|
||||||
from sushy.resources.system import constants as sys_cons
|
from sushy.resources.system import constants as sys_cons
|
||||||
from sushy.resources.system import mappings as sys_maps
|
from sushy.resources.system import mappings as sys_maps
|
||||||
from sushy import utils
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -77,12 +76,11 @@ class System(base.ResourceBase):
|
|||||||
"""A class representing a ComputerSystem
|
"""A class representing a ComputerSystem
|
||||||
|
|
||||||
:param connector: A Connector instance
|
:param connector: A Connector instance
|
||||||
:param identity: The id of the ComputerSystem
|
:param identity: The identity of the System resource
|
||||||
:param redfish_version: The version of RedFish. Used to construct
|
:param redfish_version: The version of RedFish. Used to construct
|
||||||
the object according to schema of the given version.
|
the object according to schema of the given version.
|
||||||
"""
|
"""
|
||||||
super(System, self).__init__(connector, 'Systems/%s' % identity,
|
super(System, self).__init__(connector, identity, redfish_version)
|
||||||
redfish_version)
|
|
||||||
|
|
||||||
def _parse_attributes(self):
|
def _parse_attributes(self):
|
||||||
self.asset_tag = self.json.get('AssetTag')
|
self.asset_tag = self.json.get('AssetTag')
|
||||||
@ -144,13 +142,13 @@ class System(base.ResourceBase):
|
|||||||
def _get_reset_system_path(self):
|
def _get_reset_system_path(self):
|
||||||
reset_action = self._get_reset_action_element()
|
reset_action = self._get_reset_action_element()
|
||||||
|
|
||||||
target_url = reset_action.get('target')
|
target_uri = reset_action.get('target')
|
||||||
if not target_url:
|
if not target_uri:
|
||||||
raise exceptions.MissingAttributeError(
|
raise exceptions.MissingAttributeError(
|
||||||
attribute='Actions/ComputerSystem.Reset/target',
|
attribute='Actions/ComputerSystem.Reset/target',
|
||||||
resource=self._path)
|
resource=self._path)
|
||||||
|
|
||||||
return utils.strip_redfish_base(target_url)
|
return target_uri
|
||||||
|
|
||||||
def reset_system(self, value):
|
def reset_system(self, value):
|
||||||
"""Reset the system.
|
"""Reset the system.
|
||||||
@ -165,11 +163,11 @@ class System(base.ResourceBase):
|
|||||||
parameter='value', value=value, valid_values=valid_resets)
|
parameter='value', value=value, valid_values=valid_resets)
|
||||||
|
|
||||||
value = sys_maps.RESET_SYSTEM_VALUE_MAP_REV[value]
|
value = sys_maps.RESET_SYSTEM_VALUE_MAP_REV[value]
|
||||||
|
target_uri = self._get_reset_system_path()
|
||||||
|
|
||||||
path = self._get_reset_system_path()
|
|
||||||
# TODO(lucasagomes): Check the return code and response body ?
|
# TODO(lucasagomes): Check the return code and response body ?
|
||||||
# Probably we should call refresh() as well.
|
# Probably we should call refresh() as well.
|
||||||
self._conn.post(path, data={'ResetType': value})
|
self._conn.post(target_uri, data={'ResetType': value})
|
||||||
|
|
||||||
def get_allowed_system_boot_source_values(self):
|
def get_allowed_system_boot_source_values(self):
|
||||||
"""Get the allowed values for changing the boot source.
|
"""Get the allowed values for changing the boot source.
|
||||||
@ -254,12 +252,13 @@ class SystemCollection(base.ResourceCollectionBase):
|
|||||||
def _resource_type(self):
|
def _resource_type(self):
|
||||||
return System
|
return System
|
||||||
|
|
||||||
def __init__(self, connector, redfish_version=None):
|
def __init__(self, connector, path, redfish_version=None):
|
||||||
"""A class representing a ComputerSystemCollection
|
"""A class representing a ComputerSystemCollection
|
||||||
|
|
||||||
:param connector: A Connector instance
|
:param connector: A Connector instance
|
||||||
|
:param path: The canonical path to the System collection resource
|
||||||
:param redfish_version: The version of RedFish. Used to construct
|
:param redfish_version: The version of RedFish. Used to construct
|
||||||
the object according to schema of the given version.
|
the object according to schema of the given version.
|
||||||
"""
|
"""
|
||||||
super(SystemCollection, self).__init__(connector, 'Systems',
|
super(SystemCollection, self).__init__(connector, path,
|
||||||
redfish_version)
|
redfish_version)
|
||||||
|
@ -31,7 +31,8 @@ class SystemTestCase(base.TestCase):
|
|||||||
with open('sushy/tests/unit/json_samples/system.json', 'r') as f:
|
with open('sushy/tests/unit/json_samples/system.json', 'r') as f:
|
||||||
self.conn.get.return_value.json.return_value = json.loads(f.read())
|
self.conn.get.return_value.json.return_value = json.loads(f.read())
|
||||||
|
|
||||||
self.sys_inst = system.System(self.conn, '437XR1138R2',
|
self.sys_inst = system.System(
|
||||||
|
self.conn, '/redfish/v1/Systems/437XR1138R2',
|
||||||
redfish_version='1.0.2')
|
redfish_version='1.0.2')
|
||||||
|
|
||||||
def test__parse_attributes(self):
|
def test__parse_attributes(self):
|
||||||
@ -117,7 +118,8 @@ class SystemTestCase(base.TestCase):
|
|||||||
|
|
||||||
def test__get_reset_system_path(self):
|
def test__get_reset_system_path(self):
|
||||||
value = self.sys_inst._get_reset_system_path()
|
value = self.sys_inst._get_reset_system_path()
|
||||||
expected = 'Systems/437XR1138R2/Actions/ComputerSystem.Reset'
|
expected = (
|
||||||
|
'/redfish/v1/Systems/437XR1138R2/Actions/ComputerSystem.Reset')
|
||||||
self.assertEqual(expected, value)
|
self.assertEqual(expected, value)
|
||||||
|
|
||||||
@mock.patch.object(system.System, '_get_reset_action_element',
|
@mock.patch.object(system.System, '_get_reset_action_element',
|
||||||
@ -133,7 +135,7 @@ class SystemTestCase(base.TestCase):
|
|||||||
def test_reset_system(self):
|
def test_reset_system(self):
|
||||||
self.sys_inst.reset_system(sushy.RESET_FORCE_OFF)
|
self.sys_inst.reset_system(sushy.RESET_FORCE_OFF)
|
||||||
self.sys_inst._conn.post.assert_called_once_with(
|
self.sys_inst._conn.post.assert_called_once_with(
|
||||||
'Systems/437XR1138R2/Actions/ComputerSystem.Reset',
|
'/redfish/v1/Systems/437XR1138R2/Actions/ComputerSystem.Reset',
|
||||||
data={'ResetType': 'ForceOff'})
|
data={'ResetType': 'ForceOff'})
|
||||||
|
|
||||||
def test_reset_system_invalid_value(self):
|
def test_reset_system_invalid_value(self):
|
||||||
@ -191,7 +193,7 @@ class SystemTestCase(base.TestCase):
|
|||||||
enabled=sushy.BOOT_SOURCE_ENABLED_CONTINUOUS,
|
enabled=sushy.BOOT_SOURCE_ENABLED_CONTINUOUS,
|
||||||
mode=sushy.BOOT_SOURCE_MODE_UEFI)
|
mode=sushy.BOOT_SOURCE_MODE_UEFI)
|
||||||
self.sys_inst._conn.patch.assert_called_once_with(
|
self.sys_inst._conn.patch.assert_called_once_with(
|
||||||
'Systems/437XR1138R2',
|
'/redfish/v1/Systems/437XR1138R2',
|
||||||
data={'Boot': {'BootSourceOverrideEnabled': 'Continuous',
|
data={'Boot': {'BootSourceOverrideEnabled': 'Continuous',
|
||||||
'BootSourceOverrideTarget': 'Pxe',
|
'BootSourceOverrideTarget': 'Pxe',
|
||||||
'BootSourceOverrideMode': 'UEFI'}})
|
'BootSourceOverrideMode': 'UEFI'}})
|
||||||
@ -201,7 +203,7 @@ class SystemTestCase(base.TestCase):
|
|||||||
sushy.BOOT_SOURCE_TARGET_HDD,
|
sushy.BOOT_SOURCE_TARGET_HDD,
|
||||||
enabled=sushy.BOOT_SOURCE_ENABLED_ONCE)
|
enabled=sushy.BOOT_SOURCE_ENABLED_ONCE)
|
||||||
self.sys_inst._conn.patch.assert_called_once_with(
|
self.sys_inst._conn.patch.assert_called_once_with(
|
||||||
'Systems/437XR1138R2',
|
'/redfish/v1/Systems/437XR1138R2',
|
||||||
data={'Boot': {'BootSourceOverrideEnabled': 'Once',
|
data={'Boot': {'BootSourceOverrideEnabled': 'Once',
|
||||||
'BootSourceOverrideTarget': 'Hdd'}})
|
'BootSourceOverrideTarget': 'Hdd'}})
|
||||||
|
|
||||||
@ -225,27 +227,28 @@ class SystemCollectionTestCase(base.TestCase):
|
|||||||
with open('sushy/tests/unit/json_samples/'
|
with open('sushy/tests/unit/json_samples/'
|
||||||
'system_collection.json', 'r') as f:
|
'system_collection.json', 'r') as f:
|
||||||
self.conn.get.return_value.json.return_value = json.loads(f.read())
|
self.conn.get.return_value.json.return_value = json.loads(f.read())
|
||||||
self.sys_col = system.SystemCollection(self.conn,
|
self.sys_col = system.SystemCollection(
|
||||||
redfish_version='1.0.2')
|
self.conn, '/redfish/v1/Systems', redfish_version='1.0.2')
|
||||||
|
|
||||||
def test__parse_attributes(self):
|
def test__parse_attributes(self):
|
||||||
self.sys_col._parse_attributes()
|
self.sys_col._parse_attributes()
|
||||||
self.assertEqual('1.0.2', self.sys_col.redfish_version)
|
self.assertEqual('1.0.2', self.sys_col.redfish_version)
|
||||||
self.assertEqual('Computer System Collection', self.sys_col.name)
|
self.assertEqual('Computer System Collection', self.sys_col.name)
|
||||||
self.assertEqual(('437XR1138R2',), self.sys_col.members_identities)
|
self.assertEqual(('/redfish/v1/Systems/437XR1138R2',),
|
||||||
|
self.sys_col.members_identities)
|
||||||
|
|
||||||
@mock.patch.object(system, 'System', autospec=True)
|
@mock.patch.object(system, 'System', autospec=True)
|
||||||
def test_get_member(self, mock_system):
|
def test_get_member(self, mock_system):
|
||||||
self.sys_col.get_member('437XR1138R2')
|
self.sys_col.get_member('/redfish/v1/Systems/437XR1138R2')
|
||||||
mock_system.assert_called_once_with(
|
mock_system.assert_called_once_with(
|
||||||
self.sys_col._conn, '437XR1138R2',
|
self.sys_col._conn, '/redfish/v1/Systems/437XR1138R2',
|
||||||
redfish_version=self.sys_col.redfish_version)
|
redfish_version=self.sys_col.redfish_version)
|
||||||
|
|
||||||
@mock.patch.object(system, 'System', autospec=True)
|
@mock.patch.object(system, 'System', autospec=True)
|
||||||
def test_get_members(self, mock_system):
|
def test_get_members(self, mock_system):
|
||||||
members = self.sys_col.get_members()
|
members = self.sys_col.get_members()
|
||||||
mock_system.assert_called_once_with(
|
mock_system.assert_called_once_with(
|
||||||
self.sys_col._conn, '437XR1138R2',
|
self.sys_col._conn, '/redfish/v1/Systems/437XR1138R2',
|
||||||
redfish_version=self.sys_col.redfish_version)
|
redfish_version=self.sys_col.redfish_version)
|
||||||
self.assertIsInstance(members, list)
|
self.assertIsInstance(members, list)
|
||||||
self.assertEqual(1, len(members))
|
self.assertEqual(1, len(members))
|
||||||
|
@ -127,7 +127,7 @@ class ResourceCollectionBaseTestCase(base.TestCase):
|
|||||||
|
|
||||||
def test_get_members(self):
|
def test_get_members(self):
|
||||||
# | GIVEN |
|
# | GIVEN |
|
||||||
# setting some valid member identities
|
# setting some valid member paths
|
||||||
member_ids = ('1', '2')
|
member_ids = ('1', '2')
|
||||||
self.test_resource_collection.members_identities = member_ids
|
self.test_resource_collection.members_identities = member_ids
|
||||||
# | WHEN |
|
# | WHEN |
|
||||||
|
@ -26,7 +26,7 @@ class ConnectorTestCase(base.TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(ConnectorTestCase, self).setUp()
|
super(ConnectorTestCase, self).setUp()
|
||||||
self.conn = connector.Connector(
|
self.conn = connector.Connector(
|
||||||
'http://foo.bar:1234/redfish/v1', username='user',
|
'http://foo.bar:1234', username='user',
|
||||||
password='pass', verify=True)
|
password='pass', verify=True)
|
||||||
self.data = {'fake': 'data'}
|
self.data = {'fake': 'data'}
|
||||||
self.headers = {'X-Fake': 'header'}
|
self.headers = {'X-Fake': 'header'}
|
||||||
@ -58,7 +58,7 @@ class ConnectorTestCase(base.TestCase):
|
|||||||
headers=headers)
|
headers=headers)
|
||||||
mock_session.assert_called_once_with()
|
mock_session.assert_called_once_with()
|
||||||
fake_session.request.assert_called_once_with(
|
fake_session.request.assert_called_once_with(
|
||||||
'GET', 'http://foo.bar:1234/redfish/v1/fake/path',
|
'GET', 'http://foo.bar:1234/fake/path',
|
||||||
data='{"fake": "data"}')
|
data='{"fake": "data"}')
|
||||||
self.assertEqual(expected_headers, fake_session.headers)
|
self.assertEqual(expected_headers, fake_session.headers)
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ import json
|
|||||||
import mock
|
import mock
|
||||||
|
|
||||||
from sushy import connector
|
from sushy import connector
|
||||||
|
from sushy import exceptions
|
||||||
from sushy import main
|
from sushy import main
|
||||||
from sushy.resources.system import system
|
from sushy.resources.system import system
|
||||||
from sushy.tests.unit import base
|
from sushy.tests.unit import base
|
||||||
@ -31,14 +32,14 @@ class MainTestCase(base.TestCase):
|
|||||||
self.conn = mock.Mock()
|
self.conn = mock.Mock()
|
||||||
mock_connector.return_code = self.conn
|
mock_connector.return_code = self.conn
|
||||||
self.root = main.Sushy(
|
self.root = main.Sushy(
|
||||||
'http://foo.bar:1234/redfish/v1', username='foo', password='bar',
|
'http://foo.bar:1234', username='foo', password='bar',
|
||||||
verify=True)
|
verify=True)
|
||||||
mock_connector.assert_called_once_with(
|
|
||||||
'http://foo.bar:1234/redfish/v1', 'foo', 'bar', True)
|
|
||||||
|
|
||||||
def test__parse_attributes(self):
|
|
||||||
with open('sushy/tests/unit/json_samples/root.json', 'r') as f:
|
with open('sushy/tests/unit/json_samples/root.json', 'r') as f:
|
||||||
self.root._json = json.loads(f.read())
|
self.root._json = json.loads(f.read())
|
||||||
|
mock_connector.assert_called_once_with(
|
||||||
|
'http://foo.bar:1234', 'foo', 'bar', True)
|
||||||
|
|
||||||
|
def test__parse_attributes(self):
|
||||||
self.root._parse_attributes()
|
self.root._parse_attributes()
|
||||||
self.assertEqual('RootService', self.root.identity)
|
self.assertEqual('RootService', self.root.identity)
|
||||||
self.assertEqual('Root Service', self.root.name)
|
self.assertEqual('Root Service', self.root.name)
|
||||||
@ -46,11 +47,23 @@ class MainTestCase(base.TestCase):
|
|||||||
self.assertEqual('92384634-2938-2342-8820-489239905423',
|
self.assertEqual('92384634-2938-2342-8820-489239905423',
|
||||||
self.root.uuid)
|
self.root.uuid)
|
||||||
|
|
||||||
|
def test__get_system_collection_path(self):
|
||||||
|
self.assertEqual(
|
||||||
|
'/redfish/v1/Systems', self.root._get_system_collection_path())
|
||||||
|
|
||||||
|
def test__get_system_collection_path_missing_systems_attr(self):
|
||||||
|
self.root._json.pop('Systems')
|
||||||
|
self.assertRaisesRegex(
|
||||||
|
exceptions.MissingAttributeError,
|
||||||
|
'The attribute Systems is missing',
|
||||||
|
self.root._get_system_collection_path)
|
||||||
|
|
||||||
@mock.patch.object(system, 'SystemCollection', autospec=True)
|
@mock.patch.object(system, 'SystemCollection', autospec=True)
|
||||||
def test_get_system_collection(self, mock_system_collection):
|
def test_get_system_collection(self, mock_system_collection):
|
||||||
self.root.get_system_collection()
|
self.root.get_system_collection()
|
||||||
mock_system_collection.assert_called_once_with(
|
mock_system_collection.assert_called_once_with(
|
||||||
self.root._conn, redfish_version=self.root.redfish_version)
|
self.root._conn, '/redfish/v1/Systems',
|
||||||
|
redfish_version=self.root.redfish_version)
|
||||||
|
|
||||||
@mock.patch.object(system, 'System', autospec=True)
|
@mock.patch.object(system, 'System', autospec=True)
|
||||||
def test_get_system(self, mock_system):
|
def test_get_system(self, mock_system):
|
||||||
|
@ -28,23 +28,10 @@ class UtilsTestCase(base.TestCase):
|
|||||||
self.assertEqual(expected, utils.revert_dictionary(source))
|
self.assertEqual(expected, utils.revert_dictionary(source))
|
||||||
|
|
||||||
@mock.patch.object(utils.LOG, 'warning', autospec=True)
|
@mock.patch.object(utils.LOG, 'warning', autospec=True)
|
||||||
def test_get_members_ids(self, log_mock):
|
def test_get_members_identities(self, log_mock):
|
||||||
members = [{"@odata.id": "/redfish/v1/Systems/FOO"},
|
members = [{"@odata.id": "/redfish/v1/Systems/FOO"},
|
||||||
{"other_key": "/redfish/v1/Systems/FUN"},
|
{"other_key": "/redfish/v1/Systems/FUN"},
|
||||||
{"@odata.id": "/redfish/v1/Systems/BAR/"}]
|
{"@odata.id": "/redfish/v1/Systems/BAR/"}]
|
||||||
expected = ('FOO', 'BAR')
|
expected = ('/redfish/v1/Systems/FOO', '/redfish/v1/Systems/BAR')
|
||||||
self.assertEqual(expected, utils.get_members_ids(members))
|
self.assertEqual(expected, utils.get_members_identities(members))
|
||||||
self.assertEqual(1, log_mock.call_count)
|
self.assertEqual(1, log_mock.call_count)
|
||||||
|
|
||||||
def test_strip_redfish_base(self):
|
|
||||||
expected = 'Systems/1'
|
|
||||||
self.assertEqual(expected, utils.strip_redfish_base('Systems/1'))
|
|
||||||
self.assertEqual(expected, utils.strip_redfish_base('/Systems/1'))
|
|
||||||
self.assertEqual(expected,
|
|
||||||
utils.strip_redfish_base('/redfish/v1/Systems/1'))
|
|
||||||
self.assertEqual(expected,
|
|
||||||
utils.strip_redfish_base('redfish/v1/Systems/1'))
|
|
||||||
self.assertEqual(expected,
|
|
||||||
utils.strip_redfish_base('/redfish/v2/Systems/1'))
|
|
||||||
self.assertEqual(expected,
|
|
||||||
utils.strip_redfish_base('redfish/v2/Systems/1'))
|
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -29,40 +28,20 @@ def revert_dictionary(dictionary):
|
|||||||
return {v: k for k, v in dictionary.items()}
|
return {v: k for k, v in dictionary.items()}
|
||||||
|
|
||||||
|
|
||||||
def get_members_ids(members):
|
def get_members_identities(members):
|
||||||
"""Extract and return a tuple of members identities
|
"""Extract and return a tuple of members identities
|
||||||
|
|
||||||
:param members: A list of members in JSON format
|
:param members: A list of members in JSON format
|
||||||
:returns: A tuple containing the members identities
|
:returns: A tuple containing the members paths
|
||||||
|
|
||||||
"""
|
"""
|
||||||
members_list = []
|
members_list = []
|
||||||
for member in members:
|
for member in members:
|
||||||
identity = member.get('@odata.id')
|
path = member.get('@odata.id')
|
||||||
if not identity:
|
if not path:
|
||||||
LOG.warning('Could not find the \'@odata.id\' attribute for '
|
LOG.warning('Could not find the \'@odata.id\' attribute for '
|
||||||
'member %s', member)
|
'member %s', member)
|
||||||
continue
|
continue
|
||||||
members_list.append(os.path.basename(identity.rstrip('/')))
|
members_list.append(path.rstrip('/'))
|
||||||
|
|
||||||
return tuple(members_list)
|
return tuple(members_list)
|
||||||
|
|
||||||
|
|
||||||
def strip_redfish_base(path):
|
|
||||||
"""Strip redfish base 'redfish/v1/' from path
|
|
||||||
|
|
||||||
:param path: A string of redfish resource path
|
|
||||||
:returns: path without redfish base 'redfish/v1/'
|
|
||||||
|
|
||||||
"""
|
|
||||||
sub_path = path.lstrip('/')
|
|
||||||
|
|
||||||
# To support further redfish version, didn't hardcode to 'redfish/v1'
|
|
||||||
redfish_base_path = 'redfish/v'
|
|
||||||
|
|
||||||
if sub_path.startswith(redfish_base_path):
|
|
||||||
# Find next occurrence of '/' after redfish base path and strip the
|
|
||||||
# base path before it
|
|
||||||
sub_path = sub_path[sub_path.find('/', len(redfish_base_path)) + 1:]
|
|
||||||
|
|
||||||
return sub_path
|
|
||||||
|
Loading…
Reference in New Issue
Block a user