[PATCH v3 1/3] Generate libvirt's interface XML definition for vlan tagged bridge

To support vlan tagged virtual network, kimchi needs create the underlying vlan and bridge interface. Libvirt's interface driver can do it with a given XML definition. This patch targets to generate the XML according to the interface and vlan id. Signed-off-by: Mark Wu <wudxw@linux.vnet.ibm.com> --- src/kimchi/networkxml.py | 19 +++++++++++++++++++ tests/test_networkxml.py | 22 ++++++++++++++++++++++ tests/utils.py | 6 ++++++ 3 files changed, 47 insertions(+) diff --git a/src/kimchi/networkxml.py b/src/kimchi/networkxml.py index 786cb69..63cb210 100644 --- a/src/kimchi/networkxml.py +++ b/src/kimchi/networkxml.py @@ -21,6 +21,10 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA import ipaddr +import lxml.etree as ET + + +from lxml.builder import E # FIXME, do not support ipv6 @@ -109,3 +113,18 @@ def to_network_xml(**kwargs): </network> """ % params return xml + + +def create_vlan_tagged_bridge_xml(bridge, interface, vlan_id): + vlan = E.vlan(E.interface(name=interface)) + vlan.set('tag', vlan_id) + m = E.interface( + E.start(mode='onboot'), + E.bridge( + E.interface( + vlan, + type='vlan', + name='.'.join([interface, vlan_id]))), + type='bridge', + name=bridge) + return ET.tostring(m) diff --git a/tests/test_networkxml.py b/tests/test_networkxml.py index 3073bce..42b3ea9 100644 --- a/tests/test_networkxml.py +++ b/tests/test_networkxml.py @@ -25,6 +25,9 @@ import unittest import kimchi.networkxml as nxml +import utils + + from kimchi.xmlutils import xpath_get_text @@ -151,3 +154,22 @@ class NetworkXmlTests(unittest.TestCase): netmask = xpath_get_text(xml, "/network/ip/@netmask")[0] self.assertEquals(netmask, str(ipaddr.IPNetwork(params["net"]).netmask)) + + +class InterfaceXmlTests(unittest.TestCase): + + def test_vlan_tagged_bridge_no_ip(self): + expected_xml = """ + <interface type='bridge' name='br10'> + <start mode='onboot'/> + <bridge> + <interface type='vlan' name='em1.10'> + <vlan tag='10'> + <interface name='em1'/> + </vlan> + </interface> + </bridge> + </interface> + """ + actual_xml = nxml.create_vlan_tagged_bridge_xml('br10', 'em1', '10') + self.assertEquals(actual_xml, utils.normalize_xml(expected_xml)) diff --git a/tests/utils.py b/tests/utils.py index 008f668..79fc2e2 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -32,6 +32,7 @@ import unittest from contextlib import closing +from lxml import etree import kimchi.server @@ -159,3 +160,8 @@ def patch_auth(): import kimchi.auth kimchi.auth.authenticate = _authenticate + + +def normalize_xml(xml_str): + return etree.tostring(etree.fromstring(xml_str, + etree.XMLParser(remove_blank_text=True))) -- 1.8.4.2

It creates a vlan interface on top of the given nic or bond interface, and bridge the vlan interface to a new created bridge, which is used to forward VM's traffic. So all packets transmitted from VM will be tagged before it departs from the physical network interface. Signed-off-by: Mark Wu <wudxw@linux.vnet.ibm.com> --- src/kimchi/API.json | 6 ++++++ src/kimchi/model.py | 47 ++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/kimchi/API.json b/src/kimchi/API.json index 3a3c48f..19b1c51 100644 --- a/src/kimchi/API.json +++ b/src/kimchi/API.json @@ -125,6 +125,12 @@ "interface": { "description": "The name of a network interface on the host", "type": "string" + }, + "vlan_id": { + "description": "Network's VLAN ID", + "type": "integer", + "maximum": 4094, + "minimum": 1 } } }, diff --git a/src/kimchi/model.py b/src/kimchi/model.py index a21fcf7..1332b07 100644 --- a/src/kimchi/model.py +++ b/src/kimchi/model.py @@ -58,6 +58,7 @@ from kimchi import config from kimchi import isoinfo from kimchi import netinfo from kimchi import network as knetwork +from kimchi import networkxml from kimchi import vnc from kimchi import xmlutils from kimchi.asynctask import AsyncTask @@ -66,7 +67,6 @@ from kimchi.exception import InvalidOperation, InvalidParameter, MissingParamete from kimchi.exception import NotFoundError, OperationFailed from kimchi.featuretests import FeatureTests from kimchi.iscsi import TargetClient -from kimchi.networkxml import to_network_xml from kimchi.objectstore import ObjectStore from kimchi.scan import Scanner from kimchi.screenshot import VMScreenshot @@ -821,7 +821,12 @@ class Model(object): if netinfo.is_bridge(iface): params['bridge'] = iface elif netinfo.is_bare_nic(iface) or netinfo.is_bonding(iface): - params['forward']['dev'] = iface + if params.get('vlan_id') is None: + params['forward']['dev'] = iface + else: + params['bridge'] = \ + self._create_vlan_tagged_bridge(str(iface), + str(params['vlan_id'])) else: raise InvalidParameter("the interface should be bare nic, " "bonding or bridge device.") @@ -845,7 +850,7 @@ class Model(object): if connection == 'bridge': self._set_network_bridge(params) - xml = to_network_xml(**params) + xml = networkxml.to_network_xml(**params) try: network = conn.networkDefineXML(xml) @@ -903,8 +908,44 @@ class Model(object): if network.isActive(): raise InvalidOperation( "Unable to delete the active network %s" % name) + self._remove_vlan_tagged_bridge(network) network.undefine() + def _get_vlan_tagged_bridge_name(self, interface, vlan_id): + return '-'.join(('kimchi', interface, vlan_id)) + + def _is_vlan_tagged_bridge(self, bridge): + return bridge.startswith('kimchi-') + + def _create_vlan_tagged_bridge(self, interface, vlan_id): + br_name = self._get_vlan_tagged_bridge_name(interface, vlan_id) + br_xml = networkxml.create_vlan_tagged_bridge_xml(br_name, interface, + vlan_id) + conn = self.conn.get() + conn.changeBegin() + try: + vlan_tagged_br = conn.interfaceDefineXML(br_xml) + vlan_tagged_br.create() + except libvirt.libvirtError as e: + conn.changeRollback() + raise OperationFailed(e.message) + else: + conn.changeCommit() + return br_name + + def _remove_vlan_tagged_bridge(self, network): + try: + bridge = network.bridgeName() + except libvirt.libvirtError: + pass + else: + if self._is_vlan_tagged_bridge(bridge): + conn = self.conn.get() + iface = conn.interfaceLookupByName(bridge) + if iface.isActive(): + iface.destroy() + iface.undefine() + def add_task(self, target_uri, fn, opaque=None): id = self.next_taskid self.next_taskid = self.next_taskid + 1 -- 1.8.4.2

Reviewed-by: Aline Manera <alinefm@linux.vnet.ibm.com> On 01/07/2014 01:51 AM, Mark Wu wrote:
It creates a vlan interface on top of the given nic or bond interface, and bridge the vlan interface to a new created bridge, which is used to forward VM's traffic. So all packets transmitted from VM will be tagged before it departs from the physical network interface.
Signed-off-by: Mark Wu <wudxw@linux.vnet.ibm.com> --- src/kimchi/API.json | 6 ++++++ src/kimchi/model.py | 47 ++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 3 deletions(-)
diff --git a/src/kimchi/API.json b/src/kimchi/API.json index 3a3c48f..19b1c51 100644 --- a/src/kimchi/API.json +++ b/src/kimchi/API.json @@ -125,6 +125,12 @@ "interface": { "description": "The name of a network interface on the host", "type": "string" + }, + "vlan_id": { + "description": "Network's VLAN ID", + "type": "integer", + "maximum": 4094, + "minimum": 1 } } }, diff --git a/src/kimchi/model.py b/src/kimchi/model.py index a21fcf7..1332b07 100644 --- a/src/kimchi/model.py +++ b/src/kimchi/model.py @@ -58,6 +58,7 @@ from kimchi import config from kimchi import isoinfo from kimchi import netinfo from kimchi import network as knetwork +from kimchi import networkxml from kimchi import vnc from kimchi import xmlutils from kimchi.asynctask import AsyncTask @@ -66,7 +67,6 @@ from kimchi.exception import InvalidOperation, InvalidParameter, MissingParamete from kimchi.exception import NotFoundError, OperationFailed from kimchi.featuretests import FeatureTests from kimchi.iscsi import TargetClient -from kimchi.networkxml import to_network_xml from kimchi.objectstore import ObjectStore from kimchi.scan import Scanner from kimchi.screenshot import VMScreenshot @@ -821,7 +821,12 @@ class Model(object): if netinfo.is_bridge(iface): params['bridge'] = iface elif netinfo.is_bare_nic(iface) or netinfo.is_bonding(iface): - params['forward']['dev'] = iface + if params.get('vlan_id') is None: + params['forward']['dev'] = iface + else: + params['bridge'] = \ + self._create_vlan_tagged_bridge(str(iface), + str(params['vlan_id'])) else: raise InvalidParameter("the interface should be bare nic, " "bonding or bridge device.") @@ -845,7 +850,7 @@ class Model(object): if connection == 'bridge': self._set_network_bridge(params)
- xml = to_network_xml(**params) + xml = networkxml.to_network_xml(**params)
try: network = conn.networkDefineXML(xml) @@ -903,8 +908,44 @@ class Model(object): if network.isActive(): raise InvalidOperation( "Unable to delete the active network %s" % name) + self._remove_vlan_tagged_bridge(network) network.undefine()
+ def _get_vlan_tagged_bridge_name(self, interface, vlan_id): + return '-'.join(('kimchi', interface, vlan_id)) + + def _is_vlan_tagged_bridge(self, bridge): + return bridge.startswith('kimchi-') + + def _create_vlan_tagged_bridge(self, interface, vlan_id): + br_name = self._get_vlan_tagged_bridge_name(interface, vlan_id) + br_xml = networkxml.create_vlan_tagged_bridge_xml(br_name, interface, + vlan_id) + conn = self.conn.get() + conn.changeBegin() + try: + vlan_tagged_br = conn.interfaceDefineXML(br_xml) + vlan_tagged_br.create() + except libvirt.libvirtError as e: + conn.changeRollback() + raise OperationFailed(e.message) + else: + conn.changeCommit() + return br_name + + def _remove_vlan_tagged_bridge(self, network): + try: + bridge = network.bridgeName() + except libvirt.libvirtError: + pass + else: + if self._is_vlan_tagged_bridge(bridge): + conn = self.conn.get() + iface = conn.interfaceLookupByName(bridge) + if iface.isActive(): + iface.destroy() + iface.undefine() + def add_task(self, target_uri, fn, opaque=None): id = self.next_taskid self.next_taskid = self.next_taskid + 1

It adds a checkbox to provide choice of enabling vlan. If it's checked, it all user to type the vlan ID. Signed-off-by: Mark Wu <wudxw@linux.vnet.ibm.com> --- po/en_US.po | 3 +++ po/kimchi.pot | 3 +++ po/pt_BR.po | 3 +++ po/zh_CN.po | 3 +++ ui/css/theme-default/network.css | 9 +++++++++ ui/js/src/kimchi.network.js | 10 +++++++++- ui/pages/tabs/network.html.tmpl | 6 ++++++ 7 files changed, 36 insertions(+), 1 deletion(-) diff --git a/po/en_US.po b/po/en_US.po index f2810db..aa80424 100644 --- a/po/en_US.po +++ b/po/en_US.po @@ -543,6 +543,9 @@ msgstr "Bridged: VMs are connected to physical network directly" msgid "Destination" msgstr "Destination" +msgid "Enable VLAN" +msgstr "Enable VLAN" + msgid "No templates found." msgstr "No templates found." diff --git a/po/kimchi.pot b/po/kimchi.pot index 762f4e4..3a25dc5 100644 --- a/po/kimchi.pot +++ b/po/kimchi.pot @@ -523,6 +523,9 @@ msgstr "" msgid "Destination" msgstr "" +msgid "Enable VLAN" +msgstr "" + msgid "No templates found." msgstr "" diff --git a/po/pt_BR.po b/po/pt_BR.po index 7d59503..c437c0b 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -559,6 +559,9 @@ msgstr "Bridged: Máquinas virtuais estão conectadas diretamente a rede física msgid "Destination" msgstr "Destino" +msgid "Enable VLAN" +msgstr "Ativar VLAN" + msgid "No templates found." msgstr "Nenhum modelo encontrado." diff --git a/po/zh_CN.po b/po/zh_CN.po index de759ef..addae1a 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -541,6 +541,9 @@ msgstr "桥接: 虚拟机直接接入物理网络" msgid "Destination" msgstr "目标设备" +msgid "Enable VLAN" +msgstr "启用VLAN" + msgid "No templates found." msgstr "没有发现模板" diff --git a/ui/css/theme-default/network.css b/ui/css/theme-default/network.css index f7d1e14..8987e9a 100644 --- a/ui/css/theme-default/network.css +++ b/ui/css/theme-default/network.css @@ -351,6 +351,15 @@ margin-left: 28px; } +.network-config .VLAN { + margin-left: 28px; +} + +.network-config .VLAN input[type="text"] { + height: 25px; + width: 60px; +} + .network-config .input-hint-icon { margin: -1px 1px 0 0; display: inline-block; diff --git a/ui/js/src/kimchi.network.js b/ui/js/src/kimchi.network.js index f7b7eed..07cac60 100644 --- a/ui/js/src/kimchi.network.js +++ b/ui/js/src/kimchi.network.js @@ -135,11 +135,13 @@ kimchi.initNetworkCreation = function() { var network = kimchi.getNetworkDialogValues(); var data = { name : network.name, - connection: network.type + connection: network.type, + vlan_id: network.vlan_id, }; if (network.type === kimchi.NETWORK_TYPE_BRIDGE) { data.connection = "bridge"; data.interface = network.interface; + data.vlan_id = network.vlan_id; } kimchi.createNetwork(data, function(result) { network.state = result.state === "active" ? "up" : "down"; @@ -193,6 +195,9 @@ kimchi.openNetworkDialog = function(okCallback) { okCallback(); $("#networkConfig").dialog("close"); }); + $("#enableVlan").on("click", function() { + $("#networkVlanID").prop("disabled", !this.checked); + }); $("#networkConfig").dialog("open"); }; @@ -210,6 +215,7 @@ kimchi.getNetworkDialogValues = function() { }; if (network.type === kimchi.NETWORK_TYPE_BRIDGE) { network.interface = $("#networkInterface").val(); + network.vlan_id = parseInt($("#networkVlanID").val()); } return network; }; @@ -224,6 +230,8 @@ kimchi.cleanNetworkDialog = function() { $("#networkInterface option").removeAttr("selected").find(":first").attr("selected", "selected"); $("#networkFormOk").off("click"); $("#networkFormOk").button("disable"); + $("#networkVlanID").prop("disabled", true); + $("#enableVlan").prop("checked", false); }; kimchi.setupNetworkFormEvent = function() { diff --git a/ui/pages/tabs/network.html.tmpl b/ui/pages/tabs/network.html.tmpl index d850140..308e6d5 100644 --- a/ui/pages/tabs/network.html.tmpl +++ b/ui/pages/tabs/network.html.tmpl @@ -75,6 +75,12 @@ <label>$_("Destination"): </label> <select id="networkInterface"></select> </div> + <div class="VLAN"> + <label>$_("Enable VLAN"): </label> + <input id="enableVlan" type="checkbox" value=""/> + <label>$_("VLAN ID"): </label> + <input type="text" id="networkVlanID" disabled> + </div> </div> </div> </div> -- 1.8.4.2

Reviewed-by: Aline Manera <alinefm@linux.vnet.ibm.com> I have some comments about the UI, but it can be done in other patch. I think it should be nicer if the fields for a bridged network only appears when selecting this option. On 01/07/2014 01:51 AM, Mark Wu wrote:
It adds a checkbox to provide choice of enabling vlan. If it's checked, it all user to type the vlan ID.
Signed-off-by: Mark Wu <wudxw@linux.vnet.ibm.com> --- po/en_US.po | 3 +++ po/kimchi.pot | 3 +++ po/pt_BR.po | 3 +++ po/zh_CN.po | 3 +++ ui/css/theme-default/network.css | 9 +++++++++ ui/js/src/kimchi.network.js | 10 +++++++++- ui/pages/tabs/network.html.tmpl | 6 ++++++ 7 files changed, 36 insertions(+), 1 deletion(-)
diff --git a/po/en_US.po b/po/en_US.po index f2810db..aa80424 100644 --- a/po/en_US.po +++ b/po/en_US.po @@ -543,6 +543,9 @@ msgstr "Bridged: VMs are connected to physical network directly" msgid "Destination" msgstr "Destination"
+msgid "Enable VLAN" +msgstr "Enable VLAN" + msgid "No templates found." msgstr "No templates found."
diff --git a/po/kimchi.pot b/po/kimchi.pot index 762f4e4..3a25dc5 100644 --- a/po/kimchi.pot +++ b/po/kimchi.pot @@ -523,6 +523,9 @@ msgstr "" msgid "Destination" msgstr ""
+msgid "Enable VLAN" +msgstr "" + msgid "No templates found." msgstr ""
diff --git a/po/pt_BR.po b/po/pt_BR.po index 7d59503..c437c0b 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -559,6 +559,9 @@ msgstr "Bridged: Máquinas virtuais estão conectadas diretamente a rede física msgid "Destination" msgstr "Destino"
+msgid "Enable VLAN" +msgstr "Ativar VLAN" + msgid "No templates found." msgstr "Nenhum modelo encontrado."
diff --git a/po/zh_CN.po b/po/zh_CN.po index de759ef..addae1a 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -541,6 +541,9 @@ msgstr "桥接: 虚拟机直接接入物理网络" msgid "Destination" msgstr "目标设备"
+msgid "Enable VLAN" +msgstr "启用VLAN" + msgid "No templates found." msgstr "没有发现模板"
diff --git a/ui/css/theme-default/network.css b/ui/css/theme-default/network.css index f7d1e14..8987e9a 100644 --- a/ui/css/theme-default/network.css +++ b/ui/css/theme-default/network.css @@ -351,6 +351,15 @@ margin-left: 28px; }
+.network-config .VLAN { + margin-left: 28px; +} + +.network-config .VLAN input[type="text"] { + height: 25px; + width: 60px; +} + .network-config .input-hint-icon { margin: -1px 1px 0 0; display: inline-block; diff --git a/ui/js/src/kimchi.network.js b/ui/js/src/kimchi.network.js index f7b7eed..07cac60 100644 --- a/ui/js/src/kimchi.network.js +++ b/ui/js/src/kimchi.network.js @@ -135,11 +135,13 @@ kimchi.initNetworkCreation = function() { var network = kimchi.getNetworkDialogValues(); var data = { name : network.name, - connection: network.type + connection: network.type, + vlan_id: network.vlan_id, }; if (network.type === kimchi.NETWORK_TYPE_BRIDGE) { data.connection = "bridge"; data.interface = network.interface; + data.vlan_id = network.vlan_id; } kimchi.createNetwork(data, function(result) { network.state = result.state === "active" ? "up" : "down"; @@ -193,6 +195,9 @@ kimchi.openNetworkDialog = function(okCallback) { okCallback(); $("#networkConfig").dialog("close"); }); + $("#enableVlan").on("click", function() { + $("#networkVlanID").prop("disabled", !this.checked); + }); $("#networkConfig").dialog("open"); };
@@ -210,6 +215,7 @@ kimchi.getNetworkDialogValues = function() { }; if (network.type === kimchi.NETWORK_TYPE_BRIDGE) { network.interface = $("#networkInterface").val(); + network.vlan_id = parseInt($("#networkVlanID").val()); } return network; }; @@ -224,6 +230,8 @@ kimchi.cleanNetworkDialog = function() { $("#networkInterface option").removeAttr("selected").find(":first").attr("selected", "selected"); $("#networkFormOk").off("click"); $("#networkFormOk").button("disable"); + $("#networkVlanID").prop("disabled", true); + $("#enableVlan").prop("checked", false); };
kimchi.setupNetworkFormEvent = function() { diff --git a/ui/pages/tabs/network.html.tmpl b/ui/pages/tabs/network.html.tmpl index d850140..308e6d5 100644 --- a/ui/pages/tabs/network.html.tmpl +++ b/ui/pages/tabs/network.html.tmpl @@ -75,6 +75,12 @@ <label>$_("Destination"): </label> <select id="networkInterface"></select> </div> + <div class="VLAN"> + <label>$_("Enable VLAN"): </label> + <input id="enableVlan" type="checkbox" value=""/> + <label>$_("VLAN ID"): </label> + <input type="text" id="networkVlanID" disabled> + </div> </div> </div> </div>

Reviewed-by: Aline Manera <alinefm@linux.vnet.ibm.com> On 01/07/2014 01:51 AM, Mark Wu wrote:
To support vlan tagged virtual network, kimchi needs create the underlying vlan and bridge interface. Libvirt's interface driver can do it with a given XML definition. This patch targets to generate the XML according to the interface and vlan id.
Signed-off-by: Mark Wu <wudxw@linux.vnet.ibm.com> --- src/kimchi/networkxml.py | 19 +++++++++++++++++++ tests/test_networkxml.py | 22 ++++++++++++++++++++++ tests/utils.py | 6 ++++++ 3 files changed, 47 insertions(+)
diff --git a/src/kimchi/networkxml.py b/src/kimchi/networkxml.py index 786cb69..63cb210 100644 --- a/src/kimchi/networkxml.py +++ b/src/kimchi/networkxml.py @@ -21,6 +21,10 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
import ipaddr +import lxml.etree as ET + + +from lxml.builder import E
# FIXME, do not support ipv6 @@ -109,3 +113,18 @@ def to_network_xml(**kwargs): </network> """ % params return xml + + +def create_vlan_tagged_bridge_xml(bridge, interface, vlan_id): + vlan = E.vlan(E.interface(name=interface)) + vlan.set('tag', vlan_id) + m = E.interface( + E.start(mode='onboot'), + E.bridge( + E.interface( + vlan, + type='vlan', + name='.'.join([interface, vlan_id]))), + type='bridge', + name=bridge) + return ET.tostring(m) diff --git a/tests/test_networkxml.py b/tests/test_networkxml.py index 3073bce..42b3ea9 100644 --- a/tests/test_networkxml.py +++ b/tests/test_networkxml.py @@ -25,6 +25,9 @@ import unittest
import kimchi.networkxml as nxml +import utils + + from kimchi.xmlutils import xpath_get_text
@@ -151,3 +154,22 @@ class NetworkXmlTests(unittest.TestCase): netmask = xpath_get_text(xml, "/network/ip/@netmask")[0] self.assertEquals(netmask, str(ipaddr.IPNetwork(params["net"]).netmask)) + + +class InterfaceXmlTests(unittest.TestCase): + + def test_vlan_tagged_bridge_no_ip(self): + expected_xml = """ + <interface type='bridge' name='br10'> + <start mode='onboot'/> + <bridge> + <interface type='vlan' name='em1.10'> + <vlan tag='10'> + <interface name='em1'/> + </vlan> + </interface> + </bridge> + </interface> + """ + actual_xml = nxml.create_vlan_tagged_bridge_xml('br10', 'em1', '10') + self.assertEquals(actual_xml, utils.normalize_xml(expected_xml)) diff --git a/tests/utils.py b/tests/utils.py index 008f668..79fc2e2 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -32,6 +32,7 @@ import unittest
from contextlib import closing +from lxml import etree
import kimchi.server @@ -159,3 +160,8 @@ def patch_auth():
import kimchi.auth kimchi.auth.authenticate = _authenticate + + +def normalize_xml(xml_str): + return etree.tostring(etree.fromstring(xml_str, + etree.XMLParser(remove_blank_text=True)))

Needs rebase On 01/07/2014 01:51 AM, Mark Wu wrote:
To support vlan tagged virtual network, kimchi needs create the underlying vlan and bridge interface. Libvirt's interface driver can do it with a given XML definition. This patch targets to generate the XML according to the interface and vlan id.
Signed-off-by: Mark Wu <wudxw@linux.vnet.ibm.com> --- src/kimchi/networkxml.py | 19 +++++++++++++++++++ tests/test_networkxml.py | 22 ++++++++++++++++++++++ tests/utils.py | 6 ++++++ 3 files changed, 47 insertions(+)
diff --git a/src/kimchi/networkxml.py b/src/kimchi/networkxml.py index 786cb69..63cb210 100644 --- a/src/kimchi/networkxml.py +++ b/src/kimchi/networkxml.py @@ -21,6 +21,10 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
import ipaddr +import lxml.etree as ET + + +from lxml.builder import E
# FIXME, do not support ipv6 @@ -109,3 +113,18 @@ def to_network_xml(**kwargs): </network> """ % params return xml + + +def create_vlan_tagged_bridge_xml(bridge, interface, vlan_id): + vlan = E.vlan(E.interface(name=interface)) + vlan.set('tag', vlan_id) + m = E.interface( + E.start(mode='onboot'), + E.bridge( + E.interface( + vlan, + type='vlan', + name='.'.join([interface, vlan_id]))), + type='bridge', + name=bridge) + return ET.tostring(m) diff --git a/tests/test_networkxml.py b/tests/test_networkxml.py index 3073bce..42b3ea9 100644 --- a/tests/test_networkxml.py +++ b/tests/test_networkxml.py @@ -25,6 +25,9 @@ import unittest
import kimchi.networkxml as nxml +import utils + + from kimchi.xmlutils import xpath_get_text
@@ -151,3 +154,22 @@ class NetworkXmlTests(unittest.TestCase): netmask = xpath_get_text(xml, "/network/ip/@netmask")[0] self.assertEquals(netmask, str(ipaddr.IPNetwork(params["net"]).netmask)) + + +class InterfaceXmlTests(unittest.TestCase): + + def test_vlan_tagged_bridge_no_ip(self): + expected_xml = """ + <interface type='bridge' name='br10'> + <start mode='onboot'/> + <bridge> + <interface type='vlan' name='em1.10'> + <vlan tag='10'> + <interface name='em1'/> + </vlan> + </interface> + </bridge> + </interface> + """ + actual_xml = nxml.create_vlan_tagged_bridge_xml('br10', 'em1', '10') + self.assertEquals(actual_xml, utils.normalize_xml(expected_xml)) diff --git a/tests/utils.py b/tests/utils.py index 008f668..79fc2e2 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -32,6 +32,7 @@ import unittest
from contextlib import closing +from lxml import etree
import kimchi.server @@ -159,3 +160,8 @@ def patch_auth():
import kimchi.auth kimchi.auth.authenticate = _authenticate + + +def normalize_xml(xml_str): + return etree.tostring(etree.fromstring(xml_str, + etree.XMLParser(remove_blank_text=True)))
participants (2)
-
Aline Manera
-
Mark Wu