Merge remote-tracking branch 'upstream/master' into external_login

This commit is contained in:
zhongyehong 2016-10-29 17:47:02 +08:00
commit c41771e0b7
12 changed files with 196 additions and 173 deletions

View File

@ -65,7 +65,7 @@ First copy docklet.conf.template to get docklet.conf.
Pay attention to the following settings:
- NETWORK_DEVICE : the network interface to use.
- ETCD : the etcd server address. For distributed muli hosts
- ETCD : the etcd server address. For distributed multi hosts
environment, it should be one of the ETCD public server address.
For single host environment, the default value should be OK.
- STORAGE : using disk or file to storage persistent data, for
@ -127,7 +127,7 @@ The master logs are in **FS_PREFIX/local/log/docklet-master.log** and
Worker needs a basefs image to create containers.
You can create such an image with `lxc-create -n test -t download`,
then copy the rootfs to **FS_PREFIX/local**, and renamed `rootfs`
then copy the rootfs to **FS_PREFIX/local**, and rename `rootfs`
to `basefs`.
Note the `jupyerhub` package must be installed for this image. And the
@ -138,9 +138,9 @@ You can check and run `tools/update-basefs.sh` to update basefs.
Run `bin/docklet-worker start`, will start worker in background.
You can check the daemon status by running `bin/docklet-worker status`
You can check the daemon status by running `bin/docklet-worker status`.
The log is in **FS_PREFIX/local/log/docklet-worker.log**
The log is in **FS_PREFIX/local/log/docklet-worker.log**.
Currently, the worker must be run after the master has been started.

View File

@ -1,3 +1,4 @@
import threading,datetime,random,time
from model import db,User,ApplyMsg
from userManager import administration_required
@ -54,3 +55,28 @@ class ApplicationMgr:
applymsg.status = "Rejected"
db.session.commit()
return {"success":"true"}
class ApprovalRobot(threading.Thread):
def __init__(self,maxtime=3600):
threading.Thread.__init__(self)
self.stop = False
self.interval = 20
self.maxtime = maxtime
def stop(self):
self.stop = True
def run(self):
while not self.stop:
applymsgs = ApplyMsg.query.filter_by(status="Processing").all()
for msg in applymsgs:
secs = (datetime.datetime.now() - msg.time).seconds
ranint = random.randint(self.interval,self.maxtime)
if secs >= ranint:
msg.status = "Agreed"
user = User.query.filter_by(username=msg.username).first()
if user is not None:
user.beans += msg.number
db.session.commit()
time.sleep(self.interval)

View File

@ -42,13 +42,15 @@ def login_required(func):
@wraps(func)
def wrapper(*args, **kwargs):
global G_usermgr
logger.info ("get request, path: %s" % request.path)
token = request.form.get("token", None)
if (token == None):
logger.info ("get request without token, path: %s" % request.path)
return json.dumps({'success':'false', 'message':'user or key is null'})
cur_user = G_usermgr.auth_token(token)
if (cur_user == None):
logger.info ("get request with an invalid token, path: %s" % request.path)
return json.dumps({'success':'false', 'message':'token failed or expired', 'Unauthorized': 'True'})
logger.info ("get request, user: %s, path: %s" % (cur_user.username, request.path))
return func(cur_user, cur_user.username, request.form, *args, **kwargs)
return wrapper
@ -64,6 +66,47 @@ def beans_check(func):
return wrapper
# def inside_ip_required(func):
# @wraps(func)
# def wrapper(*args, **kwargs):
# global G_usermgr
# global G_vclustermgr
# #cur_user = G_usermgr.find_by_ip(request.remote_addr)
# cur_user = G_usermgr.auth_token(G_usermgr.auth('username', 'password')['data']['token'])
# if (cur_user == None):
# logger.info ("get request with an invalid ip, path: %s" % request.path)
# return json.dumps({'success':'false', 'message':'invalid ip address', 'Unauthorized': 'True'})
# #cluster_info = G_vclustermgr.find_by_ip(cur_user.username, request.remote_addr)
# cluster_filename = '/opt/docklet/global/users/liupd/clusters/asdf'
# cluster_file = open(cluster_filename,'r')
# cluster_info = json.loads(cluster_file.read())
# cluster_file.close()
# cluster_info['name'] = 'asdf'
# logger.info ("get request with ip, user: %s, ip: %s ,path: %s" % (cur_user.username, request.remote_addr, request.path))
# return func(cur_user, cluster_info, request.form, *args, **kwargs)
#
# return wrapper
def worker_ip_required(func):
@wraps(func)
def wrapper(*args, **kwargs):
global G_nodemgr
workers = G_nodemgr.get_rpcs()
ip = request.remote_addr
flag = False
for worker in workers:
workerip = G_nodemgr.rpc_to_ip(worker)
#logger.info(str(ip) + " " + str(workerip))
if ip == '127.0.0.1' or ip == '0.0.0.0' or ip == workerip:
flag = True
break
if not flag:
return json.dumps({'success':'false','message':'Worker\'s ip is required!'})
else:
return func(*args, **kwargs)
return wrapper
@app.route("/login/", methods=['POST'])
def login():
global G_usermgr
@ -175,6 +218,7 @@ def scaleout_cluster(cur_user, user, form):
global G_usermgr
global G_vclustermgr
clustername = form.get('clustername', None)
logger.info ("scaleout: %s" % form)
if (clustername == None):
return json.dumps({'success':'false', 'message':'clustername is null'})
logger.info("handle request : scale out %s" % clustername)
@ -288,9 +332,12 @@ def list_cluster(cur_user, user, form):
return json.dumps({'success':'false', 'action':'list cluster', 'message':clusterlist})
@app.route("/cluster/stopall/",methods=['POST'])
@login_required
def stopall_cluster(cur_user, user, form):
@worker_ip_required
def stopall_cluster():
global G_vclustermgr
user = request.form.get('username',None)
if user is None:
return json.dumps({'success':'false', 'message':'User is required!'})
logger.info ("handle request : stop all clusters for %s" % user)
[status, clusterlist] = G_vclustermgr.list_clusters(user)
if status:
@ -853,6 +900,35 @@ def resetall_system(cur_user, user, form):
return json.dumps({'success':'false', 'message': message})
return json.dumps(result)
# @app.route("/inside/cluster/scaleout/", methods=['POST'])
# @inside_ip_required
# def inside_cluster_scalout(cur_user, cluster_info, form):
# global G_usermgr
# global G_vclustermgr
# clustername = cluster_info['name']
# logger.info("handle request : scale out %s" % clustername)
# image = {}
# image['name'] = form.get("imagename", None)
# image['type'] = form.get("imagetype", None)
# image['owner'] = form.get("imageowner", None)
# user_info = G_usermgr.selfQuery(cur_user = cur_user)
# user = user_info['data']['username']
# user_info = json.dumps(user_info)
# setting = {
# 'cpu': form.get('cpuSetting'),
# 'memory': form.get('memorySetting'),
# 'disk': form.get('diskSetting')
# }
# [status, result] = G_usermgr.usageInc(cur_user = cur_user, modification = setting)
# if not status:
# return json.dumps({'success':'false', 'action':'scale out', 'message': result})
# [status, result] = G_vclustermgr.scale_out_cluster(clustername, user, image, user_info, setting)
# if status:
# return json.dumps({'success':'true', 'action':'scale out', 'message':result})
# else:
# G_usermgr.usageRecover(cur_user = cur_user, modification = setting)
# return json.dumps({'success':'false', 'action':'scale out', 'message':result})
@app.errorhandler(500)
def internal_server_error(error):
logger.debug("An internel server error occured")
@ -998,6 +1074,8 @@ if __name__ == '__main__':
master_collector.start()
logger.info("master_collector started")
G_applicationmgr = beansapplicationmgr.ApplicationMgr()
approvalrbt = beansapplicationmgr.ApprovalRobot()
approvalrbt.start()
# server = http.server.HTTPServer((masterip, masterport), DockletHttpHandler)
logger.info("starting master server")

View File

@ -95,7 +95,7 @@ class User(db.Model):
self.department = department
self.truename = truename
self.tel = tel
self.beans = 100
self.beans = 1000
if (date != None):
self.register_date = date
else:
@ -247,12 +247,14 @@ class ApplyMsg(db.Model):
number = db.Column(db.Integer)
reason = db.Column(db.String(600))
status = db.Column(db.String(10))
time = db.Column(db.DateTime(10))
def __init__(self,username, number, reason):
self.username = username
self.number = number
self.reason = reason
self.status = "Processing"
self.time = datetime.now()
def __repr__(self):
return "{\"id\":\"%d\", \"username\":\"%s\", \"number\": \"%d\", \"reason\":\"%s\", \"status\":\"%s\"}" % (self.id, self.username, self.number, self.reason, self.status)
return "{\"id\":\"%d\", \"username\":\"%s\", \"number\": \"%d\", \"reason\":\"%s\", \"status\":\"%s\", \"time\":\"%s\"}" % (self.id, self.username, self.number, self.reason, self.status, self.time.strftime("%Y-%m-%d %H:%M:%S"))

View File

@ -109,13 +109,18 @@ class Container_Collector(threading.Thread):
try:
vnode = VNode.query.get(vnode_name)
vnode.billing = nowbillingval
db.session.commit()
except Exception as err:
vnode = VNode(vnode_name)
vnode.billing = nowbillingval
db.session.add(vnode)
db.session.commit()
logger.warning(err)
try:
db.session.commit()
except Exception as err:
db.session.rollback()
logger.warning(traceback.format_exc())
logger.warning(err)
raise
workercinfo[vnode_name]['basic_info']['billing'] = nowbillingval
owner_name = get_owner(vnode_name)
owner = User.query.filter_by(username=owner_name).first()
@ -129,7 +134,7 @@ class Container_Collector(threading.Thread):
if owner.beans <= 0:
logger.info("The beans of User(" + str(owner) + ") are less than or equal to zero, the container("+vnode_name+") will be stopped.")
token = owner.generate_auth_token()
form = {'token':token}
form = {'username':owner.username}
header = {'Content-Type':'application/x-www-form-urlencoded'}
http = Http()
[resp,content] = http.request("http://"+G_masterip+"/cluster/stopall/","POST",urlencode(form),headers = header)

View File

@ -440,6 +440,7 @@ class userManager:
"group" : user.user_group,
"groupinfo": group,
"beans" : user.beans,
"auth_method": user.auth_method,
},
}
return result
@ -467,6 +468,12 @@ class userManager:
user.e_mail = value
elif (name == 'tel'):
user.tel = value
elif (name == 'password'):
old_password = hashlib.sha512(form.get('old_value', '').encode('utf-8')).hexdigest()
if (user.password != old_password):
result = {'success': 'false'}
return result
user.password = hashlib.sha512(value.encode('utf-8')).hexdigest()
else:
result = {'success': 'false'}
return result

View File

@ -10,6 +10,7 @@ if [ $# -eq 0 ] ; then
exit 1
fi
etcd_1=$1
index=1
while [ $# -gt 0 ] ; do
h="etcd_$index"
@ -31,7 +32,6 @@ done
# -initial-cluster-state : new means join a new cluster; existing means join an existing cluster
# : new not means clear
etcd --name etcd_1 \
--initial-advertise-peer-urls http://$etcd_1:2380 \
--listen-peer-urls http://$etcd_1:2380 \

View File

@ -3,8 +3,8 @@
echo "Backup UserTable..."
cp /opt/docklet/global/sys/UserTable.db /opt/docklet/global/sys/UserTable.db.backup
sed -i "75s/^ /#/g" ../src/model.py
sed -i "95s/^ /#/g" ../src/model.py
sed -i "s/^ beans/# beans/g" ../src/model.py
sed -i "s/^ self.beans/# self.beans/g" ../src/model.py
echo "Alter UserTable..."
python3 alterUserTable.py

View File

@ -35,7 +35,7 @@
</div>
<div class="box-body">
<p>
<button type="button" class="btn btn-primary btn-sm" data-toggle="modal" data-target="#AddApplication"><i class="fa fa-plus"></i> Add Application</button>
<button type="button" class="btn btn-primary btn-sm" data-toggle="modal" data-target="#AddApplication"><i class="fa fa-plus"></i> Apply For Beans</button>
</p>
<div class="modal inmodal" id="AddApplication" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog">
@ -74,6 +74,7 @@
<th>Application ID</th>
<th>Username</th>
<th>Number</th>
<th>Submission Time</th>
<th>Reason</th>
<th>Status</th>
</tr>
@ -83,7 +84,8 @@
<tr>
<td>{{ application.id }}</td>
<td>{{ application.username }}</td>
<td>{{ application.number }} beans</td>
<td>{{ application.number }} <img src='/static/img/bean.png'></td>
<td>{{ application.time }}</td>
<td>{{ application.reason }}</td>
<td>{{ application.status }}</td>
</tr>

View File

@ -1,139 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Docklet | Home</title>
<link rel="shortcut icon" href="/static/img/favicon.ico">
<link href="http://cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
<link href="http://cdn.bootcss.com/font-awesome/4.5.0/css/font-awesome.css" rel="stylesheet">
<style type="text/css">
a.linkbtn, a.linkbtn:visited, a.linkbtn:active{
color:white;
padding:8px;
}
a.linkbtn:hover{
color:white;
text-decoration:none;
border-bottom: 1px solid white;
}
.navbar-custom-top{
background:transparent;
position:absolute;
z-index:1030;
left:0px;
right:0px;
}
</style>
</head>
<body>
<div class="navbar navbar-custom-top" role="navigation">
<div class="container">
<div class="row" style="font-size:16px; color:white; padding:16px">
<div class="pull-right" >
<a class="linkbtn" href="http://docklet.unias.org/docklet-book/userguide/_book/">Document</a>
&centerdot;
<a class="linkbtn" href="/login/" >Sign In</a>
</div>
<div>
<a class="linkbtn" href="http://docklet.unias.org"><strong>Docklet Cloud OS</strong></a>
</div>
</div>
</div>
</div>
<!-- Carousel -->
<div id="myCarousel" class="carousel slide" data-ride="carousel" style="box-shadow:0px 2px 10px 0px black">
<!-- Indicators -->
<ol class="carousel-indicators">
<li data-target="#myCarousel" data-slide-to="0" class="active"></li>
<li data-target="#myCarousel" data-slide-to="1"></li>
</ol>
<div class="carousel-inner" role="listbox">
<div class="item active">
<img src="/static/img/home/cloud.png" alt="Cloud OS" style="min-height:600px">
<div class="container">
<div class="carousel-caption">
<h1>Cloud OS</h1>
<p class="lead">Cloud OS for your cloud Apps.</p>
<br>
</div>
</div>
</div>
<div class="item">
<img src="/static/img/home/workspace.png" alt="Cloud Workspace" style="min-height:600px">
<div class="container">
<div class="carousel-caption">
<h1>Cloud Workspace</h1>
<p class="lead">Workspace in cloud, all your work in cloud.</p>
<br>
</div>
</div>
</div>
</div>
<a class="left carousel-control" href="#myCarousel" role="button" data-slide="prev">
<span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="right carousel-control" href="#myCarousel" role="button" data-slide="next">
<span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</div><!-- /.carousel -->
<div class="container" style="margin-top:60px">
<div class="row">
<div class="col-lg-6 col-lg-offset-1 col-md-7 col-md-offset-0 col-sm-7 col-sm-offset-0 col-xs-12 col-xs-offset-0">
<h2>Workspace=Cluster+Service+Data</h2>
<br/>
<p class="lead">Package service and data based on virtual cluster as virtual compute environment for your work.
<br> This is your Workspace !</p>
</div>
<div class="col-lg-3 col-lg-offset-1 col-md-4 col-md-offset-1 col-sm-5 col-sm-offset-0 col-xs-10 col-xs-offset-1">
<img src="/static/img/home/app-workspace.png" alt="feature-workspace" width="100%">
</div>
</div>
<hr>
<div class="row">
<div class="col-lg-3 col-lg-offset-1 col-md-4 col-md-offset-1 col-sm-5 col-sm-offset-0 col-xs-10 col-xs-offset-1">
<img src="/static/img/home/app-dist.png" alt="feature-app" width="100%">
</div>
<div class="col-lg-6 col-lg-offset-1 col-md-7 col-md-offset-0 col-sm-7 col-sm-offset-0 col-xs-12 col-xs-offset-0">
<h2>Click and Go</h2>
<br/>
<p class="lead">Distributed or single node ? Never mind !
Click it just like start an app on your smart phone, and your workspace is ready for you.</p>
</div>
</div>
<hr>
<div class="row">
<div class="col-lg-6 col-lg-offset-1 col-md-7 col-md-offset-0 col-sm-7 col-sm-offset-0 col-xs-12 col-xs-offset-0">
<h2>All in Web</h2>
<br/>
<p class="lead">All you need is a web browser. Compute in web, code in web, plot in web, anything in web !
You can get to work anytime and anywhere by internet.</p>
</div>
<div class="col-lg-3 col-lg-offset-1 col-md-4 col-md-offset-1 col-sm-5 col-sm-offset-0 col-xs-10 col-xs-offset-1">
<img src="/static/img/home/app-web.png" alt="feature-web" width="100%">
</div>
</div>
<hr>
<footer>
<p class="pull-right">Powered by <a href="http://docklet.unias.org" style="color:blue">Docklet</a></p>
<p>&copy; SEI, PKU</p>
</footer>
</div>
<script src="http://cdn.bootcss.com/jquery/2.2.1/jquery.js"></script>
<script src="http://cdn.bootcss.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
</body>
</html>

View File

@ -4,6 +4,7 @@
{% block css_src %}
<link href="//cdn.bootcss.com/x-editable/1.5.1/bootstrap3-editable/css/bootstrap-editable.css" rel="stylesheet">
<link href="/static/dist/css/modalconfig.css" rel="stylesheet">
{% endblock %}
{% block panel_title %}Detail for User Infomation{% endblock %}
@ -71,12 +72,12 @@
<td>Telephone</td>
<td><a href="#" id="tel" data-type="text" data-pk="1" data-url="/user/info/" data-title="Enter telephone number">{{ info['tel'] }}</a></td>
</tr>
<!--
{% if info['auth_method'] == 'local' %}
<tr>
<td>password</td>
<td>
<div class="col-md-12" >
<button type="button" class="btn btn-white btn-xs btn-block" data-toggle="modal" data-target="#ChpasswordModal"> Change Password</button>
<button type="button" class="btn btn-primary btn-xs btn-block" data-toggle="modal" data-target="#ChpasswordModal"> Change Password</button>
</div>
<div class="modal inmodal" id="ChpasswordModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog">
@ -89,25 +90,26 @@
</div>
<div class="modal-body">
<form action="/group/add/" method="POST" id="ChpasswordForm">
<form action="/user/info/" method="POST" id="ChpasswordForm">
<div class="form-group">
<label>Old password</label>
<input type = "password" placeholder="Enter old password" class="form-control" name="o_password">
<input type = "password" placeholder="Enter old password" class="form-control" name="o_password" id="o_password">
</div>
<div class="form-group">
<label>New password</label>
<input type = "password" placeholder="Enter new password" class="form-control" name="n_password">
<input type = "password" placeholder="Enter new password" class="form-control" name="n_password" id = "n_password">
</div>
<div class="form-group">
<label>Verify</label>
<input type = "password" placeholder="Enter new password again" class="form-control" name="v_password">
<input type = "password" placeholder="Enter new password again" class="form-control" name="v_password" id = "v_password">
</div>
<p style="color:red" id="notCorrectFlag"></p>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-white" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" onClick="javascript:sendAddGroup();">Submit</button>
<button type="button" class="btn btn-primary" onClick="javascript:sendChpassword();">Submit</button>
</div>
</div>
</div>
@ -115,7 +117,8 @@
</td>
</tr>
-->
{% endif %}
</table>
</div>
@ -248,6 +251,33 @@ $(document).ready(function(){
*/
});
function sendChpassword(){
if ($("#n_password").val() == $("#v_password").val())
{
$.post("/user/info/",
{
name: 'password',
value: $("#n_password").val(),
old_value : $("#o_password").val()
},
function(data,status){
var result = eval("("+data+")");
if (result.success == 'true')
{
location.reload();
}
else
{
$("#notCorrectFlag").html("Cannot modify your password");
}
});
}
else
{
$("#notCorrectFlag").html("Two passwords are not identical");
}
}
</script>

View File

@ -166,6 +166,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-white" data-dismiss="modal">Close</button>
<a class="btn btn-danger" data-toggle="modal" data-target="#ChpasswordModal" onClick="" id="ChpasswordButton">Change Password</a>
<button type="button" class="btn btn-primary" onClick="javascript:sendModifyUser();">Submit</button>
</div>
</div>
@ -191,6 +192,11 @@
<input type="password" placeholder="Enter Password" class="form-control" name="password" id="mpPassword">
</div>
</form>
<div class="form-group">
<label>Retype Password</label>
<input type="password" placeholder="Enter Password" class="form-control" id="mpPassword2">
</div>
<p style="color:red" id="notCorrectFlag"></p>
</div>
<div class="modal-footer">
@ -227,6 +233,7 @@
<th>Application ID</th>
<th>Username</th>
<th>Number</th>
<th>Submission Time</th>
<th>Reason</th>
<th>Command</th>
</tr>
@ -237,6 +244,7 @@
<td>{{ application.id }}</td>
<td>{{ application.username }}</td>
<td>{{ application.number }} beans</td>
<td>{{ application.time }}</td>
<td>{{ application.reason }}</td>
<td><a class="btn btn-xs btn-info" href="/beans/admin/{{ application.id }}/agree/">Agree</a>&nbsp;&nbsp;&nbsp;&nbsp;
<a class="btn btn-xs btn-danger" href="/beans/admin/{{ application.id }}/reject/">Reject</a></td>
@ -272,10 +280,6 @@
{
str=str + '<a class="btn btn-danger btn-xs" onClick="javascript:setActivateUser(' + row[0] + ');">' + 'Activate' + '</a>';
}
else
{
str=str + '<a class="btn btn-primary btn-xs" data-toggle="modal" data-target="#ChpasswordModal" onClick="javascript:setFormChpassword(\'' + row[1] + '\');">' + 'Chpassword' + '</a>';
}
return str;
},
"targets": 9
@ -310,8 +314,15 @@
document.getElementById("modifyUserForm").submit();
}
function sendChpassword(){
if ($("#mpPassword").val() == $("#mpPassword2").val())
{
document.getElementById("chpasswordForm").submit();
}
else
{
$("#notCorrectFlag").html("Two passwords are not identical");
}
}
function sendModifyGroup(){
document.getElementById("modifyGroupForm").submit();
}
@ -332,6 +343,7 @@
$("#mUserGroup").val(result.group);
$("#mAuthMethod").val(result.auth_method);
$("#mDescription").val(result.description);
$("#ChpasswordButton").attr("onClick", "javascript:setFormChpassword(\"" + result.username + "\");");
});
}
function setFormChpassword(arg){