Redfish: Adds macs discovery

This patch adds EthernetInterfaces subresource.
It returns the data in the form of a dictionary as
{'Port 1': 'aa:bb:cc:dd:ee:ff'}

Change-Id: I123bdc4aae68bbbdf9b637746aa4d7f109d12b03
This commit is contained in:
Nisha Agarwal 2017-07-12 23:06:46 +00:00
parent a1ac87394c
commit 7a01a4fd98
9 changed files with 459 additions and 0 deletions

View File

@ -61,3 +61,10 @@ CPUVT_DISABLED = 'cpu_vt disabled'
SUPPORTED_LEGACY_BIOS_ONLY = 'legacy bios only'
SUPPORTED_UEFI_ONLY = 'uefi only'
SUPPORTED_LEGACY_BIOS_AND_UEFI = 'legacy bios and uefi'
# Health related constants
HEALTH_STATE_ENABLED = 'enabled'
HEALTH_STATE_DISABLED = 'disabled'
HEALTH_OK = 'ok'
HEALTH_WARNING = 'warning'
HEALTH_CRITICAL = 'critical'

View File

@ -0,0 +1,87 @@
# Copyright 2017 Hewlett Packard Enterprise Development LP
#
# 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.
from proliantutils.redfish.resources.system import constants as sys_cons
from proliantutils.redfish.resources.system import mappings as sys_map
from sushy.resources import base
class HealthStatusField(base.CompositeField):
state = base.MappedField(
'State', sys_map.HEALTH_STATE_VALUE_MAP)
health = base.MappedField('Health', sys_map.HEALTH_VALUE_MAP)
class EthernetInterface(base.ResourceBase):
"""This class represents the EthernetInterfaces resource"""
identity = base.Field('Id', required=True)
"""The Ethernet Interface identity string"""
name = base.Field('Name')
"""The name of the resource or array element"""
description = base.Field('Description')
"""Description"""
permanent_mac_address = base.Field('PermanentMACAddress')
"""This is the permanent MAC address assigned to this interface (port) """
mac_address = base.Field('MACAddress')
"""This is the currently configured MAC address of the interface."""
speed_mbps = base.Field('SpeedMbps')
"""This is the current speed in Mbps of this interface."""
status = HealthStatusField("Status")
class EthernetInterfaceCollection(base.ResourceCollectionBase):
_summary = None
@property
def _resource_type(self):
return EthernetInterface
@property
def summary(self):
"""property to return the summary MAC addresses and state
This filters the MACs whose health is OK,
and in 'Enabled' State would be returned.
The returned format will be {<port_id>: <mac_address>}.
This is because RIBCL returns the data in format
{'Port 1': 'aa:bb:cc:dd:ee:ff'} and ironic ilo drivers inspection
consumes the data in this format.
Note: 'Id' is referred to as "Port number".
"""
if self._summary is None:
mac_dict = {}
for eth in self.get_members():
if eth.mac_address is not None:
if (eth.status is not None and
eth.status.health == sys_cons.HEALTH_OK
and eth.status.state ==
sys_cons.HEALTH_STATE_ENABLED):
mac_dict.update(
{'Port ' + eth.identity: eth.mac_address})
self._summary = mac_dict
return self._summary
def refresh(self):
super(EthernetInterfaceCollection, self).refresh()
self._summary = None

View File

@ -77,3 +77,20 @@ SUPPORTED_BOOT_MODE = {
2: constants.SUPPORTED_LEGACY_BIOS_AND_UEFI,
3: constants.SUPPORTED_UEFI_ONLY
}
HEALTH_STATE_VALUE_MAP = {
'Enabled': constants.HEALTH_STATE_ENABLED,
'Disabled': constants.HEALTH_STATE_DISABLED,
}
HEALTH_STATE_VALUE_MAP_REV = (
utils.revert_dictionary(HEALTH_STATE_VALUE_MAP))
HEALTH_VALUE_MAP = {
'OK': constants.HEALTH_OK,
'Warning': constants.HEALTH_WARNING,
'Critical': constants.HEALTH_CRITICAL
}
HEALTH_VALUE_MAP_REV = (
utils.revert_dictionary(HEALTH_VALUE_MAP))

View File

@ -22,6 +22,7 @@ from proliantutils import exception
from proliantutils import log
from proliantutils.redfish.resources.system import bios
from proliantutils.redfish.resources.system import constants
from proliantutils.redfish.resources.system import ethernet_interface
from proliantutils.redfish.resources.system import mappings
from proliantutils.redfish.resources.system import pci_device
from proliantutils.redfish.resources.system import secure_boot
@ -72,6 +73,7 @@ class HPESystem(system.System):
_bios_settings = None # ref to BIOSSettings instance
_secure_boot = None # ref to SecureBoot instance
_pci_devices = None
_ethernet_interfaces = None
def _get_hpe_push_power_button_action_element(self):
push_action = self._hpe_actions.computer_system_ext_powerbutton
@ -188,3 +190,26 @@ class HPESystem(system.System):
self._bios_settings = None
self._pci_devices = None
self._secure_boot = None
self._ethernet_interfaces = None
def _get_hpe_sub_resource_collection_path(self, sub_res):
path = None
try:
path = utils.get_subresource_path_by(self, sub_res)
except exception.MissingAttributeError:
path = utils.get_subresource_path_by(
self, ['Oem', 'Hpe', 'Links', sub_res])
return path
@property
def ethernet_interfaces(self):
"""Provide reference to EthernetInterfacesCollection instance"""
if self._ethernet_interfaces is None:
sub_res = 'EthernetInterfaces'
self._ethernet_interfaces = (
ethernet_interface.EthernetInterfaceCollection(
self._conn,
self._get_hpe_sub_resource_collection_path(sub_res),
redfish_version=self.redfish_version))
return self._ethernet_interfaces

View File

@ -0,0 +1,37 @@
{
"@odata.type": "#EthernetInterface.v1_0_2.EthernetInterface",
"Id": "1",
"Name": "Ethernet Interface",
"Description": "System NIC 1",
"Status": {
"State": "Enabled",
"Health": "OK"
},
"PermanentMACAddress": "12:44:6A:3B:04:11",
"MACAddress": "12:44:6A:3B:04:11",
"SpeedMbps": 1000,
"FullDuplex": true,
"HostName": "web483",
"FQDN": "web483.contoso.com",
"IPv6DefaultGateway": "fe80::3ed9:2bff:fe34:600",
"NameServers": [
"names.contoso.com"
],
"IPv4Addresses": [{
"Address": "192.168.0.10",
"SubnetMask": "255.255.252.0",
"AddressOrigin": "Static",
"Gateway": "192.168.0.1"
}],
"IPv6Addresses": [{
"Address": "fe80::1ec1:deff:fe6f:1e24",
"PrefixLength": 64,
"AddressOrigin": "Static",
"AddressState": "Preferred"
}],
"VLANs": {
"@odata.id": "/redfish/v1/Systems/437XR1138R2/EthernetInterfaces/12446A3B0411/VLANs"
},
"@odata.context": "/redfish/v1/$metadata#EthernetInterface.EthernetInterface",
"@odata.id": "/redfish/v1/Systems/437XR1138R2/EthernetInterfaces/12446A3B0411"
}

View File

@ -0,0 +1,12 @@
{
"@odata.type": "#EthernetInterfaceCollection.EthernetInterfaceCollection",
"Name": "Ethernet Interface Collection",
"Description": "System NICs on Contoso Servers",
"Members@odata.count": 1,
"Members": [{
"@odata.id": "/redfish/v1/Systems/437XR1138R2/EthernetInterfaces/12446A3B0411"
}],
"Oem": {},
"@odata.context": "/redfish/v1/$metadata#EthernetInterfaceCollection.EthernetInterfaceCollection",
"@odata.id": "/redfish/v1/Systems/437XR1138R2/EthernetInterfaces"
}

View File

@ -46,6 +46,9 @@
"PciRoot(0x0)/Pci(0x17,0x0)/Sata(0x3,0x0,0x0)"
]
},
"EthernetInterfaces": {
"@odata.id": "/redfish/v1/Systems/1/EthernetInterfaces/"
},
"HostName": "",
"Id": "1",
"IndicatorLED": "Off",
@ -226,6 +229,87 @@
"UUID": "00000000-0000-0000-0000-000000000000"
},
"System_for_oem_ethernet_interfaces":{
"@odata.context": "/redfish/v1/$metadata#Systems/Members/$entity",
"@odata.etag": "W/\"0E79655D\"",
"@odata.id": "/redfish/v1/Systems/1/",
"@odata.type": "#ComputerSystem.v1_2_0.ComputerSystem",
"Actions": {
"#ComputerSystem.Reset": {
"ResetType@Redfish.AllowableValues": [
"On",
"ForceOff",
"ForceRestart",
"Nmi",
"PushPowerButton"
],
"target": "/redfish/v1/Systems/1/Actions/ComputerSystem.Reset/"
}
},
"AssetTag": "",
"Bios": {
"@odata.id": "/redfish/v1/systems/1/bios/"
},
"BiosVersion": "U31 v1.00 (03/11/2017)",
"Boot": {
"BootSourceOverrideEnabled": "Disabled",
"BootSourceOverrideMode": "UEFI",
"BootSourceOverrideTarget": "None",
"BootSourceOverrideTarget@Redfish.AllowableValues": [
"None",
"Cd",
"Hdd",
"Usb",
"SDCard",
"Utilities",
"Diags",
"BiosSetup",
"Pxe",
"UefiShell",
"UefiHttp",
"UefiTarget"
],
"UefiTargetBootSourceOverride": "None",
"UefiTargetBootSourceOverride@Redfish.AllowableValues": [
"HD(1,GPT,7F14DF43-6600-420A-9950-C028836F6A5D,0x800,0x64000)/\\EFI\\centos\\shim.efi",
"UsbClass(0xFFFF,0xFFFF,0xFF,0xFF,0xFF)",
"PciRoot(0x0)/Pci(0x17,0x0)/Sata(0x3,0x0,0x0)"
]
},
"HostName": "",
"Id": "1",
"Model": "ProLiant DL180 Gen10",
"Oem": {
"Hpe": {
"@odata.type": "#HpeComputerSystemExt.v2_1_0.HpeComputerSystemExt",
"Actions": {
"#HpeComputerSystemExt.PowerButton": {
"PushType@Redfish.AllowableValues": [
"Press",
"PressAndHold"
],
"target": "/redfish/v1/Systems/1/Actions/Oem/Hpe/HpeComputerSystemExt.PowerButton/"
},
"#HpeComputerSystemExt.SystemReset": {
"ResetType@Redfish.AllowableValues": [
"ColdBoot"
],
"target": "/redfish/v1/Systems/1/Actions/Oem/Hpe/HpeComputerSystemExt.SystemReset/"
}
},
"Links": {
"EthernetInterfaces": {
"@odata.id": "/redfish/v1/Systems/1/EthernetInterfaces/"
},
"SmartStorage": {
"@odata.id": "/redfish/v1/Systems/1/SmartStorage/"
}
}
}
}
},
"System_op_for_one_time_boot_cdrom": {
"@odata.context": "/redfish/v1/$metadata#Systems/Members/$entity",
"@odata.etag": "W/\"0E79655D\"",

View File

@ -0,0 +1,109 @@
# Copyright 2017 Hewlett Packard Enterprise Development LP
# 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
import testtools
from proliantutils.redfish.resources.system import constants as sys_cons
from proliantutils.redfish.resources.system import ethernet_interface
class EthernetInterfaceTestCase(testtools.TestCase):
def setUp(self):
super(EthernetInterfaceTestCase, self).setUp()
self.conn = mock.Mock()
eth_file = ('proliantutils/tests/redfish/json_samples/'
'ethernet_interface.json')
with open(eth_file, 'r') as f:
self.conn.get.return_value.json.return_value = json.loads(f.read())
eth_path = ("/redfish/v1/Systems/437XR1138R2/EthernetInterfaces/"
"12446A3B0411")
self.sys_eth = ethernet_interface.EthernetInterface(
self.conn, eth_path, redfish_version='1.0.2')
def test__parse_attributes(self):
self.sys_eth._parse_attributes()
self.assertEqual('1.0.2', self.sys_eth.redfish_version)
self.assertEqual('1', self.sys_eth.identity)
self.assertEqual('Ethernet Interface', self.sys_eth.name)
self.assertEqual('System NIC 1', self.sys_eth.description)
self.assertEqual(
'12:44:6A:3B:04:11', self.sys_eth.permanent_mac_address)
self.assertEqual('12:44:6A:3B:04:11', self.sys_eth.mac_address)
self.assertEqual(sys_cons.HEALTH_STATE_ENABLED,
self.sys_eth.status.state)
self.assertEqual(sys_cons.HEALTH_OK, self.sys_eth.status.health)
self.assertEqual(1000, self.sys_eth.speed_mbps)
class EthernetInterfaceCollectionTestCase(testtools.TestCase):
def setUp(self):
super(EthernetInterfaceCollectionTestCase, self).setUp()
self.conn = mock.Mock()
with open('proliantutils/tests/redfish/json_samples/'
'ethernet_interface_collection.json', 'r') as f:
self.conn.get.return_value.json.return_value = json.loads(f.read())
self.sys_eth_col = ethernet_interface.EthernetInterfaceCollection(
self.conn, '/redfish/v1/Systems/437XR1138R2/EthernetInterfaces',
redfish_version='1.0.2')
def test__parse_attributes(self):
self.sys_eth_col._parse_attributes()
self.assertEqual('1.0.2', self.sys_eth_col.redfish_version)
self.assertEqual('Ethernet Interface Collection',
self.sys_eth_col.name)
eth_path = ('/redfish/v1/Systems/437XR1138R2/EthernetInterfaces/'
'12446A3B0411',)
self.assertEqual(eth_path, self.sys_eth_col.members_identities)
@mock.patch.object(ethernet_interface, 'EthernetInterface', autospec=True)
def test_get_member(self, mock_eth):
self.sys_eth_col.get_member(
'/redfish/v1/Systems/437XR1138R2/EthernetInterfaces/'
'12446A3B0411')
mock_eth.assert_called_once_with(
self.sys_eth_col._conn,
('/redfish/v1/Systems/437XR1138R2/EthernetInterfaces/'
'12446A3B0411'),
redfish_version=self.sys_eth_col.redfish_version)
@mock.patch.object(ethernet_interface, 'EthernetInterface', autospec=True)
def test_get_members(self, mock_eth):
members = self.sys_eth_col.get_members()
eth_path = ("/redfish/v1/Systems/437XR1138R2/EthernetInterfaces/"
"12446A3B0411")
calls = [
mock.call(self.sys_eth_col._conn, eth_path,
redfish_version=self.sys_eth_col.redfish_version),
]
mock_eth.assert_has_calls(calls)
self.assertIsInstance(members, list)
self.assertEqual(1, len(members))
def test_summary(self):
self.assertIsNone(self.sys_eth_col._summary)
self.conn.get.return_value.json.reset_mock()
path = ('proliantutils/tests/redfish/json_samples/'
'ethernet_interface.json')
with open(path, 'r') as f:
self.conn.get.return_value.json.return_value = json.loads(f.read())
expected_summary = {'Port 1': '12:44:6A:3B:04:11'}
actual_summary = self.sys_eth_col.summary
self.assertEqual(expected_summary, actual_summary)

View File

@ -22,8 +22,10 @@ import testtools
from proliantutils import exception
from proliantutils.redfish.resources.system import bios
from proliantutils.redfish.resources.system import constants as sys_cons
from proliantutils.redfish.resources.system import ethernet_interface
from proliantutils.redfish.resources.system import secure_boot
from proliantutils.redfish.resources.system import system
from proliantutils.redfish import utils
from sushy.resources.system import system as sushy_system
@ -264,3 +266,82 @@ class HPESystemTestCase(testtools.TestCase):
# | WHEN & THEN |
self.assertIsInstance(self.sys_inst.secure_boot,
secure_boot.SecureBoot)
@mock.patch.object(utils, 'get_subresource_path_by')
def test_get_hpe_sub_resource_collection_path(self, res_mock):
res = 'EthernetInterfaces'
res_mock.return_value = '/redfish/v1/Systems/1/EthernetInterfaces'
path = self.sys_inst._get_hpe_sub_resource_collection_path(res)
self.assertTrue(res_mock.called)
self.assertEqual(path, res_mock.return_value)
@mock.patch.object(utils, 'get_subresource_path_by')
def test_get_hpe_sub_resource_collection_path_oem_path(self, res_mock):
res = 'EthernetInterfaces'
error_val = exception.MissingAttributeError
oem_path = '/redfish/v1/Systems/1/EthernetInterfaces'
res_mock.side_effect = [error_val, oem_path]
path = self.sys_inst._get_hpe_sub_resource_collection_path(res)
self.assertTrue(res_mock.called)
self.assertEqual(path, oem_path)
@mock.patch.object(utils, 'get_subresource_path_by')
def test_get_hpe_sub_resource_collection_path_fail(self, res_mock):
error_val = exception.MissingAttributeError
res_mock.side_effect = [error_val, error_val]
self.assertRaises(
exception.MissingAttributeError,
self.sys_inst._get_hpe_sub_resource_collection_path,
'EthernetInterfaces')
self.assertTrue(res_mock.called)
def test_ethernet_interfaces(self):
self.conn.get.return_value.json.reset_mock()
eth_coll = None
eth_value = None
path = ('proliantutils/tests/redfish/json_samples/'
'ethernet_interface_collection.json')
with open(path, 'r') as f:
eth_coll = json.loads(f.read())
with open('proliantutils/tests/redfish/json_samples/'
'ethernet_interface.json', 'r') as f:
eth_value = (json.loads(f.read()))
self.conn.get.return_value.json.side_effect = [eth_coll,
eth_value]
self.assertIsNone(self.sys_inst._ethernet_interfaces)
actual_macs = self.sys_inst.ethernet_interfaces.summary
self.assertEqual({'Port 1': '12:44:6A:3B:04:11'},
actual_macs)
self.assertIsInstance(self.sys_inst._ethernet_interfaces,
ethernet_interface.EthernetInterfaceCollection)
def test_ethernet_interfaces_oem(self):
self.conn = mock.MagicMock()
with open('proliantutils/tests/redfish/'
'json_samples/system.json', 'r') as f:
system_json = json.loads(f.read())
self.conn.get.return_value.json.return_value = (
system_json['System_for_oem_ethernet_interfaces'])
self.sys_inst = system.HPESystem(
self.conn, '/redfish/v1/Systems/1',
redfish_version='1.0.2')
self.conn.get.return_value.json.reset_mock()
eth_coll = None
eth_value = None
path = ('proliantutils/tests/redfish/json_samples/'
'ethernet_interface_collection.json')
with open(path, 'r') as f:
eth_coll = json.loads(f.read())
with open('proliantutils/tests/redfish/json_samples/'
'ethernet_interface.json', 'r') as f:
eth_value = (json.loads(f.read()))
self.conn.get.return_value.json.side_effect = [eth_coll,
eth_value]
self.assertIsNone(self.sys_inst._ethernet_interfaces)
actual_macs = self.sys_inst.ethernet_interfaces.summary
self.assertEqual({'Port 1': '12:44:6A:3B:04:11'},
actual_macs)
self.assertIsInstance(self.sys_inst._ethernet_interfaces,
ethernet_interface.EthernetInterfaceCollection)