aboutsummaryrefslogtreecommitdiffstats
path: root/vicn/resource
diff options
context:
space:
mode:
Diffstat (limited to 'vicn/resource')
-rw-r--r--vicn/resource/central.py280
-rw-r--r--vicn/resource/channel.py25
-rw-r--r--vicn/resource/group.py38
-rw-r--r--vicn/resource/gui.py29
-rw-r--r--vicn/resource/icn/ndnpingserver.py2
-rw-r--r--vicn/resource/icn/webserver.py4
-rw-r--r--vicn/resource/ip_assignment.py37
-rw-r--r--vicn/resource/linux/application.py1
-rw-r--r--vicn/resource/linux/bridge.py2
-rw-r--r--vicn/resource/linux/dnsmasq.py10
-rw-r--r--vicn/resource/linux/net_device.py16
-rw-r--r--vicn/resource/linux/package_manager.py55
-rw-r--r--vicn/resource/linux/repository.py7
-rw-r--r--vicn/resource/lxd/lxc_container.py89
-rw-r--r--vicn/resource/lxd/lxd_hypervisor.py45
-rw-r--r--vicn/resource/lxd/lxd_profile.py56
-rw-r--r--vicn/resource/node.py26
17 files changed, 358 insertions, 364 deletions
diff --git a/vicn/resource/central.py b/vicn/resource/central.py
index 1013d1a5..09b24184 100644
--- a/vicn/resource/central.py
+++ b/vicn/resource/central.py
@@ -22,13 +22,14 @@ import os
from netmodel.model.type import String
from netmodel.util.misc import pairwise
-from vicn.core.attribute import Attribute
+from vicn.core.attribute import Attribute, Reference
from vicn.core.exception import ResourceNotFound
from vicn.core.resource import Resource
from vicn.core.task import async_task, inline_task
from vicn.core.task import EmptyTask, BashTask
from vicn.resource.channel import Channel
from vicn.resource.ip.route import IPRoute
+from vicn.resource.group import Group
from vicn.resource.icn.forwarder import Forwarder
from vicn.resource.icn.face import L2Face, L4Face, FaceProtocol
from vicn.resource.icn.producer import Producer
@@ -148,60 +149,63 @@ MAP_ROUTING_STRATEGY = {
# L2 and L4/ICN graphs
#------------------------------------------------------------------------------
-def _get_l2_graph(manager, with_managed = False):
+def _get_l2_graph(groups, with_managed = False):
G = nx.Graph()
- for node in manager.by_type(Node):
- G.add_node(node._state.uuid)
-
- for channel in manager.by_type(Channel):
- if channel.has_type('emulatedchannel'):
- src = channel._ap_if
- for dst in channel._sta_ifs.values():
- if not with_managed and (not src.managed or not dst.managed):
- continue
- if G.has_edge(src.node._state.uuid, dst.node._state.uuid):
- continue
-
- map_node_interface = { src.node._state.uuid : src._state.uuid,
- dst.node._state.uuid: dst._state.uuid}
- G.add_edge(src.node._state.uuid, dst.node._state.uuid,
- map_node_interface = map_node_interface)
- else:
- # This is for a normal Channel
- for src_it in range(0,len(channel.interfaces)):
- src = channel.interfaces[src_it]
-
- # Iterate over the remaining interface to create all the
- # possible combination
- for dst_it in range(src_it+1,len(channel.interfaces)):
- dst = channel.interfaces[dst_it]
-
- if not with_managed and (not src.managed or
- not dst.managed):
+# for node in manager.by_type(Node):
+# G.add_node(node._state.uuid)
+
+ for group in groups:
+ for channel in group.iter_by_type_str('channel'):
+ if channel.has_type('emulatedchannel'):
+ src = channel._ap_if
+ for dst in channel._sta_ifs.values():
+ if not with_managed and (not src.managed or not dst.managed):
continue
if G.has_edge(src.node._state.uuid, dst.node._state.uuid):
continue
- map_node_interface = {
- src.node._state.uuid : src._state.uuid,
+
+ map_node_interface = { src.node._state.uuid : src._state.uuid,
dst.node._state.uuid: dst._state.uuid}
G.add_edge(src.node._state.uuid, dst.node._state.uuid,
map_node_interface = map_node_interface)
+ else:
+ # This is for a normal Channel
+ for src_it in range(0, len(channel.interfaces)):
+ src = channel.interfaces[src_it]
+
+ # Iterate over the remaining interface to create all the
+ # possible combination
+ for dst_it in range(src_it+1,len(channel.interfaces)):
+ dst = channel.interfaces[dst_it]
+
+ if not with_managed and (not src.managed or
+ not dst.managed):
+ continue
+ if G.has_edge(src.node._state.uuid, dst.node._state.uuid):
+ continue
+ map_node_interface = {
+ src.node._state.uuid : src._state.uuid,
+ dst.node._state.uuid: dst._state.uuid}
+ G.add_edge(src.node._state.uuid, dst.node._state.uuid,
+ map_node_interface = map_node_interface)
return G
-def _get_icn_graph(manager):
+def _get_icn_graph(manager, groups):
G = nx.Graph()
- for forwarder in manager.by_type(Forwarder):
- node = forwarder.node
- G.add_node(node._state.uuid)
- for face in forwarder.faces:
- other_face = manager.by_uuid(face._internal_data['sibling_face'])
- other_node = other_face.node
- if G.has_edge(node._state.uuid, other_node._state.uuid):
- continue
- map_node_face = { node._state.uuid: face._state.uuid,
- other_node._state.uuid: other_face._state.uuid }
- G.add_edge(node._state.uuid, other_node._state.uuid,
- map_node_face = map_node_face)
+ for group in groups:
+ # It's safer to iterate on node which we know are in the right groups,
+ # while it might not be the case for the forwarders...
+ for node in group.iter_by_type_str('node'):
+ G.add_node(node._state.uuid)
+ for face in node.forwarder.faces:
+ other_face = manager.by_uuid(face._internal_data['sibling_face'])
+ other_node = other_face.node
+ if G.has_edge(node._state.uuid, other_node._state.uuid):
+ continue
+ map_node_face = { node._state.uuid: face._state.uuid,
+ other_node._state.uuid: other_face._state.uuid }
+ G.add_edge(node._state.uuid, other_node._state.uuid,
+ map_node_face = map_node_face)
return G
@@ -241,14 +245,18 @@ class IPRoutes(Resource):
def _get_ip_origins(self):
origins = dict()
- for node in self._state.manager.by_type(Node):
- node_uuid = node._state.uuid
- if not node_uuid in origins:
- origins[node_uuid] = list()
- for interface in node.interfaces:
- origins[node_uuid].append(interface.ip4_address)
- if interface.ip6_address: #Control interfaces have no v6 address
- origins[node_uuid].append(interface.ip6_address)
+ for group in self.groups:
+ for node in group.iter_by_type_str('node'):
+ node_uuid = node._state.uuid
+ if not node_uuid in origins:
+ origins[node_uuid] = list()
+ for interface in node.interfaces:
+ # XXX temp fix (WouldBlock)
+ try:
+ origins[node_uuid].append(interface.ip4_address)
+ if interface.ip6_address: #Control interfaces have no v6 address
+ origins[node_uuid].append(interface.ip6_address)
+ except: pass
return origins
def _get_ip_routes(self):
@@ -257,7 +265,7 @@ class IPRoutes(Resource):
strategy = MAP_ROUTING_STRATEGY.get(self.routing_strategy)
- G = _get_l2_graph(self._state.manager)
+ G = _get_l2_graph(self.groups)
origins = self._get_ip_origins()
# node -> list(origins for which we have routes)
@@ -294,7 +302,7 @@ class IPRoutes(Resource):
if prefix == next_hop_ingress_ip:
# Direct route on src_node.name :
# route add [prefix] dev [next_hop_interface_.device_name]
- route4 = IPRoute(node = src_node,
+ route = IPRoute(node = src_node,
managed = False,
owner = self,
ip_address = prefix,
@@ -334,7 +342,7 @@ class IPRoutes(Resource):
IP routing strategy : direct routes only
"""
routes = list()
- G = _get_l2_graph(self._state.manager)
+ G = _get_l2_graph(self.groups)
for src_node_uuid, dst_node_uuid, data in G.edges_iter(data = True):
src_node = self._state.manager.by_uuid(src_node_uuid)
dst_node = self._state.manager.by_uuid(dst_node_uuid)
@@ -424,7 +432,7 @@ class ICNFaces(Resource):
protocol = FaceProtocol.from_string(self.protocol_name)
faces = list()
- G = _get_l2_graph(self._state.manager)
+ G = _get_l2_graph(self.groups)
for src_node_uuid, dst_node_uuid, data in G.edges_iter(data = True):
src_node = self._state.manager.by_uuid(src_node_uuid)
dst_node = self._state.manager.by_uuid(dst_node_uuid)
@@ -436,14 +444,16 @@ class ICNFaces(Resource):
log.debug('{} -> {} ({} -> {})'.format(src_node_uuid,
dst_node_uuid, src.device_name, dst.device_name))
+ # XXX This should be moved to the various faces, that register to a
+ # factory
if protocol == FaceProtocol.ether:
src_face = L2Face(node = src_node,
- owner = self,
+ owner = self,
protocol = protocol,
src_nic = src,
dst_mac = dst.mac_address)
dst_face = L2Face(node = dst_node,
- owner = self,
+ owner = self,
protocol = protocol,
src_nic = dst,
dst_mac = src.mac_address)
@@ -451,14 +461,14 @@ class ICNFaces(Resource):
elif protocol in (FaceProtocol.tcp4, FaceProtocol.tcp6,
FaceProtocol.udp4, FaceProtocol.udp6):
src_face = L4Face(node = src_node,
- owner = self,
+ owner = self,
protocol = protocol,
src_ip = src.ip4_address,
dst_ip = dst.ip4_address,
src_port = TMP_DEFAULT_PORT,
dst_port = TMP_DEFAULT_PORT)
dst_face = L4Face(node = dst_node,
- owner = self,
+ owner = self,
protocol = protocol,
src_ip = dst.ip4_address,
dst_ip = src.ip4_address,
@@ -510,17 +520,18 @@ class ICNRoutes(Resource):
def _get_prefix_origins(self):
origins = dict()
- for producer in self._state.manager.by_type(Producer):
- node_uuid = producer.node._state.uuid
- if not node_uuid in origins:
- origins[node_uuid] = list()
- origins[node_uuid].extend(producer.prefixes)
+ for group in self.groups:
+ for producer in group.iter_by_type_str('producer'):
+ node_uuid = producer.node._state.uuid
+ if not node_uuid in origins:
+ origins[node_uuid] = list()
+ origins[node_uuid].extend(producer.prefixes)
return origins
def _get_icn_routes(self):
strategy = MAP_ROUTING_STRATEGY.get(self.routing_strategy)
- G = _get_icn_graph(self._state.manager)
+ G = _get_icn_graph(self._state.manager, self.groups)
origins = self._get_prefix_origins()
routes = list()
@@ -571,104 +582,6 @@ class DnsServerEntry(Resource):
#------------------------------------------------------------------------------
-class ContainerSetup(Resource):
- """
- Resource: ContainerSetup
-
- Setup of container networking
-
- Todo:
- - This should be merged into the LxcContainer resource
- """
-
- container = Attribute(LxcContainer)
-
- #--------------------------------------------------------------------------
- # Resource lifecycle
- #--------------------------------------------------------------------------
-
- def __subresources__(self):
-
- dns_server_entry = DnsServerEntry(node = self.container,
- owner = self,
- ip_address = self.container.node.bridge.ip4_address,
- interface_name = self.container.host_interface.device_name)
-
- return dns_server_entry
-
- @inline_task
- def __get__(self):
- raise ResourceNotFound
-
- def __create__(self):
- #If no IP has been given on the host interface (e.g., through DHCP)
- #We need to assign one
- if not self.container.host_interface.ip4_address:
- # a) get the IP
- assign=self._state.manager.by_type(Ipv4Assignment)[0]
- ip4_addr = assign.get_control_address(self.container.host_interface)
- self.container.host_interface.ip4_address = ip4_addr
-
-
- # a) routes: host -> container
- # . container interfaces
- # . container host (main) interface
- # route add -host {ip_address} dev {bridge_name}
- route = IPRoute(node = self.container.node,
- managed = False,
- owner = self,
- ip_address = ip4_addr,
- interface = self.container.node.bridge)
- route.node.routing_table.routes << route
-
- # b) route: container -> host
- # route add {ip_gateway} dev {interface_name}
- # route add default gw {ip_gateway} dev {interface_name}
- route = IPRoute(node = self.container,
- owner = self,
- managed = False,
- ip_address = self.container.node.bridge.ip4_address,
- interface = self.container.host_interface)
- route.node.routing_table.routes << route
- route_gw = IPRoute(node = self.container,
- managed = False,
- owner = self,
- ip_address = 'default',
- interface = self.container.host_interface,
- gateway = self.container.node.bridge.ip4_address)
- route_gw.node.routing_table.routes << route_gw
-
-
- return BashTask(self.container.node, CMD_IP_FORWARD)
-
-#------------------------------------------------------------------------------
-
-class ContainersSetup(Resource):
- """
- Resource: ContainersSetup
-
- Setup of LxcContainers (main resource)
-
- Todo:
- - This should be merged into the LxcContainer resource
- """
-
- #--------------------------------------------------------------------------
- # Resource lifecycle
- #--------------------------------------------------------------------------
-
- def __subresources__(self):
- containers = self._state.manager.by_type(LxcContainer)
- if len(containers) == 0:
- return None
-
- container_resources = [ContainerSetup(owner = self, container = c)
- for c in containers]
-
- return Resource.__concurrent__(*container_resources)
-
-#------------------------------------------------------------------------------
-
class CentralIP(Resource):
"""
Resource: CentralIP
@@ -678,32 +591,31 @@ class CentralIP(Resource):
ip_routing_strategy = Attribute(String, description = 'IP routing strategy',
default = 'pair') # spt, pair
- ip6_data_prefix = Attribute(String, description="Prefix for IPv6 forwarding", mandatory=True)
- ip4_data_prefix = Attribute(String, description="Prefix for IPv4 forwarding", mandatory=True)
- ip4_control_prefix = Attribute(String, description="Prefix for IPv4 control", mandatory=True)
+ ip6_data_prefix = Attribute(String, description="Prefix for IPv6 forwarding",
+ mandatory = True)
+ ip4_data_prefix = Attribute(String, description="Prefix for IPv4 forwarding",
+ mandatory = True)
#--------------------------------------------------------------------------
# Resource lifecycle
#--------------------------------------------------------------------------
- #def __after_init__(self):
- # return ('Node', 'Channel', 'Interface')
+ def __after_init__(self):
+ return ('Node', 'Channel', 'Interface')
+
+ def __after__(self):
+ return ('EmulatedChannel')
def __subresources__(self):
- ip4_assign = Ipv4Assignment(prefix=self.ip4_data_prefix,
- control_prefix=self.ip4_control_prefix)
- ip6_assign = Ipv6Assignment(prefix=self.ip6_data_prefix)
- containers_setup = ContainersSetup(owner=self)
+ ip4_assign = Ipv4Assignment(prefix = self.ip4_data_prefix,
+ groups = Reference(self, 'groups'))
+ ip6_assign = Ipv6Assignment(prefix = self.ip6_data_prefix,
+ groups = Reference(self, 'groups'))
ip_routes = IPRoutes(owner = self,
+ groups = Reference(self, 'groups'),
routing_strategy = self.ip_routing_strategy)
- return (ip4_assign | ip6_assign) > (ip_routes | containers_setup)
-
- @inline_task
- def __get__(self):
- raise ResourceNotFound
-
- __delete__ = None
+ return (ip4_assign | ip6_assign) > ip_routes
#------------------------------------------------------------------------------
@@ -734,9 +646,11 @@ class CentralICN(Resource):
return ('CentralIP',)
def __subresources__(self):
- icn_faces = ICNFaces(owner = self, protocol_name = self.face_protocol)
+ icn_faces = ICNFaces(owner = self, protocol_name = self.face_protocol,
+ groups = Reference(self, 'groups'))
icn_routes = ICNRoutes(owner = self,
- routing_strategy = self.icn_routing_strategy)
+ routing_strategy = self.icn_routing_strategy,
+ groups = Reference(self, 'groups'))
return icn_faces > icn_routes
@inline_task
diff --git a/vicn/resource/channel.py b/vicn/resource/channel.py
index d91bebcf..4576e0e7 100644
--- a/vicn/resource/channel.py
+++ b/vicn/resource/channel.py
@@ -22,8 +22,6 @@ from vicn.core.attribute import Attribute
from vicn.core.task import EmptyTask
from vicn.resource.ip_assignment import Ipv6Assignment, Ipv4Assignment
-from math import log, ceil
-
class Channel(Resource):
"""
Resource: Channel
@@ -49,26 +47,3 @@ class Channel(Resource):
ret = "{:03}".format(len(self.interfaces))
ret = ret + ''.join(sorted(map(lambda x : x.node.name, self.interfaces)))
return ret
-
- def __create__(self):
- interfaces = sorted(self.interfaces, key = lambda x : x.device_name)
- if interfaces:
- #IPv6
- central6 = self._state.manager.by_type(Ipv6Assignment)[0]
- prefix6_size = min(64, 128 - ceil(log(len(self.interfaces), 2)))
- prefix6 = iter(central6.get_prefix(self, prefix6_size))
-
- #IPv4
- central4 = self._state.manager.by_type(Ipv4Assignment)[0]
- prefix4_size = 32 - ceil(log(len(self.interfaces), 2))
- prefix4 = iter(central4.get_prefix(self, prefix4_size))
-
- for interface in interfaces:
- try:
- interface.ip4_address = next(prefix4)
- except StopIteration as e:
- import pdb; pdb.set_trace()
- interface.ip6_address = next(prefix6)
- interface.ip6_prefix = prefix6_size
-
- return EmptyTask()
diff --git a/vicn/resource/group.py b/vicn/resource/group.py
new file mode 100644
index 00000000..1557c42e
--- /dev/null
+++ b/vicn/resource/group.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2017 Cisco and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from vicn.core.resource import Resource
+from vicn.core.attribute import Attribute, Multiplicity
+
+class Group(Resource):
+ resources = Attribute(Resource, description = 'Resources belonging to the group',
+ multiplicity = Multiplicity.ManyToMany,
+ default = [],
+ reverse_name = 'groups',
+ reverse_description = 'Groups to which the resource belongs')
+
+ def iter_by_type(self, type):
+ for r in self.resources:
+ if isinstance(r, type):
+ yield r
+
+ def iter_by_type_str(self, typestr):
+ cls = self._state.manager._available.get(typestr.lower())
+ if not cls:
+ return list()
+ return self.iter_by_type(cls)
diff --git a/vicn/resource/gui.py b/vicn/resource/gui.py
deleted file mode 100644
index 3ded7a5a..00000000
--- a/vicn/resource/gui.py
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#
-# Copyright (c) 2017 Cisco and/or its affiliates.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at:
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-from vicn.helpers.resource_definition import *
-
-class GUI(Resource):
- """
- Resource: GUI
-
- This resource is empty on purpose. It is a temporary resource used as a
- placeholder for controlling the GUI and should be deprecated in future
- releases.
- """
- pass
diff --git a/vicn/resource/icn/ndnpingserver.py b/vicn/resource/icn/ndnpingserver.py
index da13f59b..f9cfa7cc 100644
--- a/vicn/resource/icn/ndnpingserver.py
+++ b/vicn/resource/icn/ndnpingserver.py
@@ -55,7 +55,7 @@ class NDNPingServerBase(Producer):
node = Attribute(requirements = [
Requirement("forwarder",
- capabilities = set(['ICN_SUITE_CCNX_1_0'])) ])
+ capabilities = set(['ICN_SUITE_NDN_1_0'])) ])
__package_names__ = ['ndnping']
diff --git a/vicn/resource/icn/webserver.py b/vicn/resource/icn/webserver.py
index 8b8e2ef3..71e9f200 100644
--- a/vicn/resource/icn/webserver.py
+++ b/vicn/resource/icn/webserver.py
@@ -25,5 +25,5 @@ class WebServer(Producer):
CCNX Webserver
"""
- __package_names__ = ['webserver-ccnx']
- __service_name__ = 'webserver-ccnx'
+ __package_names__ = ['http-server']
+ __service_name__ = 'http-server'
diff --git a/vicn/resource/ip_assignment.py b/vicn/resource/ip_assignment.py
index 7553f4ff..62a32389 100644
--- a/vicn/resource/ip_assignment.py
+++ b/vicn/resource/ip_assignment.py
@@ -16,6 +16,8 @@
# limitations under the License.
#
+import math
+
from vicn.core.resource import Resource
from netmodel.model.type import String
from vicn.core.attribute import Attribute
@@ -60,9 +62,42 @@ class IpAssignment(Resource):
self._assigned_addresses[obj] = ret
return ret
+ @inline_task
+ def __get__(self):
+ raise ResourceNotFound
+
+ @inline_task
+ def __create__(self):
+ # XXX code from Channel.__create__, until Events are properly implemented.
+ # Iterate on channels for allocate IP addresses
+ for group in self.groups:
+ for channel in group.iter_by_type_str('channel'):
+ interfaces = sorted(channel.interfaces, key = lambda x : x.device_name)
+ if not interfaces:
+ continue
+
+ min_prefix_size = math.ceil(math.log(len(channel.interfaces), 2))
+ prefix_size = min(self.DEFAULT_PREFIX_SIZE, self.MAX_PREFIX_SIZE - min_prefix_size)
+ prefix = iter(self.get_prefix(channel, prefix_size))
+
+ for interface in interfaces:
+ ip = next(prefix)
+ print('attribute ip=', ip)
+ setattr(interface, self.ATTR_ADDRESS, ip)
+ setattr(interface, self.ATTR_PREFIX, prefix_size)
+
+ __delete__ = None
+
class Ipv6Assignment(IpAssignment):
PrefixClass = Inet6Prefix
-
+ DEFAULT_PREFIX_SIZE = 64
+ MAX_PREFIX_SIZE = 128
+ ATTR_ADDRESS = 'ip6_address'
+ ATTR_PREFIX = 'ip6_prefix'
class Ipv4Assignment(IpAssignment):
PrefixClass = Inet4Prefix
+ DEFAULT_PREFIX_SIZE = 32
+ MAX_PREFIX_SIZE = 32
+ ATTR_ADDRESS = 'ip4_address'
+ ATTR_PREFIX = 'ip4_prefix'
diff --git a/vicn/resource/linux/application.py b/vicn/resource/linux/application.py
index d2b5139e..ed135da6 100644
--- a/vicn/resource/linux/application.py
+++ b/vicn/resource/linux/application.py
@@ -21,7 +21,6 @@ from vicn.core.resource import Resource, EmptyResource
from vicn.resource.application import Application
from vicn.resource.linux.package_manager import Packages
-
class LinuxApplication(Application):
"""
Resource: Linux Application
diff --git a/vicn/resource/linux/bridge.py b/vicn/resource/linux/bridge.py
index 882f0226..7b5ceed7 100644
--- a/vicn/resource/linux/bridge.py
+++ b/vicn/resource/linux/bridge.py
@@ -46,7 +46,7 @@ class Bridge(Channel, BaseNetDevice):
Requirement('bridge_manager')
])
device_name = Attribute(
- default = DEFAULT_BRIDGE_NAME,
+ default = lambda self: self._state.manager.get('bridge_name'),
mandatory = False)
#--------------------------------------------------------------------------
diff --git a/vicn/resource/linux/dnsmasq.py b/vicn/resource/linux/dnsmasq.py
index e18f750f..b5aa8053 100644
--- a/vicn/resource/linux/dnsmasq.py
+++ b/vicn/resource/linux/dnsmasq.py
@@ -42,7 +42,6 @@ TPL_CONF='''
interface=$interface
dhcp-range=$dhcp_range
-dhcp-host=00:0e:c6:81:79:01,192.168.128.200,12h
#server=$server
$flags
@@ -60,12 +59,12 @@ class DnsMasq(Service, DnsServer):
__package_names__ = ['dnsmasq']
__service_name__ = 'dnsmasq'
- interface = Attribute(Interface,
+ interface = Attribute(Interface,
description = 'Interface on which to listen')
lease_interval = Attribute(String,
default = '12h')
server = Attribute(String)
- dhcp_authoritative = Attribute(Bool,
+ dhcp_authoritative = Attribute(Bool,
description = 'Flag: DHCP authoritative',
default = True)
log_queries = Attribute(Bool, description = 'Flag: log DNS queries',
@@ -80,10 +79,7 @@ class DnsMasq(Service, DnsServer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not self.interface:
- if self.node.bridge:
- self.interface = self.node.bridge
- else:
- self.interface = self.node.host_interface
+ raise Exception("Cannot initialize bridge without interface")
def __subresources__(self):
# Overwrite configuration file
diff --git a/vicn/resource/linux/net_device.py b/vicn/resource/linux/net_device.py
index 1ce7e4d5..e40256ea 100644
--- a/vicn/resource/linux/net_device.py
+++ b/vicn/resource/linux/net_device.py
@@ -22,14 +22,14 @@ import math
import random
import string
-from netmodel.model.type import Integer, String, Bool
-from vicn.core.address_mgr import AddressManager
-from vicn.core.attribute import Attribute
-from vicn.core.exception import ResourceNotFound
-from vicn.core.resource import BaseResource
-from vicn.core.task import BashTask, task, EmptyTask
-from vicn.resource.application import Application
-from vicn.resource.interface import Interface
+from netmodel.model.type import Integer, String, Bool
+from vicn.core.address_mgr import AddressManager
+from vicn.core.attribute import Attribute
+from vicn.core.exception import ResourceNotFound
+from vicn.core.resource import BaseResource
+from vicn.core.task import BashTask, task, EmptyTask
+from vicn.resource.linux.application import LinuxApplication as Application
+from vicn.resource.interface import Interface
# parse_ip_addr inspired from:
# From: https://github.com/ohmu/poni/blob/master/poni/cloud_libvirt.py
diff --git a/vicn/resource/linux/package_manager.py b/vicn/resource/linux/package_manager.py
index 86b7057a..eaf83e17 100644
--- a/vicn/resource/linux/package_manager.py
+++ b/vicn/resource/linux/package_manager.py
@@ -19,7 +19,7 @@
import asyncio
import logging
-from netmodel.model.type import String
+from netmodel.model.type import String, Bool
from vicn.core.attribute import Attribute, Multiplicity
from vicn.core.exception import ResourceNotFound
from vicn.core.requirement import Requirement
@@ -63,7 +63,7 @@ class PackageManager(Resource):
Resource: PackageManager
APT package management wrapper.
-
+
Todo:
- We assume a package manager is always installed on every machine.
- Currently, we limit ourselves to debian/ubuntu, and voluntarily don't
@@ -79,6 +79,9 @@ class PackageManager(Resource):
reverse_auto = True,
mandatory = True,
multiplicity = Multiplicity.OneToOne)
+ trusted = Attribute(Bool,
+ description="Force repository trust",
+ default=False)
#--------------------------------------------------------------------------
# Constructor and Accessors
@@ -94,34 +97,28 @@ class PackageManager(Resource):
#--------------------------------------------------------------------------
def __after__(self):
- if self.node.__class__.__name__ == 'Physical':
- # UGLY : This blocking code is currently needed
- task = self.node.host_interface._get_ip4_address()
- ip_dict = task.execute_blocking()
- self.node.host_interface.ip4_address = ip_dict['ip4_address']
- return ('Repository',)
- else:
- return ('Repository', 'CentralIP', 'RoutingTable')
+ return ('Repository',)
@inline_task
def __get__(self):
raise ResourceNotFound
- def __create__(self):
+ #---------------------------------------------------------------------------
+ # Methods
+ #---------------------------------------------------------------------------
+
+ def __method_setup_repositories__(self):
repos = EmptyTask()
for repository in self._state.manager.by_type_str('Repository'):
deb_source = self._get_deb_source(repository)
path = self._get_path(repository)
- repo = BashTask(self.node, CMD_SETUP_REPO,
+ # XXX There is no need to setup a repo if there is no package to install
+ repo = BashTask(self.node, CMD_SETUP_REPO,
{'deb_source': deb_source, 'path': path})
repos = repos | repo
- return repos
+ return repos
- #---------------------------------------------------------------------------
- # Methods
- #---------------------------------------------------------------------------
-
def __method_update__(self):
kill = BashTask(self.node, CMD_APT_GET_KILL, {'node': self.node.name},
lock = self.apt_lock)
@@ -139,13 +136,12 @@ class PackageManager(Resource):
else:
update = EmptyTask()
- return (kill > dpkg_configure_a) > update
+ return (self.__method_setup_repositories__() > (kill > dpkg_configure_a)) > update
def __method_install__(self, package_name):
- update = self.__method_update__()
install = BashTask(self.node, CMD_PKG_INSTALL, {'package_name':
package_name}, lock = self.apt_lock)
- return update > install
+ return self.__method_update__() > install
#---------------------------------------------------------------------------
# Internal methods
@@ -158,10 +154,20 @@ class PackageManager(Resource):
return '/etc/apt/sources.list.d/{}.list'.format(repository.repo_name)
def _get_deb_source(self, repository):
- path = repository.node.host_interface.ip4_address + '/'
+ protocol = 'https' if repository.ssl else 'http'
+ path = repository.node.hostname + '/'
if repository.directory:
path += repository.directory + '/'
- return 'deb http://{} {}/'.format(path, self.node.dist)
+ trusted = '[trusted=yes] ' if self.trusted else ''
+ if repository.sections:
+ sections = ' {}'.format(' '.join(repository.sections))
+ else:
+ sections = ''
+ if '$DISTRIBUTION' in path:
+ path = path.replace('$DISTRIBUTION', self.node.dist)
+ return 'deb {}{}://{} ./{}'.format(trusted, protocol, path, sections)
+ else:
+ return 'deb {}{}://{} {}{}'.format(trusted, protocol, path, self.node.dist, sections)
#------------------------------------------------------------------------------
@@ -173,7 +179,7 @@ class Package(Resource):
"""
package_name = Attribute(String, mandatory = True)
- node = Attribute(Node,
+ node = Attribute(Node,
mandatory = True,
requirements=[
Requirement('package_manager')
@@ -208,7 +214,7 @@ class Packages(Resource):
since package_names are static for a resource, this is not a problem here.
"""
names = Attribute(String, multiplicity = Multiplicity.OneToMany)
- node = Attribute(Node,
+ node = Attribute(Node,
mandatory = True,
requirements=[
Requirement('package_manager')
@@ -229,4 +235,3 @@ class Packages(Resource):
return Resource.__concurrent__(*packages)
else:
return None
-
diff --git a/vicn/resource/linux/repository.py b/vicn/resource/linux/repository.py
index f3e70565..cd740d38 100644
--- a/vicn/resource/linux/repository.py
+++ b/vicn/resource/linux/repository.py
@@ -16,7 +16,7 @@
# limitations under the License.
#
-from netmodel.model.type import String
+from netmodel.model.type import String, Bool
from vicn.core.attribute import Attribute, Multiplicity
from vicn.resource.application import Application
@@ -35,7 +35,12 @@ class Repository(Application):
default = 'vicn')
directory = Attribute(String, description = 'Directory holding packages',
default = '')
+ sections = Attribute(String, description = 'Sections',
+ multiplicity = Multiplicity.OneToMany,
+ default = [])
distributions = Attribute(String,
description = 'List of distributions served by this repository',
multiplicity = Multiplicity.ManyToMany,
default = ['sid', 'trusty', 'xenial'])
+ ssl = Attribute(Bool, description = 'Use SSL (https) for repository',
+ default = True)
diff --git a/vicn/resource/lxd/lxc_container.py b/vicn/resource/lxd/lxc_container.py
index 9daaffb7..5670d1a2 100644
--- a/vicn/resource/lxd/lxc_container.py
+++ b/vicn/resource/lxd/lxc_container.py
@@ -18,16 +18,14 @@
import logging
import shlex
-import time
-# Suppress logging from pylxd dependency on ws4py
+# Suppress logging from pylxd dependency on ws4py
# (this needs to be included before pylxd)
from ws4py import configure_logger
configure_logger(level=logging.ERROR)
import pylxd
from netmodel.model.type import String, Integer, Bool, Self
-from vicn.core.address_mgr import AddressManager
from vicn.core.attribute import Attribute, Reference, Multiplicity
from vicn.core.commands import ReturnValue
from vicn.core.exception import ResourceNotFound
@@ -37,12 +35,10 @@ from vicn.core.task import task, inline_task, BashTask, EmptyTas
from vicn.resource.linux.net_device import NetDevice
from vicn.resource.node import Node
from vicn.resource.vpp.scripts import APPARMOR_VPP_PROFILE
+from vicn.resource.lxd.lxd_profile import LXD_PROFILE_DEFAULT_IFNAME
log = logging.getLogger(__name__)
-# Default name of VICN management/monitoring interface
-DEFAULT_LXC_NETDEVICE = 'eth0'
-
# Default remote server (pull mode only)
DEFAULT_SOURCE_URL = 'https://cloud-images.ubuntu.com/releases/'
@@ -56,8 +52,10 @@ CMD_UNSET_IP6_FWD = 'sysctl -w net.ipv6.conf.all.forwarding=0'
CMD_SET_IP6_FWD = 'sysctl -w net.ipv6.conf.all.forwarding=1'
CMD_GET_IP6_FWD = 'sysctl -n net.ipv6.conf.all.forwarding'
+CMD_NETWORK_DHCP='dhclient {container.management_interface.device_name}'
+
# Type: ContainerName
-ContainerName = String(max_size = 64, ascii = True,
+ContainerName = String(max_size = 64, ascii = True,
forbidden = ('/', ',', ':'))
class LxcContainer(Node):
@@ -74,12 +72,12 @@ class LxcContainer(Node):
architecture = Attribute(String, description = 'Architecture',
default = 'x86_64')
- container_name = Attribute(ContainerName,
+ container_name = Attribute(ContainerName,
description = 'Name of the container',
default = Reference(Self, 'name'))
ephemeral = Attribute(Bool, description = 'Ephemeral container flag',
default = False)
- node = Attribute(Node,
+ node = Attribute(Node,
description = 'Node on which the container is running',
mandatory = True,
requirements = [
@@ -91,26 +89,26 @@ class LxcContainer(Node):
Requirement('bridge'),
# A DNS server is required to provide internet connectivity to
# the containers
- Requirement('dns_server'),
+ # Requirement('dns_server'),
])
- profiles = Attribute(String, multiplicity = Multiplicity.OneToMany,
- default = ['default'])
+ profiles = Attribute(String, multiplicity = Multiplicity.OneToMany,
+ default = ['vicn'])
image = Attribute(String, description = 'image', default = None)
is_image = Attribute(Bool, defaut = False)
pid = Attribute(Integer, description = 'PID of the container')
ip6_forwarding = Attribute(Bool, default=True)
- #--------------------------------------------------------------------------
+ #--------------------------------------------------------------------------
# Constructor / Accessors
- #--------------------------------------------------------------------------
+ #--------------------------------------------------------------------------
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._container = None
- #--------------------------------------------------------------------------
+ #--------------------------------------------------------------------------
# Resource lifecycle
- #--------------------------------------------------------------------------
+ #--------------------------------------------------------------------------
@inline_task
def __initialize__(self):
@@ -120,11 +118,11 @@ class LxcContainer(Node):
self.node_with_kernel = Reference(self, 'node')
# We automatically add the management/monitoring interface
- self._host_interface = NetDevice(node = self,
+ self._management_interface = NetDevice(node = self,
owner = self,
monitored = False,
- device_name = DEFAULT_LXC_NETDEVICE)
- self._state.manager.commit_resource(self._host_interface)
+ device_name = LXD_PROFILE_DEFAULT_IFNAME)
+ self._state.manager.commit_resource(self._management_interface)
for iface in self.interfaces:
if iface.get_type() == "dpdkdevice":
@@ -150,7 +148,9 @@ class LxcContainer(Node):
wait_vpp_host = wait_resource_task(self.node.vpp_host)
create = self._create_container()
start = self.__method_start__()
- return wait_vpp_host > (create > start)
+ #XXX Should be an option on the netdevice
+ dhcp_interface = BashTask(self, CMD_NETWORK_DHCP, {'container':self})
+ return (wait_vpp_host > (create > start)) > dhcp_interface
@task
def _create_container(self):
@@ -158,54 +158,52 @@ class LxcContainer(Node):
log.debug('Container description: {}'.format(container))
client = self.node.lxd_hypervisor.client
self._container = client.containers.create(container, wait=True)
- self._container.start(wait = True)
+ #self._container.start(wait = True)
def _get_container_description(self):
# Base configuration
container = {
- 'name' : self.container_name,
+ 'name' : self.container_name,
'architecture' : self.architecture,
- 'ephemeral' : self.ephemeral,
- 'profiles' : ['default'],
+ 'ephemeral' : self.ephemeral,
+ 'profiles' : self.profiles,
'config' : {},
'devices' : {},
}
# DEVICES
- devices = {}
# FIXME Container profile support is provided by setting changes into
# configuration (currently only vpp profile is supported)
for profile in self.profiles:
if profile == 'vpp':
# Set the new apparmor profile. This will be created in VPP
- # application
+ # application
# Mount hugetlbfs in the container.
container['config']['raw.lxc'] = APPARMOR_VPP_PROFILE
container['config']['security.privileged'] = 'true'
for device in self.node.vpp_host.uio_devices:
container['devices'][device] = {
- 'path' : '/dev/{}'.format(device),
+ 'path' : '/dev/{}'.format(device),
'type' : 'unix-char' }
- # NETWORK (not for images)
-
- if not self.is_image:
- container['config']['user.network_mode'] = 'link-local'
- device = {
- 'type' : 'nic',
- 'name' : self.host_interface.device_name,
- 'nictype' : 'bridged',
- 'parent' : self.node.bridge.device_name,
- }
- device['hwaddr'] = AddressManager().get_mac(self)
- prefix = 'veth-{}'.format(self.container_name)
- device['host_name'] = AddressManager().get('device_name', self,
- prefix = prefix, scope = prefix)
-
- container['devices'][device['name']] = device
-
+# # NETWORK (not for images)
+#
+# if not self.is_image:
+# container['config']['user.network_mode'] = 'link-local'
+# device = {
+# 'type' : 'nic',
+# 'name' : self.host_interface.device_name,
+# 'nictype' : 'bridged',
+# 'parent' : self.node.bridge.device_name,
+# }
+# device['hwaddr'] = AddressManager().get_mac(self)
+# prefix = 'veth-{}'.format(self.container_name)
+# device['host_name'] = AddressManager().get('device_name', self,
+# prefix = prefix, scope = prefix)
+#
+# container['devices'][device['name']] = device
# SOURCE
@@ -233,6 +231,7 @@ class LxcContainer(Node):
@task
def __delete__(self):
log.info("Delete container {}".format(self.container_name))
+ import pdb; pdb.set_trace()
self.node.lxd_hypervisor.client.containers.remove(self.name)
#--------------------------------------------------------------------------
@@ -243,7 +242,7 @@ class LxcContainer(Node):
"""
Attribute: pid (getter)
"""
- return BashTask(self.node, CMD_GET_PID, {'container': self},
+ return BashTask(self.node, CMD_GET_PID, {'container': self},
parse = lambda rv: {'pid': rv.stdout.strip()})
#--------------------------------------------------------------------------
diff --git a/vicn/resource/lxd/lxd_hypervisor.py b/vicn/resource/lxd/lxd_hypervisor.py
index 68b7ab28..f9952e4f 100644
--- a/vicn/resource/lxd/lxd_hypervisor.py
+++ b/vicn/resource/lxd/lxd_hypervisor.py
@@ -38,6 +38,7 @@ from vicn.core.task import BashTask, task
from vicn.resource.linux.application import LinuxApplication as Application
from vicn.resource.linux.service import Service
from vicn.resource.linux.certificate import Certificate
+from vicn.resource.lxd.lxd_profile import LxdProfile
# Suppress non-important logging messages from requests and urllib3
logging.getLogger("requests").setLevel(logging.WARNING)
@@ -52,19 +53,20 @@ DEFAULT_KEY_PATH = os.path.expanduser(os.path.join(
'~', '.vicn', 'lxd_client_cert', 'client_key.pem'))
# FIXME hardcoded password for LXD server
-DEFAULT_TRUST_PASSWORD = 'vicn'
+LXD_TRUST_PWD_DEFAULT = 'vicn'
-DEFAULT_LXD_STORAGE = 100 # GB
+LXD_STORAGE_SIZE_DEFAULT = 100 # GB
+LXD_NETWORK_DEFAULT = 'lxdbr-vicn'
+LXD_PROFILE_NAME_DEFAULT = 'vicn'
+ZFS_DEFAULT_POOL_NAME = 'vicn'
# Commands used to interact with the LXD hypervisor
CMD_LXD_CHECK_INIT = 'lsof -i:{lxd.lxd_port}'
CMD_LXD_INIT_BASE = 'lxd init --auto '
-CMD_LXD_INIT='''
-{base}
-lxc profile unset default environment.http_proxy
-lxc profile unset default user.network_mode
-'''
+
+CMD_LXD_NETWORK_GET = 'lxc network list | grep {lxd_hypervisor.network}'
+CMD_LXD_NETWORK_SET = 'lxc network create {lxd_hypervisor.network} || true'
#------------------------------------------------------------------------------
# Subresources
@@ -82,7 +84,7 @@ class LxdInit(Application):
'storage-backend' : self.owner.storage_backend,
'network-port' : self.owner.lxd_port,
'network-address' : '0.0.0.0',
- 'trust-password' : DEFAULT_TRUST_PASSWORD,
+ 'trust-password' : self.owner.trust_password,
}
if self.owner.storage_backend == 'zfs':
@@ -104,8 +106,7 @@ class LxdInit(Application):
# error: Failed to create the ZFS pool: The ZFS modules are not loaded.
# Try running '/sbin/modprobe zfs' as root to load them.
# zfs-dkms in the host
- return BashTask(self.owner.node, CMD_LXD_INIT, {'base': cmd},
- as_root = True)
+ return BashTask(self.owner.node, cmd, as_root = True)
def __delete__(self):
raise NotImplementedError
@@ -134,7 +135,7 @@ class LxdInstallCert(Resource):
client certificate for the LXD daemon.
"""
log.info('Adding certificate on LXD')
- self.owner.client.authenticate(DEFAULT_TRUST_PASSWORD)
+ self.owner.client.authenticate(self.owner.trust_password)
if not self.owner.client.trusted:
raise Exception
@@ -154,9 +155,13 @@ class LxdHypervisor(Service):
default = 'zfs',
choices = ['zfs'])
storage_size = Attribute(Integer, description = 'Storage size',
- default = DEFAULT_LXD_STORAGE) # GB
+ default = LXD_STORAGE_SIZE_DEFAULT) # GB
zfs_pool = Attribute(String, description = 'ZFS pool',
- default='vicn')
+ default=ZFS_DEFAULT_POOL_NAME)
+ network = Attribute(String, description = 'LXD network name',
+ default=LXD_NETWORK_DEFAULT)
+ trust_password = Attribute(String, description = 'Trust password for the LXD server',
+ default=LXD_TRUST_PWD_DEFAULT)
# Just overload attribute with a new reverse
node = Attribute(
@@ -194,8 +199,13 @@ class LxdHypervisor(Service):
owner = self)
lxd_cert_install = LxdInstallCert(certificate = lxd_local_cert,
owner = self)
+ lxd_vicn_profile = LxdProfile(name=LXD_PROFILE_NAME_DEFAULT,
+ node=self.node,
+ description='vICN profile',
+ network=self.network,
+ pool=self.zfs_pool)
- return (lxd_init | lxd_local_cert) > lxd_cert_install
+ return (lxd_init | lxd_local_cert) > (lxd_vicn_profile | lxd_cert_install)
#--------------------------------------------------------------------------
# Private methods
@@ -221,3 +231,10 @@ class LxdHypervisor(Service):
@property
def aliases(self):
return [alias for image in self.images for alias in image.aliases]
+
+ @task
+ def _get_network(self):
+ return None #XXX We assume it's always nothing
+
+ def _set_network(self):
+ return BashTask(self.node, CMD_LXD_NETWORK_SET, {'lxd_hypervisor': self})
diff --git a/vicn/resource/lxd/lxd_profile.py b/vicn/resource/lxd/lxd_profile.py
new file mode 100644
index 00000000..e7afee4a
--- /dev/null
+++ b/vicn/resource/lxd/lxd_profile.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2017 Cisco and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from vicn.core.resource import Resource
+from netmodel.model.type import String
+from vicn.core.attribute import Attribute, Multiplicity
+from vicn.core.task import BashTask
+from vicn.core.exception import ResourceNotFound
+
+CMD_LXD_PROFILE_CREATE = '''
+lxc profile create {profile.name} description="{profile.description}"
+lxc profile device add {profile.name} root disk pool={profile.pool} path=/
+lxc profile device add {profile.name} {profile.iface_name} nic name={profile.iface_name} nictype=bridged parent={profile.network}
+lxc profile unset {profile.name} environment.http_proxy
+lxc profile unset {profile.name} user.network_mode
+'''
+
+CMD_LXD_PROFILE_GET = 'lxc profile list | grep {profile.name}'
+
+# Default name of VICN management/monitoring interface
+#
+# This should be kept in sync with /etc/network/interfaces in the image file so that dhcp works
+LXD_PROFILE_DEFAULT_IFNAME = 'vicn_mgmt'
+
+class LxdProfile(Resource):
+
+ description = Attribute(String, descr="profile description", mandatory=True)
+ pool = Attribute(String, descr="ZFS pool used by the containers", mandatory=True)
+ network = Attribute(String, description='Network on which to attach', mandatory=True)
+ iface_name = Attribute(String, description='Default interface name',
+ default = LXD_PROFILE_DEFAULT_IFNAME)
+ node = Attribute(Resource, mandatory=True)
+
+ def __get__(self):
+ def parse(rv):
+ if not rv.stdout:
+ raise ResourceNotFound
+ return BashTask(self.node, CMD_LXD_PROFILE_GET, {'profile':self}, parse=parse)
+
+ def __create__(self):
+ return BashTask(self.node, CMD_LXD_PROFILE_CREATE, {'profile':self})
diff --git a/vicn/resource/node.py b/vicn/resource/node.py
index ad519666..c785e32b 100644
--- a/vicn/resource/node.py
+++ b/vicn/resource/node.py
@@ -61,33 +61,17 @@ class Node(Resource):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
- self._host_interface = None
+ self._management_interface = None
#---------------------------------------------------------------------------
# Public API
#---------------------------------------------------------------------------
@property
- def host_interface(self):
- """
- We assume that any unmanaged interface associated to the host is the
- main host interface. It should thus be declared in the JSON topology.
- We might later perform some kind of auto discovery.
-
- This unmanaged interface is only required to get the device_name:
- - to create Veth (need a parent)
- - to ssh a node, get its ip address (eg for the repo)
- - to avoid loops in type specification
-
- It is used for all nodes to provide network connectivity.
- """
-
- for interface in self.interfaces:
- if not interface.managed or interface.owner is not None:
- return interface
-
- raise Exception('Cannot find host interface for node {}: {}'.format(
- self, self.interfaces))
+ def management_interface(self):
+ if not self._management_interface:
+ raise Exception("No management interface has been defined")
+ return self._management_interface
def execute(self, command, output = False, as_root = False):
raise NotImplementedError