You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

315 lines
11 KiB

r'''Python interface to Linux-VServer for managing hosting systems.
'''
__version__ = '0.2'
__author__ = 'Volker Grabsch'
__author_email__ = 'vog@notjusthosting.com'
__url__ = 'http://www.profv.de/python-vserver/'
__classifiers__ = '''
Development Status :: 5 - Production/Stable
Environment :: Console
Intended Audience :: Developers
Intended Audience :: System Administrators
License :: OSI Approved :: MIT License
Operating System :: POSIX :: Linux
Programming Language :: Python
Topic :: Software Development :: Libraries :: Python Modules
Topic :: System :: Installation/Setup
Topic :: System :: Systems Administration
Topic :: Utilities
'''
__license__ = '''
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject
to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
'''
import subprocess
import os
import urllib2
import re
import math
class Error(RuntimeError):
def __init__(self, message_format, *args):
self._message = message_format % tuple(args)
def __str__(self):
return self._message.encode('UTF-8')
class System(object):
def __init__(self):
pass
def read_uri(self, uri):
f = urllib2.urlopen(uri)
try:
return f.read().decode('UTF-8')
finally:
f.close()
def read_binary(self, path):
if path[0] != u'/':
raise Error(u'Not an absolute path: %s', path)
f = file(path.encode('UTF-8'), 'r')
try:
return f.read()
finally:
f.close()
def write_binary(self, path, mode, binary):
if path[0] != u'/':
raise Error(u'Not an absolute path: %s', path)
try:
current_mode = os.stat(path.encode('UTF-8')).st_mode & 07777
if current_mode != mode:
raise Error(u'File already exists with different mode: %s\n'
u'\n'
u'Current mode: %04o\n'
u'Expected mode: %04o',
path, current_mode, mode)
except OSError, e:
pass
fd = os.open(path.encode('UTF-8'), os.O_CREAT | os.O_WRONLY | os.O_TRUNC, mode)
f = os.fdopen(fd, 'w')
try:
f.write(binary)
finally:
f.close()
# set mode again, because os.open() never sets suid/sgid/sticky bits
os.chmod(path.encode('UTF-8'), mode)
def run(self, command, input=None, allowed_returncodes=None):
if isinstance(command, basestring):
raise Error(u'The command should be given as list, not string: %r',
command)
if input is None:
stdin = file(os.devnull, 'r')
else:
stdin = subprocess.PIPE
input = input.encode('UTF-8')
try:
process = subprocess.Popen(
[arg.encode('UTF-8') for arg in command],
bufsize=0,
stdin=stdin,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
close_fds=True,
shell=False,
cwd=None,
env={u'PATH': os.getenv(u'PATH')},
universal_newlines=False,
)
except OSError, e:
raise Error(u'Command %r: %s', command, e)
output, error = process.communicate(input)
output = output.decode('UTF-8')
error = error.decode('UTF-8')
returncode = process.returncode
if allowed_returncodes is None:
allowed_returncodes = [0]
if returncode not in allowed_returncodes:
raise Error(u'Command failed: %r\n\nReturn code: %i\n\nOutput:\n%s\n\nError:\n%s',
command, returncode, output.strip('\n'), error.strip('\n'))
return returncode, output.decode('UTF-8')
def convert_human(self, secs):
mins, secs = divmod(secs, 60)
hours, mins = divmod(mins, 60)
days, hours = divmod(hours, 24)
interval = '%d days %d hours %d minutes %d seconds' % (days, hours, mins, secs)
return interval
def get_line_value(self, file, tag):
if os.path.isfile(file):
with open(file) as f:
val = f.readline()
while val:
if val.startswith(tag):
val=val.replace(tag,'')
value = val.strip()
val = f.readline()
return value
return 'None'
class Host(object):
def __init__(self):
self.p = System()
def vserver_list(self):
list = os.listdir(u'/etc/vservers/')
list.remove(u'.defaults')
list.remove(u'.distributions')
list.remove(u'lost+found')
return list
@property
def info(self):
information= {'kernel':'', 'uptime':'' }
information['kernel'] = os.uname()[2]
secs = math.ceil(float(self.p.read_binary(u'/proc/uptime').split()[1]))
information['uptime'] = self.p.convert_human(secs)
return information
class VServer(object):
def __init__(self, name):
self.p = System()
self._name = name
self._dirs = {}
def read_uri(self, uri):
return self.p.read_uri(uri)
def _one_line(self, text):
if text == u'':
raise Error(u'Empty line.')
if u'\n' in text[:-1]:
raise Error(u'Multiple lines where a single line was expected:\n%s', text.strip(u'\n'))
if text[-1] != u'\n':
raise Error(u'Incomplete line: %s', text)
return text[:-1]
def _path(self, path_type, path):
if path[0] != u'/':
raise Error(u'Not an absolute path: %s', path)
if not self._dirs.has_key(path_type):
returncode, output = self.p.run([u'vserver-info', self._name, path_type])
self._dirs[path_type] = self._one_line(output)
return self._dirs[path_type] + path
def _read_cfg(self, path):
return self.p.read_binary(self._path(u'CFGDIR', path)).decode('UTF-8')
def _write_cfg(self, path, mode, content):
self.p.write_binary(self._path(u'CFGDIR', path), mode, content.encode('UTF-8'))
def read_binary(self, path):
return self.p.read_binary(self._path(u'VDIR', path))
def write_binary(self, path, mode, binary):
self.p.write_binary(self._path(u'VDIR', path), mode, binary)
def read(self, path):
return self.read_binary(path).decode('UTF-8')
def write(self, path, mode, content):
self.write_binary(path, mode, content.encode('UTF-8'))
def read_one_line(self, path):
return self._one_line(self.read(path))
def write_one_line(self, path, mode, line):
if u'\n' in line:
raise Error(u'Invalid line break in: %r', line)
self.write(path, mode, u'%s\n' % (line,))
def run(self, command, input=None, allowed_returncodes=None):
return self.p.run([u'vserver', self._name, u'exec'] + command,
input, allowed_returncodes)
def _get_running(self):
returncode, output = self.p.run([u'vserver', self._name, u'running'],
allowed_returncodes=[0, 1])
return (returncode == 0)
def _set_running(self, running):
if running:
self.p.run([u'vserver', self._name, u'start'])
else:
self.p.run([u'vserver', self._name, u'stop'])
running = property(_get_running, _set_running)
@property
def context(self):
"""Get the context of speciffied vserver."""
with open(u'/etc/vservers/' + self._name + u'/context') as f:
ctx = f.read().strip()
return ctx
@property
def load(self):
"""Get the load of specified vserver."""
fl = u'/proc/virtual/' + self.context + u'/cvirt'
load = self.p.get_line_value(fl, u'loadavg:')
return load
@property
def uptime(self):
"""Get uptime of the vserver."""
fl = u'/proc/virtual/' + self.context + u'/cvirt'
bupx = self.p.get_line_value(fl, u'BiasUptime:')
if (bupx != 'None'):
bupx = float(bupx)
hupx = math.ceil(float(self.p.read_binary(u'/proc/uptime').split()[1]))
cupx = hupx - bupx
return self.p.convert_human(cupx)
return bupx
def _get_start_on_boot(self):
try:
mark = self._read_cfg(u'/apps/init/mark')
except IOError, e:
return False
if mark == u'default\n':
return True
elif mark == u'':
return False
else:
raise Error(u'Unexpected init mark: %r', mark)
def _set_start_on_boot(self, start_on_boot):
if start_on_boot:
self._write_cfg(u'/apps/init/mark', 0644, u'default\n')
else:
self._write_cfg(u'/apps/init/mark', 0644, u'')
start_on_boot = property(_get_start_on_boot, _set_start_on_boot)
def build(self, ip, fqdn, interface, method):
self.p.run([u'vserver', self._name, u'build',
u'--hostname', self._name,
u'--interface', interface,
u'-m'] + method)
self.write(u'/etc/hosts', 0644,
u'%s %s %s\n' % (ip, fqdn, self._name)
)
self._write_cfg(u'/fstab', 0644,
# disable ramdisk /tmp
u'none /proc proc defaults 0 0\n'
u'none /dev/pts devpts gid=5,mode=620 0 0\n'
)
self.running = True
def delete(self):
self.p.run([u'vserver', self._name, u'delete'],
input=u'Y\n')
def _test():
test = Host()
print 'Host kernel is',test.info['kernel']
print 'Uptime of host is', test.info['uptime']
lst = test.vserver_list()
print '%-10s %-20s %-10s %-20s %-20s' % ('Context', 'Name', 'Running', 'Load', 'Uptime')
for i in range(len(lst)):
vs = VServer(lst[i])
print '%-10s %-20s %-10s %-20s %-20s' % (vs.context, lst[i], vs.running, vs.load, vs.uptime)
if __name__ == '__main__':
_test()