From: Ondrej Lichtner olichtne@redhat.com
Second version of the patchset that brings OvS support to LNST. The difference is the way we define vlans - instead of using the vlan_tag/vlan_tags attributes use the <vlan> element like this: <vlan tag="1"> <slaves> <slave id="port1"/> <slave id="port2"/> </slaves> </vlan>
If a single port is present in more than one vlan it becomes a trunk port.
The previous example recipe therefore becomes this: <lnstrecipe> <network> <host id="1"> <params> <param name="os" value="fedora20"/> </params> <interfaces> <eth id="t1" label="ttnet1"/> <eth id="t2" label="ttnet2"/> <ovs_bridge id="xyz"> <define> <alias name="aa" value="bb"/> </define> <slaves> <slave id="t1"/> <slave id="t2"/> </slaves> <vlan tag="1"> <slaves> <slave id="t1"/> <slave id="t2"/> </slaves> </vlan> <bond id="mfg"> <slaves> <slave id="t1"/> <slave id="t2"/> </slaves> </bond> </ovs_bridge> </interfaces> </host> <host id="2"> <interfaces> <eth id="t1" label="ttnet1"> <addresses> <address value="192.168.100.215/24"/> </addresses> </eth> </interfaces> </host> <host id="3"> <interfaces> <eth id="t2" label="ttnet2"> <addresses> <address value="192.168.100.216/24"/> </addresses> </eth> </interfaces> </host> </network>
<task> <run host="2" module="IcmpPing" timeout="30"> <options> <option name="addr" value="{ip(3,t2)}"/> <option name="count" value="40"/> <option name="interval" value="0"/> <option name="limit_rate" value="95"/> </options> </run> </task> </lnstrecipe>
Ondrej Lichtner (5): schema-recipe.rng: add element ovs_bridge RecipeParser: add support for ovs_bridge element Controller: add support for ovs_conf {Nm, Net}ConfigDevice: add support for OvS bridges InterfaceManager: generate ovs_bridge interface names
lnst/Controller/Machine.py | 10 ++++- lnst/Controller/NetTestController.py | 3 ++ lnst/Controller/RecipeParser.py | 79 ++++++++++++++++++++++++++++++--- lnst/Slave/InterfaceManager.py | 2 +- lnst/Slave/NetConfigDevice.py | 86 +++++++++++++++++++++++++++++++++++- lnst/Slave/NmConfigDevice.py | 19 +++++++- schema-recipe.rng | 78 ++++++++++++++++++++++++++++++++ 7 files changed, 268 insertions(+), 9 deletions(-)
From: Ondrej Lichtner olichtne@redhat.com
This element represents an Open vSwitch bridge. This element has a mandatory child element <slaves> that defines which previously defined interfaces should be connected to the new bridge as ports.
To create VLANs on this bridge we can use the <vlan> elements. This vlan element is different from the element that defines a linux vlan interface. This element defines a virtual lan identified by it's "tag" (a mandatory attribute) that contains ports that are defined in the <slaves> element in the usual way. It is important to note that one port can be in multiple vlans, this makes the port a VLAN trunk.
Finally the bridge can define zero or more bonds by child elements <bond>. These are similar to normal linux bonds, however at the moment they don't accept any options, only the specification of it's slaves. These bond slaves MUST be a subset of the bridges slaves!
As usual the <define> tag can be used anywhere.
An example use in a recipe: <ovs_bridge id="ovs_br"> <slaves> <slave id="t1"/> <slave id="t2"/> </slaves> <vlan tag="1"> <slaves> <slave id="t1"/> </slaves> </vlan> <bond id="mfg"> <slaves> <slave id="t1"/> <slave id="t2"/> </slaves> </bond> </ovs_bridge>
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- schema-recipe.rng | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+)
diff --git a/schema-recipe.rng b/schema-recipe.rng index 129d7e7..b623a99 100644 --- a/schema-recipe.rng +++ b/schema-recipe.rng @@ -134,6 +134,7 @@ <ref name="vlan"/> <ref name="macvlan"/> <ref name="team"/> + <ref name="ovs_bridge"/> </choice> </zeroOrMore> </element> @@ -175,6 +176,83 @@ </element> </define>
+ <define name="ovs_bridge"> + <element name="ovs_bridge"> + <attribute name="id"/> + + <interleave> + <optional> + <ref name="define"/> + </optional> + + <element name="slaves"> + <interleave> + <optional> + <ref name="define"/> + </optional> + + <oneOrMore> + <element name="slave"> + <attribute name="id"/> + </element> + </oneOrMore> + </interleave> + </element> + + <zeroOrMore> + <element name="bond"> + <attribute name="id"/> + + <interleave> + <optional> + <ref name="define"/> + </optional> + + <element name="slaves"> + <interleave> + <optional> + <ref name="define"/> + </optional> + + <oneOrMore> + <element name="slave"> + <attribute name="id"/> + </element> + </oneOrMore> + </interleave> + </element> + </interleave> + </element> + </zeroOrMore> + <zeroOrMore> + <element name="vlan"> + <attribute name="tag"/> + + <interleave> + <optional> + <ref name="define"/> + </optional> + + <element name="slaves"> + <interleave> + <optional> + <ref name="define"/> + </optional> + + <oneOrMore> + <element name="slave"> + <attribute name="id"/> + </element> + </oneOrMore> + </interleave> + </element> + </interleave> + </element> + </zeroOrMore> + </interleave> + </element> + </define> + <define name="vlan"> <element name="vlan"> <attribute name="id"/>
From: Ondrej Lichtner olichtne@redhat.com
The slaves of the ovs_bridge are translated to the internal representation in the same way as slaves of other devices. However information about vlan tags and bonds is stored in a new "ovs_conf" field. I added this field to make it relatively simple to add new features to the recipe schema of ovs_bridges.
In addition to this, the new code checks for conflicts such as definition of the same bond, enslaving the same port in different bonds or referencing non-existing ports.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- lnst/Controller/RecipeParser.py | 79 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 5 deletions(-)
diff --git a/lnst/Controller/RecipeParser.py b/lnst/Controller/RecipeParser.py index e3b6df9..c4fe61f 100644 --- a/lnst/Controller/RecipeParser.py +++ b/lnst/Controller/RecipeParser.py @@ -86,9 +86,6 @@ class RecipeParser(XmlParser): iface["id"] = self._get_attribute(iface_tag, "id") iface["type"] = iface_tag.tag
- if iface["type"] == "eth": - iface["network"] = self._get_attribute(iface_tag, "label") - # params params_tag = iface_tag.find("params") params = self._process_params(params_tag) @@ -106,8 +103,9 @@ class RecipeParser(XmlParser): addr = self._get_content(addr_tag) iface["addresses"].append(addr)
- - if iface["type"] in ["bond", "bridge", "vlan", "macvlan", "team"]: + if iface["type"] == "eth": + iface["network"] = self._get_attribute(iface_tag, "label") + elif iface["type"] in ["bond", "bridge", "vlan", "macvlan", "team"]: # slaves slaves_tag = iface_tag.find("slaves") if slaves_tag is not None and len(slaves_tag) > 0: @@ -129,6 +127,77 @@ class RecipeParser(XmlParser): opts = self._proces_options(opts_tag) if len(opts) > 0: iface["options"] = opts + elif iface["type"] == "ovs_bridge": + slaves_tag = iface_tag.find("slaves") + iface["slaves"] = XmlCollection(slaves_tag) + ovsb_slaves = [] + + iface["ovs_conf"] = XmlData(slaves_tag) + for slave_tag in slaves_tag: + slave = XmlData(slave_tag) + slave["id"] = str(self._get_attribute(slave_tag, "id")) + ovsb_slaves.append(slave["id"]) + + iface["slaves"].append(slave) + + vlan_elems = iface_tag.findall("vlan") + if len(vlan_elems) > 0: + vlans = iface["ovs_conf"]["vlans"] = XmlData(slaves_tag) + for vlan in vlan_elems: + vlan_tag = str(self._get_attribute(vlan, "tag")) + if vlan_tag in vlans: + msg = "VLAN '%s' already defined for "\ + "this ovs_bridge." % vlan_tag + raise RecipeError(msg, vlan) + + vlans[vlan_tag] = XmlData(vlan) + vlans[vlan_tag]["slaves"] = XmlCollection(vlan) + vlan_slaves = vlans[vlan_tag]["slaves"] + + slaves_tag = vlan.find("slaves") + for slave_tag in slaves_tag: + slave_id = str(self._get_attribute(slave_tag, "id")) + if slave_id not in ovsb_slaves: + msg = "No port with id '%s' defined for "\ + "this ovs_bridge." % slave_id + raise RecipeError(msg, slave_tag) + + if slave_id in vlan_slaves: + msg = "Port '%s' already a member of vlan %s"\ + % (slave_id, vlan_tag) + raise RecipeError(msg, slave_tag) + else: + vlan_slaves.append(slave_id) + + bonded_slaves = {} + bond_elems = iface_tag.findall("bond") + if len(bond_elems) > 0: + bonds = iface["ovs_conf"]["bonds"] = XmlData(slaves_tag) + for bond_tag in bond_elems: + bond_id = str(self._get_attribute(bond_tag, "id")) + if bond_id in bonds: + msg = "Bond with id '%s' already defined for "\ + "this ovs_bridge." % bond_id + raise RecipeError(msg, bond_tag) + bonds[bond_id] = XmlData(bond_tag) + bond_slaves = bonds[bond_id]["slaves"] = XmlCollection(bond_tag) + + slaves_tag = bond_tag.find("slaves") + for slave_tag in slaves_tag: + slave_id = str(self._get_attribute(slave_tag, "id")) + if slave_id not in ovsb_slaves: + msg = "No port with id '%s' defined for "\ + "this ovs_bridge." % slave_id + raise RecipeError(msg, slave_tag) + + if slave_id in bonded_slaves: + msg = "Port with id '%s' already in bond with id '%s'"\ + % (slave_id, bonded_slaves[slave_id]) + raise RecipeError(msg, slave_tag) + else: + bonded_slaves[slave_id] = bond_id + + bond_slaves.append(slave_id)
return iface
From: Ondrej Lichtner olichtne@redhat.com
This commit adds Controller support for the ovs_conf field introduced in the previous commit. The data is now propagated into the Interface object representing the ovs_bridge and will be sent to the slave when the configure() method is called.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- lnst/Controller/Machine.py | 10 +++++++++- lnst/Controller/NetTestController.py | 3 +++ 2 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/lnst/Controller/Machine.py b/lnst/Controller/Machine.py index a849e3b..e2f1d3e 100644 --- a/lnst/Controller/Machine.py +++ b/lnst/Controller/Machine.py @@ -393,6 +393,8 @@ class Interface(object):
self._master = None
+ self._ovs_conf = None + def get_id(self): return self._id
@@ -454,6 +456,12 @@ class Interface(object): def get_address(self, num): return self._addresses[num].split('/')[0]
+ def set_ovs_conf(self, ovs_conf): + self._ovs_conf = ovs_conf + + def get_ovs_conf(self): + return self._ovs_conf + def get_prefix(self, num): try: return self._addresses[num].split('/')[1] @@ -465,7 +473,7 @@ class Interface(object): "addresses": self._addresses, "slaves": self._slaves.keys(), "options": self._options, "slave_options": self._slave_options, - "master": None} + "master": None, "ovs_conf": self._ovs_conf}
if self._master != None: config["master"] = self._master.get_id() diff --git a/lnst/Controller/NetTestController.py b/lnst/Controller/NetTestController.py index 40f35b3..2453042 100644 --- a/lnst/Controller/NetTestController.py +++ b/lnst/Controller/NetTestController.py @@ -284,6 +284,9 @@ class NetTestController: for opt in iface_xml_data["options"]: iface.set_option(opt["name"], opt["value"])
+ if "ovs_conf" in iface_xml_data: + iface.set_ovs_conf(iface_xml_data["ovs_conf"]) + def _prepare_tasks(self): self._tasks = [] for task_data in self._recipe["tasks"]:
From: Ondrej Lichtner olichtne@redhat.com
This commit adds the classes NmConfigDeviceOvsBridge and NetConfigDeviceOvsBridge that represent the configuration of an Open vSwitch bridge. The Nm class only defines the is_nm_managed method that is set to return False and check the devices slaves, since NM does not support OvS configuration.
The Net class takes care of the OvS configuration by calling appropriate shell commands, at the moment it can handle the creation and destruction of an OvS bridge, add/remove ports and set their vlan tags and finally add/remove bonds. This is sufficient for basic support of Open vSwitch.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- lnst/Slave/NetConfigDevice.py | 86 ++++++++++++++++++++++++++++++++++++++++++- lnst/Slave/NmConfigDevice.py | 19 +++++++++- 2 files changed, 103 insertions(+), 2 deletions(-)
diff --git a/lnst/Slave/NetConfigDevice.py b/lnst/Slave/NetConfigDevice.py index e279d66..39267a6 100644 --- a/lnst/Slave/NetConfigDevice.py +++ b/lnst/Slave/NetConfigDevice.py @@ -294,13 +294,97 @@ class NetConfigDeviceTeam(NetConfigDeviceGeneric): dbus_option = "-D" if self._should_enable_dbus() else "" exec_cmd("teamdctl %s %s port remove %s" % (dbus_option, dev_name, port_name))
+class NetConfigDeviceOvsBridge(NetConfigDeviceGeneric): + _modulename = "openvswitch" + _moduleload = True + + @classmethod + def type_init(self): + super(NetConfigDeviceOvsBridge, self).type_init() + exec_cmd("mkdir -p /var/run/openvswitch/") + exec_cmd("ovsdb-server --detach --pidfile "\ + "--remote=punix:/var/run/openvswitch/db.sock", + die_on_err=False) + exec_cmd("ovs-vswitchd --detach --pidfile", die_on_err=False) + + def _add_ports(self): + slaves = self._dev_config["slaves"] + vlans = self._dev_config["ovs_conf"]["vlans"] + + br_name = self._dev_config["name"] + + for slave_id in slaves: + slave_dev = self._if_manager.get_mapped_device(slave_id) + slave_name = slave_dev.get_name() + + vlan_tags = [] + for tag, vlan in vlans.iteritems(): + if slave_id in vlan["slaves"]: + vlan_tags.append(tag) + if len(vlan_tags) == 0: + tags = "" + elif len(vlan_tags) == 1: + tags = " tag=%s" % vlan_tags[0] + elif len(vlan_tags) > 1: + tags = " trunks=" + ",".join(vlan_tags) + exec_cmd("ovs-vsctl add-port %s %s%s" % (br_name, slave_name, tags)) + + def _del_ports(self): + slaves = self._dev_config["slaves"] + + br_name = self._dev_config["name"] + + for slave_id in slaves: + slave_dev = self._if_manager.get_mapped_device(slave_id) + slave_name = slave_dev.get_name() + + exec_cmd("ovs-vsctl del-port %s %s" % (br_name, slave_name)) + + def _add_bonds(self): + br_name = self._dev_config["name"] + + bonds = self._dev_config["ovs_conf"]["bonds"] + for bond_id, bond in bonds.iteritems(): + ifaces = "" + for slave in bond["slaves"]: + ifaces += " %s" % slave + exec_cmd("ovs-vsctl add-bond %s %s %s" % (br_name, bond_id, ifaces)) + + def _del_bonds(self): + br_name = self._dev_config["name"] + + bonds = self._dev_config["ovs_conf"]["bonds"] + for bond_id, bond in bonds.iteritems(): + exec_cmd("ovs-vsctl del-port %s %s" % (br_name, bond_id)) + + def configure(self): + dev_cfg = self._dev_config + + br_name = dev_cfg["name"] + exec_cmd("ovs-vsctl add-br %s" % br_name) + + self._add_ports() + + self._add_bonds() + + def deconfigure(self): + dev_cfg = self._dev_config + + self._del_bonds() + + self._del_ports() + + br_name = dev_cfg["name"] + exec_cmd("ovs-vsctl del-br %s" % br_name) + type_class_mapping = { "eth": NetConfigDeviceEth, "bond": NetConfigDeviceBond, "bridge": NetConfigDeviceBridge, "macvlan": NetConfigDeviceMacvlan, "vlan": NetConfigDeviceVlan, - "team": NetConfigDeviceTeam + "team": NetConfigDeviceTeam, + "ovs_bridge": NetConfigDeviceOvsBridge }
def NetConfigDevice(dev_config, if_manager): diff --git a/lnst/Slave/NmConfigDevice.py b/lnst/Slave/NmConfigDevice.py index 40f4b70..460fd26 100644 --- a/lnst/Slave/NmConfigDevice.py +++ b/lnst/Slave/NmConfigDevice.py @@ -781,13 +781,30 @@ class NmConfigDeviceTeam(NmConfigDeviceGeneric): self._rm_slaves() self._rm_team()
+class NmConfigDeviceOvsBridge(NmConfigDeviceGeneric): + #Not supported by NetworkManager + @classmethod + def is_nm_managed(cls, dev_config, if_manager): + managed = False + + for slave_id in get_slaves(dev_config): + slave_dev = if_manager.get_mapped_device(slave_id) + slave_config = slave_dev.get_conf_dict() + if is_nm_managed(slave_config, if_manager) != managed: + msg = "Mixing NM managed and not managed devices in a "\ + "master-slave relationship is not allowed!" + raise Exception(msg) + + return managed + type_class_mapping = { "eth": NmConfigDeviceEth, "bond": NmConfigDeviceBond, "bridge": NmConfigDeviceBridge, "macvlan": NmConfigDeviceMacvlan, "vlan": NmConfigDeviceVlan, - "team": NmConfigDeviceTeam + "team": NmConfigDeviceTeam, + "ovs_bridge": NmConfigDeviceOvsBridge }
def is_nm_managed(dev_config, if_manager):
From: Ondrej Lichtner olichtne@redhat.com
Add the "ovs_bridge" interface type to the assign_name method. The ovs bridges will get names generated as if they were normal bridges.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- lnst/Slave/InterfaceManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnst/Slave/InterfaceManager.py b/lnst/Slave/InterfaceManager.py index a69e4d4..107601c 100644 --- a/lnst/Slave/InterfaceManager.py +++ b/lnst/Slave/InterfaceManager.py @@ -162,7 +162,7 @@ class InterfaceManager(object): return dev.get_name() elif dev_type == "bond": return self._assign_name_generic("t_bond") - elif dev_type == "bridge": + elif dev_type == "bridge" or dev_type == "ovs_bridge": return self._assign_name_generic("t_br") elif dev_type == "macvlan": return self._assign_name_generic("t_macvlan")
Mon, Mar 31, 2014 at 01:25:38PM CEST, olichtne@redhat.com wrote:
From: Ondrej Lichtner olichtne@redhat.com
Second version of the patchset that brings OvS support to LNST. The difference is the way we define vlans - instead of using the vlan_tag/vlan_tags attributes use the <vlan> element like this:
<vlan tag="1"> <slaves> <slave id="port1"/> <slave id="port2"/> </slaves> </vlan>
If a single port is present in more than one vlan it becomes a trunk port.
The previous example recipe therefore becomes this:
<lnstrecipe> <network> <host id="1"> <params> <param name="os" value="fedora20"/> </params> <interfaces> <eth id="t1" label="ttnet1"/> <eth id="t2" label="ttnet2"/> <ovs_bridge id="xyz"> <define> <alias name="aa" value="bb"/> </define> <slaves> <slave id="t1"/> <slave id="t2"/> </slaves> <vlan tag="1"> <slaves> <slave id="t1"/> <slave id="t2"/> </slaves> </vlan> <bond id="mfg"> <slaves> <slave id="t1"/> <slave id="t2"/> </slaves> </bond> </ovs_bridge> </interfaces> </host> <host id="2"> <interfaces> <eth id="t1" label="ttnet1"> <addresses> <address value="192.168.100.215/24"/> </addresses> </eth> </interfaces> </host> <host id="3"> <interfaces> <eth id="t2" label="ttnet2"> <addresses> <address value="192.168.100.216/24"/> </addresses> </eth> </interfaces> </host> </network>
<task> <run host="2" module="IcmpPing" timeout="30"> <options> <option name="addr" value="{ip(3,t2)}"/> <option name="count" value="40"/> <option name="interval" value="0"/> <option name="limit_rate" value="95"/> </options> </run> </task> </lnstrecipe>
Ondrej Lichtner (5): schema-recipe.rng: add element ovs_bridge RecipeParser: add support for ovs_bridge element Controller: add support for ovs_conf {Nm, Net}ConfigDevice: add support for OvS bridges InterfaceManager: generate ovs_bridge interface names
lnst/Controller/Machine.py | 10 ++++- lnst/Controller/NetTestController.py | 3 ++ lnst/Controller/RecipeParser.py | 79 ++++++++++++++++++++++++++++++--- lnst/Slave/InterfaceManager.py | 2 +- lnst/Slave/NetConfigDevice.py | 86 +++++++++++++++++++++++++++++++++++- lnst/Slave/NmConfigDevice.py | 19 +++++++- schema-recipe.rng | 78 ++++++++++++++++++++++++++++++++ 7 files changed, 268 insertions(+), 9 deletions(-)
-- 1.8.5.3
LNST-developers mailing list LNST-developers@lists.fedorahosted.org https://lists.fedorahosted.org/mailman/listinfo/lnst-developers
applied. Thanks!
lnst-developers@lists.fedorahosted.org