Browse Source

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
changes/06/695606/9
Pete Vander Giessen 1 month ago
parent
commit
a89f5574c3
10 changed files with 169 additions and 38 deletions
  1. +17
    -0
      README.md
  2. +6
    -0
      snap-overlay/bin/set-default-config
  3. +3
    -0
      snapcraft.yaml
  4. +4
    -2
      tests/framework.py
  5. +21
    -0
      tests/test_basic.py
  6. +67
    -9
      tools/init/init/main.py
  7. +1
    -1
      tools/init/init/questions/__init__.py
  8. +47
    -0
      tools/init/init/questions/uninstall.py
  9. +0
    -24
      tools/init/init/set_network_info.py
  10. +3
    -2
      tools/init/setup.py

+ 17
- 0
README.md View File

@@ -67,6 +67,23 @@ credentials are:
username: admin
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

To customise services and settings, look in the `.d` directories under

+ 6
- 0
snap-overlay/bin/set-default-config View File

@@ -52,3 +52,9 @@ snapctl set \
cluster.role=control \
cluster.password=null \
;

# Uninstall stuff
snapctl set \
config.cleanup.delete-bridge=true \
config.cleanup.remove=true \
;

+ 3
- 0
snapcraft.yaml View File

@@ -27,6 +27,9 @@ apps:
# plugs:
# - network

remove:
command: microstack_remove

# Keystone
keystone-uwsgi:
command: snap-openstack launch keystone-uwsgi

+ 4
- 2
tests/framework.py View File

@@ -141,7 +141,9 @@ class Host():
check('sudo', 'multipass', 'delete', self.machine)
check('sudo', 'multipass', 'purge')
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):
@@ -241,7 +243,7 @@ class Framework(unittest.TestCase):
self.driver.find_element(By.LINK_TEXT, "Images").click()

def setUp(self):
self.passed = False # HACK: trigger (or skip) cleanup.
self.passed = False # HACK: trigger (or skip) log dumps.

def tearDown(self):
"""Clean hosts up, possibly leaving debug information behind."""

+ 21
- 0
tests/test_basic.py View File

@@ -65,6 +65,27 @@ class TestBasics(Framework):
# The Horizon Dashboard should function
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



+ 67
- 9
tools/init/init/main.py View File

@@ -35,13 +35,27 @@ import secrets
import string
import sys

from functools import wraps

from init.config import log
from init.shell import check
from init.shell import default_network, check, check_output

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.add_argument('--auto', '-a', action='store_true',
help='Run non interactively.')
@@ -53,7 +67,7 @@ def parse_args():
return args


def process_args(args):
def process_init_args(args):
"""Look through our args object and set the proper default config
values in our snap config, based on those args.

@@ -80,7 +94,8 @@ def process_args(args):
if auto and not args.cluster_password:
alphabet = string.ascii_letters + string.digits
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:
log.setLevel(logging.DEBUG)
@@ -88,9 +103,10 @@ def process_args(args):
return auto


def main() -> None:
args = parse_args()
auto = process_args(args)
@requires_sudo
def init() -> None:
args = parse_init_args()
auto = process_init_args(args)

question_list = [
questions.Clustering(),
@@ -125,5 +141,47 @@ def main() -> None:
sys.exit(1)


if __name__ == '__main__':
main()
def set_network_info() -> None:
"""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()

+ 1
- 1
tools/init/init/questions/__init__.py View File

@@ -31,7 +31,7 @@ from init.shell import (check, call, check_output, shell, sql, nc_wait,
log_wait, restart, download)
from init.config import Env, log
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()

+ 47
- 0
tools/init/init/questions/uninstall.py 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!')

+ 0
- 24
tools/init/init/set_network_info.py 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()

+ 3
- 2
tools/init/setup.py View File

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

Loading…
Cancel
Save