From 3c7c2275b2d4660b83db9495c5f6ece5c6557b43 Mon Sep 17 00:00:00 2001 From: Jordan Augé Date: Sat, 25 Mar 2017 02:00:42 +0100 Subject: Misc. improvements to vICN codebase detailed below. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - vICN core . Added python setup script (allowing package installation) . Better error handling - Resources . LXD : better handling of certificate generation . Physical : generation of SSH keypair within vICN . Link : code simplification . EmulatedLteChannel: fixed typo in netmask configuration of emu-radio (missing /) - Examples . Added json file for tutorial #2 - Dumbell . New tutorial #03 - Load balancing in WiFi/LTE hetnet - Other minor changes incl. code cleanup (trailing spaces, etc.) Change-Id: Id306ca71e27d9859aa72760f63a2bc364bfe8159 Signed-off-by: Jordan Augé --- MANIFEST.in | 2 + VERSION | 1 + bootstrap.sh | 3 +- emu-radio/ns3-patch/README.md | 1 - examples/tutorial/tutorial02-dumbell.json | 318 ++++++++++++++++++++++++++++++ examples/tutorial/tutorial03-hetnet.json | 117 +++++++++++ netmon/bin/netmon.py | 2 +- setup.py | 75 +++++++ vicn/core/resource_mgr.py | 62 +++--- vicn/core/state.py | 2 + vicn/core/task.py | 38 +++- vicn/helpers/__init__.py | 0 vicn/resource/central.py | 91 ++++----- vicn/resource/linux/certificate.py | 9 +- vicn/resource/linux/keypair.py | 71 +++++++ vicn/resource/linux/link.py | 75 ++----- vicn/resource/linux/net_device.py | 3 + vicn/resource/linux/physical.py | 40 ++-- vicn/resource/lxd/lxd_hypervisor.py | 12 +- vicn/resource/node.py | 8 +- vicn/resource/ns3/emulated_channel.py | 12 +- vicn/resource/ns3/emulated_lte_channel.py | 10 +- 22 files changed, 780 insertions(+), 172 deletions(-) create mode 100644 MANIFEST.in create mode 100644 VERSION create mode 100644 examples/tutorial/tutorial02-dumbell.json create mode 100644 examples/tutorial/tutorial03-hetnet.json create mode 100755 setup.py create mode 100644 vicn/helpers/__init__.py create mode 100644 vicn/resource/linux/keypair.py diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..a17c59a6 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include README.md +include VERSION diff --git a/VERSION b/VERSION new file mode 100644 index 00000000..49d59571 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.1 diff --git a/bootstrap.sh b/bootstrap.sh index 6618f570..3ba8abcb 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -1,4 +1,3 @@ #!/bin/bash -ssh-keygen -t rsa -N "" -f config/ssh_client_cert/ssh_client_key - +mkdir -p ~/.vicn/ssh_client_cert/ && ssh-keygen -t rsa -N "" -f ~/.vicn/ssh_client_cert/ssh_client_key diff --git a/emu-radio/ns3-patch/README.md b/emu-radio/ns3-patch/README.md index ac3b244d..4547f50e 100644 --- a/emu-radio/ns3-patch/README.md +++ b/emu-radio/ns3-patch/README.md @@ -19,4 +19,3 @@ minstrel rate adaptation, RSSI based wifi handover(missing), block ack agreement you just need to replace the original files in ns3/src/wifi with the ones that are patched, to do so: * cd ns3-patch * cp -r wifi/ path/to/ns3/src/wifi - diff --git a/examples/tutorial/tutorial02-dumbell.json b/examples/tutorial/tutorial02-dumbell.json new file mode 100644 index 00000000..17a4e710 --- /dev/null +++ b/examples/tutorial/tutorial02-dumbell.json @@ -0,0 +1,318 @@ +{ + "resources": [ + { + "type": "Physical", + "name": "server", + "hostname": "localhost", + "managed": true + }, + { + "type": "NetDevice", + "device_name": "enp0s3", + "node": "server", + "ip_address": "10.0.2.15", + "managed": false + }, + { + "type": "LxcImage", + "name": "ubuntu1604-cicnsuite-rc1", + "node": "server", + "managed": false + }, + { + "type": "LxcContainer", + "node": "server", + "name": "bridge1", + "image": "ubuntu1604-cicnsuite-rc1" + }, + { + "type": "VPP", + "node": "bridge1", + "name": "bridge1-vpp1" + }, + { + "type": "DpdkDevice", + "node": "bridge1", + "device_name": "GigabitEthernet0/8/0", + "pci_address" : "0000:00:08.0", + "ip_address" : "172.17.1.20", + "mac_address": "08:00:27:b8:f3:a3", + "name": "bridge1-dpdk1" + }, + { + "type": "LxcContainer", + "node": "server", + "name": "core1", + "image": "ubuntu1604-cicnsuite-rc1" + }, + { + "type": "VPP", + "node": "core1", + "name": "core1-vpp" + }, + { + "type": "DpdkDevice", + "node": "core1", + "device_name": "GigabitEthernet0/9/0", + "pci_address": "0000:00:09.0", + "ip_address" : "172.17.1.21", + "mac_address": "08:00:27:d1:b5:d1", + "name": "core1-dpdk1" + }, + { + "type": "VPPInterface", + "name": "core1-vppdpdk1", + "vpp": "core1-vpp", + "node": "core1", + "ip_address": "172.17.1.21", + "parent": "core1-dpdk1" + }, + { + "type": "DpdkDevice", + "node": "core1", + "device_name": "GigabitEthernet0/a/0", + "pci_address": "0000:00:0a.0", + "ip_address" : "172.17.2.21", + "mac_address": "08:00:27:d1:b5:c1", + "name": "core1-dpdk2" + }, + { + "type": "VPPInterface", + "name": "core1-vppdpdk2", + "vpp": "core1-vpp", + "node": "core1", + "ip_address": "172.17.2.21", + "parent": "core1-dpdk2" + }, + { + "type": "CICNForwarder", + "node": "core1", + "name": "core1-fwd" + }, + { + "type": "LxcContainer", + "node": "server", + "name": "core2", + "image": "ubuntu1604-cicnsuite-rc1" + }, + { + "type": "VPP", + "node": "core2", + "name": "core2-vpp" + }, + { + "type": "DpdkDevice", + "node": "core2", + "device_name": "GigabitEthernet0/10/0", + "pci_address": "0000:00:10.0", + "ip_address" : "172.17.2.22", + "mac_address": "08:00:27:96:e1:dc", + "name": "core2-dpdk1" + }, + { + "type": "VPPInterface", + "name": "core2-vppdpdk1", + "vpp": "core2-vpp", + "node": "core2", + "ip_address": "172.17.2.22", + "parent": "core2-dpdk1" + }, + { + "type": "DpdkDevice", + "node": "core2", + "device_name": "GigabitEthernet0/11/0", + "pci_address": "0000:00:11.0", + "ip_address" : "172.17.3.22", + "mac_address": "08:00:27:d3:9e:d6", + "name": "core2-dpdk2" + }, + { + "type": "VPPInterface", + "name": "core2-vppdpdk2", + "vpp": "core2-vpp", + "node": "core2", + "ip_address": "172.17.3.22", + "parent": "core2-dpdk2" + }, + { + "type": "CICNForwarder", + "node": "core2", + "name": "core2-fwd" + }, + { + "type": "LxcContainer", + "node": "server", + "name": "bridge2", + "image": "ubuntu1604-cicnsuite-rc1" + }, + { + "type": "VPP", + "node": "bridge2", + "name": "bridge2-vpp1" + }, + { + "type": "DpdkDevice", + "node": "bridge2", + "device_name": "GigabitEthernet0/12/0", + "pci_address" : "0000:00:12.0", + "ip_address" : "172.17.3.23", + "mac_address": "08:00:27:f2:a8:d9", + "name": "bridge2-dpdk1" + }, + { + "type": "LxcContainer", + "node": "server", + "image": "ubuntu1604-cicnsuite-rc1", + "name": "cons1" + }, + { + "type": "MetisForwarder", + "node": "cons1", + "log_file": "/root/log.txt", + "cache_size": 0 + }, + { + "type": "LxcContainer", + "node": "server", + "image": "ubuntu1604-cicnsuite-rc1", + "name": "cons2" + }, + { + "type": "MetisForwarder", + "node": "cons2", + "log_file": "/root/log.txt", + "cache_size": 0 + }, + { + "type": "LxcContainer", + "node": "server", + "image": "ubuntu1604-cicnsuite-rc1", + "name": "cons3" + }, + { + "type": "MetisForwarder", + "node": "cons3", + "log_file": "/root/log.txt", + "cache_size": 0 + }, + { + "type": "LxcContainer", + "node": "server", + "image": "ubuntu1604-cicnsuite-rc1", + "name": "cons4" + }, + { + "type": "MetisForwarder", + "node": "cons4", + "log_file": "/root/log.txt", + "cache_size": 0 + }, + { + "type": "LxcContainer", + "node": "server", + "image": "ubuntu1604-cicnsuite-rc1", + "name": "cons5" + }, + { + "type": "MetisForwarder", + "node": "cons5", + "log_file": "/root/log.txt", + "cache_size": 0 + }, + { + "type": "LxcContainer", + "node": "server", + "image": "ubuntu1604-cicnsuite-rc1", + "name": "prod1" + }, + { + "type": "MetisForwarder", + "node": "prod1", + "log_file": "/root/log.txt", + "cache_size": 0 + }, + { + "type": "LxcContainer", + "node": "server", + "image": "ubuntu1604-cicnsuite-rc1", + "name": "prod2" + }, + { + "type": "MetisForwarder", + "node": "prod2", + "log_file": "/root/log.txt", + "cache_size": 0 + }, + { + "type": "LxcContainer", + "node": "server", + "image": "ubuntu1604-cicnsuite-rc1", + "name": "prod3" + }, + { + "type": "MetisForwarder", + "node": "prod3", + "log_file": "/root/log.txt", + "cache_size": 0 + }, + { + "type": "LxcContainer", + "node": "server", + "image": "ubuntu1604-cicnsuite-rc1", + "name": "prod4" + }, + { + "type": "MetisForwarder", + "node": "prod4", + "log_file": "/root/log.txt", + "cache_size": 0 + }, + { + "type": "LxcContainer", + "node": "server", + "image": "ubuntu1604-cicnsuite-rc1", + "name": "prod5" + }, + { + "type": "MetisForwarder", + "node": "prod5", + "log_file": "/root/log.txt", + "cache_size": 0 + }, + { + "type": "VPPBridge", + "connected_nodes": ["cons1","cons2","cons3","cons4","cons5"], + "interfaces": ["core1-dpdk1"], + "node": "bridge1" + }, + { + "type": "PhyLink", + "src": "core1-dpdk2", + "dst": "core2-dpdk1" + }, + { + "type": "VPPBridge", + "connected_nodes": ["prod1","prod2","prod3","prod4","prod5"], + "interfaces": ["core2-dpdk2"], + "node": "bridge2" + }, + { + "type": "CcnxSimpleTrafficGenerator", + "prefix": "/ccnx1", + "consumers": ["cons1"], + "producers": ["prod1"] + }, + { + "type": "CentralIP", + "ip_routing_strategy" : "spt" + }, + { + "type" : "CentralICN", + "face_protocol": "udp4" + } + ], + "settings": { + "network": "192.168.133.0/24", + "ulimit-n": 10000 + } +} diff --git a/examples/tutorial/tutorial03-hetnet.json b/examples/tutorial/tutorial03-hetnet.json new file mode 100644 index 00000000..42d4292d --- /dev/null +++ b/examples/tutorial/tutorial03-hetnet.json @@ -0,0 +1,117 @@ +{ + "resources": [ + { + "type": "Physical", + "name": "server", + "hostname": "MY-SERVER" + }, + { + "type": "NetDevice", + "device_name": "br0", + "node": "server", + "managed": false + }, + { + "type": "LxcImage", + "name": "ubuntu1604-cicnsuite-rc1", + "node": "server" + }, + { + "type": "LxcContainer", + "image": "ubuntu1604-cicnsuite-rc1", + "name": "cons", + "node": "server", + "category": "tablet", + "x": 1, + "y": 2 + }, + { + "type": "LxcContainer", + "image": "ubuntu1604-cicnsuite-rc1", + "name": "wifi", + "node": "server", + "category": "wifi", + "x": 2, + "y": 1 + }, + { + "type": "LxcContainer", + "image": "ubuntu1604-cicnsuite-rc1", + "name": "lte", + "node": "server", + "category": "lte", + "x": 2, + "y": 3 + }, + { + "type": "LxcContainer", + "image": "ubuntu1604-cicnsuite-rc1", + "name": "prod", + "node": "server", + "category": "video-server", + "x": 3, + "y": 2 + }, + { + "type": "MetisForwarder", + "node": "cons" + }, + { + "type": "MetisForwarder", + "node": "wifi" + }, + { + "type": "MetisForwarder", + "node": "lte" + }, + { + "type": "MetisForwarder", + "node": "prod" + }, + { + "type": "WebServer", + "node": "prod", + "prefixes": [ + "/webserver" + ] + }, + { + "type": "Link", + "src_node": "wifi", + "dst_node": "prod" + }, + { + "type": "Link", + "src_node": "lte", + "dst_node": "prod" + }, + { + "type": "EmulatedWiFiChannel", + "name": "wch", + "node": "server", + "ap": "wifi", + "stations": ["cons"], + "control_port": 30001 + }, + { + "type": "EmulatedLteChannel", + "name": "lch", + "node": "server", + "ap": "lte", + "stations": ["cons"], + "control_port": 30002 + }, + { + "type": "CentralIP", + "ip_routing_strategy": "spt" + }, + { + "type": "CentralICN", + "icnip_routing_strategy": "spt", + "face_protocol": "udp4" + } + ], + "settings": { + "network": "192.168.2.0/24" + } +} diff --git a/netmon/bin/netmon.py b/netmon/bin/netmon.py index 65ef9f8d..46e6327e 100755 --- a/netmon/bin/netmon.py +++ b/netmon/bin/netmon.py @@ -24,7 +24,7 @@ import sys PATH=os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir) sys.path.insert(0, os.path.abspath(PATH)) -from netmodel.network.interfaces +import netmodel.network.interfaces from netmodel.network.router import Router from netmodel.model.query import Query, ACTION_SELECT, ACTION_SUBSCRIBE from netmodel.util.daemon import Daemon diff --git a/setup.py b/setup.py new file mode 100755 index 00000000..170833c6 --- /dev/null +++ b/setup.py @@ -0,0 +1,75 @@ +#!/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 os +from glob import glob +from platform import dist + +# XXX +from setuptools import find_packages, setup + +# Versions should comply with PEP440. For a discussion on single-sourcing +# the version across setup.py and the project code, see +# https://packaging.python.org/en/latest/single_source_version.html +with open(os.path.join(os.path.dirname(__file__), 'VERSION')) as version_file: + version = version_file.read().strip() + +# Like VERSION, this file is made available through MANIFEST.in +with open('README.md') as f: + long_description = f.read() + +# XXX TODO +required_modules = list() + +setup( + name = 'vICN', + version = version, + description = 'vICN experiment controller', + long_description = long_description, + license = 'Apache 2.0', + + download_url = 'https://gerrit.fd.io/r/cicn', + url = 'https://wiki.fd.io/view/Vicn', + + # See https://pypi.python.org/pypi?%3Aaction=list_classifiers + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Intended Audience :: Developers', + 'Intended Audience :: Science/Research', + 'Topic :: Software Development :: Build Tools', + 'Operating System :: POSIX :: Linux', + 'Operating System :: MacOS :: MacOS X', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + ], + keywords = 'Experiment Controller; Orchestrator; ICN; LXC; Containers', + platforms = "Linux, OSX", + packages = find_packages(), + + install_requires = required_modules, + + # To provide executable scripts, use entry points in preference to the + # "scripts" keyword. Entry points provide cross-platform support and allow + # pip to create the appropriate form of executable for the target platform. + entry_points = { + 'console_scripts': [ + 'vicn = vicn.bin.vicn:main', + ], + }, +) diff --git a/vicn/core/resource_mgr.py b/vicn/core/resource_mgr.py index f6082488..57dcafef 100644 --- a/vicn/core/resource_mgr.py +++ b/vicn/core/resource_mgr.py @@ -44,6 +44,8 @@ from vicn.core.task import EmptyTask, BashTask log = logging.getLogger(__name__) +# NOTE: Do not fully reinitialize a resource after a step fails since it will +# call initialize several times, and might created spurious resources. ENABLE_LXD_WORKAROUND = True # Monitoring queries @@ -148,6 +150,9 @@ class ResourceManager(metaclass=Singleton): # For debug self._committed = set() + self._num = 0 + self._num_clean = 0 + def terminate(self): self._router.terminate() @@ -317,6 +322,7 @@ class ResourceManager(metaclass=Singleton): Committing a resource creates an asyncio function implementing a state management automaton. """ + self._num += 1 asyncio.ensure_future(self._process_resource(resource)) def commit(self): @@ -496,10 +502,10 @@ class ResourceManager(metaclass=Singleton): # Task management #-------------------------------------------------------------------------- - def schedule(self, task): + def schedule(self, task, resource = None): if task is None or isinstance(task, EmptyTask): return - self._task_mgr.schedule(task) + self._task_mgr.schedule(task, resource) #-------------------------------------------------------------------------- # Asynchronous resource API @@ -785,8 +791,10 @@ class ResourceManager(metaclass=Singleton): self.attr_log(resource, attribute, 'Current state is {}'.format(state)) + # AttributeState.ERROR if resource._state.attr_change_success == False: - log.error('Attribute error') + log.error('Attribute error {} for resource {}'.format( + resource.get_uuid(), attribute.name)) e = resource._state.attr_change_value[attribute.name] import traceback; traceback.print_tb(e.__traceback__) raise NotImplementedError @@ -794,7 +802,7 @@ class ResourceManager(metaclass=Singleton): # Signal update errors to the parent resource resource._state.attr_change_event[attribute.name].set() - elif state == AttributeState.UNINITIALIZED: + if state == AttributeState.UNINITIALIZED: pending_state = AttributeState.PENDING_INIT elif state in AttributeState.INITIALIZED: pending_state = AttributeState.PENDING_UPDATE @@ -898,10 +906,10 @@ class ResourceManager(metaclass=Singleton): new_state = AttributeState.CLEAN else: - log.error('Attribute error') + log.error('Attribute error {} for resource {}'.format( + resource.get_uuid(), attribute.name)) e = resource._state.attr_change_value[attribute.name] - import traceback; traceback.print_tb(e.__traceback__) - raise NotImplementedError + new_state = AttributeState.ERROR else: raise RuntimeError @@ -1046,13 +1054,20 @@ class ResourceManager(metaclass=Singleton): It is important to centralize state change since some states are associated with Events(). """ + prev_state = resource._state.state resource._state.state = state if state == ResourceState.CLEAN: # Monitoring hook self._monitor(resource) resource._state.clean.set() + if prev_state != ResourceState.CLEAN: + self._num_clean += 1 + log.info("Resource {} is marked as CLEAN ({}/{})".format( + resource.get_uuid(), self._num_clean, self._num)) else: resource._state.clean.clear() + if prev_state == ResourceState.CLEAN: + self._num_clean -= 1 if state == ResourceState.INITIALIZED: resource._state.init.set() @@ -1211,12 +1226,7 @@ class ResourceManager(metaclass=Singleton): state = resource._state.state self.log(resource, 'Current state is {}'.format(state)) - if resource._state.change_success == False: - e = resource._state.change_value - import traceback; traceback.print_tb(e.__traceback__) - raise NotImplementedError - - elif state == ResourceState.UNINITIALIZED: + if state == ResourceState.UNINITIALIZED: pending_state = ResourceState.PENDING_DEPS elif state == ResourceState.DEPS_OK: pending_state = ResourceState.PENDING_INIT @@ -1296,9 +1306,10 @@ class ResourceManager(metaclass=Singleton): raise RuntimeError if task is not None and not isinstance(task, EmptyTask): + resource._state.change_success = None # undetermined state state_change = functools.partial(self._trigger_state_change, resource) task.add_done_callback(state_change) - self.schedule(task) + self.schedule(task, resource) self.log(resource, 'Trigger {} -> {}. Waiting task completion'.format( state, pending_state)) @@ -1321,8 +1332,8 @@ class ResourceManager(metaclass=Singleton): new_state = ResourceState.INITIALIZED else: e = resource._state.change_value - import traceback; traceback.print_tb(e.__traceback__) - raise NotImplementedError + log.error('Cannot setup resource {} : {}'.format( + resource.get_uuid(), e)) elif pending_state == ResourceState.PENDING_GET: if resource._state.change_success == True: @@ -1339,13 +1350,13 @@ class ResourceManager(metaclass=Singleton): # does not exists. anyways the bug should only occur # with container.execute(), not container.get() log.error('LXD Fix (not found). Reset resource') - new_state = ResourceState.UNINITIALIZED + new_state = ResourceState.INITIALIZED elif ENABLE_LXD_WORKAROUND and isinstance(e, LXDAPIException): # "not found" is the normal exception when the container # does not exists. anyways the bug should only occur # with container.execute(), not container.get() log.error('LXD Fix (API error). Reset resource') - new_state = ResourceState.UNINITIALIZED + new_state = ResourceState.INITIALIZED elif isinstance(e, ResourceNotFound): # The resource does not exist self.log(resource, S_GET_DONE.format( @@ -1354,8 +1365,9 @@ class ResourceManager(metaclass=Singleton): resource._state.change_value = None else: e = resource._state.change_value - import traceback; traceback.print_tb(e.__traceback__) - raise NotImplementedError + log.error('Cannot get resource state {} : {}'.format( + resource.get_uuid(), e)) + new_state = ResourceState.ERROR resource._state.change_success = True elif pending_state == ResourceState.PENDING_KEYS: @@ -1372,8 +1384,8 @@ class ResourceManager(metaclass=Singleton): resource._state.change_success = True else: e = resource._state.change_value - import traceback; traceback.print_tb(e.__traceback__) - raise NotImplementedError + log.error('Cannot create resource {} : {}'.format( + resource.get_uuid(), e)) elif pending_state == ResourceState.PENDING_CREATE: if resource._state.change_success == True: @@ -1386,19 +1398,19 @@ class ResourceManager(metaclass=Singleton): if ENABLE_LXD_WORKAROUND and isinstance(e, LxdNotFound): log.error('LXD Fix (not found). Reset resource') - new_state = ResourceState.UNINITIALIZED + new_state = ResourceState.INITIALIZED resource._state.change_success = True elif ENABLE_LXD_WORKAROUND and \ isinstance(e, LXDAPIException): log.error('LXD Fix (API error). Reset resource') - new_state = ResourceState.UNINITIALIZED + new_state = ResourceState.INITIALIZED resource._state.change_success = True elif 'File exists' in str(e): new_state = ResourceState.CREATED resource._state.change_success = True elif 'dpkg --configure -a' in str(e): resource._dpkg_configure_a = True - new_state = ResourceState.UNINITIALIZED + new_state = ResourceState.INITIALIZED resource._state.change_success = True else: self.log(resource, 'CREATE failed: {}'.format(e)) diff --git a/vicn/core/state.py b/vicn/core/state.py index d5069b24..c32b8237 100644 --- a/vicn/core/state.py +++ b/vicn/core/state.py @@ -46,6 +46,7 @@ class ResourceState: PENDING_UPDATE = 'PENDING_UPDATE' PENDING_DELETE = 'PENDING_DELETE' DELETED = 'DELETED' + ERROR = 'ERROR' class AttributeState: UNINITIALIZED = 'UNINITIALIZED' @@ -54,6 +55,7 @@ class AttributeState: PENDING_INIT = 'PENDING_INIT' PENDING_UPDATE = 'PENDING_UPDATE' CLEAN = 'CLEAN' + ERROR = 'ERROR' class Operations: SET = 'set' diff --git a/vicn/core/task.py b/vicn/core/task.py index 2e9bc275..5aecb40b 100644 --- a/vicn/core/task.py +++ b/vicn/core/task.py @@ -34,7 +34,7 @@ log = logging.getLogger(__name__) EXECUTOR=concurrent.futures.ThreadPoolExecutor #EXECUTOR=concurrent.futures.ProcessPoolExecutor -MAX_WORKERS=50 # None +MAX_WORKERS = 50 # None class BaseTask: """Base class for all tasks @@ -171,6 +171,27 @@ def get_attributes_task(resource, attribute_names): return {attribute_name: ret} return func() +def _get_func_desc(f): + """ + Returns a string representation of a function for logging purposes. + + Todo: args and keywords (including from partial) + """ + partial = isinstance(f, functools.partial) + if partial: + f = f.func + + s = '' + if hasattr(f, '__name__'): + s += f.__name__ + if hasattr(f, '__doc__') and f.__doc__: + if s: + s += ' : ' + s += f.__doc__ + + return 'partial<{}>'.format(s) if partial else s + + class PythonTask(Task): def __init__(self, func, *args, **kwargs): super().__init__() @@ -197,8 +218,8 @@ class PythonTask(Task): fut.add_done_callback(self._done_callback) def __repr__(self): - return ''.format(self._func, self._args, - self._kwargs) + s = _get_func_desc(self._func) + return ''.format(s) if s else '' class PythonAsyncTask(PythonTask): async def execute(self, *args, **kwargs): @@ -213,7 +234,8 @@ class PythonAsyncTask(PythonTask): fut.add_done_callback(self._done_callback) def __repr__(self): - return '' + s = _get_func_desc(self._func) + return ''.format(s) if s else '' class PythonInlineTask(PythonTask): async def execute(self, *args, **kwargs): @@ -229,6 +251,10 @@ class PythonInlineTask(PythonTask): self._future.set_exception(e) return self._future + def __repr__(self): + s = _get_func_desc(self._func) + return ''.format(s) if s else '' + class BashTask(Task): def __init__(self, node, cmd, parameters=None, parse=None, as_root=False, output=False, pre=None, post=None, lock=None): @@ -339,12 +365,14 @@ class TaskManager: loop = asyncio.get_event_loop() loop.set_default_executor(executor) - def schedule(self, task): + def schedule(self, task, resource = None): """All instances of BaseTask can be scheduled Here we might decide to do more advanced scheduling, like merging bash tasks, etc. thanks to the task algebra. """ + uuid = resource.get_uuid() if resource else '(unknown)' + log.info('Scheduling task {} for resource {}'.format(task, uuid)) asyncio.ensure_future(task.execute()) @task diff --git a/vicn/helpers/__init__.py b/vicn/helpers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/vicn/resource/central.py b/vicn/resource/central.py index 73a43478..6f3c680b 100644 --- a/vicn/resource/central.py +++ b/vicn/resource/central.py @@ -16,8 +16,9 @@ # limitations under the License. # -import networkx as nx import logging +import networkx as nx +import os from netmodel.model.type import String from netmodel.util.misc import pairwise @@ -54,7 +55,7 @@ CMD_NAT = '\n'.join([ # For containers CMD_IP_FORWARD = 'echo 1 > /proc/sys/net/ipv4/ip_forward' -HOST_FILE_PATH = "/etc/hosts.vicn" +HOST_FILE = "hosts.vicn" #------------------------------------------------------------------------------ # Routing strategies @@ -162,29 +163,29 @@ def _get_l2_graph(manager, with_managed = False): if G.has_edge(src.node._state.uuid, dst.node._state.uuid): continue - map_node_interface = { src.node._state.uuid : src._state.uuid, + map_node_interface = { src.node._state.uuid : src._state.uuid, dst.node._state.uuid: dst._state.uuid} - G.add_edge(src.node._state.uuid, dst.node._state.uuid, + G.add_edge(src.node._state.uuid, dst.node._state.uuid, map_node_interface = map_node_interface) else: # This is for a normal Channel for src_it in range(0,len(channel.interfaces)): src = channel.interfaces[src_it] - # Iterate over the remaining interface to create all the + # 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 + + if not with_managed and (not src.managed or not dst.managed): continue if G.has_edge(src.node._state.uuid, dst.node._state.uuid): continue - map_node_interface = { - src.node._state.uuid : src._state.uuid, + map_node_interface = { + src.node._state.uuid : src._state.uuid, dst.node._state.uuid: dst._state.uuid} - G.add_edge(src.node._state.uuid, dst.node._state.uuid, + G.add_edge(src.node._state.uuid, dst.node._state.uuid, map_node_interface = map_node_interface) return G @@ -198,9 +199,9 @@ def _get_icn_graph(manager): 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, + 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, + G.add_edge(node._state.uuid, other_node._state.uuid, map_node_face = map_node_face) return G @@ -224,7 +225,9 @@ class IPAssignment(Resource): raise ResourceNotFound def __subresources__(self): - self.host_file = TextFile(node = None, filename = HOST_FILE_PATH, + basedir = os.path.dirname(self._state.manager._base) + self.host_file = TextFile(node = None, + filename = os.path.join(basedir, HOST_FILE), overwrite = True) return self.host_file @@ -241,13 +244,13 @@ class IPAssignment(Resource): # We sort nodes by names for IP assignment. This code ensures that # interfaces on the same channel get consecutive IP addresses. That # way, we can assign /31 on p2p channels. - channels = sorted(self._state.manager.by_type(Channel), + channels = sorted(self._state.manager.by_type(Channel), key = lambda x : x.get_sortable_name()) - channels.extend(sorted(self._state.manager.by_type(Node), + channels.extend(sorted(self._state.manager.by_type(Node), key = lambda node : node.name)) host_file_content = "" - + # Dummy code to start IP addressing on an even number for /31 ip = AddressManager().get_ip(None) if int(ip[-1]) % 2 == 0: @@ -256,7 +259,7 @@ class IPAssignment(Resource): for channel in channels: # Sort interfaces in a deterministic order to ensure consistent # addressing across restarts of the tool - interfaces = sorted(channel.interfaces, + interfaces = sorted(channel.interfaces, key = lambda x : x.device_name) for interface in interfaces: @@ -279,7 +282,7 @@ class IPAssignment(Resource): host_file_content += '# {} {} {}\n'.format( interface.node.name, interface.device_name, ip) if interface == interface.node.host_interface: - host_file_content += '{} {}\n'.format(ip, + host_file_content += '{} {}\n'.format(ip, interface.node.name) self.host_file.content = host_file_content @@ -339,7 +342,7 @@ class IPRoutes(Resource): G = _get_l2_graph(self._state.manager) origins = self._get_ip_origins() - + # node -> list(origins for which we have routes) ip_routes = dict() @@ -347,7 +350,7 @@ class IPRoutes(Resource): 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] @@ -356,7 +359,7 @@ class IPRoutes(Resource): src_node = self._state.manager.by_uuid(src) mac_addr = None - if ((hasattr(next_hop_ingress, 'vpp') and + 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 @@ -366,15 +369,15 @@ class IPRoutes(Resource): ip_routes[src_node] = list() if prefix in ip_routes[src_node]: continue - + if prefix == next_hop_ingress.ip_address: # Direct route on src_node.name : # route add [prefix] dev [next_hop_interface_.device_name] - route = IPRoute(node = src_node, + route = IPRoute(node = src_node, managed = False, owner = self, ip_address = prefix, - mac_address = mac_addr, + mac_address = mac_addr, interface = next_hop_interface) else: # We need to be sure we have a route to the gw from the node @@ -387,11 +390,11 @@ class IPRoutes(Resource): interface = next_hop_interface) ip_routes[src_node].append(next_hop_ingress.ip_address) pre_routes.append(pre_route) - - # Route on src_node.name: + + # 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, + route = IPRoute(node = src_node, managed = False, owner = self, ip_address = prefix, @@ -407,7 +410,7 @@ class IPRoutes(Resource): IP routing strategy : direct routes only """ routes = list() - G = _get_l2_graph(self._state.manager) + G = _get_l2_graph(self._state.manager) 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) @@ -423,7 +426,7 @@ class IPRoutes(Resource): dst_node.name, dst.device_name, dst.ip_address, src_node.name, src.device_name, src.ip_address)) - route = IPRoute(node = src_node, + route = IPRoute(node = src_node, managed = False, owner = self, ip_address = dst.ip_address, @@ -431,7 +434,7 @@ class IPRoutes(Resource): interface = src) routes.append(route) - route = IPRoute(node = dst_node, + route = IPRoute(node = dst_node, managed = False, owner = self, ip_address = src.ip_address, @@ -498,14 +501,14 @@ class ICNFaces(Resource): owner = self, protocol = protocol, src_nic = src, - dst_mac = dst.mac_address) + 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, + elif protocol in (FaceProtocol.tcp4, FaceProtocol.tcp6, FaceProtocol.udp4, FaceProtocol.udp6): src_face = L4Face(node = src_node, owner = self, @@ -513,14 +516,14 @@ class ICNFaces(Resource): src_ip = src.ip_address, dst_ip = dst.ip_address, src_port = TMP_DEFAULT_PORT, - dst_port = TMP_DEFAULT_PORT) + dst_port = TMP_DEFAULT_PORT) dst_face = L4Face(node = dst_node, owner = self, protocol = protocol, src_ip = dst.ip_address, dst_ip = src.ip_address, src_port = TMP_DEFAULT_PORT, - dst_port = TMP_DEFAULT_PORT) + dst_port = TMP_DEFAULT_PORT) else: raise NotImplementedError @@ -583,7 +586,7 @@ class ICNRoutes(Resource): 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] @@ -675,9 +678,9 @@ class ContainerSetup(Resource): route_gw.node.routing_table.routes << route_gw # c) dns - dns_server_entry = DnsServerEntry(node = self.container, + dns_server_entry = DnsServerEntry(node = self.container, owner = self, - ip_address = self.container.node.bridge.ip_address, + ip_address = self.container.node.bridge.ip_address, interface_name = self.container.host_interface.device_name) return dns_server_entry @@ -710,7 +713,7 @@ class ContainersSetup(Resource): if len(containers) == 0: return None - container_resources = [ContainerSetup(owner = self, container = c) + container_resources = [ContainerSetup(owner = self, container = c) for c in containers] return Resource.__concurrent__(*container_resources) @@ -737,7 +740,7 @@ class CentralIP(Resource): def __subresources__(self): ip_assign = IPAssignment(owner=self) containers_setup = ContainersSetup(owner=self) - ip_routes = IPRoutes(owner = self, + ip_routes = IPRoutes(owner = self, routing_strategy = self.ip_routing_strategy) return ip_assign > (ip_routes | containers_setup) @@ -758,10 +761,10 @@ class CentralICN(Resource): """ # Choices: spt, max_flow - icn_routing_strategy = Attribute(String, + icn_routing_strategy = Attribute(String, description = 'ICN routing strategy', - default = 'spt') - face_protocol = Attribute(String, + default = 'spt') + face_protocol = Attribute(String, description = 'Protocol used to create faces', default = 'ether') @@ -777,8 +780,8 @@ class CentralICN(Resource): return ('CentralIP',) def __subresources__(self): - icn_faces = ICNFaces(owner = self, protocol_name = self.face_protocol) - icn_routes = ICNRoutes(owner = self, + icn_faces = ICNFaces(owner = self, protocol_name = self.face_protocol) + icn_routes = ICNRoutes(owner = self, routing_strategy = self.icn_routing_strategy) return icn_faces > icn_routes diff --git a/vicn/resource/linux/certificate.py b/vicn/resource/linux/certificate.py index e8750dff..7f9b8a74 100644 --- a/vicn/resource/linux/certificate.py +++ b/vicn/resource/linux/certificate.py @@ -31,6 +31,8 @@ DEFAULT_SUBJECT = '/CN=www.cisco.com/L=Paris/O=Cisco/C=FR' CMD_CREATE='\n'.join([ '# Generate a new certificate', + 'mkdir -p $(dirname {self.key})', + 'mkdir -p $(dirname {self.cert})', 'openssl req -x509 -newkey rsa:' + DEFAULT_RSA_LENGTH + ' -keyout {self.key} -out {self.cert} -subj ' + DEFAULT_SUBJECT + ' -nodes' ]) @@ -40,9 +42,6 @@ class Certificate(Resource): Resource: Certificate Implements a SSL certificate. - - Todo: - - ideally, this should be implemented as a pair of tightly coupled files. """ node = Attribute(Node, description = 'Node on which the certificate is created', @@ -53,6 +52,10 @@ class Certificate(Resource): key = Attribute(String, description = 'Key path', mandatory = True) + #-------------------------------------------------------------------------- + # Resource lifecycle + #-------------------------------------------------------------------------- + @inline_task def __initialize__(self): self._cert_file = File(node = Reference(self, 'node'), diff --git a/vicn/resource/linux/keypair.py b/vicn/resource/linux/keypair.py new file mode 100644 index 00000000..a81a40d4 --- /dev/null +++ b/vicn/resource/linux/keypair.py @@ -0,0 +1,71 @@ +#!/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 os.path + +from netmodel.model.type import String +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.resource.linux.file import File +from vicn.resource.node import Node + +CMD_CREATE=''' +mkdir -p {dirname} +ssh-keygen -t rsa -N "" -f {self.key} +''' + +class Keypair(Resource): + """ + Resource: Keypair + + Implements a SSH keypair + """ + node = Attribute(Node, + description = 'Node on which the certificate is created', + mandatory = True, + multiplicity = Multiplicity.ManyToOne) + key = Attribute(String, description = 'Key path', + mandatory = True) + + #-------------------------------------------------------------------------- + # Resource lifecycle + #-------------------------------------------------------------------------- + + @inline_task + def __initialize__(self): + self._pubkey_file = File(node = Reference(self, 'node'), + filename = self.key + '.pub', + managed = False) + self._key_file = File(node = Reference(self, 'node'), + filename = self.key, + managed = False) + + def __get__(self): + return self._pubkey_file.__get__() | self._key_file.__get__() + + def __create__(self): + return BashTask(None, CMD_CREATE, { + 'dirname': os.path.dirname(self.key), + 'self': self}) + + 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 4304a948..a4771f9c 100644 --- a/vicn/resource/linux/link.py +++ b/vicn/resource/linux/link.py @@ -42,8 +42,8 @@ CMD_DELETE_IF_EXISTS='ip link show {interface.device_name} && ' \ CMD_CREATE=''' # Create veth pair in the host node ip link add name {tmp_src} type veth peer name {tmp_dst} -ip link set dev {tmp_src} netns {pid[0]} name {interface.src.device_name} -ip link set dev {tmp_dst} netns {pid[1]} name {interface.dst.device_name} +ip link set dev {tmp_src} netns {pid[0]} name {interface._src.device_name} +ip link set dev {tmp_dst} netns {pid[1]} name {interface._dst.device_name} ''' CMD_UP=''' ip link set dev {interface.device_name} up @@ -61,9 +61,6 @@ class Link(Channel): the current implementation. """ - src = Attribute(Interface, description = 'Source interface') - dst = Attribute(Interface, description = 'Destination interface') - capacity = Attribute(Integer, description = 'Link capacity (Mb/s)') delay = Attribute(String, description = 'Link propagation delay') @@ -73,29 +70,29 @@ class Link(Channel): mandatory = True) def __init__(self, *args, **kwargs): - assert 'src' not in kwargs and 'dst' not in kwargs assert 'src_node' in kwargs and 'dst_node' in kwargs + self._src = None + self._dst = None super().__init__(*args, **kwargs) @inline_task def __initialize__(self): - # 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. - self.src = NonTapBaseNetDevice(node = self.src_node, + self._src = NonTapBaseNetDevice(node = self.src_node, device_name = self.dst_node.name, channel = self, capacity = self.capacity, - owner = self.owner) - self.dst = NonTapBaseNetDevice(node = self.dst_node, + owner = self) + self._dst = NonTapBaseNetDevice(node = self.dst_node, device_name = self.src_node.name, channel = self, capacity = self.capacity, - owner = self.owner) - self.dst.remote = self.src - self.src.remote = self.dst + owner = self) + self._dst.remote = self._src + self._src.remote = self._dst #-------------------------------------------------------------------------- # Internal methods @@ -104,21 +101,8 @@ class Link(Channel): async def _commit(self): manager = self._state.manager - # We mark the src and dst interfaces created because we are pre-setting - # them up in __create__ using a VethPair - # We go through both INITIALIZED and CREATED stats to raise the proper - # events and satisfy any eventual wait_* command. - await manager._set_resource_state(self.src, ResourceState.INITIALIZED) - await manager._set_resource_state(self.dst, ResourceState.INITIALIZED) - await manager._set_resource_state(self.src, ResourceState.CREATED) - await manager._set_resource_state(self.dst, ResourceState.CREATED) - - # We mark the attribute clean so that it is not updated - await manager._set_attribute_state(self, 'src', AttributeState.CLEAN) - await manager._set_attribute_state(self, 'dst', AttributeState.CLEAN) - - manager.commit_resource(self.src) - manager.commit_resource(self.dst) + manager.commit_resource(self._src) + manager.commit_resource(self._dst) # Disable rp_filtering # self.src.rp_filter = False @@ -143,36 +127,23 @@ class Link(Channel): # Resource lifecycle #-------------------------------------------------------------------------- - @async_task - async def __get__(self): - manager = self._state.manager + def __get__(self): + return (self._src.__get__() | self._dst.__get__()) > async_task(self._commit)() - try: - await run_task(self.src.__get__(), manager) - await run_task(self.dst.__get__(), manager) - except ResourceNotFound: - # This is raised if any of the two side of the VethPair is missing - raise ResourceNotFound - - # We always need to commit the two endpoints so that their attributes - # are correctly updated - await self._commit() - def __create__(self): assert self.src_node.get_type() == 'lxccontainer' assert self.dst_node.get_type() == 'lxccontainer' src_host = self.src_node.node dst_host = self.dst_node.node - assert src_host == dst_host host = src_host # Sometimes a down interface persists on one side delif_src = BashTask(self.src_node, CMD_DELETE_IF_EXISTS, - {'interface': self.src}) + {'interface': self._src}) delif_dst = BashTask(self.dst_node, CMD_DELETE_IF_EXISTS, - {'interface': self.dst}) + {'interface': self._dst}) pid_src = get_attributes_task(self.src_node, ['pid']) pid_dst = get_attributes_task(self.dst_node, ['pid']) @@ -185,17 +156,13 @@ class Link(Channel): 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}) - - @async_task - async def set_state(): - # We always need to commit the two endpoints so that their attributes - # are correctly updated - await self._commit() + up_src = BashTask(self.src_node, CMD_UP, {'interface': self._src}) + up_dst = BashTask(self.dst_node, CMD_UP, {'interface': self._dst}) delif = delif_src | delif_dst up = up_src | up_dst pid = pid_src | pid_dst - return ((delif > (pid @ create)) > up) > set_state() + return ((delif > (pid @ create)) > up) > async_task(self._commit)() + def __delete__(self): + return self._src.__delete__() | self._dst.__delete__() diff --git a/vicn/resource/linux/net_device.py b/vicn/resource/linux/net_device.py index f0a08991..84a946a4 100644 --- a/vicn/resource/linux/net_device.py +++ b/vicn/resource/linux/net_device.py @@ -464,6 +464,9 @@ 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') diff --git a/vicn/resource/linux/physical.py b/vicn/resource/linux/physical.py index e5eba2d3..8decb517 100644 --- a/vicn/resource/linux/physical.py +++ b/vicn/resource/linux/physical.py @@ -22,16 +22,17 @@ import logging import subprocess import shlex -from netmodel.model.type import String, Integer -from netmodel.util.misc import is_local_host -from netmodel.util.socket import check_port -from vicn.core.attribute import Attribute -from vicn.core.commands import Command, ReturnValue -from vicn.core.exception import ResourceNotFound -from vicn.core.task import Task, task -from vicn.resource.node import Node, DEFAULT_USERNAME -from vicn.resource.node import DEFAULT_SSH_PUBLIC_KEY -from vicn.resource.node import DEFAULT_SSH_PRIVATE_KEY +from netmodel.model.type import String, Integer +from netmodel.util.misc import is_local_host +from netmodel.util.socket import check_port +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 +from vicn.resource.linux.keypair import Keypair +from vicn.resource.node import Node, DEFAULT_USERNAME +from vicn.resource.node import DEFAULT_SSH_PUBLIC_KEY +from vicn.resource.node import DEFAULT_SSH_PRIVATE_KEY log = logging.getLogger(__name__) @@ -42,6 +43,9 @@ CMD_SSH = 'ssh {ssh_options} -i {private_key} -p {port} ' \ CMD_SSH_NF = 'ssh -n -f {ssh_options} -i {private_key} -p {port} ' \ '{user}@{host} {command}' +FN_KEY = os.path.expanduser(os.path.join( + '~', '.vicn', 'ssh_client_cert', 'ssh_client_key')) + class Physical(Node): """ Resource: Physical @@ -67,12 +71,20 @@ class Physical(Node): # Resource lifecycle #-------------------------------------------------------------------------- - @task - def __get__(self, attributes=None): + def __subresources__(self): + """ + Require a SSH keypair to be present for authentication on nodes + """ + return Keypair(node = None, key = FN_KEY) + + def __initialize__(self): + """ + Initialization require the ssh port to be open on the node, and the ssh + public key to be copied on the remote node. + """ if not check_port(self.hostname, self.ssh_port): - raise ResourceNotFound + raise VICNException - def __create__(self): tasks = list() modes = (True, False) if DEFAULT_USERNAME != 'root' else (True,) for as_root in modes: diff --git a/vicn/resource/lxd/lxd_hypervisor.py b/vicn/resource/lxd/lxd_hypervisor.py index 328f3fdf..b6e1c9ff 100644 --- a/vicn/resource/lxd/lxd_hypervisor.py +++ b/vicn/resource/lxd/lxd_hypervisor.py @@ -44,11 +44,10 @@ logging.getLogger("requests").setLevel(logging.WARNING) logging.getLogger("urllib3").setLevel(logging.WARNING) log = logging.getLogger(__name__) -# FIXME use system-wide files -DEFAULT_CERT_PATH = os.path.join(os.path.dirname(__file__), - '..', '..', '..', 'config', 'lxd_client_cert', 'client_cert.pem') -DEFAULT_KEY_PATH = os.path.join(os.path.dirname(__file__), - '..', '..', '..', 'config', 'lxd_client_cert', 'client_key.pem') +DEFAULT_CERT_PATH = os.path.expanduser(os.path.join( + '~', '.vicn', 'lxd_client_cert', 'client_cert.pem')) +DEFAULT_KEY_PATH = os.path.expanduser(os.path.join( + '~', '.vicn', 'lxd_client_cert', 'client_key.pem')) # FIXME hardcoded password for LXD server DEFAULT_TRUST_PASSWORD = 'vicn' @@ -191,8 +190,7 @@ class LxdHypervisor(Service): cert = DEFAULT_CERT_PATH, key = DEFAULT_KEY_PATH, owner = self) - lxd_cert_install = LxdInstallCert(node = Reference(self, 'node'), - certificate = lxd_local_cert, + lxd_cert_install = LxdInstallCert(certificate = lxd_local_cert, owner = self) return (lxd_init | lxd_local_cert) > lxd_cert_install diff --git a/vicn/resource/node.py b/vicn/resource/node.py index bfb2f9ec..ad519666 100644 --- a/vicn/resource/node.py +++ b/vicn/resource/node.py @@ -27,10 +27,10 @@ from vicn.core.resource import Resource log = logging.getLogger(__name__) DEFAULT_USERNAME = 'root' -DEFAULT_SSH_PRIVATE_KEY = os.path.join(os.path.dirname(__file__), - '..', '..', 'config', 'ssh_client_cert', 'ssh_client_key') -DEFAULT_SSH_PUBLIC_KEY = os.path.join(os.path.dirname(__file__), - '..', '..', 'config', 'ssh_client_cert', 'ssh_client_key.pub') +DEFAULT_SSH_PRIVATE_KEY = os.path.expanduser(os.path.join( + '~', '.vicn', 'ssh_client_cert', 'ssh_client_key')) +DEFAULT_SSH_PUBLIC_KEY = os.path.expanduser(os.path.join( + '~', '.vicn', 'ssh_client_cert', 'ssh_client_key.pub')) class Node(Resource): """ diff --git a/vicn/resource/ns3/emulated_channel.py b/vicn/resource/ns3/emulated_channel.py index 08d7a14b..5f61960c 100644 --- a/vicn/resource/ns3/emulated_channel.py +++ b/vicn/resource/ns3/emulated_channel.py @@ -69,7 +69,7 @@ class EmulatedChannel(Channel, Application): ap = Attribute(Node, description = 'AP', key = True) stations = Attribute(Node, description = 'List of stations', multiplicity = Multiplicity.OneToMany, key = True) - control_port = Attribute(Integer, + control_port = Attribute(Integer, description = 'Control port for the simulation') # Overloaded attributes @@ -141,7 +141,7 @@ class EmulatedChannel(Channel, Application): managed = False) self._ap_if = VethPair(node = self.ap, name = 'vh-' + ap.name + '-' + self.name, - device_name = 'vh-' + ap.name + '-' + self.name, + device_name = 'vh-' + ap.name + '-' + self.name, host = host, owner = self) self._ap_bridged = self._ap_if.host @@ -152,7 +152,7 @@ class EmulatedChannel(Channel, Application): interfaces.append(self._ap_if) # Add a tap interface for the AP... - self._ap_tap = TapDevice(node = self.node, + self._ap_tap = TapDevice(node = self.node, owner = self, device_name = 'tap-' + ap.name + '-' + self.name, up = True, @@ -165,11 +165,11 @@ class EmulatedChannel(Channel, Application): await wait_resources(interfaces) # NOTE: only set channel after the resource is created or it might - # create loops which, at this time, are not handled + # create loops which, at this time, are not handled self._ap_if.set('channel', self) # Add interfaces to bridge - vlan = AddressManager().get('vlan', self, tag='ap') + vlan = AddressManager().get('vlan', self, tag='ap') # AS the container has created the VethPair already without Vlan, we # need to delete and recreate it @@ -181,8 +181,6 @@ class EmulatedChannel(Channel, Application): task = self.node.bridge._add_interface(self._ap_tap, vlan = vlan) await run_task(task, self._state.manager) - print('/!\ pass information to the running simulation') - @inline_task def _get_ap(self): return {'ap': None} diff --git a/vicn/resource/ns3/emulated_lte_channel.py b/vicn/resource/ns3/emulated_lte_channel.py index 8c7382cb..39370228 100644 --- a/vicn/resource/ns3/emulated_lte_channel.py +++ b/vicn/resource/ns3/emulated_lte_channel.py @@ -61,7 +61,7 @@ class EmulatedLteChannel(EmulatedChannel): # ... and each station if not station.managed: sta_if = None - else: + else: if isinstance(station, LxcContainer): host = NetDevice(node = station.node, device_name='vhh-' + station.name + '-' + self.name, @@ -103,8 +103,8 @@ 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, + + task = self.node.bridge._add_interface(bridged_sta, vlan = vlan) await run_task(task, self._state.manager) @@ -178,8 +178,8 @@ 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) + - DEFAULT_NETMASK, + 'bs-ip' : AddressManager().get_ip(self._ap_if) + '/' + + str(DEFAULT_NETMASK), 'txBuffer' : '800000', 'isFading' : 'true' if DEFAULT_FADING_ENABLED else 'false', } -- cgit 1.2.3-korg