| #!/usr/bin/env python3 |
| |
| import logging |
| import n2vc.vnf |
| import pylxd |
| import os |
| import shlex |
| import subprocess |
| import time |
| import uuid |
| import yaml |
| |
| # Disable InsecureRequestWarning w/LXD |
| import urllib3 |
| urllib3.disable_warnings() |
| |
| here = os.path.dirname(os.path.realpath(__file__)) |
| |
| |
| def get_charm_path(): |
| return "{}/charms".format(here) |
| |
| |
| def get_layer_path(): |
| return "{}/charms/layers".format(here) |
| |
| |
| def parse_metrics(application, results): |
| """Parse the returned metrics into a dict.""" |
| |
| # We'll receive the results for all units, to look for the one we want |
| # Caveat: we're grabbing results from the first unit of the application, |
| # which is enough for testing, since we're only deploying a single unit. |
| retval = {} |
| for unit in results: |
| if unit.startswith(application): |
| for result in results[unit]: |
| retval[result['key']] = result['value'] |
| return retval |
| |
| def collect_metrics(application): |
| """Invoke Juju's metrics collector. |
| |
| Caveat: this shells out to the `juju collect-metrics` command, rather than |
| making an API call. At the time of writing, that API is not exposed through |
| the client library. |
| """ |
| |
| try: |
| logging.debug("Collecting metrics") |
| subprocess.check_call(['juju', 'collect-metrics', application]) |
| except subprocess.CalledProcessError as e: |
| raise Exception("Unable to collect metrics: {}".format(e)) |
| |
| |
| def build_charm(charm): |
| """Build a test charm. |
| |
| Builds one of the charms in tests/charms/layers and returns the path |
| to the compiled charm. The calling test is responsible for removing |
| the charm artifact during cleanup. |
| """ |
| # stream_handler = logging.StreamHandler(sys.stdout) |
| # log.addHandler(stream_handler) |
| |
| # Make sure the charm snap is installed |
| try: |
| logging.debug("Looking for charm-tools") |
| subprocess.check_call(['which', 'charm']) |
| except subprocess.CalledProcessError as e: |
| raise Exception("charm snap not installed.") |
| |
| try: |
| builds = get_charm_path() |
| |
| cmd = "charm build {}/{} -o {}/".format( |
| get_layer_path(), |
| charm, |
| builds, |
| ) |
| subprocess.check_call(shlex.split(cmd)) |
| return "{}/{}".format(builds, charm) |
| except subprocess.CalledProcessError as e: |
| raise Exception("charm build failed: {}.".format(e)) |
| |
| return None |
| |
| |
| def get_descriptor(descriptor): |
| desc = None |
| try: |
| tmp = yaml.load(descriptor) |
| |
| # Remove the envelope |
| root = list(tmp.keys())[0] |
| if root == "nsd:nsd-catalog": |
| desc = tmp['nsd:nsd-catalog']['nsd'][0] |
| elif root == "vnfd:vnfd-catalog": |
| desc = tmp['vnfd:vnfd-catalog']['vnfd'][0] |
| except ValueError: |
| assert False |
| return desc |
| |
| def get_n2vc(): |
| """Return an instance of N2VC.VNF.""" |
| log = logging.getLogger() |
| log.level = logging.DEBUG |
| |
| # Extract parameters from the environment in order to run our test |
| vca_host = os.getenv('VCA_HOST', '127.0.0.1') |
| vca_port = os.getenv('VCA_PORT', 17070) |
| vca_user = os.getenv('VCA_USER', 'admin') |
| vca_charms = os.getenv('VCA_CHARMS', None) |
| vca_secret = os.getenv('VCA_SECRET', None) |
| client = n2vc.vnf.N2VC( |
| log=log, |
| server=vca_host, |
| port=vca_port, |
| user=vca_user, |
| secret=vca_secret, |
| artifacts=vca_charms, |
| ) |
| return client |
| |
| def create_lxd_container(public_key=None): |
| """ |
| Returns a container object |
| |
| If public_key isn't set, we'll use the Juju ssh key |
| """ |
| |
| client = get_lxd_client() |
| test_machine = "test-{}-add-manual-machine-ssh".format( |
| uuid.uuid4().hex[-4:] |
| ) |
| |
| private_key_path, public_key_path = find_juju_ssh_keys() |
| # private_key_path = os.path.expanduser( |
| # "~/.local/share/juju/ssh/juju_id_rsa" |
| # ) |
| # public_key_path = os.path.expanduser( |
| # "~/.local/share/juju/ssh/juju_id_rsa.pub" |
| # ) |
| |
| # Use the self-signed cert generated by lxc on first run |
| crt = os.path.expanduser('~/snap/lxd/current/.config/lxc/client.crt') |
| assert os.path.exists(crt) |
| |
| key = os.path.expanduser('~/snap/lxd/current/.config/lxc/client.key') |
| assert os.path.exists(key) |
| |
| # create profile w/cloud-init and juju ssh key |
| if not public_key: |
| public_key = "" |
| with open(public_key_path, "r") as f: |
| public_key = f.readline() |
| |
| profile = client.profiles.create( |
| test_machine, |
| config={'user.user-data': '#cloud-config\nssh_authorized_keys:\n- {}'.format(public_key)}, |
| devices={ |
| 'root': {'path': '/', 'pool': 'default', 'type': 'disk'}, |
| 'eth0': { |
| 'nictype': 'bridged', |
| 'parent': 'lxdbr0', |
| 'type': 'nic' |
| } |
| } |
| ) |
| |
| # create lxc machine |
| config = { |
| 'name': test_machine, |
| 'source': { |
| 'type': 'image', |
| 'alias': 'xenial', |
| 'mode': 'pull', |
| 'protocol': 'simplestreams', |
| 'server': 'https://cloud-images.ubuntu.com/releases', |
| }, |
| 'profiles': [test_machine], |
| } |
| container = client.containers.create(config, wait=True) |
| container.start(wait=True) |
| |
| def wait_for_network(container, timeout=30): |
| """Wait for eth0 to have an ipv4 address.""" |
| starttime = time.time() |
| while(time.time() < starttime + timeout): |
| time.sleep(1) |
| if 'eth0' in container.state().network: |
| addresses = container.state().network['eth0']['addresses'] |
| if len(addresses) > 0: |
| if addresses[0]['family'] == 'inet': |
| return addresses[0] |
| return None |
| |
| host = wait_for_network(container) |
| |
| # HACK: We need to give sshd a chance to bind to the interface, |
| # and pylxd's container.execute seems to be broken and fails and/or |
| # hangs trying to properly check if the service is up. |
| time.sleep(5) |
| |
| return container |
| |
| |
| def destroy_lxd_container(container): |
| """Stop and delete a LXD container.""" |
| container.stop(wait=True) |
| container.delete() |
| |
| |
| def find_lxd_config(): |
| """Find the LXD configuration directory.""" |
| paths = [] |
| paths.append(os.path.expanduser("~/.config/lxc")) |
| paths.append(os.path.expanduser("~/snap/lxd/current/.config/lxc")) |
| |
| for path in paths: |
| if os.path.exists(path): |
| crt = os.path.expanduser("{}/client.crt".format(path)) |
| key = os.path.expanduser("{}/client.key".format(path)) |
| if os.path.exists(crt) and os.path.exists(key): |
| return (crt, key) |
| return (None, None) |
| |
| |
| def find_juju_ssh_keys(): |
| """Find the Juju ssh keys.""" |
| |
| paths = [] |
| paths.append(os.path.expanduser("~/.local/share/juju/ssh/")) |
| |
| for path in paths: |
| if os.path.exists(path): |
| private = os.path.expanduser("{}/juju_id_rsa".format(path)) |
| public = os.path.expanduser("{}/juju_id_rsa.pub".format(path)) |
| if os.path.exists(private) and os.path.exists(public): |
| return (private, public) |
| return (None, None) |
| |
| |
| def get_juju_private_key(): |
| keys = find_juju_ssh_keys() |
| return keys[0] |
| |
| |
| def get_lxd_client(host="127.0.0.1", port="8443", verify=False): |
| """ Get the LXD client.""" |
| client = None |
| (crt, key) = find_lxd_config() |
| |
| if crt and key: |
| client = pylxd.Client( |
| endpoint="https://{}:{}".format(host, port), |
| cert=(crt, key), |
| verify=verify, |
| ) |
| |
| return client |