From: Ramon Medeiros <ramonn(a)linux.vnet.ibm.com>
This commit adds a new option to bridged network creation
API: "macvtap". When it is chosen, the usual behavior for
option "bridge" will be applied.
This commit also changes the behavior for the already
existing "bridge" option. Now, when that option is chosen,
a Linux bridge will be created, not a macvtap one.
Signed-off-by: Ramon Medeiros <ramonn(a)linux.vnet.ibm.com>
Signed-off-by: Lucio Correia <luciojhc(a)linux.vnet.ibm.com>i
Changes in V4:
- Fixed tests
- Fixed macvtap use case
---
src/wok/plugins/kimchi/API.json | 2 +-
src/wok/plugins/kimchi/docs/API.md | 11 ++-
src/wok/plugins/kimchi/i18n.py | 5 +-
src/wok/plugins/kimchi/model/networks.py | 108 +++++++++++++++------
src/wok/plugins/kimchi/tests/test_mock_network.py | 4 +-
src/wok/plugins/kimchi/tests/test_model_network.py | 5 +
src/wok/plugins/kimchi/tests/test_networkxml.py | 24 +++++
src/wok/plugins/kimchi/xmlutils/network.py | 25 +++++
8 files changed, 149 insertions(+), 35 deletions(-)
diff --git a/src/wok/plugins/kimchi/API.json b/src/wok/plugins/kimchi/API.json
index ab9ac9b..7addfc7 100644
--- a/src/wok/plugins/kimchi/API.json
+++ b/src/wok/plugins/kimchi/API.json
@@ -353,7 +353,7 @@
"connection": {
"description": "Specifies how this network should be
connected to the other networks",
"type": "string",
- "pattern": "^isolated|nat|bridge$",
+ "pattern": "^isolated|nat|bridge|macvtap$",
"required": true,
"error": "KCHNET0012E"
},
diff --git a/src/wok/plugins/kimchi/docs/API.md b/src/wok/plugins/kimchi/docs/API.md
index 5787755..e1c9396 100644
--- a/src/wok/plugins/kimchi/docs/API.md
+++ b/src/wok/plugins/kimchi/docs/API.md
@@ -627,13 +627,16 @@ A interface represents available interface on host.
networks visible to this host.
* isolated: Create a private, isolated virtual network.
* nat: Outgoing traffic will be routed through the host.
- * bridge: All traffic on this network will be bridged through the indicated
- interface.
+ * macvtap: All traffic on this network will be bridged through the
+ specified interface.
+ * bridge: All traffic on this network will be bridged through a Linux
+ bridged, which is created upon specified interface in case
+ it is not already a Linux bridge.
* subnet *(optional)*: Network segment in slash-separated format with ip address and
prefix or netmask used to create nat network.
* interface *(optional)*: The name of a network interface on the host.
- For bridge network, the interface can be a bridge or nic/bonding
- device.
+ For "macvtap" and "bridge" connections,
the
+ interface can be a nic, bridge or bonding device.
* vlan_id *(optional)*: VLAN tagging ID for the bridge network.
### Resource: Network
diff --git a/src/wok/plugins/kimchi/i18n.py b/src/wok/plugins/kimchi/i18n.py
index aede103..a72f1ee 100644
--- a/src/wok/plugins/kimchi/i18n.py
+++ b/src/wok/plugins/kimchi/i18n.py
@@ -256,7 +256,7 @@ messages = {
"KCHNET0009E": _("Unable to find a free IP address for network
'%(name)s'"),
"KCHNET0010E": _("The interface %(iface)s already exists."),
"KCHNET0011E": _("Network name must be a string without slashes (/) or
quotes (\")"),
- "KCHNET0012E": _("Supported network types are isolated, NAT and
bridge"),
+ "KCHNET0012E": _("Supported network types are isolated, NAT, macvtap
and bridge"),
"KCHNET0013E": _("Network subnet must be a string with IP address and
prefix or netmask"),
"KCHNET0014E": _("Network interface must be a string"),
"KCHNET0015E": _("Network VLAN ID must be an integer between 1 and
4094"),
@@ -267,6 +267,9 @@ messages = {
"KCHNET0020E": _("Failed to activate interface %(iface)s:
%(err)s."),
"KCHNET0021E": _("Failed to activate interface %(iface)s. Please check
the physical link status."),
"KCHNET0022E": _("Failed to start network %(name)s. Details:
%(err)s"),
+ "KCHNET0023E": _("Unable to get XML definition for interface %(name)s.
Details: %(err)s"),
+ "KCHNET0024E": _("Unable to redefine interface %(name)s. Details:
%(err)s"),
+ "KCHNET0025E": _("Unable to create bridge %(name)s. Details:
%(err)s"),
"KCHSR0001E": _("Storage server %(server)s was not used by
Kimchi"),
diff --git a/src/wok/plugins/kimchi/model/networks.py
b/src/wok/plugins/kimchi/model/networks.py
index 71ea595..89c8c3a 100644
--- a/src/wok/plugins/kimchi/model/networks.py
+++ b/src/wok/plugins/kimchi/model/networks.py
@@ -21,19 +21,21 @@ import ipaddr
import libvirt
import sys
import time
+from libvirt import VIR_INTERFACE_XML_INACTIVE
from xml.sax.saxutils import escape
from wok.config import PluginPaths
from wok.exception import InvalidOperation, InvalidParameter
from wok.exception import MissingParameter, NotFoundError, OperationFailed
-from wok.rollbackcontext import RollbackContext
from wok.utils import run_command, wok_log
from wok.xmlutils.utils import xpath_get_text
from wok.plugins.kimchi import netinfo
from wok.plugins.kimchi import network as knetwork
from wok.plugins.kimchi.osinfo import defaults as tmpl_defaults
+from wok.plugins.kimchi.xmlutils.network import create_linux_bridge_xml
from wok.plugins.kimchi.xmlutils.network import create_vlan_tagged_bridge_xml
+from wok.plugins.kimchi.xmlutils.network import get_no_network_config_xml
from wok.plugins.kimchi.xmlutils.network import to_network_xml
@@ -82,7 +84,9 @@ class NetworksModel(object):
connection = params["connection"]
# set forward mode, isolated do not need forward
- if connection != 'isolated':
+ if connection == 'macvtap':
+ params['forward'] = {'mode': 'bridge'}
+ elif connection != 'isolated':
params['forward'] = {'mode': connection}
# set subnet, bridge network do not need subnet
@@ -90,7 +94,7 @@ class NetworksModel(object):
self._set_network_subnet(params)
# only bridge network need bridge(linux bridge) or interface(macvtap)
- if connection == 'bridge':
+ if connection in ['bridge', 'macvtap']:
self._set_network_bridge(params)
params['name'] = escape(params['name'])
@@ -159,6 +163,7 @@ class NetworksModel(object):
def _set_network_bridge(self, params):
try:
+ # fails if host interface is already in use by a libvirt network
iface = params['interface']
if iface in self.get_all_networks_interfaces():
msg_args = {'iface': iface, 'network':
params['name']}
@@ -166,11 +171,23 @@ class NetworksModel(object):
except KeyError:
raise MissingParameter("KCHNET0004E", {'name':
params['name']})
+ # Linux bridges cannot be the trunk device of a VLAN
+ if 'vlan_id' in params and \
+ (netinfo.is_bridge(iface) or params['connection'] ==
"bridge"):
+ raise InvalidParameter('KCHNET0019E', {'name': iface})
+
+ # User specified bridge interface, simply use it
self._ensure_iface_up(iface)
if netinfo.is_bridge(iface):
- if 'vlan_id' in params:
- raise InvalidParameter('KCHNET0019E', {'name': iface})
params['bridge'] = iface
+
+ # User wants Linux bridge network, but didn't specify bridge interface
+ elif params['connection'] == "bridge":
+ # create Linux bridge interface first and use it as actual iface
+ iface = self._create_linux_bridge(iface)
+ params['bridge'] = iface
+
+ # connection == macvtap and iface is not bridge
elif netinfo.is_bare_nic(iface) or netinfo.is_bonding(iface):
if params.get('vlan_id') is None:
params['forward']['dev'] = iface
@@ -196,36 +213,73 @@ class NetworksModel(object):
net_dict['bridge'] and interfaces.append(net_dict['bridge'])
return interfaces
+ def _create_bridge(self, name, xml):
+ conn = self.conn.get()
+
+ # check if name exists
+ if name in netinfo.all_interfaces():
+ raise InvalidOperation("KCHNET0010E", {'iface': name})
+
+ # create bridge through libvirt
+ try:
+ bridge = conn.interfaceDefineXML(xml)
+ bridge.create()
+ except libvirt.libvirtError as e:
+ raise OperationFailed("KCHNET0025E", {'name': name,
+ 'err': e.get_error_message()})
+
+ def _create_linux_bridge(self, interface):
+ # get xml definition of interface
+ iface_xml = self._get_interface_desc_xml(interface)
+
+ # Truncate the interface name if it exceeds 13 characters to make sure
+ # the length of bridge name is less than 15 (its maximum value).
+ br_name = KIMCHI_BRIDGE_PREFIX + interface[-13:]
+ br_xml = create_linux_bridge_xml(br_name, interface, iface_xml)
+
+ # drop network config from interface
+ self._redefine_iface_no_network(interface)
+
+ # create and start bridge
+ self._create_bridge(br_name, br_xml)
+
+ return br_name
+
def _create_vlan_tagged_bridge(self, interface, vlan_id):
# Truncate the interface name if it exceeds 8 characters to make sure
# the length of bridge name is less than 15 (its maximum value).
br_name = KIMCHI_BRIDGE_PREFIX + interface[-8:] + '-' + vlan_id
br_xml = create_vlan_tagged_bridge_xml(br_name, interface, vlan_id)
+
+ self._create_bridge(br_name, br_xml)
+
+ return br_name
+
+ def _get_interface_desc_xml(self, name):
conn = self.conn.get()
- bridges = []
- for net in conn.listAllNetworks():
- # Bridged networks do not have a bridge name
- # So in those cases, libvirt raises an error when trying to get
- # the bridge name
- try:
- bridges.append(net.bridgeName())
- except libvirt.libvirtError, e:
- wok_log.error(e.message)
+ try:
+ iface = conn.interfaceLookupByName(name)
+ xml = iface.XMLDesc(flags=VIR_INTERFACE_XML_INACTIVE)
+ except libvirt.libvirtError, e:
+ raise OperationFailed("KCHNET0023E",
+ {'name': name, 'err':
e.get_error_message()})
- if br_name in bridges:
- raise InvalidOperation("KCHNET0010E", {'iface': br_name})
+ return xml
- with RollbackContext() as rollback:
- try:
- vlan_tagged_br = conn.interfaceDefineXML(br_xml, 0)
- rollback.prependDefer(vlan_tagged_br.destroy)
- vlan_tagged_br.create(0)
- except libvirt.libvirtError as e:
- raise OperationFailed(e.message)
- else:
- return br_name
+ def _redefine_iface_no_network(self, name):
+ conn = self.conn.get()
+ # drop network config from definition of interface
+ iface_xml = self._get_interface_desc_xml(name)
+ xml = get_no_network_config_xml(iface_xml.encode("utf-8"))
+
+ try:
+ # redefine interface
+ iface = conn.interfaceDefineXML(xml.encode("utf-8"))
+ except libvirt.libvirtError as e:
+ raise OperationFailed("KCHNET0024E", {'name': name,
+ 'err': e.get_error_message()})
class NetworkModel(object):
def __init__(self, **kargs):
@@ -333,7 +387,7 @@ class NetworkModel(object):
if network.isActive():
raise InvalidOperation("KCHNET0005E", {'name': name})
- self._remove_vlan_tagged_bridge(network)
+ self._remove_bridge(network)
network.undefine()
@staticmethod
@@ -369,7 +423,7 @@ class NetworkModel(object):
'interface': forward_if,
'pf': forward_pf}}
- def _remove_vlan_tagged_bridge(self, network):
+ def _remove_bridge(self, network):
try:
bridge = network.bridgeName()
except libvirt.libvirtError:
diff --git a/src/wok/plugins/kimchi/tests/test_mock_network.py
b/src/wok/plugins/kimchi/tests/test_mock_network.py
index 8368ced..4b0a284 100644
--- a/src/wok/plugins/kimchi/tests/test_mock_network.py
+++ b/src/wok/plugins/kimchi/tests/test_mock_network.py
@@ -69,6 +69,6 @@ class MockNetworkTests(unittest.TestCase):
)
if len(interfaces) > 0:
iface = interfaces[0]['name']
- _do_network_test(self, model, {'name': u'bridge-network',
- 'connection': 'bridge',
+ _do_network_test(self, model, {'name': u'macvtap-network',
+ 'connection': 'macvtap',
'interface': iface, 'vlan_id':
987})
diff --git a/src/wok/plugins/kimchi/tests/test_model_network.py
b/src/wok/plugins/kimchi/tests/test_model_network.py
index 6be2d39..776e69e 100644
--- a/src/wok/plugins/kimchi/tests/test_model_network.py
+++ b/src/wok/plugins/kimchi/tests/test_model_network.py
@@ -142,6 +142,11 @@ class NetworkTests(unittest.TestCase):
)
if len(interfaces) > 0:
iface = interfaces[0]['name']
+ networks.append({'name': u'macvtap-network',
+ 'connection': 'macvtap',
'interface': iface})
+
+ if len(interfaces) > 1:
+ iface = interfaces[1]['name']
networks.append({'name': u'bridge-network',
'connection': 'bridge',
'interface': iface})
diff --git a/src/wok/plugins/kimchi/tests/test_networkxml.py
b/src/wok/plugins/kimchi/tests/test_networkxml.py
index 69965e6..d4e2c3f 100644
--- a/src/wok/plugins/kimchi/tests/test_networkxml.py
+++ b/src/wok/plugins/kimchi/tests/test_networkxml.py
@@ -171,3 +171,27 @@ class InterfaceXmlTests(unittest.TestCase):
"""
actual_xml = nxml.create_vlan_tagged_bridge_xml('br10', 'em1',
'10')
self.assertEquals(actual_xml, normalize_xml(expected_xml))
+
+ def test_linux_bridge_no_ip(self):
+ em1_xml = """
+ <interface type='ethernet' name='em1'>
+ <start mode='onboot'/>
+ <protocol family='ipv4'>
+ <dhcp/>
+ </protocol>
+ </interface>
+ """
+ expected_xml = """
+ <interface type='bridge' name='br10'>
+ <start mode='onboot'/>
+ <bridge>
+ <interface type='ethernet' name='em1' />
+ </bridge>
+ <protocol family='ipv4'>
+ <dhcp/>
+ </protocol>
+ </interface>
+ """
+ actual_xml = nxml.create_linux_bridge_xml('br10', 'em1',
+ normalize_xml(em1_xml))
+ self.assertEquals(actual_xml, normalize_xml(expected_xml))
diff --git a/src/wok/plugins/kimchi/xmlutils/network.py
b/src/wok/plugins/kimchi/xmlutils/network.py
index c73aad9..14c7e13 100644
--- a/src/wok/plugins/kimchi/xmlutils/network.py
+++ b/src/wok/plugins/kimchi/xmlutils/network.py
@@ -120,3 +120,28 @@ def create_vlan_tagged_bridge_xml(bridge, interface, vlan_id):
type='bridge',
name=bridge)
return ET.tostring(m)
+
+def create_linux_bridge_xml(bridge, interface, iface_xml):
+ m = E.interface(
+ E.start(mode='onboot'),
+ E.bridge(
+ E.interface(
+ type='ethernet',
+ name=interface)),
+ type='bridge',
+ name=bridge)
+
+ # use same network configuration of lower interface
+ iface = ET.fromstring(iface_xml)
+ for element in iface.iter("protocol"):
+ m.append(element)
+
+ return ET.tostring(m)
+
+def get_no_network_config_xml(iface_xml):
+ # remove all protocol elements from interface xml
+ xml = ET.fromstring(iface_xml)
+ for element in xml.iter("protocol"):
+ element.getparent().remove(element)
+
+ return ET.tostring(xml)
--
1.9.1