From c00a55915ed95dd7f6f8e4f99525b6aa7010399d Mon Sep 17 00:00:00 2001 From: "ya.wang" Date: Thu, 30 Aug 2018 19:58:09 +0800 Subject: [PATCH] Add acl rule field for rsd v2_1 Change-Id: I2e7bfaa0fdb8b5de4147900052438ac8a5a1b3ae --- rsd_lib/resources/v2_1/ethernet_switch/acl.py | 29 ++++- .../v2_1/ethernet_switch/acl_rule.py | 119 ++++++++++++++++++ .../unit/json_samples/v2_1/acl_rule.json | 46 +++++++ .../v2_1/acl_rule_collection.json | 13 ++ .../v2_1/ethernet_switch/test_acl.py | 68 +++++++++- .../v2_1/ethernet_switch/test_acl_rule.py | 114 +++++++++++++++++ 6 files changed, 385 insertions(+), 4 deletions(-) create mode 100644 rsd_lib/resources/v2_1/ethernet_switch/acl_rule.py create mode 100644 rsd_lib/tests/unit/json_samples/v2_1/acl_rule.json create mode 100644 rsd_lib/tests/unit/json_samples/v2_1/acl_rule_collection.json create mode 100644 rsd_lib/tests/unit/resources/v2_1/ethernet_switch/test_acl_rule.py diff --git a/rsd_lib/resources/v2_1/ethernet_switch/acl.py b/rsd_lib/resources/v2_1/ethernet_switch/acl.py index 57e5d39..a26bd2c 100644 --- a/rsd_lib/resources/v2_1/ethernet_switch/acl.py +++ b/rsd_lib/resources/v2_1/ethernet_switch/acl.py @@ -13,8 +13,10 @@ # License for the specific language governing permissions and limitations # under the License. +from sushy import exceptions from sushy.resources import base +from rsd_lib.resources.v2_1.ethernet_switch import acl_rule from rsd_lib import utils as rsd_lib_utils @@ -32,7 +34,7 @@ class ACL(base.ResourceBase): oem = base.Field('Oem') """The acl oem info""" - rules = base.Field('Rules', adapter=rsd_lib_utils.get_resource_identity) + _rules = None # ref to ACLRuleCollection instance """The acl rules""" links = base.Field('Links') @@ -48,6 +50,31 @@ class ACL(base.ResourceBase): """ super(ACL, self).__init__(connector, identity, redfish_version) + def _get_acl_rule_collection_path(self): + """Helper function to find the RuleCollection path""" + rule_col = self.json.get('Rules') + if not rule_col: + raise exceptions.MissingAttributeError(attribute='ACLRules', + resource=self._path) + return rsd_lib_utils.get_resource_identity(rule_col) + + @property + def rules(self): + """Property to provide reference to `RuleCollection` instance + + It is calculated once when it is queried for the first time. On + refresh, this property is reset. + """ + if self._rules is None: + self._rules = acl_rule.ACLRuleCollection( + self._conn, self._get_acl_rule_collection_path(), + redfish_version=self.redfish_version) + return self._rules + + def refresh(self): + super(ACL, self).refresh() + self._rules = None + class ACLCollection(base.ResourceCollectionBase): diff --git a/rsd_lib/resources/v2_1/ethernet_switch/acl_rule.py b/rsd_lib/resources/v2_1/ethernet_switch/acl_rule.py new file mode 100644 index 0000000..bd47feb --- /dev/null +++ b/rsd_lib/resources/v2_1/ethernet_switch/acl_rule.py @@ -0,0 +1,119 @@ +# Copyright 2018 99cloud, Inc. +# 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 sushy.resources import base +from sushy import utils + +from rsd_lib import utils as rsd_lib_utils + + +class IPSourceField(base.CompositeField): + ipv4_address = base.Field('IPv4Address') + mask = base.Field('Mask') + + +class MACSourceField(base.CompositeField): + address = base.Field('Address') + mask = base.Field('Mask', adapter=rsd_lib_utils.int_or_none) + + +class VLANIdField(base.CompositeField): + id = base.Field('Id', adapter=rsd_lib_utils.int_or_none) + mask = base.Field('Mask', adapter=rsd_lib_utils.int_or_none) + + +class L4SourcePortField(base.CompositeField): + port = base.Field('Port', adapter=rsd_lib_utils.int_or_none) + mask = base.Field('Mask', adapter=rsd_lib_utils.int_or_none) + + +class ConditionField(base.CompositeField): + ip_source = IPSourceField('IPSource') + ip_destination = base.Field('IPDestination') + mac_source = MACSourceField('MACSource') + mac_destination = base.Field('MACDestination') + vlan_id = VLANIdField('VLANId') + l4_source_port = L4SourcePortField('L4SourcePort') + l4_destination_port = base.Field('L4DestinationPort', + adapter=rsd_lib_utils.int_or_none) + l4_protocol = base.Field('L4Protocol') + + +class ACLRule(base.ResourceBase): + + identity = base.Field('Id') + """The acl rule identity string""" + + name = base.Field('Name') + """The acl rule name""" + + description = base.Field('Description') + """The acl rule description""" + + rule_id = base.Field('RuleId', adapter=rsd_lib_utils.int_or_none) + """The acl rule id""" + + action = base.Field('Action') + """The acl rule action""" + + forward_mirror_interface = base.Field( + 'ForwardMirrorInterface', + adapter=rsd_lib_utils.get_resource_identity) + """The acl rule forward mirror interface""" + + mirror_port_region = base.Field('MirrorPortRegion', + adapter=utils.get_members_identities) + """The acl rule mirror port region""" + + mirror_type = base.Field('MirrorType') + """The acl rule mirror type""" + + condition = ConditionField('Condition') + """The acl rule condition field""" + + oem = base.Field('Oem') + """The ac rule oem field""" + + links = base.Field('Links') + """The acl rule links field""" + + def __init__(self, connector, identity, redfish_version=None): + """A class representing an ACL Rule + + :param connector: A connector instance + :param identity: The identity of the ACL Rule resource + :param redfish_version: The version of RedFish. Used to construct + the object according to schema of the given version. + """ + super(ACLRule, self).__init__(connector, identity, redfish_version) + + +class ACLRuleCollection(base.ResourceCollectionBase): + + @property + def _resource_type(self): + return ACLRule + + def __init__(self, connector, path, redfish_version=None): + """A class representing an ACL Rule Collection + + :param connector: A Connector instance + :param path: The canonical path to the ACL Rule collection resource + :param redfish_version: The version of RedFish. Used to construct + the object according to schema of the given version. + """ + super(ACLRuleCollection, self).__init__(connector, + path, + redfish_version) diff --git a/rsd_lib/tests/unit/json_samples/v2_1/acl_rule.json b/rsd_lib/tests/unit/json_samples/v2_1/acl_rule.json new file mode 100644 index 0000000..f4b1dc3 --- /dev/null +++ b/rsd_lib/tests/unit/json_samples/v2_1/acl_rule.json @@ -0,0 +1,46 @@ +{ + "@odata.context": "/redfish/v1/$metadata#EthernetSwitches/Members/Switch1/ACLs/Members/Rules/Members/$entity", + "@odata.id": "/redfish/v1/EthernetSwitches/Switch1/ACLs/ACL1/Rules/Rule1", + "@odata.type": "#EthernetSwitchACLRule.v1_0_0.EthernetSwitchACLRule", + "Id": "Rule1", + "Name": "Example Rule", + "Description": "User defined rule for ACL", + "RuleId": 1, + "Action": "Mirror", + "ForwardMirrorInterface": { + "@odata.id": "/redfish/v1/EthernetSwitches/Switch1/Ports/Port9" + }, + "MirrorPortRegion": [ + { + "@odata.id": "/redfish/v1/EthernetSwitches/Switch1/Ports/Port1" + }, + { + "@odata.id": "/redfish/v1/EthernetSwitches/Switch1/Ports/Port2" + } + ], + "MirrorType": "Bidirectional", + "Condition": { + "IPSource": { + "IPv4Address": "192.168.1.0", + "Mask": "0.0.0.255" + }, + "IPDestination": null, + "MACSource": { + "Address": "00:11:22:33:44:55", + "Mask": null + }, + "MACDestination": null, + "VLANId": { + "Id": 1088, + "Mask": 4095 + }, + "L4SourcePort": { + "Port": 22, + "Mask": 255 + }, + "L4DestinationPort": null, + "L4Protocol": null + }, + "Oem": {}, + "Links": {} +} diff --git a/rsd_lib/tests/unit/json_samples/v2_1/acl_rule_collection.json b/rsd_lib/tests/unit/json_samples/v2_1/acl_rule_collection.json new file mode 100644 index 0000000..f8fd9c4 --- /dev/null +++ b/rsd_lib/tests/unit/json_samples/v2_1/acl_rule_collection.json @@ -0,0 +1,13 @@ +{ + "@odata.context": "/redfish/v1/$metadata#EthernetSwitches/Members/Switch1/ACLs/Members/Rules", + "@odata.id": "/redfish/v1/EthernetSwitches/Switch1/ACLs/ACL1/Rules", + "@odata.type": "#EthernetSwitchACLRuleCollection.EthernetSwitchACLRuleCollection", + "Name": "Ethernet Switch Access Control List Rules Collection", + "Description": "Rules for switch Access Control List. Each Rule defines single action and at least one condition", + "Members@odata.count": 1, + "Members": [ + { + "@odata.id": "/redfish/v1/EthernetSwitches/Switch1/ACLs/ACL1/Rules/Rule1" + } + ] +} diff --git a/rsd_lib/tests/unit/resources/v2_1/ethernet_switch/test_acl.py b/rsd_lib/tests/unit/resources/v2_1/ethernet_switch/test_acl.py index b839e15..3668663 100644 --- a/rsd_lib/tests/unit/resources/v2_1/ethernet_switch/test_acl.py +++ b/rsd_lib/tests/unit/resources/v2_1/ethernet_switch/test_acl.py @@ -18,7 +18,10 @@ import json import mock import testtools +from sushy import exceptions + from rsd_lib.resources.v2_1.ethernet_switch import acl +from rsd_lib.resources.v2_1.ethernet_switch import acl_rule class ACLTestCase(testtools.TestCase): @@ -36,7 +39,7 @@ class ACLTestCase(testtools.TestCase): redfish_version='1.0.2' ) - def test__parst_attributes(self): + def test__parse_attributes(self): self.acl_inst._parse_attributes() self.assertEqual('1.0.2', self.acl_inst.redfish_version) self.assertEqual('ACL1', self.acl_inst.identity) @@ -44,8 +47,67 @@ class ACLTestCase(testtools.TestCase): self.acl_inst.name) self.assertEqual('Switch ACL', self.acl_inst.description) self.assertEqual({}, self.acl_inst.oem) - self.assertEqual('/redfish/v1/EthernetSwitches/Switch1/ACLs/ACL1/' - 'Rules', self.acl_inst.rules) + self.assertIsNone(self.acl_inst._rules) + + def test__get_acl_rule_collection_path(self): + self.assertEqual( + '/redfish/v1/EthernetSwitches/Switch1/ACLs/ACL1/Rules', + self.acl_inst._get_acl_rule_collection_path()) + + def test__get_acl_rule_collection_path_missing_attr(self): + self.acl_inst._json.pop('Rules') + self.assertRaisesRegex( + exceptions.MissingAttributeError, 'attribute ACLRule', + self.acl_inst._get_acl_rule_collection_path) + + def test_acl_rule(self): + # check for the underpath variable value + self.assertIsNone(self.acl_inst._rules) + # | GIVEN | + self.conn.get.return_value.json.reset_mock() + with open('rsd_lib/tests/unit/json_samples/v2_1/' + 'ethernet_switch_acl.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + # | WHEN | + actual_acl_rules = self.acl_inst.rules + # | THEN | + self.assertIsInstance(actual_acl_rules, + acl_rule.ACLRuleCollection) + self.conn.get.return_value.json.assert_called_once_with() + + # reset mock + self.conn.get.return_value.json.reset_mock() + # | WHEN & THEN | + # tests for same object on invoking subsequently + self.assertIs(actual_acl_rules, + self.acl_inst.rules) + self.conn.get.return_value.json.assert_not_called() + + def test_acl_rule_on_refresh(self): + # | GIVEN | + with open('rsd_lib/tests/unit/json_samples/v2_1/' + 'acl_rule_collection.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + # | WHEN & THEN | + self.assertIsInstance(self.acl_inst.rules, + acl_rule.ACLRuleCollection) + + # On refreshing the acl_rule instance... + with open('rsd_lib/tests/unit/json_samples/v2_1/' + 'ethernet_switch_acl.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + self.acl_inst.refresh() + + # | WHEN & THEN | + self.assertIsNone(self.acl_inst._rules) + + # | GIVEN | + with open('rsd_lib/tests/unit/json_samples/v2_1/' + 'acl_rule_collection.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + # | WHEN & THEN | + self.assertIsInstance(self.acl_inst.rules, + acl_rule.ACLRuleCollection) class ACLCollectionTestCase(testtools.TestCase): diff --git a/rsd_lib/tests/unit/resources/v2_1/ethernet_switch/test_acl_rule.py b/rsd_lib/tests/unit/resources/v2_1/ethernet_switch/test_acl_rule.py new file mode 100644 index 0000000..78a6bdd --- /dev/null +++ b/rsd_lib/tests/unit/resources/v2_1/ethernet_switch/test_acl_rule.py @@ -0,0 +1,114 @@ +# Copyright 2018 99cloud, Inc. +# 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 rsd_lib.resources.v2_1.ethernet_switch import acl_rule + + +class ACLRuleTestCase(testtools.TestCase): + + def setUp(self): + super(ACLRuleTestCase, self).setUp() + self.conn = mock.Mock() + with open('rsd_lib/tests/unit/json_samples/v2_1/acl_rule.json', + 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + + self.acl_rule_inst = acl_rule.ACLRule( + self.conn, + '/redfish/v1/EthernetSwitches/Switch1/ACLs/ACL1/Rules/Rule1', + redfish_version='1.0.2') + + def test__parse_attributes(self): + self.acl_rule_inst._parse_attributes() + self.assertEqual('1.0.2', self.acl_rule_inst.redfish_version) + self.assertEqual('Rule1', self.acl_rule_inst.identity) + self.assertEqual('Example Rule', self.acl_rule_inst.name) + self.assertEqual('User defined rule for ACL', + self.acl_rule_inst.description) + self.assertEqual(1, self.acl_rule_inst.rule_id) + self.assertEqual('Mirror', self.acl_rule_inst.action) + self.assertEqual('/redfish/v1/EthernetSwitches/Switch1/Ports/Port9', + self.acl_rule_inst.forward_mirror_interface) + self.assertEqual(('/redfish/v1/EthernetSwitches/Switch1/Ports/Port1', + '/redfish/v1/EthernetSwitches/Switch1/Ports/Port2',), + self.acl_rule_inst.mirror_port_region) + self.assertEqual('Bidirectional', + self.acl_rule_inst.mirror_type) + self.assertEqual('192.168.1.0', + self.acl_rule_inst.condition.ip_source.ipv4_address) + self.assertEqual('0.0.0.255', + self.acl_rule_inst.condition.ip_source.mask) + self.assertEqual(None, self.acl_rule_inst.condition.ip_destination) + self.assertEqual('00:11:22:33:44:55', + self.acl_rule_inst.condition.mac_source.address) + self.assertEqual(None, self.acl_rule_inst.condition.mac_source.mask) + self.assertEqual(1088, self.acl_rule_inst.condition.vlan_id.id) + self.assertEqual(4095, + self.acl_rule_inst.condition.vlan_id.mask) + self.assertEqual(22, + self.acl_rule_inst.condition.l4_source_port.port) + self.assertEqual(255, + self.acl_rule_inst.condition.l4_source_port.mask) + self.assertEqual(None, + self.acl_rule_inst.condition.l4_destination_port) + + +class ACLRuleCollectionTestCase(testtools.TestCase): + + def setUp(self): + super(ACLRuleCollectionTestCase, self).setUp() + self.conn = mock.Mock() + + with open('rsd_lib/tests/unit/json_samples/v2_1/' + 'acl_rule_collection.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + + self.acl_rule_col = acl_rule.ACLRuleCollection( + self.conn, + '/redfish/v1/EthernetSwitches/Switch1/ACLs/ACL1/Rules', + redfish_version='1.0.2') + + def test__parse_attributes(self): + self.acl_rule_col._parse_attributes() + self.assertEqual('1.0.2', self.acl_rule_col.redfish_version) + self.assertEqual('Ethernet Switch Access Control ' + 'List Rules Collection', + self.acl_rule_col.name) + self.assertEqual(('/redfish/v1/EthernetSwitches/Switch1/ACLs/ACL1/' + 'Rules/Rule1',), + self.acl_rule_col.members_identities) + + @mock.patch.object(acl_rule, 'ACLRule', autospec=True) + def test_get_member(self, mock_acl_rule): + self.acl_rule_col.get_member( + '/redfish/v1/EthernetSwitches/Switch1/ACLs/ACL1/Rules/Rule1') + + mock_acl_rule.assert_called_once_with( + self.acl_rule_col._conn, + '/redfish/v1/EthernetSwitches/Switch1/ACLs/ACL1/Rules/Rule1', + redfish_version=self.acl_rule_col.redfish_version + ) + + @mock.patch.object(acl_rule, 'ACLRule', autospec=True) + def test_get_members(self, mock_acl_rule): + members = self.acl_rule_col.get_members() + self.assertEqual(mock_acl_rule.call_count, 1) + self.assertIsInstance(members, list) + self.assertEqual(1, len(members))