From 3fdca97ff5053770161059a34a95fd39463eaecd Mon Sep 17 00:00:00 2001 From: "Mytnyk, Volodymyr" Date: Thu, 31 May 2018 10:03:10 +0100 Subject: Configure ACL via static file This patch allows user to configure ACL/vFW SampleVNF ACL via configuration file provided in TC definition. The Yardstick applies the rules to SampleVNF from specified config file + rules generated by Yardstick (default rules). The example of SampleVNF ACL CLI commands generated/applied by Yardstick can be found at (using default ACL config file): ACL VNF ACL CLI commands: acl/tc_heat_rfc2544_ipv4_1rule_1flow_64B_trex.yaml http://paste.openstack.org/show/723303/ vFW VNF ACL CLI commands: vfw/tc_heat_rfc2544_ipv4_1rule_1flow_64B_trex.yaml http://paste.openstack.org/show/723304/ Change-Id: I76a630261a982083b628e3985fc3bec14ca495db Signed-off-by: Mytnyk, Volodymyr --- samples/vnf_samples/nsut/acl/acl_1rule.yaml | 60 +++--- samples/vnf_samples/nsut/acl/acl_rules.yaml | 60 +++--- .../vnf_samples/nsut/acl/acl_worstcaserules.yaml | 64 +++---- samples/vnf_samples/nsut/vfw/acl_1rule.yaml | 60 +++--- yardstick/common/exceptions.py | 9 + yardstick/common/utils.py | 13 ++ .../network_services/helpers/samplevnf_helper.py | 121 ++---------- .../network_services/vnf_generic/vnf/acl_vnf.py | 207 +++++++++++++++++++-- .../network_services/vnf_generic/vnf/sample_vnf.py | 15 +- .../network_services/vnf_generic/vnf/vfw_vnf.py | 18 +- .../vnf_generic/vnf/vnf_ssh_helper.py | 1 + yardstick/network_services/yang_model.py | 108 ----------- .../helpers/test_samplevnf_helper.py | 62 +----- .../tests/unit/network_services/test_yang_model.py | 129 ------------- .../vnf_generic/vnf/test_acl_vnf.py | 147 ++++++++++++++- .../vnf_generic/vnf/test_cgnapt_vnf.py | 1 + .../vnf_generic/vnf/test_sample_vnf.py | 4 +- .../vnf_generic/vnf/test_vfw_vnf.py | 4 +- 18 files changed, 511 insertions(+), 572 deletions(-) delete mode 100644 yardstick/network_services/yang_model.py delete mode 100644 yardstick/tests/unit/network_services/test_yang_model.py diff --git a/samples/vnf_samples/nsut/acl/acl_1rule.yaml b/samples/vnf_samples/nsut/acl/acl_1rule.yaml index b184a29e2..49066e924 100644 --- a/samples/vnf_samples/nsut/acl/acl_1rule.yaml +++ b/samples/vnf_samples/nsut/acl/acl_1rule.yaml @@ -11,37 +11,29 @@ # 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. - -access-list1: - acl: - access-list-entries: - - ace: - ace-oper-data: - match-counter: 0 - actions: drop,count - matches: - destination-ipv4-network: 152.16.40.20/24 - destination-port-range: - lower-port: 0 - upper-port: 65535 - source-ipv4-network: 0.0.0.0/0 - source-port-range: - lower-port: 0 - upper-port: 65535 - rule-name: rule1588 - - ace: - ace-oper-data: - match-counter: 0 - actions: drop,count - matches: - destination-ipv4-network: 0.0.0.0/0 - destination-port-range: - lower-port: 0 - upper-port: 65535 - source-ipv4-network: 152.16.100.20/24 - source-port-range: - lower-port: 0 - upper-port: 65535 - rule-name: rule1589 - acl-name: sample-ipv4-acl - acl-type: ipv4-acl +--- +access-list-entries: + - + actions: [drop,count] + matches: + destination-ipv4-network: 152.16.40.20/24 + destination-port-range: + lower-port: 0 + upper-port: 65535 + source-ipv4-network: 0.0.0.0/0 + source-port-range: + lower-port: 0 + upper-port: 65535 + rule-name: rule1588 + - + actions: [drop,count] + matches: + destination-ipv4-network: 0.0.0.0/0 + destination-port-range: + lower-port: 0 + upper-port: 65535 + source-ipv4-network: 152.16.100.20/24 + source-port-range: + lower-port: 0 + upper-port: 65535 + rule-name: rule1589 diff --git a/samples/vnf_samples/nsut/acl/acl_rules.yaml b/samples/vnf_samples/nsut/acl/acl_rules.yaml index b184a29e2..49066e924 100644 --- a/samples/vnf_samples/nsut/acl/acl_rules.yaml +++ b/samples/vnf_samples/nsut/acl/acl_rules.yaml @@ -11,37 +11,29 @@ # 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. - -access-list1: - acl: - access-list-entries: - - ace: - ace-oper-data: - match-counter: 0 - actions: drop,count - matches: - destination-ipv4-network: 152.16.40.20/24 - destination-port-range: - lower-port: 0 - upper-port: 65535 - source-ipv4-network: 0.0.0.0/0 - source-port-range: - lower-port: 0 - upper-port: 65535 - rule-name: rule1588 - - ace: - ace-oper-data: - match-counter: 0 - actions: drop,count - matches: - destination-ipv4-network: 0.0.0.0/0 - destination-port-range: - lower-port: 0 - upper-port: 65535 - source-ipv4-network: 152.16.100.20/24 - source-port-range: - lower-port: 0 - upper-port: 65535 - rule-name: rule1589 - acl-name: sample-ipv4-acl - acl-type: ipv4-acl +--- +access-list-entries: + - + actions: [drop,count] + matches: + destination-ipv4-network: 152.16.40.20/24 + destination-port-range: + lower-port: 0 + upper-port: 65535 + source-ipv4-network: 0.0.0.0/0 + source-port-range: + lower-port: 0 + upper-port: 65535 + rule-name: rule1588 + - + actions: [drop,count] + matches: + destination-ipv4-network: 0.0.0.0/0 + destination-port-range: + lower-port: 0 + upper-port: 65535 + source-ipv4-network: 152.16.100.20/24 + source-port-range: + lower-port: 0 + upper-port: 65535 + rule-name: rule1589 diff --git a/samples/vnf_samples/nsut/acl/acl_worstcaserules.yaml b/samples/vnf_samples/nsut/acl/acl_worstcaserules.yaml index b184a29e2..6f09bb848 100644 --- a/samples/vnf_samples/nsut/acl/acl_worstcaserules.yaml +++ b/samples/vnf_samples/nsut/acl/acl_worstcaserules.yaml @@ -11,37 +11,33 @@ # 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. - -access-list1: - acl: - access-list-entries: - - ace: - ace-oper-data: - match-counter: 0 - actions: drop,count - matches: - destination-ipv4-network: 152.16.40.20/24 - destination-port-range: - lower-port: 0 - upper-port: 65535 - source-ipv4-network: 0.0.0.0/0 - source-port-range: - lower-port: 0 - upper-port: 65535 - rule-name: rule1588 - - ace: - ace-oper-data: - match-counter: 0 - actions: drop,count - matches: - destination-ipv4-network: 0.0.0.0/0 - destination-port-range: - lower-port: 0 - upper-port: 65535 - source-ipv4-network: 152.16.100.20/24 - source-port-range: - lower-port: 0 - upper-port: 65535 - rule-name: rule1589 - acl-name: sample-ipv4-acl - acl-type: ipv4-acl +--- +access-list-entries: + - + ace-oper-data: + match-counter: 0 + actions: [drop,count] + matches: + destination-ipv4-network: 152.16.40.20/24 + destination-port-range: + lower-port: 0 + upper-port: 65535 + source-ipv4-network: 0.0.0.0/0 + source-port-range: + lower-port: 0 + upper-port: 65535 + rule-name: rule1588 + - + ace-oper-data: + match-counter: 0 + actions: [drop,count] + matches: + destination-ipv4-network: 0.0.0.0/0 + destination-port-range: + lower-port: 0 + upper-port: 65535 + source-ipv4-network: 152.16.100.20/24 + source-port-range: + lower-port: 0 + upper-port: 65535 + rule-name: rule1589 diff --git a/samples/vnf_samples/nsut/vfw/acl_1rule.yaml b/samples/vnf_samples/nsut/vfw/acl_1rule.yaml index 6753645ba..f7569b32c 100644 --- a/samples/vnf_samples/nsut/vfw/acl_1rule.yaml +++ b/samples/vnf_samples/nsut/vfw/acl_1rule.yaml @@ -11,37 +11,29 @@ # 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. - -access-list1: - acl: - access-list-entries: - - ace: - ace-oper-data: - match-counter: 0 - actions: drop,count - matches: - destination-ipv4-network: 152.16.0.0/24 - destination-port-range: - lower-port: 0 - upper-port: 65535 - source-ipv4-network: 0.0.0.0/0 - source-port-range: - lower-port: 0 - upper-port: 65535 - rule-name: rule1588 - - ace: - ace-oper-data: - match-counter: 0 - actions: drop,count - matches: - destination-ipv4-network: 0.0.0.0/0 - destination-port-range: - lower-port: 0 - upper-port: 65535 - source-ipv4-network: 152.16.0.0/24 - source-port-range: - lower-port: 0 - upper-port: 65535 - rule-name: rule1589 - acl-name: sample-ipv4-acl - acl-type: ipv4-acl +--- +access-list-entries: + - + actions: [drop,count] + matches: + destination-ipv4-network: 152.16.0.0/24 + destination-port-range: + lower-port: 0 + upper-port: 65535 + source-ipv4-network: 0.0.0.0/0 + source-port-range: + lower-port: 0 + upper-port: 65535 + rule-name: rule1588 + - + actions: [drop,count] + matches: + destination-ipv4-network: 0.0.0.0/0 + destination-port-range: + lower-port: 0 + upper-port: 65535 + source-ipv4-network: 152.16.0.0/24 + source-port-range: + lower-port: 0 + upper-port: 65535 + rule-name: rule1589 diff --git a/yardstick/common/exceptions.py b/yardstick/common/exceptions.py index 954d655cb..f2d2e95c1 100644 --- a/yardstick/common/exceptions.py +++ b/yardstick/common/exceptions.py @@ -313,3 +313,12 @@ class IxNetworkFieldNotPresentInStackItem(YardstickException): class SLAValidationError(YardstickException): message = '%(case_name)s SLA validation failed. Error: %(error_msg)s' + + +class AclMissingActionArguments(YardstickException): + message = ('Missing ACL action parameter ' + '[action=%(action_name)s parameter=%(action_param)s]') + + +class AclUknownActionTemplate(YardstickException): + message = 'No ACL CLI template found for "%(action_name)s" action' diff --git a/yardstick/common/utils.py b/yardstick/common/utils.py index 108ee17bc..869db469b 100644 --- a/yardstick/common/utils.py +++ b/yardstick/common/utils.py @@ -306,6 +306,19 @@ def get_ip_version(ip_addr): return address.version +def make_ip_addr(ip, mask): + """ + :param ip[str]: ip adddress + :param mask[str]: /24 prefix of 255.255.255.0 netmask + :return: IPv4Interface object + """ + try: + return ipaddress.ip_interface(six.text_type('/'.join([ip, mask]))) + except (TypeError, ValueError): + # None so we can skip later + return None + + def ip_to_hex(ip_addr, separator=''): try: address = ipaddress.ip_address(six.text_type(ip_addr)) diff --git a/yardstick/network_services/helpers/samplevnf_helper.py b/yardstick/network_services/helpers/samplevnf_helper.py index 0ab10d7b7..8e6a3a3ea 100644 --- a/yardstick/network_services/helpers/samplevnf_helper.py +++ b/yardstick/network_services/helpers/samplevnf_helper.py @@ -23,8 +23,7 @@ from itertools import chain, repeat import six from six.moves.configparser import ConfigParser - -from yardstick.common.utils import ip_to_hex +from yardstick.common import utils LOG = logging.getLogger(__name__) @@ -34,19 +33,6 @@ link {0} config {1} {2} link {0} up """ -ACTION_TEMPLATE = """\ -p action add {0} accept -p action add {0} fwd {0} -p action add {0} count -""" - -FW_ACTION_TEMPLATE = """\ -p action add {0} accept -p action add {0} fwd {0} -p action add {0} count -p action add {0} conntrack -""" - # This sets up a basic passthrough with no rules SCRIPT_TPL = """ {link_config} @@ -59,9 +45,7 @@ SCRIPT_TPL = """ {arp_route_tbl6} -{actions} - -{rules} +{flows} """ @@ -182,26 +166,9 @@ class MultiPortConfig(object): return parser.get(section, key) return default - @staticmethod - def make_ip_addr(ip, mask): - """ - :param ip: ip adddress - :type ip: str - :param mask: /24 prefix of 255.255.255.0 netmask - :type mask: str - :return: interface - :rtype: IPv4Interface - """ - - try: - return ipaddress.ip_interface(six.text_type('/'.join([ip, mask]))) - except (TypeError, ValueError): - # None so we can skip later - return None - @classmethod def validate_ip_and_prefixlen(cls, ip_addr, prefixlen): - ip_addr = cls.make_ip_addr(ip_addr, prefixlen) + ip_addr = utils.make_ip_addr(ip_addr, prefixlen) return ip_addr.ip.exploded, ip_addr.network.prefixlen def __init__(self, topology_file, config_tpl, tmp_file, vnfd_helper, @@ -245,7 +212,7 @@ class MultiPortConfig(object): self.ports_len = 0 self.prv_que_handler = None self.vnfd = None - self.rules = None + self.flows = None self.pktq_out = [] @staticmethod @@ -360,7 +327,7 @@ class MultiPortConfig(object): "%s/%s" % (interface["dst_ip"], interface["netmask"]))) arp_vars = { - "port_netmask_hex": ip_to_hex(dst_port_ip.network.netmask.exploded), + "port_netmask_hex": utils.ip_to_hex(dst_port_ip.network.netmask.exploded), # this is the port num that contains port0 subnet and next_hop_ip_hex # this is LINKID which should be based on DPDK port number "port_num": dpdk_port_num, @@ -542,7 +509,7 @@ class MultiPortConfig(object): self.update_write_parser(self.loadb_tpl) self.start_core += 1 - for i in range(self.worker_threads): + for _ in range(self.worker_threads): vnf_data = self.generate_vnf_data() if not self.vnf_tpl: self.vnf_tpl = {} @@ -637,65 +604,8 @@ class MultiPortConfig(object): return '\n'.join(('p {3} arpadd {0} {1} {2}'.format(*values) for values in arp_config6)) - def generate_action_config(self): - port_list = (self.vnfd_helper.port_num(p) for p in self.all_ports) - if self.vnf_type == "VFW": - template = FW_ACTION_TEMPLATE - else: - template = ACTION_TEMPLATE - - return ''.join((template.format(port) for port in port_list)) - - def get_ip_from_port(self, port): - # we can't use gateway because in OpenStack gateways interfer with floating ip routing - # return self.make_ip_addr(self.get_ports_gateway(port), self.get_netmask_gateway(port)) - vintf = self.vnfd_helper.find_interface(name=port)["virtual-interface"] - ip = vintf["local_ip"] - netmask = vintf["netmask"] - return self.make_ip_addr(ip, netmask) - - def get_network_and_prefixlen_from_ip_of_port(self, port): - ip_addr = self.get_ip_from_port(port) - # handle cases with no gateway - if ip_addr: - return ip_addr.network.network_address.exploded, ip_addr.network.prefixlen - else: - return None, None - - def generate_rule_config(self): - cmd = 'acl' if self.vnf_type == "ACL" else "vfw" - rules_config = self.rules if self.rules else '' - new_rules = [] - new_ipv6_rules = [] - pattern = 'p {0} add {1} {2} {3} {4} {5} 0 65535 0 65535 0 0 {6}' - for src_intf, dst_intf in self.port_pair_list: - src_port = self.vnfd_helper.port_num(src_intf) - dst_port = self.vnfd_helper.port_num(dst_intf) - - src_net, src_prefix_len = self.get_network_and_prefixlen_from_ip_of_port(src_intf) - dst_net, dst_prefix_len = self.get_network_and_prefixlen_from_ip_of_port(dst_intf) - # ignore entires with empty values - if all((src_net, src_prefix_len, dst_net, dst_prefix_len)): - new_rules.append((cmd, self.txrx_pipeline, src_net, src_prefix_len, - dst_net, dst_prefix_len, dst_port)) - new_rules.append((cmd, self.txrx_pipeline, dst_net, dst_prefix_len, - src_net, src_prefix_len, src_port)) - - # src_net = self.get_ports_gateway6(port_pair[0]) - # src_prefix_len = self.get_netmask_gateway6(port_pair[0]) - # dst_net = self.get_ports_gateway6(port_pair[1]) - # dst_prefix_len = self.get_netmask_gateway6(port_pair[0]) - # # ignore entires with empty values - # if all((src_net, src_prefix_len, dst_net, dst_prefix_len)): - # new_ipv6_rules.append((cmd, self.txrx_pipeline, src_net, src_prefix_len, - # dst_net, dst_prefix_len, dst_port)) - # new_ipv6_rules.append((cmd, self.txrx_pipeline, dst_net, dst_prefix_len, - # src_net, src_prefix_len, src_port)) - - acl_apply = "\np %s applyruleset" % cmd - new_rules_config = '\n'.join(pattern.format(*values) for values - in chain(new_rules, new_ipv6_rules)) - return ''.join([rules_config, new_rules_config, acl_apply]) + def get_flows_config(self): + return self.flows if self.flows else '' def generate_script_data(self): self._port_pairs = PortPairs(self.vnfd_helper.interfaces) @@ -707,24 +617,15 @@ class MultiPortConfig(object): # disable IPv6 for now # 'arp_config6': self.generate_arp_config6(), 'arp_config6': "", - 'arp_config': self.generate_arp_config(), 'arp_route_tbl': self.generate_arp_route_tbl(), 'arp_route_tbl6': "", - 'actions': '', - 'rules': '', + 'flows': self.get_flows_config() } - - if self.vnf_type in ('ACL', 'VFW'): - script_data.update({ - 'actions': self.generate_action_config(), - 'rules': self.generate_rule_config(), - }) - return script_data - def generate_script(self, vnfd, rules=None): + def generate_script(self, vnfd, flows=None): self.vnfd = vnfd - self.rules = rules + self.flows = flows script_data = self.generate_script_data() script = SCRIPT_TPL.format(**script_data) if self.lb_config == self.HW_LB: diff --git a/yardstick/network_services/vnf_generic/vnf/acl_vnf.py b/yardstick/network_services/vnf_generic/vnf/acl_vnf.py index d9719eb4e..1357f6b26 100644 --- a/yardstick/network_services/vnf_generic/vnf/acl_vnf.py +++ b/yardstick/network_services/vnf_generic/vnf/acl_vnf.py @@ -13,10 +13,14 @@ # limitations under the License. import logging - +import ipaddress +import six from yardstick.common import utils +from yardstick.common import exceptions + from yardstick.network_services.vnf_generic.vnf.sample_vnf import SampleVNF, DpdkVnfSetupEnvHelper -from yardstick.network_services.yang_model import YangModel +from yardstick.network_services.helpers.samplevnf_helper import PortPairs +from itertools import chain LOG = logging.getLogger(__name__) @@ -38,6 +42,196 @@ class AclApproxSetupEnvSetupEnvHelper(DpdkVnfSetupEnvHelper): SW_DEFAULT_CORE = 5 DEFAULT_CONFIG_TPL_CFG = "acl.cfg" VNF_TYPE = "ACL" + RULE_CMD = "acl" + + DEFAULT_PRIORITY = 1 + DEFAULT_PROTOCOL = 0 + DEFAULT_PROTOCOL_MASK = 0 + # Default actions to be applied to SampleVNF. Please note, + # that this list is extended with `fwd` action when default + # actions are generated. + DEFAULT_FWD_ACTIONS = ["accept", "count"] + + def __init__(self, vnfd_helper, ssh_helper, scenario_helper): + super(AclApproxSetupEnvSetupEnvHelper, self).__init__(vnfd_helper, + ssh_helper, + scenario_helper) + self._action_id = 0 + + def get_ip_from_port(self, port): + # we can't use gateway because in OpenStack gateways interfere with floating ip routing + # return self.make_ip_addr(self.get_ports_gateway(port), self.get_netmask_gateway(port)) + vintf = self.vnfd_helper.find_interface(name=port)["virtual-interface"] + return utils.make_ip_addr(vintf["local_ip"], vintf["netmask"]) + + def get_network_and_prefixlen_from_ip_of_port(self, port): + ip_addr = self.get_ip_from_port(port) + # handle cases with no gateway + if ip_addr: + return ip_addr.network.network_address.exploded, ip_addr.network.prefixlen + else: + return None, None + + @property + def new_action_id(self): + """Get new action id""" + self._action_id += 1 + return self._action_id + + def get_default_flows(self): + """Get default actions/rules + Returns: (, ) + : + { : [ ]} + Example: + { 0 : [ "accept", "count", {"fwd" : "port": 0} ], ... } + : + [ {"src_ip": "x.x.x.x", "src_ip_mask", 24, ...}, ... ] + Note: + See `generate_rule_cmds()` to get list of possible map keys. + """ + actions, rules = {}, [] + _port_pairs = PortPairs(self.vnfd_helper.interfaces) + port_pair_list = _port_pairs.port_pair_list + for src_intf, dst_intf in port_pair_list: + # get port numbers of the interfaces + src_port = self.vnfd_helper.port_num(src_intf) + dst_port = self.vnfd_helper.port_num(dst_intf) + # get interface addresses and prefixes + src_net, src_prefix_len = self.get_network_and_prefixlen_from_ip_of_port(src_intf) + dst_net, dst_prefix_len = self.get_network_and_prefixlen_from_ip_of_port(dst_intf) + # ignore entries with empty values + if all((src_net, src_prefix_len, dst_net, dst_prefix_len)): + # flow: src_net:dst_net -> dst_port + action_id = self.new_action_id + actions[action_id] = self.DEFAULT_FWD_ACTIONS[:] + actions[action_id].append({"fwd": {"port": dst_port}}) + rules.append({"priority": 1, 'cmd': self.RULE_CMD, + "src_ip": src_net, "src_ip_mask": src_prefix_len, + "dst_ip": dst_net, "dst_ip_mask": dst_prefix_len, + "src_port_from": 0, "src_port_to": 65535, + "dst_port_from": 0, "dst_port_to": 65535, + "protocol": 0, "protocol_mask": 0, + "action_id": action_id}) + # flow: dst_net:src_net -> src_port + action_id = self.new_action_id + actions[action_id] = self.DEFAULT_FWD_ACTIONS[:] + actions[action_id].append({"fwd": {"port": src_port}}) + rules.append({"cmd":self.RULE_CMD, "priority": 1, + "src_ip": dst_net, "src_ip_mask": dst_prefix_len, + "dst_ip": src_net, "dst_ip_mask": src_prefix_len, + "src_port_from": 0, "src_port_to": 65535, + "dst_port_from": 0, "dst_port_to": 65535, + "protocol": 0, "protocol_mask": 0, + "action_id": action_id}) + return actions, rules + + def get_flows(self, options): + """Get actions/rules based on provided options. + The `options` is a dict representing the ACL rules configuration + file. Result is the same as described in `get_default_flows()`. + """ + actions, rules = {}, [] + for ace in options['access-list-entries']: + # Generate list of actions + action_id = self.new_action_id + actions[action_id] = ace['actions'] + # Destination nestwork + matches = ace['matches'] + dst_ipv4_net = matches['destination-ipv4-network'] + dst_ipv4_net_ip = ipaddress.ip_interface(six.text_type(dst_ipv4_net)) + # Source network + src_ipv4_net = matches['source-ipv4-network'] + src_ipv4_net_ip = ipaddress.ip_interface(six.text_type(src_ipv4_net)) + # Append the rule + rules.append({'action_id': action_id, 'cmd': self.RULE_CMD, + 'dst_ip': dst_ipv4_net_ip.network.network_address.exploded, + 'dst_ip_mask': dst_ipv4_net_ip.network.prefixlen, + 'src_ip': src_ipv4_net_ip.network.network_address.exploded, + 'src_ip_mask': src_ipv4_net_ip.network.prefixlen, + 'dst_port_from': matches['destination-port-range']['lower-port'], + 'dst_port_to': matches['destination-port-range']['upper-port'], + 'src_port_from': matches['source-port-range']['lower-port'], + 'src_port_to': matches['source-port-range']['upper-port'], + 'priority': matches.get('priority', self.DEFAULT_PRIORITY), + 'protocol': matches.get('protocol', self.DEFAULT_PROTOCOL), + 'protocol_mask': matches.get('protocol_mask', + self.DEFAULT_PROTOCOL_MASK) + }) + return actions, rules + + def generate_rule_cmds(self, rules, apply_rules=False): + """Convert rules into list of SampleVNF CLI commands""" + rule_template = ("p {cmd} add {priority} {src_ip} {src_ip_mask} " + "{dst_ip} {dst_ip_mask} {src_port_from} {src_port_to} " + "{dst_port_from} {dst_port_to} {protocol} " + "{protocol_mask} {action_id}") + rule_cmd_list = [] + for rule in rules: + rule_cmd_list.append(rule_template.format(**rule)) + if apply_rules: + # add command to apply all rules at the end + rule_cmd_list.append("p {cmd} applyruleset".format(cmd=self.RULE_CMD)) + return rule_cmd_list + + def generate_action_cmds(self, actions): + """Convert actions into list of SampleVNF CLI commands. + These method doesn't validate the provided list of actions. Supported + list of actions are limited by SampleVNF. Thus, the user should be + responsible to specify correct action name(s). Yardstick should take + the provided action by user and apply it to SampleVNF. + Anyway, some of the actions require addition parameters to be + specified. In case of `fwd` & `nat` action used have to specify + the port attribute. + """ + _action_template_map = { + "fwd": "p action add {action_id} fwd {port}", + "nat": "p action add {action_id} nat {port}" + } + action_cmd_list = [] + for action_id, actions in actions.items(): + for action in actions: + if isinstance(action, dict): + for action_name in action.keys(): + # user provided an action name with addition options + # e.g.: {"fwd": {"port": 0}} + # format action CLI command and add it to the list + if action_name not in _action_template_map.keys(): + raise exceptions.AclUknownActionTemplate( + action_name=action_name) + template = _action_template_map[action_name] + try: + action_cmd_list.append(template.format( + action_id=action_id, **action[action_name])) + except KeyError as exp: + raise exceptions.AclMissingActionArguments( + action_name=action_name, + action_param=exp.args[0]) + else: + # user provided an action name w/o addition options + # e.g.: "accept", "count" + action_cmd_list.append( + "p action add {action_id} {action}".format( + action_id=action_id, action=action)) + return action_cmd_list + + def get_flows_config(self, options=None): + """Get action/rules configuration commands (string) to be + applied to SampleVNF to configure ACL rules (flows). + """ + action_cmd_list, rule_cmd_list = [], [] + if options: + # if file name is set, read actions/rules from the file + actions, rules = self.get_flows(options) + action_cmd_list = self.generate_action_cmds(actions) + rule_cmd_list = self.generate_rule_cmds(rules) + # default actions/rules + dft_actions, dft_rules = self.get_default_flows() + dft_action_cmd_list = self.generate_action_cmds(dft_actions) + dft_rule_cmd_list = self.generate_rule_cmds(dft_rules, apply_rules=True) + # generate multi-line commands to add actions/rules + return '\n'.join(chain(action_cmd_list, dft_action_cmd_list, + rule_cmd_list, dft_rule_cmd_list)) class AclApproxVnf(SampleVNF): @@ -57,12 +251,3 @@ class AclApproxVnf(SampleVNF): setup_env_helper_type = AclApproxSetupEnvSetupEnvHelper super(AclApproxVnf, self).__init__(name, vnfd, setup_env_helper_type, resource_helper_type) - self.acl_rules = None - - def _start_vnf(self): - yang_model_path = utils.find_relative_file( - self.scenario_helper.options['rules'], - self.scenario_helper.task_path) - yang_model = YangModel(yang_model_path) - self.acl_rules = yang_model.get_rules() - super(AclApproxVnf, self)._start_vnf() diff --git a/yardstick/network_services/vnf_generic/vnf/sample_vnf.py b/yardstick/network_services/vnf_generic/vnf/sample_vnf.py index ef8b3f126..653603da8 100644 --- a/yardstick/network_services/vnf_generic/vnf/sample_vnf.py +++ b/yardstick/network_services/vnf_generic/vnf/sample_vnf.py @@ -32,6 +32,7 @@ from yardstick.benchmark.contexts.base import Context from yardstick.common import exceptions as y_exceptions from yardstick.common.process import check_if_process_failed from yardstick.common import utils +from yardstick.common import yaml_loader from yardstick.network_services import constants from yardstick.network_services.helpers.dpdkbindnic_helper import DpdkBindHelper, DpdkNode from yardstick.network_services.helpers.samplevnf_helper import MultiPortConfig @@ -144,6 +145,13 @@ class DpdkVnfSetupEnvHelper(SetupEnvHelper): 'vnf_type': self.VNF_TYPE, } + # read actions/rules from file + acl_options = None + acl_file_name = self.scenario_helper.options.get('rules') + if acl_file_name: + with utils.open_relative_file(acl_file_name, task_path) as infile: + acl_options = yaml_loader.yaml_load(infile) + config_tpl_cfg = utils.find_relative_file(self.DEFAULT_CONFIG_TPL_CFG, task_path) config_basename = posixpath.basename(self.CFG_CONFIG) @@ -176,12 +184,17 @@ class DpdkVnfSetupEnvHelper(SetupEnvHelper): new_config = self._update_packet_type(new_config, traffic_options) self.ssh_helper.upload_config_file(config_basename, new_config) self.ssh_helper.upload_config_file(script_basename, - multiport.generate_script(self.vnfd_helper)) + multiport.generate_script(self.vnfd_helper, + self.get_flows_config(acl_options))) LOG.info("Provision and start the %s", self.APP_NAME) self._build_pipeline_kwargs() return self.PIPELINE_COMMAND.format(**self.pipeline_kwargs) + def get_flows_config(self, options=None): # pylint: disable=unused-argument + """No actions/rules (flows) by default""" + return None + def _build_pipeline_kwargs(self): tool_path = self.ssh_helper.provision_tool(tool_file=self.APP_NAME) # count the number of actual ports in the list of pairs diff --git a/yardstick/network_services/vnf_generic/vnf/vfw_vnf.py b/yardstick/network_services/vnf_generic/vnf/vfw_vnf.py index 3ba1f91b7..432f30a0c 100644 --- a/yardstick/network_services/vnf_generic/vnf/vfw_vnf.py +++ b/yardstick/network_services/vnf_generic/vnf/vfw_vnf.py @@ -14,9 +14,8 @@ import logging -from yardstick.common import utils -from yardstick.network_services.vnf_generic.vnf.sample_vnf import SampleVNF, DpdkVnfSetupEnvHelper -from yardstick.network_services.yang_model import YangModel +from yardstick.network_services.vnf_generic.vnf.sample_vnf import SampleVNF +from yardstick.network_services.vnf_generic.vnf.acl_vnf import AclApproxSetupEnvSetupEnvHelper LOG = logging.getLogger(__name__) @@ -27,7 +26,7 @@ FW_COLLECT_KPI = (r"""VFW TOTAL:[^p]+pkts_received"?:\s(\d+),[^p]+pkts_fw_forwar r"""[^p]+pkts_drop_fw"?:\s(\d+),\s""") -class FWApproxSetupEnvHelper(DpdkVnfSetupEnvHelper): +class FWApproxSetupEnvHelper(AclApproxSetupEnvSetupEnvHelper): APP_NAME = "vFW" CFG_CONFIG = "/tmp/vfw_config" @@ -37,6 +36,8 @@ class FWApproxSetupEnvHelper(DpdkVnfSetupEnvHelper): SW_DEFAULT_CORE = 5 HW_DEFAULT_CORE = 2 VNF_TYPE = "VFW" + RULE_CMD = "vfw" + DEFAULT_FWD_ACTIONS = ["accept", "count", "conntrack"] class FWApproxVnf(SampleVNF): @@ -56,12 +57,3 @@ class FWApproxVnf(SampleVNF): setup_env_helper_type = FWApproxSetupEnvHelper super(FWApproxVnf, self).__init__(name, vnfd, setup_env_helper_type, resource_helper_type) - self.vfw_rules = None - - def _start_vnf(self): - yang_model_path = utils.find_relative_file( - self.scenario_helper.options['rules'], - self.scenario_helper.task_path) - yang_model = YangModel(yang_model_path) - self.vfw_rules = yang_model.get_rules() - super(FWApproxVnf, self)._start_vnf() diff --git a/yardstick/network_services/vnf_generic/vnf/vnf_ssh_helper.py b/yardstick/network_services/vnf_generic/vnf/vnf_ssh_helper.py index de6fd9329..6c5c6c833 100644 --- a/yardstick/network_services/vnf_generic/vnf/vnf_ssh_helper.py +++ b/yardstick/network_services/vnf_generic/vnf/vnf_ssh_helper.py @@ -47,6 +47,7 @@ class VnfSshHelper(AutoConnectSSH): def upload_config_file(self, prefix, content): cfg_file = os.path.join(constants.REMOTE_TMP, prefix) + LOG.debug('Config file name: %s', cfg_file) LOG.debug(content) file_obj = StringIO(content) self.put_file_obj(file_obj, cfg_file) diff --git a/yardstick/network_services/yang_model.py b/yardstick/network_services/yang_model.py deleted file mode 100644 index ec00c4513..000000000 --- a/yardstick/network_services/yang_model.py +++ /dev/null @@ -1,108 +0,0 @@ -# Copyright (c) 2017 Intel Corporation -# -# 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 __future__ import absolute_import -from __future__ import print_function -import logging -import ipaddress -import six - -from yardstick.common.yaml_loader import yaml_load - -LOG = logging.getLogger(__name__) - - -class YangModel(object): - - RULE_TEMPLATE = "p acl add 1 {0} {1} {2} {3} {4} {5} {6} {7} 0 0 {8}" - - def __init__(self, config_file): - super(YangModel, self).__init__() - self._config_file = config_file - self._options = {} - self._rules = '' - - @property - def config_file(self): - return self._config_file - - @config_file.setter - def config_file(self, value): - self._config_file = value - self._options = {} - self._rules = '' - - def _read_config(self): - # TODO: add some error handling in case of empty or non-existing file - try: - with open(self._config_file) as f: - self._options = yaml_load(f) - except Exception as e: - LOG.exception("Failed to load the yaml %s", e) - raise - - def _get_entries(self): - if not self._options: - return '' - - rule_list = [] - for ace in self._options['access-list1']['acl']['access-list-entries']: - # TODO: resolve ports using topology file and nodes' - # ids: public or private. - matches = ace['ace']['matches'] - dst_ipv4_net = matches['destination-ipv4-network'] - dst_ipv4_net_ip = ipaddress.ip_interface(six.text_type(dst_ipv4_net)) - port0_local_network = dst_ipv4_net_ip.network.network_address.exploded - port0_prefix = dst_ipv4_net_ip.network.prefixlen - - src_ipv4_net = matches['source-ipv4-network'] - src_ipv4_net_ip = ipaddress.ip_interface(six.text_type(src_ipv4_net)) - port1_local_network = src_ipv4_net_ip.network.network_address.exploded - port1_prefix = src_ipv4_net_ip.network.prefixlen - - lower_dport = matches['destination-port-range']['lower-port'] - upper_dport = matches['destination-port-range']['upper-port'] - - lower_sport = matches['source-port-range']['lower-port'] - upper_sport = matches['source-port-range']['upper-port'] - - # TODO: proto should be read from file also. - # Now all rules in sample ACL file are TCP. - rule_list.append('') # get an extra new line - rule_list.append(self.RULE_TEMPLATE.format(port0_local_network, - port0_prefix, - port1_local_network, - port1_prefix, - lower_dport, - upper_dport, - lower_sport, - upper_sport, - 0)) - rule_list.append(self.RULE_TEMPLATE.format(port1_local_network, - port1_prefix, - port0_local_network, - port0_prefix, - lower_sport, - upper_sport, - lower_dport, - upper_dport, - 1)) - - self._rules = '\n'.join(rule_list) - - def get_rules(self): - if not self._rules: - self._read_config() - self._get_entries() - return self._rules diff --git a/yardstick/tests/unit/network_services/helpers/test_samplevnf_helper.py b/yardstick/tests/unit/network_services/helpers/test_samplevnf_helper.py index 6d5e1da60..e66e7fbb8 100644 --- a/yardstick/tests/unit/network_services/helpers/test_samplevnf_helper.py +++ b/yardstick/tests/unit/network_services/helpers/test_samplevnf_helper.py @@ -223,7 +223,7 @@ class TestMultiPortConfig(unittest.TestCase): mock.Mock(return_value={'link_config': 0, 'arp_config': '', 'arp_config6': '', 'actions': '', 'arp_route_tbl': '', 'arp_route_tbl6': '', - 'rules': ''}) + 'flows': ''}) opnfv_vnf.port_pair_list = [("xe0", "xe1")] self.assertIsNotNone(opnfv_vnf.generate_script(self.VNFD)) opnfv_vnf.lb_config = 'HW' @@ -249,66 +249,6 @@ class TestMultiPortConfig(unittest.TestCase): opnfv_vnf.generate_rule_config = mock.Mock() self.assertIsNotNone(opnfv_vnf.generate_script_data()) - def test_generate_rule_config(self): - topology_file = mock.Mock() - config_tpl = mock.Mock() - tmp_file = mock.Mock() - vnfd_mock = mock.MagicMock() - opnfv_vnf = samplevnf_helper.MultiPortConfig( - topology_file, config_tpl, tmp_file, vnfd_mock) - opnfv_vnf.get_config_tpl_data = mock.MagicMock() - opnfv_vnf.socket = 0 - opnfv_vnf.start_core = 0 - opnfv_vnf.update_write_parser = mock.MagicMock() - opnfv_vnf.generate_script_data = \ - mock.Mock(return_value={'link_config': 0, 'arp_config': '', - 'arp_config6': '', 'actions': '', - 'rules': ''}) - opnfv_vnf.port_pair_list = [("xe0", "xe1")] - opnfv_vnf.get_port_pairs = mock.Mock() - opnfv_vnf.vnf_type = 'ACL' - opnfv_vnf.get_ports_gateway = mock.Mock(return_value=u'1.1.1.1') - opnfv_vnf.get_netmask_gateway = mock.Mock( - return_value=u'255.255.255.0') - opnfv_vnf.get_ports_gateway6 = mock.Mock(return_value=u'1.1.1.1') - opnfv_vnf.get_netmask_gateway6 = mock.Mock( - return_value=u'255.255.255.0') - opnfv_vnf.txrx_pipeline = '' - opnfv_vnf.vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] - opnfv_vnf.interfaces = opnfv_vnf.vnfd['vdu'][0]['external-interface'] - opnfv_vnf.rules = '' - self.assertIsNotNone(opnfv_vnf.generate_rule_config()) - opnfv_vnf.rules = 'new' - self.assertIsNotNone(opnfv_vnf.generate_rule_config()) - - def test_generate_action_config(self): - topology_file = mock.Mock() - config_tpl = mock.Mock() - tmp_file = mock.Mock() - vnfd_mock = mock.MagicMock() - opnfv_vnf = samplevnf_helper.MultiPortConfig( - topology_file, config_tpl, tmp_file, vnfd_mock) - opnfv_vnf.get_config_tpl_data = mock.MagicMock() - opnfv_vnf.socket = 0 - opnfv_vnf.start_core = 0 - opnfv_vnf.update_write_parser = mock.MagicMock() - opnfv_vnf.generate_script_data = \ - mock.Mock(return_value={'link_config': 0, 'arp_config': '', - 'arp_config6': '', 'actions': '', - 'rules': ''}) - opnfv_vnf.port_pair_list = [("xe0", "xe1")] - opnfv_vnf.get_port_pairs = mock.Mock() - opnfv_vnf.vnf_type = 'VFW' - opnfv_vnf.get_ports_gateway = mock.Mock(return_value=u'1.1.1.1') - opnfv_vnf.get_netmask_gateway = mock.Mock( - return_value=u'255.255.255.0') - opnfv_vnf.get_ports_gateway6 = mock.Mock(return_value=u'1.1.1.1') - opnfv_vnf.get_netmask_gateway6 = mock.Mock( - return_value=u'255.255.255.0') - opnfv_vnf.txrx_pipeline = '' - opnfv_vnf.rules = '' - self.assertIsNotNone(opnfv_vnf.generate_action_config()) - def test_generate_arp_config6(self): topology_file = mock.Mock() config_tpl = mock.Mock() diff --git a/yardstick/tests/unit/network_services/test_yang_model.py b/yardstick/tests/unit/network_services/test_yang_model.py deleted file mode 100644 index cbeb3a1f2..000000000 --- a/yardstick/tests/unit/network_services/test_yang_model.py +++ /dev/null @@ -1,129 +0,0 @@ -# Copyright (c) 2016-2017 Intel Corporation -# -# 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 mock -import unittest - -from yardstick.network_services.yang_model import YangModel - - -class YangModelTestCase(unittest.TestCase): - """Test all Yang Model methods.""" - - ENTRIES = { - 'access-list1': { - 'acl': { - 'access-list-entries': [{ - 'ace': { - 'ace-oper-data': { - 'match-counter': 0}, - 'actions': 'drop,count', - 'matches': { - 'destination-ipv4-network': - '152.16.40.20/24', - 'destination-port-range': { - 'lower-port': 0, - 'upper-port': 65535}, - 'source-ipv4-network': '0.0.0.0/0', - 'source-port-range': { - 'lower-port': 0, - 'upper-port': 65535}}, - 'rule-name': 'rule1588'}}, - { - 'ace': { - 'ace-oper-data': { - 'match-counter': 0}, - 'actions': 'drop,count', - 'matches': { - 'destination-ipv4-network': - '0.0.0.0/0', - 'destination-port-range': { - 'lower-port': 0, - 'upper-port': 65535}, - 'source-ipv4-network': - '152.16.100.20/24', - 'source-port-range': { - 'lower-port': 0, - 'upper-port': 65535}}, - 'rule-name': 'rule1589'}}], - 'acl-name': 'sample-ipv4-acl', - 'acl-type': 'ipv4-acl'} - } - } - - def test__init__(self): - cfg = "yang.yaml" - y = YangModel(cfg) - self.assertEqual(y.config_file, cfg) - - def test_config_file_setter(self): - cfg = "yang.yaml" - y = YangModel(cfg) - self.assertEqual(y.config_file, cfg) - cfg2 = "yang2.yaml" - y.config_file = cfg2 - self.assertEqual(y.config_file, cfg2) - - def test__get_entries(self): - cfg = "yang.yaml" - y = YangModel(cfg) - y._options = self.ENTRIES - y._get_entries() - self.assertIn("p acl add", y._rules) - - def test__get_entries_no_options(self): - cfg = "yang.yaml" - y = YangModel(cfg) - y._get_entries() - self.assertEqual(y._rules, '') - - @mock.patch('yardstick.network_services.yang_model.open') - @mock.patch('yardstick.network_services.yang_model.yaml_load') - def test__read_config(self, mock_safe_load, *args): - cfg = "yang.yaml" - y = YangModel(cfg) - mock_safe_load.return_value = expected = {'key1': 'value1', 'key2': 'value2'} - y._read_config() - self.assertDictEqual(y._options, expected) - - @mock.patch('yardstick.network_services.yang_model.open') - def test__read_config_open_error(self, mock_open): - cfg = "yang.yaml" - y = YangModel(cfg) - mock_open.side_effect = IOError('my error') - - self.assertEqual(y._options, {}) - with self.assertRaises(IOError) as raised: - y._read_config() - - self.assertIn('my error', str(raised.exception)) - self.assertEqual(y._options, {}) - - def test_get_rules(self): - cfg = "yang.yaml" - y = YangModel(cfg) - y._read_config = read_mock = mock.Mock() - y._get_entries = get_mock = mock.Mock() - - y._rules = None - self.assertIsNone(y.get_rules()) - read_mock.assert_called_once() - get_mock.assert_called_once() - - # True value should prevent calling read and get - y._rules = 999 - self.assertEqual(y.get_rules(), 999) - read_mock.assert_called_once() - get_mock.assert_called_once() diff --git a/yardstick/tests/unit/network_services/vnf_generic/vnf/test_acl_vnf.py b/yardstick/tests/unit/network_services/vnf_generic/vnf/test_acl_vnf.py index f75fa226a..ce32a31e2 100644 --- a/yardstick/tests/unit/network_services/vnf_generic/vnf/test_acl_vnf.py +++ b/yardstick/tests/unit/network_services/vnf_generic/vnf/test_acl_vnf.py @@ -16,10 +16,13 @@ import unittest import mock import os +import re +import copy from yardstick.tests import STL_MOCKS from yardstick.tests.unit.network_services.vnf_generic.vnf.test_base import mock_ssh from yardstick.common import utils +from yardstick.common import exceptions STLClient = mock.MagicMock() @@ -28,6 +31,7 @@ stl_patch.start() if stl_patch: from yardstick.network_services.vnf_generic.vnf.acl_vnf import AclApproxVnf + from yardstick.network_services.vnf_generic.vnf.base import VnfdHelper from yardstick.network_services.nfvi.resource import ResourceProfile from yardstick.network_services.vnf_generic.vnf.acl_vnf import AclApproxSetupEnvSetupEnvHelper @@ -311,7 +315,6 @@ class TestAclApproxVnf(unittest.TestCase): acl_approx_vnf._run() acl_approx_vnf.ssh_helper.run.assert_called_once() - @mock.patch("yardstick.network_services.vnf_generic.vnf.acl_vnf.YangModel") @mock.patch.object(utils, 'find_relative_file') @mock.patch("yardstick.network_services.vnf_generic.vnf.sample_vnf.Context") @mock.patch(SSH_HELPER) @@ -350,6 +353,145 @@ class TestAclApproxVnf(unittest.TestCase): class TestAclApproxSetupEnvSetupEnvHelper(unittest.TestCase): + ACL_CONFIG = {"access-list-entries": [{ + "actions": [ + "count", + {"fwd": { + "port": 0 + } + } + ], + "matches": { + "destination-ipv4-network": "152.16.0.0/24", + "destination-port-range": { + "lower-port": 0, + "upper-port": 65535 + }, + "source-ipv4-network": "0.0.0.0/0", + "source-port-range": { + "lower-port": 0, + "upper-port": 65535 + }, + "protocol-mask": 255, + "protocol": 127, + "priority": 1 + }, + "rule-name": "rule1588" + } + ]} + + def test_get_default_flows(self): + """Check if default ACL SampleVNF CLI commands are + generated correctly""" + ssh_helper = mock.Mock() + vnfd_helper = VnfdHelper({'vdu': [ + {'external-interface': [ + { + 'virtual-interface': { + 'local_ip': '152.16.100.19', + 'netmask': '255.255.255.0', + 'dpdk_port_num': 0, + 'dst_ip': '152.16.100.20', + 'vld_id': 'uplink_0', + 'ifname': 'xe0', + }, + 'vnfd-connection-point-ref': 'xe0', + 'name': 'xe0' + }, + { + 'virtual-interface': { + 'local_ip': '152.16.40.19', + 'netmask': '255.255.255.0', + 'dpdk_port_num': 1, + 'dst_ip': '152.16.40.20', + 'vld_id': 'downlink_0', + 'ifname': 'xe1', + }, + 'vnfd-connection-point-ref': 'xe1', + 'name': 'xe1' + } + ]} + ]}) + setup_helper = AclApproxSetupEnvSetupEnvHelper(vnfd_helper, ssh_helper, None) + self.check_acl_commands(setup_helper.get_flows_config(), [ + # format: (, ) + ("^p action add [0-9]+ accept$", 2), + ("^p action add [0-9]+ count$", 2), + ("^p action add [0-9]+ fwd 1$", 1), + ("^p action add [0-9]+ fwd 0$", 1), + ("^p acl add 1 152.16.100.0 24 152.16.40.0 24 0 65535 0 65535 0 0 [0-9]+$", 1), + ("^p acl add 1 152.16.40.0 24 152.16.100.0 24 0 65535 0 65535 0 0 [0-9]+$", 1), + ("^p acl applyruleset$", 1) + ]) + + @mock.patch.object(AclApproxSetupEnvSetupEnvHelper, 'get_default_flows') + def test_get_flows_config(self, get_default_flows): + """Check if provided ACL config can be converted to + ACL SampleVNF CLI commands correctly""" + ssh_helper = mock.Mock() + setup_helper = AclApproxSetupEnvSetupEnvHelper(None, ssh_helper, None) + get_default_flows.return_value = ({}, []) + self.check_acl_commands(setup_helper.get_flows_config(self.ACL_CONFIG), [ + # format: (, ) + ("^p action add [0-9]+ count$", 1), + ("^p action add [0-9]+ fwd 0$", 1), + ("^p acl add 1 0.0.0.0 0 152.16.0.0 24 0 65535 0 65535 127 0 [0-9]+$", 1), + ("^p acl applyruleset$", 1) + ]) + + @mock.patch.object(AclApproxSetupEnvSetupEnvHelper, 'get_default_flows') + def test_get_flows_config_invalid_action(self, get_default_flows): + """Check if incorrect ACL config fails to convert + to ACL SampleVNF CLI commands""" + ssh_helper = mock.Mock() + setup_helper = AclApproxSetupEnvSetupEnvHelper(None, ssh_helper, None) + get_default_flows.return_value = ({}, []) + # duplicate config and add invald action + acl_config = copy.deepcopy(self.ACL_CONFIG) + acl_config['access-list-entries'][0]["actions"].append({"xnat": {}}) + self.assertRaises(exceptions.AclUknownActionTemplate, + setup_helper.get_flows_config, acl_config) + + @mock.patch.object(AclApproxSetupEnvSetupEnvHelper, 'get_default_flows') + def test_get_flows_config_invalid_action_param(self, get_default_flows): + """Check if ACL config with invalid action parameter fails to convert + to ACL SampleVNF CLI commands""" + ssh_helper = mock.Mock() + setup_helper = AclApproxSetupEnvSetupEnvHelper(None, ssh_helper, None) + get_default_flows.return_value = ({}, []) + # duplicate config and add action with invalid parameter + acl_config = copy.deepcopy(self.ACL_CONFIG) + acl_config['access-list-entries'][0]["actions"].append( + {"nat": {"xport": 0}}) + self.assertRaises(exceptions.AclMissingActionArguments, + setup_helper.get_flows_config, acl_config) + + def check_acl_commands(self, config, expected_cli_patterns): + """Check if expected ACL CLI commands (given as a list of patterns, + `expected_cli_patterns` parameter) present in SampleVNF ACL + configuration (given as a multiline string, `config` parameter)""" + # Example of expected config: + # --------------------------- + # p action add 1 accept + # p action add 1 fwd 1 + # p action add 2 accept + # p action add 2 count + # p action add 2 fwd 0 + # p acl add 1 152.16.100.0 24 152.16.40.0 24 0 65535 0 65535 0 0 1 + # p acl add 1 152.16.40.0 24 152.16.100.0 24 0 65535 0 65535 0 0 2 + # p acl applyruleset + # --------------------------- + # NOTE: The config above consists of actions ids, which are actually + # unknown (generated at runtime), thus it's incorrect just to compare + # the example ACL config above with the configuration returned by + # get_flows_config() function. It's more correct to use CLI patterns + # (RE) to find the required SampleVNF CLI commands in the multiline + # string (SampleVNF ACL configuration). + for pattern, num_of_match in expected_cli_patterns: + # format: (, ) + result = re.findall(pattern, config, re.MULTILINE) + self.assertEqual(len(result), num_of_match) + @mock.patch('yardstick.network_services.vnf_generic.vnf.sample_vnf.open') @mock.patch.object(utils, 'find_relative_file') @mock.patch('yardstick.network_services.vnf_generic.vnf.sample_vnf.MultiPortConfig') @@ -359,14 +501,17 @@ class TestAclApproxSetupEnvSetupEnvHelper(unittest.TestCase): ssh_helper = mock.Mock() scenario_helper = mock.Mock() scenario_helper.vnf_cfg = {'lb_config': 'HW'} + scenario_helper.options = {} scenario_helper.all_options = {} acl_approx_setup_helper = AclApproxSetupEnvSetupEnvHelper(vnfd_helper, ssh_helper, scenario_helper) + acl_approx_setup_helper.get_flows_config = mock.Mock() acl_approx_setup_helper.ssh_helper.provision_tool = mock.Mock(return_value='tool_path') acl_approx_setup_helper.ssh_helper.all_ports = mock.Mock() acl_approx_setup_helper.vnfd_helper.port_nums = mock.Mock(return_value=[0, 1]) expected = 'sudo tool_path -p 0x3 -f /tmp/acl_config -s /tmp/acl_script --hwlb 3' self.assertEqual(acl_approx_setup_helper.build_config(), expected) + acl_approx_setup_helper.get_flows_config.assert_called_once() diff --git a/yardstick/tests/unit/network_services/vnf_generic/vnf/test_cgnapt_vnf.py b/yardstick/tests/unit/network_services/vnf_generic/vnf/test_cgnapt_vnf.py index bd8f53e21..d2dc69905 100644 --- a/yardstick/tests/unit/network_services/vnf_generic/vnf/test_cgnapt_vnf.py +++ b/yardstick/tests/unit/network_services/vnf_generic/vnf/test_cgnapt_vnf.py @@ -87,6 +87,7 @@ link 1 up ssh_helper = mock.Mock() scenario_helper = mock.Mock() scenario_helper.vnf_cfg = {'lb_config': 'HW'} + scenario_helper.options = {} scenario_helper.all_options = {} cgnat_approx_setup_helper = CgnaptApproxSetupEnvHelper(vnfd_helper, diff --git a/yardstick/tests/unit/network_services/vnf_generic/vnf/test_sample_vnf.py b/yardstick/tests/unit/network_services/vnf_generic/vnf/test_sample_vnf.py index 603f05b97..7ee2005ba 100644 --- a/yardstick/tests/unit/network_services/vnf_generic/vnf/test_sample_vnf.py +++ b/yardstick/tests/unit/network_services/vnf_generic/vnf/test_sample_vnf.py @@ -572,6 +572,7 @@ class TestDpdkVnfSetupEnvHelper(unittest.TestCase): ssh_helper = mock.Mock() scenario_helper = mock.Mock() scenario_helper.vnf_cfg = {} + scenario_helper.options = {} scenario_helper.all_options = {} dpdk_setup_helper = DpdkVnfSetupEnvHelper(vnfd_helper, ssh_helper, scenario_helper) @@ -583,6 +584,7 @@ class TestDpdkVnfSetupEnvHelper(unittest.TestCase): mock_multi_port_config.generate_config.assert_called() mock_multi_port_config.generate_script.assert_called() + scenario_helper.options = {'rules': 'fake_file'} scenario_helper.vnf_cfg = {'file': 'fake_file'} dpdk_setup_helper = DpdkVnfSetupEnvHelper(vnfd_helper, ssh_helper, scenario_helper) mock_open_rf.side_effect = mock.mock_open(read_data='fake_data') @@ -590,7 +592,7 @@ class TestDpdkVnfSetupEnvHelper(unittest.TestCase): result = dpdk_setup_helper.build_config() - mock_open_rf.assert_called_once() + mock_open_rf.assert_called() self.assertEqual(result, expected) self.assertGreaterEqual(ssh_helper.upload_config_file.call_count, 2) mock_find.assert_called() diff --git a/yardstick/tests/unit/network_services/vnf_generic/vnf/test_vfw_vnf.py b/yardstick/tests/unit/network_services/vnf_generic/vnf/test_vfw_vnf.py index ffb5cd6f0..a0a0794ea 100644 --- a/yardstick/tests/unit/network_services/vnf_generic/vnf/test_vfw_vnf.py +++ b/yardstick/tests/unit/network_services/vnf_generic/vnf/test_vfw_vnf.py @@ -334,7 +334,6 @@ pipeline> vfw_approx_vnf.ssh_helper.run.assert_called_once() @mock.patch.object(utils, 'find_relative_file') - @mock.patch("yardstick.network_services.vnf_generic.vnf.vfw_vnf.YangModel") @mock.patch("yardstick.network_services.vnf_generic.vnf.sample_vnf.Context") @mock.patch(SSH_HELPER) def test_instantiate(self, ssh, *args): @@ -363,12 +362,15 @@ class TestFWApproxSetupEnvHelper(unittest.TestCase): ssh_helper = mock.Mock() scenario_helper = mock.Mock() scenario_helper.vnf_cfg = {'lb_config': 'HW'} + scenario_helper.options = {} scenario_helper.all_options = {} vfw_approx_setup_helper = FWApproxSetupEnvHelper(vnfd_helper, ssh_helper, scenario_helper) + vfw_approx_setup_helper.get_flows_config = mock.Mock() vfw_approx_setup_helper.ssh_helper.provision_tool = mock.Mock(return_value='tool_path') vfw_approx_setup_helper.ssh_helper.all_ports = mock.Mock() vfw_approx_setup_helper.vnfd_helper.port_nums = mock.Mock(return_value=[0, 1]) expected = 'sudo tool_path -p 0x3 -f /tmp/vfw_config -s /tmp/vfw_script --hwlb 3' self.assertEqual(vfw_approx_setup_helper.build_config(), expected) + vfw_approx_setup_helper.get_flows_config.assert_called_once() -- cgit 1.2.3-korg