36dc6050a170b03343f4b0aeded091ed9126f4ad
[csit.git] / resources / libraries / python / IPv4Setup.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 """IPv4 setup library"""
15
16 from socket import inet_ntoa
17 from struct import pack
18 from abc import ABCMeta, abstractmethod
19
20 from robot.api.deco import keyword
21
22 from resources.libraries.python.ssh import exec_cmd_no_error
23 from resources.libraries.python.Routing import Routing
24 from resources.libraries.python.topology import NodeType, Topology
25 from resources.libraries.python.VatExecutor import VatExecutor
26
27
28 class IPv4Node(object):
29     """Abstract class of a node in a topology."""
30     __metaclass__ = ABCMeta
31
32     def __init__(self, node_info):
33         self.node_info = node_info
34
35     @staticmethod
36     def _get_netmask(prefix_length):
37         """Convert IPv4 network prefix length into IPV4 network mask.
38
39         :param prefix_length: Length of network prefix.
40         :type prefix_length: int
41         :returns: Network mask.
42         :rtype: str
43         """
44
45         bits = 0xffffffff ^ (1 << 32 - prefix_length) - 1
46         return inet_ntoa(pack('>I', bits))
47
48     @abstractmethod
49     def set_ip(self, interface, address, prefix_length):
50         """Configure IPv4 address on interface.
51
52         :param interface: Interface name.
53         :param address: IPv4 address.
54         :param prefix_length: IPv4 prefix length.
55         :type interface: str
56         :type address: str
57         :type prefix_length: int
58         :returns: nothing
59         """
60         pass
61
62     @abstractmethod
63     def set_route(self, network, prefix_length, gateway, interface, count=1):
64         """Configure IPv4 route.
65
66         :param network: Network IPv4 address.
67         :param prefix_length: IPv4 prefix length.
68         :param gateway: IPv4 address of the gateway.
69         :param interface: Interface name.
70         :param count: Number of consecutive routes to add.
71         :type network: str
72         :type prefix_length: int
73         :type gateway: str
74         :type interface: str
75         :type route: int
76         :returns: nothing
77         """
78         pass
79
80     @abstractmethod
81     def unset_route(self, network, prefix_length, gateway, interface):
82         """Remove specified IPv4 route.
83
84         :param network: Network IPv4 address.
85         :param prefix_length: IPv4 prefix length.
86         :param gateway: IPv4 address of the gateway.
87         :param interface: Interface name.
88         :type network: str
89         :type prefix_length: int
90         :type gateway: str
91         :type interface: str
92         :returns: nothing
93         """
94         pass
95
96     @abstractmethod
97     def flush_ip_addresses(self, interface):
98         """Flush all IPv4 addresses from specified interface.
99
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
110         :param destination_address: Address to send the ICMP request.
111         :param source_interface: Source interface name.
112         :type destination_address: str
113         :type source_interface: str
114         :returns: nothing
115         """
116         pass
117
118
119 class Tg(IPv4Node):
120     """Traffic generator node"""
121     def __init__(self, node_info):
122         super(Tg, self).__init__(node_info)
123
124     def _execute(self, cmd):
125         """Executes the specified command on TG using SSH.
126
127         :param cmd: Command to be executed.
128         :type cmd: str
129         :returns: Content of stdout and stderr returned by command.
130         :rtype: tuple
131         """
132         return exec_cmd_no_error(self.node_info, cmd)
133
134     def _sudo_execute(self, cmd):
135         """Executes the specified command with sudo on TG using SSH.
136
137         :param cmd: Command to be executed.
138         :type cmd: str
139         :returns: Content of stdout and stderr returned by command.
140         :rtype: tuple
141         """
142         return exec_cmd_no_error(self.node_info, cmd, sudo=True)
143
144     def set_ip(self, interface, address, prefix_length):
145         cmd = 'ip -4 addr flush dev {}'.format(interface)
146         self._sudo_execute(cmd)
147         cmd = 'ip addr add {}/{} dev {}'.format(address, prefix_length,
148                                                 interface)
149         self._sudo_execute(cmd)
150
151     def set_route(self, network, prefix_length, gateway, interface, count=1):
152         netmask = self._get_netmask(prefix_length)
153         cmd = 'route add -net {} netmask {} gw {}'.\
154             format(network, netmask, gateway)
155         self._sudo_execute(cmd)
156
157     def unset_route(self, network, prefix_length, gateway, interface):
158         self._sudo_execute('ip route delete {}/{}'.
159                            format(network, prefix_length))
160
161     def arp_ping(self, destination_address, source_interface):
162         """Execute 'arping' command to send one ARP packet from the TG node.
163
164         :param destination_address: Destination IP address for the ARP packet.
165         :param source_interface: Name of an interface to send ARP packet from.
166         :type destination_address: str
167         :type source_interface: str
168         """
169         self._sudo_execute('arping -c 1 -I {} {}'.format(source_interface,
170                                                          destination_address))
171
172     def ping(self, destination_address, source_interface):
173         self._execute('ping -c 1 -w 5 -I {} {}'.format(source_interface,
174                                                        destination_address))
175
176     def flush_ip_addresses(self, interface):
177         self._sudo_execute('ip addr flush dev {}'.format(interface))
178
179
180 class Dut(IPv4Node):
181     """Device under test"""
182     def __init__(self, node_info):
183         super(Dut, self).__init__(node_info)
184
185     def get_sw_if_index(self, interface):
186         """Get sw_if_index of specified interface from current node.
187
188         :param interface: Interface name.
189         :type interface: str
190         :returns: sw_if_index of the interface or None.
191         :rtype: int
192         """
193         return Topology().get_interface_sw_index(self.node_info, interface)
194
195     def exec_vat(self, script, **args):
196         """Wrapper for VAT executor.
197
198         :param script: Script to execute.
199         :param args: Parameters to the script.
200         :type script: str
201         :type args: dict
202         :returns: nothing
203         """
204         # TODO: check return value
205         VatExecutor.cmd_from_template(self.node_info, script, **args)
206
207     def set_arp(self, iface_key, ip_address, mac_address):
208         """Set entry in ARP cache.
209
210         :param iface_key: Interface key.
211         :param ip_address: IP address.
212         :param mac_address: MAC address.
213         :type iface_key: str
214         :type ip_address: str
215         :type mac_address: str
216         """
217         self.exec_vat('add_ip_neighbor.vat',
218                       sw_if_index=self.get_sw_if_index(iface_key),
219                       ip_address=ip_address, mac_address=mac_address)
220
221     def set_ip(self, interface, address, prefix_length):
222         self.exec_vat('add_ip_address.vat',
223                       sw_if_index=self.get_sw_if_index(interface),
224                       address=address, prefix_length=prefix_length)
225
226     def set_route(self, network, prefix_length, gateway, interface, count=1):
227         Routing.vpp_route_add(self.node_info,
228                               network=network, prefix_len=prefix_length,
229                               gateway=gateway, interface=interface, count=count)
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         """Does nothing."""
238         pass
239
240     def flush_ip_addresses(self, interface):
241         self.exec_vat('flush_ip_addresses.vat',
242                       sw_if_index=self.get_sw_if_index(interface))
243
244     def ping(self, destination_address, source_interface):
245         pass
246
247
248 def get_node(node_info):
249     """Creates a class instance derived from Node based on type.
250
251     :param node_info: Dictionary containing information on nodes in topology.
252     :type node_info: dict
253     :returns: Class instance that is derived from Node.
254     """
255     if node_info['type'] == NodeType.TG:
256         return Tg(node_info)
257     elif node_info['type'] == NodeType.DUT:
258         return Dut(node_info)
259     else:
260         raise NotImplementedError('Node type "{}" unsupported!'.
261                                   format(node_info['type']))
262
263
264 class IPv4Setup(object):
265     """IPv4 setup in topology."""
266
267     @staticmethod
268     def vpp_nodes_set_ipv4_addresses(nodes, nodes_addr):
269         """Set IPv4 addresses on all VPP nodes in topology.
270
271         :param nodes: Nodes of the test topology.
272         :param nodes_addr: Available nodes IPv4 addresses.
273         :type nodes: dict
274         :type nodes_addr: dict
275         :returns: Affected interfaces as list of (node, interface) tuples.
276         :rtype: list
277         """
278         interfaces = []
279         for net in nodes_addr.values():
280             for port in net['ports'].values():
281                 host = port.get('node')
282                 if host is None:
283                     continue
284                 topo = Topology()
285                 node = topo.get_node_by_hostname(nodes, host)
286                 if node is None:
287                     continue
288                 if node['type'] != NodeType.DUT:
289                     continue
290                 iface_key = topo.get_interface_by_name(node, port['if'])
291                 get_node(node).set_ip(iface_key, port['addr'], net['prefix'])
292                 interfaces.append((node, port['if']))
293
294         return interfaces
295
296     @staticmethod
297     @keyword('Get IPv4 address of node "${node}" interface "${port}" '
298              'from "${nodes_addr}"')
299     def get_ip_addr(node, iface_key, nodes_addr):
300         """Return IPv4 address of the node port.
301
302         :param node: Node in the topology.
303         :param iface_key: Interface key of the node.
304         :param nodes_addr: Nodes IPv4 addresses.
305         :type node: dict
306         :type iface_key: str
307         :type nodes_addr: dict
308         :returns: IPv4 address.
309         :rtype: str
310         """
311         interface = Topology.get_interface_name(node, iface_key)
312         for net in nodes_addr.values():
313             for port in net['ports'].values():
314                 host = port.get('node')
315                 dev = port.get('if')
316                 if host == node['host'] and dev == interface:
317                     ip_addr = port.get('addr')
318                     if ip_addr is not None:
319                         return ip_addr
320                     else:
321                         raise Exception(
322                             'Node {n} port {p} IPv4 address is not set'.format(
323                                 n=node['host'], p=interface))
324
325         raise Exception('Node {n} port {p} IPv4 address not found.'.format(
326             n=node['host'], p=interface))
327
328     @staticmethod
329     def setup_arp_on_all_duts(nodes_info, nodes_addr):
330         """For all DUT nodes extract MAC and IP addresses of adjacent
331         interfaces from topology and use them to setup ARP entries.
332
333         :param nodes_info: Dictionary containing information on all nodes
334         in topology.
335         :param nodes_addr: Nodes IPv4 addresses.
336         :type nodes_info: dict
337         :type nodes_addr: dict
338         """
339         for node in nodes_info.values():
340             if node['type'] == NodeType.TG:
341                 continue
342             for iface_key in node['interfaces'].keys():
343                 adj_node, adj_int = Topology.\
344                     get_adjacent_node_and_interface(nodes_info, node, iface_key)
345                 ip_address = IPv4Setup.get_ip_addr(adj_node, adj_int,
346                                                    nodes_addr)
347                 mac_address = Topology.get_interface_mac(adj_node, adj_int)
348                 get_node(node).set_arp(iface_key, ip_address, mac_address)
349
350     @staticmethod
351     def add_arp_on_dut(node, iface_key, ip_address, mac_address):
352         """Set ARP cache entree on DUT node.
353
354         :param node: VPP Node in the topology.
355         :param iface_key: Interface key.
356         :param ip_address: IP address of the interface.
357         :param mac_address: MAC address of the interface.
358         :type node: dict
359         :type iface_key: str
360         :type ip_address: str
361         :type mac_address: str
362         """
363         get_node(node).set_arp(iface_key, ip_address, mac_address)