Merge "Change "KubernetesObject" class name to "ReplicationController""
[yardstick.git] / yardstick / network_services / vnf_generic / vnf / acl_vnf.py
1 # Copyright (c) 2016-2017 Intel Corporation
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #      http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 import logging
16 import ipaddress
17 import six
18 from yardstick.common import utils
19 from yardstick.common import exceptions
20
21 from yardstick.network_services.vnf_generic.vnf.sample_vnf import SampleVNF, DpdkVnfSetupEnvHelper
22 from yardstick.network_services.helpers.samplevnf_helper import PortPairs
23 from itertools import chain
24
25 LOG = logging.getLogger(__name__)
26
27 # ACL should work the same on all systems, we can provide the binary
28 ACL_PIPELINE_COMMAND = \
29     'sudo {tool_path} -p {port_mask_hex} -f {cfg_file} -s {script} {hwlb}'
30
31 ACL_COLLECT_KPI = r"""\
32 ACL TOTAL:[^p]+pkts_processed"?:\s(\d+),[^p]+pkts_drop"?:\s(\d+),[^p]+pkts_received"?:\s(\d+),"""
33
34
35 class AclApproxSetupEnvSetupEnvHelper(DpdkVnfSetupEnvHelper):
36
37     APP_NAME = "vACL"
38     CFG_CONFIG = "/tmp/acl_config"
39     CFG_SCRIPT = "/tmp/acl_script"
40     PIPELINE_COMMAND = ACL_PIPELINE_COMMAND
41     HW_DEFAULT_CORE = 2
42     SW_DEFAULT_CORE = 5
43     DEFAULT_CONFIG_TPL_CFG = "acl.cfg"
44     VNF_TYPE = "ACL"
45     RULE_CMD = "acl"
46
47     DEFAULT_PRIORITY = 1
48     DEFAULT_PROTOCOL = 0
49     DEFAULT_PROTOCOL_MASK = 0
50     # Default actions to be applied to SampleVNF. Please note,
51     # that this list is extended with `fwd` action when default
52     # actions are generated.
53     DEFAULT_FWD_ACTIONS = ["accept", "count"]
54
55     def __init__(self, vnfd_helper, ssh_helper, scenario_helper):
56         super(AclApproxSetupEnvSetupEnvHelper, self).__init__(vnfd_helper,
57                                                               ssh_helper,
58                                                               scenario_helper)
59         self._action_id = 0
60
61     def get_ip_from_port(self, port):
62         # we can't use gateway because in OpenStack gateways interfere with floating ip routing
63         # return self.make_ip_addr(self.get_ports_gateway(port), self.get_netmask_gateway(port))
64         vintf = self.vnfd_helper.find_interface(name=port)["virtual-interface"]
65         return utils.make_ip_addr(vintf["local_ip"], vintf["netmask"])
66
67     def get_network_and_prefixlen_from_ip_of_port(self, port):
68         ip_addr = self.get_ip_from_port(port)
69         # handle cases with no gateway
70         if ip_addr:
71             return ip_addr.network.network_address.exploded, ip_addr.network.prefixlen
72         else:
73             return None, None
74
75     @property
76     def new_action_id(self):
77         """Get new action id"""
78         self._action_id += 1
79         return self._action_id
80
81     def get_default_flows(self):
82         """Get default actions/rules
83         Returns: (<actions>, <rules>)
84             <actions>:
85                  { <action_id>: [ <list of actions> ]}
86             Example:
87                  { 0 : [ "accept", "count", {"fwd" : "port": 0} ], ... }
88             <rules>:
89                  [ {"src_ip": "x.x.x.x", "src_ip_mask", 24, ...}, ... ]
90             Note:
91                 See `generate_rule_cmds()` to get list of possible map keys.
92         """
93         actions, rules = {}, []
94         _port_pairs = PortPairs(self.vnfd_helper.interfaces)
95         port_pair_list = _port_pairs.port_pair_list
96         for src_intf, dst_intf in port_pair_list:
97             # get port numbers of the interfaces
98             src_port = self.vnfd_helper.port_num(src_intf)
99             dst_port = self.vnfd_helper.port_num(dst_intf)
100             # get interface addresses and prefixes
101             src_net, src_prefix_len = self.get_network_and_prefixlen_from_ip_of_port(src_intf)
102             dst_net, dst_prefix_len = self.get_network_and_prefixlen_from_ip_of_port(dst_intf)
103             # ignore entries with empty values
104             if all((src_net, src_prefix_len, dst_net, dst_prefix_len)):
105                 # flow: src_net:dst_net -> dst_port
106                 action_id = self.new_action_id
107                 actions[action_id] = self.DEFAULT_FWD_ACTIONS[:]
108                 actions[action_id].append({"fwd": {"port": dst_port}})
109                 rules.append({"priority": 1, 'cmd': self.RULE_CMD,
110                     "src_ip": src_net, "src_ip_mask": src_prefix_len,
111                     "dst_ip": dst_net, "dst_ip_mask": dst_prefix_len,
112                     "src_port_from": 0, "src_port_to": 65535,
113                     "dst_port_from": 0, "dst_port_to": 65535,
114                     "protocol": 0, "protocol_mask": 0,
115                     "action_id": action_id})
116                 # flow: dst_net:src_net -> src_port
117                 action_id = self.new_action_id
118                 actions[action_id] = self.DEFAULT_FWD_ACTIONS[:]
119                 actions[action_id].append({"fwd": {"port": src_port}})
120                 rules.append({"cmd":self.RULE_CMD, "priority": 1,
121                     "src_ip": dst_net, "src_ip_mask": dst_prefix_len,
122                     "dst_ip": src_net, "dst_ip_mask": src_prefix_len,
123                     "src_port_from": 0, "src_port_to": 65535,
124                     "dst_port_from": 0, "dst_port_to": 65535,
125                     "protocol": 0, "protocol_mask": 0,
126                     "action_id": action_id})
127         return actions, rules
128
129     def get_flows(self, options):
130         """Get actions/rules based on provided options.
131         The `options` is a dict representing the ACL rules configuration
132         file. Result is the same as described in `get_default_flows()`.
133         """
134         actions, rules = {}, []
135         for ace in options['access-list-entries']:
136             # Generate list of actions
137             action_id = self.new_action_id
138             actions[action_id] = ace['actions']
139             # Destination nestwork
140             matches = ace['matches']
141             dst_ipv4_net = matches['destination-ipv4-network']
142             dst_ipv4_net_ip = ipaddress.ip_interface(six.text_type(dst_ipv4_net))
143             # Source network
144             src_ipv4_net = matches['source-ipv4-network']
145             src_ipv4_net_ip = ipaddress.ip_interface(six.text_type(src_ipv4_net))
146             # Append the rule
147             rules.append({'action_id': action_id, 'cmd': self.RULE_CMD,
148                 'dst_ip': dst_ipv4_net_ip.network.network_address.exploded,
149                 'dst_ip_mask': dst_ipv4_net_ip.network.prefixlen,
150                 'src_ip': src_ipv4_net_ip.network.network_address.exploded,
151                 'src_ip_mask': src_ipv4_net_ip.network.prefixlen,
152                 'dst_port_from': matches['destination-port-range']['lower-port'],
153                 'dst_port_to': matches['destination-port-range']['upper-port'],
154                 'src_port_from': matches['source-port-range']['lower-port'],
155                 'src_port_to': matches['source-port-range']['upper-port'],
156                 'priority': matches.get('priority', self.DEFAULT_PRIORITY),
157                 'protocol': matches.get('protocol', self.DEFAULT_PROTOCOL),
158                 'protocol_mask': matches.get('protocol_mask',
159                                               self.DEFAULT_PROTOCOL_MASK)
160             })
161         return actions, rules
162
163     def generate_rule_cmds(self, rules, apply_rules=False):
164         """Convert rules into list of SampleVNF CLI commands"""
165         rule_template = ("p {cmd} add {priority} {src_ip} {src_ip_mask} "
166                          "{dst_ip} {dst_ip_mask} {src_port_from} {src_port_to} "
167                          "{dst_port_from} {dst_port_to} {protocol} "
168                          "{protocol_mask} {action_id}")
169         rule_cmd_list = []
170         for rule in rules:
171             rule_cmd_list.append(rule_template.format(**rule))
172         if apply_rules:
173             # add command to apply all rules at the end
174             rule_cmd_list.append("p {cmd} applyruleset".format(cmd=self.RULE_CMD))
175         return rule_cmd_list
176
177     def generate_action_cmds(self, actions):
178         """Convert actions into list of SampleVNF CLI commands.
179         These method doesn't validate the provided list of actions. Supported
180         list of actions are limited by SampleVNF. Thus, the user should be
181         responsible to specify correct action name(s). Yardstick should take
182         the provided action by user and apply it to SampleVNF.
183         Anyway, some of the actions require addition parameters to be
184         specified. In case of `fwd` & `nat` action used have to specify
185         the port attribute.
186         """
187         _action_template_map = {
188             "fwd": "p action add {action_id} fwd {port}",
189             "nat": "p action add {action_id} nat {port}"
190         }
191         action_cmd_list = []
192         for action_id, actions in actions.items():
193             for action in actions:
194                 if isinstance(action, dict):
195                     for action_name in action.keys():
196                         # user provided an action name with addition options
197                         # e.g.: {"fwd": {"port": 0}}
198                         # format action CLI command and add it to the list
199                         if action_name not in _action_template_map.keys():
200                             raise exceptions.AclUknownActionTemplate(
201                                 action_name=action_name)
202                         template = _action_template_map[action_name]
203                         try:
204                             action_cmd_list.append(template.format(
205                                 action_id=action_id, **action[action_name]))
206                         except KeyError as exp:
207                             raise exceptions.AclMissingActionArguments(
208                                 action_name=action_name,
209                                 action_param=exp.args[0])
210                 else:
211                     # user provided an action name w/o addition options
212                     # e.g.: "accept", "count"
213                     action_cmd_list.append(
214                         "p action add {action_id} {action}".format(
215                         action_id=action_id, action=action))
216         return action_cmd_list
217
218     def get_flows_config(self, options=None):
219         """Get action/rules configuration commands (string) to be
220         applied to SampleVNF to configure ACL rules (flows).
221         """
222         action_cmd_list, rule_cmd_list = [], []
223         if options:
224             # if file name is set, read actions/rules from the file
225             actions, rules = self.get_flows(options)
226             action_cmd_list = self.generate_action_cmds(actions)
227             rule_cmd_list = self.generate_rule_cmds(rules)
228         # default actions/rules
229         dft_actions, dft_rules = self.get_default_flows()
230         dft_action_cmd_list = self.generate_action_cmds(dft_actions)
231         dft_rule_cmd_list = self.generate_rule_cmds(dft_rules, apply_rules=True)
232         # generate multi-line commands to add actions/rules
233         return '\n'.join(chain(action_cmd_list, dft_action_cmd_list,
234                                rule_cmd_list, dft_rule_cmd_list))
235
236
237 class AclApproxVnf(SampleVNF):
238
239     APP_NAME = "vACL"
240     APP_WORD = 'acl'
241     COLLECT_KPI = ACL_COLLECT_KPI
242
243     COLLECT_MAP = {
244         'packets_in': 3,
245         'packets_fwd': 1,
246         'packets_dropped': 2,
247     }
248
249     def __init__(self, name, vnfd, setup_env_helper_type=None, resource_helper_type=None):
250         if setup_env_helper_type is None:
251             setup_env_helper_type = AclApproxSetupEnvSetupEnvHelper
252
253         super(AclApproxVnf, self).__init__(name, vnfd, setup_env_helper_type, resource_helper_type)