From 3e6678f9c692553e8902da4d6fb1fe6c087db1f4 Mon Sep 17 00:00:00 2001 From: Marcel Enguehard Date: Wed, 19 Jul 2017 11:26:26 +0200 Subject: * GUI resource * MemIf interface for VPP * Better netmodel integration * Draft documentation * New tutorials * Improved monitoring and error handling * Refactored IP addresses and prefixes representation * Improved image mgmt for LXD * Various bugfixes and code refactoring Change-Id: I90da6cf7b5716bc7deb6bf4e24d3f9f01b5a9b0f Signed-off-by: Marcel Enguehard --- vicn/resource/vpp/cicn.py | 138 ------------------------------------ vicn/resource/vpp/dpdk_device.py | 2 +- vicn/resource/vpp/interface.py | 103 +++++++++++++++++++++++---- vicn/resource/vpp/memif_device.py | 77 ++++++++++++++++++++ vicn/resource/vpp/memif_link.py | 144 ++++++++++++++++++++++++++++++++++++++ vicn/resource/vpp/scripts.py | 2 +- vicn/resource/vpp/vpp.py | 96 ++++++++++++++----------- vicn/resource/vpp/vpp_bridge.py | 7 +- vicn/resource/vpp/vpp_commands.py | 50 +++++++------ vicn/resource/vpp/vpp_host.py | 10 +-- 10 files changed, 410 insertions(+), 219 deletions(-) delete mode 100644 vicn/resource/vpp/cicn.py create mode 100644 vicn/resource/vpp/memif_device.py create mode 100644 vicn/resource/vpp/memif_link.py (limited to 'vicn/resource/vpp') diff --git a/vicn/resource/vpp/cicn.py b/vicn/resource/vpp/cicn.py deleted file mode 100644 index 1a68f11f..00000000 --- a/vicn/resource/vpp/cicn.py +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# Copyright (c) 2017 Cisco and/or its affiliates. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at: -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -import re - -from netmodel.model.type import Integer, Bool -from vicn.core.attribute import Attribute -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.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 - -_ADD_FACE_RETURN_FORMAT = "Face ID: [0-9]+" - -def check_face_id_return_format(data): - prog = re.compile(_ADD_FACE_RETURN_FORMAT) - return prog.match(data) - -def parse_face_id(data): - return data.partition(':')[2] - -class CICNForwarder(Forwarder): - """ - NOTE: Based on the Vagrantfile, we recommend a node with mem=4096, cpu=4 - """ - - node = Attribute(Node, - mandatory=True, - requirements = [Requirement('vpp')], - reverse_name='cicn') - numa_node = Attribute(Integer, - description = 'Numa node on which vpp will run', - default = None) - core = Attribute(Integer, - description = 'Core belonging the numa node on which vpp will run', - default = None) - enable_worker = Attribute(Bool, - description = 'Enable one worker for packet processing', - default = False) - - #__packages__ = ['vpp-plugin-cicn'] - - def __after__(self): - return ['CentralICN'] - - def __get__(self): - def parse(rv): - if rv.return_value > 0 or 'cicn: not enabled' in rv.stdout: - raise ResourceNotFound - return BashTask(self.node, CMD_VPP_CICN_GET, - lock = self.node.vpp.vppctl_lock, parse=parse) - - def __create__(self): - - #self.node.vpp.plugins.append("cicn") - lock = self.node.vpp.vppctl_lock - create_task = BashTask(self.node, CMD_VPP_ENABLE_PLUGIN, - {'plugin' : 'cicn'}, lock = lock) - - face_task = EmptyTask() - route_task = EmptyTask() - - def parse_face(rv, face): - if check_face_id_return_format(rv.stdout): - face.id = parse_face_id(rv.stdout) - return {} - - for face in self.faces: - face_task = face_task > BashTask(self.node, CMD_VPP_ADD_ICN_FACE, - {'face':face}, - parse = (lambda x : parse_face(x, face)), lock = lock) - - if not self.routes: - from vicn.resource.icn.route import Route - for route in self._state.manager.by_type(Route): - if route.node is self.node: - self.routes.append(route) - for route in self.routes: - route_task = route_task > BashTask(self.node, - CMD_VPP_ADD_ICN_ROUTE, {'route' : route}, lock = lock) - - return (wait_resource_task(self.node.vpp) > create_task) > (face_task > route_task) - - # Nothing to do - __delete__ = None - - #-------------------------------------------------------------------------- - # Attributes - #-------------------------------------------------------------------------- - - # Force local update - - _add_faces = None - _remove_faces = None - _get_faces = None - _set_faces = None - - _add_routes = None - _remove_routes = None - _get_routes = None - _set_routes = None - - #-------------------------------------------------------------------------- - # Internal methods - #-------------------------------------------------------------------------- - - def _set_cache_size(self): - return BashTask(self.node, CMD_VPP_CICN_SET_CACHE_SIZE, {'self': self}, - lock = self.node.vpp.vppctl_lock) - - def _get_cache_size(self): - def parse(rv): - return int(rv.stdout) - return BashTask(self.node, CMD_VPP_CICN_GET_CACHE_SIZE, parse=parse, - lock = self.node.vpp.vppctl_lock) 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 #-------------------------------------------------------------------------- -- cgit 1.2.3-korg