Add a virtual media resource
Describe virtual media resources in Python classes close to Redfish schema. Story: 1526753 Task: 12509 Co-Authored-By: Ilya Etingof <etingof@gmail.com> Change-Id: Ifccd43036378a2808f6c89d4e15307b54e00ed6f
This commit is contained in:
parent
e5400176cd
commit
8338d8b4cd
|
@ -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::
|
||||
|
|
|
@ -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
|
||||
-------------------------------------------------
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
Adds support for the virtual media resource to the library.
|
|
@ -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'
|
||||
|
|
|
@ -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):
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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."
|
||||
}
|
|
@ -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."
|
||||
}
|
|
@ -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):
|
||||
|
||||
|
|
|
@ -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)
|
Loading…
Reference in New Issue