From 5fac8bf996800b789f4c66057139f2683ba63564 Mon Sep 17 00:00:00 2001 From: Nagy Karoly Gabriel Date: Thu, 25 Mar 2010 18:09:48 +0200 Subject: [PATCH] ecn-robots: First commit --- pgdb.py | 139 +++++++++++++++++++++++ vserver.py | 316 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 455 insertions(+) create mode 100644 pgdb.py create mode 100644 vserver.py diff --git a/pgdb.py b/pgdb.py new file mode 100644 index 0000000..46f3e52 --- /dev/null +++ b/pgdb.py @@ -0,0 +1,139 @@ +r'''Python interface to PostgreSQL for managing database nodes. +''' + +from subprocess import Popen, PIPE + +class Db(object): + + def __init__(self,name): + self._name = name + + def _run(self, command,args=[]): + execfn = [command] + list(args) + try: + p = Popen(execfn, stdout=PIPE, stderr=PIPE) + return p.communicate() + except Exception,e: + print str(e) + return -1 + + def _runsql(self, sql, db='postgres'): + given_sql = sql + out,error = self._run('/usr/bin/psql', ['-Aqt','-U','postgres','-d', db, '-c', given_sql]) + return out.strip() + + def _get_owner(self): + sql = "SELECT pg_get_userbyid(datdba) FROM pg_database WHERE datname ='"+self._name+"';" + own = self._runsql(sql) + return own + + def _set_owner(self, owner): + sql = "ALTER DATABASE "+self._name+" OWNER TO "+owner+";" + own = self._runsql(sql) + return own + + owner = property(_get_owner, _set_owner) + + @property + def OID(self): + sql = "SELECT oid FROM pg_database WHERE datname = '"+self._name+"';" + oid = self._runsql(sql) + return oid + + @property + def info(self): + information = {'size':'', 'encoding':'', 'collation':'','ctype':''} + information['size'] = self._runsql("SELECT pg_size_pretty(pg_database_size('"+self._name+"'));").strip() + information['encoding'], information['collation'], \ + information['ctype'] = self._runsql("""SELECT pg_encoding_to_char(encoding), + datcollate, datctype FROM pg_database WHERE datname='"""+self._name+"';").strip().split('|') + return information + + @property + def connections(self): + sql = "SELECT numbackends from pg_stat_database WHERE datname = '"+self._name+"';" + cncs = self._runsql(sql) + return cncs + + def user_exists(self, user): + sql = "SELECT rolname FROM pg_authid WHERE rolname = '"+user+"';" + u = self._runsql(sql).strip() + if (u == ""): + return False + return True + + def db_exists(self, xdb): + sql = "SELECT datname FROM pg_database WHERE datname = '"+xdb+"';" + d = self._runsql(sql).strip() + if (d == ""): + return False + return True + + def delete(self): + if self.db_exists(self._name) == True: + sql = "DROP DATABASE "+self._name+";" + drop = self._runsql(sql) + return drop + return "Failed" + + def create(self, own, coll, ctyp, enc=u'UTF8'): + if self.db_exists(self._name) == False: + sql = "CREATE DATABASE "+self._name+" WITH OWNER = "+own+" ENCODING = '"+enc+"' LC_COLLATE = '"+coll+"' LC_CTYPE = '"+ctyp+"';" + create = self._runsql(sql) + return create + return "Failed" + + def dump(self, path, method): + dump = Popen(['/usr/bin/pg_dump', '-U','postgres','-F'+ method, self._name], stdout=PIPE) + fl = open(path,"wb") + gz = Popen(['gzip'], stdin = dump.stdout, stdout = fl) + fl.close + return "Finished dumping "+self._name + + + def rename(self,old, new): + if self.db_exists(new) == True or self.db_exists(old) == False: + return "Cannot" + sql = "ALTER DATABASE "+old+" RENAME TO "+new+";" + rename = self._runsql(sql) + return rename + + def copy(): + pass + + def dblist(self): + sql = "SELECT datname FROM pg_database WHERE datname NOT IN ('template0', 'template1', 'postgres');" + dblist = self._runsql(sql) + return dblist + + def usrlist(self): + sql = "SELECT rolname FROM pg_authid WHERE rolcanlogin=true;" + usrlist = self._runsql(sql) + return usrlist + +def _test(): + #test = Db(u'postgres') + #print test.info['encoding'], test.info['collation'], test.info['ctype'] + #print test.owner + #print test.connections + #print "User aaa is ",test.user_exists("aaa") + #print "User postgres is ",test.user_exists("postgres") + #print "database xxxaaa is ", test.db_exists("xxxaaa") + #print "database postgres is ", test.db_exists("postgres") + #print test.dblist() + #test2 = Db(u'aaa') + #print test2.create(u'postgres', u'en_US.UTF-8', u'en_US.UTF-8') + #print test2.dblist() + #test2.rename(u'aaa',u'bbb') + #print test2.dblist() + #print test2.usrlist() + #print test2.owner + #test2.owner = u'karasz' + #print test2.owner + test = Db(u'aaa') + test.create(u'postgres', u'en_US.UTF-8', u'en_US.UTF-8') + print test.dump('/tmp/aaa.gz',u'p') + #test.delete() +if __name__ == '__main__': + _test() + diff --git a/vserver.py b/vserver.py new file mode 100644 index 0000000..8302208 --- /dev/null +++ b/vserver.py @@ -0,0 +1,316 @@ +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':'' } + returncode, kernel = self.p.run([u'uname', u'-r']) + information['kernel'] = kernel.strip() + 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() +