From: Ondrej Lichtner olichtne@redhat.com
The following patch set brings in experimental support for Open vSwitch bridges on linux machines. Naturally it requires the involved slave machines to have open vswitch installed.
At the moment this is very experimental and anything can change, including the recipe format. I'll be happy to reply to any questions or suggestions.
An example recipe that you can try: <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" vlan_tag="1"/> <slave id="t2" vlan_tag="1"/> </slaves> <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 | 60 +++++++++++++++++++++++--- lnst/Slave/InterfaceManager.py | 2 +- lnst/Slave/NetConfigDevice.py | 82 +++++++++++++++++++++++++++++++++++- lnst/Slave/NmConfigDevice.py | 19 ++++++++- schema-recipe.rng | 60 ++++++++++++++++++++++++++ 7 files changed, 227 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. These slaves have an optional attribute vlan_tag that makes the port an access port for the specified VLAN, or an attribute vlan_tags if the port is a trunk port. Only one of these attributes can be used at a time.
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" vlan_tag="1"/> <slave id="t2" vlan_tag="1"/> </slaves> <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 | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+)
diff --git a/schema-recipe.rng b/schema-recipe.rng index 129d7e7..08e5107 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,65 @@ </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"/> + + <optional> + <choice> + <attribute name="vlan_tag"/> + <attribute name="vlan_tags"/> + </choice> + </optional> + </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> + </interleave> + </element> + </define> + <define name="vlan"> <element name="vlan"> <attribute name="id"/>
Thu, Mar 27, 2014 at 01:57:06PM CET, olichtne@redhat.com wrote:
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. These slaves have an optional attribute vlan_tag that makes the port an access port for the specified VLAN, or an attribute vlan_tags if the port is a trunk port. Only one of these attributes can be used at a time.
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" vlan_tag="1"/> <slave id="t2" vlan_tag="1"/> </slaves> <bond id="mfg"> <slaves> <slave id="t1"/> <slave id="t2"/> </slaves> </bond>
I wonder if the vlan definition should not be on the same level as bond. That would make more sense to me. Just define slaves in <slaves> tag and after that, model the topo.
</ovs_bridge>
Signed-off-by: Ondrej Lichtner olichtne@redhat.com
schema-recipe.rng | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+)
diff --git a/schema-recipe.rng b/schema-recipe.rng index 129d7e7..08e5107 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,65 @@ </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"/>
<optional>
<choice>
<attribute name="vlan_tag"/>
<attribute name="vlan_tags"/>
</choice>
</optional>
</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>
</interleave>
</element>
</define>
<define name="vlan"> <element name="vlan"> <attribute name="id"/>
-- 1.8.5.3
LNST-developers mailing list LNST-developers@lists.fedorahosted.org https://lists.fedorahosted.org/mailman/listinfo/lnst-developers
Yeah, I've thought about that and it shouldn't be a huge change if we want it. I chose this variation because it is more concise if you want to define a VLAN trunk port - you just do <slave id="xyz" vlan_tags="1,2,3"/> instead of defining 3 vlans and adding the port to all of them. Note that a slave tag can have either the "vlan_tag" or "vlan_tags" attribute (or neither).
I accept that this might not be the best choice of attributes (if we use them at all), so I'm willing to iterate and improve on this as I've explained in the cover letter.
----- Original Message ----- From: "Jiri Pirko" jpirko@redhat.com To: olichtne@redhat.com Cc: lnst-developers@lists.fedorahosted.org Sent: Friday, March 28, 2014 7:50:58 AM Subject: Re: [PATCH 1/5] schema-recipe.rng: add element ovs_bridge
An example use in a recipe:
<ovs_bridge id="ovs_br"> <slaves> <slave id="t1" vlan_tag="1"/> <slave id="t2" vlan_tag="1"/> </slaves> <bond id="mfg"> <slaves> <slave id="t1"/> <slave id="t2"/> </slaves> </bond>
I wonder if the vlan definition should not be on the same level as bond. That would make more sense to me. Just define slaves in <slaves> tag and after that, model the topo.
Fri, Mar 28, 2014 at 10:04:49AM CET, olichtne@redhat.com wrote:
Yeah, I've thought about that and it shouldn't be a huge change if we want it. I chose this variation because it is more concise if you want to define a VLAN trunk port - you just do <slave id="xyz" vlan_tags="1,2,3"/> instead of defining 3 vlans and adding the port to all of them. Note that a slave tag can have either the "vlan_tag" or "vlan_tags" attribute (or neither).
I think we should be consistent which what we already have for plain vlan devices.
I accept that this might not be the best choice of attributes (if we use them at all), so I'm willing to iterate and improve on this as I've explained in the cover letter.
----- Original Message ----- From: "Jiri Pirko" jpirko@redhat.com To: olichtne@redhat.com Cc: lnst-developers@lists.fedorahosted.org Sent: Friday, March 28, 2014 7:50:58 AM Subject: Re: [PATCH 1/5] schema-recipe.rng: add element ovs_bridge
An example use in a recipe:
<ovs_bridge id="ovs_br"> <slaves> <slave id="t1" vlan_tag="1"/> <slave id="t2" vlan_tag="1"/> </slaves> <bond id="mfg"> <slaves> <slave id="t1"/> <slave id="t2"/> </slaves> </bond>
I wonder if the vlan definition should not be on the same level as bond. That would make more sense to me. Just define slaves in <slaves> tag and after that, model the topo.
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 | 60 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 5 deletions(-)
diff --git a/lnst/Controller/RecipeParser.py b/lnst/Controller/RecipeParser.py index e3b6df9..123f7b3 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,58 @@ 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) + slave_vlans = iface["ovs_conf"]["vlan_tags"] = 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"]) + + slave_vlans[slave["id"]] = XmlCollection(slave_tag) + if self._has_attribute(slave_tag, "vlan_tag"): + vlan_tag = self._get_attribute(slave_tag, "vlan_tag") + slave_vlans[slave["id"]].append(vlan_tag) + elif self._has_attribute(slave_tag, "vlan_tags"): + vlan_tags = str(self._get_attribute(slave_tag, "vlan_tags")) + for vlan_tag in vlan_tags.split(','): + slave_vlans[slave["id"]].append(vlan_tag) + + iface["slaves"].append(slave) + + bonded_slaves = {} + bonds_tags = iface_tag.findall("bond") + if len(bonds_tags) > 0: + bonds = iface["ovs_conf"]["bonds"] = XmlData(slaves_tag) + for bond_tag in bonds_tags: + 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 348c064..d82785d 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 | 82 ++++++++++++++++++++++++++++++++++++++++++- lnst/Slave/NmConfigDevice.py | 19 +++++++++- 2 files changed, 99 insertions(+), 2 deletions(-)
diff --git a/lnst/Slave/NetConfigDevice.py b/lnst/Slave/NetConfigDevice.py index e279d66..c3cf74c 100644 --- a/lnst/Slave/NetConfigDevice.py +++ b/lnst/Slave/NetConfigDevice.py @@ -294,13 +294,93 @@ 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"] + vlan_tags = self._dev_config["ovs_conf"]["vlan_tags"] + + 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() + + if len(vlan_tags[slave_id]) == 0: + tags = "" + elif len(vlan_tags[slave_id]) == 1: + tags = " tag=%s" % vlan_tags[slave_id][0] + elif len(vlan_tags[slave_id]) > 1: + tags = " trunks=" + ",".join(vlan_tags[slave_id]) + 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")
lnst-developers@lists.fedorahosted.org