Merge "Support credentials for virtual media"

This commit is contained in:
Zuul 2021-09-07 20:43:29 +00:00 committed by Gerrit Code Review
commit 97aa7a925c
6 changed files with 124 additions and 27 deletions

View File

@ -0,0 +1,5 @@
---
features:
- |
The emulator now supports providing ``UserName`` and ``Password`` for
virtual media.

View File

@ -411,9 +411,7 @@ def virtual_media_resource(identity, device):
media_types = app.vmedia.get_device_media_types(
identity, device)
(image_name, image_url, inserted,
write_protected) = app.vmedia.get_device_image_info(
identity, device)
device_info = app.vmedia.get_device_image_info(identity, device)
except error.FishyError as ex:
app.logger.warning(
@ -430,10 +428,12 @@ def virtual_media_resource(identity, device):
device=device,
name=device_name,
media_types=media_types,
image_url=image_url,
image_name=image_name,
inserted=inserted,
write_protected=write_protected
image_url=device_info.image_url,
image_name=device_info.image_name,
inserted=device_info.inserted,
write_protected=device_info.write_protected,
username=device_info.username,
password=device_info.password,
)
@ -444,7 +444,13 @@ def virtual_media_resource(identity, device):
def virtual_media_insert(identity, device):
image = flask.request.json.get('Image')
inserted = flask.request.json.get('Inserted', True)
write_protected = flask.request.json.get('WriteProtected', False)
write_protected = flask.request.json.get('WriteProtected', True)
username = flask.request.json.get('UserName', '')
password = flask.request.json.get('Password', '')
if (not username and password) or (username and not password):
message = "UserName and Password must be passed together"
return flask.render_template('error.json', message=message), 400
manager = app.managers.get_manager(identity)
systems = app.managers.get_managed_systems(manager)
@ -453,7 +459,8 @@ def virtual_media_insert(identity, device):
return '', 204
image_path = app.vmedia.insert_image(
identity, device, image, inserted, write_protected)
identity, device, image, inserted, write_protected,
username=username, password=password)
for system in systems:
try:

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import collections
import os
import re
import tempfile
@ -25,6 +26,12 @@ from sushy_tools.emulator.resources import base
from sushy_tools import error
DeviceInfo = collections.namedtuple(
'DeviceInfo',
['image_name', 'image_url', 'inserted', 'write_protected',
'username', 'password'])
class StaticDriver(base.DriverBase):
"""Redfish virtual media simulator."""
@ -116,16 +123,19 @@ class StaticDriver(base.DriverBase):
:param identity: parent resource ID
:param device: device name
:returns: a `tuple` of: image name, image path, `True` is media is
inserted, `True` if media is write-protected
:returns: a `DeviceInfo` with: image name, image path,
`True` is media is inserted, `True` if media is write-protected,
user name and password
:raises: `error.FishyError`
"""
device_info = self._get_device(identity, device)
return (device_info.get('ImageName', ''),
device_info.get('Image', ''),
device_info.get('Inserted', False),
device_info.get('WriteProtected', False))
return DeviceInfo(device_info.get('ImageName', ''),
device_info.get('Image', ''),
device_info.get('Inserted', False),
device_info.get('WriteProtected', False),
device_info.get('UserName', ''),
device_info.get('Password', ''))
def _write_from_response(self, image_url, rsp, tmp_file):
with open(tmp_file.name, 'wb') as fl:
@ -152,7 +162,8 @@ class StaticDriver(base.DriverBase):
return local_file
def insert_image(self, identity, device, image_url,
inserted=True, write_protected=True):
inserted=True, write_protected=True,
username=None, password=None):
"""Upload, remove or insert virtual media
:param identity: parent resource ID
@ -166,10 +177,12 @@ class StaticDriver(base.DriverBase):
device_info = self._get_device(identity, device)
verify_media_cert = self._config.get(
'SUSHY_EMULATOR_VMEDIA_VERIFY_SSL', True)
auth = (username, password) if (username and password) else None
try:
with requests.get(image_url,
stream=True,
auth=auth,
verify=verify_media_cert) as rsp:
if rsp.status_code >= 400:
self._logger.error(
@ -208,6 +221,8 @@ class StaticDriver(base.DriverBase):
device_info['Image'] = local_file
device_info['Inserted'] = inserted
device_info['WriteProtected'] = write_protected
device_info['UserName'] = username or ''
device_info['Password'] = password or ''
device_info['_local_file'] = local_file_path
self._devices.update({(identity, device): device_info})
@ -227,6 +242,8 @@ class StaticDriver(base.DriverBase):
device_info['ImageName'] = ''
device_info['Inserted'] = False
device_info['WriteProtected'] = False
device_info['UserName'] = ''
device_info['Password'] = ''
self._devices.update({(identity, device): device_info})

View File

@ -1,5 +1,5 @@
{
"@odata.type": "#VirtualMedia.v1_1_0.VirtualMedia",
"@odata.type": "#VirtualMedia.v1_3_0.VirtualMedia",
"Id": {{ device|string|tojson }},
"Name": {{ name|string|tojson }},
"MediaTypes": [
@ -21,7 +21,9 @@
},
"Oem": {}
},
"UserName": {{ username|string|tojson }},
"Password": "{{ '******' if password else '' }}",
"@odata.context": "/redfish/v1/$metadata#VirtualMedia.VirtualMedia",
"@odata.id": {{ "/redfish/v1/Managers/%s/VirtualMedia/%s"|format(identity, device)|string|tojson }},
"@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

@ -69,7 +69,7 @@ class StaticDriverTestCase(base.BaseTestCase):
def test_get_device_image_info(self):
dev_info = self.test_driver.get_device_image_info(
self.UUID, 'Cd')
expected = ('', '', False, False)
expected = ('', '', False, False, '', '')
self.assertEqual(expected, dev_info)
@mock.patch.object(vmedia.StaticDriver, '_get_device', autospec=True)
@ -99,7 +99,7 @@ class StaticDriverTestCase(base.BaseTestCase):
self.assertEqual('/alphabet/soup/fish.iso', local_file)
mock_requests.get.assert_called_once_with(
'http://fish.it/red.iso', stream=True, verify=True)
'http://fish.it/red.iso', stream=True, verify=True, auth=None)
mock_open.assert_called_once_with(mock.ANY, 'wb')
mock_rename.assert_called_once_with(
'alphabet.soup', '/alphabet/soup/fish.iso')
@ -107,6 +107,48 @@ class StaticDriverTestCase(base.BaseTestCase):
self.assertEqual('fish.iso', device_info['Image'])
self.assertTrue(device_info['Inserted'])
self.assertFalse(device_info['WriteProtected'])
self.assertEqual('', device_info['UserName'])
self.assertEqual('', device_info['Password'])
self.assertEqual(local_file, device_info['_local_file'])
@mock.patch.object(vmedia.StaticDriver, '_get_device', autospec=True)
@mock.patch.object(builtins, 'open', autospec=True)
@mock.patch.object(vmedia.os, 'rename', autospec=True)
@mock.patch.object(vmedia, 'tempfile', autospec=True)
@mock.patch.object(vmedia, 'requests', autospec=True)
def test_insert_image_auth(self, mock_requests, mock_tempfile, mock_rename,
mock_open, mock_get_device):
device_info = {}
mock_get_device.return_value = device_info
mock_tempfile.mkdtemp.return_value = '/alphabet/soup'
mock_tempfile.gettempdir.return_value = '/tmp'
mock_tmp_file = (mock_tempfile.NamedTemporaryFile
.return_value.__enter__.return_value)
mock_tmp_file.name = 'alphabet.soup'
mock_rsp = mock_requests.get.return_value.__enter__.return_value
mock_rsp.headers = {
'content-disposition': 'attachment; filename="fish.iso"'
}
mock_rsp.status_code = 200
local_file = self.test_driver.insert_image(
self.UUID, 'Cd', 'http://fish.it/red.iso', inserted=True,
write_protected=False, username='Admin', password='Secret')
self.assertEqual('/alphabet/soup/fish.iso', local_file)
mock_requests.get.assert_called_once_with(
'http://fish.it/red.iso', stream=True, verify=True,
auth=('Admin', 'Secret'))
mock_open.assert_called_once_with(mock.ANY, 'wb')
mock_rename.assert_called_once_with(
'alphabet.soup', '/alphabet/soup/fish.iso')
self.assertEqual('fish.iso', device_info['Image'])
self.assertTrue(device_info['Inserted'])
self.assertFalse(device_info['WriteProtected'])
self.assertEqual('Admin', device_info['UserName'])
self.assertEqual('Secret', device_info['Password'])
self.assertEqual(local_file, device_info['_local_file'])
@mock.patch.object(vmedia.StaticDriver, '_get_device', autospec=True)
@ -135,7 +177,7 @@ class StaticDriverTestCase(base.BaseTestCase):
self.assertEqual('/alphabet/soup/red.iso', local_file)
mock_requests.get.assert_called_once_with(
'http://fish.it/red.iso', stream=True, verify=True)
'http://fish.it/red.iso', stream=True, verify=True, auth=None)
mock_open.assert_called_once_with(mock.ANY, 'wb')
mock_rename.assert_called_once_with(
'alphabet.soup', '/alphabet/soup/red.iso')
@ -171,7 +213,7 @@ class StaticDriverTestCase(base.BaseTestCase):
self.assertEqual('/alphabet/soup/boot-abc', local_file)
mock_requests.get.assert_called_once_with(full_url, stream=True,
verify=True)
verify=True, auth=None)
mock_open.assert_called_once_with(mock.ANY, 'wb')
mock_rename.assert_called_once_with(
'alphabet.soup', '/alphabet/soup/boot-abc')
@ -215,7 +257,7 @@ class StaticDriverTestCase(base.BaseTestCase):
self.assertEqual('/alphabet/soup/fish.iso', local_file)
mock_requests.get.assert_called_once_with(
'https://fish.it/red.iso', stream=True, verify=False)
'https://fish.it/red.iso', stream=True, verify=False, auth=None)
mock_open.assert_called_once_with(mock.ANY, 'wb')
mock_rename.assert_called_once_with(
'alphabet.soup', '/alphabet/soup/fish.iso')
@ -251,7 +293,7 @@ class StaticDriverTestCase(base.BaseTestCase):
self.UUID, 'Cd', 'http://fish.it/red.iso',
inserted=True, write_protected=False)
mock_requests.get.assert_called_once_with(
'http://fish.it/red.iso', stream=True, verify=True)
'http://fish.it/red.iso', stream=True, auth=None, verify=True)
mock_open.assert_not_called()
self.assertEqual({}, device_info)

View File

@ -16,6 +16,7 @@ from unittest import mock
from oslotest import base
from sushy_tools.emulator import main
from sushy_tools.emulator.resources import vmedia
from sushy_tools import error
@ -491,19 +492,42 @@ class VirtualMediaTestCase(EmulatorTestCase):
vmedia_mock.get_device_name.return_value = 'CD'
vmedia_mock.get_device_media_types.return_value = [
'CD', 'DVD']
vmedia_mock.get_device_image_info.return_value = [
'image-of-a-fish', 'fishy.iso', True, True]
vmedia_mock.get_device_image_info.return_value = vmedia.DeviceInfo(
'image-of-a-fish', 'fishy.iso', True, True, '', '')
response = self.app.get(
'/redfish/v1/Managers/%s/VirtualMedia/CD' % self.uuid)
self.assertEqual(200, response.status_code)
self.assertEqual(200, response.status_code, response.json)
self.assertEqual('CD', response.json['Id'])
self.assertEqual(['CD', 'DVD'], response.json['MediaTypes'])
self.assertEqual('fishy.iso', response.json['Image'])
self.assertEqual('image-of-a-fish', response.json['ImageName'])
self.assertTrue(response.json['Inserted'])
self.assertTrue(response.json['WriteProtected'])
self.assertEqual('', response.json['UserName'])
self.assertEqual('', response.json['Password'])
def test_virtual_media_with_auth(self, managers_mock, vmedia_mock):
vmedia_mock = vmedia_mock.return_value
vmedia_mock.get_device_name.return_value = 'CD'
vmedia_mock.get_device_media_types.return_value = [
'CD', 'DVD']
vmedia_mock.get_device_image_info.return_value = vmedia.DeviceInfo(
'image-of-a-fish', 'fishy.iso', True, True, 'Admin', 'Secret')
response = self.app.get(
'/redfish/v1/Managers/%s/VirtualMedia/CD' % self.uuid)
self.assertEqual(200, response.status_code, response.json)
self.assertEqual('CD', response.json['Id'])
self.assertEqual(['CD', 'DVD'], response.json['MediaTypes'])
self.assertEqual('fishy.iso', response.json['Image'])
self.assertEqual('image-of-a-fish', response.json['ImageName'])
self.assertTrue(response.json['Inserted'])
self.assertTrue(response.json['WriteProtected'])
self.assertEqual('Admin', response.json['UserName'])
self.assertEqual('******', response.json['Password'])
def test_virtual_media_not_found(self, managers_mock, vmedia_mock):
vmedia_mock.return_value.get_device_name.side_effect = error.FishyError