diff --git a/conf.json b/conf.json new file mode 100644 index 0000000..ef43c90 --- /dev/null +++ b/conf.json @@ -0,0 +1,5 @@ +{ + "zookeeper" : "10.8.0.26:2181", + "api": "http://127.0.0.1:9076/v1", + "ws": "ws://py.difrex.ru/socket" +} diff --git a/rbmd.py b/rbmd.py new file mode 100644 index 0000000..8beabd0 --- /dev/null +++ b/rbmd.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python + +import bcrypt +import json +from kazoo.client import KazooClient +from kazoo.recipe.watchers import DataWatch +import logging +import requests +import sqlite3 +import tornado.escape +import tornado.ioloop +import tornado.options +import tornado.web +import tornado.websocket +import threading +import os.path +import uuid + + +from tornado.options import define, options + +define("port", default=8000, help="run on the given port", type=int) +define("sqlite_db", default=os.path.join(os.path.dirname(__file__), "auth.db"), help="User DB") + +class Application(tornado.web.Application): + def __init__(self): + handlers = [ + (r"/", MainHandler), + (r"/socket", SocketHandler), + (r"/mount", MountHandler), + (r"/unmount", UnmountHandler), + (r"/resolve", ResolveHandler), + (r"/status", StatusHandler), + (r"/user", User), + (r"/login", Auth), + + ] + settings = dict( + cookie_secret="=&r854^9nk7ys49@m7a5eu(g&jn8pytk6f%@quumabt*x5e*)i", + template_path=os.path.join(os.path.dirname(__file__), "templates/rbmd"), + static_path=os.path.join(os.path.dirname(__file__), "static/rbmd"), + xsrf_cookies=True, + websocket_ping_interval = 59, + login_url="/login", + ) + super(Application, self).__init__(handlers, **settings) + + +class MainHandler(tornado.web.RequestHandler): + @tornado.web.authenticated + def get(self): + user_id = self.get_secure_cookie("user") + ws = config("ws") + metrics = json.loads(action('metrics', 'get')) + my_status = zk_fetch("/rbmd/log/health") #action('status', 'get') + dct = {'ws': ws, 'metrics': metrics, 'status': my_status} + self.render("index.html", **dct) + + def get_current_user(self): + return self.get_secure_cookie("user") + + +class Auth(tornado.web.RequestHandler): + def get(self): + self.render("login.html", error=None) + + def post(self): + con = sqlite3.connect(options.sqlite_db) + cur = con.cursor() + cur.execute("SELECT * FROM users WHERE name = :name", {"name": self.get_argument("username")}) + #cur.execute("SELECT * FROM users WHERE name = ?", (self.get_argument("username"),)) + user = cur.fetchone() + cur.close() + con.close() + if not user: + self.render("login.html", error="user not found") + return + hashed_password = bcrypt.hashpw(tornado.escape.utf8(self.get_argument("password")), tornado.escape.utf8(user[2])) + if hashed_password == user[2]: + self.set_secure_cookie("user", str(user[0])) + self.redirect("/") + else: + self.render("login.html", error="incorrect password") + + +#create new user +class User(tornado.web.RequestHandler): + def get(self): + con = sqlite3.connect(options.sqlite_db) + cur = con.cursor() + data = self.request.arguments + hashed_password = bcrypt.hashpw(tornado.escape.utf8(self.get_argument("password")), bcrypt.gensalt()) + cur.execute( + "INSERT INTO users (name, password) VALUES (?, ?)", + (self.get_argument("name"), hashed_password) + ) + con.commit() + if hashed_password == bcrypt.hashpw(tornado.escape.utf8(self.get_argument("password")), hashed_password): + logging.info('OK') + cur.close() + con.close() + self.write('done') + + +class MountHandler(tornado.web.RequestHandler): + def post(self): + if self.get_secure_cookie("user"): + data = {k: v[0] for k, v in self.request.arguments.items()} + res = action('mount', 'post', json.dumps(data)) + logging.info(res) + + +class UnmountHandler(tornado.web.RequestHandler): + def get(self): + data = self.request.arguments + logging.info(data) + res = action('mount', 'post', json.dumps(data)) + logging.info(res) + +class ResolveHandler(tornado.web.RequestHandler): + #my_status = action('status', 'get') + def get(self): + data = self.request.arguments + logging.info(data) + res = action('resolve', 'post', json.dumps(data)) + logging.info(res) + +class StatusHandler(tornado.web.RequestHandler): + pass + +class SocketHandler(tornado.websocket.WebSocketHandler): + waiters = set() + quorum = dict() + + def get_compression_options(self): + # Non-None enables compression with default options. + return {} + + def check_origin(self, origin): + return True + + def open(self): + SocketHandler.waiters.add(self) + SocketHandler.send_updates(SocketHandler.quorum) + + def on_close(self): + SocketHandler.waiters.remove(self) + + @classmethod + def send_updates(cls, data, *stat): + SocketHandler.quorum = data + for waiter in cls.waiters: + try: + waiter.write_message(SocketHandler.quorum) + except: + logging.error("Error sending message", exc_info=True) + + def on_message(self, message): + logging.info(logging.info(message)) + +def config(point): + with open('conf.json') as conf: + path = json.load(conf)[point] + return path + +def action(name, method, data=None): + with open('conf.json') as conf: + url = json.load(conf)["api"] + '/' + name + if method == 'get': + try: + res = requests.get(url).content + except: + res = 'connection can\'t be established' + elif method == 'post': + try: + res = requests.post(url, data).content + except: + res = 'connection can\'t be established' + return res + + +def zk_handler(): + logging.basicConfig() + zk = KazooClient(hosts=config("zookeeper")) + zk.start() + t1 = threading.Thread(target=DataWatch, args=(zk, "/rbmd/log/quorum"), kwargs=dict(func=SocketHandler.send_updates)) + t1.setDaemon(True) + t1.start() + + + +def zk_fetch(path): + zk = KazooClient(hosts=config("zookeeper")) + zk.start() + data = zk.get(path) + zk.stop() + + +def main(): + tornado.options.parse_command_line() + zk_handler() + app = Application() + app.listen(options.port) + tornado.ioloop.IOLoop.current().start() + + +if __name__ == "__main__": + main() diff --git a/schema.sql b/schema.sql new file mode 100644 index 0000000..bd2532c --- /dev/null +++ b/schema.sql @@ -0,0 +1,10 @@ +-- sqlite3 auth.db +-- sqlite3 auth.db < schema.sql + +DROP TABLE IF EXISTS users; + +CREATE TABLE users ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + password TEXT NOT NULL +); diff --git a/static/rbmd/images-logo.png b/static/rbmd/images-logo.png new file mode 100644 index 0000000..9ab8646 Binary files /dev/null and b/static/rbmd/images-logo.png differ diff --git a/static/rbmd/script.js b/static/rbmd/script.js new file mode 100644 index 0000000..8ac5ad9 --- /dev/null +++ b/static/rbmd/script.js @@ -0,0 +1,134 @@ +$(function() { + $('#mountFormTrigger').click(function(event){ + var htmlSelect = ''; + a.quorum.forEach(function(item) {htmlSelect += "";}) + $('#selectNode').html(htmlSelect); + + /* + $.ajax({ + url:"status", + success:function(data){ + var status = JSON.parse(data); + var htmlSelect = ''; + for (n in status["quorum"]) { htmlSelect += "";} + $('#selectNode').html(htmlSelect); + } + }) + */ + }) + +}); + + function resolve() { + $.ajax({ + url:"resolve", + data:{"node":deadNode}, + success:function(data){$('#details').css("display", "none");} + }) + } + + function unmount(a, b, c) { + var u = confirm(a + ": confirm unmount of " + b); + if (u == true) { + $.ajax({ + url:"unmount", + data:{"node":a, "mountpoint":b, "block":c}, + success:function(data){ + var res = JSON.parse(data); + message = "

" + res["state"] + "

"+ res["message"] +"

"; + $("#rspContainer").css("display", "block"); + $("#rsp").html(message) + if (res["state"] == 'OK'){$("#rspContainer").css("background-color", "#4CAF50" );} + else {$("#rspContainer").css("background-color", "#f44336" )} + } + }) + } + } + + function displayData(a){ + $("#status").html("

"+a.health+"

"); + if (a.health == 'deadly.') { + $('#showDeadlyDetails').css("display","block"); + $('#resolve').css("display","block"); + $("#mountFormTrigger").addClass("w3-disabled") + } else { + $('#showDeadlyDetails').css("display","none"); + $('#resolve').css("display","none"); + $("#mountFormTrigger").removeClass("w3-disabled") + $('#details').css("border", "0"); + } + $( "#statusContainer:contains('alive')" ).css("background-color", "#4CAF50" ); + $( "#statusContainer:contains('resizing')" ).css("background-color", "#ff9800" ); + $( "#statusContainer:contains('deadly')" ).css("background-color", "#f44336" );; + var node2 = a; + w3DisplayData("id01", node2); + $('.tablink').css('display', 'block'); + if (selected_node != undefined && selected_node != 'dead'){ + //a.quorum.forEach(function(item) {if (item.node == selected_node) { console.log(item.node)}}) + var selected_node_body = a.quorum.find(function(node) { return node.node == selected_node}); + var t = new Date(selected_node_body["updated"] * 1000) + var up_formatted = t.getFullYear() + "/" + + (t.getMonth() + 1) + "/" + + t.getDate() + " " + + t.getHours() + ":" + + t.getMinutes() + ":" + + t.getSeconds(); + $("#name").html(selected_node); + $("#ipv4").html(selected_node_body["ip"]["v4"].join("
")); + $("#ipv6").html(selected_node_body["ip"]["v6"].join("
")); + $("#updated").html(up_formatted); + if (selected_node_body["mounts"] != null) { + var mnt_block = ""; + for (i in selected_node_body.mounts) { + var mnt = selected_node_body.mounts[i]; + mnt_block += "unmount
Mountpoint: " + + mnt.mountpoint + "
Mountopts: " + + mnt.mountopts + "
Fstype: " + + mnt.fstype + + "
Pool: " + mnt.pool + + "
Image: " + mnt.image + + "
Block: " + mnt.block +"
"; + } + $("#mon").html(mnt_block); + } + else { $("#mon").html("")} + } + if (selected_node =='dead') { + var t, up_formatted + if (a.deadlyreason["updated"] != 0) { + t = new Date(a.deadlyreason["updated"] * 1000) + up_formatted = t.getFullYear() + "/" + (t.getMonth() + 1) + "/" + t.getDate() + " " + t.getHours() + ":" + t.getMinutes() + ":" + t.getSeconds(); + } else { up_formatted=0 } + deadNode = a.deadlyreason["node"]; + $("#name").html(a.deadlyreason["node"]); + $("#ipv4").html(a.deadlyreason["ip"]["v4"].join("
")); + $("#ipv6").html(a.deadlyreason["ip"]["v6"].join("
")); + $("#updated").html(up_formatted); + if (a.deadlyreason["mounts"] != null) { + var mnt_block = ""; + for (i in a.deadlyreason.mounts) { + var mnt = a.deadlyreason.mounts[i]; + mnt_block += mnt.mountpoint + "
" + mnt.mountopts + "
" + mnt.fstype + "
" + mnt.pool + "
" + mnt.image + "

"; + } + $("#mon").html(mnt_block); + } else { $("#mon").html("") } + } + $("#leader").html(a.leader); + + } +function openNode(evt, nodeName) { + var i, x, tablinks; + selected_node = nodeName; + x = document.getElementsByClassName("node"); + tablinks = document.getElementsByClassName("tablink"); + if (selected_node == 'dead') { + $('#details').css("border", "2px solid #f44336"); + if ($('#showDeadlyDetails').html() == "Show details") { + $('#details').css("display", "block"); + $('#showDeadlyDetails').text('Hide details'); + } else { + $('#details').css("display", "none"); + $('#showDeadlyDetails').text('Show details'); + } + } else {$('#details').css("border", "").css("display", "block"); displayData(a) } +} diff --git a/templates/rbmd/index.html b/templates/rbmd/index.html new file mode 100644 index 0000000..ae3d9b3 --- /dev/null +++ b/templates/rbmd/index.html @@ -0,0 +1,105 @@ + + + RBMD + + + + + + + + +
+ Mount + +
+ +
+
+
+

Leader:

+

Nodes:

+ +

Metrics:

+{% for k, v in metrics.items() %} {{k}} {{v}}
{% end %} +
+ +
+
+
Status...
+ +
+ + + + +
+
+
+
+
+ × +

Mount

+
+
{% module xsrf_form_html() %} +
+ +
+
+
+
+
+ +
+ +
+
+
+ + + diff --git a/templates/rbmd/login.html b/templates/rbmd/login.html new file mode 100644 index 0000000..cbebea6 --- /dev/null +++ b/templates/rbmd/login.html @@ -0,0 +1,19 @@ + + + + + + + +{% if error %} +Error: {{ error }}

+{% end %} + +

+ Username:
+ Password:
+ {% module xsrf_form_html() %} + +
+ +