Merge "Add a virtual media resource"

This commit is contained in:
Zuul 2018-08-28 09:25:18 +00:00 committed by Gerrit Code Review
commit 2086423798
11 changed files with 381 additions and 0 deletions

View File

@ -10,6 +10,7 @@ Features
* Systems power management (Both soft and hard; Including NMI injection)
* Changing systems boot device, frequency (Once or permanently) and mode
(UEFI or BIOS)
* Virtual media management
* SessionManagement
.. toctree::

View File

@ -209,6 +209,45 @@ Creating and using a sushy manager object
# Refresh the manager object (with all its sub-resources)
mgr_inst.refresh(force=True)
# Using Virtual Media
# Instantiate a VirtualMediaCollection object
virtmedia_col = mgr_inst.virtual_media
# Print the ID of the VirtualMedia available in the collection
print(virtmedia_col.members_identities)
# Get a list of VirtualMedia objects available in the collection
virtmedia_insts = virtmedia_col.get_members()
# Instantiate a VirtualMedia object
virtmedia_inst = virtmedia_col.get_member(
virtmedia_col.members_identities[0])
# Print out some of the VirtualMedia properties
print(virtmedia_inst.name,
virtmedia_inst.media_types)
# Insert virtual media (invalidates virtmedia_inst contents)
virtmedia_inst.insert_media('https://www.dmtf.org/freeImages/Sardine.img')
# Refresh the resource to load actual contents
virtmedia_inst.refresh()
# Print out some of the VirtualMedia properties
print(virtmedia_inst.image,
virtmedia_inst.image_path,
virtmedia_inst.inserted,
virtmedia_inst.write_protected)
# ... Boot the system off the virtual media...
# Eject virtual media (invalidates virtmedia_inst contents)
virtmedia_inst.eject_media()
-------------------------------------------------
Creating and using a sushy session service object
-------------------------------------------------

View File

@ -0,0 +1,4 @@
---
features:
- |
Adds support for the virtual media resource to the library.

View File

@ -76,3 +76,17 @@ COMMAND_SHELL_IPMI = 'command shell ipmi'
COMMAND_SHELL_OEM = 'command shell oem'
"""Command Shell connection using an OEM-specific protocol"""
# Virtual Media Type constants
VIRTUAL_MEDIA_CD = 'cd'
VIRTUAL_MEDIA_DVD = 'dvd'
VIRTUAL_MEDIA_FLOPPY = 'floppy'
VIRTUAL_MEDIA_USBSTICK = 'usb'
# Connected Via constants
CONNECTED_VIA_APPLET = 'applet'
CONNECTED_VIA_NOT_CONNECTED = 'not_connected'
CONNECTED_VIA_OEM = 'oem'
CONNECTED_VIA_URI = 'uri'

View File

@ -16,6 +16,9 @@ from sushy import exceptions
from sushy.resources import base
from sushy.resources import common
from sushy.resources.manager import mappings as mgr_maps
from sushy.resources.manager import virtual_media
from sushy import utils
LOG = logging.getLogger(__name__)
@ -74,6 +77,8 @@ class Manager(base.ResourceBase):
_actions = ActionsField('Actions', required=True)
_virtual_media = None
def __init__(self, connector, identity, redfish_version=None):
"""A class representing a Manager
@ -84,6 +89,10 @@ class Manager(base.ResourceBase):
"""
super(Manager, self).__init__(connector, identity, redfish_version)
def _do_refresh(self, force=False):
if self._virtual_media is not None:
self._virtual_media.invalidate(force)
def get_supported_graphical_console_types(self):
"""Get the supported values for Graphical Console connection types.
@ -178,6 +187,17 @@ class Manager(base.ResourceBase):
self._conn.post(target_uri, data={'ResetType': value})
LOG.info('The Manager %s is being reset', self.identity)
@property
def virtual_media(self):
if self._virtual_media is None:
self._virtual_media = virtual_media.VirtualMediaCollection(
self._conn,
utils.get_sub_resource_path_by(self, 'VirtualMedia'),
redfish_version=self.redfish_version)
self._virtual_media.refresh(force=False)
return self._virtual_media
class ManagerCollection(base.ResourceCollectionBase):

View File

@ -59,3 +59,17 @@ COMMAND_SHELL_VALUE_MAP = {
COMMAND_SHELL_VALUE_MAP_REV = (
utils.revert_dictionary(COMMAND_SHELL_VALUE_MAP))
MEDIA_TYPE_MAP = {
'CD': mgr_cons.VIRTUAL_MEDIA_CD,
'DVD': mgr_cons.VIRTUAL_MEDIA_DVD,
'Floppy': mgr_cons.VIRTUAL_MEDIA_FLOPPY,
'USBStick': mgr_cons.VIRTUAL_MEDIA_USBSTICK
}
CONNECTED_VIA_MAP = {
"Applet": mgr_cons.CONNECTED_VIA_APPLET,
"NotConnected": mgr_cons.CONNECTED_VIA_NOT_CONNECTED,
"Oem": mgr_cons.CONNECTED_VIA_OEM,
"URI": mgr_cons.CONNECTED_VIA_URI
}

View File

@ -0,0 +1,108 @@
# 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/VirtualMedia.v1_2_0.json
from sushy import exceptions
from sushy.resources import base
from sushy.resources import common
from sushy.resources.manager import mappings as mgr_maps
class ActionsField(base.CompositeField):
insert_media = common.ActionField("#VirtualMedia.InsertMedia")
eject_media = common.ActionField("#VirtualMedia.EjectMedia")
class VirtualMedia(base.ResourceBase):
identity = base.Field('Id', required=True)
"""Virtual Media resource identity string"""
name = base.Field('Name', required=True)
"""The name of resource"""
image = base.Field('Image')
"""A URI providing the location of the selected image"""
image_name = base.Field('ImageName')
"""The image name"""
inserted = base.Field('Inserted')
"""Indicates if virtual media is inserted in the virtual device"""
write_protected = base.Field('WriteProtected')
"""Indicates the media is write protected"""
media_types = base.MappedField('MediaTypes', mgr_maps.MEDIA_TYPE_MAP)
"""This is the media types supported as virtual media"""
connected_via = base.MappedField('ConnectedVia',
mgr_maps.CONNECTED_VIA_MAP)
"""Current virtual media connection methods
Applet: Connected to a client application
NotConnected: No current connection
Oem: Connected via an OEM-defined method
URI: Connected to a URI location
"""
_actions = ActionsField('Actions')
"""Insert/eject action fot virtual media"""
def _get_insert_media_element(self):
insert_media = self._actions.insert_media
if not insert_media:
raise exceptions.MissingActionError(
action='#VirtualMedia.InsertMedia', resource=self._path)
return insert_media
def _get_eject_media_element(self):
eject_media = self._actions.eject_media
if not eject_media:
raise exceptions.MissingActionError(
action='#VirtualMedia.EjectMedia', resource=self._path)
return eject_media
def insert_media(self, image, inserted=True, write_protected=False):
"""Attach remote media to virtual media
:param image: a URI providing the location of the selected image
:param inserted: specify if the image is to be treated as inserted upon
completion of the action.
:param write_protected: indicates the media is write protected
"""
target_uri = self._get_insert_media_element().target_uri
self._conn.post(target_uri, data={"Image": image, "Inserted": inserted,
"WriteProtected": write_protected})
self.invalidate()
def eject_media(self):
"""Detach remote media from virtual media
After ejecting media inserted will be False and image_name will be
empty.
"""
target_uri = self._get_eject_media_element().target_uri
self._conn.post(target_uri)
self.invalidate()
class VirtualMediaCollection(base.ResourceCollectionBase):
"""A collection of virtual media attached to a Manager"""
@property
def _resource_type(self):
return VirtualMedia

View File

@ -0,0 +1,24 @@
{
"@odata.type": "#VirtualMedia.v1_1_0.VirtualMedia",
"Id": "Floppy1",
"Name": "Virtual Removable Media",
"MediaTypes": "Floppy",
"Actions": {
"#VirtualMedia.EjectMedia": {
"target": "/redfish/v1/Managers/BMC/VirtualMedia/Floppy1/Actions/VirtualMedia.EjectMedia",
"title": "Mock Eject Media"
},
"#VirtualMedia.InsertMedia": {
"target": "/redfish/v1/Managers/BMC/VirtualMedia/Floppy1/Actions/VirtualMedia.InsertMedia",
"title": "Mock Insert Media"
}
},
"Image": "https://www.dmtf.org/freeImages/Sardine.img",
"ImageName": "Sardine2.1.43.35.6a",
"ConnectedVia": "URI",
"Inserted": true,
"WriteProtected": false,
"@odata.context": "/redfish/v1/$metadata#VirtualMedia.VirtualMedia",
"@odata.id": "/redfish/v1/Managers/BMC/VirtualMedia/Floppy1",
"@Redfish.Copyright": "Copyright 2014-2017 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright."
}

View File

@ -0,0 +1,15 @@
{
"@odata.type": "#VirtualMediaCollection.VirtualMediaCollection",
"Name": "Virtual Media Services",
"Description": "Redfish-BMC Virtual Media Service Settings",
"Members@odata.count": 1,
"Members": [
{
"@odata.id": "/redfish/v1/Managers/BMC/VirtualMedia/Floppy1"
}
],
"Oem": {},
"@odata.context": "/redfish/v1/$metadata#VirtualMediaCollection.VirtualMediaCollection",
"@odata.id": "/redfish/v1/Managers/BMC/VirtualMedia",
"@Redfish.Copyright": "Copyright 2014-2017 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright."
}

View File

@ -17,6 +17,7 @@ import mock
import sushy
from sushy import exceptions
from sushy.resources.manager import manager
from sushy.resources.manager import virtual_media
from sushy.tests.unit import base
@ -53,6 +54,7 @@ class ManagerTestCase(base.TestCase):
self.assertEqual(sushy.MANAGER_TYPE_BMC, self.manager.manager_type)
self.assertEqual('58893887-8974-2487-2389-841168418919',
self.manager.uuid)
self.assertIsNone(self.manager._virtual_media)
def test_get_supported_graphical_console_types(self):
# | GIVEN |
@ -206,6 +208,64 @@ class ManagerTestCase(base.TestCase):
self.assertRaises(exceptions.InvalidParameterValueError,
self.manager.reset_manager, 'invalid-value')
def test_virtual_media(self):
# | GIVEN |
with open('sushy/tests/unit/json_samples/'
'virtual_media_collection.json') as f:
virtual_media_collection_return_value = json.load(f)
with open('sushy/tests/unit/json_samples/'
'virtual_media.json') as f:
virtual_media_return_value = json.load(f)
self.conn.get.return_value.json.side_effect = [
virtual_media_collection_return_value, virtual_media_return_value]
# | WHEN |
actual_virtual_media = self.manager.virtual_media
# | THEN |
self.assertIsInstance(actual_virtual_media,
virtual_media.VirtualMediaCollection)
self.assertEqual(actual_virtual_media.name, 'Virtual Media Services')
member = actual_virtual_media.get_member('Floppy1')
self.assertEqual(member.image_name, "Sardine2.1.43.35.6a")
self.assertTrue(member.inserted)
self.assertFalse(member.write_protected)
def test_virtual_media_on_refresh(self):
# | GIVEN |
with open('sushy/tests/unit/json_samples/'
'virtual_media_collection.json') as f:
self.conn.get.return_value.json.return_value = json.load(f)
# | WHEN & THEN |
self.assertIsInstance(self.manager.virtual_media,
virtual_media.VirtualMediaCollection)
# On refreshing the manager instance...
with open('sushy/tests/unit/json_samples/manager.json', 'r') as f:
self.conn.get.return_value.json.return_value = json.loads(f.read())
self.manager.invalidate()
self.manager.refresh(force=False)
# | WHEN & THEN |
self.assertIsNotNone(self.manager._virtual_media)
self.assertTrue(self.manager._virtual_media._is_stale)
# | GIVEN |
with open('sushy/tests/unit/json_samples/'
'virtual_media_collection.json') as f:
self.conn.get.return_value.json.return_value = json.load(f)
# | WHEN & THEN |
self.assertIsInstance(self.manager.virtual_media,
virtual_media.VirtualMediaCollection)
self.assertFalse(self.manager._virtual_media._is_stale)
class ManagerCollectionTestCase(base.TestCase):

View File

@ -0,0 +1,82 @@
# All Rights Reserved.
#
# 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.manager import virtual_media
from sushy.tests.unit import base
class VirtualMediaTestCase(base.TestCase):
def setUp(self):
super(VirtualMediaTestCase, self).setUp()
self.conn = mock.Mock()
with open('sushy/tests/unit/json_samples/'
'virtual_media.json') as f:
self.conn.get.return_value.json.return_value = json.load(f)
self.sys_virtual_media = virtual_media.VirtualMedia(
self.conn, '/redfish/v1/Managers/BMC/VirtualMedia/Floppy1',
redfish_version='1.0.2')
def test__parse_atrtributes(self):
self.sys_virtual_media._parse_attributes()
self.assertEqual('Virtual Removable Media',
self.sys_virtual_media.name)
self.assertEqual('Floppy1', self.sys_virtual_media.identity)
self.assertEqual('https://www.dmtf.org/freeImages/Sardine.img',
self.sys_virtual_media.image)
self.assertEqual('Sardine2.1.43.35.6a',
self.sys_virtual_media.image_name)
self.assertEqual('uri', self.sys_virtual_media.connected_via)
self.assertEqual('floppy',
self.sys_virtual_media.media_types)
self.assertEqual(True, self.sys_virtual_media.inserted)
self.assertEqual(False, self.sys_virtual_media.write_protected)
def test_insert_media_none(self):
self.sys_virtual_media._actions.insert_media = None
self.assertRaisesRegex(
exceptions.MissingActionError, 'action #VirtualMedia.InsertMedia',
self.sys_virtual_media.insert_media,
"https://www.dmtf.org/freeImages/Sardine.img", True, False)
def test_insert_media(self):
self.assertFalse(self.sys_virtual_media._is_stale)
self.sys_virtual_media.insert_media(
"https://www.dmtf.org/freeImages/Sardine.img", True, False)
self.sys_virtual_media._conn.post.assert_called_once_with(
("/redfish/v1/Managers/BMC/VirtualMedia/Floppy1/Actions"
"/VirtualMedia.InsertMedia"),
data={"Image": "https://www.dmtf.org/freeImages/Sardine.img",
"Inserted": True, "WriteProtected": False}
)
self.assertTrue(self.sys_virtual_media._is_stale)
def test_eject_media_none(self):
self.sys_virtual_media._actions.eject_media = None
self.assertRaisesRegex(
exceptions.MissingActionError, 'action #VirtualMedia.EjectMedia',
self.sys_virtual_media.eject_media)
def test_eject_media(self):
self.assertFalse(self.sys_virtual_media._is_stale)
self.sys_virtual_media.eject_media()
self.sys_virtual_media._conn.post.assert_called_once_with(
("/redfish/v1/Managers/BMC/VirtualMedia/Floppy1/Actions"
"/VirtualMedia.EjectMedia"))
self.assertTrue(self.sys_virtual_media._is_stale)