Reorganize vm/lxc/app functions
This commit is contained in:
parent
c213b0c0d8
commit
050b26f11f
@ -2,7 +2,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import argparse
|
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')
|
parser = argparse.ArgumentParser(description='VM application manager')
|
||||||
subparsers = parser.add_subparsers()
|
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')
|
parser_unregister_proxy.add_argument('app', help='Application name')
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
conf = Config()
|
vmmgr = VMMgr(Config())
|
||||||
vmmgr = VMMgr(conf)
|
|
||||||
lxcmgr = LXCMgr(conf)
|
|
||||||
if args.action == 'register-app':
|
if args.action == 'register-app':
|
||||||
# Used by app install scripts
|
# Used by app install scripts
|
||||||
vmmgr.register_app(args.app, args.login, args.password)
|
vmmgr.register_app(args.app, args.login, args.password)
|
||||||
@ -58,7 +58,7 @@ elif args.action == 'unregister-container':
|
|||||||
lxcmgr.unregister_container()
|
lxcmgr.unregister_container()
|
||||||
elif args.action == 'register-proxy':
|
elif args.action == 'register-proxy':
|
||||||
# Used in init scripts
|
# Used in init scripts
|
||||||
lxcmgr.register_proxy(args.app, args.host)
|
vmmgr.register_proxy(args.app, args.host)
|
||||||
elif args.action == 'unregister-proxy':
|
elif args.action == 'unregister-proxy':
|
||||||
# Used in init scripts
|
# Used in init scripts
|
||||||
lxcmgr.unregister_proxy(args.app)
|
vmmgr.unregister_proxy(args.app)
|
||||||
|
@ -1,15 +1 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- 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:
|
except:
|
||||||
pass
|
pass
|
||||||
return []
|
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
|
import subprocess
|
||||||
|
|
||||||
from . import templates
|
from . import templates
|
||||||
|
from .config import Config
|
||||||
from .paths import HOSTS_FILE, HOSTS_LOCK, LXC_ROOT, NGINX_DIR
|
from .paths import HOSTS_FILE, HOSTS_LOCK, LXC_ROOT, NGINX_DIR
|
||||||
|
|
||||||
class LXCMgr:
|
def prepare_container():
|
||||||
def __init__(self, conf):
|
# Extract the variables from values given via lxc.hook.pre-start hook
|
||||||
# Load JSON configuration
|
app = os.environ['LXC_NAME']
|
||||||
self.conf = conf
|
# Remove ephemeral layer data
|
||||||
|
clean_ephemeral_layer(app)
|
||||||
|
# Configure host and common params used in the app
|
||||||
|
configure_app(app)
|
||||||
|
|
||||||
def prepare_container(self):
|
def clean_ephemeral_layer(app):
|
||||||
# Extract the variables from values given via lxc.hook.pre-start hook
|
# Cleans containers ephemeral layer.
|
||||||
app = os.environ['LXC_NAME']
|
# This is done early in the container start process, so the inode of the delta0 directory must remain unchanged
|
||||||
# Remove ephemeral layer data
|
layer = os.path.join(LXC_ROOT, app, 'delta0')
|
||||||
self.clean_ephemeral_layer(app)
|
if os.path.exists(layer):
|
||||||
# Configure host and common params used in the app
|
for item in os.scandir(layer):
|
||||||
self.configure_app(app)
|
shutil.rmtree(item.path) if item.is_dir() else os.unlink(item.path)
|
||||||
|
|
||||||
def clean_ephemeral_layer(self, app):
|
def register_container():
|
||||||
# Cleans containers ephemeral layer.
|
# Extract the variables from values given via lxc.hook.start-host hook
|
||||||
# This is done early in the container start process, so the inode of the delta0 directory must remain unchanged
|
app = os.environ['LXC_NAME']
|
||||||
layer = os.path.join(LXC_ROOT, app, 'delta0')
|
pid = os.environ['LXC_PID']
|
||||||
if os.path.exists(layer):
|
# Lease the first unused IP to the container
|
||||||
for item in os.scandir(layer):
|
ip = update_hosts_lease(app, True)
|
||||||
shutil.rmtree(item.path) if item.is_dir() else os.unlink(item.path)
|
# 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):
|
def unregister_container():
|
||||||
# Extract the variables from values given via lxc.hook.start-host hook
|
# Extract the variables from values given via lxc.hook.post-stop hook
|
||||||
app = os.environ['LXC_NAME']
|
app = os.environ['LXC_NAME']
|
||||||
pid = os.environ['LXC_PID']
|
# Release the container IP
|
||||||
# Lease the first unused IP to the container
|
update_hosts_lease(app, False)
|
||||||
ip = self.update_hosts_lease(app, True)
|
# Remove ephemeral layer data
|
||||||
# Set IP in container based on PID given via lxc.hook.start-host hook
|
clean_ephemeral_layer(app)
|
||||||
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(self):
|
def update_hosts_lease(app, is_request):
|
||||||
# Extract the variables from values given via lxc.hook.post-stop hook
|
# This is a poor man's DHCP server which uses /etc/hosts as lease database
|
||||||
app = os.environ['LXC_NAME']
|
# Leases the first unused IP from range 172.17.0.0/16
|
||||||
# Release the container IP
|
# Uses file lock as interprocess mutex
|
||||||
self.update_hosts_lease(app, False)
|
ip = None
|
||||||
# Remove ephemeral layer data
|
with open(HOSTS_LOCK, 'w') as lock:
|
||||||
self.clean_ephemeral_layer(app)
|
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):
|
def configure_app(app):
|
||||||
# This is a poor man's DHCP server which uses /etc/hosts as lease database
|
# Supply common configuration for the application. Done as part of container preparation during service startup
|
||||||
# Leases the first unused IP from range 172.17.0.0/16
|
script = os.path.join('/srv', app, 'update-conf.sh')
|
||||||
# Uses file lock as interprocess mutex
|
if os.path.exists(script):
|
||||||
ip = None
|
conf = Config()
|
||||||
with open(HOSTS_LOCK, 'w') as lock:
|
setup_env = os.environ.copy()
|
||||||
fcntl.lockf(lock, fcntl.LOCK_EX)
|
setup_env['DOMAIN'] = conf['host']['domain']
|
||||||
# Load all existing records
|
setup_env['PORT'] = conf['host']['port']
|
||||||
with open(HOSTS_FILE, 'r') as f:
|
setup_env['EMAIL'] = conf['common']['email']
|
||||||
leases = [l.strip().split(' ', 1) for l in f]
|
setup_env['GMAPS_API_KEY'] = conf['common']['gmaps-api-key']
|
||||||
# If this call is a request for lease, find the first unassigned IP
|
subprocess.run([script], env=setup_env, check=True)
|
||||||
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()
|
|
||||||
|
@ -21,7 +21,7 @@ class VMMgr:
|
|||||||
|
|
||||||
def register_app(self, app, login, password):
|
def register_app(self, app, login, password):
|
||||||
# Register newly installed application, its metadata and credentials
|
# 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)
|
meta = json.load(f)
|
||||||
self.conf['apps'][app] = {**meta,
|
self.conf['apps'][app] = {**meta,
|
||||||
'login': login if login else 'N/A',
|
'login': login if login else 'N/A',
|
||||||
@ -34,6 +34,17 @@ class VMMgr:
|
|||||||
except:
|
except:
|
||||||
pass
|
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):
|
def update_host(self, domain, port):
|
||||||
# Update domain and port and rebuild all configuration. Web interface calls restart_nginx() in WSGI close handler
|
# Update domain and port and rebuild all configuration. Web interface calls restart_nginx() in WSGI close handler
|
||||||
self.domain = self.conf['host']['domain'] = domain
|
self.domain = self.conf['host']['domain'] = domain
|
||||||
@ -58,6 +69,12 @@ class VMMgr:
|
|||||||
with open(ISSUE_FILE, 'w') as f:
|
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)))
|
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):
|
def update_password(self, oldpassword, newpassword):
|
||||||
# Update LUKS password and adminpwd for WSGI application
|
# Update LUKS password and adminpwd for WSGI application
|
||||||
pwinput = '{}\n{}'.format(oldpassword, newpassword).encode()
|
pwinput = '{}\n{}'.format(oldpassword, newpassword).encode()
|
||||||
@ -135,3 +152,9 @@ class VMMgr:
|
|||||||
os.chmod(crypto.CERT_KEY_FILE, 0o640)
|
os.chmod(crypto.CERT_KEY_FILE, 0o640)
|
||||||
# Reload nginx
|
# Reload nginx
|
||||||
self.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):
|
if not validator.is_valid_email(email):
|
||||||
request.session['msg'] = 'common:error:{}'.format(request.session.lang.invalid_email(email))
|
request.session['msg'] = 'common:error:{}'.format(request.session.lang.invalid_email(email))
|
||||||
else:
|
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())
|
request.session['msg'] = 'common:info:{}'.format(request.session.lang.common_updated())
|
||||||
return redirect('/setup-apps')
|
return redirect('/setup-apps')
|
||||||
|
|
||||||
@ -380,13 +380,13 @@ class WSGIApp:
|
|||||||
def reboot_vm_action(self, request):
|
def reboot_vm_action(self, request):
|
||||||
# Reboots VM
|
# Reboots VM
|
||||||
response = self.render_json({'ok': request.session.lang.reboot_initiated()})
|
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
|
return response
|
||||||
|
|
||||||
def shutdown_vm_action(self, request):
|
def shutdown_vm_action(self, request):
|
||||||
# Shuts down VM
|
# Shuts down VM
|
||||||
response = self.render_json({'ok': request.session.lang.shutdown_initiated()})
|
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
|
return response
|
||||||
|
|
||||||
def reload_config_action(self, request):
|
def reload_config_action(self, request):
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from vmmgr import WSGIApp
|
from vmmgr.wsgiapp import WSGIApp
|
||||||
|
|
||||||
application = WSGIApp()
|
application = WSGIApp()
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user