From: Aline Manera <alinefm(a)br.ibm.com>
Add a new parameter to network resource to identify the network is in
use by a template or VM, and do not allow user disable/delete it in this
case.
The 'default' network is always 'in use' as Kimchi uses it when creating a
new
template.
Also update API and test cases to reflect this change
Signed-off-by: Aline Manera <alinefm(a)br.ibm.com>
---
docs/API.md | 2 ++
src/kimchi/control/networks.py | 1 +
src/kimchi/i18n.py | 4 ++--
src/kimchi/mockmodel.py | 24 ++++++++++++++++++++++++
src/kimchi/model/networks.py | 31 +++++++++++++++++++++++++++----
tests/test_model.py | 18 +++++++++++++++---
tests/test_rest.py | 12 ++++++++++--
7 files changed, 81 insertions(+), 11 deletions(-)
diff --git a/docs/API.md b/docs/API.md
index 698fc66..672ef14 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -469,6 +469,8 @@ A interface represents available interface on host.
* interface: The name of a network interface on the host.
For bridge network, the interface can be a bridge or nic/bonding
device. For isolated or NAT network, the interface is ignored.
+ * in_use: True if network is in use by a template or virtual machine;
+ False, otherwise.
### Resource: Network
diff --git a/src/kimchi/control/networks.py b/src/kimchi/control/networks.py
index dd7e08d..95e8523 100644
--- a/src/kimchi/control/networks.py
+++ b/src/kimchi/control/networks.py
@@ -39,6 +39,7 @@ class Network(Resource):
def data(self):
return {'name': self.ident,
'vms': self.info['vms'],
+ 'in_use': self.info['in_use'],
'autostart': self.info['autostart'],
'connection': self.info['connection'],
'interface': self.info['interface'],
diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py
index ddb2fcb..589c8cb 100644
--- a/src/kimchi/i18n.py
+++ b/src/kimchi/i18n.py
@@ -180,8 +180,8 @@ messages = {
"KCHNET0014E": _("Network interface must be a string"),
"KCHNET0015E": _("Network VLAN ID must be an integer between 1 and
4094"),
"KCHNET0016E": _("Specify name and type to create a Network"),
- "KCHNET0017E": _("Unable to delete network %(name)s. There are still
some VMs linked to this network."),
- "KCHNET0018E": _("Unable to deactivate network %(name)s. There are
some VMs running linked to this network."),
+ "KCHNET0017E": _("Unable to delete network %(name)s. There are some
virtual machines and/or templates linked to this network."),
+ "KCHNET0018E": _("Unable to deactivate network %(name)s. There are
some virtual machines and/or templates linked to this network."),
"KCHDR0001E": _("Debug report %(name)s does not exist"),
"KCHDR0002E": _("Debug report tool not found in system"),
diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py
index 72764b0..b70cc1b 100644
--- a/src/kimchi/mockmodel.py
+++ b/src/kimchi/mockmodel.py
@@ -596,18 +596,42 @@ class MockModel(object):
vms.append(name)
return vms
+ def _is_network_used_by_template(self, network):
+ for name, tmpl in self._mock_templates.iteritems():
+ if network in tmpl.info['networks']:
+ return True
+ return False
+
+ def _is_network_in_use(self, name):
+ # The network "default" is used for Kimchi proposal and should not be
+ # deactivate or deleted. Otherwise, we will allow user create
+ # inconsistent templates from scratch
+ if name == 'default':
+ return True
+
+ vms = self._get_vms_attach_to_a_network(name)
+ return bool(vms) or self._is_network_used_by_template(name)
+
def network_lookup(self, name):
network = self._get_network(name)
network.info['vms'] = self._get_vms_attach_to_a_network(name)
+ network.info['in_use'] = self._is_network_in_use(name)
+
return network.info
def network_activate(self, name):
self._get_network(name).info['state'] = 'active'
def network_deactivate(self, name):
+ if self._is_network_in_use(name):
+ raise InvalidOperation("KCHNET0018E", {'name': name})
+
self._get_network(name).info['state'] = 'inactive'
def network_delete(self, name):
+ if self._is_network_in_use(name):
+ raise InvalidOperation("KCHNET0017E", {'name': name})
+
# firstly, we should check the network actually exists
network = self._get_network(name)
del self._mock_networks[network.name]
diff --git a/src/kimchi/model/networks.py b/src/kimchi/model/networks.py
index 19d8756..0242156 100644
--- a/src/kimchi/model/networks.py
+++ b/src/kimchi/model/networks.py
@@ -198,6 +198,7 @@ class NetworksModel(object):
class NetworkModel(object):
def __init__(self, **kargs):
self.conn = kargs['conn']
+ self.objstore = kargs['objstore']
def lookup(self, name):
network = self.get_network(self.conn.get(), name)
@@ -228,9 +229,29 @@ class NetworkModel(object):
'subnet': subnet,
'dhcp': dhcp,
'vms': self._get_vms_attach_to_a_network(name),
+ 'in_use': self._is_network_in_use(name),
'autostart': network.autostart() == 1,
'state': network.isActive() and "active" or
"inactive"}
+ def _is_network_in_use(self, name):
+ # The network "default" is used for Kimchi proposal and should not be
+ # deactivate or deleted. Otherwise, we will allow user create
+ # inconsistent templates from scratch
+ if name == 'default':
+ return True
+
+ vms = self._get_vms_attach_to_a_network(name)
+ return bool(vms) or self._is_network_used_by_template(name)
+
+ def _is_network_used_by_template(self, network):
+ with self.objstore as session:
+ templates = session.get_list('template')
+ for tmpl in templates:
+ tmpl_net = session.get('template', tmpl)['networks']
+ if network in tmpl_net:
+ return True
+ return False
+
def _get_vms_attach_to_a_network(self, network, filter="all"):
DOM_STATE_MAP = {'nostate': 0, 'running': 1, 'blocked':
2,
'paused': 3, 'shutdown': 4, 'shutoff':
5,
@@ -255,17 +276,19 @@ class NetworkModel(object):
network.create()
def deactivate(self, name):
- network = self.get_network(self.conn.get(), name)
- if self._get_vms_attach_to_a_network(name, "running"):
+ if self._is_network_in_use(name):
raise InvalidOperation("KCHNET0018E", {'name': name})
+
+ network = self.get_network(self.conn.get(), name)
network.destroy()
def delete(self, name):
+ if self._is_network_in_use(name):
+ raise InvalidOperation("KCHNET0017E", {'name': name})
+
network = self.get_network(self.conn.get(), name)
if network.isActive():
raise InvalidOperation("KCHNET0005E", {'name': name})
- if self._get_vms_attach_to_a_network(name):
- raise InvalidOperation("KCHNET0017E", {'name': name})
self._remove_vlan_tagged_bridge(network)
network.undefine()
diff --git a/tests/test_model.py b/tests/test_model.py
index a3d8eff..60d0b94 100644
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -481,17 +481,29 @@ class ModelTests(unittest.TestCase):
iso = path + 'ubuntu12.04.iso'
iso_gen.construct_fake_iso(iso, True, '12.04', 'ubuntu')
+ args = {'name': 'test-pool',
+ 'path': '/tmp/kimchi-images',
+ 'type': 'dir'}
+ inst.storagepools_create(args)
+ rollback.prependDefer(inst.storagepool_delete, 'test-pool')
+
params = {'name': 'test', 'memory': 1024,
'cpus': 1,
- 'networks': ['test-network'], 'cdrom':
iso}
+ 'networks': ['test-network'], 'cdrom':
iso,
+ 'storagepool': '/storagepools/test-pool'}
inst.templates_create(params)
rollback.prependDefer(inst.template_delete, 'test')
+ # Try to delete network
+ # It should fail as it is associated to a template
+ self.assertRaises(InvalidOperation, inst.network_delete, net_name)
+ # Update template to release network and then delete it
+ params = {'networks': []}
+ inst.template_update('test', params)
inst.network_delete(net_name)
- shutil.rmtree(path)
+ shutil.rmtree(path)
info = inst.template_lookup('test')
self.assertEquals(info['invalid']['cdrom'], [iso])
- self.assertEquals(info['invalid']['networks'], [net_name])
@unittest.skipUnless(utils.running_as_root(), 'Must be run as root')
def test_template_clone(self):
diff --git a/tests/test_rest.py b/tests/test_rest.py
index 92c1a83..831517f 100644
--- a/tests/test_rest.py
+++ b/tests/test_rest.py
@@ -1108,7 +1108,16 @@ class RestTests(unittest.TestCase):
self.assertEquals(201, resp.status)
shutil.rmtree(path)
- # Delete the network
+ # Try to delete network
+ # It should fail as it is associated to a template
+ resp = json.loads(request(host, port, '/networks/test-network',
+ '{}', 'DELETE').read())
+ self.assertIn("KCHNET0017E", resp["reason"])
+
+ # Update template to release network and then delete it
+ params = {'networks': []}
+ req = json.dumps(params)
+ resp = request(host, port, '/templates/test', req, 'PUT')
resp = request(host, port, '/networks/test-network', '{}',
'DELETE')
self.assertEquals(204, resp.status)
@@ -1120,7 +1129,6 @@ class RestTests(unittest.TestCase):
# Verify the template
res = json.loads(self.request('/templates/test').read())
self.assertEquals(res['invalid']['cdrom'], [iso])
- self.assertEquals(res['invalid']['networks'],
['test-network'])
self.assertEquals(res['invalid']['storagepools'],
['test-storagepool'])
# Delete the template
--
1.7.10.4