Reorganize vm/lxc/app functions
This commit is contained in:
parent
c213b0c0d8
commit
050b26f11f
@ -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)
|
||||
|
@ -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'
|
||||
]
|
||||
|
@ -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'])
|
||||
|
@ -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)
|
||||
|
@ -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'])
|
||||
|
@ -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):
|
||||
|
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from vmmgr import WSGIApp
|
||||
from vmmgr.wsgiapp import WSGIApp
|
||||
|
||||
application = WSGIApp()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user