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
49 :param interface: interface name
54 :type prefix_length: int
60 def set_interface_state(self, interface, state):
61 """Set interface state
63 :param interface: interface name string
64 :param state: one of following values: "up" or "down"
70 def set_route(self, network, prefix_length, gateway, interface):
71 """Configure IPv4 route
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
78 :type prefix_length: int
86 def unset_route(self, network, prefix_length, gateway, interface):
87 """Remove specified IPv4 route
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
94 :type prefix_length: int
102 def flush_ip_addresses(self, interface):
103 """Flush all IPv4 addresses from specified interface
105 :param interface: interface name
112 def ping(self, destination_address, source_interface):
113 """Send an ICMP request to destination node
115 :param destination_address: address to send the ICMP request
116 :param source_interface:
117 :type destination_address: str
118 :type source_interface: str
125 """Traffic generator node"""
126 def __init__(self, node_info):
127 super(Tg, self).__init__(node_info)
129 def _execute(self, cmd):
130 return ssh.exec_cmd_no_error(self.node_info, cmd)
132 def _sudo_execute(self, cmd):
133 return ssh.exec_cmd_no_error(self.node_info, cmd, sudo=True)
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,
140 self._sudo_execute(cmd)
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)
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)
153 def unset_route(self, network, prefix_length, gateway, interface):
154 self._sudo_execute('ip route delete {}/{}'.
155 format(network, prefix_length))
157 def arp_ping(self, destination_address, source_interface):
158 self._sudo_execute('arping -c 1 -I {} {}'.format(source_interface,
159 destination_address))
161 def ping(self, destination_address, source_interface):
162 self._execute('ping -c 1 -w 5 -I {} {}'.format(source_interface,
163 destination_address))
165 def flush_ip_addresses(self, interface):
166 self._sudo_execute('ip addr flush dev {}'.format(interface))
170 """Device under test"""
171 def __init__(self, node_info):
172 super(Dut, self).__init__(node_info)
174 def get_sw_if_index(self, interface):
175 """Get sw_if_index of specified interface from current node
177 :param interface: interface name
179 :return: sw_if_index of 'int' type
181 return Topology().get_interface_sw_index(self.node_info, interface)
183 def exec_vat(self, script, **args):
184 """Wrapper for VAT executor.
186 :param script: script to execute
187 :param args: parameters to the script
192 # TODO: check return value
193 VatExecutor.cmd_from_template(self.node_info, script, **args)
195 def set_arp(self, interface, ip_address, mac_address):
196 """Set entry in ARP cache.
198 :param interface: Interface name.
199 :param ip_address: IP address.
200 :param mac_address: MAC address.
202 :type ip_address: str
203 :type mac_address: str
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)
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)
214 def set_interface_state(self, interface, state):
216 state = 'admin-up link-up'
217 elif state == 'down':
218 state = 'admin-down link-down'
220 raise Exception('Unexpected interface state: {}'.format(state))
222 self.exec_vat('set_if_state.vat',
223 sw_if_index=self.get_sw_if_index(interface), state=state)
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)
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))
236 def arp_ping(self, destination_address, source_interface):
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))
243 def ping(self, destination_address, source_interface):
247 def get_node(node_info):
248 """Creates a class instance derived from Node based on type.
250 :param node_info: dictionary containing information on nodes in topology
251 :return: Class instance that is derived from Node
253 if node_info['type'] == NodeType.TG:
255 elif node_info['type'] == NodeType.DUT:
256 return Dut(node_info)
258 raise NotImplementedError('Node type "{}" unsupported!'.
259 format(node_info['type']))
262 def get_node_hostname(node_info):
263 """Get string identifying specifed node.
265 :param node_info: Node in the topology.
266 :type node_info: Dict
267 :return: String identifying node.
269 return node_info['host']
272 class IPv4Util(object):
273 """Implements keywords for IPv4 tests."""
275 ADDRESSES = {} # holds configured IPv4 addresses
276 PREFIXES = {} # holds configured IPv4 addresses' prefixes
277 SUBNETS = {} # holds configured IPv4 addresses' subnets
280 Helper dictionary used when setting up ipv4 addresses in topology
283 'link1': { b'port1': {b'addr': b'192.168.3.1'},
284 b'port2': {b'addr': b'192.168.3.2'},
286 b'subnet': b'192.168.3.0'}
288 topology_helper = None
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.
295 :param nodes_info: Dictionary containing information on all nodes
297 :type nodes_info: dict
299 for node in nodes_info.values():
300 if node['type'] == NodeType.TG:
302 for interface, interface_data in node['interfaces'].iteritems():
303 if interface == 'mgmt':
305 interface_name = interface_data['name']
306 adj_node, adj_int = Topology.\
307 get_adjacent_node_and_interface(nodes_info, node,
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)
314 def next_address(subnet):
315 """Get next unused IPv4 address from a subnet
317 :param subnet: holds available IPv4 addresses
318 :return: tuple (ipv4_address, prefix_length)
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')
330 def next_network(nodes_addr):
331 """Get next unused network from dictionary
333 :param nodes_addr: dictionary of available networks
334 :return: dictionary describing an IPv4 subnet with addresses
336 assert_not_equal(len(nodes_addr), 0, 'Not enough networks')
337 _, subnet = nodes_addr.popitem()
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.
345 :param node: dictionary containing information about node
346 :param nodes_addr: dictionary containing IPv4 addresses
349 for interface, interface_data in node['interfaces'].iteritems():
350 if interface == 'mgmt':
352 if interface_data['link'] not in IPv4Util.topology_helper:
353 IPv4Util.topology_helper[interface_data['link']] = \
354 IPv4Util.next_network(nodes_addr)
356 network = IPv4Util.topology_helper[interface_data['link']]
357 address, prefix = IPv4Util.next_address(network)
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')
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']
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
373 :param nodes_info: dictionary containing information on all nodes
375 :param nodes_addr: dictionary containing IPv4 addresses
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)
385 def nodes_clear_ipv4_addresses(nodes):
386 """Clear all addresses from all nodes in topology
388 :param nodes: dictionary containing information on all nodes
391 for node in nodes.values():
392 for interface, interface_data in node['interfaces'].iteritems():
393 if interface == 'mgmt':
395 IPv4Util.flush_ip_addresses(interface_data['name'], node)
397 # TODO: not ipv4-specific, move to another class
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.
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)
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.
424 log.debug('Node {} interface {} has IPv4 address {} with prefix '
425 'length {}'.format(get_node_hostname(node), interface,
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] =
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)
443 @keyword('Node "${node}" routes to IPv4 network "${network}" with prefix '
444 'length "${prefix_length}" using interface "${interface}" via '
446 def set_route(node, network, prefix_length, interface, gateway):
447 """See IPv4Node.set_route for more information.
451 :param prefix_length:
456 log.debug('Node {} routes to network {} with prefix length {} '
457 'via {} interface {}'.format(get_node_hostname(node),
458 network, prefix_length,
460 get_node(node).set_route(network, int(prefix_length),
464 @keyword('Remove IPv4 route from "${node}" to network "${network}" with '
465 'prefix length "${prefix_length}" interface "${interface}" via '
467 def unset_route(node, network, prefix_length, interface, gateway):
468 """See IPv4Node.unset_route for more information.
472 :param prefix_length:
477 get_node(node).unset_route(network, prefix_length, gateway, interface)
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.
487 :param nodes_info: Dictionary containing information on all nodes
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.
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))
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)
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
522 :param node: node dictionary
523 :param port: interface name
524 :return: IPv4 address of specified interface as a 'str' type
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)]
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.
535 :param node: Node dictionary.
536 :param port: Interface name.
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)]
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.
547 :param node: Node dictionary.
548 :param port: Interface name.
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)]
555 @keyword('Flush IPv4 addresses "${port}" "${node}"')
556 def flush_ip_addresses(port, node):
557 """See IPv4Node.flush_ip_addresses for more information.
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)