Add Simple Storage resource support

Simple Storage resource is a storage model used to represent the
properties of a storage controller and its directly attached
devices. As of this patch, the Simple Storage emulation is only
supported for the libvirt driver for storage devices that are
configured as a Volume via libvirt and attached to a VM/domain.

Story: #2005948
Task: #34313
Change-Id: If10bd04d517600c3a69bb43fe0ea50f2efd62422
This commit is contained in:
Varsha 2019-06-25 16:20:06 +05:30
parent 578f058f18
commit 69cfa3a4f0
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
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
~~~~~~~~~

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
@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():
parser = argparse.ArgumentParser('sushy-emulator')
parser.add_argument('--config',

View File

@ -192,3 +192,10 @@ class AbstractSystemsDriver(DriverBase):
: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
# under the License.
from collections import defaultdict
from collections import namedtuple
import logging
import os
@ -839,3 +840,96 @@ class LibvirtDriver(AbstractSystemsDriver):
'%(error)s' % {'uri': self._uri, 'error': e})
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
"""
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)
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)
self.assertEqual([{'id': 'fa:16:3e:22:18:31',
'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)
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)