diff --git a/rsd_lib/resources/v2_1/ethernet_switch/ethernet_switch_static_mac.py b/rsd_lib/resources/v2_1/ethernet_switch/ethernet_switch_static_mac.py index 9843ce4..21fbdd5 100644 --- a/rsd_lib/resources/v2_1/ethernet_switch/ethernet_switch_static_mac.py +++ b/rsd_lib/resources/v2_1/ethernet_switch/ethernet_switch_static_mac.py @@ -13,11 +13,16 @@ # License for the specific language governing permissions and limitations # under the License. +from jsonschema import validate +import logging + from sushy.resources import base from rsd_lib import base as rsd_lib_base from rsd_lib import utils as rsd_lib_utils +LOG = logging.getLogger(__name__) + class EthernetSwitchStaticMAC(rsd_lib_base.ResourceBase): """EthernetSwitchStaticMAC resource class @@ -36,3 +41,24 @@ class EthernetSwitchStaticMACCollection(rsd_lib_base.ResourceCollectionBase): @property def _resource_type(self): return EthernetSwitchStaticMAC + + def create_static_mac(self, mac_address, vlan_id=None): + """Create new static MAC entry + + :param mac_address: MAC address that should be forwarded to this port + :param vlan_id: If specified, defines which packets tagged with + specific VLANId should be forwarded to this port + :returns: The location of new static MAC entry + """ + validate(mac_address, {"type": "string"}) + data = {"MACAddress": mac_address} + + if vlan_id is not None: + validate(vlan_id, {"type": "number"}) + data["VLANId"] = vlan_id + + resp = self._conn.post(self.path, data=data) + LOG.info("Static MAC created at %s", resp.headers["Location"]) + + static_mac_url = resp.headers["Location"] + return static_mac_url[static_mac_url.find(self._path):] diff --git a/rsd_lib/tests/unit/resources/v2_1/ethernet_switch/test_ethernet_switch_static_mac.py b/rsd_lib/tests/unit/resources/v2_1/ethernet_switch/test_ethernet_switch_static_mac.py index 62e081d..9c0006e 100644 --- a/rsd_lib/tests/unit/resources/v2_1/ethernet_switch/test_ethernet_switch_static_mac.py +++ b/rsd_lib/tests/unit/resources/v2_1/ethernet_switch/test_ethernet_switch_static_mac.py @@ -13,13 +13,14 @@ # License for the specific language governing permissions and limitations # under the License. - import json +import jsonschema import mock import testtools from rsd_lib.resources.v2_1.ethernet_switch import ethernet_switch_static_mac +from rsd_lib.tests.unit.fakes import request_fakes class EthernetSwitchStaticMACTestCase(testtools.TestCase): @@ -62,13 +63,21 @@ class EthernetSwitchStaticMACCollectionTestCase(testtools.TestCase): "r", ) as f: self.conn.get.return_value.json.return_value = json.loads(f.read()) - self.static_mac_col = ethernet_switch_static_mac.\ - EthernetSwitchStaticMACCollection( - self.conn, - "/redfish/v1/EthernetSwitches/Switch1/Ports/Port1/" - "StaticMACs", - redfish_version="1.1.0", - ) + + self.conn.post.return_value = request_fakes.fake_request_post( + None, + headers={ + "Location": "https://localhost:8443/redfish/v1/" + "EthernetSwitches/Switch1/Ports/Port1/StaticMACs/1" + }, + ) + + self.static_mac_col = ethernet_switch_static_mac.\ + EthernetSwitchStaticMACCollection( + self.conn, + "/redfish/v1/EthernetSwitches/Switch1/Ports/Port1/StaticMACs", + redfish_version="1.1.0", + ) def test__parse_attributes(self): self.static_mac_col._parse_attributes() @@ -107,3 +116,44 @@ class EthernetSwitchStaticMACCollectionTestCase(testtools.TestCase): mock_static_mac.assert_has_calls(calls) self.assertIsInstance(members, list) self.assertEqual(1, len(members)) + + def test_create_static_mac(self): + reqs = {"MACAddress": "00:11:22:33:44:55"} + result = self.static_mac_col.create_static_mac("00:11:22:33:44:55") + self.static_mac_col._conn.post.assert_called_once_with( + "/redfish/v1/EthernetSwitches/Switch1/Ports/Port1/StaticMACs", + data=reqs, + ) + self.assertEqual( + result, + "/redfish/v1/EthernetSwitches/Switch1/Ports/Port1/StaticMACs/1", + ) + + self.static_mac_col._conn.post.reset_mock() + reqs = {"MACAddress": "00:11:22:33:44:55", "VLANId": 69} + result = self.static_mac_col.create_static_mac( + "00:11:22:33:44:55", vlan_id=69 + ) + self.static_mac_col._conn.post.assert_called_once_with( + "/redfish/v1/EthernetSwitches/Switch1/Ports/Port1/StaticMACs", + data=reqs, + ) + self.assertEqual( + result, + "/redfish/v1/EthernetSwitches/Switch1/Ports/Port1/StaticMACs/1", + ) + + def test_create_static_mac_invalid_reqs(self): + with self.assertRaisesRegex( + jsonschema.exceptions.ValidationError, + ("True is not of type 'string'"), + ): + self.static_mac_col.create_static_mac(True) + + with self.assertRaisesRegex( + jsonschema.exceptions.ValidationError, + ("'invalid-value' is not of type 'number'"), + ): + self.static_mac_col.create_static_mac( + "00:11:22:33:44:55", vlan_id="invalid-value" + ) diff --git a/rsd_lib/tests/unit/resources/v2_1/node/test_node.py b/rsd_lib/tests/unit/resources/v2_1/node/test_node.py index df30563..ab4f506 100644 --- a/rsd_lib/tests/unit/resources/v2_1/node/test_node.py +++ b/rsd_lib/tests/unit/resources/v2_1/node/test_node.py @@ -453,7 +453,7 @@ class NodeCollectionTestCase(testtools.TestCase): self.conn.post.return_value = request_fakes.fake_request_post( None, headers={ - "Location": "https://localhost:8443/" "redfish/v1/Nodes/1" + "Location": "https://localhost:8443/redfish/v1/Nodes/1" }, ) self.node_col = node.NodeCollection(