Hello everyone, so after many meetings and ideas, I bring first "runnable" implementation of PyRecipes. Please, read this cover letter, look at the implementation, try to run it, try creating some tests for it and let me know in case you have any questions or recommendations.
Brief explanation of how PyRecipes look: For the first phase of this feature, we agreed on not ditching XML files completely. So you still need .xml file, where network and tasks are defined. Since this implementation is backwards compatible with the old one, you can mix them together.
Network definition: You don't need to specify any <host> tags anymore, all you need is to put attribute python in network tag and write path to Python file with class for network definition in it. Example: <network python="/path/to/pyrecipe_network.py" />
Now let's take a look in what should Python file contain. # 1) Import of lnst.Controller.PyRecipe.LnstNetwork # This class will be used as parent class for our own class with network setup. from lnst.Controller.PyRecipe import LnstNetwork # 2) Class definition with parent class LnstNetwork and method setup() class MyNetwork(LnstNetwork): def setup(self): # here will be described network topology # 3) Described network topology in setup() method like here def setup(self): # create host with id 'm1' m1 = self.add_host(id='m1') # create host with id 'm2' m2 = self.add_host(id='m2')
# create eth interface on host m1, with id 'eth1' in network 'tnet' m1_eth1 = m1.add_interface(id='eth1', type='eth', label='tnet') # create eth interface on host m2, with id 'eth1' in network 'tnet' m2_eth1 = m2.add_interface(id='eth1', type='eth', label='tnet')
# assign IP address to eth interface on m1 m1_eth1.add_address("192.168.111.1/24") # assign IP address to eth interface on m2 m2_eth1.add_address("192.168.111.2/24")
LnstNetwork supported methods: add_host(self, id) ... creates Host
Host supported methods: add_interface(self, type, id, label=None, netns=None) ... creates an Interface Please note that label must be specified only on eth interfaces. netns is optional. Allowed types of interfaces: eth, vlan, bond, team, bridge, veth, macvlan, lo, vti, ovs_bridge, vxlan
add_param(self, param) ... take dictionary with format {'key' : 'value'} and sets it like param for respective host.
pair_veths(self, iface1, iface2) ... takes two veth interfaces as param and pairs them
Interface supported methods: add_address(self, address) ... takes address as string and assigns it to interface
add_addresses(self, addresses) ... runs add_address method on list of addresses
add_ovs_bond_slave(self, bond_id, iface) ... meant to be used on ovs_bridge interface, enslaves iface on ovs bond with id bond_id. If bond with that id does not exist, create it
add_ovs_bond_slaves(self, bond_id, ifaces) ... same as above, but for list of interfaces
add_ovs_vlan_slave(self, vlan_tag, iface) ... same as add_ovs_bond_slave, but for ovs VLANs
add_ovs_vlan_slaves(self, vlan_tag, ifaces) ... same as above, but for list of interfaces
add_option(self, option) ... takes dict in format {'key' : 'value'} and sets it as option for non-eth type interface
add_param(self, param) ... takes dict in format {'key' : 'value'} and sets it as param for eth type interafce
add_slave(self, iface) ... enslaves iface to self (works on vlan, vxlan, team, bond, bridge, macvlan, ovs_bridge)
add_slaves(self, ifaces) ... same as above, but for list of interfaces
Task definition: Task definition remains the same. Example: <task python="/path/to/pyrecipe_task.py" />
The file pyrecipe_task.py will loke like this: #1) Import of LnstTask from lnst.Controller.PyRecipe import LnstTask #2) Create class with Task and method task() class MyTask(LnstTask): def task(self): #3) The rest remains the same, only controller handler #is not imported directly, but is part of LnstTask class. ctl = self.ctl m1 = ctl.get_machine("m1") ...
Methods available for use are same TaskAPI methods as before. You can have multiple classes with tasks in one file, example: class MyTask1(LnstTask): def task(self): ... class MyTask2(LnstTask): def task(self): ...
For more info ask me on lnst-dev mailing list.
Thanks, Jiri
Jiri Prochazka (5): PyRecipes: add PyRecipe module RecipeParser: add support for PyRecipes schema-recipe: add support for PyRecipes NetTestController: add support for PyRecipes PyRecipes: add example recipes for PyRecipes implementation
lnst/Controller/NetTestController.py | 39 ++++- lnst/Controller/PyRecipe.py | 225 ++++++++++++++++++++++++++++ lnst/Controller/RecipeParser.py | 12 +- recipes/examples/pyrecipes/2_hosts.py | 12 ++ recipes/examples/pyrecipes/README | 39 +++++ recipes/examples/pyrecipes/bond_vs_team.py | 62 ++++++++ recipes/examples/pyrecipes/bond_vs_team.xml | 4 + recipes/examples/pyrecipes/ping_flood.py | 23 +++ recipes/examples/pyrecipes/ping_flood.xml | 4 + recipes/examples/pyrecipes/vlan.py | 60 ++++++++ recipes/examples/pyrecipes/vlan.xml | 15 ++ schema-recipe.rng | 7 +- 12 files changed, 494 insertions(+), 8 deletions(-) create mode 100644 lnst/Controller/PyRecipe.py create mode 100644 recipes/examples/pyrecipes/2_hosts.py create mode 100644 recipes/examples/pyrecipes/README create mode 100644 recipes/examples/pyrecipes/bond_vs_team.py create mode 100644 recipes/examples/pyrecipes/bond_vs_team.xml create mode 100644 recipes/examples/pyrecipes/ping_flood.py create mode 100644 recipes/examples/pyrecipes/ping_flood.xml create mode 100644 recipes/examples/pyrecipes/vlan.py create mode 100644 recipes/examples/pyrecipes/vlan.xml
This patch brings two new classes, which are used in PyRecipes.
LnstTask: Contains only Controller handle, same as in Task.ctl
LnstNetwork: contains methods for network definition, which are stored in req variable and can be accessed via get_machines() method.
For more info on how to use these classes in recipes, see cover letter.
Signed-off-by: Jiri Prochazka jprochaz@redhat.com --- lnst/Controller/PyRecipe.py | 225 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 lnst/Controller/PyRecipe.py
diff --git a/lnst/Controller/PyRecipe.py b/lnst/Controller/PyRecipe.py new file mode 100644 index 0000000..4fa7880 --- /dev/null +++ b/lnst/Controller/PyRecipe.py @@ -0,0 +1,225 @@ +""" +This module defines LnstTask and LnstNetwork classes which are +parent classes for PyRecipes. + +Copyright 2015 Red Hat, Inc. +Licensed under the GNU General Public License, version 2 as +published by the Free Software Foundation; see COPYING for details. +""" + +__author__ = """ +jprochaz@redhat.com (Jiri Prochazka) +""" + +class LnstTask(object): + def __init__(self, ctl): + self.ctl = ctl + +class LnstNetwork(object): + def __init__(self): + self.req = {'machines' : []} + + def get_machines(self): + return self.req['machines'] + + def add_host(self, id): + if self.id_exists(id): + msg = "Machine with id '%s' already exists" % id + raise Exception(msg) + + self.req['machines'].append({'id' : id, + 'interfaces' : []}) + + return Host(self.req['machines'][-1]) + + def id_exists(self, id): + for m in self.req['machines']: + if id == m['id']: + return True + return False + +class Host(object): + def __init__(self, req): + self.req = req + self.id = req['id'] + + def add_interface(self, type, id, label=None, netns=None): + if type not in ['eth', 'vlan', 'team', 'bond', 'bridge', 'veth', + 'macvlan', 'lo', 'vti', 'ovs_bridge', 'vxlan']: + msg = "Unknown interface type '%s'" % type + raise Exception(msg) + + if self.id_exists(id): + msg = "Machine with id '%s' already exists" % id + raise Exception(msg) + + if label is not None and type != 'eth': + msg = "Attribute label can be defined only on eth interfaces" + raise Exception(msg) + + if label is None and type == 'eth': + msg = "Missing attribute label on interface '%s'" % id + raise Exception(msg) + + self.req['interfaces'].append({ + 'id' : id, + 'type' : type, + 'netns' : netns + }) + if type == 'eth': + self.req['interfaces'][-1]['network'] = label + + return Interface(self.req['interfaces'][-1]) + + def add_param(self, param): + if 'params' not in self.req: + self.req['params'] = [] + + if type(param) != dict: + msg = "Param must be dict() type" + raise Exception(msg) + + name, value = param.popitem() + + for p in self.req['params']: + if p['name'] == name: + msg = "Param with name '%s' already exists on host '%s'" % (name, self.id) + raise Exception(msg) + + self.req['params'].append({'name' : name, 'value' : value}) + + def id_exists(self, id): + for i in self.req['interfaces']: + if id == i['id']: + return True + return False + + def pair_veths(self, iface1, iface2): + if iface1.type != 'veth': + msg = "Interface '%s' needs to be veth type for veth pairing" % iface1.req['id'] + raise Exception(msg) + else: + if 'peer' in iface1.req: + msg = "Interface '%s' already is in veth pair" % iface1.req['id'] + raise Exception(msg) + iface1.req['peer'] = iface2.req['id'] + + if iface2.type != 'veth': + msg = "Interface '%s' needs to be veth type for veth pairing" % iface2.req['id'] + raise Exception(msg) + else: + if 'peer' in iface2.req: + msg = "Interface '%s' already is in veth pair" % iface2.req['id'] + raise Exception(msg) + iface2.req['peer'] = iface1.req['id'] + +class Interface(object): + def __init__(self, req): + self.req = req + self.id = req['id'] + self.type = req['type'] + + def add_address(self, address): + if 'addresses' not in self.req: + self.req['addresses'] = [] + + if address not in self.req['addresses']: + self.req['addresses'].append(address) + + def add_addresses(self, addresses): + for address in addresses: + self.add_address(address) + + def add_ovs_bond_slave(self, bond_id, iface): + if self.type != 'ovs_bridge': + msg = "Open vSwitch bond slave can be assigned only to ovs_bridge" + raise Exception(msg) + + if 'ovs_conf' not in self.req: + self.req['ovs_conf'] = {'bonds' : {}, + 'vlans' : {}} + if bond_id not in self.req['ovs_conf']['bonds']: + self.req['ovs_conf']['bonds'][bond_id] = {'slaves' : []} + + if iface.id not in self.req['ovs_conf']['bonds'][bond_id]['slaves']: + self.req['ovs_conf']['bonds'][bond_id]['slaves'].append(iface.id) + + def add_ovs_bond_slaves(self, bond_id, ifaces): + for iface in ifaces: + self.add_ovs_bond_slave(bond_id, iface) + + def add_ovs_vlan_slave(self, vlan_tag, iface): + if self.type != 'ovs_bridge': + msg = "Open vSwitch VLAN slave can be assigned only to ovs_bridge" + raise Exception(msg) + + if 'ovs_conf' not in self.req: + self.req['ovs_conf'] = {'bonds' : {}, + 'vlans' : {}} + if vlan_tag not in self.req['ovs_conf']['vlans']: + self.req['ovs_conf']['vlans'][vlan_tag] = {'slaves' : []} + + if iface.id not in self.req['ovs_conf']['vlans'][vlan_tag]['slaves']: + self.req['ovs_conf']['vlans'][vlan_tag]['slaves'].append(iface.id) + + def add_ovs_vlan_slaves(self, vlan_tag, ifaces): + for iface in ifaces: + self.add_ovs_vlan_slave(vlan_tag, iface) + + def add_option(self, option): + if self.type == 'eth': + msg = "Options can't be specified on eth interfaces" + raise Exception(msg) + + if 'options' not in self.req: + self.req['options'] = [] + + name, value = option.popitem() + + for o in self.req['options']: + if o['name'] == name: + msg = "Option with name '%s' already exists on interface '%s'" % (name, self.id) + raise Exception(msg) + + self.req['options'].append({'name' : name, 'value' : value}) + + def add_param(self, param): + if self.type != 'eth': + msg = "Params must be specified only on eth interfaces" + raise Exception(msg) + + if 'params' not in self.req: + self.req['params'] = [] + + if type(param) != dict: + msg = "Param must be dict() type" + raise Exception(msg) + + name, value = param.popitem() + + for p in self.req['params']: + if p['name'] == name: + msg = "Param with name '%s' already exists on interface '%s'" % (name, self.id) + raise Exception(msg) + + self.req['params'].append({'name' : name, 'value' : value}) + + def add_slave(self, iface): + if self.type not in ['bond', 'vlan', 'team', 'bridge', + 'macvlan', 'ovs_bridge', 'vxlan']: + msg = "Interface type '%s' does not allow slaves" % self.type + raise Exception(msg) + + if 'slaves' not in self.req: + self.req['slaves'] = [] + + for slave in self.req['slaves']: + if iface.id == slave['id']: + msg = "Interface '%s' is already slave of '%s'" % (iface.id, self.id) + raise Exception(msg) + + self.req['slaves'].append({'id' : iface.id}) + + def add_slaves(self, ifaces): + for iface in ifaces: + self.add_slave(iface)
For PyRecipe network definition, the syntax is the same as it was for Python tasks, eg. when network is defined in file network.py, network tag in XML recipe will look like this: <network python="network.py" />
RecipeParser will check if network is defined with python attribute, and returns path to the file in recipe["network"] variable.
Signed-off-by: Jiri Prochazka jprochaz@redhat.com --- lnst/Controller/RecipeParser.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/lnst/Controller/RecipeParser.py b/lnst/Controller/RecipeParser.py index 827279f..d1a9e74 100644 --- a/lnst/Controller/RecipeParser.py +++ b/lnst/Controller/RecipeParser.py @@ -29,10 +29,14 @@ class RecipeParser(XmlParser):
# machines machines_tag = lnst_recipe.find("network") - if machines_tag is not None: - machines = recipe["machines"] = XmlCollection(machines_tag) - for machine_tag in machines_tag: - machines.append(self._process_machine(machine_tag)) + + if self._has_attribute(machines_tag, "python"): + recipe["network"] = self._get_attribute(machines_tag, "python") + else: + if machines_tag is not None: + machines = recipe["machines"] = XmlCollection(machines_tag) + for machine_tag in machines_tag: + machines.append(self._process_machine(machine_tag))
# tasks tasks = recipe["tasks"] = XmlCollection()
We need to modify schema-recipe.rng, because when PyRecipes are used, user no longer needs to define oneOrMore hosts and attribute python in network tag is used for specifying path to file in XML recipe.
Signed-off-by: Jiri Prochazka jprochaz@redhat.com --- schema-recipe.rng | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/schema-recipe.rng b/schema-recipe.rng index ad841bb..b7e3417 100644 --- a/schema-recipe.rng +++ b/schema-recipe.rng @@ -32,14 +32,17 @@
<define name="network"> <element name="network"> + <optional> + <attribute name="python"/> + </optional> <interleave> <optional> <ref name="define"/> </optional>
- <oneOrMore> + <zeroOrMore> <ref name="host"/> - </oneOrMore> + </zeroOrMore> </interleave> </element> </define>
This patch adds support for both Task and Network part of PyRecipes.
Tasks are wrapped in class, so Controller iterates over objects in file with Task and checks if there is a class with parent LnstTask. If there is one, import it and run task() method. This implementation is backwards compatible with old Python tasks.
What is completely new, is using Python recipes for network definition. Processing of PyRecipe network definition is similar to task definition, again, class with parent class LnstNetwork is looked for. If found, instance of this class is created, method setup() is run and result is acquired via get_machines() method. Output of this is dictionary with machines and it's interfaces, which is 1:1 as what RecipeParser generates.
For more info about how to use PyRecipes, please, see cover letter and example recipes.
Signed-off-by: Jiri Prochazka jprochaz@redhat.com --- lnst/Controller/NetTestController.py | 39 ++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-)
diff --git a/lnst/Controller/NetTestController.py b/lnst/Controller/NetTestController.py index 04aa473..9436ccc 100644 --- a/lnst/Controller/NetTestController.py +++ b/lnst/Controller/NetTestController.py @@ -19,6 +19,7 @@ import cPickle import imp import copy import sys +import inspect from time import sleep from lnst.Common.NetUtils import MacPool from lnst.Common.Utils import md5sum, dir_md5sum @@ -28,6 +29,7 @@ from lnst.Controller.RecipeParser import RecipeParser, RecipeError from lnst.Controller.SlavePool import SlavePool from lnst.Controller.Machine import MachineError, VirtualInterface from lnst.Controller.Machine import StaticInterface +from lnst.Controller.PyRecipe import LnstTask, LnstNetwork from lnst.Common.ConnectionHandler import send_data, recv_data from lnst.Common.ConnectionHandler import ConnectionHandler from lnst.Common.Config import lnst_config @@ -76,6 +78,31 @@ class NetTestController: self._parser.set_machines(self._machines) self._parser.set_aliases(defined_aliases, overriden_aliases) self._recipe = self._parser.parse() + if "network" in self._recipe: + root = Path(None, self._recipe_path).get_root() + path = Path(root, self._recipe["network"]) + + if not path.exists(): + msg = "PyRecipe file '%s' not found." % self._recipe["network"] + raise RecipeError(msg, self._recipe) + + cwd = os.getcwd() + name = os.path.basename(path.abs_path()).split(".")[0] + sys.path.append(os.path.dirname(path.resolve())) + os.chdir(os.path.dirname(path.resolve())) + + module = imp.load_source('PyRecipe', path.resolve()) + # iterates over all objects in imported module + for name, obj in inspect.getmembers(module): + # searches for classes with parent class LnstNetwork + if inspect.isclass(obj) and LnstNetwork in obj.__bases__: + n = obj() + n.setup() + self._recipe["machines"] = n.get_machines() + self._recipe = self._recipe.to_dict() + + os.chdir(cwd) + sys.path.remove(os.path.dirname(path.resolve()))
conf_pools = lnst_config.get_pools() pools = {} @@ -346,7 +373,10 @@ class NetTestController: iface.set_netem(iface_xml_data["netem"].to_dict())
if "ovs_conf" in iface_xml_data: - iface.set_ovs_conf(iface_xml_data["ovs_conf"].to_dict()) + if isinstance(iface_xml_data["ovs_conf"], dict): + iface.set_ovs_conf(iface_xml_data["ovs_conf"]) + else: + iface.set_ovs_conf(iface_xml_data["ovs_conf"].to_dict())
if iface_xml_data["netns"] != None: iface.set_netns(iface_xml_data["netns"]) @@ -704,13 +734,18 @@ class NetTestController: sys.path.append(os.path.dirname(task_path.resolve())) os.chdir(os.path.dirname(task_path.resolve())) module = imp.load_source(name, task_path.resolve()) + # iterates over all objects in imported module + for name, obj in inspect.getmembers(module): + # searches for classes with parent class LnstTask + if inspect.isclass(obj) and LnstTask in obj.__bases__: + obj(Task.ctl).task() os.chdir(cwd) sys.path.remove(os.path.dirname(task_path.resolve()))
#restore resource table self._resource_table = res_table_bkp
- return module.ctl._result + return Task.ctl._result
def _run_task(self, task): if "python" in task:
For more info about recipes, see README file in recipes/examples/pyrecipes/ directory.
Signed-off-by: Jiri Prochazka jprochaz@redhat.com --- recipes/examples/pyrecipes/2_hosts.py | 12 ++++++ recipes/examples/pyrecipes/README | 39 ++++++++++++++++++ recipes/examples/pyrecipes/bond_vs_team.py | 62 +++++++++++++++++++++++++++++ recipes/examples/pyrecipes/bond_vs_team.xml | 4 ++ recipes/examples/pyrecipes/ping_flood.py | 23 +++++++++++ recipes/examples/pyrecipes/ping_flood.xml | 4 ++ recipes/examples/pyrecipes/vlan.py | 60 ++++++++++++++++++++++++++++ recipes/examples/pyrecipes/vlan.xml | 15 +++++++ 8 files changed, 219 insertions(+) create mode 100644 recipes/examples/pyrecipes/2_hosts.py create mode 100644 recipes/examples/pyrecipes/README create mode 100644 recipes/examples/pyrecipes/bond_vs_team.py create mode 100644 recipes/examples/pyrecipes/bond_vs_team.xml create mode 100644 recipes/examples/pyrecipes/ping_flood.py create mode 100644 recipes/examples/pyrecipes/ping_flood.xml create mode 100644 recipes/examples/pyrecipes/vlan.py create mode 100644 recipes/examples/pyrecipes/vlan.xml
diff --git a/recipes/examples/pyrecipes/2_hosts.py b/recipes/examples/pyrecipes/2_hosts.py new file mode 100644 index 0000000..3a0d053 --- /dev/null +++ b/recipes/examples/pyrecipes/2_hosts.py @@ -0,0 +1,12 @@ +from lnst.Controller.PyRecipe import LnstNetwork + +class Network(LnstNetwork): + def setup(self): + m1 = self.add_host(id="m1") + m2 = self.add_host(id="m2") + + m1_eth1 = m1.add_interface(id="eth1", type="eth", label="tnet") + m1_eth1.add_address("192.168.111.1/24") + + m2_eth1 = m2.add_interface(id="eth1", type="eth", label="tnet") + m2_eth1.add_address("192.168.111.1/24") diff --git a/recipes/examples/pyrecipes/README b/recipes/examples/pyrecipes/README new file mode 100644 index 0000000..77da20c --- /dev/null +++ b/recipes/examples/pyrecipes/README @@ -0,0 +1,39 @@ +ping_flood.xml +Network description: + Host "m1" + eth1 + + Host "m2" + eth1 + +Test description: + IcmpPing from m1.eth1 to m2.eth1 + +vlan.xml +Network description: + Host "m1" + eth1 + vlan10 on eth1 + Host "m2" + eth1 + vlan10 on eth1 + +Test description: + IcmpPing from m1.vlan10 to m2.vlan10 + Netperf (TCP_STREAM), client m1, server m2 + +team_vs_bond.xml +Network description: + Host "m1" + eth1 + eth2 + bond (active backup) with slaves eth1 and eth2 + + Host "m2" + eth1 + eth2 + team (active backup) with slaves eth1 and eth2 + +Test description: + IcmpPing from m1.bond to m2.team + Netperf (TCP_STREAM), client m1, server m2 diff --git a/recipes/examples/pyrecipes/bond_vs_team.py b/recipes/examples/pyrecipes/bond_vs_team.py new file mode 100644 index 0000000..ee5e2e6 --- /dev/null +++ b/recipes/examples/pyrecipes/bond_vs_team.py @@ -0,0 +1,62 @@ +from lnst.Controller.PyRecipe import LnstTask, LnstNetwork + +class Network(LnstNetwork): + def setup(self): + m1 = self.add_host("m1") + m2 = self.add_host("m2") + + m1_eth1 = m1.add_interface(id="eth1", type="eth", label="tnet") + m1_eth2 = m1.add_interface(id="eth2", type="eth", label="tnet") + m2_eth1 = m2.add_interface(id="eth1", type="eth", label="tnet") + m2_eth2 = m2.add_interface(id="eth2", type="eth", label="tnet") + + m1_bond = m1.add_interface(id="bond", type="bond") + m2_team = m2.add_interface(id="team", type="team") + + m1_bond.add_slaves([m1_eth1, m1_eth2]) + m2_team.add_slaves([m2_eth1, m2_eth2]) + + m1_bond.add_option({'mode' : 'active-backup'}) + m1_bond.add_option({'miimon' : '100'}) + m2_team.add_option({'teamd_config' : '{"hwaddr" : "00:11:22:33:44:55", "runner" : {"name" : "activebackup"}}'}) + + m1_bond.add_address("192.168.111.1/24") + m2_team.add_address("192.168.111.2/24") + +class Task(LnstTask): + def task(self): + ctl = self.ctl + + m1 = ctl.get_host("m1") + m2 = ctl.get_host("m2") + + m1.sync_resources(modules=["IcmpPing", "Netperf"]) + m2.sync_resources(modules=["IcmpPing", "Netperf"]) + + m1_bond = m1.get_interface("bond") + m2_team = m2.get_interface("team") + + ping = ctl.get_module("IcmpPing", + options={ + 'addr' : m2_team.get_ip(0), + 'count' : 100, + 'interval' : 0.1, + 'iface' : m1_bond.get_devname()}) + + nperf_cli = ctl.get_module("Netperf", + options={ + 'role' : 'client', + 'netperf_server' : m2_team.get_ip(0), + 'duration' : '10', + 'testname' : 'TCP_STREAM'}) + nperf_srv = ctl.get_module("Netperf", + options={ + 'role' : 'server', + 'bind' : m2_team.get_ip(0)}) + ctl.wait(2) + m1.run(ping) + + srv_proc = m2.run(nperf_srv, bg=True) + ctl.wait(2) + m1.run(nperf_cli) + srv_proc.intr() diff --git a/recipes/examples/pyrecipes/bond_vs_team.xml b/recipes/examples/pyrecipes/bond_vs_team.xml new file mode 100644 index 0000000..30fd513 --- /dev/null +++ b/recipes/examples/pyrecipes/bond_vs_team.xml @@ -0,0 +1,4 @@ +<lnstrecipe> + <network python="bond_vs_team.py" /> + <task python="bond_vs_team.py" /> +</lnstrecipe> diff --git a/recipes/examples/pyrecipes/ping_flood.py b/recipes/examples/pyrecipes/ping_flood.py new file mode 100644 index 0000000..d07fc96 --- /dev/null +++ b/recipes/examples/pyrecipes/ping_flood.py @@ -0,0 +1,23 @@ +from lnst.Controller.PyRecipe import LnstTask + +class Task(LnstTask): + def task(self): + ctl = self.ctl + + m1 = ctl.get_host("m1") + m2 = ctl.get_host("m2") + + m1.sync_resources(modules=["IcmpPing"]) + m2.sync_resources(modules=["IcmpPing"]) + + m1_eth1 = m1.get_interface("eth1") + m2_eth1 = m2.get_interface("eth1") + + ping_m = ctl.get_module("IcmpPing", + options={ + "addr" : m2_eth1.get_ip(0), + "count" : 100, + "interval" : 0.2, + "iface" : m1_eth1.get_devname()}) + + m1.run(ping_m) diff --git a/recipes/examples/pyrecipes/ping_flood.xml b/recipes/examples/pyrecipes/ping_flood.xml new file mode 100644 index 0000000..3a2fa07 --- /dev/null +++ b/recipes/examples/pyrecipes/ping_flood.xml @@ -0,0 +1,4 @@ +<lnstrecipe> + <network python="2_hosts.py" /> + <task python="ping_flood.py" /> +</lnstrecipe> diff --git a/recipes/examples/pyrecipes/vlan.py b/recipes/examples/pyrecipes/vlan.py new file mode 100644 index 0000000..16faea9 --- /dev/null +++ b/recipes/examples/pyrecipes/vlan.py @@ -0,0 +1,60 @@ +from lnst.Controller.PyRecipe import LnstNetwork, LnstTask + +class Network(LnstNetwork): + def setup(self): + m1 = self.add_host("m1") + m2 = self.add_host("m2") + + m1_eth1 = m1.add_interface(id="eth1", type="eth", label="tnet") + m2_eth1 = m2.add_interface(id="eth1", type="eth", label="tnet") + + m1_vlan10 = m1.add_interface(id="vlan10", type="vlan") + m2_vlan10 = m2.add_interface(id="vlan10", type="vlan") + + m1_vlan10.add_slave(m1_eth1) + m2_vlan10.add_slave(m2_eth1) + + m1_vlan10.add_option({'vlan_tci' : '10'}) + m2_vlan10.add_option({'vlan_tci' : '10'}) + + m1_vlan10.add_address("192.168.111.1/24") + m2_vlan10.add_address("192.168.111.2/24") + +class Task(LnstTask): + def task(self): + ctl = self.ctl + + m1 = ctl.get_host("m1") + m2 = ctl.get_host("m1") + + m1.sync_resources(modules=["IcmpPing", "Netperf"]) + m2.sync_resources(modules=["IcmpPing", "Netperf"]) + + m1_vlan10 = m1.get_interface("vlan10") + m2_vlan10 = m2.get_interface("vlan10") + + ping = ctl.get_module("IcmpPing", + options={ + "addr" : m2_vlan10.get_ip(0), + "count" : 100, + "interval" : 0.2, + "iface" : m1_vlan10.get_devname()}) + + nperf_cli = ctl.get_module("Netperf", + options={ + "role" : "client", + "netperf_server" : m2_vlan10.get_ip(0), + "testname" : "TCP_STREAM", + "duration" : 10}) + + nperf_srv = ctl.get_module("Netperf", + options={ + "role" : "server", + "bind" : m2_vlan10.get_ip(0)}) + + m1.run(ping) + + srv_proc = m2.run(nperf_srv, bg=True) + ctl.wait(2) + m1.run(nperf_cli) + srv_proc.intr() diff --git a/recipes/examples/pyrecipes/vlan.xml b/recipes/examples/pyrecipes/vlan.xml new file mode 100644 index 0000000..e54e6f8 --- /dev/null +++ b/recipes/examples/pyrecipes/vlan.xml @@ -0,0 +1,15 @@ +<lnstrecipe> + <network python="vlan.py"> + <host id="m1"> + <interfaces> + <eth id="eth1" label="tnet" /> + </interfaces> + </host> + <host id="m2"> + <interfaces> + <eth id="eth1" label="tnet" /> + </interfaces> + </host> + </network> + <task python="vlan.py" /> +</lnstrecipe>
lnst-developers@lists.fedorahosted.org