Update of latest 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
49         :param interface: interface name
50         :param address:
51         :param prefix_length:
52         :type interface: str
53         :type address: str
54         :type prefix_length: int
55         :return: nothing
56         """
57         pass
58
59     @abstractmethod
60     def set_interface_state(self, interface, state):
61         """Set interface state
62
63         :param interface: interface name string
64         :param state: one of following values: "up" or "down"
65         :return: nothing
66         """
67         pass
68
69     @abstractmethod
70     def set_route(self, network, prefix_length, gateway, interface):
71         """Configure IPv4 route
72
73         :param network: network IPv4 address
74         :param prefix_length: mask length
75         :param gateway: IPv4 address of the gateway
76         :param interface: interface name
77         :type network: str
78         :type prefix_length: int
79         :type gateway: str
80         :type interface: str
81         :return: nothing
82         """
83         pass
84
85     @abstractmethod
86     def unset_route(self, network, prefix_length, gateway, interface):
87         """Remove specified IPv4 route
88
89         :param network: network IPv4 address
90         :param prefix_length: mask length
91         :param gateway: IPv4 address of the gateway
92         :param interface: interface name
93         :type network: str
94         :type prefix_length: int
95         :type gateway: str
96         :type interface: str
97         :return: nothing
98         """
99         pass
100
101     @abstractmethod
102     def flush_ip_addresses(self, interface):
103         """Flush all IPv4 addresses from specified interface
104
105         :param interface: interface name
106         :type interface: str
107         :return: nothing
108         """
109         pass
110
111     @abstractmethod
112     def ping(self, destination_address, source_interface):
113         """Send an ICMP request to destination node
114
115         :param destination_address: address to send the ICMP request
116         :param source_interface:
117         :type destination_address: str
118         :type source_interface: str
119         :return: nothing
120         """
121         pass
122
123
124 class Tg(IPv4Node):
125     """Traffic generator node"""
126     def __init__(self, node_info):
127         super(Tg, self).__init__(node_info)
128
129     def _execute(self, cmd):
130         return ssh.exec_cmd_no_error(self.node_info, cmd)
131
132     def _sudo_execute(self, cmd):
133         return ssh.exec_cmd_no_error(self.node_info, cmd, sudo=True)
134
135     def set_ip(self, interface, address, prefix_length):
136         cmd = 'ip -4 addr flush dev {}'.format(interface)
137         self._sudo_execute(cmd)
138         cmd = 'ip addr add {}/{} dev {}'.format(address, prefix_length,
139                                                 interface)
140         self._sudo_execute(cmd)
141
142     # TODO: not ipv4-specific, move to another class
143     def set_interface_state(self, interface, state):
144         cmd = 'ip link set {} {}'.format(interface, state)
145         self._sudo_execute(cmd)
146
147     def set_route(self, network, prefix_length, gateway, interface):
148         netmask = self._get_netmask(prefix_length)
149         cmd = 'route add -net {} netmask {} gw {}'.\
150             format(network, netmask, gateway)
151         self._sudo_execute(cmd)
152
153     def unset_route(self, network, prefix_length, gateway, interface):
154         self._sudo_execute('ip route delete {}/{}'.
155                            format(network, prefix_length))
156
157     def arp_ping(self, destination_address, source_interface):
158         self._sudo_execute('arping -c 1 -I {} {}'.format(source_interface,
159                                                          destination_address))
160
161     def ping(self, destination_address, source_interface):
162         self._execute('ping -c 1 -w 5 -I {} {}'.format(source_interface,
163                                                        destination_address))
164
165     def flush_ip_addresses(self, interface):
166         self._sudo_execute('ip addr flush dev {}'.format(interface))
167
168
169 class Dut(IPv4Node):
170     """Device under test"""
171     def __init__(self, node_info):
172         super(Dut, self).__init__(node_info)
173
174     def get_sw_if_index(self, interface):
175         """Get sw_if_index of specified interface from current node
176
177         :param interface: interface name
178         :type interface: str
179         :return: sw_if_index of 'int' type
180         """
181         return Topology().get_interface_sw_index(self.node_info, interface)
182
183     def exec_vat(self, script, **args):
184         """Wrapper for VAT executor.
185
186         :param script: script to execute
187         :param args: parameters to the script
188         :type script: str
189         :type args: dict
190         :return: nothing
191         """
192         # TODO: check return value
193         VatExecutor.cmd_from_template(self.node_info, script, **args)
194
195     def set_arp(self, interface, ip_address, mac_address):
196         """Set entry in ARP cache.
197
198         :param interface: Interface name.
199         :param ip_address: IP address.
200         :param mac_address: MAC address.
201         :type interface: str
202         :type ip_address: str
203         :type mac_address: str
204         """
205         self.exec_vat('add_ip_neighbor.vat',
206                       sw_if_index=self.get_sw_if_index(interface),
207                       ip_address=ip_address, mac_address=mac_address)
208
209     def set_ip(self, interface, address, prefix_length):
210         self.exec_vat('add_ip_address.vat',
211                       sw_if_index=self.get_sw_if_index(interface),
212                       address=address, prefix_length=prefix_length)
213
214     def set_interface_state(self, interface, state):
215         if state == 'up':
216             state = 'admin-up link-up'
217         elif state == 'down':
218             state = 'admin-down link-down'
219         else:
220             raise Exception('Unexpected interface state: {}'.format(state))
221
222         self.exec_vat('set_if_state.vat',
223                       sw_if_index=self.get_sw_if_index(interface), state=state)
224
225     def set_route(self, network, prefix_length, gateway, interface):
226         sw_if_index = self.get_sw_if_index(interface)
227         self.exec_vat('add_route.vat',
228                       network=network, prefix_length=prefix_length,
229                       gateway=gateway, sw_if_index=sw_if_index)
230
231     def unset_route(self, network, prefix_length, gateway, interface):
232         self.exec_vat('del_route.vat', network=network,
233                       prefix_length=prefix_length, gateway=gateway,
234                       sw_if_index=self.get_sw_if_index(interface))
235
236     def arp_ping(self, destination_address, source_interface):
237         pass
238
239     def flush_ip_addresses(self, interface):
240         self.exec_vat('flush_ip_addresses.vat',
241                       sw_if_index=self.get_sw_if_index(interface))
242
243     def ping(self, destination_address, source_interface):
244         pass
245
246
247 def get_node(node_info):
248     """Creates a class instance derived from Node based on type.
249
250     :param node_info: dictionary containing information on nodes in topology
251     :return: Class instance that is derived from Node
252     """
253     if node_info['type'] == NodeType.TG:
254         return Tg(node_info)
255     elif node_info['type'] == NodeType.DUT:
256         return Dut(node_info)
257     else:
258         raise NotImplementedError('Node type "{}" unsupported!'.
259                                   format(node_info['type']))
260
261
262 def get_node_hostname(node_info):
263     """Get string identifying specifed node.
264
265     :param node_info: Node in the topology.
266     :type node_info: Dict
267     :return: String identifying node.
268     """
269     return node_info['host']
270
271
272 class IPv4Util(object):
273     """Implements keywords for IPv4 tests."""
274
275     ADDRESSES = {}   # holds configured IPv4 addresses
276     PREFIXES = {}  # holds configured IPv4 addresses' prefixes
277     SUBNETS = {}  # holds configured IPv4 addresses' subnets
278
279     """
280     Helper dictionary used when setting up ipv4 addresses in topology
281
282     Example value:
283     'link1': {  b'port1': {b'addr': b'192.168.3.1'},
284                 b'port2': {b'addr': b'192.168.3.2'},
285                 b'prefix': 24,
286                 b'subnet': b'192.168.3.0'}
287     """
288     topology_helper = None
289
290     @staticmethod
291     def setup_arp_on_all_duts(nodes_info):
292         """For all DUT nodes extract MAC and IP addresses of adjacent interfaces
293         from topology and use them to setup ARP entries.
294
295         :param nodes_info: Dictionary containing information on all nodes
296         in topology.
297         :type nodes_info: dict
298         """
299         for node in nodes_info.values():
300             if node['type'] == NodeType.TG:
301                 continue
302             for interface, interface_data in node['interfaces'].iteritems():
303                 if interface == 'mgmt':
304                     continue
305                 interface_name = interface_data['name']
306                 adj_node, adj_int = Topology.\
307                     get_adjacent_node_and_interface(nodes_info, node,
308                                                     interface_name)
309                 ip_address = IPv4Util.get_ip_addr(adj_node, adj_int['name'])
310                 mac_address = adj_int['mac_address']
311                 get_node(node).set_arp(interface_name, ip_address, mac_address)
312
313     @staticmethod
314     def next_address(subnet):
315         """Get next unused IPv4 address from a subnet
316
317         :param subnet: holds available IPv4 addresses
318         :return: tuple (ipv4_address, prefix_length)
319         """
320         for i in range(1, 4):
321             # build a key and try to get it from address dictionary
322             interface = 'port{}'.format(i)
323             if interface in subnet:
324                 addr = subnet[interface]['addr']
325                 del subnet[interface]
326                 return addr, subnet['prefix']
327         raise Exception('Not enough ipv4 addresses in subnet')
328
329     @staticmethod
330     def next_network(nodes_addr):
331         """Get next unused network from dictionary
332
333         :param nodes_addr: dictionary of available networks
334         :return: dictionary describing an IPv4 subnet with addresses
335         """
336         assert_not_equal(len(nodes_addr), 0, 'Not enough networks')
337         _, subnet = nodes_addr.popitem()
338         return subnet
339
340     @staticmethod
341     def configure_ipv4_addr_on_node(node, nodes_addr):
342         """Configure IPv4 address for all non-management interfaces
343         on a node in topology.
344
345         :param node: dictionary containing information about node
346         :param nodes_addr: dictionary containing IPv4 addresses
347         :return:
348         """
349         for interface, interface_data in node['interfaces'].iteritems():
350             if interface == 'mgmt':
351                 continue
352             if interface_data['link'] not in IPv4Util.topology_helper:
353                 IPv4Util.topology_helper[interface_data['link']] = \
354                     IPv4Util.next_network(nodes_addr)
355
356             network = IPv4Util.topology_helper[interface_data['link']]
357             address, prefix = IPv4Util.next_address(network)
358
359             if node['type'] != NodeType.TG:
360                 get_node(node).set_ip(interface_data['name'], address, prefix)
361                 get_node(node).set_interface_state(interface_data['name'], 'up')
362
363             key = (get_node_hostname(node), interface_data['name'])
364             IPv4Util.ADDRESSES[key] = address
365             IPv4Util.PREFIXES[key] = prefix
366             IPv4Util.SUBNETS[key] = network['subnet']
367
368     @staticmethod
369     def dut_nodes_setup_ipv4_addresses(nodes_info, nodes_addr):
370         """Configure IPv4 addresses on all non-management interfaces for each
371         node in nodes_info if node type is not traffic generator
372
373         :param nodes_info: dictionary containing information on all nodes
374         in topology
375         :param nodes_addr: dictionary containing IPv4 addresses
376         :return: nothing
377         """
378         IPv4Util.topology_helper = {}
379         # make a deep copy of nodes_addr because of modifications
380         nodes_addr_copy = copy.deepcopy(nodes_addr)
381         for node in nodes_info.values():
382             IPv4Util.configure_ipv4_addr_on_node(node, nodes_addr_copy)
383
384     @staticmethod
385     def nodes_clear_ipv4_addresses(nodes):
386         """Clear all addresses from all nodes in topology
387
388         :param nodes: dictionary containing information on all nodes
389         :return: nothing
390         """
391         for node in nodes.values():
392             for interface, interface_data in node['interfaces'].iteritems():
393                 if interface == 'mgmt':
394                     continue
395                 IPv4Util.flush_ip_addresses(interface_data['name'], node)
396
397     # TODO: not ipv4-specific, move to another class
398     @staticmethod
399     @keyword('Node "${node}" interface "${interface}" is in "${state}" state')
400     def set_interface_state(node, interface, state):
401         """See IPv4Node.set_interface_state for more information.
402
403         :param node:
404         :param interface:
405         :param state:
406         :return:
407         """
408         log.debug('Node {} interface {} is in {} state'.format(
409             get_node_hostname(node), interface, state))
410         get_node(node).set_interface_state(interface, state)
411
412     @staticmethod
413     @keyword('Node "${node}" interface "${port}" has IPv4 address '
414              '"${address}" with prefix length "${prefix_length}"')
415     def set_interface_address(node, interface, address, length):
416         """See IPv4Node.set_ip for more information.
417
418         :param node:
419         :param interface:
420         :param address:
421         :param length:
422         :return:
423         """
424         log.debug('Node {} interface {} has IPv4 address {} with prefix '
425                   'length {}'.format(get_node_hostname(node), interface,
426                                      address, length))
427         get_node(node).set_ip(interface, address, int(length))
428         hostname = get_node_hostname(node)
429         IPv4Util.ADDRESSES[hostname, interface] = address
430         IPv4Util.PREFIXES[hostname, interface] = int(length)
431         # TODO: Calculate subnet from ip address and prefix length.
432         # IPv4Util.SUBNETS[hostname, interface] =
433
434     @staticmethod
435     @keyword('From node "${node}" interface "${port}" ARP-ping '
436              'IPv4 address "${ip_address}"')
437     def arp_ping(node, interface, ip_address):
438         log.debug('From node {} interface {} ARP-ping IPv4 address {}'.
439                   format(get_node_hostname(node), interface, ip_address))
440         get_node(node).arp_ping(ip_address, interface)
441
442     @staticmethod
443     @keyword('Node "${node}" routes to IPv4 network "${network}" with prefix '
444              'length "${prefix_length}" using interface "${interface}" via '
445              '"${gateway}"')
446     def set_route(node, network, prefix_length, interface, gateway):
447         """See IPv4Node.set_route for more information.
448
449         :param node:
450         :param network:
451         :param prefix_length:
452         :param interface:
453         :param gateway:
454         :return:
455         """
456         log.debug('Node {} routes to network {} with prefix length {} '
457                   'via {} interface {}'.format(get_node_hostname(node),
458                                                network, prefix_length,
459                                                gateway, interface))
460         get_node(node).set_route(network, int(prefix_length),
461                                  gateway, interface)
462
463     @staticmethod
464     @keyword('Remove IPv4 route from "${node}" to network "${network}" with '
465              'prefix length "${prefix_length}" interface "${interface}" via '
466              '"${gateway}"')
467     def unset_route(node, network, prefix_length, interface, gateway):
468         """See IPv4Node.unset_route for more information.
469
470         :param node:
471         :param network:
472         :param prefix_length:
473         :param interface:
474         :param gateway:
475         :return:
476         """
477         get_node(node).unset_route(network, prefix_length, gateway, interface)
478
479     @staticmethod
480     @keyword('After ping is sent in topology "${nodes_info}" from node '
481              '"${src_node}" interface "${src_port}" with destination IPv4 '
482              'address of node "${dst_node}" interface "${dst_port}" a ping '
483              'response arrives and TTL is decreased by "${hops}"')
484     def send_ping(nodes_info, src_node, src_port, dst_node, dst_port, hops):
485         """Send IPv4 ping and wait for response.
486
487         :param nodes_info: Dictionary containing information on all nodes
488         in topology.
489         :param src_node: Source node.
490         :param src_port: Source interface.
491         :param dst_node: Destination node.
492         :param dst_port: Destination interface.
493         :param hops: Number of hops between src_node and dst_node.
494         """
495         log.debug('After ping is sent from node "{}" interface "{}" '
496                   'with destination IPv4 address of node "{}" interface "{}" '
497                   'a ping response arrives and TTL is decreased by "${}"'.
498                   format(get_node_hostname(src_node), src_port,
499                          get_node_hostname(dst_node), dst_port, hops))
500         node = src_node
501         src_mac = Topology.get_interface_mac(src_node, src_port)
502         if dst_node['type'] == NodeType.TG:
503             dst_mac = Topology.get_interface_mac(src_node, src_port)
504         _, adj_int = Topology.\
505             get_adjacent_node_and_interface(nodes_info, src_node, src_port)
506         first_hop_mac = adj_int['mac_address']
507         src_ip = IPv4Util.get_ip_addr(src_node, src_port)
508         dst_ip = IPv4Util.get_ip_addr(dst_node, dst_port)
509         args = '--src_if "{}" --src_mac "{}" --first_hop_mac "{}" ' \
510                '--src_ip "{}" --dst_ip "{}" --hops "{}"'\
511             .format(src_port, src_mac, first_hop_mac, src_ip, dst_ip, hops)
512         if dst_node['type'] == NodeType.TG:
513             args += ' --dst_if "{}" --dst_mac "{}"'.format(dst_port, dst_mac)
514         TrafficScriptExecutor.run_traffic_script_on_node(
515             "ipv4_ping_ttl_check.py", node, args)
516
517     @staticmethod
518     @keyword('Get IPv4 address of node "${node}" interface "${port}"')
519     def get_ip_addr(node, port):
520         """Get IPv4 address configured on specified interface
521
522         :param node: node dictionary
523         :param port: interface name
524         :return: IPv4 address of specified interface as a 'str' type
525         """
526         log.debug('Get IPv4 address of node {} interface {}'.
527                   format(get_node_hostname(node), port))
528         return IPv4Util.ADDRESSES[(get_node_hostname(node), port)]
529
530     @staticmethod
531     @keyword('Get IPv4 address prefix of node "${node}" interface "${port}"')
532     def get_ip_addr_prefix(node, port):
533         """ Get IPv4 address prefix for specified interface.
534
535         :param node: Node dictionary.
536         :param port: Interface name.
537         """
538         log.debug('Get IPv4 address prefix of node {} interface {}'.
539                   format(get_node_hostname(node), port))
540         return IPv4Util.PREFIXES[(get_node_hostname(node), port)]
541
542     @staticmethod
543     @keyword('Get IPv4 subnet of node "${node}" interface "${port}"')
544     def get_ip_addr_subnet(node, port):
545         """ Get IPv4 subnet of specified interface.
546
547         :param node: Node dictionary.
548         :param port: Interface name.
549         """
550         log.debug('Get IPv4 subnet of node {} interface {}'.
551                   format(get_node_hostname(node), port))
552         return IPv4Util.SUBNETS[(get_node_hostname(node), port)]
553
554     @staticmethod
555     @keyword('Flush IPv4 addresses "${port}" "${node}"')
556     def flush_ip_addresses(port, node):
557         """See IPv4Node.flush_ip_addresses for more information.
558
559         :param port:
560         :param node:
561         :return:
562         """
563         key = (get_node_hostname(node), port)
564         del IPv4Util.ADDRESSES[key]
565         del IPv4Util.PREFIXES[key]
566         del IPv4Util.SUBNETS[key]
567         get_node(node).flush_ip_addresses(port)