@ -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() |
|||
|
@ -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() |
|||
|