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