This commit is contained in:
Anna Sudnitsina 2017-09-08 15:26:55 +03:00
parent c6e76301be
commit 693ccfc3cc
7 changed files with 481 additions and 0 deletions

5
conf.json Normal file
View File

@ -0,0 +1,5 @@
{
"zookeeper" : "10.8.0.26:2181",
"api": "http://127.0.0.1:9076/v1",
"ws": "ws://py.difrex.ru/socket"
}

208
rbmd.py Normal file
View File

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

10
schema.sql Normal file
View File

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

BIN
static/rbmd/images-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

134
static/rbmd/script.js Normal file
View File

@ -0,0 +1,134 @@
$(function() {
$('#mountFormTrigger').click(function(event){
var htmlSelect = '';
a.quorum.forEach(function(item) {htmlSelect += "<option value=" + item.node + ">" + item.node + "</option>";})
$('#selectNode').html(htmlSelect);
/*
$.ajax({
url:"status",
success:function(data){
var status = JSON.parse(data);
var htmlSelect = '';
for (n in status["quorum"]) { htmlSelect += "<option value=" + n + ">" + n + "</option>";}
$('#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 = "<h3>" + res["state"] + "</h3> <p>"+ res["message"] +"</p>";
$("#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("<p>"+a.health+"</p>");
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("<br>"));
$("#ipv6").html(selected_node_body["ip"]["v6"].join("<br>"));
$("#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 += "<a href='javascript:void(0)' onClick='unmount(selected_node, selected_node_body.mounts[i].mountpoint, selected_node_body.mounts[i].block)' >unmount</a><br>Mountpoint: "
+ mnt.mountpoint + "<br>Mountopts: "
+ mnt.mountopts + "<br>Fstype: "
+ mnt.fstype +
"<br>Pool: " + mnt.pool +
"<br>Image: " + mnt.image +
"<br>Block: " + mnt.block +"<br>";
}
$("#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("<br>"));
$("#ipv6").html(a.deadlyreason["ip"]["v6"].join("<br>"));
$("#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 + "<br>" + mnt.mountopts + "<br>" + mnt.fstype + "<br>" + mnt.pool + "<br>" + mnt.image + "<br><br>";
}
$("#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) }
}

105
templates/rbmd/index.html Normal file
View File

@ -0,0 +1,105 @@
<html>
<head>
<title>RBMD</title>
<meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://www.w3schools.com/lib/w3.css">
<script src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
<script src="https://www.w3schools.com/lib/w3data.js"></script>
<script src="{{ static_url("script.js") }}"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
</head>
<body>
<header class = "" style="padding:1% 16px;" >
<a onclick="if (a.health != 'deadly.') {$('#mount').css('display', 'block')}" style="font-size:1em" class="w3-btn" id="mountFormTrigger">Mount <i class="fa fa-hdd-o"></i></a>
<a href='/'><img src="{{ static_url("images-logo.png") }}" class="w3-right w3-hover-opacity" height="33.82em" ></a>
</header>
<div class="w3-container">
<div class="w3-row">
<div class="sidenav w3-container w3-light-grey w3-col m3" id="id01">
<h4> Leader: </h4> <span id='leader' class="tablink"></span>
<h4>Nodes: </h4>
<a w3-repeat="node in quorum" href="javascript:void(0)" class="tablink" onclick="openNode(event, '{{!node}}')" style='display:none'>{{!node}}</a>
<h4>Metrics: </h4>
{% for k, v in metrics.items() %} {{k}} {{v}}<br> {% end %}
</div>
<div class="w3-col m9" >
<div class="w3-container" style="height:50px; position:relative" id="statusContainer">
<div class="w3-padding w3-display-left" id="status">Status... </div>
<a href="javascript:void(0)" class="w3-right" id="showDeadlyDetails" onClick="openNode(event, 'dead')" style="display: none; padding:15px;">Show details</a>
</div>
<div id="rspContainer" class="w3-container w3-animate-opacity" style="display: none">
<span onclick="this.parentElement.style.display='none'" class="w3-closebtn">&times;</span>
<div id="rsp">
</div>
</div>
<div id = "details" class="w3-container" style="display:none;">
<table class='w3-table w3-bordered' id="id02">
<tr><td> Node:</td> <td> <span id='name'></span></td> </tr>
<tr><td> IPv4:</td> <td> <span id='ipv4'></span></td> </tr>
<tr><td> IPv6:</td> <td> <span id='ipv6'></span></td> </tr>
<tr><td> Updated:</td> <td> <span id='updated'></span></td></tr>
<tr><td> Mounts:</td> <td id="mon">
</td> </tr>
</table>
<a href="javascript:void(0)" class="w3-right" id="resolve" onClick="resolve()" style="display: none; padding:15px;">Resolve</a>
</div>
</div>
</div>
<div id="mount" class="w3-modal">
<div class="w3-modal-content">
<header class="w3-container w3-light-grey">
<span onclick="$('#mount').css('display', 'none')" class="w3-closebtn">&times;</span>
<h2>Mount</h2>
</header>
<form method="post" action="/" role="form" class="w3-container form" id="mountForm"> {% module xsrf_form_html() %}
<label class="w3-label w3-text-black">Node: <select name="node" class="w3-select" id='selectNode'></select> </label><br>
<!--label class="w3-label w3-text-black">Node: <input name="node" class="w3-input" type="text" required> </label><br-->
<label class="w3-label w3-text-black">Pool: <input name="pool" class="w3-input" type="text"> </label><br>
<label class="w3-label w3-text-black">Image: <input name="image" class="w3-input" type="text"> </label><br>
<label class="w3-label w3-text-black">mountpoint: <input name="mountpoint" class="w3-input" type="text"> </label><br>
<label class="w3-label w3-text-black">mountopts: <input name="mountopts" class="w3-input" type="text"> </label><br>
<label class="w3-label w3-text-black">fstype: <input name="fstype" class="w3-input" type="text"> </label><br>
<button class="w3-btn w3-black" type="submit">Mount</button>
</form>
<footer class="w3-container w3-light-grey">
</footer>
</div>
</div>
</div>
<script>
var ws = new WebSocket('{{escape(ws)}}');
var a, selected_node, deadNode;
ws.onopen = function() {ws.send(""); };
ws.onmessage = function (evt) {
console.log(evt.data);
a = JSON.parse(evt.data);
displayData(a);
};
$('#mountForm').submit(function(event){
event.preventDefault();
$.ajax({
url:"mount",
data:$(this).serialize(),
method:'POST',
success:function(data){
$('#mount').css('display', 'none');
$('input[type = "text"]').val('');
var res = JSON.parse(data);
message = "<h3>" + res["state"] + "</h3> <p>"+ res["message"] +"</p>";
$("#rspContainer").css("display", "block");
$("#rsp").html(message)
if (res["state"] == 'OK'){$("#rspContainer").css("background-color", "#4CAF50" );}
else {$("#rspContainer").css("background-color", "#f44336" )}
}
})
})
</script>
</body></html>

19
templates/rbmd/login.html Normal file
View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
{% if error %}
<span style="color: red">Error: {{ error }}</span><p>
{% end %}
<form action="/login" method="POST">
Username: <input name="username" type="text"><br>
Password: <input name="password" type="password"><br>
{% module xsrf_form_html() %}
<input type="submit">
</form>
</body>
</html>