aboutsummaryrefslogtreecommitdiffstats
path: root/vicn/resource
diff options
context:
space:
mode:
Diffstat (limited to 'vicn/resource')
-rw-r--r--vicn/resource/application.py9
-rw-r--r--vicn/resource/central.py535
-rw-r--r--vicn/resource/group.py9
-rw-r--r--vicn/resource/gui.py96
-rw-r--r--vicn/resource/icn/ccnx_keystore.py9
-rw-r--r--vicn/resource/icn/ccnx_metis.py83
-rw-r--r--vicn/resource/icn/ccnx_simpleTrafficGenerator.py9
-rw-r--r--vicn/resource/icn/central.py236
-rw-r--r--vicn/resource/icn/cicn.py (renamed from vicn/resource/vpp/cicn.py)17
-rw-r--r--vicn/resource/icn/face.py87
-rw-r--r--vicn/resource/icn/forwarder.py2
-rw-r--r--vicn/resource/icn/icn_application.py1
-rw-r--r--vicn/resource/icn/iping.py26
-rw-r--r--vicn/resource/icn/ndnpingserver.py2
-rw-r--r--vicn/resource/icn/nfd.py8
-rw-r--r--vicn/resource/icn/producer.py6
-rw-r--r--vicn/resource/icn/route.py6
-rw-r--r--vicn/resource/interface.py8
-rw-r--r--vicn/resource/ip/central.py288
-rw-r--r--vicn/resource/ip/prefix_tree.py156
-rw-r--r--vicn/resource/ip/route.py10
-rw-r--r--vicn/resource/ip/routing_table.py51
-rw-r--r--vicn/resource/ip_assignment.py106
-rw-r--r--vicn/resource/linux/bridge.py28
-rw-r--r--vicn/resource/linux/certificate.py30
-rw-r--r--vicn/resource/linux/certificate_store.py54
-rw-r--r--vicn/resource/linux/dnsmasq.py14
-rw-r--r--vicn/resource/linux/file.py12
-rw-r--r--vicn/resource/linux/folder.py88
-rw-r--r--vicn/resource/linux/gre_tunnel.py63
-rw-r--r--vicn/resource/linux/iperf.py4
-rw-r--r--vicn/resource/linux/keypair.py8
-rw-r--r--vicn/resource/linux/link.py26
-rw-r--r--vicn/resource/linux/macvlan.py8
-rw-r--r--vicn/resource/linux/macvtap.py8
-rw-r--r--vicn/resource/linux/net_device.py106
-rw-r--r--vicn/resource/linux/netmon.py2
-rw-r--r--vicn/resource/linux/ovs.py6
-rw-r--r--vicn/resource/linux/package_manager.py17
-rw-r--r--vicn/resource/linux/phy_interface.py20
-rw-r--r--vicn/resource/linux/phy_link.py51
-rw-r--r--vicn/resource/linux/physical.py6
-rw-r--r--vicn/resource/linux/qtplayer.py5
-rw-r--r--vicn/resource/linux/repository.py6
-rw-r--r--vicn/resource/linux/service.py29
-rw-r--r--vicn/resource/linux/sym_veth_pair.py52
-rw-r--r--vicn/resource/linux/tap_device.py25
-rw-r--r--vicn/resource/linux/veth_pair.py94
-rw-r--r--vicn/resource/linux/veth_pair_lxc.py74
-rw-r--r--vicn/resource/lxd/lxc_container.py69
-rw-r--r--vicn/resource/lxd/lxc_image.py7
-rw-r--r--vicn/resource/lxd/lxd_certificate_store.py48
-rw-r--r--vicn/resource/lxd/lxd_hypervisor.py30
-rw-r--r--vicn/resource/lxd/lxd_profile.py12
-rw-r--r--vicn/resource/lxd/lxd_remote.py99
-rw-r--r--vicn/resource/node.py20
-rw-r--r--vicn/resource/ns3/emulated_channel.py65
-rw-r--r--vicn/resource/ns3/emulated_lte_channel.py57
-rw-r--r--vicn/resource/ns3/emulated_wifi_channel.py23
-rw-r--r--vicn/resource/symmetric_channel.py16
-rw-r--r--vicn/resource/vpp/dpdk_device.py2
-rw-r--r--vicn/resource/vpp/interface.py103
-rw-r--r--vicn/resource/vpp/memif_device.py77
-rw-r--r--vicn/resource/vpp/memif_link.py144
-rw-r--r--vicn/resource/vpp/scripts.py2
-rw-r--r--vicn/resource/vpp/vpp.py96
-rw-r--r--vicn/resource/vpp/vpp_bridge.py7
-rw-r--r--vicn/resource/vpp/vpp_commands.py50
-rw-r--r--vicn/resource/vpp/vpp_host.py10
69 files changed, 2263 insertions, 1270 deletions
diff --git a/vicn/resource/application.py b/vicn/resource/application.py
index 0f245496..b5502f39 100644
--- a/vicn/resource/application.py
+++ b/vicn/resource/application.py
@@ -16,9 +16,10 @@
# limitations under the License.
#
-from vicn.core.attribute import Attribute, Multiplicity
-from vicn.core.resource import Resource
-from vicn.resource.node import Node
+from netmodel.model.key import Key
+from vicn.core.attribute import Attribute, Multiplicity
+from vicn.core.resource import Resource
+from vicn.resource.node import Node
class Application(Resource):
node = Attribute(Node,
@@ -26,5 +27,5 @@ class Application(Resource):
mandatory = True,
multiplicity = Multiplicity.ManyToOne,
reverse_name = 'applications',
- key = True,
reverse_description = 'Applications installed on node')
+ __key__ = Key(node)
diff --git a/vicn/resource/central.py b/vicn/resource/central.py
index bf1c8f7a..d1ef267a 100644
--- a/vicn/resource/central.py
+++ b/vicn/resource/central.py
@@ -20,43 +20,12 @@ import logging
import networkx as nx
import os
-from netmodel.model.type import String, Integer
from netmodel.util.misc import pairwise
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
-from vicn.resource.icn.route import Route as ICNRoute
-from vicn.resource.lxd.lxc_container import LxcContainer
-from vicn.resource.node import Node
-from vicn.resource.ip_assignment import Ipv4Assignment, Ipv6Assignment
log = logging.getLogger(__name__)
-TMP_DEFAULT_PORT = 6363
-
-CMD_CONTAINER_SET_DNS = 'echo "nameserver {ip_dns}" | ' \
- 'resolvconf -a {interface_name}'
-
-# For host
-CMD_NAT = '\n'.join([
- 'iptables -t nat -A POSTROUTING -o {interface_name} -s {network} ' \
- '! -d {network} -j MASQUERADE',
- 'echo 1 > /proc/sys/net/ipv4/ip_forward'
-])
-
-# For containers
-CMD_IP_FORWARD = 'echo 1 > /proc/sys/net/ipv4/ip_forward'
-
-HOST_FILE = "hosts.vicn"
-
#------------------------------------------------------------------------------
# Routing strategies
#------------------------------------------------------------------------------
@@ -84,6 +53,8 @@ def routing_strategy_spt(G, origins, weight_key = None):
origin_nodes = origins.keys()
seen = set()
for dst_node in origin_nodes:
+ if not G.has_node(dst_node):
+ continue
sssp = nx.shortest_path(G, target = dst_node)
# Notes from the documentation:
# - If only the target is specified, return a dictionary keyed by
@@ -122,9 +93,13 @@ def routing_strategy_max_flow(G, origins, weight_key = 'capacity'):
origin_nodes = origins.keys()
for dst_node in origin_nodes:
+ if not G.has_node(dst_node):
+ continue
for src_node in G.nodes:
if src_node == dst_node:
continue
+ if not G.has_node(src_node):
+ continue
_, flow_dict = nx.maximum_flow(G, src_node, dst_node,
capacity=weight_key)
@@ -149,17 +124,24 @@ MAP_ROUTING_STRATEGY = {
# L2 and L4/ICN graphs
#------------------------------------------------------------------------------
-def _get_l2_graph(groups, with_managed = False):
+def _get_l2_graph(groups):
+ """
+ We iterate on all the channels that belong to the same groups as the
+ resources.
+
+ NOTE: We have to make sure the nodes also belong to the group.
+ """
G = nx.Graph()
-# 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
+ # XXX bug in reverse collections, resources and not UUIDs seem to be stored inside
+ if group.name not in [x.name for x in src.node.groups]:
+ continue
for dst in channel._sta_ifs.values():
- if not with_managed and (not src.managed or not dst.managed):
+ if group.name not in [x.name for x in dst.node.groups]:
continue
if G.has_edge(src.node._state.uuid, dst.node._state.uuid):
continue
@@ -172,15 +154,16 @@ def _get_l2_graph(groups, with_managed = False):
# This is for a normal Channel
for src_it in range(0, len(channel.interfaces)):
src = channel.interfaces[src_it]
+ if group.name not in [x.name for x in src.node.groups]:
+ continue
# 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):
+ if group.name not in [x.name for x in dst.node.groups]:
continue
+
if G.has_edge(src.node._state.uuid, dst.node._state.uuid):
continue
map_node_interface = {
@@ -189,479 +172,3 @@ def _get_l2_graph(groups, with_managed = False):
G.add_edge(src.node._state.uuid, dst.node._state.uuid,
map_node_interface = map_node_interface)
return G
-
-def _get_icn_graph(manager, groups):
- G = nx.Graph()
- 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
-
-#-------------------------------------------------------------------------------
-
-class IPRoutes(Resource):
- """
- Resource: IPRoutes
-
- Centralized IP route computation.
- """
- routing_strategy = Attribute(String)
-
- def __after__(self):
- return ("IpAssignment",)
-
- #--------------------------------------------------------------------------
- # Resource lifecycle
- #--------------------------------------------------------------------------
-
- @inline_task
- def __get__(self):
- raise ResourceNotFound
-
- @inline_task
- def __create__(self):
- routes = list()
- pre_routes, routes = self._get_ip_routes()
- routes.extend(pre_routes)
- routes.extend(routes)
- for route in routes:
- route.node.routing_table.routes << route
-
- def __delete__(self):
- raise NotImplementedError
-
- #--------------------------------------------------------------------------
- # Internal methods
- #--------------------------------------------------------------------------
-
- def _get_ip_origins(self):
- origins = dict()
- for group in self.groups:
- for channel in group.iter_by_type_str('channel'):
- for interface in channel.interfaces:
- node_uuid = interface.node._state.uuid
- if not node_uuid in origins:
- origins[node_uuid] = list()
- ip4 = interface.ip4_address
- origins[node_uuid].append(interface.ip4_address)
- if interface.ip6_address:
- ip6 = interface.ip6_address
- origins[node_uuid].append(interface.ip6_address)
- return origins
-
- def _get_ip_routes(self):
- if self.routing_strategy == 'pair':
- return [], self._get_pair_ip_routes()
-
- strategy = MAP_ROUTING_STRATEGY.get(self.routing_strategy)
-
- G = _get_l2_graph(self.groups)
- origins = self._get_ip_origins()
-
- # node -> list(origins for which we have routes)
- ip_routes = dict()
-
- pre_routes = list()
- routes = list()
- for src, prefix, dst in strategy(G, origins):
- data = G.get_edge_data(src, dst)
-
- map_ = data['map_node_interface']
- next_hop_interface = map_[src]
-
-
- next_hop_ingress = self._state.manager.by_uuid(map_[dst])
- src_node = self._state.manager.by_uuid(src)
-
- mac_addr = None
- if ((hasattr(next_hop_ingress, 'vpp') and
- next_hop_ingress.vpp is not None) or
- (hasattr(src_node, 'vpp') and src_node.vpp is not None)):
- mac_addr = next_hop_ingress.mac_address
-
- # Avoid duplicate routes due to multiple paths in the network
- if not src_node in ip_routes:
- ip_routes[src_node] = list()
- if prefix in ip_routes[src_node]:
- continue
-
- #FIXME: should test for IP format
- ip_version = 6 if ":" in prefix else 4
- next_hop_ingress_ip = (next_hop_ingress.ip6_address if ip_version is 6 else
- next_hop_ingress.ip4_address)
- if prefix == next_hop_ingress_ip:
- # Direct route on src_node.name :
- # route add [prefix] dev [next_hop_interface_.device_name]
- route = IPRoute(node = src_node,
- managed = False,
- owner = self,
- ip_address = prefix,
- mac_address = mac_addr,
- ip_version = ip_version,
- interface = next_hop_interface)
- else:
- # We need to be sure we have a route to the gw from the node
- if not next_hop_ingress_ip in ip_routes[src_node]:
- pre_route = IPRoute(node = src_node,
- managed = False,
- owner = self,
- ip_address = next_hop_ingress_ip,
- ip_version = ip_version,
- mac_address = mac_addr,
- interface = next_hop_interface)
- ip_routes[src_node].append(next_hop_ingress_ip)
- pre_routes.append(pre_route)
-
- # Route on src_node.name:
- # route add [prefix] dev [next_hop_interface_.device_name]
- # via [next_hop_ingress.ip_address]
- route = IPRoute(node = src_node,
- managed = False,
- owner = self,
- ip_address = prefix,
- ip_version = ip_version,
- interface = next_hop_interface,
- mac_address = mac_addr,
- gateway = next_hop_ingress_ip)
- ip_routes[src_node].append(prefix)
- routes.append(route)
- return pre_routes, routes
-
- def _get_pair_ip_routes(self):
- """
- IP routing strategy : direct routes only
- """
- routes = list()
- 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)
-
- map_ = data['map_node_interface']
- src = self._state.manager.by_uuid(map_[src_node_uuid])
- dst = self._state.manager.by_uuid(map_[dst_node_uuid])
-
- log.debug('[IP ROUTE] NODES {}/{}/{} -> {}/{}/{}'.format(
- src_node.name, src.device_name, src.ip4_address,
- dst_node.name, dst.device_name, dst.ip4_address))
- log.debug('[IP ROUTE] NODES {}/{}/{} -> {}/{}/{}'.format(
- dst_node.name, dst.device_name, dst.ip4_address,
- src_node.name, src.device_name, src.ip4_address))
-
- route = IPRoute(node = src_node,
- managed = False,
- owner = self,
- ip_address = dst.ip4_address,
- mac_address = dst.mac_address,
- interface = src)
- routes.append(route)
- route = IPRoute(node = src_node,
- managed = False,
- owner = self,
- ip_address = dst.ip6_address,
- ip_version = 6,
- mac_address = dst.mac_address,
- interface = src)
- routes.append(route)
-
- route = IPRoute(node = dst_node,
- managed = False,
- owner = self,
- ip_address = src.ip4_address,
- mac_address = src.mac_address,
- interface = dst)
- routes.append(route)
- route = IPRoute(node = dst_node,
- managed = False,
- owner = self,
- ip_address = src.ip6_address,
- ip_version = 6,
- mac_address = src.mac_address,
- interface = dst)
- routes.append(route)
-
- return routes
-
-#-------------------------------------------------------------------------------
-
-class ICNFaces(Resource):
- """
- Resource: ICNFaces
-
- Centralized ICN face creation.
- """
- protocol_name = Attribute(String)
-
- #--------------------------------------------------------------------------
- # Resource lifecycle
- #--------------------------------------------------------------------------
-
- @inline_task
- def __get__(self):
- raise ResourceNotFound # always create faces
-
- @inline_task
- def __create__(self):
- icn_faces = self._get_faces()
- for face in icn_faces:
- face.node.forwarder.faces << face
-
- def __delete__(self):
- raise NotImplementedError
-
- #--------------------------------------------------------------------------
- # Internal methods
- #--------------------------------------------------------------------------
-
- def _get_faces(self):
- """
- Face creation (heuristic: facemgr)
-
- Requires: at least direct IP links
- """
- protocol = FaceProtocol.from_string(self.protocol_name)
-
- faces = list()
- 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)
-
- map_ = data['map_node_interface']
- src = self._state.manager.by_uuid(map_[src_node_uuid])
- dst = self._state.manager.by_uuid(map_[dst_node_uuid])
-
- 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,
- protocol = protocol,
- src_nic = src,
- dst_mac = dst.mac_address)
- dst_face = L2Face(node = dst_node,
- owner = self,
- protocol = protocol,
- src_nic = dst,
- dst_mac = src.mac_address)
-
- elif protocol in (FaceProtocol.tcp4, FaceProtocol.tcp6,
- FaceProtocol.udp4, FaceProtocol.udp6):
- src_face = L4Face(node = src_node,
- 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,
- protocol = protocol,
- src_ip = dst.ip4_address,
- dst_ip = src.ip4_address,
- src_port = TMP_DEFAULT_PORT,
- dst_port = TMP_DEFAULT_PORT)
- else:
- raise NotImplementedError
-
- # We key the sibling face for easier building of the ICN graph
- src_face._internal_data['sibling_face'] = dst_face._state.uuid
- dst_face._internal_data['sibling_face'] = src_face._state.uuid
-
- faces.append(src_face)
- faces.append(dst_face)
-
- return faces
-
-#------------------------------------------------------------------------------
-
-class ICNRoutes(Resource):
- """
- Resource: ICNRoutes
-
- Centralized ICN route computation.
- """
-
- routing_strategy = Attribute(String)
-
- #--------------------------------------------------------------------------
- # Resource lifecycle
- #--------------------------------------------------------------------------
-
- @inline_task
- def __get__(self):
- raise ResourceNotFound # always create routes
-
- @inline_task
- def __create__(self):
- icn_routes = self._get_icn_routes()
- for route in icn_routes:
- route.node.forwarder.routes << route
-
- def __delete__(self):
- raise NotImplementedError
-
- #--------------------------------------------------------------------------
- # Internal methods
- #--------------------------------------------------------------------------
-
- def _get_prefix_origins(self):
- origins = dict()
- 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 producer in node.producers:
- 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, self.groups)
- origins = self._get_prefix_origins()
-
- routes = list()
- for src, prefix, dst in strategy(G, origins):
- data = G.get_edge_data(src, dst)
-
- map_ = data['map_node_face']
- next_hop_face = map_[src]
-
- route = ICNRoute(node = src,
- owner = self,
- prefix = prefix,
- face = next_hop_face)
- routes.append(route)
- return routes
-
-#------------------------------------------------------------------------------
-
-class DnsServerEntry(Resource):
- """
- Resource: DnsServerEntry
-
- Setup of DNS resolver for LxcContainers
-
- Todo:
- - This should be merged into the LxcContainer resource
- """
-
- node = Attribute(String)
- ip_address = Attribute(String)
- interface_name = Attribute(String)
-
- #--------------------------------------------------------------------------
- # Resource lifecycle
- #--------------------------------------------------------------------------
-
- @inline_task
- def __get__(self):
- raise ResourceNotFound
-
- def __create__(self):
- return BashTask(self.node, CMD_CONTAINER_SET_DNS,
- {'ip_dns': self.ip_address,
- 'interface_name': self.interface_name})
-
- def __delete__(self):
- raise NotImplementedError
-
-#------------------------------------------------------------------------------
-
-class CentralIP(Resource):
- """
- Resource: CentralIP
-
- Central IP management (main 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)
- ip6_max_link_prefix = Attribute(Integer,
- description = 'Maximum prefix size assigned to each link',
- default = 64)
-
- #--------------------------------------------------------------------------
- # Resource lifecycle
- #--------------------------------------------------------------------------
-
- def __after_init__(self):
- return ('Node', 'Channel', 'Interface')
-
- def __after__(self):
- return ('EmulatedChannel')
-
- def __subresources__(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'),
- max_prefix_size = self.ip6_max_link_prefix)
- ip_routes = IPRoutes(owner = self,
- groups = Reference(self, 'groups'),
- routing_strategy = self.ip_routing_strategy)
-
- return (ip4_assign | ip6_assign) > ip_routes
-
-#------------------------------------------------------------------------------
-
-class CentralICN(Resource):
- """
- Resource: CentralICN
-
- Central ICN management (main resource)
- """
-
- # Choices: spt, max_flow
- icn_routing_strategy = Attribute(String,
- description = 'ICN routing strategy',
- default = 'spt')
- face_protocol = Attribute(String,
- description = 'Protocol used to create faces',
- default = 'ether')
-
- #--------------------------------------------------------------------------
- # Resource lifecycle
- #--------------------------------------------------------------------------
-
- def __after__(self):
- """
- We need to wait for IP configuration in order to be able to build
- overload ICN faces, and producers for prefix origins.
- """
- return ('CentralIP',)
-
- def __subresources__(self):
- 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,
- groups = Reference(self, 'groups'))
- return icn_faces > icn_routes
-
- @inline_task
- def __get__(self):
- raise ResourceNotFound
-
- __delete__ = None
diff --git a/vicn/resource/group.py b/vicn/resource/group.py
index 1557c42e..a1422c69 100644
--- a/vicn/resource/group.py
+++ b/vicn/resource/group.py
@@ -36,3 +36,12 @@ class Group(Resource):
if not cls:
return list()
return self.iter_by_type(cls)
+
+# def get_tuple(self):
+# return (self.name,)
+#
+# def hash(self):
+# return hash(self.get_tuple())
+#
+# def __eq__(self, other):
+# return self.get_tuple() == other.get_tuple()
diff --git a/vicn/resource/gui.py b/vicn/resource/gui.py
new file mode 100644
index 00000000..26129b3e
--- /dev/null
+++ b/vicn/resource/gui.py
@@ -0,0 +1,96 @@
+#!/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.
+#
+
+import http.server
+import io
+import os
+import socket
+import socketserver
+import sys
+import threading
+
+from vicn.core.resource_mgr import ResourceManager
+from vicn.helpers.resource_definition import *
+
+DEFAULT_GUI_ADDRESS = ''
+DEFAULT_GUI_PORT = 8000
+
+class GUIHandler(http.server.SimpleHTTPRequestHandler):
+ def send_head(self):
+ if self.path == '/js/settings.js':
+ return self.get_settings()
+ return super().send_head()
+
+ def get_settings(self):
+ host = self.request.getsockname()[0]
+ port = ResourceManager().get('websocket_port');
+ r = []
+ r.append("var URL='ws://{}:{}';\n".format(host, port))
+ enc = sys.getfilesystemencoding()
+ encoded = '\n'.join(r).encode(enc, 'surrogateescape')
+ f = io.BytesIO()
+ f.write(encoded)
+ f.seek(0)
+ self.send_response(http.server.HTTPStatus.OK)
+ self.send_header("Content-type", "text/javascript;")
+ self.send_header("Content-Length", str(len(encoded)))
+ self.end_headers()
+ return f
+
+
+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.
+ """
+ address = Attribute(String, description = 'Address on which the Webserver listens',
+ default = DEFAULT_GUI_ADDRESS)
+ port = Attribute(Integer, description = 'Port on which the Webserver listens',
+ default = DEFAULT_GUI_PORT)
+ path = Attribute(String, default='www')
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ thread = threading.Thread(target = self.run)
+ thread.daemon = True
+ # XXX vICN should expose internal resources for interaction
+ try:
+ thread.start()
+ except KeyboardInterrupt:
+ server.shutdown()
+ sys.exit(0)
+
+ def run(self):
+ if not self.path.startswith(os.path.sep):
+ # XXX we might also search in the experiment folder
+ base_dir = os.path.join(os.path.dirname(__file__), os.path.pardir,
+ os.path.pardir)
+ web_dir = os.path.join(base_dir, self.path)
+ else:
+ web_dir = self.path
+ os.chdir(web_dir)
+ socketserver.TCPServer.allow_reuse_address = True
+ httpd = socketserver.TCPServer((self.address, self.port), GUIHandler)
+ httpd.serve_forever()
+
+ def __del__(self):
+ pass
diff --git a/vicn/resource/icn/ccnx_keystore.py b/vicn/resource/icn/ccnx_keystore.py
index ddd87019..ceff1647 100644
--- a/vicn/resource/icn/ccnx_keystore.py
+++ b/vicn/resource/icn/ccnx_keystore.py
@@ -18,7 +18,7 @@
from netmodel.model.type import String, Integer
from vicn.core.attribute import Attribute, Reference
-from vicn.core.task import BashTask
+from vicn.core.task import BashTask, inherit_parent
from vicn.resource.linux.file import File
from vicn.resource.linux.package_manager import Packages
@@ -39,13 +39,13 @@ class MetisKeystore(File):
filename = Attribute(String, description = "File containing the keystore",
default = DEFAULT_KEYSTORE_FILE, mandatory=False)
- password = Attribute(String,
+ password = Attribute(String,
description = "Password for the keystore file",
default = DEFAULT_KEYSTORE_PASSWD)
- subject_name = Attribute(String,
+ subject_name = Attribute(String,
description = "Subject name for the keystore",
default = DEFAULT_KEYSTORE_SUBJ)
- validity = Attribute(String,
+ validity = Attribute(String,
description = "Validity period of the keystore",
default = DEFAULT_KEYSTORE_VALIDITY)
size = Attribute(Integer, description = 'Length of the keys',
@@ -62,6 +62,7 @@ class MetisKeystore(File):
names=self._get_package_names(), owner=self)
return packages
+ @inherit_parent
def __create__(self):
args = {'filename' : self.filename, 'password' : self.password,
'subject_name' : self.subject_name, 'validity' : self.validity,
diff --git a/vicn/resource/icn/ccnx_metis.py b/vicn/resource/icn/ccnx_metis.py
index ead9b9bf..418c7683 100644
--- a/vicn/resource/icn/ccnx_metis.py
+++ b/vicn/resource/icn/ccnx_metis.py
@@ -23,9 +23,9 @@ from vicn.core.attribute import Attribute
from vicn.core.exception import ResourceNotFound
from vicn.core.resource_mgr import wait_resource_task
from vicn.core.task import BashTask, EmptyTask, task
+from vicn.core.task import inherit_parent
from vicn.resource.icn.ccnx_keystore import MetisKeystore
-from vicn.resource.icn.face import L2Face, L4Face, FaceProtocol
-from vicn.resource.icn.face import DEFAULT_ETHER_PROTO
+from vicn.resource.icn.face import L2Face, L4Face
from vicn.resource.icn.forwarder import Forwarder
from vicn.resource.linux.file import TextFile
from vicn.resource.linux.service import Service
@@ -37,10 +37,12 @@ CMD_ADD_LISTENER_ETHER = (
'add listener ether ether{conn_id} {listener.src_nic.device_name} '
'{listener.ether_proto}')
CMD_ADD_LISTENER_L4 = 'add listener {protocol} transport{conn_id} {infos}'
-CMD_ADD_CONNECTION_ETHER = ('add connection ether {face.id} {face.dst_mac} '
+CMD_ADD_CX_ETHER = ('add connection ether {face.id} {face.dst_mac} '
'{face.src_nic.device_name}')
-CMD_ADD_CONNECTION_L4 = ('add connection {protocol} {face.id} {face.dst_ip} '
- '{face.dst_port} {face.src_ip} {face.src_port}')
+CMD_ADD_CX_L4_IPV4 = ('add connection {protocol} {face.id} {face.dst.ip4_address} '
+ '{face.dst_port} {face.src.ip4_address} {face.src_port}')
+CMD_ADD_CX_L4_IPV6 = ('add connection {protocol} {face.id} {face.dst.ip6_address} '
+ '{face.dst_port} {face.src.ip6_address} {face.src_port}')
CMD_ADD_ROUTE = 'add route {route.face.id} ccnx:{route.prefix} {route.cost}'
METIS_DAEMON_BOOTSTRAP = (
'metis_daemon --port {port} --daemon --log-file {log_file} '
@@ -64,24 +66,25 @@ class MetisListener:
@staticmethod
def listener_from_face(face):
- if face.protocol is FaceProtocol.ether:
- return MetisEtherListener(face.protocol, face.src_nic,
+ if face.protocol == 'ether':
+ return MetisEtherListener(face.protocol, face.src_nic,
face.ether_proto)
- elif face.protocol in [FaceProtocol.tcp4, FaceProtocol.tcp6,
- FaceProtocol.udp4, FaceProtocol.udp6]:
- return MetisL4Listener(face.protocol, face.src_ip, face.src_port)
+ elif face.protocol in ('tcp4', 'udp4'):
+ return MetisL4Listener(face.protocol, face.src.ip4_address, face.src_port)
+ elif face.protocol in ('tcp6', 'udp6'):
+ return MetisL4Listener(face.protocol, face.src.ip6_address, face.src_port)
else:
raise ValueError("Metis only supports Ethernet and TCP/UDP faces")
class MetisEtherListener(MetisListener):
- def __init__(self, protocol, src_nic, ether_proto=DEFAULT_ETHER_PROTO):
+ def __init__(self, protocol, src_nic, ether_proto):
super().__init__(protocol)
self.src_nic = src_nic
self.ether_proto = ether_proto
def get_setup_command(self, conn_id):
- return CMD_ADD_LISTENER_ETHER.format(listener = self,
+ return CMD_ADD_LISTENER_ETHER.format(listener = self,
conn_id = conn_id)
def __eq__(self, other):
@@ -102,9 +105,9 @@ class MetisL4Listener(MetisListener):
self.src_port = src_port
def _get_proto_as_str(self):
- if self.protocol in (FaceProtocol.tcp4, FaceProtocol.tcp6):
+ if self.protocol in ('tcp4', 'tcp6'):
return "tcp"
- elif self.protocol in (FaceProtocol.udp4, FaceProtocol.udp6):
+ elif self.protocol in ('udp4', 'udp6'):
return "udp"
def get_setup_command(self, conn_id):
@@ -126,14 +129,14 @@ class MetisForwarder(Forwarder, Service):
__package_names__ = ['metis-forwarder']
__service_name__ = "metis-forwarder"
- log_file = Attribute(String, description = 'File for metis logging',
+ log_file = Attribute(String, description = 'File for metis logging',
default = '/tmp/ccnx-metis.log') # '/dev/null')
- port = Attribute(Integer, description = 'TCP port for metis',
+ port = Attribute(Integer, description = 'TCP port for metis',
default = 9695)
- gen_config = Attribute(Bool,
+ gen_config = Attribute(Bool,
description = 'Set to record all metis commands in a config file',
default = True)
- config_file = Attribute(String, default = '/root/.ccnx_metis.conf')
+ config_file = Attribute(String, default = '/root/.ccnx_metis.conf')
#--------------------------------------------------------------------------
# Constructor and Accessors
@@ -160,15 +163,18 @@ class MetisForwarder(Forwarder, Service):
def __after__(self):
return ('CentralICN',)
+ @inherit_parent
def __subresources__(self):
self.keystore = MetisKeystore(node = self.node, owner = self)
self.env_file = self._write_environment_file()
return self.keystore | self.env_file
+ @inherit_parent
@task
def __get__(self):
raise ResourceNotFound
+ @inherit_parent
def __create__(self):
# Alternatively, we might put all commands in a configuration file
@@ -222,7 +228,7 @@ class MetisForwarder(Forwarder, Service):
"""
command = METIS_DAEMON_BOOTSTRAP
- args = {'port' : self.port, 'log_file' : self.log_file,
+ args = {'port' : self.port, 'log_file' : self.log_file,
'cs_size' : self.cache_size, 'config' : self.config_file}
return BashTask(self.node, command, parameters = args)
@@ -232,7 +238,7 @@ class MetisForwarder(Forwarder, Service):
"""
command = METIS_DAEMON_STOP + '; ' + METIS_DAEMON_BOOTSTRAP
- args = {'port' : self.port, 'log_file' : self.log_file,
+ args = {'port' : self.port, 'log_file' : self.log_file,
'cs_size' : self.cache_size, 'config' : self.config_file}
return BashTask(self.node, command, parameters = args)
@@ -287,23 +293,16 @@ class MetisForwarder(Forwarder, Service):
face.id = 'conn{}'.format(self._nb_conn)
self._nb_conn += 1
- if face.protocol is FaceProtocol.ether:
- assert isinstance(face, L2Face), \
- 'Ethernet face should be instance of L2Face'
- cmd = CMD_ADD_CONNECTION_ETHER.format(face = face)
-
- elif face.protocol in (FaceProtocol.tcp4, FaceProtocol.tcp6):
- assert isinstance(face, L4Face), \
- "TCP/UDP face should be instance of L4Face"
- cmd = CMD_ADD_CONNECTION_L4.format(face = face,
- protocol = 'tcp')
-
- elif face.protocol in (FaceProtocol.udp4, FaceProtocol.udp6):
- assert isinstance(face, L4Face), \
- 'TCP/UDP face should be instance of L4Face'
- cmd = CMD_ADD_CONNECTION_L4.format(face = face,
- protocol = 'udp')
-
+ if face.protocol == 'ether':
+ cmd = CMD_ADD_CX_ETHER.format(face = face)
+ elif face.protocol == 'tcp4':
+ cmd = CMD_ADD_CX_L4_IPV4.format(face = face, protocol = 'tcp')
+ elif face.protocol == 'tcp6':
+ cmd = CMD_ADD_CX_L4_IPV6.format(face = face, protocol = 'tcp')
+ elif face.protocol == 'udp4':
+ cmd = CMD_ADD_CX_L4_IPV4.format(face = face, protocol = 'udp')
+ elif face.protocol == 'udp6':
+ cmd = CMD_ADD_CX_L4_IPV6.format(face = face, protocol = 'udp')
else:
raise ValueError('Unsupported face type for Metis')
@@ -324,13 +323,13 @@ class MetisForwarder(Forwarder, Service):
delete_task = EmptyTask()
if len(delete_cmds) > 0:
- cmds = '\n'.join('{} {}'.format(self._baseline, command)
+ cmds = '\n'.join('{} {}'.format(self._baseline, command)
for command in delete_cmds)
delete_task = BashTask(self.node, cmds)
create_task = EmptyTask()
if len(create_cmds) > 0:
- cmds = '\n'.join('{} {}'.format(self._baseline, command)
+ cmds = '\n'.join('{} {}'.format(self._baseline, command)
for command in create_cmds)
create_task = BashTask(self.node, cmds)
@@ -348,16 +347,16 @@ class MetisForwarder(Forwarder, Service):
create_task = BashTask(self.node, '\n'.join(create_cmds))
return delete_task > create_task
-
+
def _write_environment_file(self):
param_port = "PORT={port}"
param_log_file = "LOG_FILE={log_file}"
param_cs_capacity = "CS_SIZE={cs_size}"
param_config = "CONFIG={config}"
- env = [param_port.format(port = self.port),
+ env = [param_port.format(port = self.port),
param_log_file.format(log_file = self.log_file),
- param_cs_capacity.format(cs_size = self.cache_size),
+ param_cs_capacity.format(cs_size = self.cache_size),
param_config.format(config = self.config_file)]
environment_file = TextFile(filename = METIS_ETC_DEFAULT,
diff --git a/vicn/resource/icn/ccnx_simpleTrafficGenerator.py b/vicn/resource/icn/ccnx_simpleTrafficGenerator.py
index 221298fc..41a1b855 100644
--- a/vicn/resource/icn/ccnx_simpleTrafficGenerator.py
+++ b/vicn/resource/icn/ccnx_simpleTrafficGenerator.py
@@ -19,7 +19,7 @@
from netmodel.model.type import String
from vicn.core.attribute import Attribute, Multiplicity
from vicn.core.resource import Resource, EmptyResource
-from vicn.core.task import EmptyTask
+from vicn.core.task import EmptyTask, inherit_parent
from vicn.resource.icn.icn_application import ICN_SUITE_CCNX_1_0
from vicn.resource.node import Node
@@ -28,7 +28,7 @@ from vicn.resource.icn.ccnx_consumer_producer_test import CcnxProducerTest
class CcnxSimpleTrafficGenerator(Resource):
- prefix = Attribute(String,
+ prefix = Attribute(String,
description = "Routable prefix for the applications",
default = lambda self: self.default_name(),
mandatory = False)
@@ -44,11 +44,12 @@ class CcnxSimpleTrafficGenerator(Resource):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._sr = None
-
+
#--------------------------------------------------------------------------
# Resource lifecycle
#--------------------------------------------------------------------------
+ @inherit_parent
def __subresources__(self):
"""
Create the list of consumers and producers.
@@ -57,7 +58,7 @@ class CcnxSimpleTrafficGenerator(Resource):
sr = EmptyResource()
for producer in self.producers:
- producer = CcnxProducerTest(node = producer,
+ producer = CcnxProducerTest(node = producer,
owner = self,
prefixes = [self.prefix])
sr = sr | producer
diff --git a/vicn/resource/icn/central.py b/vicn/resource/icn/central.py
new file mode 100644
index 00000000..aa8ea357
--- /dev/null
+++ b/vicn/resource/icn/central.py
@@ -0,0 +1,236 @@
+#!/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.
+#
+
+import logging
+import networkx as nx
+
+from netmodel.model.type import String
+from vicn.core.attribute import Attribute, Reference
+from vicn.core.exception import ResourceNotFound
+from vicn.core.resource import Resource
+from vicn.core.task import inline_task
+from vicn.resource.central import _get_l2_graph, MAP_ROUTING_STRATEGY
+from vicn.resource.icn.face import Face
+from vicn.resource.icn.route import Route
+
+log = logging.getLogger(__name__)
+
+
+def _get_icn_graph(manager, groups):
+ G = nx.Graph()
+ 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)
+ try:
+ forwarder = node.forwarder
+ except ResourceNotFound:
+ continue
+ 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)
+
+ return G
+
+#-------------------------------------------------------------------------------
+
+class ICNFaces(Resource):
+ """
+ Resource: ICNFaces
+
+ Centralized ICN face creation.
+ """
+ protocol_name = Attribute(String)
+
+ #--------------------------------------------------------------------------
+ # Resource lifecycle
+ #--------------------------------------------------------------------------
+
+ @inline_task
+ def __get__(self):
+ raise ResourceNotFound # always create faces
+
+ @inline_task
+ def __create__(self):
+ icn_faces = self._get_faces()
+ for face in icn_faces:
+ face.node.forwarder.faces << face
+
+ def __delete__(self):
+ raise NotImplementedError
+
+ #--------------------------------------------------------------------------
+ # Internal methods
+ #--------------------------------------------------------------------------
+
+ def _get_faces(self):
+ """
+ Face creation (heuristic: facemgr)
+
+ Requires: at least direct IP links
+ """
+ faces = list()
+ 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)
+
+ if not src_node.managed or not dst_node.managed:
+ continue
+
+ map_ = data['map_node_interface']
+ src = self._state.manager.by_uuid(map_[src_node_uuid])
+ dst = self._state.manager.by_uuid(map_[dst_node_uuid])
+
+ log.debug('{} -> {} ({} -> {})'.format(src_node_uuid,
+ dst_node_uuid, src.device_name, dst.device_name))
+
+ face_cls = Face.from_protocol(self.protocol_name)
+ if face_cls is None:
+ raise NotImplementedError
+
+ src_face = face_cls(protocol = self.protocol_name,
+ owner = self,
+ node = src_node,
+ src = src,
+ dst = dst)
+ dst_face = face_cls(protocol = self.protocol_name,
+ owner = self,
+ node = dst_node,
+ src = dst,
+ dst = src)
+
+
+ # We key the sibling face for easier building of the ICN graph
+ src_face._internal_data['sibling_face'] = dst_face._state.uuid
+ dst_face._internal_data['sibling_face'] = src_face._state.uuid
+
+ faces.append(src_face)
+ faces.append(dst_face)
+
+ return faces
+
+#------------------------------------------------------------------------------
+
+class ICNRoutes(Resource):
+ """
+ Resource: Routes
+
+ Centralized ICN route computation.
+ """
+
+ routing_strategy = Attribute(String)
+
+ #--------------------------------------------------------------------------
+ # Resource lifecycle
+ #--------------------------------------------------------------------------
+
+ @inline_task
+ def __get__(self):
+ raise ResourceNotFound # always create routes
+
+ @inline_task
+ def __create__(self):
+ icn_routes = self._get_icn_routes()
+ for route in icn_routes:
+ route.node.forwarder.routes << route
+
+ def __delete__(self):
+ raise NotImplementedError
+
+ #--------------------------------------------------------------------------
+ # Internal methods
+ #--------------------------------------------------------------------------
+
+ def _get_prefix_origins(self):
+ origins = dict()
+ 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 producer in node.producers:
+ 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, self.groups)
+ origins = self._get_prefix_origins()
+
+ routes = list()
+ for src, prefix, dst in strategy(G, origins):
+ src_node = self._state.manager.by_uuid(src)
+ if not src_node.managed:
+ continue
+ data = G.get_edge_data(src, dst)
+
+ map_ = data['map_node_face']
+ next_hop_face = map_[src]
+
+ route = Route(node = src,
+ owner = self,
+ prefix = prefix,
+ face = next_hop_face)
+ routes.append(route)
+
+ return routes
+
+#------------------------------------------------------------------------------
+
+class CentralICN(Resource):
+ """
+ Resource: CentralICN
+
+ Central ICN management (main resource)
+ """
+
+ # Choices: spt, max_flow
+ icn_routing_strategy = Attribute(String,
+ description = 'ICN routing strategy',
+ default = 'spt')
+ face_protocol = Attribute(String,
+ description = 'Protocol used to create faces',
+ default = 'ether')
+
+ #--------------------------------------------------------------------------
+ # Resource lifecycle
+ #--------------------------------------------------------------------------
+
+ def __after__(self):
+ """
+ We need to wait for IP configuration in order to be able to build
+ overload ICN faces, and producers for prefix origins.
+ """
+ return ('CentralIP',)
+
+ def __subresources__(self):
+ 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,
+ groups = Reference(self, 'groups'))
+ return icn_faces > icn_routes
diff --git a/vicn/resource/vpp/cicn.py b/vicn/resource/icn/cicn.py
index 1a68f11f..76dafe0e 100644
--- a/vicn/resource/vpp/cicn.py
+++ b/vicn/resource/icn/cicn.py
@@ -24,15 +24,17 @@ from vicn.core.exception import ResourceNotFound
from vicn.core.requirement import Requirement
from vicn.core.resource_mgr import wait_resource_task
from vicn.core.task import async_task, task, BashTask, EmptyTask
+from vicn.core.task import inherit_parent
from vicn.resource.icn.forwarder import Forwarder
from vicn.resource.node import Node
from vicn.resource.vpp.vpp_commands import CMD_VPP_ENABLE_PLUGIN
-from vicn.resource.vpp.vpp_commands import CMD_VPP_CICN_GET
-from vicn.resource.vpp.vpp_commands import CMD_VPP_ADD_ICN_FACE
-from vicn.resource.vpp.vpp_commands import CMD_VPP_ADD_ICN_ROUTE
-from vicn.resource.vpp.vpp_commands import CMD_VPP_CICN_GET_CACHE_SIZE
-from vicn.resource.vpp.vpp_commands import CMD_VPP_CICN_SET_CACHE_SIZE
+CMD_VPP_CICN_GET = "vppctl_wrapper cicn show"
+CMD_VPP_ADD_ICN_ROUTE = 'vppctl_wrapper cicn cfg fib add prefix {route.prefix} face {route.face.id}'
+CMD_VPP_ADD_ICN_FACE = 'vppctl_wrapper cicn cfg face add local {face.src_ip}:{face.src_port} remote {face.dst_ip}:{face.dst_port}'
+
+CMD_VPP_CICN_GET_CACHE_SIZE = 'vppctl_wrapper cicn show | grep "CS entries" | grep -Eo "[0-9]+"'
+CMD_VPP_CICN_SET_CACHE_SIZE = 'vppctl_wrapper cicn control param cs size {self.cache_size}'
_ADD_FACE_RETURN_FORMAT = "Face ID: [0-9]+"
def check_face_id_return_format(data):
@@ -66,6 +68,7 @@ class CICNForwarder(Forwarder):
def __after__(self):
return ['CentralICN']
+ @inherit_parent
def __get__(self):
def parse(rv):
if rv.return_value > 0 or 'cicn: not enabled' in rv.stdout:
@@ -73,6 +76,7 @@ class CICNForwarder(Forwarder):
return BashTask(self.node, CMD_VPP_CICN_GET,
lock = self.node.vpp.vppctl_lock, parse=parse)
+ @inherit_parent
def __create__(self):
#self.node.vpp.plugins.append("cicn")
@@ -104,9 +108,6 @@ class CICNForwarder(Forwarder):
return (wait_resource_task(self.node.vpp) > create_task) > (face_task > route_task)
- # Nothing to do
- __delete__ = None
-
#--------------------------------------------------------------------------
# Attributes
#--------------------------------------------------------------------------
diff --git a/vicn/resource/icn/face.py b/vicn/resource/icn/face.py
index 641d10e7..4fd07883 100644
--- a/vicn/resource/icn/face.py
+++ b/vicn/resource/icn/face.py
@@ -16,36 +16,31 @@
# limitations under the License.
#
-from enum import Enum
-
-from netmodel.model.type import Integer, String, Bool
-from vicn.core.attribute import Attribute
-from vicn.core.requirement import Requirement
-from vicn.core.resource import Resource
-from vicn.resource.node import Node
-from vicn.resource.interface import Interface
+from netmodel.model.type import Integer, String, Bool, InetAddress
+from netmodel.model.object import ObjectMetaclass
+from vicn.core.attribute import Attribute
+from vicn.core.requirement import Requirement
+from vicn.core.resource import Resource
+from vicn.resource.interface import Interface
+from vicn.resource.node import Node
DEFAULT_ETHER_PROTO = 0x0801
-FMT_L4FACE = '{protocol.name}://{dst_ip}:{dst_port}/'
-FMT_L2FACE = '{protocol.name}://[{dst_mac}]/{src_nic.device_name}'
-
-class FaceProtocol(Enum):
- ether = 0
- ip4 = 1
- ip6 = 2
- tcp4 = 3
- tcp6 = 4
- udp4 = 5
- udp6 = 7
- app = 8
-
- @staticmethod
- def from_string(protocol):
- return getattr(FaceProtocol, protocol)
+DEFAULT_PORT = 6363
+
+FMT_L4FACE_IPV4 = '{protocol}://{dst.ip4_address}:{dst_port}/'
+FMT_L4FACE_IPV6 = '{protocol}://{dst.ip6_address}:{dst_port}/'
+FMT_L2FACE = '{protocol}://[{dst.mac_address}]/{src.device_name}'
#------------------------------------------------------------------------------
-class Face(Resource):
+class FaceMetaclass(ObjectMetaclass):
+ def __new__(mcls, name, bases, attrs):
+ cls = super(FaceMetaclass, mcls).__new__(mcls, name, bases, attrs)
+ if name != 'Face':
+ cls.register()
+ return cls
+
+class Face(Resource, metaclass=FaceMetaclass):
"""
Resource: Face
"""
@@ -57,6 +52,10 @@ class Face(Resource):
protocol = Attribute(String,
description = 'Face underlying protocol',
mandatory = True)
+
+ src = Attribute(Interface, mandatory = True)
+ dst = Attribute(Interface, mandatory = True)
+
id = Attribute(String, description = 'Local face ID',
ro = True)
@@ -75,6 +74,18 @@ class Face(Resource):
description = 'Flags for face creation with NFDC',
func = lambda self : self._lambda_nfdc_flags())
+ # map protocol -> face class
+ _map_protocol = dict()
+
+ @classmethod
+ def register(cls):
+ for protocol in cls.__protocols__:
+ Face._map_protocol[protocol] = cls
+
+ @classmethod
+ def from_protocol(cls, protocol):
+ return cls._map_protocol.get(protocol)
+
def __repr__(self):
flags = ''
if self.permanent:
@@ -95,7 +106,7 @@ class Face(Resource):
# NFD specifics
def _lambda_nfd_uri(self):
- raise NotImplementedError
+ return 'N/A' # raise NotImplementedError
def _lambda_nfdc_flags(self):
flags = ''
@@ -111,14 +122,11 @@ class Face(Resource):
class L2Face(Face):
- src_nic = Attribute(Interface,
- description = "Name of the network interface linked to the face",
- mandatory=True)
- dst_mac = Attribute(String, description = "destination MAC address",
- mandatory=True)
+ __protocols__ = ['ether']
+
ether_proto = Attribute(String,
description = "Ethernet protocol number used by the face",
- default=DEFAULT_ETHER_PROTO)
+ default = DEFAULT_ETHER_PROTO)
def _lambda_nfd_uri(self):
return self.format(FMT_L2FACE)
@@ -127,14 +135,13 @@ class L2Face(Face):
class L4Face(Face):
- ip_version = Attribute(Integer, description = "IPv4 or IPv6", default = 4)
- src_ip = Attribute(String, description = "local IP address",
- mandatory = True)
- src_port = Attribute(Integer, description = "local TCP/UDP port")
- dst_ip = Attribute(String, descrition = "remote IP address",
- mandatory=True)
+ __protocols__ = ['tcp4', 'tcp6', 'udp4', 'udp6']
+
+ src_port = Attribute(Integer, description = "local TCP/UDP port",
+ default = DEFAULT_PORT)
dst_port = Attribute(Integer, description = "remote TCP/UDP port",
- mandatory=True)
+ default = DEFAULT_PORT)
def _lambda_nfd_uri(self):
- return self.format(FMT_L4FACE)
+ fmt = FMT_L4FACE_IPV4 if self.protocol in ['tcp4', 'udp4'] else FMT_L4FACE_IPV6
+ return self.format(fmt)
diff --git a/vicn/resource/icn/forwarder.py b/vicn/resource/icn/forwarder.py
index 748532cf..e2ad944f 100644
--- a/vicn/resource/icn/forwarder.py
+++ b/vicn/resource/icn/forwarder.py
@@ -30,7 +30,7 @@ DEFAULT_CACHE_SIZE = 1000 # pk
DEFAULT_CACHE_POLICY = 'LRU'
DEFAULT_STRATEGY = 'best-route'
-class Forwarder(ICNApplication, ABC):
+class Forwarder(ICNApplication):
"""
Resource: Forwarder
"""
diff --git a/vicn/resource/icn/icn_application.py b/vicn/resource/icn/icn_application.py
index 817d9403..e8b6e19c 100644
--- a/vicn/resource/icn/icn_application.py
+++ b/vicn/resource/icn/icn_application.py
@@ -20,6 +20,7 @@ from vicn.resource.linux.application import LinuxApplication
from vicn.core.attribute import Attribute
from netmodel.model.type import Integer
+ICN_SUITE_HICN_1_0=0
ICN_SUITE_CCNX_1_0=1
ICN_SUITE_NDN=2
diff --git a/vicn/resource/icn/iping.py b/vicn/resource/icn/iping.py
index 0e04eadc..4494c79f 100644
--- a/vicn/resource/icn/iping.py
+++ b/vicn/resource/icn/iping.py
@@ -36,28 +36,28 @@ class IPing(ICNApplication):
__package_names__ = ["libicnet"]
- prefixes = Attribute(String,
+ prefixes = Attribute(String,
description = "name served by the ping server",
default = lambda self: self.default_name(),
mandatory = False,
multiplicity = Multiplicity.OneToMany)
node = Attribute(Node,
requirements=[
- Requirement("forwarder",
- capabilities = set(['ICN_SUITE_CCNX_1_0']),
+ Requirement("forwarder",
+ capabilities = set(['ICN_SUITE_CCNX_1_0']),
properties = {"protocol_suites" : ICN_SUITE_CCNX_1_0})
])
- #--------------------------------------------------------------------------
+ #--------------------------------------------------------------------------
# Methods
- #--------------------------------------------------------------------------
+ #--------------------------------------------------------------------------
def __method_start__(self):
return self._build_command()
- #--------------------------------------------------------------------------
+ #--------------------------------------------------------------------------
# Internal methods
- #--------------------------------------------------------------------------
+ #--------------------------------------------------------------------------
def default_name(self):
return ['/iping']
@@ -72,16 +72,16 @@ class IPingClient(IPing, Producer):
Resource: IPingClient
"""
- flood = Attribute(Bool, description = 'enable flood mode',
+ flood = Attribute(Bool, description = 'enable flood mode',
default = False)
count = Attribute(Integer, description = 'number of ping to send')
- interval = Attribute(Integer,
+ interval = Attribute(Integer,
description = 'interval between interests in ping mode')
size = Attribute(Integer, description = 'size of the interests')
- #--------------------------------------------------------------------------
+ #--------------------------------------------------------------------------
# Internal methods
- #--------------------------------------------------------------------------
+ #--------------------------------------------------------------------------
def _build_command(self):
template = ["iPing_Client", "-l ccnx:{prefix}"]
@@ -110,9 +110,9 @@ class IPingServer(IPing, Consumer):
size = Attribute(Integer, description = "size of the payload")
- #--------------------------------------------------------------------------
+ #--------------------------------------------------------------------------
# Internal methods
- #--------------------------------------------------------------------------
+ #--------------------------------------------------------------------------
def _build_command(self):
template = ["iPing_Server", "-l ccnx:{prefix}"]
diff --git a/vicn/resource/icn/ndnpingserver.py b/vicn/resource/icn/ndnpingserver.py
index f9cfa7cc..8fbb5fb6 100644
--- a/vicn/resource/icn/ndnpingserver.py
+++ b/vicn/resource/icn/ndnpingserver.py
@@ -54,7 +54,7 @@ class NDNPingServerBase(Producer):
default = lambda self: self._default_prefixes())
node = Attribute(requirements = [
- Requirement("forwarder",
+ Requirement("forwarder",
capabilities = set(['ICN_SUITE_NDN_1_0'])) ])
__package_names__ = ['ndnping']
diff --git a/vicn/resource/icn/nfd.py b/vicn/resource/icn/nfd.py
index c65fdeb8..1b7f39f7 100644
--- a/vicn/resource/icn/nfd.py
+++ b/vicn/resource/icn/nfd.py
@@ -22,6 +22,7 @@ import re
from vicn.core.exception import ResourceNotFound
from vicn.core.task import inline_task, BashTask
from vicn.core.task import ParseRegexTask
+from vicn.core.task import inherit_parent
from vicn.resource.icn.forwarder import Forwarder
from vicn.resource.icn.icn_application import ICN_SUITE_NDN
@@ -74,17 +75,20 @@ class NFD(Forwarder):
# Resource lifecycle
#--------------------------------------------------------------------------
+ @inherit_parent
@inline_task
def __get__(self):
# NFD is assumed not to exist
raise ResourceNotFound
+ @inherit_parent
def __create__(self):
# Modify the configuration file before running the forwarder service
conf = BashTask(self.node, CMD_SET_STRATEGY_CACHE, {'nfd': self})
- forwarder = Forwarder.__create__(self)
- return conf.then(forwarder)
+ forwarder = super().__create__()
+ return conf > forwarder
+ @inherit_parent
def __delete__(self):
raise NotImplementedError
diff --git a/vicn/resource/icn/producer.py b/vicn/resource/icn/producer.py
index 131097f2..36cbea77 100644
--- a/vicn/resource/icn/producer.py
+++ b/vicn/resource/icn/producer.py
@@ -16,6 +16,7 @@
# limitations under the License.
#
+from netmodel.model.key import Key
from netmodel.model.type import String
from vicn.resource.icn.icn_application import ICNApplication
from vicn.core.attribute import Attribute, Multiplicity
@@ -33,5 +34,6 @@ class Producer(ICNApplication):
description = 'Node on which the producer is installed',
mandatory = True,
multiplicity = Multiplicity.ManyToOne,
- reverse_name = 'producers',
- key = True)
+ reverse_name = 'producers')
+
+ __key__ = Key(node)
diff --git a/vicn/resource/icn/route.py b/vicn/resource/icn/route.py
index 0dc2ed2f..6efd909b 100644
--- a/vicn/resource/icn/route.py
+++ b/vicn/resource/icn/route.py
@@ -25,12 +25,10 @@ from vicn.resource.node import Node
class Route(Resource):
node = Attribute(Node, mandatory = True)
prefix = Attribute(String, mandatory = True)
- face = Attribute(Face, description = "face used to forward interests",
+ face = Attribute(Face, description = "face used to forward interests",
mandatory=True)
cost = Attribute(Integer, default=1)
def __repr__(self):
- return '<Route {} {} on node {}>'.format(self.prefix, self.face,
+ return '<Route {} {} on node {}>'.format(self.prefix, self.face,
self.node.name)
-
- __str__ = __repr__
diff --git a/vicn/resource/interface.py b/vicn/resource/interface.py
index 0ae2dc94..3aa40fb4 100644
--- a/vicn/resource/interface.py
+++ b/vicn/resource/interface.py
@@ -16,8 +16,9 @@
# limitations under the License.
#
-from vicn.core.attribute import Attribute, Multiplicity
+from netmodel.model.key import Key
from netmodel.model.type import Bool
+from vicn.core.attribute import Attribute, Multiplicity
from vicn.core.resource import Resource
from vicn.resource.node import Node
from vicn.resource.channel import Channel
@@ -30,8 +31,7 @@ class Interface(Resource):
node = Attribute(Node, description = 'Node to which the interface belongs',
multiplicity = Multiplicity.ManyToOne,
reverse_name = 'interfaces',
- mandatory = True,
- key = True)
+ mandatory = True)
channel = Attribute(Channel, description = 'Channel to which the interface is attached',
multiplicity = Multiplicity.ManyToOne,
reverse_name = 'interfaces')
@@ -41,6 +41,8 @@ class Interface(Resource):
default = True)
monitored = Attribute(Bool, default = True)
+ __key__ = Key(node, Resource.name)
+
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
diff --git a/vicn/resource/ip/central.py b/vicn/resource/ip/central.py
new file mode 100644
index 00000000..0c397728
--- /dev/null
+++ b/vicn/resource/ip/central.py
@@ -0,0 +1,288 @@
+#!/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.
+#
+
+import logging
+import networkx as nx
+import os
+
+from netmodel.model.type import String, Integer, Bool
+from vicn.core.attribute import Attribute, Reference
+from vicn.core.exception import ResourceNotFound
+from vicn.core.resource import Resource
+from vicn.core.task import inline_task, BashTask
+from vicn.resource.central import _get_l2_graph, MAP_ROUTING_STRATEGY
+from vicn.resource.channel import Channel
+from vicn.resource.ip_assignment import Ipv4Assignment, Ipv6Assignment
+from vicn.resource.ip.route import IPRoute
+
+log = logging.getLogger(__name__)
+
+CMD_CONTAINER_SET_DNS = 'echo "nameserver {ip_dns}" | ' \
+ 'resolvconf -a {interface_name}'
+
+#-------------------------------------------------------------------------------
+
+class IPRoutes(Resource):
+ """
+ Resource: IPRoutes
+
+ Centralized IP route computation.
+ """
+ routing_strategy = Attribute(String)
+
+ def __after__(self):
+ return ("IpAssignment",)
+
+ #--------------------------------------------------------------------------
+ # Resource lifecycle
+ #--------------------------------------------------------------------------
+
+ @inline_task
+ def __get__(self):
+ raise ResourceNotFound
+
+ @inline_task
+ def __create__(self):
+ routes = list()
+ pre_routes, routes = self._get_ip_routes()
+ routes.extend(pre_routes)
+ routes.extend(routes)
+ for route in routes:
+ route.node.routing_table.routes << route
+
+ def __delete__(self):
+ raise NotImplementedError
+
+ #--------------------------------------------------------------------------
+ # Internal methods
+ #--------------------------------------------------------------------------
+
+ def _get_ip_origins(self):
+ origins = dict()
+ for group in self.groups:
+ for channel in group.iter_by_type_str('channel'):
+ for interface in channel.interfaces:
+ if group.name not in [x.name for x in interface.node.groups]:
+ continue
+ node_uuid = interface.node._state.uuid
+ if not node_uuid in origins:
+ origins[node_uuid] = list()
+ origins[node_uuid].append(interface.ip4_address.canonical_prefix())
+ if interface.ip6_address:
+ origins[node_uuid].append(interface.ip6_address.canonical_prefix())
+ return origins
+
+ def _get_ip_routes(self):
+ if self.routing_strategy == 'pair':
+ return [], self._get_pair_ip_routes()
+
+ strategy = MAP_ROUTING_STRATEGY.get(self.routing_strategy)
+
+ G = _get_l2_graph(self.groups)
+ origins = self._get_ip_origins()
+
+ # node -> list(origins for which we have routes)
+ ip_routes = dict()
+
+ pre_routes = list()
+ routes = list()
+ for src, prefix, dst in strategy(G, origins):
+ data = G.get_edge_data(src, dst)
+
+ map_ = data['map_node_interface']
+ next_hop_interface = map_[src]
+
+ next_hop_ingress = self._state.manager.by_uuid(map_[dst])
+ src_node = self._state.manager.by_uuid(src)
+
+ # Avoid duplicate routes due to multiple paths in the network
+ if not src_node in ip_routes:
+ ip_routes[src_node] = set()
+ #XXX Untested for VPP
+ #When you set up an IP address ip/prefix_len (ip addr add), you already create a route
+ #towards ip/prefix_len
+ for interface in src_node.interfaces:
+ ip_routes[src_node].add(interface.ip4_address.canonical_prefix())
+ ip_routes[src_node].add(interface.ip6_address.canonical_prefix())
+ if prefix in ip_routes[src_node]:
+ continue
+ ip_routes[src_node].add(prefix)
+
+ ip_version = 6 if ":" in str(prefix) else 4
+ next_hop_ingress_ip = (next_hop_ingress.ip6_address if ip_version is 6 else
+ next_hop_ingress.ip4_address)
+ if prefix == next_hop_ingress_ip:
+ # Direct route on src_node.name :
+ # route add [prefix] dev [next_hop_interface_.device_name]
+ route = IPRoute(node = src_node,
+ managed = False,
+ owner = self,
+ ip_address = prefix,
+ ip_version = ip_version,
+ interface = next_hop_interface)
+ else:
+ # We need to be sure we have a route to the gw from the node
+ if not next_hop_ingress_ip.canonical_prefix() in ip_routes[src_node]:
+ pre_route = IPRoute(node = src_node,
+ managed = False,
+ owner = self,
+ ip_address = next_hop_ingress_ip.canonical_prefix(),
+ ip_version = ip_version,
+ interface = next_hop_interface)
+ ip_routes[src_node].add(next_hop_ingress_ip.canonical_prefix())
+ pre_routes.append(pre_route)
+
+ # Route on src_node.name:
+ # route add [prefix] dev [next_hop_interface_.device_name]
+ # via [next_hop_ingress.ip_address]
+ route = IPRoute(node = src_node,
+ managed = False,
+ owner = self,
+ ip_address = prefix,
+ ip_version = ip_version,
+ interface = next_hop_interface,
+ gateway = next_hop_ingress_ip)
+ routes.append(route)
+ return pre_routes, routes
+
+ def _get_pair_ip_routes(self):
+ """
+ IP routing strategy : direct routes only
+ """
+ routes = list()
+ 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)
+
+ map_ = data['map_node_interface']
+ src = self._state.manager.by_uuid(map_[src_node_uuid])
+ dst = self._state.manager.by_uuid(map_[dst_node_uuid])
+
+ log.debug('[IP ROUTE] NODES {}/{}/{} -> {}/{}/{}'.format(
+ src_node.name, src.device_name, src.ip4_address,
+ dst_node.name, dst.device_name, dst.ip4_address))
+ log.debug('[IP ROUTE] NODES {}/{}/{} -> {}/{}/{}'.format(
+ dst_node.name, dst.device_name, dst.ip4_address,
+ src_node.name, src.device_name, src.ip4_address))
+
+ route = IPRoute(node = src_node,
+ managed = False,
+ owner = self,
+ ip_address = dst.ip4_address,
+ mac_address = dst.mac_address,
+ interface = src)
+ routes.append(route)
+ route = IPRoute(node = src_node,
+ managed = False,
+ owner = self,
+ ip_address = dst.ip6_address,
+ ip_version = 6,
+ mac_address = dst.mac_address,
+ interface = src)
+ routes.append(route)
+
+ route = IPRoute(node = dst_node,
+ managed = False,
+ owner = self,
+ ip_address = src.ip4_address,
+ mac_address = src.mac_address,
+ interface = dst)
+ routes.append(route)
+ route = IPRoute(node = dst_node,
+ managed = False,
+ owner = self,
+ ip_address = src.ip6_address,
+ ip_version = 6,
+ mac_address = src.mac_address,
+ interface = dst)
+ routes.append(route)
+
+ return routes
+
+#------------------------------------------------------------------------------
+
+class DnsServerEntry(Resource):
+ """
+ Resource: DnsServerEntry
+
+ Setup of DNS resolver for LxcContainers
+
+ Todo:
+ - This should be merged into the LxcContainer resource
+ """
+
+ node = Attribute(String)
+ ip_address = Attribute(String)
+ interface_name = Attribute(String)
+
+ #--------------------------------------------------------------------------
+ # Resource lifecycle
+ #--------------------------------------------------------------------------
+
+ @inline_task
+ def __get__(self):
+ raise ResourceNotFound
+
+ def __create__(self):
+ return BashTask(self.node, CMD_CONTAINER_SET_DNS,
+ {'ip_dns': str(self.ip_address),
+ 'interface_name': self.interface_name})
+
+ def __delete__(self):
+ raise NotImplementedError
+
+#------------------------------------------------------------------------------
+
+class CentralIP(Resource):
+ """
+ Resource: CentralIP
+
+ Central IP management (main 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)
+ ip6_max_link_prefix = Attribute(Integer,
+ description = 'Maximum prefix size assigned to each link',
+ default = 64)
+ ip6_disjoint_addresses = Attribute(Bool, description="assign disjoint addresses spanning over the whole prefix", default=False)
+
+ #--------------------------------------------------------------------------
+ # Resource lifecycle
+ #--------------------------------------------------------------------------
+
+ def __after_init__(self):
+ return ('Node', 'Channel', 'Interface', 'EmulatedChannel')
+
+ def __subresources__(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'),
+ max_prefix_len = self.ip6_max_link_prefix,
+ disjoint_addresses = self.ip6_disjoint_addresses)
+ ip_routes = IPRoutes(owner = self,
+ groups = Reference(self, 'groups'),
+ routing_strategy = self.ip_routing_strategy)
+
+ return (ip4_assign | ip6_assign) > ip_routes
diff --git a/vicn/resource/ip/prefix_tree.py b/vicn/resource/ip/prefix_tree.py
index 34af1d14..1fec2d47 100644
--- a/vicn/resource/ip/prefix_tree.py
+++ b/vicn/resource/ip/prefix_tree.py
@@ -16,134 +16,7 @@
# limitations under the License.
#
-from socket import inet_pton, inet_ntop, AF_INET6
-from struct import unpack, pack
-from abc import ABCMeta
-
-class PrefixTreeException(Exception): pass
-class NotEnoughAddresses(PrefixTreeException): pass
-class UnassignablePrefix(PrefixTreeException): pass
-
-class Prefix(metaclass=ABCMeta):
-
- def __init__(self, ip_address, prefix_size=None):
- if not prefix_size:
- ip_address, prefix_size = ip_address.split('/')
- prefix_size = int(prefix_size)
- if isinstance(ip_address, str):
- ip_address = self.aton(ip_address)
- self.ip_address = ip_address
- self.prefix_size = prefix_size
-
- def __contains__(self, obj):
- #it can be an IP as a integer
- if isinstance(obj, int):
- obj = type(self)(obj, self.MAX_PREFIX_SIZE)
- #Or it's an IP string
- if isinstance(obj, str):
- #It's a prefix as 'IP/prefix'
- if '/' in obj:
- split_obj = obj.split('/')
- obj = type(self)(split_obj[0], int(split_obj[1]))
- else:
- obj = type(self)(obj, self.MAX_PREFIX_SIZE)
-
- return self._contains_prefix(obj)
-
- @classmethod
- def mask(cls):
- mask_len = cls.MAX_PREFIX_SIZE//8 #Converts from bits to bytes
- mask = 0
- for step in range(0,mask_len):
- mask = (mask << 8) | 0xff
- return mask
-
- def _contains_prefix(self, prefix):
- assert isinstance(prefix, type(self))
- return (prefix.prefix_size >= self.prefix_size and
- prefix.ip_address >= self.first_prefix_address() and
- prefix.ip_address <= self.last_prefix_address())
-
- #Returns the first address of a prefix
- def first_prefix_address(self):
- return self.ip_address & (self.mask() << (self.MAX_PREFIX_SIZE-self.prefix_size))
-
- def last_prefix_address(self):
- return self.ip_address | (self.mask() >> self.prefix_size)
-
- def limits(self):
- return self.first_prefix_address(), self.last_prefix_address()
-
- def __str__(self):
- return "{}/{}".format(self.ntoa(self.first_prefix_address()), self.prefix_size)
-
- def __eq__(self, other):
- return (self.first_prefix_address() == other.first_prefix_address() and
- self.prefix_size == other.prefix_size)
-
- def __hash__(self):
- return hash(str(self))
-
- def __iter__(self):
- return self.get_iterator()
-
- #Iterates by steps of prefix_size, e.g., on all available /31 in a /24
- def get_iterator(self, prefix_size=None):
- if prefix_size is None:
- prefix_size=self.MAX_PREFIX_SIZE
- assert (prefix_size >= self.prefix_size and prefix_size<=self.MAX_PREFIX_SIZE)
- step = 2**(self.MAX_PREFIX_SIZE - prefix_size)
- for ip in range(self.first_prefix_address(), self.last_prefix_address()+1, step):
- yield type(self)(ip, prefix_size)
-
-class Inet4Prefix(Prefix):
-
- MAX_PREFIX_SIZE = 32
-
- @classmethod
- def aton(cls, address):
- ret = 0
- components = address.split('.')
- for comp in components:
- ret = (ret << 8) + int(comp)
- return ret
-
- @classmethod
- def ntoa(cls, address):
- components = []
- for _ in range(0,4):
- components.insert(0,'{}'.format(address % 256))
- address = address >> 8
- return '.'.join(components)
-
-class Inet6Prefix(Prefix):
-
- MAX_PREFIX_SIZE = 128
-
- @classmethod
- def aton (cls, address):
- prefix, suffix = unpack(">QQ", inet_pton(AF_INET6, address))
- return (prefix << 64) | suffix
-
- @classmethod
- def ntoa (cls, address):
- return inet_ntop(AF_INET6, pack(">QQ", address >> 64, address & ((1 << 64) -1)))
-
- #skip_internet_address: skip a:b::0, as v6 often use default /64 prefixes
- def get_iterator(self, prefix_size=None, skip_internet_address=None):
- if skip_internet_address is None:
- #We skip the internet address if we iterate over Addresses
- if prefix_size is None:
- skip_internet_address = True
- #But not if we iterate over prefixes
- else:
- skip_internet_address = False
- it = super().get_iterator(prefix_size)
- if skip_internet_address:
- next(it)
- return it
-
-###### PREFIX TREE ######
+from netmodel.model.type import Inet4Prefix, Inet6Prefix
class PrefixTree:
@@ -160,21 +33,21 @@ class PrefixTree:
self.full = False
- def find_prefix(self, prefix_size):
+ def find_prefix(self, prefix_len):
ret, lret, rret = [None]*3
- if prefix_size > self.prefix.prefix_size and not self.full:
+ if prefix_len > self.prefix.prefix_len and not self.full:
if self.left is None:
- lret = self.prefix_cls(self.prefix.first_prefix_address(), self.prefix.prefix_size+1)
+ lret = self.prefix_cls(self.prefix.first_prefix_address(), self.prefix.prefix_len+1)
else:
- lret = self.left.find_prefix(prefix_size)
+ lret = self.left.find_prefix(prefix_len)
if self.right is None:
- rret = self.prefix_cls(self.prefix.last_prefix_address(), self.prefix.prefix_size+1)
+ rret = self.prefix_cls(self.prefix.last_prefix_address(), self.prefix.prefix_len+1)
else:
- rret = self.right.find_prefix(prefix_size)
+ rret = self.right.find_prefix(prefix_len)
#Now we look for the longer prefix to assign
- if not lret or (rret and rret.prefix_size > lret.prefix_size):
+ if not lret or (rret and rret.prefix_len > lret.prefix_len):
ret = rret
else:
ret = lret
@@ -183,9 +56,9 @@ class PrefixTree:
def assign_prefix(self, prefix):
assert prefix in self.prefix
- if prefix.prefix_size > self.prefix.prefix_size:
+ if prefix.prefix_len > self.prefix.prefix_len:
#Existing prefix on the left
- lp = self.prefix_cls(self.prefix.first_prefix_address(), self.prefix.prefix_size+1)
+ lp = self.prefix_cls(self.prefix.first_prefix_address(), self.prefix.prefix_len+1)
#It's on the left branch
if prefix in lp:
if not self.left:
@@ -193,7 +66,7 @@ class PrefixTree:
self.left.assign_prefix(prefix)
#It's on the right branch
else:
- rp = self.prefix_cls(self.prefix.last_prefix_address(), self.prefix.prefix_size+1)
+ rp = self.prefix_cls(self.prefix.last_prefix_address(), self.prefix.prefix_len+1)
if not self.right:
self.right = PrefixTree(rp)
self.right.assign_prefix(prefix)
@@ -202,13 +75,14 @@ class PrefixTree:
else:
raise RuntimeError("And you may ask yourself, well, how did I get here?")
- def get_prefix(self, prefix_size):
- ret = self.find_prefix(prefix_size)
+ def get_prefix(self, prefix_len):
+ ret = self.find_prefix(prefix_len)
if not ret:
raise NotEnoughAddresses
#find_prefix returns the size of the largest available prefix in our space
#not necessarily the one we asked for
- ret.prefix_size = prefix_size
+ ret.ip_address = ret.first_prefix_address()
+ ret.prefix_len = prefix_len
self.assign_prefix(ret)
return ret
diff --git a/vicn/resource/ip/route.py b/vicn/resource/ip/route.py
index b9f82960..2f5807b6 100644
--- a/vicn/resource/ip/route.py
+++ b/vicn/resource/ip/route.py
@@ -16,7 +16,7 @@
# limitations under the License.
#
-from netmodel.model.type import String, Integer
+from netmodel.model.type import String, Integer, InetAddress
from vicn.resource.node import Node
from vicn.core.attribute import Attribute
from vicn.core.resource import Resource
@@ -24,10 +24,8 @@ from vicn.resource.interface import Interface
class IPRoute(Resource):
node = Attribute(Node, mandatory = True)
- ip_address = Attribute(String, mandatory = True)
+ ip_address = Attribute(InetAddress, mandatory = True)
interface = Attribute(Interface, mandatory = True)
- gateway = Attribute(String)
+ gateway = Attribute(InetAddress)
ip_version = Attribute(Integer, default=4)
-
- # FIXME Temp hack for VPP, migrate this to an ARP table resource
- mac_address = Attribute(String)
+ metric = Attribute(Integer)
diff --git a/vicn/resource/ip/routing_table.py b/vicn/resource/ip/routing_table.py
index e45793cc..ce16524c 100644
--- a/vicn/resource/ip/routing_table.py
+++ b/vicn/resource/ip/routing_table.py
@@ -20,28 +20,31 @@ from vicn.core.attribute import Attribute, Multiplicity
from vicn.core.exception import ResourceNotFound
from vicn.core.resource import Resource
from vicn.core.task import EmptyTask, BashTask
+from vicn.core.task import inherit_parent
from vicn.resource.ip.route import IPRoute
from vicn.resource.node import Node
from vicn.resource.vpp.vpp_commands import CMD_VPP_ADD_ROUTE
from vicn.resource.vpp.vpp_commands import CMD_VPP_ADD_ROUTE_GW
-CMD_ADD_ROUTE = ('ip -{route.ip_version} route add {route.ip_address} '
- 'dev {route.interface.device_name} || true')
-CMD_ADD_ROUTE_GW = ('ip -{route.ip_version} route add {route.ip_address} '
- 'dev {route.interface.device_name} via {route.gateway} || true')
-CMD_DEL_ROUTE = ('ip -{route.ip_version} route del {route.ip_address} '
+CMD_ADD_ROUTE = ('ip -{route.ip_version} route add {route.ip_address}/{route.ip_address.prefix_len} '
+ 'dev {route.interface.device_name}')
+CMD_ADD_ROUTE_GW = ('ip -{route.ip_version} route add {route.ip_address}/{route.ip_address.prefix_len} '
+ 'dev {route.interface.device_name} via {route.gateway}')
+CMD_ADD_ROUTE_GWM = ('ip -{route.ip_version} route add {route.ip_address}/{route.ip_address.prefix_len} '
+ 'dev {route.interface.device_name} via {route.gateway} metric {route.metric}')
+CMD_DEL_ROUTE = ('ip -{route.ip_version} route del {route.ip_address}/{route.ip_address.prefix_len} '
'dev {route.interface.device_name}')
CMD_SHOW_ROUTES = 'ip route show'
-CMD_ADD_ARP_ENTRY = 'arp -s {route.ip_address} {route.mac_address}'
-CMD_ADD_NDB_ENTRY = ('ip -6 neigh add {route.ip_address} lladr {route.mac_address} '
- 'dev {route.interface.device_name}')
+#CMD_ADD_ARP_ENTRY = 'arp -s {route.ip_address} {route.mac_address}'
+#CMD_ADD_NDB_ENTRY = ('ip -6 neigh add {route.ip_address} lladr {route.mac_address} '
+# 'dev {route.interface.device_name}')
# Populate arp table too. The current configuration with one single bridge
# connecting every container and vpp nodes seem to create loops that prevent
# vpp from netmodel.network.interface for routing ip packets.
-VPP_ARP_FIX = True
+#VPP_ARP_FIX = True
def _iter_routes(out):
for line in out.splitlines():
@@ -97,6 +100,7 @@ class RoutingTable(Resource):
def __after__(self):
return ('CentralIP', 'VPPInterface', 'ContainerSetup')
+ @inherit_parent
def __get__(self):
def cache(rv):
for route in _iter_routes(rv.stdout):
@@ -106,6 +110,7 @@ class RoutingTable(Resource):
raise ResourceNotFound
return BashTask(self.node, CMD_SHOW_ROUTES, parse=cache)
+ @inherit_parent
def __create__(self):
"""
Create a single BashTask for all routes
@@ -114,7 +119,6 @@ class RoutingTable(Resource):
done = set()
routes_cmd = list()
routes_via_cmd = list()
- arp_cmd = list()
# vppctl lock
# NOTE: we currently lock vppctl during the whole route update
@@ -122,7 +126,7 @@ class RoutingTable(Resource):
routes_via_lock = None
for route in self.routes:
- if route.ip_address in self._routes:
+ if str(route.ip_address) in self._routes:
continue
if route.ip_address in done:
continue
@@ -135,21 +139,22 @@ class RoutingTable(Resource):
cmd = CMD_ADD_ROUTE.format(route = route)
routes_cmd.append(cmd)
else:
- cmd = CMD_ADD_ROUTE_GW.format(route = route)
- routes_via_cmd.append(cmd)
- if VPP_ARP_FIX and route.mac_address:
- if route.ip_address != "default":
- cmd = CMD_ADD_ARP_ENTRY.format(route = route)
- arp_cmd.append(cmd)
+ if route.metric is None:
+ cmd = CMD_ADD_ROUTE_GW.format(route = route)
+ routes_via_cmd.append(cmd)
+ else:
+ cmd = CMD_ADD_ROUTE_GWM.format(route = route)
+ routes_via_cmd.append(cmd)
else:
+ if route.metric == 1025:
+ continue
if route.gateway is None:
cmd = CMD_VPP_ADD_ROUTE.format(route = route)
- routes_cmd.append(cmd)
routes_lock = route.node.vpp.vppctl_lock
else:
cmd = CMD_VPP_ADD_ROUTE_GW.format(route = route)
- routes_via_cmd.append(cmd)
routes_via_lock = route.node.vpp.vppctl_lock
+ routes_via_cmd.append(cmd)
# TODO: checks
clean_routes_task = EmptyTask()
@@ -166,12 +171,8 @@ class RoutingTable(Resource):
else:
routes_via_task = EmptyTask()
- if len(arp_cmd) > 0:
- arp_task = BashTask(self.node, '\n'.join(arp_cmd))
- else:
- arp_task = EmptyTask()
-
- return ((clean_routes_task > routes_task) > routes_via_task) > arp_task
+ return ((clean_routes_task > routes_task) > routes_via_task)
+ @inherit_parent
def __delete__(self):
raise NotImplementedError
diff --git a/vicn/resource/ip_assignment.py b/vicn/resource/ip_assignment.py
index 55401ecd..3bb16ff9 100644
--- a/vicn/resource/ip_assignment.py
+++ b/vicn/resource/ip_assignment.py
@@ -20,10 +20,12 @@ import math
import logging
from vicn.core.resource import Resource
-from netmodel.model.type import String
+from netmodel.model.type import String, Bool
from vicn.core.attribute import Attribute
from vicn.resource.ip.prefix_tree import Inet6Prefix, PrefixTree, Inet4Prefix
+from netmodel.model.type import Inet4Address, Inet6Address
from vicn.core.task import inline_task, async_task, EmptyTask
+from vicn.core.task import inherit_parent
from vicn.core.exception import ResourceNotFound
log = logging.getLogger(__name__)
@@ -31,8 +33,9 @@ log = logging.getLogger(__name__)
class IpAssignment(Resource):
prefix = Attribute(String, mandatory=True)
control_prefix = Attribute(String, description="prefix for control plane")
- max_prefix_size = Attribute(String,
+ max_prefix_len = Attribute(String,
description="Maximum assigned prefix size for a link")
+ disjoint_addresses = Attribute(Bool, default=False)
PrefixClass = None
@@ -41,20 +44,20 @@ class IpAssignment(Resource):
self._prefix = self.PrefixClass(self.prefix)
self._prefix_tree = PrefixTree(self._prefix)
self._assigned_prefixes = {}
- if not self.max_prefix_size:
- self.max_prefix_size = self.PrefixClass.MAX_PREFIX_SIZE
+ if not self.max_prefix_len:
+ self.max_prefix_len = self.PrefixClass.MAX_PREFIX_SIZE
if self.control_prefix:
self._ctrl_prefix = self.PrefixClass(self.control_prefix)
self._ctrl_prefix_it = iter(self._ctrl_prefix)
next(self._ctrl_prefix_it) #Removes internet address
self._assigned_addresses = {}
- def get_prefix(self, obj, prefix_size):
+ def get_prefix(self, obj, prefix_len):
ret = None
if obj in self._assigned_prefixes:
ret = self._assigned_prefixes[obj]
else:
- ret = self._prefix_tree.get_prefix(prefix_size)
+ ret = self._prefix_tree.get_prefix(prefix_len)
self._assigned_prefixes[obj] = ret
return ret
@@ -69,10 +72,12 @@ class IpAssignment(Resource):
self._assigned_addresses[obj] = ret
return ret
+ @inherit_parent
@inline_task
def __get__(self):
raise ResourceNotFound
+ @inherit_parent
#@inline_task
def __create__(self):
# XXX code from Channel.__create__, until Events are properly implemented.
@@ -80,38 +85,77 @@ class IpAssignment(Resource):
task = EmptyTask()
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.max_prefix_size,
- self.PrefixClass.MAX_PREFIX_SIZE - min_prefix_size)
- prefix = self.get_prefix(channel, prefix_size)
-
- it = prefix.get_iterator()
+ if channel.has_type("emulatedchannel"):
+ interfaces = [channel._ap_if]
+ interfaces.extend(channel._sta_ifs.values())
+ else:
+ interfaces = sorted(channel.interfaces, key = lambda x : x.device_name)
+ if not interfaces:
+ continue
+
+ if self.PrefixClass is Inet6Prefix:
+ # 0 is forbidden
+ num_required_ip = len(interfaces) + 1
+ elif channel.has_type("emulatedchannel"): #EmulatedChannel + IPv4
+ num_required_ip = len(interfaces) + 2 + channel.nb_base_stations #Internet address + bcast
+ else:
+ num_required_ip = len(interfaces)
+ min_prefix_len = math.ceil(math.log(num_required_ip, 2))
+
+ prefix_len = min(self.max_prefix_len,
+ self.PrefixClass.MAX_PREFIX_SIZE - min_prefix_len)
+
+ #XXX lte-emulator expects /24
+ if channel.has_type("emulatedltechannel") and self.PrefixClass is Inet4Prefix:
+ prefix_len = 24
+
+ # Retrieve a prefix for the whole channel
+ prefix = self.get_prefix(channel, prefix_len)
+
+ # by default iterate on /32 or /128, unless we require to
+ # iterate on less specific
+ it_prefix_len = self.PrefixClass.MAX_PREFIX_SIZE
+
+ if self.disjoint_addresses and prefix_len+min_prefix_len <= self.PrefixClass.MAX_PREFIX_SIZE:
+ it_prefix_len = min_prefix_len + prefix_len
+
+ # Use an iterator to assign IPs from the prefix to the
+ # interfaces
+ it = prefix.get_iterator(it_prefix_len)
+ # XXX MACCHA it is a prefix
+
+ if channel.has_type("emulatedchannel"):
+ #Internet address
+ next(it)
for interface in interfaces:
- ip = next(it)
- interface.set(self.ATTR_PREFIX, prefix_size)
- #XXX Why do we need to create that async task?
- #XXX Probably because the PendingValue is not created
- #XXX in the main thread
+ if_prefix = next(it)
+ #XXX We cannot assign prefix::0 as a valid address to an interface.
+ #XXX For each interface with an ip6 address belonging to prefix,
+ #XXX linux add prefix::0 to the local routing table. Therefore prefix::0 cannot be
+ #XXX the address of a non local interface
+ ip = if_prefix.ip_address
+ if ip == prefix.first_prefix_address() and self.PrefixClass is Inet6Prefix:
+ if if_prefix.prefix_len < if_prefix.MAX_PREFIX_SIZE:
+ if_prefix.ip_address = ip+1
+ else:
+ if_prefix = next(it)
+
+ if_prefix.prefix_len = prefix_len
+ if self.PrefixClass is Inet6Prefix:
+ address = Inet6Address(if_prefix.ip_address, prefix_len)
+ else:
+ address = Inet4Address(if_prefix.ip_address, prefix_len)
@async_task
- async def set_ip(interface, ip):
- await interface.async_set(self.ATTR_ADDRESS, self.PrefixClass.ntoa(ip.ip_address))
- task = task | set_ip(interface, ip)
-
+ async def set_ip(interface, address):
+ await interface.async_set(self.ATTR_ADDRESS, address)
+ task = task | set_ip(interface, address)
return task
- __delete__ = None
-
class Ipv6Assignment(IpAssignment):
PrefixClass = Inet6Prefix
- ATTR_ADDRESS = 'ip6_address'
- ATTR_PREFIX = 'ip6_prefix'
+ ATTR_ADDRESS = 'ip6_address'
class Ipv4Assignment(IpAssignment):
PrefixClass = Inet4Prefix
- ATTR_ADDRESS = 'ip4_address'
- ATTR_PREFIX = 'ip4_prefix'
+ ATTR_ADDRESS = 'ip4_address'
diff --git a/vicn/resource/linux/bridge.py b/vicn/resource/linux/bridge.py
index 7b5ceed7..edc8acdc 100644
--- a/vicn/resource/linux/bridge.py
+++ b/vicn/resource/linux/bridge.py
@@ -22,18 +22,15 @@ from vicn.core.address_mgr import AddressManager
from vicn.core.attribute import Attribute, Multiplicity
from vicn.core.exception import ResourceNotFound
from vicn.core.requirement import Requirement
-from vicn.core.task import inline_task
+from vicn.core.task import inline_task, inherit
from vicn.resource.channel import Channel
+from vicn.resource.interface import Interface
from vicn.resource.linux.bridge_mgr import BridgeManager
-from vicn.resource.linux.net_device import BaseNetDevice
+from vicn.resource.linux.net_device import NetDevice
log = logging.getLogger(__name__)
-# FIXME This should use the AddressManager to get allocated a name that does
-# not exist
-DEFAULT_BRIDGE_NAME = 'br0'
-
-class Bridge(Channel, BaseNetDevice):
+class Bridge(Channel, NetDevice):
"""
Resource: Bridge
"""
@@ -62,21 +59,10 @@ class Bridge(Channel, BaseNetDevice):
# Resource lifecycle
#--------------------------------------------------------------------------
- @inline_task
- def __get__(self):
- # FIXME we currently force the recreation of the bridge, delegating the
- # check to the creation function
- raise ResourceNotFound
-
+ @inherit(Channel, Interface)
def __create__(self):
- # FIXME : reserves .1 IP address for the bridge, provided no other
- # class uses this trick
- AddressManager().get_ip(self)
return self.node.bridge_manager.add_bridge(self.device_name)
- # Everything should be handled by BaseNetDevice
- __delete__ = None
-
#--------------------------------------------------------------------------
# Resource lifecycle
#--------------------------------------------------------------------------
@@ -86,7 +72,7 @@ class Bridge(Channel, BaseNetDevice):
Returns:
Task
"""
- return self.node.bridge_manager.add_interface(self.device_name,
+ return self.node.bridge_manager.add_interface(self.device_name,
interface.device_name, vlan)
def __method_add_interface__(self, interface, vlan=None):
@@ -99,6 +85,6 @@ class Bridge(Channel, BaseNetDevice):
"""
log.info('Removing interface {} from bridge {}'.format(
interface.device_name, self.name))
- return self.node.bridge_manager.del_interface(self.device_name,
+ return self.node.bridge_manager.del_interface(self.device_name,
interface.device_name)
diff --git a/vicn/resource/linux/certificate.py b/vicn/resource/linux/certificate.py
index dd451770..2cc6c9b0 100644
--- a/vicn/resource/linux/certificate.py
+++ b/vicn/resource/linux/certificate.py
@@ -23,6 +23,7 @@ from vicn.core.attribute import Attribute, Multiplicity, Reference
from vicn.core.exception import ResourceNotFound
from vicn.core.resource import Resource
from vicn.core.task import task, inline_task, BashTask
+from vicn.core.task import inherit_parent
from vicn.resource.linux.file import File
from vicn.resource.node import Node
@@ -49,29 +50,40 @@ class Certificate(Resource):
multiplicity = Multiplicity.ManyToOne)
cert = Attribute(String, description = 'Certificate path',
mandatory = True)
- key = Attribute(String, description = 'Key path',
- mandatory = True)
+ key = Attribute(String, description = 'Key path')
#--------------------------------------------------------------------------
# Resource lifecycle
#--------------------------------------------------------------------------
- @inline_task
- def __initialize__(self):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
self._cert_file = File(node = Reference(self, 'node'),
filename = Reference(self, 'cert'),
managed = False)
- self._key_file = File(node = Reference(self, 'node'),
- filename = Reference(self, 'key'),
- managed = False)
+ if self.key:
+ self._key_file = File(node = Reference(self, 'node'),
+ filename = Reference(self, 'key'),
+ managed = False)
+ else:
+ self._key_file = None
+ @inherit_parent
def __get__(self):
- return self._cert_file.__get__() | self._key_file.__get__()
+ if self.key:
+ return self._cert_file.__get__() | self._key_file.__get__()
+ else:
+ return self._cert_file.__get__()
+ @inherit_parent
def __create__(self):
return BashTask(self.node, CMD_CREATE, {'self': self})
+ @inherit_parent
def __delete__(self):
- return self._cert_file.__delete__() | self._key_file.__delete__()
+ if self.key:
+ return self._cert_file.__delete__() | self._key_file.__delete__()
+ else:
+ return self._cert_file.__delete__()
diff --git a/vicn/resource/linux/certificate_store.py b/vicn/resource/linux/certificate_store.py
new file mode 100644
index 00000000..59d9a239
--- /dev/null
+++ b/vicn/resource/linux/certificate_store.py
@@ -0,0 +1,54 @@
+#!/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.attribute import Attribute, Multiplicity
+from vicn.core.resource import Resource
+from vicn.resource.linux.certificate import Certificate
+from vicn.resource.node import Node
+
+PACKAGE = 'ca-certificates'
+
+CMD_ADD_CERTIFICATE = '''
+cp {certificate.cert} {store.PATH}
+dpkg-reconfigure ca-certificates
+'''
+
+class CertificateStore(Resource):
+ """
+ Resource: System-wide Certificate Store
+
+ This resource allows manipulation of the trusted certificates on the system.
+ Use with care.
+
+ TODO:
+ - Ensure ca-certificates package is installed.
+ - Issue a warning to the user when it is used.
+ """
+
+ PATH = '/usr/share/ca-certificates'
+
+ certificates = Attribute(Certificate, multiplicity = Multiplicity.OneToMany)
+ node = Attribute(Node, mandatory = True, requirements = [
+ #Requirement(PACKAGE, 'in', 'packages')
+ ])
+
+ def _add_certificate(self):
+ # Return a task that takes a certificate as parameter
+ PARAM = None
+ return BashTask(self.node, CMD_ADD_CERTIFICATE, {'store': self, 'certificate': PARAM},
+ root = True)
diff --git a/vicn/resource/linux/dnsmasq.py b/vicn/resource/linux/dnsmasq.py
index b5aa8053..70358ecb 100644
--- a/vicn/resource/linux/dnsmasq.py
+++ b/vicn/resource/linux/dnsmasq.py
@@ -24,6 +24,7 @@ from string import Template
from netmodel.model.type import String, Bool
from vicn.core.attribute import Attribute
from vicn.core.resource import EmptyResource
+from vicn.core.task import inherit_parent, override_parent
from vicn.resource.dns_server import DnsServer
from vicn.resource.interface import Interface
from vicn.resource.linux.file import TextFile
@@ -72,15 +73,16 @@ class DnsMasq(Service, DnsServer):
log_dhcp = Attribute(Bool, description = 'Flag: log DHCP queries',
default = True)
- #--------------------------------------------------------------------------
- # Resource lifecycle
- #--------------------------------------------------------------------------
-
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not self.interface:
raise Exception("Cannot initialize bridge without interface")
+ #--------------------------------------------------------------------------
+ # Resource lifecycle
+ #--------------------------------------------------------------------------
+
+ @inherit_parent
def __subresources__(self):
# Overwrite configuration file
flags = list()
@@ -112,3 +114,7 @@ class DnsMasq(Service, DnsServer):
return TextFile(node = self.node, owner = self, filename = FN_CONF,
content = conf, overwrite = True)
+
+ @override_parent
+ def __create__(self):
+ return self.__method_stop_start()
diff --git a/vicn/resource/linux/file.py b/vicn/resource/linux/file.py
index 44b4b5be..2a62f60e 100644
--- a/vicn/resource/linux/file.py
+++ b/vicn/resource/linux/file.py
@@ -16,11 +16,12 @@
# limitations under the License.
#
+from netmodel.model.key import Key
from netmodel.model.type import String, Bool
from vicn.core.attribute import Attribute, Multiplicity
from vicn.core.exception import ResourceNotFound
from vicn.core.resource import Resource
-from vicn.core.task import BashTask, inline_task
+from vicn.core.task import BashTask, inline_task, inherit_parent
from vicn.resource.node import Node
CREATE_DIR_CMD = "mkdir -p {dir}"
@@ -38,22 +39,23 @@ class File(Resource):
Resource: File
"""
filename = Attribute(String, description = 'Path to the file',
- key = True,
mandatory = True)
node = Attribute(Node, description = 'Node on which the file is created',
mandatory = True,
multiplicity = Multiplicity.ManyToOne,
reverse_name = 'files',
- key = True,
reverse_description = 'Files created on the node')
overwrite = Attribute(Bool,
description = 'Determines whether an existing file is overwritten',
default = False)
+ __key__ = Key(node, filename)
+
#--------------------------------------------------------------------------
# Resource lifecycle
#--------------------------------------------------------------------------
+ @inherit_parent
def __get__(self):
# UGLY
@inline_task
@@ -72,12 +74,14 @@ class File(Resource):
test = BashTask(self.node, GET_FILE_CMD, {"file": self}, parse=is_path)
return test
+ @inherit_parent
def __create__(self):
ctask = BashTask(self.node, CREATE_FILE_CMD, {"file": self})
if self.overwrite:
ctask = BashTask(self.node, DELETE_FILE_CMD, {'file': self}) > ctask
return ctask
+ @inherit_parent
def __delete__(self):
return BashTask(self.node, DELETE_FILE_CMD, { "file" : self})
@@ -96,6 +100,8 @@ class TextFile(File):
# Resource lifecycle
#--------------------------------------------------------------------------
+ # XXX REDUNDANT !!!
+ @inherit_parent
def __create__(self):
return BashTask(self.node, CMD_PRINT_TO_FILE, {'file': self})
diff --git a/vicn/resource/linux/folder.py b/vicn/resource/linux/folder.py
new file mode 100644
index 00000000..636ecbcd
--- /dev/null
+++ b/vicn/resource/linux/folder.py
@@ -0,0 +1,88 @@
+#!/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 netmodel.model.key import Key
+from netmodel.model.type import String, Bool, Integer
+from vicn.core.attribute import Attribute, Multiplicity
+from vicn.core.exception import ResourceNotFound
+from vicn.core.resource import Resource
+from vicn.core.task import BashTask, inline_task, EmptyTask
+from vicn.resource.node import Node
+
+CREATE_FOLDER_CMD = "mkdir -p {folder.foldername}"
+DELETE_FOLDER_CMD = "rm -f {folder.foldername}"
+
+GET_FOLDER_CMD = 'test -d {folder.foldername} && readlink -e {folder.foldername}'
+
+SET_FOLDER_PERMISSION_CMD = 'chmod {folder.permission} {folder.foldername}'
+
+class Folder(Resource):
+ """
+ Resource: Folder
+ """
+ foldername = Attribute(String, description = 'Path to the folder',
+ mandatory = True)
+ node = Attribute(Node, description = 'Node on which the directory is created',
+ mandatory = True,
+ multiplicity = Multiplicity.ManyToOne,
+ reverse_name = 'folders',
+ reverse_description = 'Folders created on the node')
+ overwrite = Attribute(Bool,
+ description = 'Determines whether an existing folder is overwritten',
+ default = False)
+ permission = Attribute(Integer,
+ description = 'Permission to set in the folder',
+ default = 775)
+
+ __key__ = Key(node, foldername)
+ #--------------------------------------------------------------------------
+ # Resource lifecycle
+ #--------------------------------------------------------------------------
+
+ @inline_task
+ def __get__(self):
+ # UGLY
+# @inline_task
+# def not_found():
+ raise ResourceNotFound
+
+ # if self.overwrite:
+ # return not_found()
+
+ # def is_path (rv):
+ # if rv is None or rv.stdout is None or len(rv.stdout) == 0 or \
+ # rv.return_value != 0:
+ # raise ResourceNotFound
+ # return {} # 'filename': rv.stdout}
+
+ # create = BashTask(self.node, GET_FOLDER_CMD, {"folder": self}, parse=is_path)
+
+ # return create
+
+ def __create__(self):
+ ctask = BashTask(self.node, CREATE_FOLDER_CMD, {"folder": self})
+
+ if self.overwrite:
+ ctask = BashTask(self.node, DELETE_FOLDER_CMD, {'folder': self}) > ctask
+
+ set_permission = BashTask(self.node, SET_FOLDER_PERMISSION_CMD, {"folder": self})
+
+ return ctask > set_permission
+
+ def __delete__(self):
+ return BashTask(self.node, DELETE_FOLFER_CMD, { "folder" : self})
diff --git a/vicn/resource/linux/gre_tunnel.py b/vicn/resource/linux/gre_tunnel.py
new file mode 100644
index 00000000..c5a25307
--- /dev/null
+++ b/vicn/resource/linux/gre_tunnel.py
@@ -0,0 +1,63 @@
+#!/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.
+
+import string
+
+from netmodel.model.type import Integer, String
+from vicn.core.attribute import Attribute
+from vicn.core.resource import Resource
+from vicn.core.task import BashTask, inherit_parent, override_parent
+from vicn.resource.channel import Channel
+from vicn.resource.linux.net_device import NetDevice, SlaveNetDevice
+
+CMD_CREATE_GRE_TUNNEL='''
+ip tunnel add {device_name} mode gre remote {dst} local {src} ttl 255
+'''
+
+class GREChannel(Channel):
+ """
+ Resource: GRETunnel
+ """
+ pass
+
+class GREInterface(SlaveNetDevice):
+
+ remote_address = Attribute(String, description ='',
+ mandatory = True)
+
+ @override_parent
+ def __create__(self):
+ return BashTask(self.src_interface.node, CMD_CREATE_GRE_TUNNEL, {
+ 'device_name': self.device_name,
+ 'src': str(self.parent.ip4_address),
+ 'dst': self.remote_address})
+
+class GRETunnel(Resource):
+
+ src_interface = Attribute(NetDevice, description = 'source interface',
+ mandatory = True)
+ dst_interface = Attribute(NetDevice, description = 'destination interface',
+ mandatory = True)
+
+ @inherit_parent
+ def __subresources__(self):
+ channel = GREChannel()
+ src = GREInterface(node=src_interface.node, device_name="gre0",
+ parent=src_interface, channel=channel)
+ dst = GREInterface(node=dst_interface.node, device_name="gre0",
+ parent=dst_interface, channel=channel)
+ return (src | dst) | channel
diff --git a/vicn/resource/linux/iperf.py b/vicn/resource/linux/iperf.py
index a0780a1c..e4b8e94c 100644
--- a/vicn/resource/linux/iperf.py
+++ b/vicn/resource/linux/iperf.py
@@ -16,12 +16,10 @@
# limitations under the License.
#
-from abc import ABC
-
from netmodel.model.type import Integer
from vicn.core.attribute import Attribute
from vicn.resource.linux.application import LinuxApplication
-class Iperf3(LinuxApplication, ABC):
+class Iperf3(LinuxApplication):
__package_names__ = ['iperf3']
diff --git a/vicn/resource/linux/keypair.py b/vicn/resource/linux/keypair.py
index 66c98e5b..b748b756 100644
--- a/vicn/resource/linux/keypair.py
+++ b/vicn/resource/linux/keypair.py
@@ -23,6 +23,7 @@ from vicn.core.attribute import Attribute, Multiplicity, Reference
from vicn.core.exception import ResourceNotFound
from vicn.core.resource import Resource
from vicn.core.task import task, inline_task, BashTask
+from vicn.core.task import inherit_parent
from vicn.resource.linux.file import File
from vicn.resource.node import Node
@@ -48,8 +49,8 @@ class Keypair(Resource):
# Resource lifecycle
#--------------------------------------------------------------------------
- @inline_task
- def __initialize__(self):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
self._pubkey_file = File(node = Reference(self, 'node'),
filename = self.key + '.pub',
managed = False)
@@ -57,14 +58,17 @@ class Keypair(Resource):
filename = self.key,
managed = False)
+ @inherit_parent
def __get__(self):
return self._pubkey_file.__get__() | self._key_file.__get__()
+ @inherit_parent
def __create__(self):
return BashTask(self.node, CMD_CREATE, {
'dirname': os.path.dirname(self.key),
'self': self})
+ @inherit_parent
def __delete__(self):
return self._pubkey_file.__delete__() | self._key_file.__delete__()
diff --git a/vicn/resource/linux/link.py b/vicn/resource/linux/link.py
index da41fbe1..3ffaae97 100644
--- a/vicn/resource/linux/link.py
+++ b/vicn/resource/linux/link.py
@@ -20,12 +20,14 @@ import random
import string
import logging
+from netmodel.model.key import Key
from netmodel.model.type import Integer, String
from vicn.core.attribute import Attribute, Reference
from vicn.core.exception import ResourceNotFound
from vicn.core.state import ResourceState, AttributeState
from vicn.core.task import inline_task, async_task, run_task
from vicn.core.task import get_attributes_task, BashTask
+from vicn.core.task import inherit_parent
from vicn.resource.channel import Channel
from vicn.resource.interface import Interface
from vicn.resource.linux.net_device import NonTapBaseNetDevice
@@ -53,7 +55,7 @@ ip link set dev {tmp_src} netns {pid} name {interface.device_name}
ip link set dev {tmp_dst} up
ovs-vsctl add-port {host.bridge.device_name} {tmp_dst}'''
-CMD_UP='''
+CMD_SET_UP='''
ip link set dev {interface.device_name} up
'''
@@ -73,18 +75,19 @@ class Link(Channel):
delay = Attribute(String, description = 'Link propagation delay')
src_node = Attribute(Node, description = 'Source node',
- key = True,
mandatory = True)
dst_node = Attribute(Node, description = 'Destination node',
- key = True,
mandatory = True)
+ __key__ = Key(src_node, dst_node)
+
def __init__(self, *args, **kwargs):
assert 'src_node' in kwargs and 'dst_node' in kwargs
self._src = None
self._dst = None
super().__init__(*args, **kwargs)
+ @inherit_parent
@inline_task
def __initialize__(self):
# We create two managed net devices that are pre-setup
@@ -124,23 +127,27 @@ class Link(Channel):
vpp_src = VPPInterface(parent = self._src,
vpp = self.src_node.vpp,
ip4_address = Reference(self._src, 'ip4_address'),
- device_name = 'vpp' + self._src.device_name)
+ ip6_address = Reference(self._src, 'ip6_address'),
+ device_name = 'host-' + self._src.device_name)
manager.commit_resource(vpp_src)
if hasattr(self.dst_node, 'vpp') and not self.dst_node.vpp is None:
vpp_dst = VPPInterface(parent = self._dst,
vpp = self.dst_node.vpp,
ip4_address = Reference(self._dst, 'ip4_address'),
- device_name = 'vpp' + self._dst.device_name)
+ ip6_address = Reference(self._dst, 'ip6_address'),
+ device_name = 'host-' + self._dst.device_name)
manager.commit_resource(vpp_dst)
#--------------------------------------------------------------------------
# Resource lifecycle
#--------------------------------------------------------------------------
+ @inherit_parent
def __get__(self):
return (self._src.__get__() | self._dst.__get__()) > async_task(self._commit)()
+ @inherit_parent
def __create__(self):
assert self.src_node.get_type() == 'lxccontainer'
assert self.dst_node.get_type() == 'lxccontainer'
@@ -168,8 +175,8 @@ class Link(Channel):
host = src_host
create = BashTask(host, CMD_CREATE, {'interface': self,
'tmp_src': tmp_src, 'tmp_dst': tmp_dst})
- up_src = BashTask(self.src_node, CMD_UP, {'interface': self._src})
- up_dst = BashTask(self.dst_node, CMD_UP, {'interface': self._dst})
+ up_src = BashTask(self.src_node, CMD_SET_UP, {'interface': self._src})
+ up_dst = BashTask(self.dst_node, CMD_SET_UP, {'interface': self._dst})
up = up_src | up_dst
delif = delif_src | delif_dst
return ((delif > (pid @ create)) > up) > async_task(self._commit)()
@@ -178,10 +185,11 @@ class Link(Channel):
'tmp_src': tmp_src, 'tmp_dst': tmp_dst, 'host' : src_host})
create2 = BashTask(dst_host, CMD_CREATE_BR_TO_LXC, {'interface': self._dst,
'tmp_src': tmp_dst, 'tmp_dst': tmp_src, 'host' : dst_host})
- up_src = BashTask(self.src_node, CMD_UP, {'interface': self._src})
- up_dst = BashTask(self.dst_node, CMD_UP, {'interface': self._dst})
+ up_src = BashTask(self.src_node, CMD_SET_UP, {'interface': self._src})
+ up_dst = BashTask(self.dst_node, CMD_SET_UP, {'interface': self._dst})
return (((pid_src @ create) | (pid_dst @ create2)) > (up_src | up_dst)) > async_task(self._commit)()
+ @inherit_parent
def __delete__(self):
return self._src.__delete__() | self._dst.__delete__()
diff --git a/vicn/resource/linux/macvlan.py b/vicn/resource/linux/macvlan.py
index ea9c37c1..3c81cde1 100644
--- a/vicn/resource/linux/macvlan.py
+++ b/vicn/resource/linux/macvlan.py
@@ -18,14 +18,15 @@
from netmodel.model.type import String
from vicn.core.attribute import Attribute
-from vicn.core.task import BashTask
-from vicn.resource.linux.net_device import SlaveBaseNetDevice
+from vicn.core.task import BashTask, inherit
+from vicn.resource.interface import Interface
+from vicn.resource.linux.net_device import SlaveNetDevice
CMD_CREATE_PARENT = 'ip link add name {netdevice.device_name} ' \
'link {netdevice.parent.device_name} ' \
'type {netdevice.netdevice_type} mode {netdevice.mode}'
-class MacVlan(SlaveBaseNetDevice):
+class MacVlan(SlaveNetDevice):
"""
Resource: MacVlan
@@ -48,5 +49,6 @@ class MacVlan(SlaveBaseNetDevice):
# Resource lifecycle
#--------------------------------------------------------------------------
+ @inherit(Interface)
def __create__(self):
return BashTask(self.node, CMD_CREATE_PARENT, {'netdevice': self})
diff --git a/vicn/resource/linux/macvtap.py b/vicn/resource/linux/macvtap.py
index 82002e02..23176763 100644
--- a/vicn/resource/linux/macvtap.py
+++ b/vicn/resource/linux/macvtap.py
@@ -18,14 +18,15 @@
from netmodel.model.type import String
from vicn.core.attribute import Attribute
-from vicn.core.task import BashTask
-from vicn.resource.linux.net_device import SlaveBaseNetDevice
+from vicn.core.task import BashTask, inherit
+from vicn.resource.interface import Interface
+from vicn.resource.linux.net_device import SlaveNetDevice
CMD_CREATE_PARENT = 'ip link add name {netdevice.device_name} ' \
'link {netdevice.parent.device_name} ' \
'type {netdevice.netdevice_type} mode {netdevice.mode}'
-class MacVtap(SlaveBaseNetDevice):
+class MacVtap(SlaveNetDevice):
"""
Resource: MacVtap
@@ -48,5 +49,6 @@ class MacVtap(SlaveBaseNetDevice):
# Resource lifecycle
#--------------------------------------------------------------------------
+ @inherit(Interface)
def __create__(self):
return BashTask(self.node, CMD_CREATE_PARENT, {'netdevice': self})
diff --git a/vicn/resource/linux/net_device.py b/vicn/resource/linux/net_device.py
index c393ac1a..f9ab40b1 100644
--- a/vicn/resource/linux/net_device.py
+++ b/vicn/resource/linux/net_device.py
@@ -23,11 +23,13 @@ import random
import string
from netmodel.model.type import Integer, String, Bool
+from netmodel.model.type import Inet4Address, Inet6Address
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.core.task import BashTask, task, EmptyTask, inherit
+from vicn.core.task import inherit_parent, override_parent
from vicn.resource.linux.application import LinuxApplication as Application
from vicn.resource.interface import Interface
@@ -59,11 +61,11 @@ CMD_SET_MAC_ADDRESS = 'ip link set dev {netdevice.device_name} ' \
'address {netdevice.mac_address}'
CMD_GET_IP_ADDRESS = 'ip addr show {netdevice.device_name}'
CMD_SET_IP4_ADDRESS = 'ip addr add dev {netdevice.device_name} ' \
- '{netdevice.ip4_address} brd + || true'
+ '{netdevice.ip4_address}/{netdevice.ip4_address.prefix_len} brd + || true'
CMD_SET_IP6_ADDRESS = 'ip addr add dev {netdevice.device_name} ' \
- '{netdevice.ip6_address}/{netdevice.ip6_prefix} || true'
+ '{netdevice.ip6_address}/{netdevice.ip6_address.prefix_len} || true'
CMD_SET_PROMISC = 'ip link set dev {netdevice.device_name} promisc {on_off}'
-CMD_SET_UP = 'ip link set {netdevice.device_name} {up_down}'
+CMD_SET_UP = 'ip link set {netdevice.device_name} {state}'
CMD_SET_CAPACITY='\n'.join([
'tc qdisc del dev {netdevice.device_name} root || true',
'tc qdisc add dev {netdevice.device_name} root handle 1: tbf rate '
@@ -92,6 +94,10 @@ CMD_UNSET_IP6_FWD = 'sysctl -w net.ipv6.conf.{netdevice.device_name}.forwarding=
CMD_SET_IP6_FWD = 'sysctl -w net.ipv6.conf.{netdevice.device_name}.forwarding=1'
CMD_GET_IP6_FWD = 'sysctl -n net.ipv6.conf.{netdevice.device_name}.forwarding'
+DEFAULT_IP4_PREFIX_LEN = 31
+DEFAULT_IP6_PREFIX_LEN = 64
+
+NetDeviceName = String.restrict(max_size = MAX_DEVICE_NAME_SIZE)
#-------------------------------------------------------------------------------
@@ -265,22 +271,20 @@ def parse_ip_addr(data):
#------------------------------------------------------------------------------
-class BaseNetDevice(Interface, Application):
+class NetDevice(Interface, Application):
__type__ = BaseResource
# XXX note: ethtool only required if we need to get the pci address
__package_names__ = ['ethtool']
- device_name = Attribute(String, description = 'Name of the NetDevice',
- default = lambda x : x._default_device_name(),
- max_size = MAX_DEVICE_NAME_SIZE)
+ device_name = Attribute(NetDeviceName, description = 'Name of the NetDevice',
+ default = lambda x : x._default_device_name())
capacity = Attribute(Integer,
- description = 'Capacity for interface shaping (Mb/s)')
+ description = 'Capacity for interface shaping (Mb/s)',
+ default = None)
mac_address = Attribute(String, description = 'Mac address of the device')
- ip4_address = Attribute(String, description = 'IP address of the device')
- ip4_prefix = Attribute(Integer, description = 'Prefix for the IPv4link', default=31) #XXX 31?
- ip6_address = Attribute(String, description = 'IPv6 address of the device')
- ip6_prefix = Attribute(Integer, description = 'Prefix for the IPv6 link', default=64)
+ ip4_address = Attribute(Inet4Address, description = 'IP address of the device')
+ ip6_address = Attribute(Inet6Address, description = 'IPv6 address of the device')
ip6_forwarding = Attribute(Bool, description = 'IPv6 forwarding', default = True)
pci_address = Attribute(String,
description = 'PCI bus address of the device',
@@ -308,6 +312,7 @@ class BaseNetDevice(Interface, Application):
# Resource lifecycle
#--------------------------------------------------------------------------
+ @inherit_parent
def __get__(self):
def check(rv):
if not bool(rv):
@@ -315,8 +320,11 @@ class BaseNetDevice(Interface, Application):
return BashTask(self.node, CMD_GET, {'netdevice' : self}, output=True,
parse=check)
- __create__ = None
+ @inherit_parent
+ def __create__(self):
+ return BashTask(self.node, CMD_CREATE, {'netdevice': self})
+ @inherit_parent
def __delete__(self):
return BashTask(self.node, CMD_DELETE, {'netdevice': self})
@@ -369,7 +377,7 @@ class BaseNetDevice(Interface, Application):
if len(ips) > 1:
log.warning('Keeping only first of many IP addresses...')
ip = ips[0]
- attrs['ip4_address'] = ip['ip-address']
+ attrs['ip4_address'] = Inet4Address(ip['ip-address'], DEFAULT_IP4_PREFIX_LEN)
else:
attrs['ip4_address'] = None
return attrs
@@ -413,8 +421,7 @@ class BaseNetDevice(Interface, Application):
if len(ips) > 1:
log.warning('Keeping only first of many IPv6 addresses...')
ip = ips[0]
- attrs['ip6_address'] = ip['ip-address']
- attrs['ip6_prefix'] = ip['prefix']
+ attrs['ip6_address'] = Inet6Address(ip_address = ip['ip-address'], prefix_len = ip['prefix'])
else:
attrs['ip6_address'] = None
return attrs
@@ -457,9 +464,9 @@ class BaseNetDevice(Interface, Application):
return {'up': False}
def _set_up(self):
- up_down = 'up' if self.up else 'down'
+ state = 'up' if self.up else 'down'
return BashTask(self.node, CMD_SET_UP,
- {'netdevice': self, 'up_down': up_down})
+ {'netdevice': self, 'state': state})
@task
def _get_capacity(self):
@@ -526,7 +533,7 @@ class BaseNetDevice(Interface, Application):
#------------------------------------------------------------------------------
-class NonTapBaseNetDevice(BaseNetDevice):
+class NonTapBaseNetDevice(NetDevice):
# Tap devices for instance don't have offload
offload = Attribute(Bool, description = 'Offload', default=True)
@@ -534,59 +541,34 @@ class NonTapBaseNetDevice(BaseNetDevice):
# Attributes
#--------------------------------------------------------------------------
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
-
def _get_offload(self):
return BashTask(self.node, CMD_GET_OFFLOAD, {'netdevice': self},
parse = lambda rv : rv.stdout.strip() == 'on')
def _set_offload(self):
- cmd = None
- if self.offload:
- cmd = CMD_SET_OFFLOAD
- else:
- cmd = CMD_UNSET_OFFLOAD
+ cmd = CMD_SET_OFFLOAD if self.offload else CMD_UNSET_OFFLOAD
return BashTask(self.node, cmd, {'netdevice' : self})
#------------------------------------------------------------------------------
-class NetDevice(NonTapBaseNetDevice):
-
- #--------------------------------------------------------------------------
- # Resource lifecycle
- #--------------------------------------------------------------------------
-
- def __create__(self):
- return BashTask(self.node, CMD_CREATE, {'netdevice': self})
-
-#------------------------------------------------------------------------------
-
-class SlaveBaseNetDevice(BaseNetDevice):
+class SlaveNetDevice(NetDevice):
parent = Attribute(NetDevice, description = 'Parent NetDevice')
- host = Attribute(NetDevice, description = 'Host interface',
- default = lambda x : x._default_host())
-
- def _default_host(self):
- if self.node.__class__.__name__ == 'LxcContainer':
- host = self.node.node
- else:
- host = self.node
- max_len = MAX_DEVICE_NAME_SIZE - len(self.node.name) - 1
- device_name = self.device_name[:max_len]
-
- return NetDevice(node = host,
- device_name = '{}-{}'.format(self.node.name, device_name),
- managed = False)
-
-#------------------------------------------------------------------------------
-
-class SlaveNetDevice(SlaveBaseNetDevice):
-
- #--------------------------------------------------------------------------
- # Resource lifecycle
- #--------------------------------------------------------------------------
+# host = Attribute(NetDevice, description = 'Host interface',
+# default = lambda x : x._default_host())
+#
+# def _default_host(self):
+# if self.node.__class__.__name__ == 'LxcContainer':
+# host = self.node.node
+# else:
+# host = self.node
+# max_len = MAX_DEVICE_NAME_SIZE - len(self.node.name) - 1
+# device_name = self.device_name[:max_len]
+#
+# return NetDevice(node = host,
+# device_name = '{}-{}'.format(self.node.name, device_name),
+# managed = False)
+ @override_parent
def __create__(self):
return BashTask(self.node, CMD_CREATE_PARENT, {'netdevice': self})
diff --git a/vicn/resource/linux/netmon.py b/vicn/resource/linux/netmon.py
index 8472f308..9c5c97fd 100644
--- a/vicn/resource/linux/netmon.py
+++ b/vicn/resource/linux/netmon.py
@@ -25,5 +25,5 @@ class NetMon(Service):
Generic network monitoring daemon, used internally by VICN for resource
monitoring.
"""
- __package_names__ = ['netmon']
+ __package_names__ = [] # XXX transition 'netmon']
__service_name__ = 'netmon'
diff --git a/vicn/resource/linux/ovs.py b/vicn/resource/linux/ovs.py
index d67e4bca..c0b57151 100644
--- a/vicn/resource/linux/ovs.py
+++ b/vicn/resource/linux/ovs.py
@@ -46,12 +46,12 @@ class OVS(BridgeManager):
#---------------------------------------------------------------------------
def add_bridge(self, bridge_name):
- return BashTask(self.node, CMD_ADD_BRIDGE,
+ return BashTask(self.node, CMD_ADD_BRIDGE,
{'bridge_name': bridge_name},
output = False, as_root = True)
def del_bridge(self, bridge_name):
- return BashTask(self.node, CMD_DEL_BRIDGE,
+ return BashTask(self.node, CMD_DEL_BRIDGE,
{'bridge_name': bridge_name},
output = False, as_root = True)
@@ -62,7 +62,7 @@ class OVS(BridgeManager):
output = False, as_root = True)
def del_interface(self, bridge_name, interface_name, vlan=None):
- return BashTask(self.node, CMD_DEL_INTERFACE,
+ return BashTask(self.node, CMD_DEL_INTERFACE,
{'bridge_name': bridge_name, 'interface_name': interface_name,
'vlan': vlan},
output = False, as_root = True)
diff --git a/vicn/resource/linux/package_manager.py b/vicn/resource/linux/package_manager.py
index 93241502..04a47986 100644
--- a/vicn/resource/linux/package_manager.py
+++ b/vicn/resource/linux/package_manager.py
@@ -19,13 +19,14 @@
import asyncio
import logging
+from netmodel.model.key import Key
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
from vicn.core.resource import Resource
from vicn.core.task import BashTask, EmptyTask, async_task
-from vicn.core.task import inline_task, run_task
+from vicn.core.task import inline_task, run_task, inherit_parent
from vicn.resource.node import Node
log = logging.getLogger(__name__)
@@ -78,12 +79,13 @@ class PackageManager(Resource):
reverse_name = 'package_manager',
reverse_auto = True,
mandatory = True,
- key = True,
multiplicity = Multiplicity.OneToOne)
trusted = Attribute(Bool,
description="Force repository trust",
default=False)
+ __key__ = Key(node)
+
#--------------------------------------------------------------------------
# Constructor and Accessors
#--------------------------------------------------------------------------
@@ -100,6 +102,7 @@ class PackageManager(Resource):
def __after__(self):
return ('Repository',)
+ @inherit_parent
@inline_task
def __get__(self):
raise ResourceNotFound
@@ -182,21 +185,25 @@ class Package(Resource):
package_name = Attribute(String, mandatory = True)
node = Attribute(Node,
mandatory = True,
- key = True,
requirements=[
Requirement('package_manager')
])
+ __key__ = Key(node)
+
#---------------------------------------------------------------------------
# Resource lifecycle
#---------------------------------------------------------------------------
+ @inherit_parent
def __get__(self):
return BashTask(self.node, CMD_PKG_TEST, {'self': self})
+ @inherit_parent
def __create__(self):
return self.node.package_manager.__method_install__(self.package_name)
+ @inherit_parent
@async_task
async def __delete__(self):
with await self.node.package_manager._lock:
@@ -218,15 +225,17 @@ class Packages(Resource):
names = Attribute(String, multiplicity = Multiplicity.OneToMany)
node = Attribute(Node,
mandatory = True,
- key = True,
requirements=[
Requirement('package_manager')
])
+ __key__ = Key(node)
+
#---------------------------------------------------------------------------
# Resource lifecycle
#---------------------------------------------------------------------------
+ @inherit_parent
def __subresources__(self):
"""
Note: Although packages are (rightfully) specified concurrent, apt tasks
diff --git a/vicn/resource/linux/phy_interface.py b/vicn/resource/linux/phy_interface.py
index 81d2950c..8d7f02c8 100644
--- a/vicn/resource/linux/phy_interface.py
+++ b/vicn/resource/linux/phy_interface.py
@@ -16,7 +16,8 @@
# limitations under the License.
#
-from netmodel.model.type import String
+from netmodel.model.type import String, Integer
+from netmodel.model.type import Inet4Address, Inet6Address
from vicn.core.attribute import Attribute
from vicn.core.resource import BaseResource
from vicn.resource.interface import Interface
@@ -34,17 +35,6 @@ class PhyInterface(Interface):
mandatory = True)
pci_address = Attribute(String, description = "Device's PCI bus address",
mandatory = True)
- mac_address = Attribute(String, description = "Device's MAC address",
- mandatory=True)
- ip4_address = Attribute(String, description = "Device's IP address")
- ip6_address = Attribute(String, description = "Device's IP address")
-
- #--------------------------------------------------------------------------
- # Constructor and Accessors
- #--------------------------------------------------------------------------
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
-
- if not self.name:
- self.name = self.node.name + '-' + self.device_name
+ mac_address = Attribute(String, description = "Device's MAC address")
+ ip4_address = Attribute(Inet4Address, description = "Device's IP address")
+ ip6_address = Attribute(Inet6Address, description = "Device's IP address")
diff --git a/vicn/resource/linux/phy_link.py b/vicn/resource/linux/phy_link.py
index 878cf7c6..2976b7f0 100644
--- a/vicn/resource/linux/phy_link.py
+++ b/vicn/resource/linux/phy_link.py
@@ -16,10 +16,12 @@
# limitations under the License.
#
-from vicn.core.attribute import Attribute
-from vicn.core.task import inline_task
+from vicn.core.attribute import Attribute, Reference
+from vicn.core.task import inline_task, async_task
+from vicn.core.task import inherit_parent
from vicn.resource.channel import Channel
from vicn.resource.linux.phy_interface import PhyInterface
+from vicn.resource.vpp.interface import VPPInterface
class PhyLink(Channel):
"""
@@ -28,12 +30,53 @@ class PhyLink(Channel):
Physical Link to inform the orchestrator about Layer2 connectivity.
"""
- src = Attribute(PhyInterface, description = 'Source interface',
+ src = Attribute(PhyInterface, description = 'Source interface',
mandatory = True)
- dst = Attribute(PhyInterface, description = 'Destination interface',
+ dst = Attribute(PhyInterface, description = 'Destination interface',
mandatory = True)
+ @inherit_parent
@inline_task
def __initialize__(self):
self.src.set('channel', self)
self.dst.set('channel', self)
+
+ #--------------------------------------------------------------------------
+ # Internal methods
+ #--------------------------------------------------------------------------
+
+ async def _commit(self):
+ manager = self._state.manager
+
+ # Disable rp_filtering
+ # self.src.rp_filter = False
+ # self.dst.rp_filter = False
+
+ #XXX VPP
+ vpp_src = VPPInterface(parent = self.src,
+ vpp = self.src.node.vpp,
+ ip4_address = Reference(self.src, 'ip4_address'),
+ ip6_address = Reference(self.src, 'ip6_address'),
+ device_name = self.src.device_name)
+ manager.commit_resource(vpp_src)
+
+
+ vpp_dst = VPPInterface(parent = self.dst,
+ vpp = self.dst.node.vpp,
+ ip4_address = Reference(self.dst, 'ip4_address'),
+ ip6_address = Reference(self.dst, 'ip6_address'),
+ device_name = self.dst.device_name)
+ manager.commit_resource(vpp_dst)
+
+ #--------------------------------------------------------------------------
+ # Resource lifecycle
+ #--------------------------------------------------------------------------
+
+ def __get__(self):
+ return async_task(self._commit)()
+
+ def __create__(self):
+ assert self.src.node.get_type() == 'lxccontainer'
+ assert self.dst.node.get_type() == 'lxccontainer'
+
+ return async_task(self._commit)()
diff --git a/vicn/resource/linux/physical.py b/vicn/resource/linux/physical.py
index f71b5856..c058ff16 100644
--- a/vicn/resource/linux/physical.py
+++ b/vicn/resource/linux/physical.py
@@ -29,6 +29,7 @@ from vicn.core.attribute import Attribute
from vicn.core.commands import Command, ReturnValue
from vicn.core.exception import ResourceNotFound, VICNException
from vicn.core.task import Task, task, EmptyTask
+from vicn.core.task import inherit_parent
from vicn.resource.linux.keypair import Keypair
from vicn.resource.node import Node, DEFAULT_USERNAME
from vicn.resource.node import DEFAULT_SSH_PUBLIC_KEY
@@ -71,14 +72,16 @@ class Physical(Node):
# Resource lifecycle
#--------------------------------------------------------------------------
+ @inherit_parent
def __subresources__(self):
"""
Require a SSH keypair to be present for authentication on nodes
"""
return Keypair(node = self, key = FN_KEY)
+ @inherit_parent
def __initialize__(self):
- if not is_local_host(self.hostname):
+ if self.managed and not is_local_host(self.hostname):
"""
Initialization require the ssh port to be open on the node, and the ssh
public key to be copied on the remote node.
@@ -124,6 +127,7 @@ class Physical(Node):
p = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE)
if output:
out, err = p.communicate()
+ #print('error {}, output {}'.format(err,out))
return ReturnValue(p.returncode, stdout=out, stderr=err)
p.wait()
diff --git a/vicn/resource/linux/qtplayer.py b/vicn/resource/linux/qtplayer.py
new file mode 100644
index 00000000..44b10ccf
--- /dev/null
+++ b/vicn/resource/linux/qtplayer.py
@@ -0,0 +1,5 @@
+from vicn.resource.linux.application import LinuxApplication
+
+class QtPlayer(LinuxApplication):
+ pass
+
diff --git a/vicn/resource/linux/repository.py b/vicn/resource/linux/repository.py
index cd740d38..f07421ba 100644
--- a/vicn/resource/linux/repository.py
+++ b/vicn/resource/linux/repository.py
@@ -31,14 +31,14 @@ class Repository(Application):
part of any basic distribution install.
"""
- repo_name = Attribute(String, description = 'Name of the repository',
+ repo_name = Attribute(String, description = 'Name of the repository',
default = 'vicn')
- directory = Attribute(String, description = 'Directory holding packages',
+ directory = Attribute(String, description = 'Directory holding packages',
default = '')
sections = Attribute(String, description = 'Sections',
multiplicity = Multiplicity.OneToMany,
default = [])
- distributions = Attribute(String,
+ distributions = Attribute(String,
description = 'List of distributions served by this repository',
multiplicity = Multiplicity.ManyToMany,
default = ['sid', 'trusty', 'xenial'])
diff --git a/vicn/resource/linux/service.py b/vicn/resource/linux/service.py
index 3eb753fc..1143461a 100644
--- a/vicn/resource/linux/service.py
+++ b/vicn/resource/linux/service.py
@@ -21,6 +21,7 @@ import logging
from vicn.core.exception import ResourceNotFound
from vicn.core.resource import CategoryResource
from vicn.core.task import inline_task, BashTask, EmptyTask
+from vicn.core.task import inherit_parent
from vicn.resource.linux.application import LinuxApplication
log = logging.getLogger(__name__)
@@ -51,33 +52,33 @@ class Service(LinuxApplication):
__type__ = CategoryResource
-
+ @inherit_parent
@inline_task
def __get__(self):
raise ResourceNotFound
-
+
def __method_restart__(self):
- return BashTask(self.node, CMD_RESTART,
+ return BashTask(self.node, CMD_RESTART,
{'service_name': self.__service_name__})
-
+
def __method_start__(self):
return BashTask(self.node, CMD_START,
{'service_name': self.__service_name__})
-
- def __create__(self):
- if self.__service_name__ == 'lxd':
- log.warning('Not restarting LXD')
- return EmptyTask()
+ def __method_stop__(self):
+ return BashTask(self.node, CMD_STOP,
+ {'service_name': self.__service_name__})
- if self.__service_name__ == 'dnsmasq':
- return BashTask(self.node, CMD_STOP_START,
- {'service_name': self.__service_name__})
+ def __method_stop_start(self):
+ return BashTask(self.node, CMD_STOP_START,
+ {'service_name': self.__service_name__})
+ @inherit_parent
+ def __create__(self):
return self.__method_restart__()
-
+ @inherit_parent
def __delete__(self):
- return BashTask(self.node, CMD_STOP,
+ return BashTask(self.node, CMD_STOP,
{'service_name': self.__service_name__})
diff --git a/vicn/resource/linux/sym_veth_pair.py b/vicn/resource/linux/sym_veth_pair.py
index bf79a69b..ebfe2968 100644
--- a/vicn/resource/linux/sym_veth_pair.py
+++ b/vicn/resource/linux/sym_veth_pair.py
@@ -26,12 +26,12 @@ from vicn.core.resource import Resource
from vicn.core.state import ResourceState, AttributeState
from vicn.core.task import BashTask, get_attributes_task
from vicn.core.task import async_task, task, inline_task
-from vicn.core.task import run_task
+from vicn.core.task import run_task, inherit_parent
from vicn.resource.interface import Interface
from vicn.resource.node import Node
from vicn.resource.linux.net_device import NonTapBaseNetDevice
from vicn.resource.linux.link import CMD_DELETE_IF_EXISTS
-from vicn.resource.linux.link import CMD_UP
+from vicn.resource.linux.link import CMD_SET_UP
CMD_CREATE='''
# Create veth pair in the host node
@@ -48,13 +48,13 @@ class SymVethPair(Resource):
resource is that is it not a channel.
"""
- node1 = Attribute(Node,
+ node1 = Attribute(Node,
description = 'Node on which one side of the veth will sit',
mandatory = True)
- node2 = Attribute(Node,
+ node2 = Attribute(Node,
description = 'Node on which the other side of the veth will sit',
mandatory = True)
- capacity = Attribute(Integer,
+ capacity = Attribute(Integer,
description = 'Capacity of the veth pair (Mb/s)')
side1 = Attribute(Interface, description = 'Source interface')
side2 = Attribute(Interface, description = 'Destination interface')
@@ -66,21 +66,22 @@ class SymVethPair(Resource):
async def _commit(self):
# see link.py for explanations
manager = self._state.manager
- await manager._set_resource_state(self.side1,
+ await manager._set_resource_state(self.side1,
ResourceState.INITIALIZED)
- await manager._set_resource_state(self.side2,
+ await manager._set_resource_state(self.side2,
ResourceState.INITIALIZED)
await manager._set_resource_state(self.side1, ResourceState.CREATED)
await manager._set_resource_state(self.side2, ResourceState.CREATED)
await manager._set_attribute_state(self, 'side1', AttributeState.CLEAN)
await manager._set_attribute_state(self, 'side2', AttributeState.CLEAN)
manager.commit_resource(self.side1)
- manager.commit_resource(self.side2)
+ manager.commit_resource(self.side2)
#--------------------------------------------------------------------------
# Resource lifecycle
#--------------------------------------------------------------------------
+ @inherit_parent
@inline_task
def __initialize__(self):
self.side1 = NonTapBaseNetDevice(node = self.node1,
@@ -94,6 +95,7 @@ class SymVethPair(Resource):
self.side1.remote = self.side2
self.side2.remote = self.side1
+ @inherit_parent
@async_task
async def __get__(self):
manager = self._state.manager
@@ -106,46 +108,48 @@ class SymVethPair(Resource):
await self._commit()
+ @inherit_parent
def __create__(self):
assert self.node1.get_type() == 'lxccontainer'
assert self.node2.get_type() == 'lxccontainer'
-
+
node1_host = self.node1.node
node2_host = self.node2.node
-
+
assert node1_host == node2_host
host = node1_host
-
+
# Sometimes a down interface persists on one side
- delif_side1 = BashTask(self.node1, CMD_DELETE_IF_EXISTS,
+ delif_side1 = BashTask(self.node1, CMD_DELETE_IF_EXISTS,
{'interface': self.side1})
- delif_side2 = BashTask(self.node2, CMD_DELETE_IF_EXISTS,
+ delif_side2 = BashTask(self.node2, CMD_DELETE_IF_EXISTS,
{'interface': self.side2})
-
+
pid_node1 = get_attributes_task(self.node1, ['pid'])
pid_node2 = get_attributes_task(self.node2, ['pid'])
-
+
tmp_side1 = 'tmp-veth-' + ''.join(random.choice(
string.ascii_uppercase + string.digits) for _ in range(5))
tmp_side2 = 'tmp-veth-' + ''.join(random.choice(
string.ascii_uppercase + string.digits) for _ in range(5))
-
- create = BashTask(host, CMD_CREATE,
+
+ create = BashTask(host, CMD_CREATE,
{'side1_device_name': self.side1.device_name,
- 'side2_device_name': self.side2.device_name,
+ 'side2_device_name': self.side2.device_name,
'tmp_side1': tmp_side1, 'tmp_side2': tmp_side2})
-
- up_side1 = BashTask(self.node1, CMD_UP, {'interface': self.side1})
- up_side2 = BashTask(self.node2, CMD_UP, {'interface': self.side2})
-
+
+ up_side1 = BashTask(self.node1, CMD_SET_UP, {'interface': self.side1})
+ up_side2 = BashTask(self.node2, CMD_SET_UP, {'interface': self.side2})
+
@async_task
async def set_state():
await self._commit()
-
+
delif = delif_side1 | delif_side2
up = up_side1 | up_side2
pid = pid_node1 | pid_node2
return ((delif > (pid @ create)) > up) > set_state()
-
+
+ @inherit_parent
def __delete__(self):
raise NotImplementedError
diff --git a/vicn/resource/linux/tap_device.py b/vicn/resource/linux/tap_device.py
index b7c9f967..88ca055d 100644
--- a/vicn/resource/linux/tap_device.py
+++ b/vicn/resource/linux/tap_device.py
@@ -18,39 +18,22 @@
from netmodel.model.type import String
from vicn.core.attribute import Attribute
-from vicn.core.task import BashTask
-from vicn.resource.linux.net_device import BaseNetDevice, IPV4, IPV6, CMD_FLUSH_IP
+from vicn.core.task import BashTask, override_parent
+from vicn.resource.linux.net_device import NetDevice, IPV4, IPV6, CMD_FLUSH_IP
CMD_CREATE='ip tuntap add name {netdevice.device_name} mode tap'
-#CMD_SET_IP_ADDRESS='ip -{version} addr add dev {netdevice.device_name} 0.0.0.0'
-class TapDevice(BaseNetDevice):
+class TapDevice(NetDevice):
def __init__(self, *args, **kwargs):
super().__init__(self, *args, **kwargs)
self.prefix = 'tap'
self.netdevice_type = 'tap'
+ @override_parent
def __create__(self):
return BashTask(self.node, CMD_CREATE, {'netdevice': self})
-##mengueha: do we actually need that?
-# def _set_ip4_address(self):
-# if self.ip4_address is None:
-# # Unset IP
-# return BashTask(self.node, CMD_FLUSH_IP,
-# {'device_name': self.device_name})
-# return BashTask(self.node, CMD_SET_IP_ADDRESS,
-# {'netdevice': self})
-#
-# def _set_ip6_address(self):
-# if self.ip6_address is None:
-# # Unset IP
-# return BashTask(self.node, CMD_FLUSH_IP,
-# {'ip_version': IPV6, 'device_name': self.device_name})
-# return BashTask(self.node, CMD_SET_IP_ADDRESS,
-# {'netdevice': self})
-
class TapChannel(TapDevice):
station_name = Attribute(String)
channel_name = Attribute(String)
diff --git a/vicn/resource/linux/veth_pair.py b/vicn/resource/linux/veth_pair.py
index 53fa9bf8..52050074 100644
--- a/vicn/resource/linux/veth_pair.py
+++ b/vicn/resource/linux/veth_pair.py
@@ -15,48 +15,82 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-import random
-import string
-from vicn.resource.linux.net_device import SlaveBaseNetDevice
-from vicn.core.task import BashTask, get_attributes_task
-
-# ip link add veth0 type veth peer name veth1
+from netmodel.model.key import Key
+from netmodel.model.type import String, Bool
+from vicn.core.attribute import Attribute, Reference
+from vicn.core.task import BashTask, inline_task, get_attributes_task
+from vicn.resource.linux.net_device import NetDevice, NetDeviceName
+from vicn.resource.node import Node
+from vicn.resource.symmetric_channel import SymmetricChannel
CMD_CREATE='''
# Create veth pair in the host node
-ip link add name {interface.host.device_name} type veth peer name {tmp_name}
+ip link add name {self.device_name} type veth peer name {self.peer_device_name}
+'''
+
+DEPRECATED_CMD_UP='''
# The host interface will always be up...
ip link set dev {interface.host.device_name} up
+
# Move interface into container and rename it
ip link set dev {tmp_name} netns {pid} name {interface.device_name}
-'''
-CMD_UP='''
ip link set dev {interface.device_name} up
'''
-# see:
-# http://stackoverflow.com/questions/22780927/lxc-linux-containers-add-new-network-interface-without-restarting
+# Forward declaration
+class VethPair(SymmetricChannel):
+ pass
+
+class VethNetDevice(NetDevice):
+ parent = Attribute(VethPair, mandatory = True, ro = True)
-class VethPair(SlaveBaseNetDevice):
- # Do not need the parent attribute...
+ __get__ = None
+ __create__ = None
+ __delete__ = None
- def __init__(self, *args, **kwargs):
- super().__init__(self, *args, **kwargs)
- self.prefix = 'veth'
- self.netdevice_type = 'veth'
+class VethPair(SymmetricChannel):
+ # Mimics NetDevice for using its __get__ and __delete__ functions
+ node = Attribute(Node)
+ device_name = Attribute(NetDeviceName)
+ peer_device_name = Attribute(NetDeviceName)
+ capacity = Attribute(String)
+ src = Attribute(ro = True, mandatory = False)
+ dst = Attribute(ro = True, mandatory = False)
+ auto_commit = Attribute(Bool, description = 'Auto commit interfaces')
+
+ __key1__ = Key(node, device_name)
+ __key2__ = Key(node, peer_device_name)
+
+ @inline_task
+ def _commit(self):
+ if self.auto_commit:
+ manager = self._state.manager
+
+ manager.commit_resource(self.src)
+ manager.commit_resource(self.dst)
+
+ def __initialize__(self):
+ # XXX owner prevents the resource to be committed
+ self.src = VethNetDevice(node = self.node,
+ parent = self,
+ device_name = self.device_name,
+ channel = self,
+ capacity = Reference(self, 'capacity'),
+ owner = self)
+ self.dst = VethNetDevice(node = self.node,
+ parent = self,
+ device_name = self.peer_device_name,
+ channel = self,
+ capacity = Reference(self, 'capacity'),
+ owner = self)
def __create__(self):
- assert self.node.__class__.__name__ == 'LxcContainer'
- host = self.node.node
- pid = get_attributes_task(self.node, ['pid'])
- tmp_name = 'tmp-veth-' + ''.join(random.choice(string.ascii_uppercase \
- + string.digits) for _ in range(5))
- create = BashTask(host, CMD_CREATE, {'tmp_name': tmp_name,
- 'interface': self})
- up = BashTask(self.node, CMD_UP, {'interface': self})
- bridge = host.bridge_manager.add_interface(host.bridge.device_name,
- self.host.device_name)
- return ((pid @ create) > up) > bridge
-
- # ... IP and UP missing...
+ veth = BashTask(self.node, CMD_CREATE, {'self': self})
+ return (veth > super().__create__()) > self._commit()
+
+ def __get__(self):
+ return NetDevice.__get__(self) > self._commit()
+
+ def __delete__(self):
+ return NetDevice.__delete__(self)
diff --git a/vicn/resource/linux/veth_pair_lxc.py b/vicn/resource/linux/veth_pair_lxc.py
new file mode 100644
index 00000000..dd26b7bb
--- /dev/null
+++ b/vicn/resource/linux/veth_pair_lxc.py
@@ -0,0 +1,74 @@
+#!/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.
+#
+import random
+import string
+
+from vicn.core.attribute import Attribute
+from vicn.resource.linux.net_device import NetDevice, SlaveNetDevice
+from vicn.core.task import BashTask, get_attributes_task
+from vicn.core.task import override_parent
+from vicn.core.attribute import Attribute
+
+# ip link add veth0 type veth peer name veth1
+
+CMD_CREATE='''
+# Create veth pair in the host node
+ip link add name {interface.host.device_name} type veth peer name {tmp_name}
+# The host interface will always be up...
+ip link set dev {interface.host.device_name} up
+# Move interface into container and rename it
+ip link set dev {tmp_name} netns {pid} name {interface.device_name}
+'''
+CMD_UP='''
+ip link set dev {interface.device_name} up
+'''
+
+# see:
+# http://stackoverflow.com/questions/22780927/lxc-linux-containers-add-new-network-interface-without-restarting
+
+class VethPairLxc(SlaveNetDevice):
+
+ host = Attribute(NetDevice, description = 'Host interface',
+ default = lambda x : x._default_host())
+
+ def _default_host(self):
+ if self.node.__class__.__name__ == 'LxcContainer':
+ host = self.node.node
+ else:
+ host = self.node
+ max_len = MAX_DEVICE_NAME_SIZE - len(self.node.name) - 1
+ device_name = self.device_name[:max_len]
+
+ return NetDevice(node = host,
+ device_name = '{}-{}'.format(self.node.name, device_name),
+ managed = False)
+
+ def __create__(self):
+ assert self.node.__class__.__name__ == 'LxcContainer'
+ host = self.node.node
+ pid = get_attributes_task(self.node, ['pid'])
+ tmp_name = 'tmp-veth-' + ''.join(random.choice(string.ascii_uppercase \
+ + string.digits) for _ in range(5))
+ create = BashTask(host, CMD_CREATE, {'tmp_name': tmp_name,
+ 'interface': self})
+ up = BashTask(self.node, CMD_UP, {'interface': self})
+ bridge = host.bridge_manager.add_interface(host.bridge.device_name,
+ self.host.device_name)
+ return ((pid @ create) > up) > bridge
+
+ # ... IP and UP missing...
diff --git a/vicn/resource/lxd/lxc_container.py b/vicn/resource/lxd/lxc_container.py
index 8c8b4816..5b1d4e3b 100644
--- a/vicn/resource/lxd/lxc_container.py
+++ b/vicn/resource/lxd/lxc_container.py
@@ -32,9 +32,11 @@ from vicn.core.exception import ResourceNotFound
from vicn.core.requirement import Requirement
from vicn.core.resource_mgr import wait_resource_task
from vicn.core.task import task, inline_task, BashTask, EmptyTask
+from vicn.core.task import inherit_parent
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.lxc_image import LxcImage
from vicn.resource.lxd.lxd_profile import LXD_PROFILE_DEFAULT_IFNAME
log = logging.getLogger(__name__)
@@ -54,9 +56,20 @@ CMD_GET_IP6_FWD = 'sysctl -n net.ipv6.conf.all.forwarding'
CMD_NETWORK_DHCP='dhclient {container.management_interface.device_name}'
+CMD_MOUNT_FOLDER='''
+lxc config device add {container.name} {device-name} disk source={host_path} path={container_path}
+sleep 1
+'''
+
# Type: ContainerName
-ContainerName = String(max_size = 64, ascii = True,
- forbidden = ('/', ',', ':'))
+# https://github.com/lxc/lxd/issues/1431
+# [...] all container names must be a valid hostname under the most
+#restrictive definition of this, that is, maximum 63 characters, may not contain
+#dots, may not start by a digit or dash, may not end by a dash and must be made
+#entirely of letters, digits or hyphens.
+# XXX better have a allowed property
+ContainerName = String.restrict(max_size = 63, ascii = True,
+ forbidden = ('/', ',', ':', '_'))
class LxcContainer(Node):
"""
@@ -93,9 +106,9 @@ class LxcContainer(Node):
])
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')
+ image = Attribute(LxcImage, description = 'image', default = None)
+ is_image = Attribute(Bool, default = False)
+ pid = Attribute(Integer, description = 'PID of the container', ro = True)
ip6_forwarding = Attribute(Bool, default=True)
#--------------------------------------------------------------------------
@@ -110,6 +123,7 @@ class LxcContainer(Node):
# Resource lifecycle
#--------------------------------------------------------------------------
+ @inherit_parent
@inline_task
def __initialize__(self):
"""
@@ -128,6 +142,7 @@ class LxcContainer(Node):
if iface.get_type() == "dpdkdevice":
self.node.vpp_host.dpdk_devices.append(iface.pci_address)
+ @inherit_parent
@task
def __get__(self):
client = self.node.lxd_hypervisor.client
@@ -136,6 +151,7 @@ class LxcContainer(Node):
except pylxd.exceptions.NotFound:
raise ResourceNotFound
+ @inherit_parent
def __create__(self):
"""
Make sure vpp_host is instanciated before starting the container.
@@ -153,6 +169,7 @@ class LxcContainer(Node):
def _create_container(self):
container = self._get_container_description()
log.debug('Container description: {}'.format(container))
+ print('Container description: {}'.format(container))
client = self.node.lxd_hypervisor.client
self._container = client.containers.create(container, wait=True)
@@ -188,13 +205,13 @@ class LxcContainer(Node):
# SOURCE
image_names = [alias['name'] for alias in self.node.lxd_hypervisor.aliases]
- image_exists = self.image is not None and self.image in image_names
+ image_exists = self.image.image is not None and self.image.image in image_names
if image_exists:
container['source'] = {
'type' : 'image',
'mode' : 'local',
- 'alias' : self.image,
+ 'alias' : self.image.image,
}
else:
container['source'] = {
@@ -234,6 +251,7 @@ class LxcContainer(Node):
Method: Start the container
"""
self._container.start(wait = True)
+ import time; time.sleep(1)
@task
def __method_stop__(self):
@@ -288,10 +306,35 @@ class LxcContainer(Node):
"""
if not self._container:
- log.error("Executing command on uninitialized container", self, command)
+ log.error("Executing command on uninitialized container {} {}".format(self, command))
import os; os._exit(1)
- ret = self._container.execute(shlex.split(command))
+ if 'vppctl_wrapper' in command:
+ vpp_log = '{}/vpp-{}.sh'.format(self._state.manager._base, self.name)
+ with open(vpp_log, 'a') as f:
+ print("lxc exec {} -- {}".format(self.name, command), file=f)
+
+ # XXX Workaround: pylxd 2.2.3 buggy (w/ lxd 2.14) ?
+ # But this workaround is broken with lxd 2.15 and pylxd 2.2.4 works
+ # lxc exec freezes
+ #return self.node.execute('lxc exec {} -- {}'.format(self.name, command),
+ # output = output, as_root = as_root)
+
+ print("lxc exec {} -- {}".format(self.name, command))
+ while True:
+ try:
+ ret = self._container.execute(shlex.split(command))
+ break
+ except pylxd.exceptions.NotFound:
+ print("=====> pylxd not found during {}".format(command))
+ time.sleep(1)
+ except pylxd.exceptions.ClientConnectionFailed:
+ print("=====> pylxd connection failed during {}".format(command))
+ time.sleep(1)
+ except requests.exceptions.SSLError:
+ print("=====> ssl error during {}".format(command))
+ time.sleep(1)
+
# NOTE: pylxd documents the return value as a tuple, while it is in
# fact a ContainerExecuteResult object
@@ -306,12 +349,8 @@ class LxcContainer(Node):
return ReturnValue(*args)
def _get_ip6_forwarding(self):
- def parse(rv):
- ret = {"ip6_forwarding" : False}
- if rv.stdout == "1":
- ret["ip6_forwarding"] = True
- return ret
- return BashTask(self, CMD_GET_IP6_FWD, parse=parse)
+ return BashTask(self, CMD_GET_IP6_FWD,
+ parse = lambda rv: {'ip6_forwarding' : rv.stdout == "1"})
def _set_ip6_forwarding(self):
cmd = CMD_SET_IP6_FWD if self.ip6_forwarding else CMD_UNSET_IP6_FWD
diff --git a/vicn/resource/lxd/lxc_image.py b/vicn/resource/lxd/lxc_image.py
index a3a03245..f630fe2f 100644
--- a/vicn/resource/lxd/lxc_image.py
+++ b/vicn/resource/lxd/lxc_image.py
@@ -24,7 +24,7 @@ from vicn.core.attribute import Attribute, Multiplicity
from vicn.core.exception import ResourceNotFound
from vicn.core.requirement import Requirement
from vicn.core.resource import Resource
-from vicn.core.task import task, inline_task
+from vicn.core.task import task, inline_task, inherit_parent
from vicn.resource.linux.application import LinuxApplication as Application
from vicn.resource.node import Node
@@ -56,8 +56,11 @@ class LxcImage(Resource):
# Resource lifecycle
#---------------------------------------------------------------------------
+ @inherit_parent
@task
def __get__(self):
+ log.warning('Image test is currently disabled')
+ return
aliases = [alias['name'] for images in self.node.lxd_hypervisor.client.images.all()
for alias in images.aliases]
if not self.image in aliases:
@@ -69,6 +72,7 @@ class LxcImage(Resource):
return
+ @inherit_parent
@task
def __create_DISABLED__(self):
"""
@@ -97,6 +101,7 @@ class LxcImage(Resource):
tmp_container.delete()
+ @inherit_parent
@task
def __delete__(self):
self.node.lxd_hypervisor.client.images.delete(self.name)
diff --git a/vicn/resource/lxd/lxd_certificate_store.py b/vicn/resource/lxd/lxd_certificate_store.py
new file mode 100644
index 00000000..97a8cada
--- /dev/null
+++ b/vicn/resource/lxd/lxd_certificate_store.py
@@ -0,0 +1,48 @@
+#!/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.attribute import Attribute, Multiplicity
+from vicn.core.resource import Resource
+from vicn.resource.linux.certificate import Certificate
+from vicn.resource.node import Node
+
+PACKAGE = 'ca-certificates'
+
+CMD_ADD_CERTIFICATE = 'cp {certificate.cert} {store.PATH}'
+
+class LxdCertificateStore(Resource):
+ """
+ Resource: System-wide Certificate Store
+
+ This resource allows manipulation of the trusted certificates on the system.
+ Use with care.
+
+ See. vicn.resource.linux.certificate_store
+ """
+
+ PATH = '~/.config/lxc/servercerts/'
+
+ certificates = Attribute(Certificate, multiplicity = Multiplicity.OneToMany)
+ node = Attribute(Node, mandatory = True, requirements = [
+ #Requirement(PACKAGE, 'in', 'packages')
+ ])
+
+ def _add_certificate(self):
+ # Return a task that takes a certificate as parameter
+ PARAM = None
+ return BashTask(self.node, CMD_ADD_CERTIFICATE, {'store': self, 'certificate': PARAM})
diff --git a/vicn/resource/lxd/lxd_hypervisor.py b/vicn/resource/lxd/lxd_hypervisor.py
index bbdba7c6..fa63b96b 100644
--- a/vicn/resource/lxd/lxd_hypervisor.py
+++ b/vicn/resource/lxd/lxd_hypervisor.py
@@ -16,15 +16,6 @@
# limitations under the License.
#
-#-------------------------------------------------------------------------------
-# NOTES
-#-------------------------------------------------------------------------------
-# - lxd >= 2.0.4 is required
-# daemon/container: Remember the return code in the non wait-for-websocket
-# case (Issue #2243)
-# - Reference: https://github.com/lxc/lxd/tree/master/doc
-#-------------------------------------------------------------------------------
-
import logging
import os
from pylxd import Client
@@ -34,7 +25,8 @@ from netmodel.model.type import String, Integer
from vicn.core.attribute import Attribute, Multiplicity, Reference
from vicn.core.exception import ResourceNotFound
from vicn.core.resource import Resource
-from vicn.core.task import BashTask, task
+from vicn.core.task import EmptyTask, BashTask, task
+from vicn.core.task import inherit_parent, override_parent
from vicn.resource.linux.application import LinuxApplication as Application
from vicn.resource.linux.service import Service
from vicn.resource.linux.certificate import Certificate
@@ -74,10 +66,12 @@ CMD_LXD_NETWORK_SET = 'lxc network create {lxd_hypervisor.network} || true'
class LxdInit(Application):
__package_names__ = ['lxd', 'zfsutils-linux', 'lsof']
+ @inherit_parent
def __get__(self):
return BashTask(self.owner.node, CMD_LXD_CHECK_INIT,
{'lxd': self.owner})
+ @inherit_parent
def __create__(self):
cmd_params = {
'storage-backend' : self.owner.storage_backend,
@@ -107,12 +101,14 @@ class LxdInit(Application):
# zfs-dkms in the host
return BashTask(self.owner.node, cmd, as_root = True)
+ @inherit_parent
def __delete__(self):
raise NotImplementedError
class LxdInstallCert(Resource):
certificate = Attribute(Certificate, mandatory = True)
+ @inherit_parent
@task
def __get__(self):
try:
@@ -126,6 +122,7 @@ class LxdInstallCert(Resource):
raise ResourceNotFound
+ @inherit_parent
@task
def __create__(self):
"""
@@ -140,6 +137,8 @@ class LxdInstallCert(Resource):
#------------------------------------------------------------------------------
+LxdStorageType = String.restrict(choices=('zfs'))
+
class LxdHypervisor(Service):
"""
Resource: LxdHypervisor
@@ -150,9 +149,8 @@ class LxdHypervisor(Service):
lxd_port = Attribute(Integer, description = 'LXD REST API port',
default = 8443)
- storage_backend = Attribute(String, description = 'Storage backend',
- default = 'zfs',
- choices = ['zfs'])
+ storage_backend = Attribute(LxdStorageType, description = 'Storage backend',
+ default = 'zfs')
storage_size = Attribute(Integer, description = 'Storage size',
default = LXD_STORAGE_SIZE_DEFAULT) # GB
zfs_pool = Attribute(String, description = 'ZFS pool',
@@ -190,6 +188,7 @@ class LxdHypervisor(Service):
# Resource lifecycle
#--------------------------------------------------------------------------
+ @inherit_parent
def __subresources__(self):
lxd_init = LxdInit(owner=self, node = self.node)
lxd_local_cert = Certificate(node = Reference(self, 'node'),
@@ -208,6 +207,11 @@ class LxdHypervisor(Service):
return (lxd_init | lxd_local_cert) > (lxd_vicn_profile | lxd_cert_install)
+ @override_parent
+ def __create__(self):
+ log.warning('Not restarting LXD')
+ return EmptyTask()
+
#--------------------------------------------------------------------------
# Private methods
#--------------------------------------------------------------------------
diff --git a/vicn/resource/lxd/lxd_profile.py b/vicn/resource/lxd/lxd_profile.py
index db871671..e8e022d4 100644
--- a/vicn/resource/lxd/lxd_profile.py
+++ b/vicn/resource/lxd/lxd_profile.py
@@ -19,7 +19,7 @@
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.task import BashTask, inherit_parent
from vicn.core.exception import ResourceNotFound
CMD_LXD_PROFILE_CREATE = '''
@@ -41,18 +41,20 @@ 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',
+ description = Attribute(String, description = "profile description", mandatory=True)
+ pool = Attribute(String, description = "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)
+ @inherit_parent
def __get__(self):
def parse(rv):
if not rv.stdout:
raise ResourceNotFound
return BashTask(self.node, CMD_LXD_PROFILE_GET, {'profile':self}, parse=parse)
+ @inherit_parent
def __create__(self):
return BashTask(self.node, CMD_LXD_PROFILE_CREATE, {'profile':self})
diff --git a/vicn/resource/lxd/lxd_remote.py b/vicn/resource/lxd/lxd_remote.py
new file mode 100644
index 00000000..b5f8454a
--- /dev/null
+++ b/vicn/resource/lxd/lxd_remote.py
@@ -0,0 +1,99 @@
+#!/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, Bool
+from vicn.core.attribute import Attribute, Multiplicity
+from vicn.core.task import BashTask, EmptyTask, inherit_parent
+from vicn.core.exception import ResourceNotFound
+
+# From LXD 2.14
+#
+# lxc remote add [<remote>] <IP|FQDN|URL> [--accept-certificate] [--password=PASSWORD] [--public] [--protocol=PROTOCOL]
+# lxc remote remove <remote>
+# lxc remote list
+# lxc remote rename <old name> <new name>
+# lxc remote set-url <remote> <url>
+# lxc remote set-default <remote>
+# lxc remote get-default
+
+CMD_GET = 'lxc remote list | grep {remote.name}'
+
+CMD_CREATE = 'lxc remote add {remote.name} {remote.url}{public}'
+CMD_CREATE += ' --protocol {remote.protocol}'
+
+CMD_DELETE = 'lxc remote remove {remote.name}'
+
+CMD_SET_URL = 'lxc remote set-url {remote.name} {remote.url}'
+
+CMD_GET_DEFAULT = 'lxc remote get-default'
+CMD_SET_DEFAULT = 'lxc remote set-default {remote.name}'
+
+LxdProtocol = String.restrict(choices = ('simplestreams', 'lxd'))
+
+class LxdRemote(Resource):
+ # name (inherited)
+ url = Attribute(String, mandatory = True)
+ protocol = Attribute(LxdProtocol, default='lxd')
+ public = Attribute(Bool, default = True)
+ static = Attribute(Bool, default = False)
+ default = Attribute(Bool, default = False)
+
+ # Used to identify the LXD instance
+ node = Attribute(Resource, mandatory=True)
+
+ @inherit_parent
+ def __get__(self):
+ def parse(rv):
+ if not rv.stdout:
+ raise ResourceNotFound
+ return {
+ 'url': self.url,
+ 'protocol': self.protocol,
+ 'public': self.public,
+ 'static': self.static,
+ 'default': self.default,
+ }
+ return BashTask(self.node, CMD_GET, {'remote': self}, parse = parse)
+
+ @inherit_parent
+ def __create__(self):
+ public = ' --public' if self.public else ''
+ return BashTask(self.node, CMD_CREATE, {'remote': self, 'public': public})
+
+ @inherit_parent
+ def __delete__(self):
+ return BashTask(self.node, CMD_DELETE, {'remote': self})
+
+ def _get_url(self):
+ return None
+
+ def _set_url(self):
+ return BashTask(self.node, CMD_SET_URL, {'remote': self})
+
+ def _get_default(self):
+ def parse(rv):
+ return {'default': rv.stdout == self.name}
+ return BashTask(self.node, CMD_GET_DEFAULT, {'remote': self},
+ parse = parse)
+
+ def _set_default(self):
+ if self.default:
+ return BashTask(self.node, CMD_SET_DEFAULT, {'remote': self})
+ else:
+ return EmptyTask()
diff --git a/vicn/resource/node.py b/vicn/resource/node.py
index c785e32b..3805677e 100644
--- a/vicn/resource/node.py
+++ b/vicn/resource/node.py
@@ -32,6 +32,10 @@ DEFAULT_SSH_PRIVATE_KEY = os.path.expanduser(os.path.join(
DEFAULT_SSH_PUBLIC_KEY = os.path.expanduser(os.path.join(
'~', '.vicn', 'ssh_client_cert', 'ssh_client_key.pub'))
+OS = String.restrict(choices=('debian', 'ubuntu'))
+Distribution = String.restrict(choices=('trusty', 'xenial', 'sid'))
+Architecture = String.restrict(choices=('amd64'))
+
class Node(Resource):
"""
Resource: Node
@@ -42,15 +46,13 @@ class Node(Resource):
y = Attribute(Double, description = 'Y coordinate',
default = 0.0)
category = Attribute(String)
- os = Attribute(String, description = 'OS',
- default = 'ubuntu',
- choices = ['debian', 'ubuntu'])
- dist = Attribute(String, description = 'Distribution name',
- default = 'xenial',
- choices = ['trusty', 'xenial', 'sid'])
- arch = Attribute(String, description = 'Architecture',
- default = 'amd64',
- choices = ['amd64'])
+ scale = Attribute(Double, default = 1)
+ os = Attribute(OS, description = 'OS',
+ default = 'ubuntu')
+ dist = Attribute(Distribution, description = 'Distribution name',
+ default = 'xenial')
+ arch = Attribute(Architecture, description = 'Architecture',
+ default = 'amd64')
node_with_kernel = Attribute(Self,
description = 'Node on which the kernel sits',
ro = True)
diff --git a/vicn/resource/ns3/emulated_channel.py b/vicn/resource/ns3/emulated_channel.py
index 5f61960c..3c090a9e 100644
--- a/vicn/resource/ns3/emulated_channel.py
+++ b/vicn/resource/ns3/emulated_channel.py
@@ -16,30 +16,33 @@
# limitations under the License.
#
+import functools
import logging
import random
+from netmodel.model.key import Key
from netmodel.model.type import Integer
from netmodel.util.socket import check_port
from vicn.core.address_mgr import AddressManager
-from vicn.core.attribute import Attribute, Multiplicity
+from vicn.core.attribute import Attribute, Multiplicity
from vicn.core.exception import ResourceNotFound
from vicn.core.requirement import Requirement
from vicn.core.resource import BaseResource
from vicn.core.resource_mgr import wait_resources
from vicn.core.task import inline_task, async_task, task
from vicn.core.task import BashTask, run_task
+from vicn.core.task import inherit_parent
from vicn.resource.channel import Channel
-from vicn.resource.linux.application import LinuxApplication as Application
+from vicn.resource.linux.application import LinuxApplication
from vicn.resource.linux.net_device import NetDevice
from vicn.resource.linux.tap_device import TapDevice
-from vicn.resource.linux.veth_pair import VethPair
+from vicn.resource.linux.veth_pair_lxc import VethPairLxc
from vicn.resource.lxd.lxc_container import LxcContainer
from vicn.resource.node import Node
log = logging.getLogger(__name__)
-class EmulatedChannel(Channel, Application):
+class EmulatedChannel(Channel, LinuxApplication):
"""EmulatedChannel resource
This resources serves as a base class for wireless channels emulated by
@@ -56,7 +59,7 @@ class EmulatedChannel(Channel, Application):
traffic and prevent loops on the bridge.
- We also need that all interfaces related to ap and stations are created
before we run the commandline (currently, dynamically adding/removing
- AP and stations is not supported by the emulator). This is made
+ AP and stations is not supported by the emulator). This is made
possible thanks to the key=True parameter, which makes sure the
attributes are processed before the __create__ is called.
@@ -66,11 +69,15 @@ class EmulatedChannel(Channel, Application):
__resource_type__ = BaseResource
- ap = Attribute(Node, description = 'AP', key = True)
+ ap = Attribute(Node, description = 'AP')
stations = Attribute(Node, description = 'List of stations',
- multiplicity = Multiplicity.OneToMany, key = True)
+ multiplicity = Multiplicity.OneToMany)
control_port = Attribute(Integer,
description = 'Control port for the simulation')
+ nb_base_stations = Attribute(Integer, description='Number of nodes emulated by the AP',
+ default=1)
+
+ __key__ = Key(ap, stations)
# Overloaded attributes
node = Attribute(requirements = [
@@ -101,20 +108,29 @@ class EmulatedChannel(Channel, Application):
# Resource lifecycle
#--------------------------------------------------------------------------
+ @inherit_parent
+ def __initialize__(self):
+ return self.__set_ap() > self.__set_stations()
+
+ @inherit_parent
@inline_task
def __get__(self):
raise ResourceNotFound
+
+ @inherit_parent
def __create__(self):
# NOTE: http://stackoverflow.com/questions/21141352/python-subprocess-
# calling-a-script-which-runs-a-background-process-hanging
# The output of the background scripts is still going to the same file
# descriptor as the child script, thus the parent script waits for it
# to finish.
- cmd = '(' + self.__app_name__ + ' ' + self._get_cmdline_params() + \
- '>/dev/null 2>&1) &'
- return BashTask(self.node, cmd)
+ def get_cmdline(channel):
+ return '(' + channel.__app_name__ + ' ' + \
+ channel._get_cmdline_params() + '>/dev/null 2>&1) &'
+ return BashTask(self.node, functools.partial(get_cmdline, self))
+ @inherit_parent
def __delete__(self):
raise NotImplementedError
@@ -123,9 +139,8 @@ class EmulatedChannel(Channel, Application):
#--------------------------------------------------------------------------
@async_task
- async def _set_ap(self, ap=None):
- if ap is None:
- ap = self.ap
+ async def __set_ap(self):
+ ap = self.ap
if ap is None:
log.info('Ignored setting ap to None...')
return
@@ -133,13 +148,13 @@ class EmulatedChannel(Channel, Application):
# Add a WiFi interface for the AP...
interfaces = list()
if isinstance(ap, LxcContainer):
- # Ideally, We need to create a VethPair for each station
+ # Ideally, We need to create a VethPairLxc for each station
# This should be monitored for the total channel bw
host = NetDevice(node = ap.node,
device_name='vhh-' + ap.name + '-' + self.name,
monitored = False,
managed = False)
- self._ap_if = VethPair(node = self.ap,
+ self._ap_if = VethPairLxc(node = self.ap,
name = 'vh-' + ap.name + '-' + self.name,
device_name = 'vh-' + ap.name + '-' + self.name,
host = host,
@@ -171,37 +186,35 @@ class EmulatedChannel(Channel, Application):
# Add interfaces to bridge
vlan = AddressManager().get('vlan', self, tag='ap')
- # AS the container has created the VethPair already without Vlan, we
+ # AS the container has created the VethPairLxc already without Vlan, we
# need to delete and recreate it
task = self.node.bridge._remove_interface(self._ap_bridged)
await run_task(task, self._state.manager)
task = self.node.bridge._add_interface(self._ap_bridged, vlan = vlan)
await run_task(task, self._state.manager)
+ task = self.node.bridge._remove_interface(self._ap_tap)
+ await run_task(task, self._state.manager)
task = self.node.bridge._add_interface(self._ap_tap, vlan = vlan)
await run_task(task, self._state.manager)
@inline_task
- def _get_ap(self):
+ def __get_ap(self):
return {'ap': None}
@inline_task
- def _get_stations(self):
+ def __get_stations(self):
return {'stations': list()}
@async_task
- async def _set_stations(self, stations=None):
- print('adding stations...')
- if stations is None:
- stations = self.stations
-
- for station in stations:
+ async def __set_stations(self):
+ for station in self.stations:
await self._add_station(station)
- def _add_stations(self, stations):
+ def __add_stations(self, stations):
raise NotImplementedError
@inline_task
- def _remove_stations(self, station):
+ def __remove_stations(self, station):
raise NotImplementedError
diff --git a/vicn/resource/ns3/emulated_lte_channel.py b/vicn/resource/ns3/emulated_lte_channel.py
index bf0f7097..0847a403 100644
--- a/vicn/resource/ns3/emulated_lte_channel.py
+++ b/vicn/resource/ns3/emulated_lte_channel.py
@@ -16,11 +16,15 @@
# limitations under the License.
#
+import math
+
from vicn.core.address_mgr import AddressManager
-from vicn.core.resource_mgr import wait_resources
-from vicn.core.task import run_task
+from vicn.core.resource_mgr import wait_resources, wait_resource_task
+from vicn.core.task import run_task, EmptyTask
from vicn.resource.ns3.emulated_channel import EmulatedChannel
from vicn.resource.linux.net_device import NetDevice
+from vicn.core.attribute import Attribute
+from netmodel.model.type import Integer
DEFAULT_FADING_ENABLED = True
DEFAULT_TW_BUFFER = 800000
@@ -48,13 +52,24 @@ class EmulatedLteChannel(EmulatedChannel):
__package_names__ = ['lte-emulator']
__app_name__ = 'lte_emulator'
+ nb_base_stations = Attribute(Integer, description='Number of nodes emulated by the AP',
+ default=8)
+
+ def __create__(self):
+ task = EmptyTask()
+ for group in self.groups:
+ ip4_assigns = group.iter_by_type_str("ipv4assignment")
+ for ip4_assign in ip4_assigns:
+ task = task | wait_resource_task(ip4_assign)
+
+ return task > super().__create__()
#---------------------------------------------------------------------------
# Attribute handlers
#---------------------------------------------------------------------------
async def _add_station(self, station):
from vicn.resource.lxd.lxc_container import LxcContainer
- from vicn.resource.linux.veth_pair import VethPair
+ from vicn.resource.linux.veth_pair_lxc import VethPairLxc
from vicn.resource.linux.tap_device import TapChannel
interfaces = list()
@@ -66,9 +81,9 @@ class EmulatedLteChannel(EmulatedChannel):
host = NetDevice(node = station.node,
device_name='vhh-' + station.name + '-' + self.name,
managed = False)
- sta_if = VethPair(node = station,
+ sta_if = VethPairLxc(node = station,
name = 'vh-' + station.name + '-' + self.name,
- device_name = 'vh-' + station.name + '-' + self.name,
+ device_name = 'vh-' + station.name + '-' + self.name,
host = host,
owner = self)
bridged_sta = sta_if.host
@@ -104,31 +119,24 @@ class EmulatedLteChannel(EmulatedChannel):
task = self.node.bridge._remove_interface(bridged_sta)
await run_task(task, self._state.manager)
- task = self.node.bridge._add_interface(bridged_sta,
- vlan = vlan)
+ task = self.node.bridge._add_interface(bridged_sta, vlan = vlan)
await run_task(task, self._state.manager)
+ task = self.node.bridge._remove_interface(sta_tap)
+ await run_task(task, self._state.manager)
task = self.node.bridge._add_interface(sta_tap, vlan = vlan)
await run_task(task, self._state.manager)
def _get_cmdline_params(self):
- # IP have not been assign, use AddressManager for simplicity since it
- # will remember the assignment
- # NOTE: here the IP address passed to emulator program is hardcoded with
- # a /24 mask(even if the associated IP with the station does not have a
- # /24 mask). This is not a problem at all because the netmask passed to
- # the emulator program has no impact on configuration in the emulator
- # program. Indeed, the IP routing table in the emulator program are
- # configured on a per address basis(one route per IP address) instead of
- # on a per prefix basis(one route per prefix). This guarantees the IP
- # routing will not change regardless of what netmask is. That is why we
- # can always safely pass a hardcoded /24 mask to the emulator program.
-
sta_list = list() # list of identifiers
sta_macs = list() # list of macs
sta_taps = list()
sta_ips = list()
+
+ bs_ip_addr = self._ap_if.ip4_address
+ bs_ip = str(bs_ip_addr) + '/' + str(bs_ip_addr.prefix_len)
+
for station in self.stations:
if not station.managed:
interface = [i for i in station.interfaces if i.channel == self]
@@ -137,17 +145,15 @@ class EmulatedLteChannel(EmulatedChannel):
sta_list.append(interface.name)
sta_macs.append(interface.mac_address)
- sta_ips.append(interface.ip4_address + '/24')
+ sta_ips.append(str(interface.ip4_address)+'/'+str(prefix_len))
else:
identifier = self._sta_ifs[station]._state.uuid._uuid
sta_list.append(identifier)
mac = self._sta_ifs[station].mac_address
sta_macs.append(mac)
-
- # Preallocate IP address
- ip = AddressManager().get_ip(self._sta_ifs[station]) + '/24'
- sta_ips.append(ip)
+ ip = self._sta_ifs[station].ip4_address
+ sta_ips.append(str(ip)+'/'+str(ip.prefix_len))
tap = self._sta_taps[station].device_name
sta_taps.append(tap)
@@ -178,8 +184,7 @@ class EmulatedLteChannel(EmulatedChannel):
# Coma-separated list of stations' IP/netmask len
'sta-ips' : ','.join(sta_ips),
# Base station IP/netmask len
- 'bs-ip' : AddressManager().get_ip(self._ap_if) + '/' +
- str(DEFAULT_NETMASK),
+ 'bs-ip' : bs_ip,
'txBuffer' : '800000',
'isFading' : 'true' if DEFAULT_FADING_ENABLED else 'false',
}
diff --git a/vicn/resource/ns3/emulated_wifi_channel.py b/vicn/resource/ns3/emulated_wifi_channel.py
index 088d4444..d8838e47 100644
--- a/vicn/resource/ns3/emulated_wifi_channel.py
+++ b/vicn/resource/ns3/emulated_wifi_channel.py
@@ -38,7 +38,7 @@ class EmulatedWiFiChannel(EmulatedChannel):
async def _add_station(self, station):
from vicn.resource.lxd.lxc_container import LxcContainer
- from vicn.resource.linux.veth_pair import VethPair
+ from vicn.resource.linux.veth_pair_lxc import VethPairLxc
from vicn.resource.linux.tap_device import TapChannel
from vicn.resource.linux.macvlan import MacVlan
@@ -46,18 +46,21 @@ class EmulatedWiFiChannel(EmulatedChannel):
if not station.managed:
sta_if = None
else:
+ # To connect a container to the EmulatedWifiChannel, we use a
+ # VethPairLxc connected to the bridge, that will be in the same VLAN as
+ # the station TAP entering the emulator
if isinstance(station, LxcContainer):
host = NetDevice(node = station.node,
device_name='vhh-' + station.name + '-' + self.name,
managed = False)
- sta_if = VethPair(node = station,
+ sta_if = VethPairLxc(node = station,
name = 'vh-' + station.name + '-' + self.name,
- device_name = 'vh-' + station.name + '-' + self.name,
+ device_name = 'vh-' + station.name + '-' + self.name,
host = host,
owner = self)
bridged_sta = sta_if.host
else:
- raise NotImplementedError
+ raise NotImplementedError
if sta_if:
self._sta_ifs[station] = sta_if
@@ -65,7 +68,7 @@ class EmulatedWiFiChannel(EmulatedChannel):
interfaces.append(sta_if)
self._state.manager.commit_resource(sta_if)
- sta_tap = TapChannel(node = self.node,
+ sta_tap = TapChannel(node = self.node,
owner = self,
device_name = 'tap-' + station.name + '-' + self.name,
up = True,
@@ -81,23 +84,23 @@ class EmulatedWiFiChannel(EmulatedChannel):
# Add interfaces to bridge
# One vlan per station is needed to avoid broadcast loops
- vlan = AddressManager().get('vlan', sta_tap)
+ vlan = AddressManager().get('vlan', sta_tap)
# sta_tap choosen because always there
if sta_if:
sta_if.set('channel', self)
task = self.node.bridge._remove_interface(bridged_sta)
await run_task(task, self._state.manager)
- task = self.node.bridge._add_interface(bridged_sta,
- vlan = vlan)
+ task = self.node.bridge._add_interface(bridged_sta, vlan = vlan)
await run_task(task, self._state.manager)
+ task = self.node.bridge._remove_interface(sta_tap)
+ await run_task(task, self._state.manager)
task = self.node.bridge._add_interface(sta_tap, vlan = vlan)
await run_task(task, self._state.manager)
def _get_cmdline_params(self, ):
-
# sta-macs and sta-list for unmanaged stations
sta_list = list() # list of identifiers
sta_macs = list() # list of macs
@@ -136,7 +139,7 @@ class EmulatedWiFiChannel(EmulatedChannel):
# Y position of the Base Station
'bs-y' : 0, #self.ap.y,
# Experiment ID
- 'experiment-id' : 'vicn',
+ 'experiment-id' : 'vicn',
# Index of the base station
'bs-name' : self._ap_tap.device_name,
# Base station MAC address
diff --git a/vicn/resource/symmetric_channel.py b/vicn/resource/symmetric_channel.py
new file mode 100644
index 00000000..d8a35030
--- /dev/null
+++ b/vicn/resource/symmetric_channel.py
@@ -0,0 +1,16 @@
+from netmodel.model.key import Key
+from vicn.core.attribute import Attribute
+from vicn.core.task import inherit_parent
+from vicn.resource.interface import Interface
+from vicn.resource.channel import Channel
+
+class SymmetricChannel(Channel):
+ src = Attribute(Interface, mandatory = True)
+ dst = Attribute(Interface, mandatory = True)
+
+ __key__ = Key(src, dst)
+
+ @inherit_parent
+ def __create__(self):
+ self.interfaces << self.src
+ self.interfaces << self.dst
diff --git a/vicn/resource/vpp/dpdk_device.py b/vicn/resource/vpp/dpdk_device.py
index 472ee26f..76659129 100644
--- a/vicn/resource/vpp/dpdk_device.py
+++ b/vicn/resource/vpp/dpdk_device.py
@@ -19,6 +19,7 @@
from netmodel.model.type import Integer, String
from vicn.core.attribute import Attribute
from vicn.resource.linux.phy_interface import PhyInterface
+from vicn.core.task import BashTask, task
class DpdkDevice(PhyInterface):
"""
@@ -32,4 +33,3 @@ class DpdkDevice(PhyInterface):
socket_mem = Attribute(Integer,
description = 'Memory used by the vpp forwarder',
default = 512)
- mac_address = Attribute(String)
diff --git a/vicn/resource/vpp/interface.py b/vicn/resource/vpp/interface.py
index 5f8f5018..d0538661 100644
--- a/vicn/resource/vpp/interface.py
+++ b/vicn/resource/vpp/interface.py
@@ -16,17 +16,63 @@
# limitations under the License.
#
+import pyparsing as pp
+
+from netmodel.model.key import Key
from netmodel.model.type import Integer, String, Bool
+from netmodel.model.type import Inet4Address, Inet6Address
from vicn.core.resource import Resource
from vicn.core.attribute import Attribute, Multiplicity
from vicn.core.exception import ResourceNotFound
from vicn.core.task import inline_task, BashTask, task
+from vicn.core.task import inherit_parent
from vicn.core.task import EmptyTask
from vicn.resource.interface import Interface
from vicn.resource.linux.net_device import NonTapBaseNetDevice
from vicn.resource.vpp.vpp import VPP
-from vicn.resource.vpp.vpp_commands import CMD_VPP_CREATE_IFACE
+from vicn.resource.vpp.vpp_commands import CMD_VPP_CREATE_IFACE, CMD_VPP_CREATE_MEMIFACE
from vicn.resource.vpp.vpp_commands import CMD_VPP_SET_IP, CMD_VPP_SET_UP
+from vicn.resource.vpp.memif_device import MemifDevice
+
+GREP_MEMIF_INFO = 'vppctl_wrapper show memif | grep interface --no-group-separator -A 1'
+
+def parse_memif(rv, vppinterface):
+ kw_interface = pp.CaselessKeyword('interface')
+ kw_key = pp.CaselessKeyword('key')
+ kw_file = pp.CaselessKeyword('file')
+ kw_listener = pp.CaselessKeyword('listener')
+ kw_connfd = pp.CaselessKeyword('conn-fd')
+ kw_intfd = pp.CaselessKeyword('int-fd')
+ kw_ringsize = pp.CaselessKeyword('ring-size')
+ kw_numc2srings = pp.CaselessKeyword('num-c2s-rings')
+ kw_nums2crings = pp.CaselessKeyword('num-s2c-rings')
+ kw_buffersize = pp.CaselessKeyword('buffer_size')
+
+ r_path = ' *(/[a-zA-Z0-9_\-]*)*\.[a-zA-Z0-9_\-]*'
+ r_id = ' *-+[0-9]*'
+
+ single = kw_interface.suppress() + pp.Word(pp.alphanums).setResultsName('interface') + \
+ kw_key.suppress() + pp.Word(pp.alphanums).setResultsName('key') + \
+ kw_file.suppress() + pp.Regex(r_path).setResultsName('path') # + \
+ # kw_listener.suppress() + pp.Word(pp.alphanums).setResultsName('listener') + \
+ # kw_connfd.suppress() + pp.Regex(r_id).setResultsName('conn-fd') + \
+ # kw_intfd.suppress() + pp.Regex(r_id).setResultsName('int-fd') + \
+ # kw_ringsize.suppress() + pp.Word(pp.nums).setResultsName('ring-size') + \
+ # kw_numc2srings.suppress() + pp.Word(pp.nums).setResultsName('num-c2s-rings') + \
+ # kw_nums2crings.suppress() + pp.Word(pp.nums).setResultsName('num-s2c-rings') + \
+ # kw_buffersize.suppress() + pp.Word(pp.nums).setResultsName('buffer-size')
+
+ multiple = pp.OneOrMore(pp.Group(single))
+
+ results = multiple.parseString(rv.stdout)
+
+ for interface in results:
+ if interface['path'] == vppinterface.parent.path_unix_socket + vppinterface.parent.socket_name:
+ vppinterface.device_name = interface['interface']
+ vppinterface.parent.device_name = interface['interface']
+
+ return vppinterface.device_name
+
class VPPInterface(Resource):
"""
@@ -39,18 +85,21 @@ class VPPInterface(Resource):
description = 'Forwarder to which this interface belong to',
mandatory = True,
multiplicity = Multiplicity.ManyToOne,
- key = True,
reverse_name = 'interfaces')
parent = Attribute(Interface, description = 'parent',
mandatory = True, reverse_name = 'vppinterface')
- ip4_address = Attribute(String)
- ip6_address = Attribute(String)
- prefix_len = Attribute(Integer, default = 31)
+ ip4_address = Attribute(Inet4Address)
+ ip6_address = Attribute(Inet6Address)
up = Attribute(Bool, description = 'Interface up/down status')
monitored = Attribute(Bool, default = True)
device_name = Attribute(String)
+ __key__ = Key(vpp, Resource.name)
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
#--------------------------------------------------------------------------
# Resource lifecycle
#--------------------------------------------------------------------------
@@ -61,10 +110,12 @@ class VPPInterface(Resource):
"""
return ['CentralIP']
+ @inherit_parent
@inline_task
def __get__(self):
raise ResourceNotFound
+ @inherit_parent
def __create__(self):
# We must control what is the type of the parent netDevice (currently
# supported only veths, physical nics are coming)
@@ -73,15 +124,27 @@ class VPPInterface(Resource):
# We must let the routing algorithm know that the parent interface
# belongs to vpp
self.parent.has_vpp_child = True
-
- self.ip4_address = self.parent.ip4_address
- self.ip6_address = self.parent.ip6_address
self.up = True
- if isinstance(self.parent,NonTapBaseNetDevice):
+ if isinstance(self.parent, MemifDevice):
+ #TODO: add output parsing to get the interface name
+ create_task = BashTask(self.vpp.node, CMD_VPP_CREATE_MEMIFACE, {
+ 'key': hex(self.parent.key),
+ 'vpp_interface': self,
+ 'master_slave': 'master' if self.parent.master else 'slave'},
+ lock = self.vpp.vppctl_lock)
+ fill_name = BashTask(self.vpp.node, GREP_MEMIF_INFO,
+ parse = (lambda x, y=self : parse_memif(x, y)),
+ lock = self.vpp.vppctl_lock)
+
+ create_task = create_task > fill_name
+
+ elif isinstance(self.parent,NonTapBaseNetDevice):
# Remove ip address in the parent device, it must only be set in
# the vpp interface otherwise vpp and the linux kernel will reply
# to non-icn request (e.g., ARP replies, port ureachable etc)
+ self.ip4_address = self.parent.ip4_address
+ self.ip6_address = self.parent.ip6_address
self.device_name = 'host-' + self.parent.device_name
create_task = BashTask(self.vpp.node, CMD_VPP_CREATE_IFACE,
@@ -94,6 +157,8 @@ class VPPInterface(Resource):
self.parent.remote.set('offload', False)
elif self.parent.get_type() == 'dpdkdevice':
+ self.ip4_address = self.parent.ip4_address
+ self.ip6_address = self.parent.ip6_address
self.device_name = self.parent.device_name
else :
# Currently assume naively that everything else will be a physical
@@ -112,13 +177,27 @@ class VPPInterface(Resource):
def _set_ip4_address(self):
if self.ip4_address:
- return BashTask(self.vpp.node, CMD_VPP_SET_IP, {'netdevice': self},
+ return BashTask(self.vpp.node, CMD_VPP_SET_IP, {
+ 'device_name': self.device_name,
+ 'ip_address': str(self.ip4_address),
+ 'prefix_len': self.ip4_address.prefix_len},
lock = self.vpp.vppctl_lock)
- def _set_up(self):
- return BashTask(self.vpp.node, CMD_VPP_SET_UP, {'netdevice': self},
+ def _set_ip6_address(self):
+ if self.ip6_address:
+ return BashTask(self.vpp.node, CMD_VPP_SET_IP, {
+ 'device_name': self.device_name,
+ 'ip_address': str(self.ip6_address),
+ 'prefix_len': self.ip6_address.prefix_len},
lock = self.vpp.vppctl_lock)
+ def _set_up(self):
+ state = 'up' if self.up else 'down'
+ return BashTask(self.vpp.node, CMD_VPP_SET_UP, {
+ 'netdevice': self,
+ 'state': state},
+ lock = self.vpp.vppctl_lock)
+
@task
def _get_up(self):
return {'up' : False}
diff --git a/vicn/resource/vpp/memif_device.py b/vicn/resource/vpp/memif_device.py
new file mode 100644
index 00000000..a114900a
--- /dev/null
+++ b/vicn/resource/vpp/memif_device.py
@@ -0,0 +1,77 @@
+#!/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 netmodel.model.key import Key
+from netmodel.model.type import Bool, String, Integer
+from netmodel.model.type import Inet4Address, Inet6Address
+from vicn.core.attribute import Attribute
+from vicn.resource.interface import Interface
+from vicn.resource.lxd.lxc_container import CMD_MOUNT_FOLDER
+from vicn.resource.linux.folder import Folder
+from vicn.core.task import inline_task, BashTask
+from vicn.core.task import inherit_parent
+from vicn.core.exception import ResourceNotFound
+from vicn.core.address_mgr import AddressManager
+
+class MemifDevice(Interface):
+ """
+ Resource: Memory interface device
+
+ A MemifDevice is device build on top of a the memory interface provided by vpp.
+ It uses a unix socket to connect the two vpp-s (one master and one slave).
+ The unix socket must be shared between the two vpp-s.
+ """
+ path_unix_socket = Attribute(String,
+ mandatory = True,
+ description = 'Path to the shared folder holding the unix socket')
+ socket_name = Attribute(String,
+ mandatory = True,
+ description = 'Path to the shared folder holding the unix socket')
+ folder_host = Attribute(Folder,
+ mandatory = True,
+ description = 'Folder in the host to be mounted in the container ih path_unix_socket')
+ master= Attribute(Bool,
+ description = 'True if this interface is connected to the master vpp',
+ default = False)
+ # We need to automatically assign a mac address to the memif so that we can
+ # recreate it after vpp reboots thanks to a config file or bash script.
+ # Just reading the actual value would not work since we need to use an
+ # external script and this mac address would thus not be prevent in the list
+ # of executed commands.
+ mac_address = Attribute(String, description = 'Mac address of the device',
+ default = lambda self: AddressManager().get_mac(self))
+ ip4_address = Attribute(Inet4Address, description = "Device's IP address")
+ ip6_address = Attribute(Inet6Address, description = "Device's IP address")
+ device_name = Attribute(String)
+ key = Attribute(Integer)
+
+ __key__ = Key(folder_host)
+
+ @inline_task
+ def __get__(self):
+ raise ResourceNotFound
+
+ @inherit_parent
+ def __create__(self):
+ return BashTask(self.node.node_with_kernel, CMD_MOUNT_FOLDER, {
+ 'container': self.node,
+ 'device-name': self.socket_name,
+ 'host_path': self.folder_host.foldername,
+ 'container_path': self.path_unix_socket}, output=True,
+ lock=self.node.vpp.memif_lock)
+
diff --git a/vicn/resource/vpp/memif_link.py b/vicn/resource/vpp/memif_link.py
new file mode 100644
index 00000000..62de03c6
--- /dev/null
+++ b/vicn/resource/vpp/memif_link.py
@@ -0,0 +1,144 @@
+#!/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.
+#
+
+import random
+import string
+import logging
+import asyncio
+
+from netmodel.model.type import Integer, String
+from netmodel.model.key import Key
+from vicn.core.attribute import Attribute, Reference
+from vicn.core.exception import ResourceNotFound
+from vicn.core.state import ResourceState, AttributeState
+from vicn.core.task import inline_task, async_task, run_task
+from vicn.core.task import get_attributes_task, BashTask
+from vicn.core.task import inherit_parent
+from vicn.resource.channel import Channel
+from vicn.resource.interface import Interface
+from vicn.resource.linux.net_device import NonTapBaseNetDevice
+from vicn.resource.node import Node
+from vicn.resource.lxd.lxc_container import CMD_MOUNT_FOLDER, LxcContainer
+from vicn.resource.vpp.memif_device import MemifDevice
+from vicn.resource.linux.folder import Folder
+
+# FIXME remove VPP specific code
+from vicn.resource.vpp.interface import VPPInterface
+
+log = logging.getLogger(__name__)
+
+CONTAINER_SOCKET_PATH='/root/{}'
+SHARED_FOLDER_PATH='/tmp/{}'
+
+class MemifLink(Channel):
+ """
+ Resource: MemifLink
+
+ Implements a virtual wired link between containers. It is made
+ with a pair of MemifDevice which are created in the two containers.
+
+ Because of this, the resource only supports passing source and destination
+ containers, and not interfaces. It also explains the relative complexity of
+ the current implementation.
+ """
+
+ src_node = Attribute(LxcContainer, description = 'Source node',
+ mandatory = True)
+ dst_node = Attribute(LxcContainer, description = 'Destination node',
+ mandatory = True)
+ folder = Attribute(Folder, description = 'Shared folder holding the socket used by the MemifDevices')
+ key = Attribute(Integer)
+
+ __key__ = Key(src_node, dst_node)
+
+ def __init__(self, *args, **kwargs):
+ assert 'src_node' in kwargs and 'dst_node' in kwargs
+ self._src = None
+ self._dst = None
+ self._folder = None
+ super().__init__(*args, **kwargs)
+
+ @inherit_parent
+ def __subresources__(self):
+ assert self.src_node.node_with_kernel == self.dst_node.node_with_kernel
+
+ host = self.src_node.node_with_kernel
+ # We create two managed net devices that are pre-setup
+ # but the resource manager has to take over for IP addresses etc.
+ # Being done in initialize, those attributes won't be considered as
+ # dependencies and will thus not block the resource state machine.
+
+ socket_name = 'socket-' + ''.join(random.choice(string.ascii_uppercase
+ + string.digits) for _ in range(5)) +'.file'
+
+ _folder = Folder(node = host,
+ foldername = SHARED_FOLDER_PATH.format(self.src_node.name + '-' + self.dst_node.name),
+ permission = 777)
+
+ self.key = random.randint(0, 2**64)
+
+ self._src = MemifDevice(node = self.src_node,
+ channel = self,
+ owner = self,
+ path_unix_socket = CONTAINER_SOCKET_PATH.format(self.src_node.name + '-'
+ + self.dst_node.name) + '/',
+ folder_host = _folder,
+ socket_name = socket_name,
+ device_name = 'memif-'+self.dst_node.name,
+ key = Reference(self, 'key'),
+ master = False)
+ self._dst = MemifDevice(node = self.dst_node,
+ channel = self,
+ owner = self,
+ path_unix_socket = CONTAINER_SOCKET_PATH.format(self.src_node.name + '-'
+ + self.dst_node.name) + '/',
+ socket_name = socket_name,
+ folder_host = _folder,
+ device_name = 'memif-'+self.src_node.name,
+ key = Reference(self, 'key'),
+ master = True)
+ self._dst.remote = self._src
+ self._src.remote = self._dst
+
+ return _folder | (self._src | self._dst)
+
+
+ #--------------------------------------------------------------------------
+ # Internal methods
+ #--------------------------------------------------------------------------
+
+ async def _commit(self):
+ manager = self._state.manager
+
+ if hasattr(self.src_node, 'vpp') and not self.src_node.vpp is None:
+ vpp_src = VPPInterface(parent = self._src,
+ vpp = self.src_node.vpp,
+ ip4_address = Reference(self._src, 'ip4_address'),
+ ip6_address = Reference(self._src, 'ip6_address'))
+ manager.commit_resource(vpp_src)
+
+ if hasattr(self.dst_node, 'vpp') and not self.dst_node.vpp is None:
+ vpp_dst = VPPInterface(parent = self._dst,
+ vpp = self.dst_node.vpp,
+ ip4_address = Reference(self._dst, 'ip4_address'),
+ ip6_address = Reference(self._dst, 'ip6_address'))
+ manager.commit_resource(vpp_dst)
+
+ @inherit_parent
+ def __get__(self):
+ return async_task(self._commit)()
diff --git a/vicn/resource/vpp/scripts.py b/vicn/resource/vpp/scripts.py
index 3a3d5e8f..d5130212 100644
--- a/vicn/resource/vpp/scripts.py
+++ b/vicn/resource/vpp/scripts.py
@@ -282,6 +282,6 @@ api-segment {
'''
APPARMOR_VPP_PROFILE = '''
-lxc.aa_profile = lxc-dpdk
+lxc.aa_profile = lxc-dpdk
lxc.mount.entry = hugetlbfs dev/hugepages hugetlbfs rw,relatime,create=dir 0 0
lxc.mount.auto = sys:rw'''
diff --git a/vicn/resource/vpp/vpp.py b/vicn/resource/vpp/vpp.py
index 9edcfea3..8250f99a 100644
--- a/vicn/resource/vpp/vpp.py
+++ b/vicn/resource/vpp/vpp.py
@@ -23,6 +23,7 @@ from vicn.core.attribute import Attribute, Multiplicity
from vicn.core.exception import ResourceNotFound
from vicn.core.resource import Resource
from vicn.core.task import BashTask, task, inline_task
+from vicn.core.task import inherit_parent, EmptyTask
from vicn.resource.lxd.lxc_container import LxcContainer
from vicn.resource.node import Node
from vicn.resource.linux.application import LinuxApplication
@@ -33,6 +34,7 @@ from vicn.resource.vpp.scripts import TPL_VPP_DPDK_DAEMON_SCRIPT
from vicn.resource.vpp.vpp_commands import CMD_VPP_DISABLE, CMD_VPP_STOP
from vicn.resource.vpp.vpp_commands import CMD_VPP_START
from vicn.resource.vpp.vpp_commands import CMD_VPP_ENABLE_PLUGIN
+from vicn.resource.vpp.vpp_commands import CMD_REMOVE_DPDK_PLUGIN
from vicn.resource.vpp.vpp_host import VPPHost
#------------------------------------------------------------------------------
@@ -50,7 +52,7 @@ class VPP(LinuxApplication):
start and stop commands
"""
- __package_names__ = ['vpp', 'vpp-dbg', 'vpp-dpdk-dev']
+ __package_names__ = ['vpp', 'vpp-dpdk-dev']
plugins = Attribute(String,
multiplicity = Multiplicity.OneToMany)
@@ -72,12 +74,15 @@ class VPP(LinuxApplication):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
- self.vppctl_lock = asyncio.Lock()
+ self.vppctl_lock = asyncio.Lock()
+
+ # needed by "lxc config device add"
+ self.memif_lock = asyncio.Lock()
- self.dpdk_setup_file = None
if isinstance(self.node, LxcContainer):
if not 'vpp' in self.node.profiles:
self.node.profiles.append('vpp')
+ self.dpdk_setup_file = None
#--------------------------------------------------------------------------
# Resource lifecycle
@@ -86,16 +91,14 @@ class VPP(LinuxApplication):
def __after__(self):
return ['BaseNetDevice']
+ @inherit_parent
+ @task
def __get__(self):
+ raise ResourceNotFound
return BashTask(self.node, CMD_GET)
+ @inherit_parent
def __subresources__(self):
- self.dpdk_setup_file = TextFile(node = self.node,
- filename = FN_VPP_DPDK_SCRIPT,
- overwrite = True)
- return self.dpdk_setup_file
-
- def __create__(self):
socket_mem = dict()
numa_mgr = self.node.node_with_kernel.numa_mgr
@@ -117,25 +120,6 @@ class VPP(LinuxApplication):
self.numa_node, self.core = \
numa_mgr.get_numa_core(numa_node = self.numa_node)
- dpdk_list = list()
-
- # On numa architecture socket-mem requires to set the amount of memory
- # to be reserved on each numa node
- socket_mem_str = 'socket-mem '
- for numa in range (0,numa_mgr.get_number_of_numa()):
- if numa in socket_mem:
- socket_mem_str = socket_mem_str + str(socket_mem[numa])
- else:
- socket_mem_str = socket_mem_str + '0'
-
- if numa < numa_mgr.get_number_of_numa()-1:
- socket_mem_str = socket_mem_str + ','
-
- dpdk_list.append(socket_mem_str)
-
- for interface in self.node.interfaces:
- if isinstance(interface, DpdkDevice):
- dpdk_list.append('dev ' + interface.pci_address)
# Add the core on which running vpp and the dpdk parameters
setup = TPL_VPP_DPDK_DAEMON_SCRIPT + 'cpu {'
@@ -146,19 +130,42 @@ class VPP(LinuxApplication):
self.numa_node, cpu_worker =numa_mgr.get_numa_core(self.numa_node)
setup = setup + '''\n corelist-workers ''' + str(cpu_worker)
- setup = setup + '''\n}\n\n dpdk { '''
-
- for dpdk_dev in dpdk_list:
- setup = setup + ''' \n ''' + dpdk_dev
-
- setup = setup + '\n}'
+ dpdk_list = list()
+ for interface in self.node.interfaces:
+ if isinstance(interface, DpdkDevice):
+ dpdk_list.append('dev ' + interface.pci_address)
+ setup = setup + '''\n}\n\n'''
+ if dpdk_list:
+ setup = setup + 'dpdk {'
+ # add socket_mem
+ # On numa architecture socket-mem requires to set the amount of memory
+ # to be reserved on each numa node
+ socket_mem_str = 'socket-mem '
+ for numa in range (0,numa_mgr.get_number_of_numa()):
+ if numa in socket_mem:
+ socket_mem_str = socket_mem_str + str(socket_mem[numa])
+ else:
+ socket_mem_str = socket_mem_str + '0'
+
+ if numa < numa_mgr.get_number_of_numa()-1:
+ socket_mem_str = socket_mem_str + ','
+
+ dpdk_list.append(socket_mem_str)
+
+ for dpdk_dev in dpdk_list:
+ setup = setup + ''' \n ''' + dpdk_dev
+ setup = setup + '''\n}'''
+
+ dpdk_setup_file = TextFile(node = self.node,
+ filename = FN_VPP_DPDK_SCRIPT,
+ content = setup,
+ overwrite = True)
- if any([isinstance(interface,DpdkDevice) for interface in self.node.interfaces]):
- self.dpdk_setup_file.content = setup
- else:
- self.dpdk_setup_file.content = TPL_VPP_DPDK_DAEMON_SCRIPT
+ return dpdk_setup_file
+ @inherit_parent
+ def __create__(self):
lock = self.node.node_with_kernel.vpp_host.vppstart_lock
vpp_disable = BashTask(self.node, CMD_VPP_DISABLE, lock = lock)
@@ -166,8 +173,19 @@ class VPP(LinuxApplication):
enable_ip_forward = BashTask(self.node, CMD_DISABLE_IP_FORWARD)
start_vpp = BashTask(self.node, CMD_VPP_START, lock = lock)
- return ((vpp_disable > vpp_stop) | enable_ip_forward) > start_vpp
+ found = False
+ for iface in self.interfaces:
+ if isinstance(iface.parent, DpdkDevice):
+ found = True
+ break
+
+ remove_dpdk_plugin = EmptyTask()
+ if (not found):
+ remove_dpdk_plugin = BashTask(self.node, CMD_REMOVE_DPDK_PLUGIN, lock = lock)
+
+ return (((vpp_disable > vpp_stop) | enable_ip_forward) > (remove_dpdk_plugin > start_vpp))
+ @inherit_parent
def __delete__(self):
return BashTask(self.node, CMD_VPP_STOP)
diff --git a/vicn/resource/vpp/vpp_bridge.py b/vicn/resource/vpp/vpp_bridge.py
index 612145d9..53523c17 100644
--- a/vicn/resource/vpp/vpp_bridge.py
+++ b/vicn/resource/vpp/vpp_bridge.py
@@ -24,6 +24,7 @@ from vicn.core.requirement import Requirement
from vicn.core.resource_mgr import wait_resource_task
from vicn.core.resource import Resource
from vicn.core.task import task, BashTask, EmptyTask
+from vicn.core.task import inherit_parent
from vicn.resource.channel import Channel
from vicn.resource.linux.application import LinuxApplication
from vicn.resource.linux.sym_veth_pair import SymVethPair
@@ -33,7 +34,7 @@ from vicn.resource.vpp.dpdk_device import DpdkDevice
from vicn.resource.vpp.interface import VPPInterface
from vicn.resource.vpp.vpp import VPP
-CMD_ADD_INTERFACE_TO_BR = ('vppctl set interface l2 bridge '
+CMD_ADD_INTERFACE_TO_BR = ('vppctl_wrapper set interface l2 bridge '
'{interface.device_name} {br_domain}')
class VPPBridge(Channel, LinuxApplication):
@@ -69,6 +70,7 @@ class VPPBridge(Channel, LinuxApplication):
# Resource lifecycle
#--------------------------------------------------------------------------
+ @inherit_parent
def __subresources__ (self):
# We don't need any reference to the list of SymVethPair because each
# side of a veth will be included in the node.interfaces list
@@ -77,12 +79,14 @@ class VPPBridge(Channel, LinuxApplication):
return Resource.__concurrent__(*self._veths)
+ @inherit_parent
@task
def __initialize__ (self):
# Add the veth side on the connected_nodes to the set of interfaces of
# the channel
self.interfaces.extend([veth.side2 for veth in self._veths])
+ @inherit_parent
@task
def __get__(self):
# Forces creation
@@ -91,6 +95,7 @@ class VPPBridge(Channel, LinuxApplication):
# Nothing to do
__delete__ = None
+ @inherit_parent
def __create__(self):
manager = self._state.manager
diff --git a/vicn/resource/vpp/vpp_commands.py b/vicn/resource/vpp/vpp_commands.py
index 40315c19..30898eae 100644
--- a/vicn/resource/vpp/vpp_commands.py
+++ b/vicn/resource/vpp/vpp_commands.py
@@ -5,37 +5,41 @@ CMD_VPP_DISABLE = 'systemctl disable vpp.service'
# 'sleep 1' ensures that VPP has enough time to start
CMD_VPP_START = '''
-systemctl start vpp
-sleep 1
+flock /tmp/vppctl.lock -c "systemctl start vpp"
'''
CMD_VPP_STOP = '''
-systemctl stop vpp
-killall -9 vpp_main || true
+flock /tmp/vppctl.lock -c "systemctl stop vpp"
+'''
+#killall -9 vpp_main || true
+CMD_VPP_ENABLE_PLUGIN = 'vppctl_wrapper {plugin} control start'
+
+CMD_REMOVE_DPDK_PLUGIN = '''
+rm /usr/lib/vpp_api_test_plugins/dpdk_test_plugin.so
+rm /usr/lib/vpp_plugins/dpdk_plugin.so
'''
-CMD_VPP_ENABLE_PLUGIN = 'vppctl {plugin} enable'
##### VPP INTERFACES #####
CMD_VPP_CREATE_IFACE = '''
-# Create vpp interface from netmodel.network.interface.device_name} with mac {self.parent.mac_address}
-vppctl create host-interface name {vpp_interface.parent.device_name} hw-addr {vpp_interface.parent.mac_address}
-vppctl set interface state {vpp_interface.device_name} up
+# Create vpp interface from {vpp_interface.parent.device_name} with mac {vpp_interface.parent.mac_address}
+vppctl_wrapper create host-interface name {vpp_interface.parent.device_name} hw-addr {vpp_interface.parent.mac_address}
+vppctl_wrapper set interface state {vpp_interface.device_name} up
'''
-CMD_VPP_SET_IP = 'vppctl set int ip address {netdevice.device_name} {netdevice.ip4_address}/{netdevice.prefix_len}'
-CMD_VPP_SET_UP = 'vppctl set int state {netdevice.device_name} up'
-
-##### VPP IP ROUTING #####
-CMD_VPP_ADD_ROUTE = 'vppctl set ip arp static {route.interface.vppinterface.device_name} {route.ip_address} {route.mac_address}'
-CMD_VPP_DEL_ROUTE = 'vppctl set ip arp del static {route.interface.vppinterface.device_name} {route.ip_address} {route.mac_address}'
-CMD_VPP_ADD_ROUTE_GW = 'vppctl ip route add {route.ip_address}/32 via {route.gateway} {route.interface.vppinterface.device_name}'
-CMD_VPP_DEL_ROUTE_GW = 'vppctl ip route del {route.ip_address}/32 via {route.gateway} {route.interface.vppinterface.device_name}'
-
-##### VPP CICN PLUGIN #####
+# It is important to pass the mac address so that it does not get randomly
+# generated by VPP, preventing any reboot of VPP and recreation of commands
+CMD_VPP_CREATE_MEMIFACE = '''
+# Create vpp interface from shared_memory
+vppctl_wrapper create memif key {key} socket {vpp_interface.parent.path_unix_socket}{vpp_interface.parent.socket_name} hw-addr {vpp_interface.parent.mac_address} {master_slave}
+'''
+CMD_VPP_SET_IP = 'vppctl_wrapper set int ip address {device_name} {ip_address}/{prefix_len}'
+CMD_VPP_SET_UP = 'vppctl_wrapper set int state {netdevice.device_name} {state}'
-CMD_VPP_CICN_GET = "timeout 1 vppctl cicn show" #We timeout if vpp is not started
-CMD_VPP_ADD_ICN_ROUTE = 'vppctl cicn cfg fib add prefix {route.prefix} face {route.face.id}'
-CMD_VPP_ADD_ICN_FACE = 'vppctl cicn cfg face add local {face.src_ip}:{face.src_port} remote {face.dst_ip}:{face.dst_port}'
+##### VPP IP ROUTING #####
-CMD_VPP_CICN_GET_CACHE_SIZE = 'vppctl cicn show | grep "CS entries" | grep -Eo "[0-9]+"'
-CMD_VPP_CICN_SET_CACHE_SIZE = 'vppctl cicn control param cs size {self.cache_size}'
+CMD_VPP_ADD_ARP = 'vppctl_wrapper set ip arp static {route.interface.vppinterface.device_name} {route.ip_address} {route.mac_address}'
+CMD_VPP_DEL_ARP = 'vppctl_wrapper set ip arp del static {route.interface.vppinterface.device_name} {route.ip_address} {route.mac_address}'
+CMD_VPP_ADD_ROUTE = 'vppctl_wrapper ip route add {route.ip_address}/{route.ip_address.prefix_len} via {route.interface.vppinterface.device_name}'
+CMD_VPP_DEL_ROUTE = 'vppctl_wrapper ip route del {route.ip_address}/{route.ip_address.prefix_len} via {route.interface.vppinterface.device_name}'
+CMD_VPP_ADD_ROUTE_GW = 'vppctl_wrapper ip route add {route.ip_address}/{route.ip_address.prefix_len} via {route.gateway} {route.interface.vppinterface.device_name}'
+CMD_VPP_DEL_ROUTE_GW = 'vppctl_wrapper ip route del {route.ip_address}/{route.ip_address.prefix_len} via {route.gateway} {route.interface.vppinterface.device_name}'
diff --git a/vicn/resource/vpp/vpp_host.py b/vicn/resource/vpp/vpp_host.py
index 954d1d32..29094451 100644
--- a/vicn/resource/vpp/vpp_host.py
+++ b/vicn/resource/vpp/vpp_host.py
@@ -23,6 +23,7 @@ from vicn.core.attribute import Attribute, Multiplicity
from vicn.core.exception import ResourceNotFound
from vicn.core.requirement import Requirement
from vicn.core.task import BashTask, task, EmptyTask
+from vicn.core.task import inherit_parent
from vicn.resource.linux.application import LinuxApplication
from vicn.resource.linux.file import TextFile
from vicn.resource.node import Node
@@ -41,7 +42,7 @@ CMD_APP_ARMOR_RELOAD = '''
CMD_SYSCTL_HUGEPAGES = 'sysctl -w vm.nr_hugepages={nb_hp}'
DEFAULT_NB_HUGEPAGES = 1024
CMD_GREP_UIO_DEV = 'ls /dev | grep uio'
-CMD_CREATE_UIO_DEVICES = "dpdk_nic_bind --bind=igb_uio {pci_address}"
+CMD_CREATE_UIO_DEVICES = "dpdk-devbind --bind=igb_uio {pci_address}"
class VPPHost(LinuxApplication):
"""
@@ -75,7 +76,7 @@ class VPPHost(LinuxApplication):
description = 'Dpdk devices on the node',
multiplicity = Multiplicity.OneToMany)
- __package_names__ = ['dpdk', 'vpp-dpdk-dkms']
+ __package_names__ = ['vpp-dpdk-dkms', 'vpp-dpdk-dev']
#--------------------------------------------------------------------------
# Constructor and Accessors
@@ -89,6 +90,7 @@ class VPPHost(LinuxApplication):
# Resource lifecycle
#--------------------------------------------------------------------------
+ @inherit_parent
def __subresources__(self):
app_armor_file = TextFile(node = self.node,
filename = FN_APPARMOR_DPDK_SCRIPT,
@@ -100,6 +102,7 @@ class VPPHost(LinuxApplication):
overwrite = True)
return app_armor_file | startup_conf
+ @inherit_parent
@task
def __get__(self):
"""
@@ -108,6 +111,7 @@ class VPPHost(LinuxApplication):
"""
raise ResourceNotFound
+ @inherit_parent
def __create__(self):
modules = BashTask(self.node, CMD_INSERT_MODULES)
app_armor_reload = BashTask(self.node, CMD_APP_ARMOR_RELOAD)
@@ -132,8 +136,6 @@ class VPPHost(LinuxApplication):
return ((modules | app_armor_reload) | sysctl_hugepages) > \
(disable_vpp > create_uio)
- __delete__ = None
-
#--------------------------------------------------------------------------
# Attributes
#--------------------------------------------------------------------------