summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNisha Agarwal <agarwalnisha1980@gmail.com>2017-04-02 04:14:47 -0700
committerNisha Agarwal <agarwalnisha1980@gmail.com>2017-10-06 00:41:11 -0700
commit8fe2904a62b0f56dc3fc3fefc5a5a746911ce891 (patch)
treea107297c3c7787c88e0ce196d3d53e8b6dc17888
parent2aa31ddf4f90517695831379eee919bbf72d4ff7 (diff)
Adds EthernetInterface to the library1.2.0
This commit adds the EthernetInterface to the library. This returns the MAC addresses and its status as a dictionary to its caller. This has been tested on HPE Redfish hardware. Change-Id: If57184d71d244cdc6f04d3f66d56c374d4336d24
Notes
Notes (review): Code-Review+2: Dmitry Tantsur <divius.inside@gmail.com> Code-Review+1: Shivanand Tendulker <stendulker@gmail.com> Code-Review+1: Debayan Ray <debayan.ray@gmail.com> Code-Review+2: Julia Kreger <juliaashleykreger@gmail.com> Workflow+1: Julia Kreger <juliaashleykreger@gmail.com> Verified+2: Jenkins Submitted-by: Jenkins Submitted-at: Mon, 09 Oct 2017 16:37:32 +0000 Reviewed-on: https://review.openstack.org/452532 Project: openstack/sushy Branch: refs/heads/master
-rw-r--r--releasenotes/notes/add_ethernet_interface-df308f814f0e4bce.yaml5
-rw-r--r--sushy/resources/system/constants.py7
-rw-r--r--sushy/resources/system/ethernet_interface.py84
-rw-r--r--sushy/resources/system/mappings.py17
-rw-r--r--sushy/resources/system/system.py21
-rw-r--r--sushy/tests/unit/json_samples/ethernet_interfaces.json37
-rw-r--r--sushy/tests/unit/json_samples/ethernet_interfaces_collection.json12
-rw-r--r--sushy/tests/unit/resources/system/test_ethernet_interfaces.py103
-rw-r--r--sushy/tests/unit/resources/system/test_system.py25
-rw-r--r--sushy/utils.py21
10 files changed, 327 insertions, 5 deletions
diff --git a/releasenotes/notes/add_ethernet_interface-df308f814f0e4bce.yaml b/releasenotes/notes/add_ethernet_interface-df308f814f0e4bce.yaml
new file mode 100644
index 0000000..6eb0328
--- /dev/null
+++ b/releasenotes/notes/add_ethernet_interface-df308f814f0e4bce.yaml
@@ -0,0 +1,5 @@
1---
2features:
3 - |
4 Adds the "EthernetInterfaces" to the library.
5 It also returns the list of connected MACs.
diff --git a/sushy/resources/system/constants.py b/sushy/resources/system/constants.py
index f0cb2e1..e76bdb1 100644
--- a/sushy/resources/system/constants.py
+++ b/sushy/resources/system/constants.py
@@ -108,3 +108,10 @@ PROCESSOR_ARCH_IA_64 = 'Intel Itanium'
108PROCESSOR_ARCH_ARM = 'ARM' 108PROCESSOR_ARCH_ARM = 'ARM'
109PROCESSOR_ARCH_MIPS = 'MIPS' 109PROCESSOR_ARCH_MIPS = 'MIPS'
110PROCESSOR_ARCH_OEM = 'OEM-defined' 110PROCESSOR_ARCH_OEM = 'OEM-defined'
111
112# Health related constants.
113HEALTH_STATE_ENABLED = 'Enabled'
114HEALTH_STATE_DISABLED = 'Disabled'
115HEALTH_OK = 'OK'
116HEALTH_WARNING = 'Warning'
117HEALTH_CRITICAL = 'Critical'
diff --git a/sushy/resources/system/ethernet_interface.py b/sushy/resources/system/ethernet_interface.py
new file mode 100644
index 0000000..86baea7
--- /dev/null
+++ b/sushy/resources/system/ethernet_interface.py
@@ -0,0 +1,84 @@
1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13# This is referred from Redfish standard schema.
14# http://redfish.dmtf.org/schemas/EthernetInterface.v1_3_0.json
15
16import logging
17
18from sushy.resources import base
19from sushy.resources.system import constants as sys_cons
20from sushy.resources.system import mappings as sys_map
21
22LOG = logging.getLogger(__name__)
23
24
25class HealthStatusField(base.CompositeField):
26 state = base.MappedField(
27 'State', sys_map.HEALTH_STATE_VALUE_MAP)
28 health = base.Field('Health')
29
30
31class EthernetInterface(base.ResourceBase):
32 """This class adds the EthernetInterface resource"""
33
34 identity = base.Field('Id', required=True)
35 """The Ethernet Interface identity string"""
36
37 name = base.Field('Name')
38 """The name of the resource or array element"""
39
40 description = base.Field('Description')
41 """Description"""
42
43 permanent_mac_address = base.Field('PermanentMACAddress')
44 """This is the permanent MAC address assigned to this interface (port) """
45
46 mac_address = base.Field('MACAddress')
47 """This is the currently configured MAC address of the interface."""
48
49 speed_mbps = base.Field('SpeedMbps')
50 """This is the current speed in Mbps of this interface."""
51
52 status = HealthStatusField("Status")
53
54
55class EthernetInterfaceCollection(base.ResourceCollectionBase):
56
57 _summary = None
58
59 @property
60 def _resource_type(self):
61 return EthernetInterface
62
63 @property
64 def summary(self):
65 """Summary MAC addresses and interfaces state
66
67 This filters the MACs whose health is OK,
68 which means the MACs in both 'Enabled' and 'Disabled' States
69 are returned.
70 :returns dictionary in the format {'aa:bb:cc:dd:ee:ff': 'Enabled'}
71 """
72 if self._summary is None:
73 mac_dict = {}
74 for eth in self.get_members():
75 if eth.mac_address is not None:
76 if (eth.status is not None and
77 eth.status.health == sys_cons.HEALTH_OK):
78 mac_dict[eth.mac_address] = eth.status.state
79 self._summary = mac_dict
80 return self._summary
81
82 def refresh(self):
83 super(EthernetInterfaceCollection, self).refresh()
84 self._summary = None
diff --git a/sushy/resources/system/mappings.py b/sushy/resources/system/mappings.py
index 6a7949b..fb6e5ab 100644
--- a/sushy/resources/system/mappings.py
+++ b/sushy/resources/system/mappings.py
@@ -82,3 +82,20 @@ PROCESSOR_ARCH_VALUE_MAP = {
82 82
83PROCESSOR_ARCH_VALUE_MAP_REV = ( 83PROCESSOR_ARCH_VALUE_MAP_REV = (
84 utils.revert_dictionary(PROCESSOR_ARCH_VALUE_MAP)) 84 utils.revert_dictionary(PROCESSOR_ARCH_VALUE_MAP))
85
86HEALTH_STATE_VALUE_MAP = {
87 'Enabled': sys_cons.HEALTH_STATE_ENABLED,
88 'Disabled': sys_cons.HEALTH_STATE_DISABLED,
89}
90
91HEALTH_STATE_VALUE_MAP_REV = (
92 utils.revert_dictionary(HEALTH_STATE_VALUE_MAP))
93
94HEALTH_VALUE_MAP = {
95 'OK': sys_cons.HEALTH_OK,
96 'Warning': sys_cons.HEALTH_WARNING,
97 'Critical': sys_cons.HEALTH_CRITICAL
98}
99
100HEALTH_VALUE_MAP_REV = (
101 utils.revert_dictionary(HEALTH_VALUE_MAP))
diff --git a/sushy/resources/system/system.py b/sushy/resources/system/system.py
index 11c2f83..7f081ae 100644
--- a/sushy/resources/system/system.py
+++ b/sushy/resources/system/system.py
@@ -19,6 +19,7 @@ from sushy import exceptions
19from sushy.resources import base 19from sushy.resources import base
20from sushy.resources import common 20from sushy.resources import common
21from sushy.resources.system import constants as sys_cons 21from sushy.resources.system import constants as sys_cons
22from sushy.resources.system import ethernet_interface
22from sushy.resources.system import mappings as sys_maps 23from sushy.resources.system import mappings as sys_maps
23from sushy.resources.system import processor 24from sushy.resources.system import processor
24from sushy import utils 25from sushy import utils
@@ -118,6 +119,8 @@ class System(base.ResourceBase):
118 119
119 _actions = ActionsField('Actions', required=True) 120 _actions = ActionsField('Actions', required=True)
120 121
122 _ethernet_interfaces = None
123
121 def __init__(self, connector, identity, redfish_version=None): 124 def __init__(self, connector, identity, redfish_version=None):
122 """A class representing a ComputerSystem 125 """A class representing a ComputerSystem
123 126
@@ -241,11 +244,7 @@ class System(base.ResourceBase):
241 244
242 def _get_processor_collection_path(self): 245 def _get_processor_collection_path(self):
243 """Helper function to find the ProcessorCollection path""" 246 """Helper function to find the ProcessorCollection path"""
244 processor_col = self.json.get('Processors') 247 return utils.get_sub_resource_path_by(self, 'Processors')
245 if not processor_col:
246 raise exceptions.MissingAttributeError(attribute='Processors',
247 resource=self._path)
248 return processor_col.get('@odata.id')
249 248
250 @property 249 @property
251 def processors(self): 250 def processors(self):
@@ -264,6 +263,18 @@ class System(base.ResourceBase):
264 def refresh(self): 263 def refresh(self):
265 super(System, self).refresh() 264 super(System, self).refresh()
266 self._processors = None 265 self._processors = None
266 self._ethernet_interfaces = None
267
268 @property
269 def ethernet_interfaces(self):
270 if self._ethernet_interfaces is None:
271 self._ethernet_interfaces = (
272 ethernet_interface.EthernetInterfaceCollection(
273 self._conn,
274 utils.get_sub_resource_path_by(self, "EthernetInterfaces"),
275 redfish_version=self.redfish_version))
276
277 return self._ethernet_interfaces
267 278
268 279
269class SystemCollection(base.ResourceCollectionBase): 280class SystemCollection(base.ResourceCollectionBase):
diff --git a/sushy/tests/unit/json_samples/ethernet_interfaces.json b/sushy/tests/unit/json_samples/ethernet_interfaces.json
new file mode 100644
index 0000000..88f9417
--- /dev/null
+++ b/sushy/tests/unit/json_samples/ethernet_interfaces.json
@@ -0,0 +1,37 @@
1{
2 "@odata.type": "#EthernetInterface.v1_0_2.EthernetInterface",
3 "Id": "1",
4 "Name": "Ethernet Interface",
5 "Description": "System NIC 1",
6 "Status": {
7 "State": "Enabled",
8 "Health": "OK"
9 },
10 "PermanentMACAddress": "12:44:6A:3B:04:11",
11 "MACAddress": "12:44:6A:3B:04:11",
12 "SpeedMbps": 1000,
13 "FullDuplex": true,
14 "HostName": "web483",
15 "FQDN": "web483.contoso.com",
16 "IPv6DefaultGateway": "fe80::3ed9:2bff:fe34:600",
17 "NameServers": [
18 "names.contoso.com"
19 ],
20 "IPv4Addresses": [{
21 "Address": "192.168.0.10",
22 "SubnetMask": "255.255.252.0",
23 "AddressOrigin": "Static",
24 "Gateway": "192.168.0.1"
25 }],
26 "IPv6Addresses": [{
27 "Address": "fe80::1ec1:deff:fe6f:1e24",
28 "PrefixLength": 64,
29 "AddressOrigin": "Static",
30 "AddressState": "Preferred"
31 }],
32 "VLANs": {
33 "@odata.id": "/redfish/v1/Systems/437XR1138R2/EthernetInterfaces/12446A3B0411/VLANs"
34 },
35 "@odata.context": "/redfish/v1/$metadata#EthernetInterface.EthernetInterface",
36 "@odata.id": "/redfish/v1/Systems/437XR1138R2/EthernetInterfaces/12446A3B0411"
37}
diff --git a/sushy/tests/unit/json_samples/ethernet_interfaces_collection.json b/sushy/tests/unit/json_samples/ethernet_interfaces_collection.json
new file mode 100644
index 0000000..4623de0
--- /dev/null
+++ b/sushy/tests/unit/json_samples/ethernet_interfaces_collection.json
@@ -0,0 +1,12 @@
1{
2 "@odata.type": "#EthernetInterfaceCollection.EthernetInterfaceCollection",
3 "Name": "Ethernet Interface Collection",
4 "Description": "System NICs on Contoso Servers",
5 "Members@odata.count": 1,
6 "Members": [{
7 "@odata.id": "/redfish/v1/Systems/437XR1138R2/EthernetInterfaces/12446A3B0411"
8 }],
9 "Oem": {},
10 "@odata.context": "/redfish/v1/$metadata#EthernetInterfaceCollection.EthernetInterfaceCollection",
11 "@odata.id": "/redfish/v1/Systems/437XR1138R2/EthernetInterfaces"
12}
diff --git a/sushy/tests/unit/resources/system/test_ethernet_interfaces.py b/sushy/tests/unit/resources/system/test_ethernet_interfaces.py
new file mode 100644
index 0000000..539f7af
--- /dev/null
+++ b/sushy/tests/unit/resources/system/test_ethernet_interfaces.py
@@ -0,0 +1,103 @@
1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13import json
14
15import mock
16
17from sushy.resources.system import constants as sys_cons
18from sushy.resources.system import ethernet_interface
19from sushy.tests.unit import base
20
21
22class EthernetInterfaceTestCase(base.TestCase):
23
24 def setUp(self):
25 super(EthernetInterfaceTestCase, self).setUp()
26 self.conn = mock.Mock()
27 eth_file = 'sushy/tests/unit/json_samples/ethernet_interfaces.json'
28 with open(eth_file, 'r') as f:
29 self.conn.get.return_value.json.return_value = json.loads(f.read())
30
31 eth_path = ("/redfish/v1/Systems/437XR1138R2/EthernetInterfaces/"
32 "12446A3B0411")
33 self.sys_eth = ethernet_interface.EthernetInterface(
34 self.conn, eth_path, redfish_version='1.0.2')
35
36 def test__parse_attributes(self):
37 self.sys_eth._parse_attributes()
38 self.assertEqual('1.0.2', self.sys_eth.redfish_version)
39 self.assertEqual('1', self.sys_eth.identity)
40 self.assertEqual('Ethernet Interface', self.sys_eth.name)
41 self.assertEqual('System NIC 1', self.sys_eth.description)
42 self.assertEqual(
43 '12:44:6A:3B:04:11', self.sys_eth.permanent_mac_address)
44 self.assertEqual('12:44:6A:3B:04:11', self.sys_eth.mac_address)
45 self.assertEqual('Enabled', self.sys_eth.status.state)
46 self.assertEqual('OK', self.sys_eth.status.health)
47 self.assertEqual(1000, self.sys_eth.speed_mbps)
48
49
50class EthernetInterfaceCollectionTestCase(base.TestCase):
51
52 def setUp(self):
53 super(EthernetInterfaceCollectionTestCase, self).setUp()
54 self.conn = mock.Mock()
55 with open('sushy/tests/unit/json_samples/'
56 'ethernet_interfaces_collection.json', 'r') as f:
57 self.conn.get.return_value.json.return_value = json.loads(f.read())
58 self.sys_eth_col = ethernet_interface.EthernetInterfaceCollection(
59 self.conn, '/redfish/v1/Systems/437XR1138R2/EthernetInterfaces',
60 redfish_version='1.0.2')
61
62 def test__parse_attributes(self):
63 self.sys_eth_col._parse_attributes()
64 self.assertEqual('1.0.2', self.sys_eth_col.redfish_version)
65 self.assertEqual('Ethernet Interface Collection',
66 self.sys_eth_col.name)
67 eth_path = ('/redfish/v1/Systems/437XR1138R2/EthernetInterfaces/'
68 '12446A3B0411',)
69 self.assertEqual(eth_path, self.sys_eth_col.members_identities)
70
71 @mock.patch.object(ethernet_interface, 'EthernetInterface', autospec=True)
72 def test_get_member(self, mock_eth):
73 self.sys_eth_col.get_member(
74 '/redfish/v1/Systems/437XR1138R2/EthernetInterfaces/'
75 '12446A3B0411')
76 mock_eth.assert_called_once_with(
77 self.sys_eth_col._conn,
78 ('/redfish/v1/Systems/437XR1138R2/EthernetInterfaces/'
79 '12446A3B0411'),
80 redfish_version=self.sys_eth_col.redfish_version)
81
82 @mock.patch.object(ethernet_interface, 'EthernetInterface', autospec=True)
83 def test_get_members(self, mock_eth):
84 members = self.sys_eth_col.get_members()
85 eth_path = ("/redfish/v1/Systems/437XR1138R2/EthernetInterfaces/"
86 "12446A3B0411")
87 calls = [
88 mock.call(self.sys_eth_col._conn, eth_path,
89 redfish_version=self.sys_eth_col.redfish_version),
90 ]
91 mock_eth.assert_has_calls(calls)
92 self.assertIsInstance(members, list)
93 self.assertEqual(1, len(members))
94
95 def test_eth_summary(self):
96 self.assertIsNone(self.sys_eth_col._summary)
97 self.conn.get.return_value.json.reset_mock()
98 path = 'sushy/tests/unit/json_samples/ethernet_interfaces.json'
99 with open(path, 'r') as f:
100 self.conn.get.return_value.json.return_value = json.loads(f.read())
101 expected_summary = {'12:44:6A:3B:04:11': sys_cons.HEALTH_STATE_ENABLED}
102 actual_summary = self.sys_eth_col.summary
103 self.assertEqual(expected_summary, actual_summary)
diff --git a/sushy/tests/unit/resources/system/test_system.py b/sushy/tests/unit/resources/system/test_system.py
index 30a98eb..6b23711 100644
--- a/sushy/tests/unit/resources/system/test_system.py
+++ b/sushy/tests/unit/resources/system/test_system.py
@@ -19,6 +19,8 @@ import mock
19 19
20import sushy 20import sushy
21from sushy import exceptions 21from sushy import exceptions
22from sushy.resources.system import constants as sys_cons
23from sushy.resources.system import ethernet_interface
22from sushy.resources.system import processor 24from sushy.resources.system import processor
23from sushy.resources.system import system 25from sushy.resources.system import system
24from sushy.tests.unit import base 26from sushy.tests.unit import base
@@ -58,6 +60,7 @@ class SystemTestCase(base.TestCase):
58 self.assertEqual(96, self.sys_inst.memory_summary.size_gib) 60 self.assertEqual(96, self.sys_inst.memory_summary.size_gib)
59 self.assertEqual("OK", self.sys_inst.memory_summary.health) 61 self.assertEqual("OK", self.sys_inst.memory_summary.health)
60 self.assertIsNone(self.sys_inst._processors) 62 self.assertIsNone(self.sys_inst._processors)
63 self.assertIsNone(self.sys_inst._ethernet_interfaces)
61 64
62 def test__parse_attributes_missing_actions(self): 65 def test__parse_attributes_missing_actions(self):
63 self.sys_inst.json.pop('Actions') 66 self.sys_inst.json.pop('Actions')
@@ -338,6 +341,28 @@ class SystemTestCase(base.TestCase):
338 self.sys_inst.processors.summary) 341 self.sys_inst.processors.summary)
339 self.conn.get.return_value.json.assert_not_called() 342 self.conn.get.return_value.json.assert_not_called()
340 343
344 def test_ethernet_interfaces(self):
345 self.conn.get.return_value.json.reset_mock()
346 eth_coll_return_value = None
347 eth_return_value = None
348 path = ('sushy/tests/unit/json_samples/'
349 'ethernet_interfaces_collection.json')
350 with open(path, 'r') as f:
351 eth_coll_return_value = json.loads(f.read())
352 with open('sushy/tests/unit/json_samples/ethernet_interfaces.json',
353 'r') as f:
354 eth_return_value = (json.loads(f.read()))
355
356 self.conn.get.return_value.json.side_effect = [eth_coll_return_value,
357 eth_return_value]
358
359 self.assertIsNone(self.sys_inst._ethernet_interfaces)
360 actual_macs = self.sys_inst.ethernet_interfaces.summary
361 self.assertEqual({'12:44:6A:3B:04:11': sys_cons.HEALTH_STATE_ENABLED},
362 actual_macs)
363 self.assertIsInstance(self.sys_inst._ethernet_interfaces,
364 ethernet_interface.EthernetInterfaceCollection)
365
341 366
342class SystemCollectionTestCase(base.TestCase): 367class SystemCollectionTestCase(base.TestCase):
343 368
diff --git a/sushy/utils.py b/sushy/utils.py
index 689a296..3bc616a 100644
--- a/sushy/utils.py
+++ b/sushy/utils.py
@@ -15,6 +15,8 @@
15 15
16import logging 16import logging
17 17
18from sushy import exceptions
19
18LOG = logging.getLogger(__name__) 20LOG = logging.getLogger(__name__)
19 21
20 22
@@ -57,3 +59,22 @@ def int_or_none(x):
57 if x is None: 59 if x is None:
58 return None 60 return None
59 return int(x) 61 return int(x)
62
63
64def get_sub_resource_path_by(resource, subresource_name):
65 """Helper function to find the subresource path
66
67 :param resource: ResourceBase instance on which the name
68 gets queried upon.
69 :param subresource_name: name of the resource field to
70 fetch the '@odata.id' from.
71 """
72 subresource_element = resource.json.get(subresource_name)
73 if not subresource_element:
74 raise exceptions.MissingAttributeError(attribute=subresource_name,
75 resource=resource.path)
76 if '@odata.id' not in subresource_element:
77 raise exceptions.MissingAttributeError(
78 attribute=(subresource_name + '/@odata.id'),
79 resource=resource.path)
80 return subresource_element['@odata.id']