diff --git a/cloudsdk-installer.sh b/cloudsdk-installer.sh new file mode 100755 index 0000000..e8f0282 --- /dev/null +++ b/cloudsdk-installer.sh @@ -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 diff --git a/prepare.sh b/prepare.sh index eaba6fe..0025f5b 100755 --- a/prepare.sh +++ b/prepare.sh @@ -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 diff --git a/src/cloudmgr.py b/src/cloudmgr.py index f519c5d..8b07b6f 100755 --- a/src/cloudmgr.py +++ b/src/cloudmgr.py @@ -3,6 +3,7 @@ from io import StringIO import os,sys,subprocess,time,re,datetime,threading,random from model import db, Image from deploy import * +import json from log import logger import env @@ -10,67 +11,54 @@ import requests fspath = env.getenv('FS_PREFIX') -class CludAccountMgr(): - def cloud_account_query(*args, **kwargs): - try: - accountfile = open(fspath+"/global/sys/cloudaccount", 'r') - account = json.loads(accountfile.read()) - accountfile.close() - except: - account = {} - account['cloud'] = env.getenv('CLOUD') - account['accesskey'] = "" - account['accesssecret'] = "" - return {"success": 'true', 'accounts':account} - - def cloud_account_modify(*args, **kwargs): - form = kwargs.get('form') - account = {} - account['cloud'] = form['cloud'] - account['accesskey'] = form['accesskey'] - account['accesssecret'] = form['accesssecret'] - accountfile = open(fspath+"/global/sys/cloudaccount", 'w') - accountfile.write(json.dumps(account)) - accountfile.close() - return {"success": "true"} - class AliyunMgr(): def __init__(self): - self.AcsClient = __import__('aliyunsdkcore.client') - self.Request = __import__('aliyunsdkecs.request.v20140526') + 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): try: - accountfile = open(fspath+"/global/sys/cloudaccount", 'r') - account = json.loads(accountfile.read()) - accountfile.close() - self.clt = self.AcsClient.AcsClient(account['accesskey'],account['accesssecret'],'cn-shanghai') + 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: + except Exception as e: + logger.error(e) logger.error("account file not existed, can not load CLT") return False - def createInstance(self,password): + def createInstance(self): request = self.Request.CreateInstanceRequest.CreateInstanceRequest() request.set_accept_format('json') - request.add_query_param('RegionId', 'cn-shanghai') + 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', 500) - request.add_query_param('InstanceType', 'ecs.xn4.small') - request.add_query_param('Password', password) + 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) - # 获取实例ID instanceid=json.loads(bytes.decode(response))['InstanceId'] return instanceid - # 启动ECS def startInstance(self, instanceid): request = self.Request.StartInstanceRequest.StartInstanceRequest() request.set_accept_format('json') @@ -79,11 +67,10 @@ class AliyunMgr(): logger.info(response) - # 创建EIP def createEIP(self): request = self.Request.AllocateEipAddressRequest.AllocateEipAddressRequest() request.set_accept_format('json') - request.add_query_param('RegionId', 'cn-shanghai') + request.add_query_param('RegionId', self.setting['RegionId']) response = self.clt.do_action_with_exception(request) logger.info(response) @@ -94,7 +81,6 @@ class AliyunMgr(): return [eipid, eipaddr] - # 绑定EIP def associateEIP(self, instanceid, eipid): request = self.Request.AssociateEipAddressRequest.AssociateEipAddressRequest() request.set_accept_format('json') @@ -125,12 +111,12 @@ class AliyunMgr(): return False return True - def rentServers(self): + def rentServers(self,number): instanceids=[] eipids=[] eipaddrs=[] for i in range(int(number)): - instanceids.append(self.createInstance(password)) + instanceids.append(self.createInstance()) time.sleep(2) time.sleep(10) for i in range(int(number)): @@ -152,16 +138,25 @@ class AliyunMgr(): time.sleep(5) return [masterip, eipaddrs] - def addNodes(self,number=1,password="Unias1616"): - if not loadClient(): - return False - [masterip, eipaddrs] = self.rentServers(number,password) + 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',password)) + 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 True + return {'success':'true'} + + def addNodeAsync(self): + thread = threading.Thread(target = self.addNode) + thread.setDaemon(True) + thread.start() + +class CloudMgr(): + def __init__(self): + self.engine = AliyunMgr() diff --git a/src/deploy.py b/src/deploy.py index 79d11d3..0be22a4 100755 --- a/src/deploy.py +++ b/src/deploy.py @@ -1,26 +1,25 @@ #!/usr/bin/python3 -#实例ip调用describe api获取 import paramiko, time from log import logger -import env +import env,os def myexec(ssh,command): stdin,stdout,stderr = ssh.exec_command(command) - endtime = time.time() + 300 + 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) -#上传deploy脚本 -def deploy(ipaddr,masterip,account,password): +def deploy(ipaddr,masterip,account,password,volumename): while True: try: transport = paramiko.Transport((ipaddr,22)) @@ -31,12 +30,12 @@ def deploy(ipaddr,masterip,account,password): pass sftp = paramiko.SFTPClient.from_transport(transport) - fspath = env.getenv('FS_PREFIX') - sftp.put('/home/zhong/docklet-deploy.sh','/root/docklet-deploy.sh') - sftp.put('/home/zhong/docklet-deploy.sh','/root/docklet-deploy.sh') + 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() - #执行deploy脚本 ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) while True: @@ -46,11 +45,9 @@ def deploy(ipaddr,masterip,account,password): except Exception as e: time.sleep(2) pass - #这行日后可以删掉 - myexec(ssh,'chmod +x /root/docklet-deploy.sh') myexec(ssh,"sed -i 's/%MASTERIP%/" + masterip + "/g' /root/docklet-deploy.sh") - myexec(ssh,'/root/docklet-deploy.sh ' + ipaddr) - myexec(ssh,'mount -t glusterfs ' + masterip + ':docklet /opt/docklet/global/') - myexec(ssh,'/home/docklet/bin/docklet-worker start') + 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 diff --git a/src/httprest.py b/src/httprest.py index b44e5db..0274eb5 100755 --- a/src/httprest.py +++ b/src/httprest.py @@ -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 @@ -470,6 +470,14 @@ def modify_account_cloud(cur_user, user, form): result = G_usermgr.cloud_account_modify(cur_user = cur_user, form = form) 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 @@ -793,6 +801,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") @@ -884,6 +893,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") diff --git a/tools/cloudsetting.aliyun.template.json b/tools/cloudsetting.aliyun.template.json new file mode 100644 index 0000000..1990e4b --- /dev/null +++ b/tools/cloudsetting.aliyun.template.json @@ -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" +} diff --git a/tools/docklet-deploy.sh b/tools/docklet-deploy.sh index 2515baa..233424b 100755 --- a/tools/docklet-deploy.sh +++ b/tools/docklet-deploy.sh @@ -1,51 +1,28 @@ -#配置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 - -#下载git包 apt-get install -y git -#下载docklet源码 git clone http://github.com/unias/docklet.git /home/docklet -#运行prepare.sh -sed -i '61s/^/#&/g' /home/docklet/prepare.sh -sed -i '62s/^/#&/g' /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 - -#解压镜像 -#mkdir -p /opt/docklet/local -#mkdir -p /opt/docklet/global -#tar -jxvf /root/basefs-0.11.tar.bz2 -C /opt/docklet/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=20000 +echo "DISKPOOL_SIZE=200000 ETCD=%MASTERIP%:2379 NETWORK_DEVICE=$NETWORK_DEVICE PROXY_PORT=8000 NGINX_PORT=80" >> /home/docklet/conf/docklet.conf -#启动worker -#/home/docklet/bin/docklet-supermaster init -#/home/docklet/bin/docklet-worker start +#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 + +/home/docklet/bin/docklet-worker start exit 0 diff --git a/web/templates/base_AdminLTE.html b/web/templates/base_AdminLTE.html index de4cf47..f977921 100644 --- a/web/templates/base_AdminLTE.html +++ b/web/templates/base_AdminLTE.html @@ -174,7 +174,7 @@
  • Logs diff --git a/web/templates/monitor/hosts.html b/web/templates/monitor/hosts.html index 76fe858..af7afd0 100644 --- a/web/templates/monitor/hosts.html +++ b/web/templates/monitor/hosts.html @@ -46,7 +46,7 @@

    - +

    diff --git a/web/web.py b/web/web.py index 94eaa7c..c35fe7a 100755 --- a/web/web.py +++ b/web/web.py @@ -534,6 +534,12 @@ def cloud_account_del(cloudname): def cloud_account_modify(cloudname): return cloudAccountModifyView.as_view() +@app.route("/cloud//node/add/", methods = ['POST', 'GET']) +@administration_required +def cloud_node_add(masterip): + cloudNodeAddView.masterip = masterip + return cloudNodeAddView.as_view() + @app.route("/notification/", methods=['GET']) @administration_required @@ -606,6 +612,7 @@ def adminpage(): def updatesettings(): return updatesettingsView.as_view() + @app.route('/index/', methods=['GET']) def jupyter_control(): return redirect('/dashboard/') diff --git a/web/webViews/cloud.py b/web/webViews/cloud.py index d67ef9e..38182a9 100644 --- a/web/webViews/cloud.py +++ b/web/webViews/cloud.py @@ -40,3 +40,14 @@ class cloudAccountModifyView(normalView): def post(self): dockletRequest.post('/cloud/account/modify/', request.form) return redirect('/cloud/') + +class cloudNodeAddView(normalView): + @classmethod + def post(self): + data = {} + dockletRequest.post('/cloud/node/add/', data, self.masterip) + return redirect('/hosts/') + + @classmethod + def get(self): + return self.post()