Merge pull request #297 from zhongyehong/master
Implement addnode function on aliyun cloud
This commit is contained in:
commit
fbe88b0cbb
|
@ -0,0 +1,11 @@
|
|||
#!/bin/bash
|
||||
|
||||
if [[ "`whoami`" != "root" ]]; then
|
||||
echo "FAILED: Require root previledge !" > /dev/stderr
|
||||
exit 1
|
||||
fi
|
||||
|
||||
pip3 install aliyun-python-sdk-core-v3
|
||||
pip3 install aliyun-python-sdk-ecs
|
||||
|
||||
exit 0
|
|
@ -178,3 +178,7 @@
|
|||
# The two ports next to '-' are inclueded. If there are several ranges,
|
||||
# Please seperate them by ',' , for example: 10000-20000,30000-40000
|
||||
# ALLOCATED_PORTS=10000-65535
|
||||
|
||||
# ALLOW_SCALE_OUT: allow docklet to rent server on the cloud to scale out
|
||||
# Only when you deploy docklet on the cloud can you set it to True
|
||||
# ALLOW_SCALE_OUT=False
|
||||
|
|
|
@ -15,13 +15,13 @@ fi
|
|||
# install packages that docklet needs (in ubuntu)
|
||||
# some packages' name maybe different in debian
|
||||
apt-get install -y cgmanager lxc lxcfs lxc-templates lvm2 bridge-utils curl exim4 openssh-server openvswitch-switch
|
||||
apt-get install -y python3 python3-netifaces python3-flask python3-flask-sqlalchemy python3-pampy python3-httplib2
|
||||
apt-get install -y python3 python3-netifaces python3-flask python3-flask-sqlalchemy python3-pampy python3-httplib2 python3-pip
|
||||
apt-get install -y python3-psutil python3-flask-migrate
|
||||
apt-get install -y python3-lxc
|
||||
apt-get install -y python3-requests python3-suds
|
||||
apt-get install -y nodejs nodejs-legacy npm
|
||||
apt-get install -y etcd
|
||||
apt-get install -y glusterfs-client
|
||||
apt-get install -y glusterfs-client attr
|
||||
apt-get install -y nginx
|
||||
|
||||
#add ip forward
|
||||
|
@ -55,7 +55,7 @@ mkdir -p /opt/docklet/local/
|
|||
|
||||
echo "directory /opt/docklet have been created"
|
||||
|
||||
if [ ! -d /opt/docklet/local/basefs ]; then
|
||||
if [[ ! -d /opt/docklet/local/basefs && ! $1 = "withoutfs" ]]; then
|
||||
mkdir -p /opt/docklet/local/basefs
|
||||
echo "Generating basefs"
|
||||
wget -P /opt/docklet/local http://iwork.pku.edu.cn:1616/basefs-0.11.tar.bz2 && tar xvf /opt/docklet/local/basefs-0.11.tar.bz2 -C /opt/docklet/local/ > /dev/null
|
||||
|
|
|
@ -0,0 +1,196 @@
|
|||
#!/usr/bin/python3
|
||||
from io import StringIO
|
||||
import os,sys,subprocess,time,re,datetime,threading,random,shutil
|
||||
from model import db, Image
|
||||
from deploy import *
|
||||
import json
|
||||
|
||||
from log import logger
|
||||
import env
|
||||
import requests
|
||||
|
||||
fspath = env.getenv('FS_PREFIX')
|
||||
|
||||
|
||||
class AliyunMgr():
|
||||
def __init__(self):
|
||||
self.AcsClient = __import__('aliyunsdkcore.client', fromlist=["AcsClient"])
|
||||
self.Request = __import__('aliyunsdkecs.request.v20140526', fromlist=[
|
||||
"CreateInstanceRequest",
|
||||
"StopInstanceRequest",
|
||||
"DescribeInstancesRequest",
|
||||
"DeleteInstanceRequest",
|
||||
"StartInstanceRequest",
|
||||
"DescribeInstancesRequest",
|
||||
"AllocateEipAddressRequest",
|
||||
"AssociateEipAddressRequest"])
|
||||
|
||||
def loadClient(self):
|
||||
if not os.path.exists(fspath+"/global/sys/cloudsetting.json"):
|
||||
currentfilepath = os.path.dirname(os.path.abspath(__file__))
|
||||
templatefilepath = currentfilepath + "/../tools/cloudsetting.aliyun.template.json"
|
||||
shutil.copyfile(templatefilepath,fspath+"/global/sys/cloudsetting.json")
|
||||
logger.error("please modify the setting file first")
|
||||
return False
|
||||
try:
|
||||
settingfile = open(fspath+"/global/sys/cloudsetting.json", 'r')
|
||||
self.setting = json.loads(settingfile.read())
|
||||
settingfile.close()
|
||||
self.clt = self.AcsClient.AcsClient(self.setting['AccessKeyId'],self.setting['AccessKeySecret'], self.setting['RegionId'])
|
||||
logger.info("load CLT of Aliyun success")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
return False
|
||||
|
||||
def createInstance(self):
|
||||
request = self.Request.CreateInstanceRequest.CreateInstanceRequest()
|
||||
request.set_accept_format('json')
|
||||
request.add_query_param('RegionId', self.setting['RegionId'])
|
||||
if 'ZoneId' in self.setting and not self.setting['ZoneId'] == "":
|
||||
request.add_query_param('ZoneId', self.setting['ZoneId'])
|
||||
if 'VSwitchId' in self.setting and not self.setting['VSwitchId'] == "":
|
||||
request.add_query_param('VSwitchId', self.setting['VSwitchId'])
|
||||
request.add_query_param('ImageId', 'ubuntu_16_0402_64_20G_alibase_20170818.vhd')
|
||||
request.add_query_param('InternetMaxBandwidthOut', 1)
|
||||
request.add_query_param('InstanceName', 'docklet_tmp_worker')
|
||||
request.add_query_param('HostName', 'worker-tmp')
|
||||
request.add_query_param('SystemDisk.Size', int(self.setting['SystemDisk.Size']))
|
||||
request.add_query_param('InstanceType', self.setting['InstanceType'])
|
||||
request.add_query_param('Password', self.setting['Password'])
|
||||
response = self.clt.do_action_with_exception(request)
|
||||
logger.info(response)
|
||||
|
||||
instanceid=json.loads(bytes.decode(response))['InstanceId']
|
||||
return instanceid
|
||||
|
||||
def startInstance(self, instanceid):
|
||||
request = self.Request.StartInstanceRequest.StartInstanceRequest()
|
||||
request.set_accept_format('json')
|
||||
request.add_query_param('InstanceId', instanceid)
|
||||
response = self.clt.do_action_with_exception(request)
|
||||
logger.info(response)
|
||||
|
||||
|
||||
def createEIP(self):
|
||||
request = self.Request.AllocateEipAddressRequest.AllocateEipAddressRequest()
|
||||
request.set_accept_format('json')
|
||||
request.add_query_param('RegionId', self.setting['RegionId'])
|
||||
response = self.clt.do_action_with_exception(request)
|
||||
logger.info(response)
|
||||
|
||||
response=json.loads(bytes.decode(response))
|
||||
eipid=response['AllocationId']
|
||||
eipaddr=response['EipAddress']
|
||||
|
||||
return [eipid, eipaddr]
|
||||
|
||||
|
||||
def associateEIP(self, instanceid, eipid):
|
||||
request = self.Request.AssociateEipAddressRequest.AssociateEipAddressRequest()
|
||||
request.set_accept_format('json')
|
||||
request.add_query_param('AllocationId', eipid)
|
||||
request.add_query_param('InstanceId', instanceid)
|
||||
response = self.clt.do_action_with_exception(request)
|
||||
logger.info(response)
|
||||
|
||||
|
||||
def getInnerIP(self, instanceid):
|
||||
request = self.Request.DescribeInstancesRequest.DescribeInstancesRequest()
|
||||
request.set_accept_format('json')
|
||||
response = self.clt.do_action_with_exception(request)
|
||||
instances = json.loads(bytes.decode(response))['Instances']['Instance']
|
||||
for instance in instances:
|
||||
if instance['InstanceId'] == instanceid:
|
||||
return instance['NetworkInterfaces']['NetworkInterface'][0]['PrimaryIpAddress']
|
||||
return json.loads(bytes.decode(response))['Instances']['Instance'][0]['VpcAttributes']['PrivateIpAddress']['IpAddress'][0]
|
||||
|
||||
def isStarted(self, instanceids):
|
||||
request = self.Request.DescribeInstancesRequest.DescribeInstancesRequest()
|
||||
request.set_accept_format('json')
|
||||
response = self.clt.do_action_with_exception(request)
|
||||
instances = json.loads(bytes.decode(response))['Instances']['Instance']
|
||||
for instance in instances:
|
||||
if instance['InstanceId'] in instanceids:
|
||||
if not instance['Status'] == "Running":
|
||||
return False
|
||||
return True
|
||||
|
||||
def rentServers(self,number):
|
||||
instanceids=[]
|
||||
eipids=[]
|
||||
eipaddrs=[]
|
||||
for i in range(int(number)):
|
||||
instanceids.append(self.createInstance())
|
||||
time.sleep(2)
|
||||
time.sleep(10)
|
||||
for i in range(int(number)):
|
||||
[eipid,eipaddr]=self.createEIP()
|
||||
eipids.append(eipid)
|
||||
eipaddrs.append(eipaddr)
|
||||
time.sleep(2)
|
||||
masterip=env.getenv('ETCD').split(':')[0]
|
||||
for i in range(int(number)):
|
||||
self.associateEIP(instanceids[i],eipids[i])
|
||||
time.sleep(2)
|
||||
time.sleep(5)
|
||||
for instanceid in instanceids:
|
||||
self.startInstance(instanceid)
|
||||
time.sleep(2)
|
||||
time.sleep(10)
|
||||
while not self.isStarted(instanceids):
|
||||
time.sleep(10)
|
||||
time.sleep(5)
|
||||
return [masterip, eipaddrs]
|
||||
|
||||
def addNode(self):
|
||||
if not self.loadClient():
|
||||
return {'success':'false'}
|
||||
[masterip, eipaddrs] = self.rentServers(1)
|
||||
threads = []
|
||||
for eip in eipaddrs:
|
||||
thread = threading.Thread(target = deploy, args=(eip,masterip,'root',self.setting['Password'],self.setting['VolumeName']))
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
threads.append(thread)
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
return {'success':'true'}
|
||||
|
||||
def addNodeAsync(self):
|
||||
thread = threading.Thread(target = self.addNode)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
|
||||
class EmptyMgr():
|
||||
def addNodeAsync(self):
|
||||
logger.error("current cluster does not support scale out")
|
||||
return False
|
||||
|
||||
class CloudMgr():
|
||||
|
||||
def getSettingFile(self):
|
||||
if not os.path.exists(fspath+"/global/sys/cloudsetting.json"):
|
||||
currentfilepath = os.path.dirname(os.path.abspath(__file__))
|
||||
templatefilepath = currentfilepath + "/../tools/cloudsetting.aliyun.template.json"
|
||||
shutil.copyfile(templatefilepath,fspath+"/global/sys/cloudsetting.json")
|
||||
settingfile = open(fspath+"/global/sys/cloudsetting.json", 'r')
|
||||
setting = settingfile.read()
|
||||
settingfile.close()
|
||||
return {'success':'true', 'result':setting}
|
||||
|
||||
def modifySettingFile(self, setting):
|
||||
if setting == None:
|
||||
logger.error("setting is None")
|
||||
return {'success':'false'}
|
||||
settingfile = open(fspath+"/global/sys/cloudsetting.json", 'w')
|
||||
settingfile.write(setting)
|
||||
settingfile.close()
|
||||
return {'success':'true'}
|
||||
|
||||
|
||||
def __init__(self):
|
||||
if env.getenv("ALLOW_SCALE_OUT") == "True":
|
||||
self.engine = AliyunMgr()
|
||||
else:
|
||||
self.engine = EmptyMgr()
|
|
@ -0,0 +1,53 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import paramiko, time
|
||||
from log import logger
|
||||
import env,os
|
||||
|
||||
def myexec(ssh,command):
|
||||
stdin,stdout,stderr = ssh.exec_command(command)
|
||||
endtime = time.time() + 3600
|
||||
while not stdout.channel.eof_received:
|
||||
time.sleep(2)
|
||||
if time.time() > endtime:
|
||||
stdout.channel.close()
|
||||
logger.error(command + ": fail")
|
||||
return
|
||||
# for line in stdout.readlines():
|
||||
# if line is None:
|
||||
# time.sleep(5)
|
||||
# else:
|
||||
# print(line)
|
||||
|
||||
def deploy(ipaddr,masterip,account,password,volumename):
|
||||
while True:
|
||||
try:
|
||||
transport = paramiko.Transport((ipaddr,22))
|
||||
transport.connect(username=account,password=password)
|
||||
break
|
||||
except Exception as e:
|
||||
time.sleep(2)
|
||||
pass
|
||||
sftp = paramiko.SFTPClient.from_transport(transport)
|
||||
|
||||
currentfilepath = os.path.dirname(os.path.abspath(__file__))
|
||||
deployscriptpath = currentfilepath + "/../tools/docklet-deploy.sh"
|
||||
sftp.put(deployscriptpath,'/root/docklet-deploy.sh')
|
||||
sftp.put('/etc/hosts', '/etc/hosts')
|
||||
transport.close()
|
||||
|
||||
ssh = paramiko.SSHClient()
|
||||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
while True:
|
||||
try:
|
||||
ssh.connect(ipaddr, username = account, password = password, timeout = 300)
|
||||
break
|
||||
except Exception as e:
|
||||
time.sleep(2)
|
||||
pass
|
||||
myexec(ssh,"sed -i 's/%MASTERIP%/" + masterip + "/g' /root/docklet-deploy.sh")
|
||||
myexec(ssh,"sed -i 's/%VOLUMENAME%/" + volumename + "/g' /root/docklet-deploy.sh")
|
||||
myexec(ssh,'chmod +x /root/docklet-deploy.sh')
|
||||
myexec(ssh,'/root/docklet-deploy.sh')
|
||||
ssh.close()
|
||||
return
|
|
@ -77,5 +77,7 @@ def getenv(key):
|
|||
return os.environ.get("APPROVAL_RBT","ON")
|
||||
elif key =="ALLOCATED_PORTS":
|
||||
return os.environ.get("ALLOCATED_PORTS","10000-65535")
|
||||
elif key =="ALLOW_SCALE_OUT":
|
||||
return os.environ.get("ALLOW_SCALE_OUT", "False")
|
||||
else:
|
||||
return os.environ.get(key,"")
|
||||
|
|
|
@ -23,7 +23,7 @@ import os
|
|||
import http.server, cgi, json, sys, shutil
|
||||
import xmlrpc.client
|
||||
from socketserver import ThreadingMixIn
|
||||
import nodemgr, vclustermgr, etcdlib, network, imagemgr, notificationmgr, lockmgr
|
||||
import nodemgr, vclustermgr, etcdlib, network, imagemgr, notificationmgr, lockmgr, cloudmgr
|
||||
from logs import logs
|
||||
import userManager,beansapplicationmgr
|
||||
import monitor,traceback
|
||||
|
@ -438,6 +438,31 @@ def copytarget_image(user, beans, form):
|
|||
return json.dumps({'success':'false', 'message':str(e)})
|
||||
return json.dumps({'success':'true', 'action':'copy image to target.'})
|
||||
|
||||
@app.route("/cloud/setting/get/", methods=['POST'])
|
||||
@login_required
|
||||
def query_account_cloud(cur_user, user, form):
|
||||
global G_cloudmgr
|
||||
logger.info("handle request: cloud/setting/get/")
|
||||
result = G_cloudmgr.getSettingFile()
|
||||
return json.dumps(result)
|
||||
|
||||
@app.route("/cloud/setting/modify/", methods=['POST'])
|
||||
@login_required
|
||||
def modify_account_cloud(cur_user, user, form):
|
||||
global G_cloudmgr
|
||||
logger.info("handle request: cloud/setting/modify/")
|
||||
result = G_cloudmgr.modifySettingFile(form.get('setting',None))
|
||||
return json.dumps(result)
|
||||
|
||||
@app.route("/cloud/node/add/", methods=['POST'])
|
||||
@login_required
|
||||
def add_node_cloud(user, beans, form):
|
||||
global G_cloudmgr
|
||||
logger.info("handle request: cloud/node/add/")
|
||||
G_cloudmgr.engine.addNodeAsync()
|
||||
result = {'success':'true'}
|
||||
return json.dumps(result)
|
||||
|
||||
@app.route("/addproxy/", methods=['POST'])
|
||||
@login_required
|
||||
def addproxy(user, beans, form):
|
||||
|
@ -760,6 +785,7 @@ if __name__ == '__main__':
|
|||
global G_historymgr
|
||||
global G_applicationmgr
|
||||
global G_ulockmgr
|
||||
global G_cloudmgr
|
||||
# move 'tools.loadenv' to the beginning of this file
|
||||
|
||||
fs_path = env.getenv("FS_PREFIX")
|
||||
|
@ -851,6 +877,8 @@ if __name__ == '__main__':
|
|||
G_networkmgr = network.NetworkMgr(clusternet, etcdclient, mode, ipaddr)
|
||||
G_networkmgr.printpools()
|
||||
|
||||
G_cloudmgr = cloudmgr.CloudMgr()
|
||||
|
||||
# start NodeMgr and NodeMgr will wait for all nodes to start ...
|
||||
G_nodemgr = nodemgr.NodeMgr(G_networkmgr, etcdclient, addr = ipaddr, mode=mode)
|
||||
logger.info("nodemgr started")
|
||||
|
|
|
@ -968,62 +968,6 @@ class userManager:
|
|||
lxcsettingfile.close()
|
||||
return {"success": 'true'}
|
||||
|
||||
@administration_required
|
||||
def cloud_account_query(*args, **kwargs):
|
||||
accountfile = open(fspath+"/global/sys/cloudaccount", 'r')
|
||||
account = json.loads(accountfile.read())
|
||||
accountfile.close()
|
||||
return {"success": 'true', 'accounts':account}
|
||||
|
||||
@administration_required
|
||||
def cloud_account_add(*args, **kwargs):
|
||||
form = kwargs.get('form')
|
||||
accountfile = open(fspath+"/global/sys/cloudaccount", 'r')
|
||||
account = json.loads(accountfile.read())
|
||||
accountfile.close()
|
||||
account.append(
|
||||
{ 'cloudname' : form['cloudname'],
|
||||
'username' : form['username'],
|
||||
'password' : form['password'],
|
||||
})
|
||||
accountfile = open(fspath+"/global/sys/cloudaccount", 'w')
|
||||
accountfile.write(json.dumps(account))
|
||||
accountfile.close()
|
||||
return {"success": 'true'}
|
||||
|
||||
@administration_required
|
||||
def cloud_account_del(*args, **kwargs):
|
||||
form = kwargs.get('form')
|
||||
cloudname = form['cloudname']
|
||||
accountfile = open(fspath+"/global/sys/cloudaccount", 'r')
|
||||
account = json.loads(accountfile.read())
|
||||
accountfile.close()
|
||||
for acc in account:
|
||||
if acc['cloudname'] == cloudname:
|
||||
account.remove(acc)
|
||||
break
|
||||
accountfile = open(fspath+"/global/sys/cloudaccount", 'w')
|
||||
accountfile.write(json.dumps(account))
|
||||
accountfile.close()
|
||||
return {"success": 'true'}
|
||||
|
||||
@administration_required
|
||||
def cloud_account_modify(*args, **kwargs):
|
||||
form = kwargs.get('form')
|
||||
cloudname = form['cloudname']
|
||||
accountfile = open(fspath+"/global/sys/cloudaccount", 'r')
|
||||
account = json.loads(accountfile.read())
|
||||
accountfile.close()
|
||||
for acc in account:
|
||||
if acc['cloudname'] == cloudname:
|
||||
acc['username'] = form['username']
|
||||
acc['password'] = form['password']
|
||||
break
|
||||
accountfile = open(fspath+"/global/sys/cloudaccount", 'w')
|
||||
accountfile.write(json.dumps(account))
|
||||
accountfile.close()
|
||||
return {"success": "true"}
|
||||
|
||||
|
||||
def queryForDisplay(*args, **kwargs):
|
||||
'''
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"CloudName": "aliyun",
|
||||
"AccessKeyId": "your-key",
|
||||
"AccessKeySecret": "your-secret",
|
||||
"RegionId": "cn-beijing",
|
||||
"ZoneId": "cn-beijing-a",
|
||||
"InstanceType": "ecs.sn1ne.xlarge",
|
||||
"SystemDisk.Size": 500,
|
||||
"Password": "Unias1234",
|
||||
"VSwitchId": "the vswitchid of your vpc",
|
||||
"VolumeName": "the volume name (with host name) of your glusterfs"
|
||||
}
|
|
@ -1,53 +1,28 @@
|
|||
if [ $# -lt 1 ]; then
|
||||
echo "please input master ip";
|
||||
exit 1;
|
||||
fi
|
||||
MASTER_IP=$1
|
||||
|
||||
#配置apt源
|
||||
#echo "deb http://mirrors.ustc.edu.cn/ubuntu/ xenial main restricted universe multiverse
|
||||
#deb http://mirrors.ustc.edu.cn/ubuntu/ xenial-security main restricted universe multiverse
|
||||
#deb http://mirrors.ustc.edu.cn/ubuntu/ xenial-updates main restricted universe multiverse
|
||||
#deb http://mirrors.ustc.edu.cn/ubuntu/ xenial-proposed main restricted universe multiverse
|
||||
#deb http://mirrors.ustc.edu.cn/ubuntu/ xenial-backports main restricted universe multiverse" > /etc/apt/sources.list
|
||||
|
||||
#更新apt
|
||||
apt-get update
|
||||
|
||||
#更新hosts
|
||||
echo "$MASTER_IP docklet-master" >> /etc/hosts
|
||||
apt-get install -y git
|
||||
|
||||
#下载git包
|
||||
apt-get -y install git
|
||||
|
||||
#下载docklet源码
|
||||
git clone http://github.com/unias/docklet.git /home/docklet
|
||||
|
||||
#运行prepare.sh
|
||||
/home/docklet/prepare.sh
|
||||
|
||||
#挂载global目录,通过gluster方式
|
||||
mount -t glusterfs docklet-master:/docklet /opt/docklet/global
|
||||
|
||||
#下载base镜像
|
||||
wget http://docklet.unias.org/images/basefs-0.11.tar.bz2 -P /opt/local/temp
|
||||
|
||||
#解压镜像
|
||||
tar -zxvf /opt/local/temp/basefs-0.11.tar.bz2 -C /opt/local/
|
||||
|
||||
#获得docklet.conf
|
||||
cp /home/docklet/conf/docklet.conf.template /home/docklet/conf/docklet.conf
|
||||
cp /home/docklet/web/templates/home.template /home/docklet/web/templates/home.html
|
||||
|
||||
#获得网卡名称
|
||||
NETWORK_DEVICE=`route | grep default | awk {'print $8'};`
|
||||
|
||||
#更改配置文件
|
||||
echo "DISKPOOL_SIZE=10000
|
||||
ETCD=$MASTER_IP:2379
|
||||
echo "DISKPOOL_SIZE=200000
|
||||
ETCD=%MASTERIP%:2379
|
||||
NETWORK_DEVICE=$NETWORK_DEVICE
|
||||
PORTAL_URL=http://iwork.pku.edu.cn
|
||||
PROXY_PORT=80" >> /home/docklet/conf/docklet.conf
|
||||
PROXY_PORT=8000
|
||||
NGINX_PORT=80" >> /home/docklet/conf/docklet.conf
|
||||
|
||||
#please modify the mount command for your corresponding distributed file system if you are not using glusterfs
|
||||
mount -t glusterfs %VOLUMENAME% /opt/docklet/global/
|
||||
|
||||
if [ -f /opt/docklet/global/packagefs.tgz ]; then
|
||||
tar zxvf /opt/docklet/global/packagefs.tgz -C /opt/docklet/local/ > /dev/null
|
||||
fi
|
||||
|
||||
#启动worker
|
||||
/home/docklet/bin/docklet-worker start
|
||||
exit 0
|
||||
|
|
32
user/user.py
32
user/user.py
|
@ -164,38 +164,6 @@ def auth_token(cur_user, user, form):
|
|||
logger.info("auth success")
|
||||
return req
|
||||
|
||||
@app.route("/cloud/account/query/", methods=['POST'])
|
||||
@login_required
|
||||
def query_account_cloud(cur_user, user, form):
|
||||
global G_usermgr
|
||||
logger.info("handle request: cloud/account/query/")
|
||||
result = G_usermgr.cloud_account_query(cur_user = cur_user)
|
||||
return json.dumps(result)
|
||||
|
||||
@app.route("/cloud/account/add/", methods=['POST'])
|
||||
@login_required
|
||||
def add_account_cloud(cur_user, user, form):
|
||||
global G_usermgr
|
||||
logger.info("handle request: cloud/account/add/")
|
||||
result = G_usermgr.cloud_account_add(cur_user = cur_user, form = form)
|
||||
return json.dumps(result)
|
||||
|
||||
@app.route("/cloud/account/delete/", methods=['POST'])
|
||||
@login_required
|
||||
def del_account_cloud(cur_user, user, form):
|
||||
global G_usermgr
|
||||
logger.info("handle request: cloud/account/delete/")
|
||||
result = G_usermgr.cloud_account_del(cur_user = cur_user, form = form)
|
||||
return json.dumps(result)
|
||||
|
||||
@app.route("/cloud/account/modify/", methods=['POST'])
|
||||
@login_required
|
||||
def modify_account_cloud(cur_user, user, form):
|
||||
global G_usermgr
|
||||
logger.info("handle request: cloud/account/modify/")
|
||||
result = G_usermgr.cloud_account_modify(cur_user = cur_user, form = form)
|
||||
return json.dumps(result)
|
||||
|
||||
|
||||
@app.route("/user/modify/", methods=['POST'])
|
||||
@login_required
|
||||
|
|
|
@ -174,7 +174,7 @@
|
|||
</li>
|
||||
|
||||
<li id="nav_Cloud">
|
||||
<a href='/cloud/'><i class="fa fa-cloud"></i> <span class="nav-label">Cloud</span></a>
|
||||
<a href="/cloud/"><i class="fa fa-cloud"></i> <span class="nav-label">Cloud</span></a>
|
||||
<li>
|
||||
<li id="logs">
|
||||
<a href='/logs/'><i class="fa fa-user"></i> <span class="nav-label">Logs</span></a>
|
||||
|
|
|
@ -24,11 +24,27 @@
|
|||
|
||||
|
||||
{% block content %}
|
||||
<ul class="nav nav-tabs" role="tablist" id="myTabs">
|
||||
{% for master,info in settings.items() %}
|
||||
{% if loop.index == 1 %}
|
||||
<li role="presentation" class="active"><a href="#{{master.split("@")[1]}}" data-toggle="tab" aria-controls="{{master.split("@")[1]}}">{{master.split("@")[1]}}</a></li>
|
||||
{% else %}
|
||||
<li role="presentation"><a href="#{{master.split("@")[1]}}" data-toggle="tab" aria-controls="{{master.split("@")[1]}}">{{master.split("@")[1]}}</a></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<div id="myTabContent" class="tab-content">
|
||||
{% for master,info in settings.items() %}
|
||||
{% if loop.index == 1 %}
|
||||
<div role="tabpanel" class="tab-pane active" aria-labelledby="{{master.split("@")[1]}}" id="{{master.split("@")[1]}}">
|
||||
{% else %}
|
||||
<div role="tabpanel" class="tab-pane" aria-labelledby="{{master.split("@")[1]}}" id="{{master.split("@")[1]}}">
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="box box-info">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Cloud</h3>
|
||||
<h3 class="box-title">Cloud Setting</h3>
|
||||
|
||||
<div class="box-tools pull-right">
|
||||
<button type="button" class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i>
|
||||
|
@ -37,117 +53,21 @@
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<p>
|
||||
<button type="button" class="btn btn-primary btn-sm" data-toggle="modal" data-target="#AddAccountModal"><i class="fa fa-plus"></i> Add Account</button>
|
||||
</p>
|
||||
<div class="modal inmodal" id="AddAccountModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content animated fadeIn">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
|
||||
<i class="fa fa-laptop modal-icon"></i>
|
||||
<h4 class="modal-title">Add Account</h4>
|
||||
<small class="font-bold">Add a Cloud Account</small>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<form action="/cloud/account/add/" method="POST" id="addAccountForm">
|
||||
<div class="form-group">
|
||||
<label>Cloud name</label>
|
||||
<input type="text" class="form-control" name="cloudname"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>username</label>
|
||||
<input type="text" class="form-control" name="username" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>password</label>
|
||||
<input type="text" class="form-control" name="password" />
|
||||
</div>
|
||||
</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:sendAddAccount();">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table table-responsive">
|
||||
<table id="accountTable" class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Cloud Name</th>
|
||||
<th>username</th>
|
||||
<th>password</th>
|
||||
<th>Command</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for account in accounts %}
|
||||
<tr>
|
||||
<th>{{ account['cloudname'] }}</th>
|
||||
<th>{{ account['username'] }}</th>
|
||||
<th>{{ account['password'] }}</th>
|
||||
<th><a class="btn btn-xs btn-info" data-toggle="modal" data-target="#ModifyAccountModal_{{ account['cloudname'] }}">Edit</a>
|
||||
<a class="btn btn-xs btn-danger" href="/cloud/account/delete/{{account['cloudname']}}">Delete</a>
|
||||
</th>
|
||||
<div class="modal inmodal" id="ModifyAccountModal_{{ account['cloudname'] }}" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content animated fadeIn">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
|
||||
<i class="fa fa-laptop modal-icon"></i>
|
||||
<h4 class="modal-title">Modify Account</h4>
|
||||
<small class="font-bold">Modify a Cloud Account</small>
|
||||
</div>
|
||||
<form action="/cloud/account/modify/{{account['cloudname']}}/" method="POST" >
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label>Cloud Name</label>
|
||||
<input type="text" placeholder="Enter Name" class="form-control" name="cloudname" readonly="true" value={{ account['cloudname'] }} />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>username</label>
|
||||
<input type="text" class="form-control" name="username" value={{ account['username']}} />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>password</label>
|
||||
<input type="text" class="form-control" name="password" value={{ account['password']}} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-white" data-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="box-body table-responsive">
|
||||
<form action="/cloud/{{master.split("@")[0]}}/setting/modify/" method="POST">
|
||||
<textarea id="setting" name="setting" class="form-control" rows="20">{{ info['result'] }}</textarea>
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
{% block script_src %}
|
||||
<script src="//cdn.bootcss.com/datatables/1.10.11/js/jquery.dataTables.min.js"></script>
|
||||
<script src="//cdn.bootcss.com/datatables/1.10.11/js/dataTables.bootstrap.min.js"></script>
|
||||
<script src="http://cdn.bootcss.com/datatables-tabletools/2.1.5/js/TableTools.min.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$('#accountTable').DataTable();
|
||||
})
|
||||
function sendAddAccount(){
|
||||
document.getElementById("addAccountForm").submit();
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
</div>
|
||||
<div class="box-body table-responsive">
|
||||
<p>
|
||||
<button type="button" class="btn btn-primary btn-sm" data-toggle="modal"><i class="fa fa-plus"></i>Add Host</button>
|
||||
<a href="/cloud/{{master.split("@")[0]}}/node/add/"><button type="button" class="btn btn-primary btn-sm"><i class="fa fa-plus"></i>Add Host</button></a>
|
||||
</p>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
|
|
22
web/web.py
22
web/web.py
|
@ -517,22 +517,17 @@ def userquery():
|
|||
def cloud():
|
||||
return cloudView.as_view()
|
||||
|
||||
@app.route("/cloud/account/add/", methods = ['POST'])
|
||||
@app.route("/cloud/<masterip>/setting/modify/", methods = ['POST'])
|
||||
@administration_required
|
||||
def cloud_account_add():
|
||||
return cloudAccountAddView.as_view()
|
||||
def cloud_setting_modify(masterip):
|
||||
cloudSettingModifyView.masterip = masterip
|
||||
return cloudSettingModifyView.as_view()
|
||||
|
||||
@app.route("/cloud/account/delete/<cloudname>/", methods = ['POST', 'GET'])
|
||||
@app.route("/cloud/<masterip>/node/add/", methods = ['POST', 'GET'])
|
||||
@administration_required
|
||||
def cloud_account_del(cloudname):
|
||||
cloudAccountDelView.cloudname = cloudname
|
||||
return cloudAccountDelView.as_view()
|
||||
|
||||
|
||||
@app.route("/cloud/account/modify/<cloudname>/", methods = ['POST'])
|
||||
@administration_required
|
||||
def cloud_account_modify(cloudname):
|
||||
return cloudAccountModifyView.as_view()
|
||||
def cloud_node_add(masterip):
|
||||
cloudNodeAddView.masterip = masterip
|
||||
return cloudNodeAddView.as_view()
|
||||
|
||||
|
||||
@app.route("/notification/", methods=['GET'])
|
||||
|
@ -606,6 +601,7 @@ def adminpage():
|
|||
def updatesettings():
|
||||
return updatesettingsView.as_view()
|
||||
|
||||
|
||||
@app.route('/index/', methods=['GET'])
|
||||
def jupyter_control():
|
||||
return redirect('/dashboard/')
|
||||
|
|
|
@ -9,34 +9,26 @@ class cloudView(normalView):
|
|||
|
||||
@classmethod
|
||||
def post(self):
|
||||
accounts = dockletRequest.post('/cloud/account/query/').get('accounts',[])
|
||||
return self.render(self.template_path, accounts = accounts)
|
||||
settings = dockletRequest.post_to_all('/cloud/setting/get/')
|
||||
return self.render(self.template_path, settings = settings)
|
||||
|
||||
@classmethod
|
||||
def get(self):
|
||||
return self.post()
|
||||
|
||||
class cloudAccountAddView(normalView):
|
||||
class cloudSettingModifyView(normalView):
|
||||
@classmethod
|
||||
def post(self):
|
||||
dockletRequest.post('/cloud/account/add/', request.form)
|
||||
dockletRequest.post('/cloud/setting/modify/', request.form, self.masterip)
|
||||
return redirect('/cloud/')
|
||||
|
||||
class cloudAccountDelView(normalView):
|
||||
class cloudNodeAddView(normalView):
|
||||
@classmethod
|
||||
def post(self):
|
||||
data = {
|
||||
'cloudname' : self.cloudname,
|
||||
}
|
||||
dockletRequest.post('/cloud/account/delete/', data)
|
||||
return redirect('/cloud/')
|
||||
data = {}
|
||||
dockletRequest.post('/cloud/node/add/', data, self.masterip)
|
||||
return redirect('/hosts/')
|
||||
|
||||
@classmethod
|
||||
def get(self):
|
||||
return self.post()
|
||||
|
||||
class cloudAccountModifyView(normalView):
|
||||
@classmethod
|
||||
def post(self):
|
||||
dockletRequest.post('/cloud/account/modify/', request.form)
|
||||
return redirect('/cloud/')
|
||||
|
|
|
@ -38,7 +38,6 @@ class dockletRequest():
|
|||
'user',
|
||||
'beans',
|
||||
'notification',
|
||||
'cloud',
|
||||
'settings'
|
||||
}
|
||||
if ":" not in endpoint:
|
||||
|
|
Loading…
Reference in New Issue