diff options
author | Matej Klotton <mklotton@cisco.com> | 2017-03-20 09:38:02 +0100 |
---|---|---|
committer | Jan Gelety <jgelety@cisco.com> | 2017-04-25 15:38:29 +0000 |
commit | 0fc813b1694a6ae70b759e7ca96741f21f81b051 (patch) | |
tree | 43ad70df6c030ad3e4b178f36a5147e848c3facf /resources | |
parent | f496b8dd088f9ba89710b8b6f8d0fb380740458f (diff) |
Vhost tests
Change-Id: I03722afe13722941e084bc01161f7c2af30e3cb1
Signed-off-by: Matej Klotton <mklotton@cisco.com>
Diffstat (limited to 'resources')
-rw-r--r-- | resources/libraries/python/QemuUtils.py | 29 | ||||
-rw-r--r-- | resources/libraries/python/ssh.py | 70 | ||||
-rw-r--r-- | resources/libraries/robot/vxlan.robot | 47 | ||||
-rwxr-xr-x | resources/traffic_scripts/send_vxlan_check_vxlan.py | 106 | ||||
-rw-r--r-- | resources/traffic_scripts/vxlan.py | 17 |
5 files changed, 229 insertions, 40 deletions
diff --git a/resources/libraries/python/QemuUtils.py b/resources/libraries/python/QemuUtils.py index 244ece2258..1664b0b916 100644 --- a/resources/libraries/python/QemuUtils.py +++ b/resources/libraries/python/QemuUtils.py @@ -34,6 +34,8 @@ class QemuUtils(object): self._qmp_sock = '/tmp/qmp{0}.sock'.format(self._qemu_id) # QEMU Guest Agent socket self._qga_sock = '/tmp/qga{0}.sock'.format(self._qemu_id) + # QEMU PID file + self._pid_file = '/tmp/qemu{0}.pid'.format(self._qemu_id) self._qemu_opt = {} # Default 1 CPU. self._qemu_opt['smp'] = '-smp 1,sockets=1,cores=1,threads=1' @@ -41,11 +43,11 @@ class QemuUtils(object): # management interface. self._qemu_opt['options'] = '-cpu host -daemonize -enable-kvm ' \ '-machine pc,accel=kvm,usb=off,mem-merge=off ' \ - '-net nic,macaddr=52:54:00:00:00:{0:02x} -balloon none'\ + '-net nic,macaddr=52:54:00:00:{0:02x}:ff -balloon none'\ .format(self._qemu_id) - self._qemu_opt['ssh_fwd_port'] = 10022 + self._qemu_opt['ssh_fwd_port'] = 10021 + qemu_id # Default serial console port - self._qemu_opt['serial_port'] = 4556 + self._qemu_opt['serial_port'] = 4555 + qemu_id # Default 512MB virtual RAM self._qemu_opt['mem_size'] = 512 # Default huge page mount point, required for Vhost-user interfaces. @@ -205,8 +207,8 @@ class QemuUtils(object): chardev += ',server' self._qemu_opt['options'] += chardev # Create Vhost-user network backend. - netdev = ' -netdev vhost-user,id=vhost{0},chardev=char{0},'\ - 'queues={1}'.format(self._vhost_id, self._qemu_opt['queues']) + netdev = (' -netdev vhost-user,id=vhost{0},chardev=char{0},queues={1}' + .format(self._vhost_id, self._qemu_opt['queues'])) self._qemu_opt['options'] += netdev # If MAC is not specified use auto-generated MAC address based on # template 52:54:00:00:<qemu_id>:<vhost_id>, e.g. vhost1 MAC of QEMU @@ -525,12 +527,14 @@ class QemuUtils(object): '-device isa-serial,chardev=qga0'.format(self._qga_sock) # Graphic setup graphic = '-monitor none -display none -vga none' + # PID file + pid = '-pidfile {}'.format(self._pid_file) # Run QEMU - cmd = '{0} {1} {2} {3} {4} {5} {6} {7} {8} {9}'.format( + cmd = '{0} {1} {2} {3} {4} {5} {6} {7} {8} {9} {10}'.format( self._qemu_bin, self._qemu_opt.get('smp'), mem, ssh_fwd, self._qemu_opt.get('options'), - drive, qmp, serial, qga, graphic) + drive, qmp, serial, qga, graphic, pid) (ret_code, _, stderr) = self._ssh.exec_command_sudo(cmd, timeout=300) if int(ret_code) != 0: logger.debug('QEMU start failed {0}'.format(stderr)) @@ -578,9 +582,18 @@ class QemuUtils(object): def qemu_kill(self): """Kill qemu process.""" - # TODO: add PID storage so that we can kill specific PID # Note: in QEMU start phase there are 3 QEMU processes because we # daemonize QEMU + self._ssh.exec_command_sudo('chmod +r {}'.format(self._pid_file)) + self._ssh.exec_command_sudo('kill -SIGKILL $(cat {})' + .format(self._pid_file)) + # Delete PID file + cmd = 'rm -f {}'.format(self._pid_file) + self._ssh.exec_command_sudo(cmd) + + def qemu_kill_all(self, node=None): + if node: + self.qemu_set_node(node) self._ssh.exec_command_sudo('pkill -SIGKILL qemu') def qemu_clear_socks(self): diff --git a/resources/libraries/python/ssh.py b/resources/libraries/python/ssh.py index 961f7e2ee8..0009bde59b 100644 --- a/resources/libraries/python/ssh.py +++ b/resources/libraries/python/ssh.py @@ -56,40 +56,46 @@ class SSH(object): return hash(frozenset([node['host'], node['port']])) - def connect(self, node): + def connect(self, node, attempts=5): """Connect to node prior to running exec_command or scp. If there already is a connection to the node, this method reuses it. """ - self._node = node - node_hash = self._node_hash(node) - if node_hash in SSH.__existing_connections: - self._ssh = SSH.__existing_connections[node_hash] - logger.debug('reusing ssh: {0}'.format(self._ssh)) - else: - start = time() - pkey = None - if 'priv_key' in node: - pkey = RSAKey.from_private_key( - StringIO.StringIO(node['priv_key'])) - - self._ssh = paramiko.SSHClient() - self._ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - - self._ssh.connect(node['host'], username=node['username'], - password=node.get('password'), pkey=pkey, - port=node['port']) - - self._ssh.get_transport().set_keepalive(10) - - SSH.__existing_connections[node_hash] = self._ssh - - logger.trace('connect took {} seconds'.format(time() - start)) - logger.debug('new ssh: {0}'.format(self._ssh)) - - logger.debug('Connect peer: {0}'. - format(self._ssh.get_transport().getpeername())) - logger.debug('Connections: {0}'.format(str(SSH.__existing_connections))) + try: + self._node = node + node_hash = self._node_hash(node) + if node_hash in SSH.__existing_connections: + self._ssh = SSH.__existing_connections[node_hash] + logger.debug('reusing ssh: {0}'.format(self._ssh)) + else: + start = time() + pkey = None + if 'priv_key' in node: + pkey = RSAKey.from_private_key( + StringIO.StringIO(node['priv_key'])) + + self._ssh = paramiko.SSHClient() + self._ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + self._ssh.connect(node['host'], username=node['username'], + password=node.get('password'), pkey=pkey, + port=node['port']) + + self._ssh.get_transport().set_keepalive(10) + + SSH.__existing_connections[node_hash] = self._ssh + + logger.trace('connect took {} seconds'.format(time() - start)) + logger.debug('new ssh: {0}'.format(self._ssh)) + + logger.debug('Connect peer: {0}'. + format(self._ssh.get_transport().getpeername())) + logger.debug('Connections: {0}'.format(str(SSH.__existing_connections))) + except: + if attempts > 0: + self._reconnect(attempts-1) + else: + raise def disconnect(self, node): """Close SSH connection to the node. @@ -104,12 +110,12 @@ class SSH(object): ssh = SSH.__existing_connections.pop(node_hash) ssh.close() - def _reconnect(self): + def _reconnect(self, attempts=0): """Close the SSH connection and open it again.""" node = self._node self.disconnect(node) - self.connect(node) + self.connect(node, attempts) logger.debug('Reconnecting peer done: {}'. format(self._ssh.get_transport().getpeername())) diff --git a/resources/libraries/robot/vxlan.robot b/resources/libraries/robot/vxlan.robot index 0b53c85494..36a31584d0 100644 --- a/resources/libraries/robot/vxlan.robot +++ b/resources/libraries/robot/vxlan.robot @@ -94,3 +94,50 @@ | | Set Test Variable | ${dut1s_vlan_index} | | Set Test Variable | ${dut2s_vlan_name} | | Set Test Variable | ${dut2s_vlan_index} + +| Send VXLAN receive VXLAN Packet +| | [Documentation] | Send VXLAN encapsulated Ethernet frame and check \ +| | ... | received one. +| | ... +| | ... | *Arguments:* +| | ... | - tg_node - Node where to run traffic script. Type: dictionary +| | ... | - tx_if - Interface from where send VXLAN packet. Type: string +| | ... | - rx_if - Interface where receive VXLAN packet. Type: string +| | ... | - tx_src_mac - Source MAC address of sent packet. Type: string +| | ... | - tx_dst_mac - Destination MAC address of sent packet. Type: string +| | ... | - tx_src_ip - Source IP address of sent VXLAN packet. Type: string +| | ... | - tx_dst_ip - Destination IP address of sent VXLAN packet. +| | ... | Type: string +| | ... | - tx_vni - VNI of sent VXLAN packet. Type: string +| | ... | - rx_src_ip - Source IP address of received VXLAN packet. Type: string +| | ... | - rx_dst_ip - Destination IP address of received VXLAN packet. +| | ... | Type: string +| | ... | - rx_vni - VNI of received VXLAN packet. Type: string +| | ... +| | ... | *Return:* +| | ... | - No value returned +| | ... +| | ... | *Example:* +| | ... +| | ... | \| Send VXLAN receive VXLAN Packet \| ${tg_node} \| port4 \| port4 \ +| | ... | \| fa:16:3e:6d:f9:c5 \| fa:16:3e:e6:6d:9a \| 192.168.0.1 \ +| | ... | \| 192.168.0.2 \| ${101} \| 192.168.0.2 \| 192.168.0.1 \| ${102} \| +| | ... +| | [Arguments] | ${tg_node} | ${tx_if} | ${rx_if} +| | ... | ${tx_src_mac} | ${tx_dst_mac} +| | ... | ${tx_src_ip} | ${tx_dst_ip} | ${tx_vni} +| | ... | ${rx_src_ip} | ${rx_dst_ip} | ${rx_vni} +| | ${tx_if_name}= | Get interface name | ${tg_node} | ${tx_if} +| | ${rx_if_name}= | Get interface name | ${tg_node} | ${rx_if} +| | ${args}= | Catenate +| | ... | --tx_if ${tx_if_name} +| | ... | --rx_if ${rx_if_name} +| | ... | --tx_src_mac ${tx_src_mac} +| | ... | --tx_dst_mac ${tx_dst_mac} +| | ... | --tx_src_ip ${tx_src_ip} +| | ... | --tx_dst_ip ${tx_dst_ip} +| | ... | --tx_vni ${tx_vni} +| | ... | --rx_src_ip ${rx_src_ip} +| | ... | --rx_dst_ip ${rx_dst_ip} +| | ... | --rx_vni ${rx_vni} +| | Run Traffic Script On Node | send_vxlan_check_vxlan.py | ${tg_node} | ${args} diff --git a/resources/traffic_scripts/send_vxlan_check_vxlan.py b/resources/traffic_scripts/send_vxlan_check_vxlan.py new file mode 100755 index 0000000000..3bf273d17a --- /dev/null +++ b/resources/traffic_scripts/send_vxlan_check_vxlan.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python +# Copyright (c) 2016 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Traffic script that sends an IP ICMPv4/ICMPv6 packet from one interface to +the other one. Dot1q or Dot1ad tagging of the ethernet frame can be set. +""" + +import sys + +import vxlan + +from scapy.layers.inet import IP, UDP, Raw +from scapy.layers.l2 import Ether +from resources.libraries.python.PacketVerifier import RxQueue, TxQueue +from resources.libraries.python.TrafficScriptArg import TrafficScriptArg + + +def main(): + """Send IP ICMPv4/ICMPv6 packet from one traffic generator interface to + the other one. Dot1q or Dot1ad tagging of the ethernet frame can be set. + """ + args = TrafficScriptArg(['tx_src_mac', 'tx_dst_mac', 'tx_src_ip', + 'tx_dst_ip', 'tx_vni', 'rx_src_ip', 'rx_dst_ip', + 'rx_vni']) + + tx_if = args.get_arg('tx_if') + rx_if = args.get_arg('rx_if') + tx_src_mac = args.get_arg('tx_src_mac') + tx_dst_mac = args.get_arg('tx_dst_mac') + tx_src_ip = args.get_arg('tx_src_ip') + tx_dst_ip = args.get_arg('tx_dst_ip') + tx_vni = args.get_arg('tx_vni') + rx_src_ip = args.get_arg('rx_src_ip') + rx_dst_ip = args.get_arg('rx_dst_ip') + rx_vni = args.get_arg('rx_vni') + + rxq = RxQueue(rx_if) + txq = TxQueue(tx_if) + + sent_packets = [] + + tx_pkt_p = (Ether(src='02:00:00:00:00:01', dst='02:00:00:00:00:02') / + IP(src='192.168.1.1', dst='192.168.1.2') / + UDP(sport=12345, dport=1234) / + Raw('rew data')) + + pkt_raw = (Ether(src=tx_src_mac, dst=tx_dst_mac) / + IP(src=tx_src_ip, dst=tx_dst_ip) / + UDP(sport=23456) / + vxlan.VXLAN(vni=int(tx_vni)) / + tx_pkt_p) + + # Send created packet on one interface and receive on the other + sent_packets.append(pkt_raw) + txq.send(pkt_raw) + + ether = rxq.recv(2, ignore=sent_packets) + + # Check whether received packet contains layers Ether, IP and VXLAN + if ether is None: + raise RuntimeError('Packet Rx timeout') + ip = ether.payload + + if ip.src != rx_src_ip: + raise RuntimeError('IP src mismatch {} != {}'.format(ip.src, rx_src_ip)) + if ip.dst != rx_dst_ip: + raise RuntimeError('IP dst mismatch {} != {}'.format(ip.dst, rx_dst_ip)) + if ip.payload.dport != 4789: + raise RuntimeError('VXLAN UDP port mismatch {} != {}'. + format(ip.payload.dport, 4789)) + vxlan_pkt = ip.payload.payload + + if int(vxlan_pkt.vni) != int(rx_vni): + raise RuntimeError('vxlan mismatch') + rx_pkt_p = vxlan_pkt.payload + + if rx_pkt_p.src != tx_pkt_p.src: + raise RuntimeError('RX encapsulated MAC src mismatch {} != {}'. + format(rx_pkt_p.src, tx_pkt_p.src)) + if rx_pkt_p.dst != tx_pkt_p.dst: + raise RuntimeError('RX encapsulated MAC dst mismatch {} != {}'. + format(rx_pkt_p.dst, tx_pkt_p.dst)) + if rx_pkt_p['IP'].src != tx_pkt_p['IP'].src: + raise RuntimeError('RX encapsulated IP src mismatch {} != {}'. + format(rx_pkt_p['IP'].src, tx_pkt_p['IP'].src)) + if rx_pkt_p['IP'].dst != tx_pkt_p['IP'].dst: + raise RuntimeError('RX encapsulated IP dst mismatch {} != {}'. + format(rx_pkt_p['IP'].dst, tx_pkt_p['IP'].dst)) + + # TODO: verify inner Ether() + + sys.exit(0) + +if __name__ == "__main__": + main() diff --git a/resources/traffic_scripts/vxlan.py b/resources/traffic_scripts/vxlan.py new file mode 100644 index 0000000000..bf86f179a8 --- /dev/null +++ b/resources/traffic_scripts/vxlan.py @@ -0,0 +1,17 @@ +from scapy.fields import BitField, XByteField, X3BytesField +from scapy.packet import Packet, bind_layers +from scapy.layers.l2 import Ether +from scapy.layers.inet import UDP + + +class VXLAN(Packet): + name = "VXLAN" + fields_desc = [BitField("flags", 0x08000000, 32), + X3BytesField("vni", 0), + XByteField("reserved", 0x00)] + + def mysummary(self): + return self.sprintf("VXLAN (vni=%VXLAN.vni%)") + +bind_layers(UDP, VXLAN, dport=4789) +bind_layers(VXLAN, Ether) |