Support EthernetInterface resource in the emulator
Get MAC addresses and use them as network interface id-s. Support both nova and libvirt driver. get_nics returns list of dictionaries so that it can be extended to return more attributes, e.g., IP addresses. In the returned dictionary I have duplicated 'id' and 'mac' in case different id strategy is necessary in future (maybe this is premature). Story: 2003018 Task: 23038 Change-Id: I3787824ffdea3ade5419fecbe6e643cf5f432e9b Co-Authored-By: Aija Jaunteva <code@clusums.eu>
This commit is contained in:
1 parent
89707034f2
commit
3f15ec981f
12 files changed
+283
No files matched your search
@@ -23,6 +23,7 @@ linecache2==1.0.0
|
||||
MarkupSafe==1.0
|
||||
mock==2.0.0
|
||||
mox3==0.20.0
|
||||
munch==2.1.0
|
||||
openstackdocstheme==1.18.1
|
||||
openstacksdk===0.11.2
|
||||
oslotest==3.2.0
|
||||
|
||||
@@ -144,3 +144,10 @@ class AbstractDriver(object):
|
||||
|
||||
:raises: `FishyError` if BIOS attributes cannot be processed
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_nics(self, identity):
|
||||
"""Get list of NICs and their attributes
|
||||
|
||||
:returns: list of dictionaries of NICs and their attributes
|
||||
"""
|
||||
@@ -472,3 +472,19 @@ class LibvirtDriver(AbstractDriver):
|
||||
"""
|
||||
self._process_bios(identity, self.DEFAULT_BIOS_ATTRIBUTES,
|
||||
update_existing_attributes=True)
|
||||
|
||||
def get_nics(self, identity):
|
||||
"""Get list of network interfaces and their MAC addresses
|
||||
|
||||
Use MAC address as network interface's id
|
||||
|
||||
:param identity: libvirt domain name or ID
|
||||
|
||||
:returns: list of network interfaces dict with their attributes
|
||||
"""
|
||||
with libvirt_open(self._uri, readonly=True) as conn:
|
||||
domain = conn.lookupByName(identity)
|
||||
tree = ET.fromstring(domain.XMLDesc())
|
||||
return [{'id': iface.get('address'), 'mac': iface.get('address')}
|
||||
for iface in tree.findall(
|
||||
".//devices/interface[@type='network']/mac")]
|
||||
@@ -284,3 +284,26 @@ class OpenStackDriver(AbstractDriver):
|
||||
def reset_bios(self, identity):
|
||||
"""Not supported as Openstack SDK does not expose API for BIOS"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_nics(self, identity):
|
||||
"""Get server's network interfaces
|
||||
|
||||
Use MAC address as network interface's id
|
||||
|
||||
:param identity: OpenStack instance name or ID
|
||||
|
||||
:returns: list of dictionaries with NIC attributes (id and mac)
|
||||
"""
|
||||
instance = self._get_instance(identity)
|
||||
macs = set()
|
||||
if not instance.addresses:
|
||||
return macs
|
||||
|
||||
for addresses in instance.addresses.values():
|
||||
for adr in addresses:
|
||||
try:
|
||||
macs.add(adr['OS-EXT-IPS-MAC:mac_addr'])
|
||||
except KeyError:
|
||||
logger.warning('Could not find MAC address in %s', adr)
|
||||
return [{'id': mac, 'mac': mac}
|
||||
for mac in macs]
|
||||
@@ -164,6 +164,31 @@ def system_resource(identity):
|
||||
return '', 204
|
||||
|
||||
|
||||
@app.route('/redfish/v1/Systems/<identity>/EthernetInterfaces',
|
||||
methods=['GET'])
|
||||
@init_virt_driver
|
||||
@returns_json
|
||||
def ethernet_interfaces_collection(identity):
|
||||
nics = driver.get_nics(identity)
|
||||
return flask.render_template(
|
||||
'ethernet_interfaces_collection.json', identity=identity,
|
||||
nics=nics)
|
||||
|
||||
|
||||
@app.route('/redfish/v1/Systems/<identity>/EthernetInterfaces/<nic_id>',
|
||||
methods=['GET'])
|
||||
@init_virt_driver
|
||||
@returns_json
|
||||
def ethernet_interface(identity, nic_id):
|
||||
nics = driver.get_nics(identity)
|
||||
for nic in nics:
|
||||
if nic['id'] == nic_id:
|
||||
return flask.render_template(
|
||||
'ethernet_interface.json', identity=identity, nic=nic)
|
||||
|
||||
return 'Not found', 404
|
||||
|
||||
|
||||
@app.route('/redfish/v1/Systems/<identity>/Actions/ComputerSystem.Reset',
|
||||
methods=['POST'])
|
||||
@init_virt_driver
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"@odata.type": "#EthernetInterface.v1_0_2.EthernetInterface",
|
||||
"Id": "{{ nic['id'] }}",
|
||||
"Name": "VNIC {{ nic['id'] }}",
|
||||
"PermanentMACAddress": "{{ nic['mac'] }}",
|
||||
"MACAddress": "{{ nic['mac'] }}",
|
||||
"@odata.context": "/redfish/v1/$metadata#EthernetInterface.EthernetInterface",
|
||||
"@odata.id": "/redfish/v1/Systems/{{ identity }}/EthernetInterfaces/{{ nic['id'] }}"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"@odata.type": "#EthernetInterfaceCollection.EthernetInterfaceCollection",
|
||||
"Name": "Ethernet Interface Collection",
|
||||
"Description": "Virtual NICs",
|
||||
"Members@odata.count": {{ nics|length }},
|
||||
"Members": [
|
||||
{% for nic in nics %}
|
||||
{
|
||||
"@odata.id": "/redfish/v1/Systems/{{ identity }}/EthernetInterfaces/{{ nic.id }}"
|
||||
}{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
],
|
||||
"Oem": {},
|
||||
"@odata.context": "/redfish/v1/$metadata#EthernetInterfaceCollection.EthernetInterfaceCollection",
|
||||
"@odata.id": "/redfish/v1/Systems/{{ identity }}/EthernetInterfaces"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<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='i686' machine='pc'>hvm</type>
|
||||
<boot dev='cdrom'/>
|
||||
<loader type='rom'/>
|
||||
</os>
|
||||
<devices>
|
||||
<emulator>/usr/bin/qemu-system-x86_64</emulator>
|
||||
<disk type='file' device='cdrom'>
|
||||
<source file='/home/user/boot.iso'/>
|
||||
<target dev='hdc'/>
|
||||
<readonly/>
|
||||
</disk>
|
||||
<disk type='file' device='disk'>
|
||||
<source file='/home/user/fedora.img'/>
|
||||
<target dev='hda'/>
|
||||
</disk>
|
||||
<interface type='network'>
|
||||
<mac address='52:54:00:4e:5d:37'/>
|
||||
<source network='default'/>
|
||||
<address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
|
||||
</interface>
|
||||
<interface type='network'>
|
||||
<source network='default' portgroup='engineering'/>
|
||||
<target dev='vnet7'/>
|
||||
<mac address='00:11:22:33:44:55'/>
|
||||
</interface>
|
||||
<graphics type='vnc' port='-1'/>
|
||||
</devices>
|
||||
</domain>
|
||||
@@ -318,3 +318,31 @@ class EmulatorTestCase(base.BaseTestCase):
|
||||
'xxx-yyy-zzz',
|
||||
{"BootMode": "Uefi",
|
||||
"ProcTurboMode": "Enabled"})
|
||||
|
||||
@mock.patch('libvirt.openReadOnly', autospec=True)
|
||||
def test_get_nics(self, libvirt_mock):
|
||||
with open('sushy_tools/tests/unit/emulator/domain_nics.xml') as f:
|
||||
domain_xml = f.read()
|
||||
|
||||
conn_mock = libvirt_mock.return_value
|
||||
domain_mock = conn_mock.lookupByName.return_value
|
||||
domain_mock.XMLDesc.return_value = domain_xml
|
||||
|
||||
nics = self.test_driver.get_nics('xxx-yyy-zzz')
|
||||
self.assertEqual([{'id': '00:11:22:33:44:55',
|
||||
'mac': '00:11:22:33:44:55'},
|
||||
{'id': '52:54:00:4e:5d:37',
|
||||
'mac': '52:54:00:4e:5d:37'}],
|
||||
sorted(nics, key=lambda k: k['id']))
|
||||
|
||||
@mock.patch('libvirt.openReadOnly', autospec=True)
|
||||
def test_get_nics_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
|
||||
domain_mock = conn_mock.lookupByName.return_value
|
||||
domain_mock.XMLDesc.return_value = domain_xml
|
||||
|
||||
nics = self.test_driver.get_nics('xxx-yyy-zzz')
|
||||
self.assertEqual([], nics)
|
||||
@@ -68,3 +68,61 @@ class EmulatorTestCase(base.BaseTestCase):
|
||||
|
||||
self.assertEqual('204 NO CONTENT', response.status)
|
||||
driver_mock.reset_bios.assert_called_once_with('xxxx-yyyy-zzzz')
|
||||
|
||||
def test_ethernet_interfaces_collection(self, driver_mock):
|
||||
driver_mock.get_nics.return_value = [{'id': 'nic1',
|
||||
'mac': '52:54:00:4e:5d:37'},
|
||||
{'id': 'nic2',
|
||||
'mac': '00:11:22:33:44:55'}]
|
||||
response = self.app.get('redfish/v1/Systems/xxx-yyy-zzz/'
|
||||
'EthernetInterfaces')
|
||||
|
||||
self.assertEqual('200 OK', response.status)
|
||||
self.assertEqual('Ethernet Interface Collection',
|
||||
response.json['Name'])
|
||||
self.assertEqual(2, response.json['Members@odata.count'])
|
||||
self.assertEqual(['/redfish/v1/Systems/xxx-yyy-zzz/'
|
||||
'EthernetInterfaces/nic1',
|
||||
'/redfish/v1/Systems/xxx-yyy-zzz/'
|
||||
'EthernetInterfaces/nic2'],
|
||||
[m['@odata.id'] for m in response.json['Members']])
|
||||
|
||||
def test_ethernet_interfaces_collection_empty(self, driver_mock):
|
||||
driver_mock.get_nics.return_value = []
|
||||
response = self.app.get('redfish/v1/Systems/xxx-yyy-zzz/'
|
||||
'EthernetInterfaces')
|
||||
|
||||
self.assertEqual('200 OK', response.status)
|
||||
self.assertEqual('Ethernet Interface Collection',
|
||||
response.json['Name'])
|
||||
self.assertEqual(0, response.json['Members@odata.count'])
|
||||
self.assertEqual([], response.json['Members'])
|
||||
|
||||
def test_ethernet_interface(self, driver_mock):
|
||||
driver_mock.get_nics.return_value = [{'id': 'nic1',
|
||||
'mac': '52:54:00:4e:5d:37'},
|
||||
{'id': 'nic2',
|
||||
'mac': '00:11:22:33:44:55'}]
|
||||
response = self.app.get('/redfish/v1/Systems/xxx-yyy-zzz/'
|
||||
'EthernetInterfaces/nic2')
|
||||
|
||||
self.assertEqual('200 OK', response.status)
|
||||
self.assertEqual('nic2', response.json['Id'])
|
||||
self.assertEqual('VNIC nic2', response.json['Name'])
|
||||
self.assertEqual('00:11:22:33:44:55',
|
||||
response.json['PermanentMACAddress'])
|
||||
self.assertEqual('00:11:22:33:44:55',
|
||||
response.json['MACAddress'])
|
||||
self.assertEqual('/redfish/v1/Systems/xxx-yyy-zzz/'
|
||||
'EthernetInterfaces/nic2',
|
||||
response.json['@odata.id'])
|
||||
|
||||
def test_ethernet_interface_not_found(self, driver_mock):
|
||||
driver_mock.get_nics.return_value = [{'id': 'nic1',
|
||||
'mac': '52:54:00:4e:5d:37'},
|
||||
{'id': 'nic2',
|
||||
'mac': '00:11:22:33:44:55'}]
|
||||
response = self.app.get('/redfish/v1/Systems/xxx-yyy-zzz/'
|
||||
'EthernetInterfaces/nic3')
|
||||
|
||||
self.assertEqual('404 NOT FOUND', response.status)
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
import os
|
||||
|
||||
from munch import Munch
|
||||
from oslotest import base
|
||||
from six.moves import mock
|
||||
|
||||
@@ -182,3 +183,65 @@ class EmulatorTestCase(base.BaseTestCase):
|
||||
NotImplementedError,
|
||||
test_driver.reset_bios,
|
||||
'xxx-yyy-zzz')
|
||||
|
||||
def test_get_nics(self, nova_mock):
|
||||
addresses = Munch(
|
||||
{u'public': [
|
||||
Munch({u'OS-EXT-IPS-MAC:mac_addr': u'fa:16:3e:46:e3:ac',
|
||||
u'version': 6,
|
||||
u'addr': u'2001:db8::7',
|
||||
u'OS-EXT-IPS:type': u'fixed'}),
|
||||
Munch({u'OS-EXT-IPS-MAC:mac_addr': u'fa:16:3e:46:e3:ac',
|
||||
u'version': 4,
|
||||
u'addr': u'172.24.4.4',
|
||||
u'OS-EXT-IPS:type': u'fixed'})],
|
||||
u'private': [
|
||||
Munch({u'OS-EXT-IPS-MAC:mac_addr': u'fa:16:3e:22:18:31',
|
||||
u'version': 6,
|
||||
u'addr': u'fdc2:e509:41b8:0:f816:3eff:fe22:1831',
|
||||
u'OS-EXT-IPS:type': u'fixed'}),
|
||||
Munch({u'OS-EXT-IPS-MAC:mac_addr': u'fa:16:3e:22:18:31',
|
||||
u'version': 4,
|
||||
u'addr': u'10.0.0.10',
|
||||
u'OS-EXT-IPS:type': u'fixed'})]})
|
||||
server = mock.Mock(addresses=addresses)
|
||||
nova_mock.return_value.get_server.return_value = server
|
||||
|
||||
test_driver = OpenStackDriver('fake-cloud')
|
||||
nics = test_driver.get_nics('xxxx-yyyy-zzzz')
|
||||
self.assertEqual([{'id': 'fa:16:3e:22:18:31',
|
||||
'mac': 'fa:16:3e:22:18:31'},
|
||||
{'id': 'fa:16:3e:46:e3:ac',
|
||||
'mac': 'fa:16:3e:46:e3:ac'}],
|
||||
sorted(nics, key=lambda k: k['id']))
|
||||
|
||||
def test_get_nics_empty(self, nova_mock):
|
||||
server = mock.Mock(addresses=None)
|
||||
nova_mock.return_value.get_server.return_value = server
|
||||
test_driver = OpenStackDriver('fake-cloud')
|
||||
nics = test_driver.get_nics('xxxx-yyyy-zzzz')
|
||||
self.assertEqual(set(), nics)
|
||||
|
||||
def test_get_nics_error(self, nova_mock):
|
||||
addresses = Munch(
|
||||
{u'public': [
|
||||
Munch({u'version': 6,
|
||||
u'addr': u'2001:db8::7'}),
|
||||
Munch({u'version': 4,
|
||||
u'addr': u'172.24.4.4'})],
|
||||
u'private': [
|
||||
Munch({u'OS-EXT-IPS-MAC:mac_addr': u'fa:16:3e:22:18:31',
|
||||
u'version': 6,
|
||||
u'addr': u'fdc2:e509:41b8:0:f816:3eff:fe22:1831',
|
||||
u'OS-EXT-IPS:type': u'fixed'}),
|
||||
Munch({u'OS-EXT-IPS-MAC:mac_addr': u'fa:16:3e:22:18:31',
|
||||
u'version': 4,
|
||||
u'addr': u'10.0.0.10',
|
||||
u'OS-EXT-IPS:type': u'fixed'})]})
|
||||
server = mock.Mock(addresses=addresses)
|
||||
nova_mock.return_value.get_server.return_value = server
|
||||
test_driver = OpenStackDriver('fake-cloud')
|
||||
nics = test_driver.get_nics('xxxx-yyyy-zzzz')
|
||||
self.assertEqual([{'id': 'fa:16:3e:22:18:31',
|
||||
'mac': 'fa:16:3e:22:18:31'}],
|
||||
nics)
|
||||
@@ -12,3 +12,4 @@ oslotest>=3.2.0 # Apache-2.0
|
||||
stestr>=1.0.0 # Apache-2.0
|
||||
testscenarios>=0.4 # Apache-2.0/BSD
|
||||
testtools>=2.2.0 # MIT
|
||||
munch>=2.1.0 # MIT
|
||||
Reference in new issue
Block a user