Merge "Add Simple Storage resource support"

This commit is contained in:
Zuul 2019-09-04 14:06:30 +00:00 committed by Gerrit Code Review
commit 3d65187c0c
12 changed files with 505 additions and 0 deletions

View File

@ -85,6 +85,62 @@ You should be able to flip its power state via the Redfish call:
You can have as many domains as you need. The domains can be concurrently You can have as many domains as you need. The domains can be concurrently
managed over Redfish and some other tool like *Virtual BMC*. managed over Redfish and some other tool like *Virtual BMC*.
Simple Storage resource
~~~~~~~~~~~~~~~~~~~~~~~
For emulating the *Simple Storage* resource, some additional preparation is
required on the host side.
First, you need to create, build and start a libvirt storage pool using virsh:
.. code-block:: bash
virsh pool-define-as testPool dir - - - - "/testPool"
virsh pool-build testPool
virsh pool-start testPool
virsh pool-autostart testPool
Next, create a storage volume in the above created storage pool:
.. code-block:: bash
virsh vol-create-as testPool testVol 1G
Next, attach the created volume to the virtual machine/domain:
.. code-block:: bash
virsh attach-disk vbmc-node /testPool/testVol sda
Now, query the *Simple Storage* resource collection for the `vbmc-node` domain
in a closely similar format (with 'ide' and 'scsi', here, referring to the two
Redfish Simple Storage Controllers available for this domain):
.. code-block:: bash
curl http://localhost:8000/redfish/v1/vbmc-node/SimpleStorage
{
"@odata.type": "#SimpleStorageCollection.SimpleStorageCollection",
"Name": "Simple Storage Collection",
"Members@odata.count": 2,
"Members": [
{
"@odata.id": "/redfish/v1/Systems/vbmc-node/SimpleStorage/ide"
},
{
"@odata.id": "/redfish/v1/Systems/vbmc-node/SimpleStorage/scsi"
}
],
"Oem": {},
"@odata.context": "/redfish/v1/$metadata#SimpleStorageCollection.SimpleStorageCollection",
"@odata.id": "/redfish/v1/Systems/vbmc-node/SimpleStorage"
}
UEFI boot UEFI boot
~~~~~~~~~ ~~~~~~~~~

View File

@ -0,0 +1,8 @@
---
features:
- |
Adds emulation support for Simple Storage resource to libvirt
virtualization backend of the dynamic Redfish emulator. The emulation
functionality assumes that the storage devices attached to a VM are
configured as a libvirt Volume via a storage pool. Devices not configured
as a Volume will not be considered for emulation purposes.

View File

@ -626,6 +626,37 @@ def system_reset_bios(identity):
return '', 204 return '', 204
@app.route('/redfish/v1/Systems/<identity>/SimpleStorage',
methods=['GET'])
@ensure_instance_access
@returns_json
def simple_storage_collection(identity):
with Resources() as resources:
simple_storage_controllers = (
resources.systems.get_simple_storage_collection(identity))
return flask.render_template(
'simple_storage_collection.json', identity=identity,
simple_storage_controllers=simple_storage_controllers)
@app.route('/redfish/v1/Systems/<identity>/SimpleStorage/<simple_storage_id>',
methods=['GET'])
@ensure_instance_access
@returns_json
def simple_storage(identity, simple_storage_id):
with Resources() as resources:
simple_storage_controllers = (
resources.systems.get_simple_storage_collection(identity))
try:
storage_controller = simple_storage_controllers[simple_storage_id]
except KeyError:
app.logger.debug('"%s" Simple Storage resource was not found')
return 'Not found', 404
return flask.render_template('simple_storage.json', identity=identity,
simple_storage=storage_controller)
def parse_args(): def parse_args():
parser = argparse.ArgumentParser('sushy-emulator') parser = argparse.ArgumentParser('sushy-emulator')
parser.add_argument('--config', parser.add_argument('--config',

View File

@ -192,3 +192,10 @@ class AbstractSystemsDriver(DriverBase):
:raises: `error.FishyError` if boot device can't be set :raises: `error.FishyError` if boot device can't be set
""" """
@abc.abstractmethod
def get_simple_storage_collection(self, identity):
"""Get a dict of Simple Storage Controllers and their devices
:returns: dict of Simple Storage Controllers and their atributes
"""

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from collections import defaultdict
from collections import namedtuple from collections import namedtuple
import logging import logging
import os import os
@ -839,3 +840,96 @@ class LibvirtDriver(AbstractSystemsDriver):
'%(error)s' % {'uri': self._uri, 'error': e}) '%(error)s' % {'uri': self._uri, 'error': e})
raise error.FishyError(msg) raise error.FishyError(msg)
def _find_device_by_path(self, vol_path):
"""Get device attributes using path
:param vol_path: path for the libvirt volume
:returns: a dict (or None) of the corresponding device attributes
"""
with libvirt_open(self._uri, readonly=True) as conn:
try:
vol = conn.storageVolLookupByPath(vol_path)
except libvirt.libvirtError as e:
msg = ('Could not find storage volume by path '
'"%(path)s" at libvirt URI "%(uri)s": '
'%(err)s' %
{'path': vol_path, 'uri': self._uri,
'err': e})
logger.debug(msg)
return
disk_device = {
'Name': vol.name(),
'CapacityBytes': vol.info()[1]
}
return disk_device
def _find_device_from_pool(self, pool_name, vol_name):
"""Get device attributes from pool
:param pool_name: libvirt pool name
:param vol_name: libvirt volume name
:returns: a dict (or None) of the corresponding device attributes
"""
with libvirt_open(self._uri, readonly=True) as conn:
try:
pool = conn.storagePoolLookupByName(pool_name)
except libvirt.libvirtError as e:
msg = ('Error finding Storage Pool by name "%(name)s" at'
'libvirt URI "%(uri)s": %(err)s' %
{'name': pool_name, 'uri': self._uri, 'err': e})
logger.debug(msg)
return
try:
vol = pool.storageVolLookupByName(vol_name)
except libvirt.libvirtError as e:
msg = ('Error finding Storage Volume by name "%(name)s" '
'in Pool '"%(pName)s"' at libvirt URI "%(uri)s"'
': %(err)s' %
{'name': vol_name, 'pName': pool_name,
'uri': self._uri, 'err': e})
logger.debug(msg)
return
disk_device = {
'Name': vol.name(),
'CapacityBytes': vol.info()[1]
}
return disk_device
def get_simple_storage_collection(self, identity):
"""Get a dict of simple storage controllers and their devices
Only those storage devices that are configured as a libvirt volume
via a pool and attached to the domain will reflect as a device.
Others are skipped.
:param identity: libvirt domain or ID
:returns: dict of simple storage controller dict with their attributes
"""
domain = self._get_domain(identity, readonly=True)
tree = ET.fromstring(domain.XMLDesc())
simple_storage = defaultdict(lambda: defaultdict(DeviceList=list()))
for disk_element in tree.findall(".//disk/target[@bus]/.."):
source_element = disk_element.find('source')
if source_element is not None:
disk_type = disk_element.attrib['type']
ctl_type = disk_element.find('target').attrib['bus']
disk_device = None
if disk_type in ('file', 'block'):
if disk_type == 'file':
vol_path = source_element.attrib['file']
else:
vol_path = source_element.attrib['dev']
disk_device = self._find_device_by_path(vol_path)
elif disk_type == 'volume':
pool_name = source_element.attrib['pool']
vol_name = source_element.attrib['volume']
disk_device = self._find_device_from_pool(pool_name,
vol_name)
if disk_device is not None:
simple_storage[ctl_type]['Id'] = ctl_type
simple_storage[ctl_type]['Name'] = ctl_type
simple_storage[ctl_type]['DeviceList'].append(disk_device)
return simple_storage

View File

@ -371,3 +371,6 @@ class OpenStackDriver(AbstractSystemsDriver):
:raises: `error.FishyError` if boot device can't be set :raises: `error.FishyError` if boot device can't be set
""" """
raise error.NotSupportedError('Not implemented') raise error.NotSupportedError('Not implemented')
def get_simple_storage_collection(self, identity):
raise error.NotSupportedError('Not implemented')

View File

@ -0,0 +1,21 @@
{
"@odata.type": "#SimpleStorage.v1_2_0.SimpleStorage",
"Id": {{ simple_storage['Id']|string|tojson }},
"Name": {{ "%s Controller"|format(simple_storage['Name'])|tojson }},
"Devices": [
{% for device in simple_storage['DeviceList'] %}
{
"@odata.type": "#SimpleStorage.v1_1_0.Device",
"Name": {{ device['Name']|string|tojson }},
"CapacityBytes": {{ device['CapacityBytes'] }},
"Status": {
"@odata.type": "#Resource.Status",
"State": "Enabled",
"Health": "OK"
}
}{% if not loop.last %},{% endif %}
{% endfor %}
],
"@odata.context": "/redfish/v1/$metadata#SimpleStorage.SimpleStorage",
"@odata.id": {{ "/redfish/v1/Systems/%s/SimpleStorage/%s"|format(identity, simple_storage['Id'])|tojson }}
}

View File

@ -0,0 +1,16 @@
{
"@odata.type": "#SimpleStorageCollection.SimpleStorageCollection",
"Name": "Simple Storage Collection",
"Members@odata.count": {{ simple_storage_controllers|length }},
"Members": [
{% for simple_storage in simple_storage_controllers %}
{
"@odata.id": {{ "/redfish/v1/Systems/%s/SimpleStorage/%s"|format(identity, simple_storage_controllers[simple_storage]['Id'])|tojson }}
}{% if not loop.last %},{% endif %}
{% endfor %}
],
"Oem": {},
"@odata.context": "/redfish/v1/$metadata#SimpleStorageCollection.SimpleStorageCollection",
"@odata.id": {{ "/redfish/v1/Systems/%s/SimpleStorage"|format(identity)|tojson }}
}

View File

@ -0,0 +1,42 @@
<domain type='qemu'>
<name>QEmu-fedora-i686</name>
<uuid>c7a5fdbd-cdaf-9455-926a-d65c16db1809</uuid>
<memory>219200</memory>
<currentMemory>219200</currentMemory>
<vcpu>2</vcpu>
<os>
<type arch='x86_64' machine='pc'>hvm</type>
<boot dev='cdrom'/>
<loader type='rom'/>
</os>
<devices>
<emulator>/usr/bin/qemu-system-x86_64</emulator>
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2'/>
<source file='/var/lib/libvirt/images/testVM1.img'/>
<target dev='vda' bus='virtio'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/>
</disk>
<disk type='file' device='disk'>
<driver name='qemu' type='raw'/>
<source file='/home/varsha/Documents/testPool/testVol1.img'/>
<target dev='hdb' bus='ide'/>
<address type='drive' controller='0' bus='0' target='0' unit='1'/>
</disk>
<disk type='block' device='disk'>
<driver name='qemu' type='raw' cache='none' io='native'/>
<source dev='/dev/sdb1'/>
<target dev='vdc' bus='virtio'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x0a' function='0x0'/>
</disk>
<disk type='volume' device='disk'>
<driver name='qemu' type='raw'/>
<source pool='blk-pool0' volume='blk-pool0-vol0'/>
<target dev='hdk' bus='ide'/>
</disk>
<interface type='network'>
<source network='default'/>
</interface>
<graphics type='vnc' port='-1'/>
</devices>
</domain>

View File

@ -567,3 +567,81 @@ class LibvirtDriverTestCase(base.BaseTestCase):
nics = self.test_driver.get_nics(self.uuid) nics = self.test_driver.get_nics(self.uuid)
self.assertEqual([], nics) self.assertEqual([], nics)
@mock.patch('libvirt.openReadOnly', autospec=True)
def test_get_simple_storage_collection(self, libvirt_mock):
with open('sushy_tools/tests/unit/emulator/'
'domain_simple_storage.xml', 'r') as f:
data = f.read()
conn_mock = libvirt_mock.return_value
dom_mock = conn_mock.lookupByUUID.return_value
dom_mock.XMLDesc.return_value = data
vol_mock = conn_mock.storageVolLookupByPath.return_value
vol_mock.name.side_effect = ['testVM1.img', 'testVol1.img', 'sdb1']
vol_mock.info.side_effect = [['testVM1.img', 100000],
['testVol1.img', 200000],
['sdb1', 150000]]
pool_mock = conn_mock.storagePoolLookupByName.return_value
pvol_mock = pool_mock.storageVolLookupByName.return_value
pvol_mock.name.return_value = 'blk-pool0-vol0'
pvol_mock.info.return_value = ['volType', 300000]
simple_storage_response = (self.test_driver
.get_simple_storage_collection(self.uuid))
simple_storage_expected = {
'virtio': {
'Id': 'virtio',
'Name': 'virtio',
'DeviceList': [
{
'Name': 'testVM1.img',
'CapacityBytes': 100000
},
{
'Name': 'sdb1',
'CapacityBytes': 150000
}
]
},
'ide': {
'Id': 'ide',
'Name': 'ide',
'DeviceList': [
{
'Name': 'testVol1.img',
'CapacityBytes': 200000
},
{
'Name': 'blk-pool0-vol0',
'CapacityBytes': 300000
}
]
}
}
self.assertEqual(simple_storage_response, simple_storage_expected)
@mock.patch('libvirt.openReadOnly', autospec=True)
def test_get_simple_storage_collection_empty(self, libvirt_mock):
with open('sushy_tools/tests/unit/emulator/domain.xml') as f:
domain_xml = f.read()
conn_mock = libvirt_mock.return_value
dom_mock = conn_mock.lookupByUUID.return_value
dom_mock.XMLDesc.return_value = domain_xml
vol_mock = conn_mock.storageVolLookupByPath.return_value
vol_mock.name.side_effect = ['testVM1.img', 'testVol1.img', 'sdb1']
vol_mock.info.side_effect = [['testType1', 100000],
['testType2', 200000],
['testType1', 150000]]
pool_mock = conn_mock.storagePoolLookupByName.return_value
pvol_mock = pool_mock.storageVolLookupByName.return_value
pvol_mock.name.return_value = 'blk-pool0-vol0'
pvol_mock.info.return_value = ['volType', 300000]
simple_storage_response = (self.test_driver
.get_simple_storage_collection(self.uuid))
self.assertEqual({}, simple_storage_response)

View File

@ -256,3 +256,8 @@ class NovaDriverTestCase(base.BaseTestCase):
nics = self.test_driver.get_nics(self.uuid) nics = self.test_driver.get_nics(self.uuid)
self.assertEqual([{'id': 'fa:16:3e:22:18:31', self.assertEqual([{'id': 'fa:16:3e:22:18:31',
'mac': 'fa:16:3e:22:18:31'}], nics) 'mac': 'fa:16:3e:22:18:31'}], nics)
def test_get_simple_storage_collection(self):
self.assertRaises(
error.FishyError,
self.test_driver.get_simple_storage_collection, self.uuid)

View File

@ -574,3 +574,147 @@ class EmulatorTestCase(base.BaseTestCase):
self.assertEqual(204, response.status_code) self.assertEqual(204, response.status_code)
vmedia_mock.eject_image.called_once_with('CD') vmedia_mock.eject_image.called_once_with('CD')
def test_simple_storage_collection(self, resources_mock):
resources_mock = resources_mock.return_value.__enter__.return_value
systems_mock = resources_mock.systems
systems_mock.get_simple_storage_collection.return_value = {
'virtio': {
'Id': 'virtio',
'Name': 'virtio',
'DeviceList': [
{
'Name': 'testVM1.img',
'CapacityBytes': 100000
},
{
'Name': 'sdb1',
'CapacityBytes': 150000
}
]
},
'ide': {
'Id': 'ide',
'Name': 'ide',
'DeviceList': [
{
'Name': 'testVol1.img',
'CapacityBytes': 200000
},
{
'Name': 'blk-pool0-vol0',
'CapacityBytes': 300000
}
]
}
}
response = self.app.get('redfish/v1/Systems/' + self.uuid +
'/SimpleStorage')
self.assertEqual(200, response.status_code)
self.assertEqual('Simple Storage Collection',
response.json['Name'])
self.assertEqual(2, response.json['Members@odata.count'])
self.assertEqual({'/redfish/v1/Systems/' + self.uuid +
'/SimpleStorage/virtio',
'/redfish/v1/Systems/' + self.uuid +
'/SimpleStorage/ide'},
{m['@odata.id'] for m in response.json['Members']})
def test_simple_storage_collection_empty(self, resources_mock):
resources_mock = resources_mock.return_value.__enter__.return_value
systems_mock = resources_mock.systems
systems_mock.get_simple_storage_collection.return_value = []
response = self.app.get('redfish/v1/Systems/' + self.uuid +
'/SimpleStorage')
self.assertEqual(200, response.status_code)
self.assertEqual('Simple Storage Collection',
response.json['Name'])
self.assertEqual(0, response.json['Members@odata.count'])
self.assertEqual([], response.json['Members'])
def test_simple_storage(self, resources_mock):
resources_mock = resources_mock.return_value.__enter__.return_value
systems_mock = resources_mock.systems
systems_mock.get_simple_storage_collection.return_value = {
'virtio': {
'Id': 'virtio',
'Name': 'virtio',
'DeviceList': [
{
'Name': 'testVM1.img',
'CapacityBytes': 100000
},
{
'Name': 'sdb1',
'CapacityBytes': 150000
}
]
},
'ide': {
'Id': 'ide',
'Name': 'ide',
'DeviceList': [
{
'Name': 'testVol1.img',
'CapacityBytes': 200000
},
{
'Name': 'blk-pool0-vol0',
'CapacityBytes': 300000
}
]
}
}
response = self.app.get('/redfish/v1/Systems/' + self.uuid +
'/SimpleStorage/virtio')
self.assertEqual(200, response.status_code)
self.assertEqual('virtio', response.json['Id'])
self.assertEqual('virtio Controller', response.json['Name'])
self.assertEqual('testVM1.img', response.json['Devices'][0]['Name'])
self.assertEqual(100000, response.json['Devices'][0]['CapacityBytes'])
self.assertEqual('sdb1', response.json['Devices'][1]['Name'])
self.assertEqual(150000, response.json['Devices'][1]['CapacityBytes'])
self.assertEqual('/redfish/v1/Systems/' + self.uuid +
'/SimpleStorage/virtio',
response.json['@odata.id'])
def test_simple_storage_not_found(self, resources_mock):
resources_mock = resources_mock.return_value.__enter__.return_value
systems_mock = resources_mock.systems
systems_mock.get_simple_storage_collection.return_value = {
'virtio': {
'Id': 'virtio',
'Name': 'virtio',
'DeviceList': [
{
'Name': 'testVM1.img',
'CapacityBytes': 100000
},
{
'Name': 'sdb1',
'CapacityBytes': 150000
}
]
},
'ide': {
'Id': 'ide',
'Name': 'ide',
'DeviceList': [
{
'Name': 'testVol1.img',
'CapacityBytes': 200000
},
{
'Name': 'blk-pool0-vol0',
'CapacityBytes': 300000
}
]
}
}
response = self.app.get('/redfish/v1/Systems/' + self.uuid +
'/SimpleStorage/scsi')
self.assertEqual(404, response.status_code)