Added microstack.remove command

Running microstack.remove will remove the br-ex virtual bridge device,
then uninstall MicroStack.

We do this because we can't use ovs-ctl to remove the bridge as part
of a remove hook, as the Open vSwitch daemons are not running at that
point. The microstack.remove command gives operators a way to cleanly
uninstall the snap, without needing to reboot to get rid of br-ex.

Added test exercising the code to test_basic.py.

Rerranged entry points a bit (moved some things into main.py) to make
code sharing easier, and to prevent a proliferation of entry point
scripts in our root dir.

Change-Id: I9ff25864cd96ada3a9b3da8992c2b33955eff0b4
Closes-Bug: #1852147
This commit is contained in:
Pete Vander Giessen 2019-11-27 21:47:52 +00:00
parent 1a25e50a17
commit a89f5574c3
10 changed files with 169 additions and 38 deletions

View File

@ -67,6 +67,23 @@ credentials are:
username: admin username: admin
password: keystone password: keystone
## Removing MicroStack
To remove MicroStack, run:
sudo microstack.remove --auto
This will clean up the Open vSwitch bridge device and uninstall
MicroStack. If you remove MicroStack with the `snap remove` command
instead, don't worry -- the Open vSwitch bridge will disappear the
next time that you reboot your system.
Note that you can pass any arguments that you'd pass to the `snap
remove` command to `microstack.remove`. To purge the snap,
for example, run:
sudo microstack.remove --auto --purge
## Customising and contributing ## Customising and contributing
To customise services and settings, look in the `.d` directories under To customise services and settings, look in the `.d` directories under

View File

@ -52,3 +52,9 @@ snapctl set \
cluster.role=control \ cluster.role=control \
cluster.password=null \ cluster.password=null \
; ;
# Uninstall stuff
snapctl set \
config.cleanup.delete-bridge=true \
config.cleanup.remove=true \
;

View File

@ -27,6 +27,9 @@ apps:
# plugs: # plugs:
# - network # - network
remove:
command: microstack_remove
# Keystone # Keystone
keystone-uwsgi: keystone-uwsgi:
command: snap-openstack launch keystone-uwsgi command: snap-openstack launch keystone-uwsgi

View File

@ -141,7 +141,9 @@ class Host():
check('sudo', 'multipass', 'delete', self.machine) check('sudo', 'multipass', 'delete', self.machine)
check('sudo', 'multipass', 'purge') check('sudo', 'multipass', 'purge')
else: else:
check('sudo', 'snap', 'remove', '--purge', 'microstack') if call('snap', 'list', 'microstack'):
# Uninstall microstack if it is installed (it may not be).
check('sudo', 'snap', 'remove', '--purge', 'microstack')
class Framework(unittest.TestCase): class Framework(unittest.TestCase):
@ -241,7 +243,7 @@ class Framework(unittest.TestCase):
self.driver.find_element(By.LINK_TEXT, "Images").click() self.driver.find_element(By.LINK_TEXT, "Images").click()
def setUp(self): def setUp(self):
self.passed = False # HACK: trigger (or skip) cleanup. self.passed = False # HACK: trigger (or skip) log dumps.
def tearDown(self): def tearDown(self):
"""Clean hosts up, possibly leaving debug information behind.""" """Clean hosts up, possibly leaving debug information behind."""

View File

@ -65,6 +65,27 @@ class TestBasics(Framework):
# The Horizon Dashboard should function # The Horizon Dashboard should function
self.verify_gui(host) self.verify_gui(host)
# Verify that we can uninstall the snap cleanly, and that the
# ovs bridge goes away.
# Check to verify that our bridge is there.
self.assertTrue('br-ex' in check_output(*prefix, 'ip', 'a'))
# Try to uninstall snap without sudo.
self.assertFalse(call(*prefix, '/snap/bin/microstack.remove',
'--purge', '--auto'))
# Retry with sudo (should succeed).
check(*prefix, 'sudo', '/snap/bin/microstack.remove',
'--purge', '--auto')
# Verify that MicroStack is gone.
self.assertFalse(call(*prefix, 'snap', 'list', 'microstack'))
# Verify that bridge is gone.
self.assertFalse('br-ex' in check_output(*prefix, 'ip', 'a'))
# We made it to the end. Set passed to True!
self.passed = True self.passed = True

View File

@ -35,13 +35,27 @@ import secrets
import string import string
import sys import sys
from functools import wraps
from init.config import log from init.config import log
from init.shell import check from init.shell import default_network, check, check_output
from init import questions from init import questions
def parse_args(): def requires_sudo(func):
@wraps(func)
def wrapper(*args, **kwargs):
if int(check_output('id', '-u')):
log.error("This script must be run with root privileges. "
"Please re-run with sudo.")
sys.exit(1)
return func(*args, **kwargs)
return wrapper
def parse_init_args():
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('--auto', '-a', action='store_true', parser.add_argument('--auto', '-a', action='store_true',
help='Run non interactively.') help='Run non interactively.')
@ -53,7 +67,7 @@ def parse_args():
return args return args
def process_args(args): def process_init_args(args):
"""Look through our args object and set the proper default config """Look through our args object and set the proper default config
values in our snap config, based on those args. values in our snap config, based on those args.
@ -80,7 +94,8 @@ def process_args(args):
if auto and not args.cluster_password: if auto and not args.cluster_password:
alphabet = string.ascii_letters + string.digits alphabet = string.ascii_letters + string.digits
password = ''.join(secrets.choice(alphabet) for i in range(10)) password = ''.join(secrets.choice(alphabet) for i in range(10))
check('snapctl', 'set', 'config.cluster.password={}'.format(password)) check('snapctl', 'set', 'config.cluster.password={}'.format(
password))
if args.debug: if args.debug:
log.setLevel(logging.DEBUG) log.setLevel(logging.DEBUG)
@ -88,9 +103,10 @@ def process_args(args):
return auto return auto
def main() -> None: @requires_sudo
args = parse_args() def init() -> None:
auto = process_args(args) args = parse_init_args()
auto = process_init_args(args)
question_list = [ question_list = [
questions.Clustering(), questions.Clustering(),
@ -125,5 +141,47 @@ def main() -> None:
sys.exit(1) sys.exit(1)
if __name__ == '__main__': def set_network_info() -> None:
main() """Find and use the default network on a machine.
Helper to find the default network on a machine, and configure
MicroStack to use it in its default settings.
"""
try:
ip, gate, cidr = default_network()
except Exception:
# TODO: more specific exception handling.
log.exception(
'Could not determine default network info. '
'Falling back on 10.20.20.1')
return
check('snapctl', 'set', 'config.network.ext-gateway={}'.format(gate))
check('snapctl', 'set', 'config.network.ext-cidr={}'.format(cidr))
check('snapctl', 'set', 'config.network.control-ip={}'.format(ip))
check('snapctl', 'set', 'config.network.control-ip={}'.format(ip))
@requires_sudo
def remove() -> None:
"""Helper to cleanly uninstall MicroStack."""
# Strip '--auto' out of the args passed to this command, as we
# need to check it, but also pass the other args off to the
# snapd's uninstall command. TODO: make this less hacky.
auto = False
if '--auto' in questions.uninstall.ARGS:
auto = True
questions.uninstall.ARGS = [
arg for arg in questions.uninstall.ARGS if 'auto' not in arg]
question_list = [
questions.uninstall.DeleteBridge(),
questions.uninstall.RemoveMicrostack(),
]
for question in question_list:
if auto:
question.interactive = False
question.ask()

View File

@ -31,7 +31,7 @@ from init.shell import (check, call, check_output, shell, sql, nc_wait,
log_wait, restart, download) log_wait, restart, download)
from init.config import Env, log from init.config import Env, log
from init.questions.question import Question from init.questions.question import Question
from init.questions import clustering, network from init.questions import clustering, network, uninstall # noqa F401
_env = Env().get_env() _env = Env().get_env()

View File

@ -0,0 +1,47 @@
import sys
from init.config import Env, log
from init.questions.question import Question
from init.shell import check, call
_env = Env().get_env()
# Save off command line args. If you use any of these to answer a
# question, pop it from this list -- the remaining args will get
# passed to "snap remove"
ARGS = list(sys.argv)
class DeleteBridge(Question):
_type = 'boolean'
_question = 'Do you wish to delete the ovs bridge? (br-ex)'
interactive = True
config_key = 'config.cleanup.delete-bridge'
def yes(self, answer):
log.info('Removing ovs bridge.')
# Remove bridge. This may not exist, so we silently skip on error.
# TODO get bridge name from config (if it gets added to config)
# TODO clean up other ovs artifacts?
call('ovs-vsctl', 'del-br', 'br-ex')
# TODO: cleanup system optimizations
# TODO: cleanup kernel modules?
# TODO: cleanup iptables rules
class RemoveMicrostack(Question):
_type = 'auto'
_question = 'Do you really wish to remove MicroStack?'
interactive = True
config_key = 'config.cleanup.remove'
def yes(self, answer):
"""Uninstall MicroStack, passing any command line options to snapd."""
log.info('Uninstalling MicroStack (this may take a while) ...')
check('snap', 'remove', '{SNAP_INSTANCE_NAME}'.format(**_env),
*ARGS)
log.info('MicroStack has been removed from your system!')

View File

@ -1,24 +0,0 @@
#!/usr/bin/env/python3
from init.shell import default_network, check
from init.config import log # TODO name log.
def main():
try:
ip, gate, cidr = default_network()
except Exception:
# TODO: more specific exception handling.
log.exception(
'Could not determine default network info. '
'Falling back on 10.20.20.1')
return
check('snapctl', 'set', 'config.network.ext-gateway={}'.format(gate))
check('snapctl', 'set', 'config.network.ext-cidr={}'.format(cidr))
check('snapctl', 'set', 'config.network.control-ip={}'.format(ip))
check('snapctl', 'set', 'config.network.control-ip={}'.format(ip))
if __name__ == '__main__':
main()

View File

@ -7,8 +7,9 @@ setup(
version="0.0.1", version="0.0.1",
entry_points={ entry_points={
'console_scripts': [ 'console_scripts': [
'microstack_init = init.main:main', 'microstack_init = init.main:init',
'set_network_info = init.set_network_info:main', 'set_network_info = init.main:set_network_info',
'microstack_remove = init.main:remove',
], ],
}, },
) )