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:
6 # http://www.apache.org/licenses/LICENSE-2.0
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.
14 """Implements IPv4 RobotFramework keywords"""
16 from socket import inet_ntoa
17 from struct import pack
18 from abc import ABCMeta, abstractmethod
21 from robot.api import logger as log
22 from robot.api.deco import keyword
23 from robot.utils.asserts import assert_not_equal
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
33 class IPv4Node(object):
34 """Abstract class of a node in a topology."""
35 __metaclass__ = ABCMeta
37 def __init__(self, node_info):
38 self.node_info = node_info
41 def _get_netmask(prefix_length):
42 bits = 0xffffffff ^ (1 << 32 - prefix_length) - 1
43 return inet_ntoa(pack('>I', bits))
46 def set_ip(self, interface, address, prefix_length):
47 """Configure IPv4 address on interface
48 :param interface: interface name
53 :type prefix_length: int
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"
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
75 :type prefix_length: int
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
90 :type prefix_length: int
98 def flush_ip_addresses(self, interface):
99 """Flush all IPv4 addresses from specified interface
100 :param interface: interface name
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
119 """Traffic generator node"""
120 def __init__(self, node_info):
121 super(Tg, self).__init__(node_info)
123 def _execute(self, cmd):
124 return ssh.exec_cmd_no_error(self.node_info, cmd)
126 def _sudo_execute(self, cmd):
127 return ssh.exec_cmd_no_error(self.node_info, cmd, sudo=True)
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,
134 self._sudo_execute(cmd)
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)
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)
147 def unset_route(self, network, prefix_length, gateway, interface):
148 self._sudo_execute('ip route delete {}/{}'.
149 format(network, prefix_length))
151 def arp_ping(self, destination_address, source_interface):
152 self._sudo_execute('arping -c 1 -I {} {}'.format(source_interface,
153 destination_address))
155 def ping(self, destination_address, source_interface):
156 self._execute('ping -c 1 -w 5 -I {} {}'.format(source_interface,
157 destination_address))
159 def flush_ip_addresses(self, interface):
160 self._sudo_execute('ip addr flush dev {}'.format(interface))
164 """Device under test"""
165 def __init__(self, node_info):
166 super(Dut, self).__init__(node_info)
168 def get_sw_if_index(self, interface):
169 """Get sw_if_index of specified interface from current node
170 :param interface: interface name
172 :return: sw_if_index of 'int' type
174 return Topology().get_interface_sw_index(self.node_info, interface)
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
184 # TODO: check return value
185 VatExecutor.cmd_from_template(self.node_info, script, **args)
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)
192 def set_interface_state(self, interface, state):
194 state = 'admin-up link-up'
195 elif state == 'down':
196 state = 'admin-down link-down'
198 raise Exception('Unexpected interface state: {}'.format(state))
200 self.exec_vat('set_if_state.vat',
201 sw_if_index=self.get_sw_if_index(interface), state=state)
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)
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))
214 def arp_ping(self, destination_address, source_interface):
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))
221 def ping(self, destination_address, source_interface):
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
230 if node_info['type'] == NodeType.TG:
232 elif node_info['type'] == NodeType.DUT:
233 return Dut(node_info)
235 raise NotImplementedError('Node type "{}" unsupported!'.
236 format(node_info['type']))
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.
245 return node_info['host']
248 class IPv4Util(object):
249 """Implements keywords for IPv4 tests."""
251 ADDRESSES = {} # holds configured IPv4 addresses
252 PREFIXES = {} # holds configured IPv4 addresses' prefixes
253 SUBNETS = {} # holds configured IPv4 addresses' subnets
256 Helper dictionary used when setting up ipv4 addresses in topology
259 'link1': { b'port1': {b'addr': b'192.168.3.1'},
260 b'port2': {b'addr': b'192.168.3.2'},
262 b'subnet': b'192.168.3.0'}
264 topology_helper = None
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)
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')
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
287 assert_not_equal(len(nodes_addr), 0, 'Not enough networks')
288 _, subnet = nodes_addr.popitem()
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
298 for interface, interface_data in node['interfaces'].iteritems():
299 if interface == 'mgmt':
301 if interface_data['link'] not in IPv4Util.topology_helper:
302 IPv4Util.topology_helper[interface_data['link']] = \
303 IPv4Util.next_network(nodes_addr)
305 network = IPv4Util.topology_helper[interface_data['link']]
306 address, prefix = IPv4Util.next_address(network)
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']
315 def nodes_setup_ipv4_addresses(nodes_info, nodes_addr):
316 """Configure IPv4 addresses on all non-management interfaces for each
318 :param nodes_info: dictionary containing information on all nodes
320 :param nodes_addr: dictionary containing IPv4 addresses
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)
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
335 for _, node in nodes.iteritems():
336 for interface, interface_data in node['interfaces'].iteritems():
337 if interface == 'mgmt':
339 IPv4Util.flush_ip_addresses(interface_data['name'], node)
341 # TODO: not ipv4-specific, move to another class
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.
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)
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.
366 log.debug('Node {} interface {} has IPv4 address {} with prefix '
367 'length {}'.format(get_node_hostname(node), interface,
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] =
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)
385 @keyword('Node "${node}" routes to IPv4 network "${network}" with prefix '
386 'length "${prefix_length}" using interface "${interface}" via '
388 def set_route(node, network, prefix_length, interface, gateway):
389 """See IPv4Node.set_route for more information.
392 :param prefix_length:
397 log.debug('Node {} routes to network {} with prefix length {} '
398 'via {} interface {}'.format(get_node_hostname(node),
399 network, prefix_length,
401 get_node(node).set_route(network, int(prefix_length),
405 @keyword('Remove IPv4 route from "${node}" to network "${network}" with '
406 'prefix length "${prefix_length}" interface "${interface}" via '
408 def unset_route(node, network, prefix_length, interface, gateway):
409 """See IPv4Node.unset_route for more information.
412 :param prefix_length:
417 get_node(node).unset_route(network, prefix_length, gateway, interface)
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.
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))
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)
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
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)]
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.
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)]
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.
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)]
488 @keyword('Flush IPv4 addresses "${port}" "${node}"')
489 def flush_ip_addresses(port, node):
490 """See IPv4Node.flush_ip_addresses for more information.
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)