Skip to content

Instantly share code, notes, and snippets.

@ocelotl
Last active May 10, 2017 16:51
Show Gist options
  • Save ocelotl/1d4c525d360f92c291f5d456499b1069 to your computer and use it in GitHub Desktop.
Save ocelotl/1d4c525d360f92c291f5d456499b1069 to your computer and use it in GitHub Desktop.
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 Hewlett Packard Enterprise Development LP
#
# 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.
"""
The following is an example of a test suite.
A test suite contains one or more test cases that are recognized by pytest,
functions whose name begins with test_.
"""
from time import sleep
from re import search
# To use the debugger uncomment the following line:
# from ipdb import set_trace
# After doing that, add set_trace() wherever you'll like the execution to stop.
# ipdb will launch a debuggin terminal when the execution reaches the
# set_trace(). From that terminal, you can type any object name to find out
# about the object itself. You can run any Python code also, you can do an
# import, you can call print() etc. To continue the execution in the next line
# type n. To continue execution until the next set_trace() (or to the end of
# the execution if no more calls to set_trace are found) type c. ipdb uses the
# same commands that the built-in pdb Python package does, but with
# colorized output and tab autocompletion. The pdb commands are documented
# here: https://docs.python.org/3.4/library/pdb.html#debugger-commands
TOPOLOGY = """
# This string is known as a SZN string, it describes the topology that is to be
# used by every test case in this suite.
# Lines that begin with a # sign (as this one) are comments and are ignored by
# the SZN parser.
#
# This is a diagram of the topology, it consists of two hosts that are
# connected to a switch:
#
# +-------+ +-------+
# | | +--------+ | |
# | hs1 <-----> ops1 <-----> hs2 |
# | | +--------+ | |
# +-------+ +-------+
#
# Every element in the topology is known as a node. The node type defines its
# behavior and capabilities.
#
# The available node types depend on the platform engine that is running the
# topology, for example, the topology_docker platform engine supports "host"
# and "openswitch" types among others.
#
# Please consult the documentation of the platform engine for available node
# types.
#
# Nodes are defined like this:
#
# [attribute_0=value_0, attribute_1=value_1, ...] node_identifier
#
# At least, the attribute list should contain the "type" attribute. The
# attribute types are dependent on the platform engine too, please consult its
# documentation for available attributes.
#
# Here an openswitch node is defined:
#
[type=openswitch name="OpenSwitch 1"] ops1
#
# And now, the two hosts are defined too:
#
[type=host name="Host 1"] hs1
[type=host name="Host 2"] hs2
#
# Nodes are connected together using links, which are defined like this:
#
# node_0_identifier:port_label -- node_1_identifier:port_label
#
# Please be advised that the value that exists in "port_label" is not
# necesarily the exact port identifier that the node will be using when
# the test case is executed.
#
# Is the responsibility of the platform engine to decide which exact port will
# be used to create the link. Because of this, the value of "port_label" may be
# any string.
#
hs1:port1 -- ops1:port6
ops1:port3 -- hs2:port2
"""
# Here is a test case. This particular one receives 2 pytest fixtures:
# 1. topology
# 2. step
# A pytest fixture is a function that defines what is to be executed at the
# beginning of a test case (and possibly also at the end of the test case too).
#
# The topology fixture is provided by the Topology Modular Framework and it
# takes care of creating and destroying the topology, so the test engineer does
# not have to worry about adding code that does this in the test case.
#
# This fixture has a scope that affects the whole suite, so the topology is
# created at the beginning of the first test case in the test suite and
# destroyed at the end of the last test case of the suite.
#
# The test cases in this repo are executed by pytest in a random order, so keep
# that in mind when writing your test cases. Do not assume that the state of a
# device at the end of a test case will be the same at the beginning of
# another. Keep your test cases atomic by putting together in the same test
# case all the code that is to be executed in a certain order.
# The step fixture provides a function that allows the test engineer to inject
# comments in the test execution logs. The execution logs can be found in
# .tox/py34/tmp/tests.xml.
#
# The step-provided function is useful to introduce parts of the test case that
# have a logical meaning for the engineer, for example, to show that the
# following lines configure the nodes, it could be used like this:
#
# ...
# step('Configuring the switch for VLAN usage.')
# ...
def test_case(topology, step):
"""
The documentation for the test case goes here. The following lines are an
example:
Test that a VLAN configuration is functional with an OpenSwitch switch.
Build a topology of one switch and two hosts and connect the hosts to the
switch. Setup a VLAN for the ports connected to the hosts and ping from
host 1 to host 2. Check that the ping and its reply were sent and received
correctly.
"""
# The topology fixture allows the engineer to create Python objects that
# represent the topology devices. Use its get method with a string with the
# node identifier as the parameter:
ops1 = topology.get('ops1')
hs1 = topology.get('hs1')
hs2 = topology.get('hs2')
# Every node object has an attribute named ports, a dictionary whose keys
# are the port labels defined in the test case, its values are the actual
# port labels defined by the platform engine for that specific node.
p3 = ops1.ports['port3']
p6 = ops1.ports['port6']
# Here, step is being used to mark a logical section of the test case,
# adding configuration for switch interfaces.
step('Configuring switch interfaces.')
# To execute a command in the node, you can use this syntax:
#
# node_identifier('command', shell='name_of_the_shell')
#
# Shells are Python objects that encapsulate a way of communicating with a
# node. Each node has a default shell and at least one shell. The default
# shell will be used if the "shell" argument is not specified in the call.
# For example, the following commands are equivalent:
#
# node_identifier('command', shell='name_of_the_default_shell')
# node_identifier('command')
#
# Shells are defined for every node type, please consult their
# documentation for available shells.
# Even when any command can be sent to a node using the previous syntax,
# libraries are the preferred way of doing so because they offer several
# advantages, for example:
# 1. Automating command-related tasks (as switching contexts, for example).
# 2. Handling command output.
# 3. Checking for command correct execution.
# A library is a set of functions that call commands of a certain command
# family. Since each library is tailored for a single command family,
# each one has its own characteristics and architecture.
# Here the library for OpenSwitch vtysh is being used. vtysh has contexts,
# commands are supposed to return output and commands that only return
# some output if something wrong or unexpected happens when they are
# called. The vtysh library handles all these 3 situations automatically so
# they don't have to be handled manually in the test case.
# In this library, contexts are hanlded by using with, like in this line:
with ops1.libs.vtysh.ConfigInterface('port3') as ctx:
# This will take care of sending the necessary commands to enter the
# relevant context to configure the 'port3' interface. This library
# also takes care of using the actual port that matches the port3
# label. Once that line is executed, the ctx object is created, an
# instance of the ConfigInterface class. This class has methods that
# match the methods available in the corresponding vtysh context, two
# of them are these ones:
ctx.no_routing()
ctx.no_shutdown()
# ctx.no_routing sends the vtysh no routing command and asserts that
# output was not received, because this command is not supposed to
# send output if everything works fine. ctx.no_shutdown is analogous.
# Once the last indentation level is exited, the library takes care of
# sending the vtysh end command to return to the vtysh root context. In
# this way, the entering and exiting of contexts is handled automatically.
# Libraries just "wrap" commands, so the past lines are equivalent to:
# ops1('configure terminal')
# ops1('interface {p3}'.format(**locals()))
# ops1('no routing')
# ops1('no shutdown')
# ops1('end')
with ops1.libs.vtysh.ConfigInterface('port6') as ctx:
ctx.no_routing()
ctx.no_shutdown()
step('Adding VLAN.')
with ops1.libs.vtysh.ConfigVlan('8') as ctx:
ctx.no_shutdown()
step('Adding interfaces to VLAN.')
with ops1.libs.vtysh.ConfigInterface('port3') as ctx:
ctx.vlan_access(8)
with ops1.libs.vtysh.ConfigInterface('port6') as ctx:
ctx.vlan_access(8)
# Here a library is not being used, in that case all the raw output is
# received and stored in vlan_result.
vlan_result = ops1('show vlan 8')
# Because of this, the parsing needs to be done in the test case, like
# its shown here:
assert search(
r'8\s+(vlan|VLAN)8\s+up\s+ok\s+({p3}|{p6}),\s*({p3}|{p6})'.format(
**locals()
),
vlan_result
)
step('Configuring interfaces on hosts.')
# As was explained above, libraries are to be design in a way that best
# matches the corresponding command library behavior. Here the ip library
# is being used to configure interfaces in both hosts:
hs1.libs.ip.interface('port1', addr='10.0.10.1/24', up=True)
hs2.libs.ip.interface('port2', addr='10.0.10.2/24', up=True)
sleep(3)
step('Pinging from host 1 to host 2.')
ping = hs1.libs.ping.ping(1, '10.0.10.2')
# Since ping is called using a library, its output can be nicely parsed
# into a dictionary. Here, the dictionary has the 'transmitted' and
# 'received' keys whose associated values are the transmitted and recevied
# bytes, of course.
assert ping['transmitted'] == ping['received'] == 1
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015-2016 Hewlett Packard Enterprise Development LP
#
# 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.
"""
OpenSwitch Test for simple ping between nodes.
"""
from __future__ import unicode_literals, absolute_import
from __future__ import print_function, division
from time import sleep
from .helpers import wait_until_interface_up
TOPOLOGY = """
# +-------+ +-------+
# | | +-------+ +-------+ | |
# | hs1 <-----> sw1 <-----> sw2 <-----> hs2 |
# | | +-------+ +-------+ | |
# +-------+ +-------+
# Nodes
[type=openswitch name="Switch 1"] sw1
[type=openswitch name="Switch 2"] sw2
[type=host name="Host 1"] hs1
[type=host name="Host 2"] hs2
# Links
hs1:1 -- sw1:3
sw1:4 -- sw2:3
sw2:4 -- hs2:1
"""
def test_ping(topology):
"""
Set network addresses and static routes between nodes and ping h2 from h1.
"""
sw1 = topology.get('sw1')
sw2 = topology.get('sw2')
hs1 = topology.get('hs1')
hs2 = topology.get('hs2')
assert sw1 is not None
assert sw2 is not None
assert hs1 is not None
assert hs2 is not None
# Configure IP and bring UP host 1 interfaces
hs1.libs.ip.interface('1', addr='10.0.10.1/24', up=True)
# Configure IP and bring UP host 2 interfaces
hs2.libs.ip.interface('1', addr='10.0.30.1/24', up=True)
# Configure IP and bring UP switch 1 interfaces
with sw1.libs.vtysh.ConfigInterface('3') as ctx:
ctx.ip_address('10.0.10.2/24')
ctx.no_shutdown()
with sw1.libs.vtysh.ConfigInterface('4') as ctx:
ctx.ip_address('10.0.20.1/24')
ctx.no_shutdown()
# Configure IP and bring UP switch 2 interfaces
with sw2.libs.vtysh.ConfigInterface('3') as ctx:
ctx.ip_address('10.0.20.2/24')
ctx.no_shutdown()
with sw2.libs.vtysh.ConfigInterface('4') as ctx:
ctx.ip_address('10.0.30.2/24')
ctx.no_shutdown()
# Wait until interfaces are up
for switch, portlbl in [(sw1, '3'), (sw1, '4'), (sw2, '3'), (sw2, '4')]:
wait_until_interface_up(switch, portlbl)
# Set static routes in switches
sw1.libs.ip.add_route('10.0.30.0/24', '10.0.20.2', shell='bash_swns')
sw2.libs.ip.add_route('10.0.10.0/24', '10.0.20.1', shell='bash_swns')
# Set gateway in hosts
hs1.libs.ip.add_route('default', '10.0.10.2')
hs2.libs.ip.add_route('default', '10.0.30.2')
sleep(1)
ping = hs1.libs.ping.ping(1, '10.0.30.1')
assert ping['transmitted'] == ping['received'] == 1
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 Hewlett Packard Enterprise Development LP
#
# 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.
"""
OpenSwitch Test for simple static routes between nodes.
"""
from __future__ import unicode_literals, absolute_import
from __future__ import print_function, division
TOPOLOGY = """
# +-------+ +-------+ +-------+ +-------+
# | | | | | | | |
# | hs1 <-----> hs2 <-----> hs3 <-----> hs4 |
# | | | | | | | |
# +-------+ +-------+ +-------+ +-------+
# Nodes
[type=host] hs1 hs2 hs3 hs4
# Ports
[ipv4="10.0.10.1/24" up=True] hs1:right
[ipv4="10.0.10.2/24" up=True] hs2:left
[ipv4="10.0.20.1/24" up=True] hs2:right
[ipv4="10.0.20.2/24" up=True] hs3:left
[ipv4="10.0.30.1/24" up=True] hs3:right
[ipv4="10.0.30.2/24" up=True] hs4:left
# Links
hs1:right -- hs2:left
hs2:right -- hs3:left
hs3:right -- hs4:left
"""
def test_route(topology):
"""
Set static routes and test a ping.
"""
hs1 = topology.get('hs1')
hs2 = topology.get('hs2')
hs3 = topology.get('hs3')
hs4 = topology.get('hs4')
assert hs1 is not None
assert hs2 is not None
assert hs3 is not None
assert hs4 is not None
# Setup static routes
hs1.libs.ip.add_route('default', '10.0.10.2')
hs2.libs.ip.add_route('10.0.30.0/24', '10.0.20.2')
hs3.libs.ip.add_route('10.0.10.0/24', '10.0.20.1')
hs4.libs.ip.add_route('default', '10.0.30.1')
# Test ping
ping = hs1.libs.ping.ping(5, '10.0.30.2')
assert ping['transmitted'] == ping['received'] == 5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment