From 050b26f11ff85ec0c00cbbd8d001dca599a809f2 Mon Sep 17 00:00:00 2001 From: Disassembler Date: Tue, 19 Feb 2019 16:05:21 +0100 Subject: [PATCH] Reorganize vm/lxc/app functions --- usr/bin/vmmgr | 12 +-- usr/lib/python3.6/vmmgr/__init__.py | 14 --- usr/lib/python3.6/vmmgr/appmgr.py | 12 --- usr/lib/python3.6/vmmgr/lxcmgr.py | 150 +++++++++++++--------------- usr/lib/python3.6/vmmgr/vmmgr.py | 25 ++++- usr/lib/python3.6/vmmgr/wsgiapp.py | 6 +- usr/share/vmmgr/wsgi.py | 2 +- 7 files changed, 102 insertions(+), 119 deletions(-) diff --git a/usr/bin/vmmgr b/usr/bin/vmmgr index b5b3b76..4118c8f 100755 --- a/usr/bin/vmmgr +++ b/usr/bin/vmmgr @@ -2,7 +2,9 @@ # -*- coding: utf-8 -*- import argparse -from vmmgr import Config, LXCMgr, VMMgr +from vmmgr import lxcmgr +from vmmgr.config import Config +from vmmgr.vmmgr import VMMgr parser = argparse.ArgumentParser(description='VM application manager') subparsers = parser.add_subparsers() @@ -38,9 +40,7 @@ parser_unregister_proxy.set_defaults(action='unregister-proxy') parser_unregister_proxy.add_argument('app', help='Application name') args = parser.parse_args() -conf = Config() -vmmgr = VMMgr(conf) -lxcmgr = LXCMgr(conf) +vmmgr = VMMgr(Config()) if args.action == 'register-app': # Used by app install scripts vmmgr.register_app(args.app, args.login, args.password) @@ -58,7 +58,7 @@ elif args.action == 'unregister-container': lxcmgr.unregister_container() elif args.action == 'register-proxy': # Used in init scripts - lxcmgr.register_proxy(args.app, args.host) + vmmgr.register_proxy(args.app, args.host) elif args.action == 'unregister-proxy': # Used in init scripts - lxcmgr.unregister_proxy(args.app) + vmmgr.unregister_proxy(args.app) diff --git a/usr/lib/python3.6/vmmgr/__init__.py b/usr/lib/python3.6/vmmgr/__init__.py index e897d6e..40a96af 100644 --- a/usr/lib/python3.6/vmmgr/__init__.py +++ b/usr/lib/python3.6/vmmgr/__init__.py @@ -1,15 +1 @@ # -*- coding: utf-8 -*- - -from .appmgr import AppMgr -from .config import Config -from .lxcmgr import LXCMgr -from .vmmgr import VMMgr -from .wsgiapp import WSGIApp - -__all__ = [ - 'AppMgr', - 'Config', - 'LXCMgr', - 'VMMgr', - 'WSGIApp' -] diff --git a/usr/lib/python3.6/vmmgr/appmgr.py b/usr/lib/python3.6/vmmgr/appmgr.py index 147fcee..d44a738 100644 --- a/usr/lib/python3.6/vmmgr/appmgr.py +++ b/usr/lib/python3.6/vmmgr/appmgr.py @@ -114,15 +114,3 @@ class AppMgr: except: pass return [] - - def update_common_settings(self, email, gmaps_api_key): - # Update common configuration values - self.conf['common']['email'] = email - self.conf['common']['gmaps-api-key'] = gmaps_api_key - self.conf.save() - - def shutdown_vm(self): - subprocess.run(['/sbin/poweroff']) - - def reboot_vm(self): - subprocess.run(['/sbin/reboot']) diff --git a/usr/lib/python3.6/vmmgr/lxcmgr.py b/usr/lib/python3.6/vmmgr/lxcmgr.py index fa31c29..de02d81 100644 --- a/usr/lib/python3.6/vmmgr/lxcmgr.py +++ b/usr/lib/python3.6/vmmgr/lxcmgr.py @@ -6,92 +6,78 @@ import shutil import subprocess from . import templates +from .config import Config from .paths import HOSTS_FILE, HOSTS_LOCK, LXC_ROOT, NGINX_DIR -class LXCMgr: - def __init__(self, conf): - # Load JSON configuration - self.conf = conf +def prepare_container(): + # Extract the variables from values given via lxc.hook.pre-start hook + app = os.environ['LXC_NAME'] + # Remove ephemeral layer data + clean_ephemeral_layer(app) + # Configure host and common params used in the app + configure_app(app) - def prepare_container(self): - # Extract the variables from values given via lxc.hook.pre-start hook - app = os.environ['LXC_NAME'] - # Remove ephemeral layer data - self.clean_ephemeral_layer(app) - # Configure host and common params used in the app - self.configure_app(app) +def clean_ephemeral_layer(app): + # Cleans containers ephemeral layer. + # This is done early in the container start process, so the inode of the delta0 directory must remain unchanged + layer = os.path.join(LXC_ROOT, app, 'delta0') + if os.path.exists(layer): + for item in os.scandir(layer): + shutil.rmtree(item.path) if item.is_dir() else os.unlink(item.path) - def clean_ephemeral_layer(self, app): - # Cleans containers ephemeral layer. - # This is done early in the container start process, so the inode of the delta0 directory must remain unchanged - layer = os.path.join(LXC_ROOT, app, 'delta0') - if os.path.exists(layer): - for item in os.scandir(layer): - shutil.rmtree(item.path) if item.is_dir() else os.unlink(item.path) +def register_container(): + # Extract the variables from values given via lxc.hook.start-host hook + app = os.environ['LXC_NAME'] + pid = os.environ['LXC_PID'] + # Lease the first unused IP to the container + ip = update_hosts_lease(app, True) + # Set IP in container based on PID given via lxc.hook.start-host hook + cmd = 'ip addr add {}/16 broadcast 172.17.255.255 dev eth0 && ip route add default via 172.17.0.1'.format(ip) + subprocess.run(['nsenter', '-a', '-t', pid, '--', '/bin/sh', '-c', cmd]) - def register_container(self): - # Extract the variables from values given via lxc.hook.start-host hook - app = os.environ['LXC_NAME'] - pid = os.environ['LXC_PID'] - # Lease the first unused IP to the container - ip = self.update_hosts_lease(app, True) - # Set IP in container based on PID given via lxc.hook.start-host hook - cmd = 'ip addr add {}/16 broadcast 172.17.255.255 dev eth0 && ip route add default via 172.17.0.1'.format(ip) - subprocess.run(['nsenter', '-a', '-t', pid, '--', '/bin/sh', '-c', cmd]) +def unregister_container(): + # Extract the variables from values given via lxc.hook.post-stop hook + app = os.environ['LXC_NAME'] + # Release the container IP + update_hosts_lease(app, False) + # Remove ephemeral layer data + clean_ephemeral_layer(app) - def unregister_container(self): - # Extract the variables from values given via lxc.hook.post-stop hook - app = os.environ['LXC_NAME'] - # Release the container IP - self.update_hosts_lease(app, False) - # Remove ephemeral layer data - self.clean_ephemeral_layer(app) +def update_hosts_lease(app, is_request): + # This is a poor man's DHCP server which uses /etc/hosts as lease database + # Leases the first unused IP from range 172.17.0.0/16 + # Uses file lock as interprocess mutex + ip = None + with open(HOSTS_LOCK, 'w') as lock: + fcntl.lockf(lock, fcntl.LOCK_EX) + # Load all existing records + with open(HOSTS_FILE, 'r') as f: + leases = [l.strip().split(' ', 1) for l in f] + # If this call is a request for lease, find the first unassigned IP + if is_request: + used_ips = [l[0] for l in leases] + for i in range(2, 65534): + ip = '172.17.{}.{}'. format(i // 256, i % 256) + if ip not in used_ips: + leases.append([ip, app]) + break + # Otherwise it is a release in which case we just delete the record + else: + leases = [l for l in leases if l[1] != app] + # Write the contents back to the file + with open(HOSTS_FILE, 'w') as f: + for lease in leases: + f.write('{} {}\n'.format(lease[0], lease[1])) + return ip - def update_hosts_lease(self, app, is_request): - # This is a poor man's DHCP server which uses /etc/hosts as lease database - # Leases the first unused IP from range 172.17.0.0/16 - # Uses file lock as interprocess mutex - ip = None - with open(HOSTS_LOCK, 'w') as lock: - fcntl.lockf(lock, fcntl.LOCK_EX) - # Load all existing records - with open(HOSTS_FILE, 'r') as f: - leases = [l.strip().split(' ', 1) for l in f] - # If this call is a request for lease, find the first unassigned IP - if is_request: - used_ips = [l[0] for l in leases] - for i in range(2, 65534): - ip = '172.17.{}.{}'. format(i // 256, i % 256) - if ip not in used_ips: - leases.append([ip, app]) - break - # Otherwise it is a release in which case we just delete the record - else: - leases = [l for l in leases if l[1] != app] - # Write the contents back to the file - with open(HOSTS_FILE, 'w') as f: - for lease in leases: - f.write('{} {}\n'.format(lease[0], lease[1])) - return ip - - def configure_app(self, app): - # Supply common configuration for the application. Done as part of container preparation during service startup - script = os.path.join('/srv', app, 'update-conf.sh') - if os.path.exists(script): - setup_env = os.environ.copy() - setup_env['DOMAIN'] = self.conf['host']['domain'] - setup_env['PORT'] = self.conf['host']['port'] - setup_env['EMAIL'] = self.conf['common']['email'] - setup_env['GMAPS_API_KEY'] = self.conf['common']['gmaps-api-key'] - subprocess.run([script], env=setup_env, check=True) - - def register_proxy(self, app, host): - # Setup proxy configuration and reload nginx - with open(os.path.join(NGINX_DIR, '{}.conf'.format(app)), 'w') as f: - f.write(templates.NGINX.format(app=app, host=host, domain=self.conf['host']['domain'], port=self.conf['host']['port'])) - self.reload_nginx() - - def unregister_proxy(self, app): - # Remove proxy configuration and reload nginx - os.unlink(os.path.join(NGINX_DIR, '{}.conf'.format(app))) - self.reload_nginx() +def configure_app(app): + # Supply common configuration for the application. Done as part of container preparation during service startup + script = os.path.join('/srv', app, 'update-conf.sh') + if os.path.exists(script): + conf = Config() + setup_env = os.environ.copy() + setup_env['DOMAIN'] = conf['host']['domain'] + setup_env['PORT'] = conf['host']['port'] + setup_env['EMAIL'] = conf['common']['email'] + setup_env['GMAPS_API_KEY'] = conf['common']['gmaps-api-key'] + subprocess.run([script], env=setup_env, check=True) diff --git a/usr/lib/python3.6/vmmgr/vmmgr.py b/usr/lib/python3.6/vmmgr/vmmgr.py index d38b4eb..e414d11 100644 --- a/usr/lib/python3.6/vmmgr/vmmgr.py +++ b/usr/lib/python3.6/vmmgr/vmmgr.py @@ -21,7 +21,7 @@ class VMMgr: def register_app(self, app, login, password): # Register newly installed application, its metadata and credentials - with open('/var/lib/lxcpkgs/{app}/meta'.format(app)) as f: + with open('/var/lib/lxcpkgs/{}/meta'.format(app)) as f: meta = json.load(f) self.conf['apps'][app] = {**meta, 'login': login if login else 'N/A', @@ -34,6 +34,17 @@ class VMMgr: except: pass + def register_proxy(self, app, host): + # Setup proxy configuration and reload nginx + with open(os.path.join(NGINX_DIR, '{}.conf'.format(app)), 'w') as f: + f.write(templates.NGINX.format(app=app, host=host, domain=self.conf['host']['domain'], port=self.conf['host']['port'])) + self.reload_nginx() + + def unregister_proxy(self, app): + # Remove proxy configuration and reload nginx + os.unlink(os.path.join(NGINX_DIR, '{}.conf'.format(app))) + self.reload_nginx() + def update_host(self, domain, port): # Update domain and port and rebuild all configuration. Web interface calls restart_nginx() in WSGI close handler self.domain = self.conf['host']['domain'] = domain @@ -58,6 +69,12 @@ class VMMgr: with open(ISSUE_FILE, 'w') as f: f.write(templates.ISSUE.format(url=net.compile_url(self.domain, self.port), ip=net.compile_url(net.get_local_ip(), self.port))) + def update_common_settings(self, email, gmaps_api_key): + # Update common configuration values + self.conf['common']['email'] = email + self.conf['common']['gmaps-api-key'] = gmaps_api_key + self.conf.save() + def update_password(self, oldpassword, newpassword): # Update LUKS password and adminpwd for WSGI application pwinput = '{}\n{}'.format(oldpassword, newpassword).encode() @@ -135,3 +152,9 @@ class VMMgr: os.chmod(crypto.CERT_KEY_FILE, 0o640) # Reload nginx self.reload_nginx() + + def shutdown_vm(self): + subprocess.run(['/sbin/poweroff']) + + def reboot_vm(self): + subprocess.run(['/sbin/reboot']) diff --git a/usr/lib/python3.6/vmmgr/wsgiapp.py b/usr/lib/python3.6/vmmgr/wsgiapp.py index acd53b6..d6fd7b6 100644 --- a/usr/lib/python3.6/vmmgr/wsgiapp.py +++ b/usr/lib/python3.6/vmmgr/wsgiapp.py @@ -308,7 +308,7 @@ class WSGIApp: if not validator.is_valid_email(email): request.session['msg'] = 'common:error:{}'.format(request.session.lang.invalid_email(email)) else: - self.appmgr.update_common_settings(email, request.form['gmaps-api-key']) + self.vmmgr.update_common_settings(email, request.form['gmaps-api-key']) request.session['msg'] = 'common:info:{}'.format(request.session.lang.common_updated()) return redirect('/setup-apps') @@ -380,13 +380,13 @@ class WSGIApp: def reboot_vm_action(self, request): # Reboots VM response = self.render_json({'ok': request.session.lang.reboot_initiated()}) - response.call_on_close(self.appmgr.reboot_vm) + response.call_on_close(self.vmmgr.reboot_vm) return response def shutdown_vm_action(self, request): # Shuts down VM response = self.render_json({'ok': request.session.lang.shutdown_initiated()}) - response.call_on_close(self.appmgr.shutdown_vm) + response.call_on_close(self.vmmgr.shutdown_vm) return response def reload_config_action(self, request): diff --git a/usr/share/vmmgr/wsgi.py b/usr/share/vmmgr/wsgi.py index d2ad901..b870028 100755 --- a/usr/share/vmmgr/wsgi.py +++ b/usr/share/vmmgr/wsgi.py @@ -1,7 +1,7 @@ #!/usr/bin/python3 # -*- coding: utf-8 -*- -from vmmgr import WSGIApp +from vmmgr.wsgiapp import WSGIApp application = WSGIApp()