From cb940157abb752fe1ed90004132617d723bf8c88 Mon Sep 17 00:00:00 2001 From: Pete Vander Giessen Date: Tue, 5 Nov 2019 03:22:42 +0000 Subject: [PATCH] Fix Eoan. Moved to pure Python where clib conflicts arose in using command line tools. Fixed erroneous assumptions about the presence and reliability of a $HOME variable while running init. Added tests specific to eoan, disco and xenial. They are not yet part of the gate. Change-Id: I2fc74fcc2ae9876442bb87a3446aef48d0428f2f --- snapcraft.yaml | 7 +-- tests/framework.py | 3 +- tests/test_basic.py | 53 ++++++++++++++++- tools/init/init/questions/__init__.py | 22 +++---- tools/init/init/shell.py | 12 ++-- tools/update_path.py | 85 +++++++++++++++++++++++++++ tox.ini | 32 ++++++++++ 7 files changed, 188 insertions(+), 26 deletions(-) create mode 100755 tools/update_path.py diff --git a/snapcraft.yaml b/snapcraft.yaml index 6f50647..46de3d6 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -8,8 +8,9 @@ description: | grade: stable confinement: classic environment: - LD_LIBRARY_PATH: $SNAP/lib:$SNAP/lib/$SNAPCRAFT_ARCH_TRIPLET:$SNAP/usr/lib:$SNAP/usr/lib/$SNAPCRAFT_ARCH_TRIPLET - PATH: $SNAP/usr/sbin:$SNAP/usr/bin:$SNAP/sbin:$SNAP/bin:/snap/core18/current/bin:$PATH + # Edit the following lines with tools/update_path.py + LD_LIBRARY_PATH: $SNAP/lib:$SNAP/lib/$SNAPCRAFT_ARCH_TRIPLET:$SNAP/usr/lib:$SNAP/usr/lib/$SNAPCRAFT_ARCH_TRIPLET:$SNAP/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/pulseaudio + PATH: $SNAP/usr/sbin:$SNAP/usr/bin:$SNAP/sbin:$SNAP/bin:$PATH LC_ALL: C OS_PLACEMENT_CONFIG_DIR: $SNAP/etc/nova/ @@ -219,8 +220,6 @@ apps: libvirtd: command: libvirtd daemon: simple - environment: - LD_LIBRARY_PATH: $SNAP/lib:$SNAP/lib/$SNAPCRAFT_ARCH_TRIPLET:$SNAP/usr/lib:$SNAP/usr/lib/$SNAPCRAFT_ARCH_TRIPLET:$SNAP/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/pulseaudio virtlogd: command: virtlogd daemon: simple diff --git a/tests/framework.py b/tests/framework.py index 8961c0b..b1e138d 100644 --- a/tests/framework.py +++ b/tests/framework.py @@ -72,10 +72,11 @@ class Framework(unittest.TestCase): self.MACHINE = petname.generate() self.PREFIX = ['multipass', 'exec', self.MACHINE, '--'] + distro = os.environ.get('DISTRO') or self.DISTRO check('sudo', 'snap', 'install', '--classic', '--edge', 'multipass') - check('multipass', 'launch', '--cpus', '2', '--mem', '8G', self.DISTRO, + check('multipass', 'launch', '--cpus', '2', '--mem', '8G', distro, '--name', self.MACHINE) check('multipass', 'copy-files', self.SNAP, '{}:'.format(self.MACHINE)) diff --git a/tests/test_basic.py b/tests/test_basic.py index 399ccd3..cd7fc17 100755 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -14,8 +14,10 @@ Web IDE. """ +import json import os import sys +import time import unittest import xvfbwrapper from selenium import webdriver @@ -50,7 +52,7 @@ class TestBasics(Framework): """ launch = '/snap/bin/microstack.launch' - # openstack = '/snap/bin/microstack.openstack' + openstack = '/snap/bin/microstack.openstack' print("Testing microstack.launch ...") @@ -66,6 +68,55 @@ class TestBasics(Framework): # Endpoints should not contain localhost self.assertFalse("localhost" in endpoints) + if 'multipass' in self.PREFIX: + # Verify that microstack.launch completed successfully + # Skip these tests in the gate, as they are not reliable there. + # TODO: fix these in the gate! + + # Ping the instance + ip = None + servers = check_output(*self.PREFIX, openstack, + 'server', 'list', '--format', 'json') + servers = json.loads(servers) + for server in servers: + if server['Name'] == 'breakfast': + ip = server['Networks'].split(",")[1].strip() + break + + self.assertTrue(ip) + + pings = 1 + max_pings = 600 # ~10 minutes! + while not call(*self.PREFIX, 'ping', '-c1', '-w1', ip): + pings += 1 + if pings > max_pings: + self.assertFalse(True, msg='Max pings reached!') + + print("Testing instances' ability to connect to the Internet") + # Test Internet connectivity + attempts = 1 + max_attempts = 300 # ~10 minutes! + username = check_output(*self.PREFIX, 'whoami') + + while not call( + *self.PREFIX, + 'ssh', + '-oStrictHostKeyChecking=no', + '-i', '/home/{}/.ssh/id_microstack'.format(username), + 'cirros@{}'.format(ip), + '--', 'ping', '-c1', '91.189.94.250'): + attempts += 1 + if attempts > max_attempts: + self.assertFalse( + True, + msg='Unable to access the Internet!') + time.sleep(1) + + else: + # Artificial wait, to allow for stuff to settle for the GUI test. + # TODO: get rid of this, when we drop the ping tests back int. + time.sleep(10) + if 'multipass' in self.PREFIX: print("Opening {}:80 up to the outside world".format( self.HORIZON_IP)) diff --git a/tools/init/init/questions/__init__.py b/tools/init/init/questions/__init__.py index 96bc0b3..d4d91dc 100644 --- a/tools/init/init/questions/__init__.py +++ b/tools/init/init/questions/__init__.py @@ -291,12 +291,10 @@ class RabbitMq(Question): (actions may have already been run, in which case we fail silently). """ - # Add Erlang HOME to env. - env = dict(**_env) - env['HOME'] = '{SNAP_COMMON}/lib/rabbitmq'.format(**_env) # Configure RabbitMQ - call('rabbitmqctl', 'add_user', 'openstack', 'rabbitmq', env=env) - shell('rabbitmqctl set_permissions openstack ".*" ".*" ".*"', env=env) + call('microstack.rabbitmqctl', 'add_user', 'openstack', 'rabbitmq') + shell( + 'microstack.rabbitmqctl set_permissions openstack ".*" ".*" ".*"') def yes(self, answer: str) -> None: log.info('Waiting for RabbitMQ to start ...') @@ -692,20 +690,18 @@ class KeyPair(Question): def yes(self, answer: str) -> None: if 'microstack' not in check_output('openstack', 'keypair', 'list'): + user = check_output('logname') + home = '/home/{}'.format(user) # TODO make more portable! + log.info('Creating microstack keypair (~/.ssh/{})'.format(answer)) - check('mkdir', '-p', '{HOME}/.ssh'.format(**_env)) - check('chmod', '700', '{HOME}/.ssh'.format(**_env)) + check('mkdir', '-p', '{home}/.ssh'.format(home=home)) + check('chmod', '700', '{home}/.ssh'.format(home=home)) id_ = check_output('openstack', 'keypair', 'create', 'microstack') - id_path = '{HOME}/.ssh/{answer}'.format( - HOME=_env['HOME'], - answer=answer - ) + id_path = '{home}/.ssh/{answer}'.format(home=home, answer=answer) with open(id_path, 'w') as file_: file_.write(id_) check('chmod', '600', id_path) - # TODO: too many assumptions in the below. Make it portable! - user = _env['HOME'].split("/")[2] check('chown', '{}:{}'.format(user, user), id_path) diff --git a/tools/init/init/shell.py b/tools/init/init/shell.py index 63073e8..3caa482 100644 --- a/tools/init/init/shell.py +++ b/tools/init/init/shell.py @@ -30,6 +30,7 @@ from typing import Dict, List import netaddr import netifaces import pymysql +import socket import wget from init.config import Env, log @@ -107,16 +108,12 @@ def shell(cmd: str, env: Dict = _env) -> int: """ proc = subprocess.Popen(cmd, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1, - universal_newlines=True, shell=True, - executable='/snap/core18/current/bin/bash') + universal_newlines=True, shell=True) for line in iter(proc.stdout.readline, ''): log.debug(line) proc.wait() if proc.returncode: - raise subprocess.CalledProcessError( - "Command '{}' returned non-zero exit status {}".format( - cmd, - proc.returncode)) + raise subprocess.CalledProcessError(proc.returncode, cmd) return proc.returncode @@ -141,7 +138,8 @@ def sql(cmd: str) -> None: def nc_wait(addr: str, port: str) -> None: """Wait for a service to be answering on a port.""" print('Waiting for {}:{}'.format(addr, port)) - while not call('nc', '-z', addr, port): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + while sock.connect_ex((addr, int(port))) != 0: sleep(1) diff --git a/tools/update_path.py b/tools/update_path.py new file mode 100755 index 0000000..fe1dc2b --- /dev/null +++ b/tools/update_path.py @@ -0,0 +1,85 @@ +#!/usr/bin/python3 +"""Update LD_LIBRARY_PATH and PATH snapcraft.yaml in the current +working directory. + +Editing the lines in question directly in snapcraft.yaml is pretty +terrible, as the lines are long, and we cannot break them up into a +normal yaml string w/ a | and still get snapcraft's variable +expansion. (Or, if we can, I don't know what magic invocation will do +so.) + +This script will not check in the new snapcraft.yaml. You should +inspect the updates and check in the file yourself! + +""" + +import os +import shutil +import sys + +LD_LIBRARY_PATH = ( + '$SNAP/lib', + '$SNAP/lib/$SNAPCRAFT_ARCH_TRIPLET', + '$SNAP/usr/lib', + '$SNAP/usr/lib/$SNAPCRAFT_ARCH_TRIPLET', + '$SNAP/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/pulseaudio', +# '/snap/core18/current/lib', +# '/snap/core18/current/lib/$SNAPCRAFT_ARCH_TRIPLET', +# '/snap/core18/current/lib/systemd', +# '/snap/core18/current/usr/lib', +# '/snap/core18/current/var/lib', +# '/snap/core18/current/usr/lib/$SNAPCRAFT_ARCH_TRIPLET', +) +PATH = ( + '$SNAP/usr/sbin', + '$SNAP/usr/bin', + '$SNAP/sbin', + '$SNAP/bin', +# '/snap/core18/current/bin', +# '/snap/core18/current/usr/sbin', +# '/snap/core18/current/usr/bin', +# '/snap/core18/current/sbin', + '$PATH' +) + +def main(): + """Replace PATH and LD_LIBRARY_PATH with lists above. + + This is dead simple code that relies on there being one setting + for LD_LIBRARY_PATH and PATH. It needs to be updated to be made + smarter if more instances are added. + + Note that it would be nice if we could just read and write the + yaml, but we'd chomp comments if we did so. And we like our + comments! + + """ + if not os.path.isfile('./snapcraft.yaml'): + print('Cannot file snapcraft.yaml in the current working dir!') + print('Exiting.') + sys.exit(1) + + print('snapcraft.yaml found in the current working dir. ' + 'Updating LD_LIBRARY_PATH and PATH ...') + + libs = ':'.join(LD_LIBRARY_PATH) + path_ = ':'.join(PATH) + + with open('./snapcraft.yaml', 'r') as source: + with open('./snapcraft.yaml.updated', 'w') as dest: + lines = source.readlines() + for line in lines: + if line.startswith(' LD_LIBRARY_PATH: '): + line = ' LD_LIBRARY_PATH: {}\n'.format(libs) + if line.startswith(' PATH: '): + line = ' PATH: {}\n'.format(path_) + + dest.write(line) + + shutil.move('./snapcraft.yaml.updated', './snapcraft.yaml') + + print('File updated! Please manually inspect the changes ' + 'and commit them via git.') + +if __name__ == '__main__': + main() diff --git a/tox.ini b/tox.ini index 101a79a..c860e52 100644 --- a/tox.ini +++ b/tox.ini @@ -53,6 +53,38 @@ commands = flake8 {toxinidir}/tests/ {toxinidir}/tests/test_basic.py +[testenv:eoan] +# Just run basic_test.sh, with multipass support, on eoan. +# TODO: refactor so that there isn't so much copypasta here and below. +deps = -r{toxinidir}/test-requirements.txt +setenv = + MULTIPASS=true + DISTRO=eoan + +commands = + {toxinidir}/tools/basic_setup.sh + flake8 {toxinidir}/tests/ + {toxinidir}/tests/test_basic.py + +[testenv:disco] +# Just run basic_test.sh, with multipass support, on disco. +deps = -r{toxinidir}/test-requirements.txt +setenv = + MULTIPASS=true + DISTRO=disco + +[testenv:xenial] +# Just run basic_test.sh, with multipass support, on xenial. +deps = -r{toxinidir}/test-requirements.txt +setenv = + MULTIPASS=true + DISTRO=xenial + +commands = + {toxinidir}/tools/basic_setup.sh + flake8 {toxinidir}/tests/ + {toxinidir}/tests/test_basic.py + [testenv:cluster] # Test out clustering! # Requires multipass.