commit
aaa3b1a6ec
5 changed files with 406 additions and 0 deletions
@ -0,0 +1,122 @@ |
|||
#!/usr/bin/env python |
|||
|
|||
import os, sys, pwd, glob, setuplib, subprocess |
|||
|
|||
def get_uid_gid(): |
|||
try: |
|||
target_user = pwd.getpwnam("pylsa") |
|||
except KeyError, e: |
|||
raise Exception("The PyLSA installation appears to have been corrupted.") |
|||
|
|||
return (target_user[2], target_user[3]) |
|||
|
|||
def switch_user(): |
|||
target_uid, target_gid = get_uid_gid() |
|||
|
|||
os.setgid(target_gid) |
|||
os.setuid(target_uid) |
|||
|
|||
def install(as_root=False): |
|||
try: |
|||
target_uid, target_gid = get_uid_gid() |
|||
except Exception, e: |
|||
target_uid, target_gid = (-1, -1) |
|||
|
|||
if as_root == True: |
|||
target_path = "/home/pylsa/.pylsa" |
|||
else: |
|||
target_path = os.path.expanduser("~/.pylsa") |
|||
|
|||
setuplib.create_directory(target_path, True, target_uid, target_gid) |
|||
setuplib.copy_file("src/daemon.py", "%s/daemon.py" % target_path, False, target_uid, target_gid) |
|||
setuplib.copy_file("pylsa.conf", "%s/pylsa.conf" % target_path, True, target_uid, target_gid) |
|||
subprocess.call(["tar", "-xzf", "psutil-0.6.1.tar.gz"]) |
|||
os.chdir("psutil-0.6.1") |
|||
|
|||
stfu = open("/dev/null", "w") |
|||
if subprocess.call(["python", "setup.py", "build"], stdout=stfu, stderr=stfu) != 0: |
|||
sys.stderr.write("An error occurred during compilation.\n") |
|||
exit(1) |
|||
|
|||
try: |
|||
build_dir = glob.glob("build/lib.*")[0] |
|||
except IndexError, e: |
|||
sys.stderr.write("Compilation appears to have failed, exiting...\n") |
|||
exit(1) |
|||
|
|||
setuplib.copy_directory(build_dir, "%s/lib" % target_path, False, target_uid, target_gid) |
|||
|
|||
def add_user(uname): |
|||
# Lock /etc/passwd and /etc/group so we can safely add a user. |
|||
open("/etc/passwd.lock", "w").close() |
|||
open("/etc/group.lock", "w").close() |
|||
|
|||
# Find highest non-reserved UID in the user list |
|||
passwd = open("/etc/passwd", "r+") |
|||
highest_uid = 1000 |
|||
|
|||
for line in passwd: |
|||
username, password, uid, gid, name, homedir, shell = line.split(":") |
|||
|
|||
if username == uname: |
|||
return True |
|||
|
|||
if int(uid) < 32000 and int(uid) > highest_uid: |
|||
highest_uid = int(uid) |
|||
|
|||
new_uid = highest_uid + 1 |
|||
|
|||
# Find highest non-reserved GID in the group list - we will assume same restrictions as for UID |
|||
grp = open("/etc/group", "r+") |
|||
highest_gid = 1000 |
|||
|
|||
for line in grp: |
|||
groupname, password, gid, users = line.split(":") |
|||
|
|||
if groupname == uname: |
|||
return True |
|||
|
|||
if int(gid) < 32000 and int(gid) > highest_gid: |
|||
highest_gid = int(gid) |
|||
|
|||
new_gid = highest_gid + 1 |
|||
|
|||
# Append new user and group |
|||
passwd.seek(0, 2) |
|||
grp.seek(0, 2) |
|||
|
|||
setuplib.create_directory("/home/%s" % uname, True, new_uid, new_gid, "u+rwx g+rx") |
|||
passwd.write("%s::%d:%d::/home/cvm:/bin/false\n" % (uname, new_uid, new_gid)) |
|||
grp.write("%s::%d:\n" % (uname, new_gid)) |
|||
|
|||
# We're done with /etc/passwd and /etc/group |
|||
passwd.close() |
|||
grp.close() |
|||
|
|||
# Remove the locks on /etc/passwd and /etc/group |
|||
os.remove("/etc/passwd.lock") |
|||
os.remove("/etc/group.lock") |
|||
|
|||
return True |
|||
|
|||
if os.getuid() == 0: |
|||
# Script is run as root |
|||
if os.path.isdir("/home/pylsa/.pylsa"): |
|||
# Already installed, run as pylsa user |
|||
switch_user() |
|||
exit(subprocess.call(["python", "/home/pylsa/.pylsa/daemon.py"])) |
|||
else: |
|||
# Not installed yet, install as pylsa user then run |
|||
add_user("pylsa") |
|||
install(True) |
|||
switch_user() |
|||
exit(subprocess.call(["python", "/home/pylsa/.pylsa/daemon.py"])) |
|||
else: |
|||
# Script is run as unprivileged user |
|||
if os.path.isdir(os.path.expanduser("~/.pylsa")): |
|||
# Already installed |
|||
exit(subprocess.call(["python", os.path.expanduser("~/.pylsa/daemon.py")])) |
|||
else: |
|||
# Not installed yet |
|||
install(False) |
|||
exit(subprocess.call(["python", os.path.expanduser("~/.pylsa/daemon.py")])) |
Binary file not shown.
@ -0,0 +1 @@ |
|||
|
@ -0,0 +1,148 @@ |
|||
import stat, os, shutil, urllib, subprocess |
|||
from itertools import dropwhile |
|||
|
|||
def create_directory(path, ignore_failure=True, uid=-1, gid=-1, modes=""): |
|||
try: |
|||
os.makedirs(path) |
|||
except OSError, e: |
|||
if ignore_failure: |
|||
pass |
|||
else: |
|||
raise |
|||
|
|||
if uid != -1 or gid != -1: |
|||
os.chown(path, uid, gid) |
|||
|
|||
if modes != "": |
|||
set_modes(path, modes) |
|||
|
|||
def copy_file(source, destination, ignore_failure=True, uid=-1, gid=-1, modes=""): |
|||
if ignore_failure == False: |
|||
if os.path.exists(destination): |
|||
raise Exception("Destination path already exists.") |
|||
|
|||
try: |
|||
shutil.copy(source, destination) |
|||
except IOError, e: |
|||
if ignore_failure: |
|||
pass |
|||
else: |
|||
raise |
|||
|
|||
if uid != -1 or gid != -1: |
|||
os.chown(destination, uid, gid) |
|||
|
|||
if modes != "": |
|||
set_modes(destination, modes) |
|||
|
|||
def copy_directory(source, destination, ignore_failure=True, uid=-1, gid=-1, modes=""): |
|||
if ignore_failure == False: |
|||
if os.path.exists(destination): |
|||
raise Exception("Destination path already exists.") |
|||
|
|||
try: |
|||
shutil.copytree(source, destination) |
|||
except IOError, e: |
|||
if ignore_failure: |
|||
pass |
|||
else: |
|||
raise |
|||
|
|||
if uid != -1 or gid != -1: |
|||
os.chown(destination, uid, gid) |
|||
|
|||
if modes != "": |
|||
set_modes(destination, modes) |
|||
|
|||
def create_file(path, contents="", uid=-1, gid=-1, modes=""): |
|||
f = open(path, "w") |
|||
|
|||
if contents != "": |
|||
f.write(contents) |
|||
|
|||
f.close() |
|||
|
|||
if uid != -1 or gid != -1: |
|||
os.chown(path, uid, gid) |
|||
|
|||
if modes != "": |
|||
set_modes(path, modes) |
|||
|
|||
def set_modes(path, modes): |
|||
mode_map = { |
|||
"u": { |
|||
"r": stat.S_IRUSR, |
|||
"w": stat.S_IWUSR, |
|||
"x": stat.S_IXUSR |
|||
}, |
|||
"g": { |
|||
"r": stat.S_IRGRP, |
|||
"w": stat.S_IWGRP, |
|||
"x": stat.S_IXGRP |
|||
}, |
|||
"o": { |
|||
"r": stat.S_IROTH, |
|||
"w": stat.S_IWOTH, |
|||
"x": stat.S_IXOTH |
|||
} |
|||
|
|||
} |
|||
|
|||
chunks = modes.split(" ") |
|||
mode = 0 |
|||
|
|||
for chunk in chunks: |
|||
usertype, changes = chunk.split("+") |
|||
|
|||
if usertype in mode_map: |
|||
for change in list(changes): |
|||
if change in mode_map[usertype]: |
|||
mode = mode | mode_map[usertype][change] |
|||
else: |
|||
raise Exception("Unknown permission in modes specification.") |
|||
elif usertype == "a": |
|||
for change in list(changes): |
|||
for i in mode_map: |
|||
if change in mode_map[i]: |
|||
mode = mode | mode_map[i][change] |
|||
else: |
|||
raise Exception("Unknown user type in modes specification.") |
|||
|
|||
os.chmod(path, mode) |
|||
|
|||
def download_file(name, mirrors): |
|||
try: |
|||
file_mirrors = mirrors[name] |
|||
except KeyError, e: |
|||
raise Exception("No such file exists in the mirror list.") |
|||
|
|||
for url in file_mirrors: |
|||
try: |
|||
urllib.urlretrieve(url, name) |
|||
except: |
|||
continue |
|||
else: |
|||
return name |
|||
|
|||
raise Exception("No functional mirrors found for this file.") |
|||
|
|||
def install_rpm(path): |
|||
stfu = open("/dev/null", "wb") |
|||
|
|||
result = subprocess.call(["yum", "--nogpgcheck", "install", "-y", path], stdout=stfu, stderr=stfu) |
|||
|
|||
stfu.close() |
|||
|
|||
if result != 0: |
|||
raise Exception("Failed to install package.") |
|||
|
|||
def install_remote_rpm(name, mirrors): |
|||
download_file(name, mirrors) |
|||
install_rpm(name) |
|||
|
|||
def rindex(lst, item): |
|||
# http://stackoverflow.com/a/6892096/1332715 |
|||
try: |
|||
return dropwhile(lambda x: lst[x].strip() != item, reversed(xrange(len(lst)))).next() |
|||
except StopIteration: |
|||
raise ValueError, "rindex(lst, item): item not in list" |
@ -0,0 +1,135 @@ |
|||
import sys, os, json |
|||
from datetime import datetime |
|||
|
|||
# We need to set both the Python module path and system environment path, to make sure that the daemon |
|||
# can find both the psutil library and its compiled libraries. |
|||
sys.path.insert(0, "%s/lib" % os.path.split(os.path.realpath(__file__))[0]) |
|||
os.environ['PATH'] = "%s/lib:%s" % (os.path.split(os.path.realpath(__file__))[0], os.environ['PATH']) |
|||
|
|||
import psutil |
|||
|
|||
import urlparse |
|||
import SocketServer, SimpleHTTPServer |
|||
|
|||
bind_ip = "" |
|||
port = 8081 |
|||
|
|||
def generate_stats(get_processes): |
|||
listed_filesystems = ["ext2", "ext3", "ext4", "reiserfs", "removable", "fixed", "simfs"] |
|||
|
|||
mem = psutil.virtual_memory() |
|||
swap = psutil.swap_memory() |
|||
disks = {} |
|||
|
|||
for disk in psutil.disk_partitions(True): |
|||
if disk.fstype in listed_filesystems: |
|||
usage = psutil.disk_usage(disk.mountpoint) |
|||
|
|||
disks[disk.mountpoint] = { |
|||
"device": disk.device, |
|||
"options": disk.opts, |
|||
"filesystem": disk.fstype, |
|||
"total": usage.total, |
|||
"free": usage.free |
|||
} |
|||
|
|||
return_data = { |
|||
"uptime": (datetime.now() - datetime.fromtimestamp(psutil.BOOT_TIME)).total_seconds(), |
|||
"memory": { |
|||
"total": mem.total, |
|||
"available": mem.available, |
|||
"used": mem.used, |
|||
"unused": mem.free |
|||
}, |
|||
"swap": { |
|||
"total": swap.total, |
|||
"used": swap.used, |
|||
"unused": swap.free, |
|||
"in": swap.sin, |
|||
"out": swap.sout |
|||
}, |
|||
"disk": disks, |
|||
"cpu": psutil.cpu_percent(percpu=True) |
|||
} |
|||
|
|||
if get_processes: |
|||
processes = [] |
|||
|
|||
for proc in psutil.process_iter(): |
|||
try: |
|||
cwd = proc.getcwd() |
|||
except psutil.AccessDenied, e: |
|||
cwd = None |
|||
|
|||
processes.append({ |
|||
"pid": proc.pid, |
|||
"parent": proc.ppid, |
|||
"name": proc.name, |
|||
"command": proc.cmdline, |
|||
"user": proc.username, |
|||
"status": str(proc.status), # If we don't explicitly use str() here, json module will get confused |
|||
"cwd": cwd, |
|||
"cpu": proc.get_cpu_percent(interval=0.1), |
|||
"rss": proc.get_memory_info()[0] |
|||
}) |
|||
|
|||
return_data['process'] = processes |
|||
|
|||
return return_data |
|||
|
|||
class StatsHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): |
|||
def do_GET(self): |
|||
req = urlparse.urlparse(self.path) |
|||
get_params = urlparse.parse_qs(req.query) |
|||
path = req.path |
|||
|
|||
try: |
|||
get_processes = (get_params['processes'][0] == "1") |
|||
except: |
|||
get_processes = False |
|||
|
|||
if path=='/': |
|||
self.send_response(200) |
|||
self.send_header('Content-type','text/json') |
|||
self.end_headers() |
|||
self.wfile.write(json.dumps(generate_stats(get_processes))) |
|||
return |
|||
else: |
|||
self.send_response(404) |
|||
self.send_header('Content-type','text/plain') |
|||
self.end_headers() |
|||
self.wfile.write("404 Not Found") |
|||
return |
|||
|
|||
#### Hacky way to daemonize the whole thing |
|||
|
|||
# Fork away |
|||
if os.fork(): exit(0) |
|||
os.umask(0) |
|||
os.setsid() |
|||
if os.fork(): exit(0) |
|||
|
|||
# Write PID to file |
|||
outfile = open("pylsa.pid", "w") |
|||
outfile.write('%i' % os.getpid()) |
|||
outfile.close() |
|||
|
|||
# Bind the server |
|||
httpd = SocketServer.ThreadingTCPServer((bind_ip, port), StatsHandler, False) |
|||
httpd.allow_reuse_address = True |
|||
httpd.server_bind() |
|||
httpd.server_activate() |
|||
|
|||
# If all went well, we'll redirect streams to silence them |
|||
sys.stdout.flush() |
|||
sys.stderr.flush() |
|||
si = file('/dev/null', 'r') |
|||
so = file('/dev/null', 'a+') |
|||
se = file('/dev/null', 'a+', 0) |
|||
os.dup2(si.fileno(), sys.stdin.fileno()) |
|||
os.dup2(so.fileno(), sys.stdout.fileno()) |
|||
os.dup2(se.fileno(), sys.stderr.fileno()) |
|||
|
|||
# Done! |
|||
print 'Port=',port |
|||
httpd.serve_forever() |
Loading…
Reference in new issue