New version of RF tests.
[csit.git] / resources / libraries / python / IPv4Util.py
1 # Copyright (c) 2016 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 """Implements IPv4 RobotFramework keywords"""
15
16 from socket import inet_ntoa
17 from struct import pack
18 from abc import ABCMeta, abstractmethod
19 import copy
20
21 from robot.api import logger as log
22 from robot.api.deco import keyword
23 from robot.utils.asserts import assert_not_equal
24
25 import resources.libraries.python.ssh as ssh
26 from resources.libraries.python.topology import Topology
27 from resources.libraries.python.topology import NodeType
28 from resources.libraries.python.VatExecutor import VatExecutor
29 from resources.libraries.python.TrafficScriptExecutor\
30     import TrafficScriptExecutor
31
32
33 class IPv4Node(object):
34     """Abstract class of a node in a topology."""
35     __metaclass__ = ABCMeta
36
37     def __init__(self, node_info):
38         self.node_info = node_info
39
40     @staticmethod
41     def _get_netmask(prefix_length):
42         bits = 0xffffffff ^ (1 << 32 - prefix_length) - 1
43         return inet_ntoa(pack('>I', bits))
44
45     @abstractmethod
46     def set_ip(self, interface, address, prefix_length):
47         """Configure IPv4 address on interface
48         :param interface: interface name
49         :param address:
50         :param prefix_length:
51         :type interface: str
52         :type address: str
53         :type prefix_length: int
54         :return: nothing
55         """
56         pass
57
58     @abstractmethod
59     def set_interface_state(self, interface, state):
60         """Set interface state
61         :param interface: interface name string
62         :param state: one of following values: "up" or "down"
63         :return: nothing
64         """
65         pass
66
67     @abstractmethod
68     def set_route(self, network, prefix_length, gateway, interface):
69         """Configure IPv4 route
70         :param network: network IPv4 address
71         :param prefix_length: mask length
72         :param gateway: IPv4 address of the gateway
73         :param interface: interface name
74         :type network: str
75         :type prefix_length: int
76         :type gateway: str
77         :type interface: str
78         :return: nothing
79         """
80         pass
81
82     @abstractmethod
83     def unset_route(self, network, prefix_length, gateway, interface):
84         """Remove specified IPv4 route
85         :param network: network IPv4 address
86         :param prefix_length: mask length
87         :param gateway: IPv4 address of the gateway
88         :param interface: interface name
89         :type network: str
90         :type prefix_length: int
91         :type gateway: str
92         :type interface: str
93         :return: nothing
94         """
95         pass
96
97     @abstractmethod
98     def flush_ip_addresses(self, interface):
99         """Flush all IPv4 addresses from specified interface
100         :param interface: interface name
101         :type interface: str
102         :return: nothing
103         """
104         pass
105
106     @abstractmethod
107     def ping(self, destination_address, source_interface):
108         """Send an ICMP request to destination node
109         :param destination_address: address to send the ICMP request
110         :param source_interface:
111         :type destination_address: str
112         :type source_interface: str
113         :return: nothing
114         """
115         pass
116
117
118 class Tg(IPv4Node):
119     """Traffic generator node"""
120     def __init__(self, node_info):
121         super(Tg, self).__init__(node_info)
122
123     def _execute(self, cmd):
124         return ssh.exec_cmd_no_error(self.node_info, cmd)
125
126     def _sudo_execute(self, cmd):
127         return ssh.exec_cmd_no_error(self.node_info, cmd, sudo=True)
128
129     def set_ip(self, interface, address, prefix_length):
130         cmd = 'ip -4 addr flush dev {}'.format(interface)
131         self._sudo_execute(cmd)
132         cmd = 'ip addr add {}/{} dev {}'.format(address, prefix_length,
133                                                 interface)
134         self._sudo_execute(cmd)
135
136     # TODO: not ipv4-specific, move to another class
137     def set_interface_state(self, interface, state):
138         cmd = 'ip link set {} {}'.format(interface, state)
139         self._sudo_execute(cmd)
140
141     def set_route(self, network, prefix_length, gateway, interface):
142         netmask = self._get_netmask(prefix_length)
143         cmd = 'route add -net {} netmask {} gw {}'.\
144             format(network, netmask, gateway)
145         self._sudo_execute(cmd)
146
147     def unset_route(self, network, prefix_length, gateway, interface):
148         self._sudo_execute('ip route delete {}/{}'.
149                            format(network, prefix_length))
150
151     def arp_ping(self, destination_address, source_interface):
152         self._sudo_execute('arping -c 1 -I {} {}'.format(source_interface,
153                                                          destination_address))
154
155     def ping(self, destination_address, source_interface):
156         self._execute('ping -c 1 -w 5 -I {} {}'.format(source_interface,
157                                                        destination_address))
158
159     def flush_ip_addresses(self, interface):
160         self._sudo_execute('ip addr flush dev {}'.format(interface))
161
162
163 class Dut(IPv4Node):
164     """Device under test"""
165     def __init__(self, node_info):
166         super(Dut, self).__init__(node_info)
167
168     def get_sw_if_index(self, interface):
169         """Get sw_if_index of specified interface from current node
170         :param interface: interface name
171         :type interface: str
172         :return: sw_if_index of 'int' type
173         """
174         return Topology().get_interface_sw_index(self.node_info, interface)
175
176     def exec_vat(self, script, **args):
177         """Wrapper for VAT executor.
178         :param script: script to execute
179         :param args: parameters to the script
180         :type script: str
181         :type args: dict
182         :return: nothing
183         """
184         # TODO: check return value
185         VatExecutor.cmd_from_template(self.node_info, script, **args)
186
187     def set_ip(self, interface, address, prefix_length):
188         self.exec_vat('add_ip_address.vat',
189                       sw_if_index=self.get_sw_if_index(interface),
190                       address=address, prefix_length=prefix_length)
191
192     def set_interface_state(self, interface, state):
193         if state == 'up':
194             state = 'admin-up link-up'
195         elif state == 'down':
196             state = 'admin-down link-down'
197         else:
198             raise Exception('Unexpected interface state: {}'.format(state))
199
200         self.exec_vat('set_if_state.vat',
201                       sw_if_index=self.get_sw_if_index(interface), state=state)
202
203     def set_route(self, network, prefix_length, gateway, interface):
204         sw_if_index = self.get_sw_if_index(interface)
205         self.exec_vat('add_route.vat',
206                       network=network, prefix_length=prefix_length,
207                       gateway=gateway, sw_if_index=sw_if_index)
208
209     def unset_route(self, network, prefix_length, gateway, interface):
210         self.exec_vat('del_route.vat', network=network,
211                       prefix_length=prefix_length, gateway=gateway,
212                       sw_if_index=self.get_sw_if_index(interface))
213
214     def arp_ping(self, destination_address, source_interface):
215         pass
216
217     def flush_ip_addresses(self, interface):
218         self.exec_vat('flush_ip_addresses.vat',
219                       sw_if_index=self.get_sw_if_index(interface))
220
221     def ping(self, destination_address, source_interface):
222         pass
223
224
225 def get_node(node_info):
226     """Creates a class instance derived from Node based on type.
227     :param node_info: dictionary containing information on nodes in topology
228     :return: Class instance that is derived from Node
229     """
230     if node_info['type'] == NodeType.TG:
231         return Tg(node_info)
232     elif node_info['type'] == NodeType.DUT:
233         return Dut(node_info)
234     else:
235         raise NotImplementedError('Node type "{}" unsupported!'.
236                                   format(node_info['type']))
237
238
239 def get_node_hostname(node_info):
240     """Get string identifying specifed node.
241     :param node_info: Node in the topology.
242     :type node_info: Dict
243     :return: String identifying node.
244     """
245     return node_info['host']
246
247
248 class IPv4Util(object):
249     """Implements keywords for IPv4 tests."""
250
251     ADDRESSES = {}   # holds configured IPv4 addresses
252     PREFIXES = {}  # holds configured IPv4 addresses' prefixes
253     SUBNETS = {}  # holds configured IPv4 addresses' subnets
254
255     """
256     Helper dictionary used when setting up ipv4 addresses in topology
257
258     Example value:
259     'link1': {  b'port1': {b'addr': b'192.168.3.1'},
260                 b'port2': {b'addr': b'192.168.3.2'},
261                 b'prefix': 24,
262                 b'subnet': b'192.168.3.0'}
263     """
264     topology_helper = None
265
266     @staticmethod
267     def next_address(subnet):
268         """Get next unused IPv4 address from a subnet
269         :param subnet: holds available IPv4 addresses
270         :return: tuple (ipv4_address, prefix_length)
271         """
272         for i in range(1, 4):
273             # build a key and try to get it from address dictionary
274             interface = 'port{}'.format(i)
275             if interface in subnet:
276                 addr = subnet[interface]['addr']
277                 del subnet[interface]
278                 return addr, subnet['prefix']
279         raise Exception('Not enough ipv4 addresses in subnet')
280
281     @staticmethod
282     def next_network(nodes_addr):
283         """Get next unused network from dictionary
284         :param nodes_addr: dictionary of available networks
285         :return: dictionary describing an IPv4 subnet with addresses
286         """
287         assert_not_equal(len(nodes_addr), 0, 'Not enough networks')
288         _, subnet = nodes_addr.popitem()
289         return subnet
290
291     @staticmethod
292     def configure_ipv4_addr_on_node(node, nodes_addr):
293         """Configure IPv4 address for all interfaces on a node in topology
294         :param node: dictionary containing information about node
295         :param nodes_addr: dictionary containing IPv4 addresses
296         :return:
297         """
298         for interface, interface_data in node['interfaces'].iteritems():
299             if interface == 'mgmt':
300                 continue
301             if interface_data['link'] not in IPv4Util.topology_helper:
302                 IPv4Util.topology_helper[interface_data['link']] = \
303                     IPv4Util.next_network(nodes_addr)
304
305             network = IPv4Util.topology_helper[interface_data['link']]
306             address, prefix = IPv4Util.next_address(network)
307
308             get_node(node).set_ip(interface_data['name'], address, prefix)
309             key = (get_node_hostname(node), interface_data['name'])
310             IPv4Util.ADDRESSES[key] = address
311             IPv4Util.PREFIXES[key] = prefix
312             IPv4Util.SUBNETS[key] = network['subnet']
313
314     @staticmethod
315     def nodes_setup_ipv4_addresses(nodes_info, nodes_addr):
316         """Configure IPv4 addresses on all non-management interfaces for each
317         node in nodes_info
318         :param nodes_info: dictionary containing information on all nodes
319         in topology
320         :param nodes_addr: dictionary containing IPv4 addresses
321         :return: nothing
322         """
323         IPv4Util.topology_helper = {}
324         # make a deep copy of nodes_addr because of modifications
325         nodes_addr_copy = copy.deepcopy(nodes_addr)
326         for _, node in nodes_info.iteritems():
327             IPv4Util.configure_ipv4_addr_on_node(node, nodes_addr_copy)
328
329     @staticmethod
330     def nodes_clear_ipv4_addresses(nodes):
331         """Clear all addresses from all nodes in topology
332         :param nodes: dictionary containing information on all nodes
333         :return: nothing
334         """
335         for _, node in nodes.iteritems():
336             for interface, interface_data in node['interfaces'].iteritems():
337                 if interface == 'mgmt':
338                     continue
339                 IPv4Util.flush_ip_addresses(interface_data['name'], node)
340
341     # TODO: not ipv4-specific, move to another class
342     @staticmethod
343     @keyword('Node "${node}" interface "${interface}" is in "${state}" state')
344     def set_interface_state(node, interface, state):
345         """See IPv4Node.set_interface_state for more information.
346         :param node:
347         :param interface:
348         :param state:
349         :return:
350         """
351         log.debug('Node {} interface {} is in {} state'.format(
352             get_node_hostname(node), interface, state))
353         get_node(node).set_interface_state(interface, state)
354
355     @staticmethod
356     @keyword('Node "${node}" interface "${port}" has IPv4 address '
357              '"${address}" with prefix length "${prefix_length}"')
358     def set_interface_address(node, interface, address, length):
359         """See IPv4Node.set_ip for more information.
360         :param node:
361         :param interface:
362         :param address:
363         :param length:
364         :return:
365         """
366         log.debug('Node {} interface {} has IPv4 address {} with prefix '
367                   'length {}'.format(get_node_hostname(node), interface,
368                                      address, length))
369         get_node(node).set_ip(interface, address, int(length))
370         hostname = get_node_hostname(node)
371         IPv4Util.ADDRESSES[hostname, interface] = address
372         IPv4Util.PREFIXES[hostname, interface] = int(length)
373         # TODO: Calculate subnet from ip address and prefix length.
374         # IPv4Util.SUBNETS[hostname, interface] =
375
376     @staticmethod
377     @keyword('From node "${node}" interface "${port}" ARP-ping '
378              'IPv4 address "${ip_address}"')
379     def arp_ping(node, interface, ip_address):
380         log.debug('From node {} interface {} ARP-ping IPv4 address {}'.
381                   format(get_node_hostname(node), interface, ip_address))
382         get_node(node).arp_ping(ip_address, interface)
383
384     @staticmethod
385     @keyword('Node "${node}" routes to IPv4 network "${network}" with prefix '
386              'length "${prefix_length}" using interface "${interface}" via '
387              '"${gateway}"')
388     def set_route(node, network, prefix_length, interface, gateway):
389         """See IPv4Node.set_route for more information.
390         :param node:
391         :param network:
392         :param prefix_length:
393         :param interface:
394         :param gateway:
395         :return:
396         """
397         log.debug('Node {} routes to network {} with prefix length {} '
398                   'via {} interface {}'.format(get_node_hostname(node),
399                                                network, prefix_length,
400                                                gateway, interface))
401         get_node(node).set_route(network, int(prefix_length),
402                                  gateway, interface)
403
404     @staticmethod
405     @keyword('Remove IPv4 route from "${node}" to network "${network}" with '
406              'prefix length "${prefix_length}" interface "${interface}" via '
407              '"${gateway}"')
408     def unset_route(node, network, prefix_length, interface, gateway):
409         """See IPv4Node.unset_route for more information.
410         :param node:
411         :param network:
412         :param prefix_length:
413         :param interface:
414         :param gateway:
415         :return:
416         """
417         get_node(node).unset_route(network, prefix_length, gateway, interface)
418
419     @staticmethod
420     @keyword('After ping is sent from node "${src_node}" interface '
421              '"${src_port}" with destination IPv4 address of node '
422              '"${dst_node}" interface "${dst_port}" a ping response arrives '
423              'and TTL is decreased by "${ttl_dec}"')
424     def send_ping(src_node, src_port, dst_node, dst_port, hops):
425         """Send IPv4 ping and wait for response.
426         :param src_node: Source node.
427         :param src_port: Source interface.
428         :param dst_node: Destination node.
429         :param dst_port: Destination interface.
430         :param hops: Number of hops between src_node and dst_node.
431         """
432         log.debug('After ping is sent from node "{}" interface "{}" '
433                   'with destination IPv4 address of node "{}" interface "{}" '
434                   'a ping response arrives and TTL is decreased by "${}"'.
435                   format(get_node_hostname(src_node), src_port,
436                          get_node_hostname(dst_node), dst_port, hops))
437         node = src_node
438         src_mac = Topology.get_interface_mac(src_node, src_port)
439         if dst_node['type'] == NodeType.TG:
440             dst_mac = Topology.get_interface_mac(src_node, src_port)
441         adj_int = Topology.get_adjacent_interface(src_node, src_port)
442         first_hop_mac = adj_int['mac_address']
443         src_ip = IPv4Util.get_ip_addr(src_node, src_port)
444         dst_ip = IPv4Util.get_ip_addr(dst_node, dst_port)
445         args = '--src_if "{}" --src_mac "{}" --first_hop_mac "{}" ' \
446                '--src_ip "{}" --dst_ip "{}" --hops "{}"'\
447             .format(src_port, src_mac, first_hop_mac, src_ip, dst_ip, hops)
448         if dst_node['type'] == NodeType.TG:
449             args += ' --dst_if "{}" --dst_mac "{}"'.format(dst_port, dst_mac)
450         TrafficScriptExecutor.run_traffic_script_on_node(
451             "ipv4_ping_ttl_check.py", node, args)
452
453     @staticmethod
454     @keyword('Get IPv4 address of node "${node}" interface "${port}"')
455     def get_ip_addr(node, port):
456         """Get IPv4 address configured on specified interface
457         :param node: node dictionary
458         :param port: interface name
459         :return: IPv4 address of specified interface as a 'str' type
460         """
461         log.debug('Get IPv4 address of node {} interface {}'.
462                   format(get_node_hostname(node), port))
463         return IPv4Util.ADDRESSES[(get_node_hostname(node), port)]
464
465     @staticmethod
466     @keyword('Get IPv4 address prefix of node "${node}" interface "${port}"')
467     def get_ip_addr_prefix(node, port):
468         """ Get IPv4 address prefix for specified interface.
469         :param node: Node dictionary.
470         :param port: Interface name.
471         """
472         log.debug('Get IPv4 address prefix of node {} interface {}'.
473                   format(get_node_hostname(node), port))
474         return IPv4Util.PREFIXES[(get_node_hostname(node), port)]
475
476     @staticmethod
477     @keyword('Get IPv4 subnet of node "${node}" interface "${port}"')
478     def get_ip_addr_subnet(node, port):
479         """ Get IPv4 subnet of specified interface.
480         :param node: Node dictionary.
481         :param port: Interface name.
482         """
483         log.debug('Get IPv4 subnet of node {} interface {}'.
484                   format(get_node_hostname(node), port))
485         return IPv4Util.SUBNETS[(get_node_hostname(node), port)]
486
487     @staticmethod
488     @keyword('Flush IPv4 addresses "${port}" "${node}"')
489     def flush_ip_addresses(port, node):
490         """See IPv4Node.flush_ip_addresses for more information.
491         :param port:
492         :param node:
493         :return:
494         """
495         key = (get_node_hostname(node), port)
496         del IPv4Util.ADDRESSES[key]
497         del IPv4Util.PREFIXES[key]
498         del IPv4Util.SUBNETS[key]
499         get_node(node).flush_ip_addresses(port)