Browse Source

一键部署-服务器管理界面

maxiaoshan 3 years ago
parent
commit
e9d1e44462
2 changed files with 657 additions and 0 deletions
  1. 270 0
      src/vps/aliecs.go
  2. 387 0
      src/web/templates/vps/vpslist.html

+ 270 - 0
src/vps/aliecs.go

@@ -0,0 +1,270 @@
+/**
+阿里云ecs实例自动申请、部署、释放
+**/
+package vps
+
+import (
+	"bytes"
+	"crypto/hmac"
+	"crypto/sha1"
+	"encoding/base64"
+	"encoding/json"
+	"fmt"
+	"hash"
+	"io"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"net/url"
+	qu "qfw/util"
+	"sort"
+	"strings"
+	"time"
+	"util"
+)
+
+const (
+	Password = "Jy_ExtractBid_2019"
+	URL      = "https://ecs.aliyuncs.com"
+)
+
+//批量创建实例
+func RunInstances(name, image, stype string, public bool, num, hours int, ok *bool) {
+	defer qu.Catch()
+	widthOut := "0"
+	if public { //是否公网
+		widthOut = "10"
+	}
+	if VpsConfig.Available {
+		if zoneIdsLen := len(VpsConfig.ZoneIds); zoneIdsLen > 0 {
+			pernum := num / zoneIdsLen //申请/配置
+			if pernum < 1 {
+				zoneMap := VpsConfig.ZoneIds[zoneIdsLen-1]
+				*ok = runInstances(zoneMap, name, widthOut, stype, image, num, hours)
+			} else {
+				for index, zoneMap := range VpsConfig.ZoneIds {
+					if (index == zoneIdsLen-1) && (num%zoneIdsLen != 0) {
+						pernum = num - pernum*(zoneIdsLen-1)
+					}
+					*ok = runInstances(zoneMap, name, widthOut, stype, image, pernum, hours)
+				}
+			}
+		}
+	}
+}
+
+func runInstances(zoneMap ZoneMap, name, widthOut, stype, image string, pernum, hours int) bool {
+	log.Println("apply ecs:", zoneMap, name, widthOut, stype, pernum, hours)
+	applyOk := false
+	launchTemplateId := zoneMap.LaunchTemplateId4
+	if stype == "8" {
+		launchTemplateId = zoneMap.LaunchTemplateId8
+	}
+	res := GET("RunInstances", [][]string{
+		[]string{"RegionId", "cn-beijing"},             //实例所属的地域ID
+		[]string{"ZoneId", zoneMap.ZoneId},             //实例所属的可用区ID(设置了VSwitchId参数,则设置的ZoneId参数必须和交换机所在的可用区保持一致)
+		[]string{"VSwitchId", zoneMap.VswitchId},       //虚拟交换机ID
+		[]string{"LaunchTemplateId", launchTemplateId}, //启动模板ID
+		//[]string{"ImageId", "centos_7_06_64_20G_alibase_20181212.vhd"},//镜像ID
+		//[]string{"InstanceType", "ecs.ic5.large"},
+		//[]string{"SecurityGroupId", "sg-bp16x3td2evrejhkshp7"},[]string{"InternetMaxBandwidthIn", "50"},
+		[]string{"InternetMaxBandwidthOut", widthOut},  //公网出带宽最大值
+		[]string{"InstanceChargeType", "PostPaid"},     //付费方式
+		[]string{"SpotStrategy", "SpotWithPriceLimit"}, //按量付费实例的竞价策略(SpotWithPriceLimit:设置上限价格的抢占式实例。NoSpot:正常按量付费实例。SpotAsPriceGo:系统自动出价,跟随当前市场实际价格。)
+		[]string{"SpotPriceLimit", "4.99"},             //设置实例的每小时最高价格
+		[]string{"InstanceName", "lua"},                //实例名称
+		[]string{"UniqueSuffix", "true"},               //当创建多台实例时,是否为HostName和InstanceName自动添加有序后缀
+		[]string{"Password", Password},
+		[]string{"Amount", fmt.Sprint(pernum)},
+		[]string{"AutoReleaseTime", time.Now().Add(time.Duration(hours) * time.Hour).UTC().Format("2006-01-02T15:04:05Z")},
+	})
+	if tmp, ok := res["InstanceIdSets"].(map[string]interface{}); ok {
+		if t, ok := tmp["InstanceIdSet"].([]interface{}); ok {
+			applyOk = true
+			//实例id持久化
+			for _, v := range t {
+				util.MgoE.Save("ecs", map[string]interface{}{
+					"InstanceId": v,
+					"TaskName":   name,
+					"UseFor":     "lua",
+					"VpsState":   0,
+				})
+			}
+		} else {
+			applyOk = false
+		}
+	} else {
+		applyOk = false
+	}
+	return applyOk
+}
+
+//查询多台实例的详细信息
+func DescribeInstances(ok *bool) {
+	res := GET("DescribeInstances", [][]string{
+		[]string{"RegionId", "cn-beijing"},
+		[]string{"InstanceChargeType", "PostPaid"},
+		[]string{"PageSize", "100"},
+	})
+	if len(res) > 0 {
+		*ok = true
+	}
+	for _, ins := range res["Instances"].(map[string]interface{}) {
+		for _, val := range ins.([]interface{}) {
+			if tmp, ok := val.(map[string]interface{}); ok {
+				if t, ok := tmp["VpcAttributes"].(map[string]interface{}); ok {
+					if tt, ok := t["PrivateIpAddress"].(map[string]interface{}); ok {
+						ttt := tt["IpAddress"].([]interface{})
+						tmp["ip_nw"] = ttt[0]
+					}
+				}
+				if t, ok := tmp["PublicIpAddress"].(map[string]interface{}); ok {
+					if tt, ok := t["IpAddress"].([]interface{}); ok && len(tt) > 0 {
+						tmp["ip_ww"] = tt[0]
+					}
+				}
+				if strings.Contains(qu.ObjToString(tmp["InstanceName"]), "lua") {
+					//更新实例信息
+					util.MgoE.Update("ecs", `{"InstanceId":"`+qu.ObjToString(tmp["InstanceId"])+`"}`, map[string]interface{}{"$set": tmp}, true, false)
+				}
+			}
+		}
+	}
+}
+
+//停止实例
+func StopInstance(InstanceId string) {
+	res := GET("StopInstance", [][]string{
+		[]string{"InstanceId", InstanceId},
+	})
+	util.MgoE.Update("ecs", `{"InstanceId":"`+InstanceId+`"}`, map[string]interface{}{"$set": map[string]interface{}{"Status": "Released"}}, true, false)
+	log.Println("StopInstance", res)
+}
+
+//释放实例
+func DeleteInstance(InstanceId string, ok *bool) {
+	res := GET("DeleteInstance", [][]string{
+		[]string{"InstanceId", InstanceId},
+		[]string{"Force", "true"},
+	})
+	if len(res) > 0 {
+		*ok = true
+	}
+	util.MgoE.Update("ecs", `{"InstanceId":"`+InstanceId+`"}`, map[string]interface{}{"$set": map[string]interface{}{"Status": "Deleted"}}, true, false)
+	qu.Debug("DeleteInstance", res)
+}
+
+//实例自动释放时间
+func ModifyInstanceAutoReleaseTime(InstanceId string, hours int, ok *bool) {
+	qu.Debug(VpsConfig.AccessId, time.Now().Add(time.Duration(hours)*time.Hour).UTC().Format("2006-01-02T15:04:05Z"))
+	res := GET("ModifyInstanceAutoReleaseTime", [][]string{
+		[]string{"InstanceId", InstanceId},
+		[]string{"RegionId", "cn-beijing"},
+		[]string{"AutoReleaseTime", time.Now().Add(time.Duration(hours) * time.Hour).UTC().Format("2006-01-02T15:04:05Z")},
+	})
+	*ok = len(res) > 0
+	qu.Debug("ModifyInstanceAutoReleaseTime", res)
+}
+
+//GET请求
+func GET(action string, param [][]string) (mres map[string]interface{}) {
+	ps := &paramSorter{[]string{
+		"Format",
+		"Version",
+		"SignatureMethod",
+		"SignatureNonce",
+		"SignatureVersion",
+		"AccessKeyId",
+		"Timestamp",
+	}, []string{
+		"JSON",
+		"2014-05-26",
+		"HMAC-SHA1",
+		fmt.Sprintf("%d", time.Now().UnixNano()/1000),
+		"1.0",
+		VpsConfig.AccessId,
+		time.Now().UTC().Format("2006-01-02T15:04:05Z"),
+	}}
+	ps.Keys = append(ps.Keys, "Action")
+	ps.Vals = append(ps.Vals, action)
+	if len(param) > 0 {
+		for _, v := range param {
+			ps.Keys = append(ps.Keys, v[0])
+			ps.Vals = append(ps.Vals, v[1])
+		}
+	}
+	ps.Sort()
+	reqStr := ps.String()
+	str := "GET&" +
+		percentEncode("/") + "&" +
+		percentEncode(reqStr)
+	str = SP(str, "%3A", "%253A", -1)
+	h := hmac.New(func() hash.Hash { return sha1.New() }, []byte(VpsConfig.AccessSecret+"&"))
+	io.WriteString(h, str)
+	signedStr := base64.StdEncoding.EncodeToString(h.Sum(nil))
+	ps.Keys = append(ps.Keys, "Signature")
+	ps.Vals = append(ps.Vals, signedStr)
+	reqStr = ps.Query()
+	res, err := http.Get(fmt.Sprintf("%s?%s", URL, reqStr))
+	if err != nil {
+		log.Println(err.Error())
+	} else {
+		defer res.Body.Close()
+		bt, err := ioutil.ReadAll(res.Body)
+		if err == nil {
+			//log.Println(string(bt))
+			err := json.Unmarshal(bt, &mres)
+			if err != nil {
+				log.Println(err.Error())
+			}
+		}
+	}
+	return
+}
+
+var SP = strings.Replace
+
+func percentEncode(str string) string {
+	str = url.QueryEscape(str)
+	str = SP(SP(SP(str, "+", "%20", -1), "*", "%2A", -1), "%7E", "~", -1)
+	return str
+}
+
+type paramSorter struct {
+	Keys []string
+	Vals []string
+}
+
+func (ps *paramSorter) String() string {
+	str := ""
+	for n, k := range ps.Keys {
+		str += k + "=" + ps.Vals[n]
+		if n < len(ps.Keys)-1 {
+			str += "&"
+		}
+	}
+	return str
+}
+func (ps *paramSorter) Query() string {
+	str := ""
+	for n, k := range ps.Keys {
+		str += k + "=" + url.QueryEscape(ps.Vals[n])
+		if n < len(ps.Keys)-1 {
+			str += "&"
+		}
+	}
+	return str
+}
+func (ps *paramSorter) Sort() {
+	sort.Sort(ps)
+}
+func (ps *paramSorter) Len() int {
+	return len(ps.Vals)
+}
+func (ps *paramSorter) Less(i, j int) bool {
+	return bytes.Compare([]byte(ps.Keys[i]), []byte(ps.Keys[j])) < 0
+}
+func (ps *paramSorter) Swap(i, j int) {
+	ps.Vals[i], ps.Vals[j] = ps.Vals[j], ps.Vals[i]
+	ps.Keys[i], ps.Keys[j] = ps.Keys[j], ps.Keys[i]
+}

+ 387 - 0
src/web/templates/vps/vpslist.html

@@ -0,0 +1,387 @@
+{{include "head.html"}}
+<!-- 申请阿里云实例(Modal) -->
+<div class="modal fade" id="applyvps" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
+	<div class="modal-dialog">
+		<div class="modal-content">
+			<div class="modal-header">
+				<div class="modal-header">
+					<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+					<div class="edit-form">
+						<div class="edit-info">
+							<span class="glyphicon glyphicon-cloud" aria-hidden="true"></span>
+							<span class="info">批量申请实例</span>
+						</div>
+						<form class="form-horizontal" role="form">
+							<div class="form-group">
+								<label for="vpsname" class="col-sm-2 control-label">实例名称 :</label>
+								<div class="col-sm-10">
+									<input type="text" class="form-control" id="vpsname" placeholder="请输入实例名称">
+								</div>
+							</div>
+							<div class="form-group">
+								<label for="vpsimage" class="col-sm-2 control-label">镜像标识 :</label>
+								<div class="col-sm-10">
+									<input type="text" style="color: red" class="form-control" id="vpsimage" placeholder="请输入镜像标识">
+								</div>
+							</div>
+							<div class="form-group">
+								<label for="vpsnum" class="col-sm-2 control-label">机器数量:</label>
+								<div class="col-sm-10">
+									<input type="text" class="form-control" id="vpsnum" placeholder="请输入机器数量">
+								</div>
+							</div>
+							<div class="form-group">
+								<label for="vpsduration" class="col-sm-2 control-label">时长(h):</label>
+								<div class="col-sm-10">
+									<input type="text" class="form-control" id="vpsduration" placeholder="请输入申请时长">
+								</div>
+							</div>
+<!--							<div class="form-group">-->
+<!--								<label for="vpsuse" class="col-sm-2 control-label">服务功能:</label>-->
+<!--								<div class="col-sm-10">-->
+<!--									<select class="form-control" id="vpsuse" style="color: red">-->
+<!--										<option value="data">数据(801)</option>-->
+<!--										<option value="special">专用(803)</option>-->
+<!--										<option value="file">附件(805)</option>-->
+<!--									</select>-->
+<!--								</div>-->
+<!--							</div>-->
+							<div class="form-group">
+								<label for="vpstype" class="col-sm-2 control-label">机器型号:</label>
+								<div class="col-sm-10">
+									<select class="form-control" id="vpstype">
+										<option value="4">4核</option>
+										<option value="8">8核</option>
+									</select>
+								</div>
+							</div>
+							<div class="form-group">
+								<label for="vpspublic" class="col-sm-2 control-label">是否公网:</label>
+								<div class="col-sm-10">
+									<select class="form-control" id="vpspublic">
+										<option value="false">否</option>
+										<option value="true">是</option>
+									</select>
+								</div>
+							</div>
+							<div class="form-group">
+								<div class="col-sm-offset-2 col-sm-10 operateStyle">
+									<input type="button"  onclick="apply()" class="btn btn-success saveBtn" value="申请">
+									<input type="button" onclick="cancelAdd()" class="btn btn-default" value="取消">
+								</div>
+							</div>
+						</form>
+					</div>
+				</div>
+			</div>
+		</div><!-- /.modal-content -->
+	</div><!-- /.modal -->
+</div>
+<!-- 模态框(Modal) -->
+<!-- 延时实例(Modal) -->
+<div class="modal fade" id="delayvps" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
+	<div class="modal-dialog">
+		<div class="modal-content">
+			<div class="modal-header">
+				<div class="modal-header">
+					<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+					<div class="edit-form">
+						<div class="edit-info">
+							<span class="glyphicon glyphicon-time" aria-hidden="true"></span>
+							<span class="info">延时实例</span>
+						</div>
+						<form class="form-horizontal" role="form">
+							<div class="form-group" id="delay">
+								<label for="delayid" class="col-sm-2 control-label">实例ID :</label>
+								<div class="col-sm-10">
+									<input type="text" disabled class="form-control" id="delayid" placeholder="请输入实例ID">
+								</div>
+							</div>
+							<div class="form-group">
+								<label for="delaytime" class="col-sm-2 control-label">延时(h) :</label>
+								<div class="col-sm-10">
+									<input type="text" style="color: red" class="form-control" id="delaytime" placeholder="请输入延时时间">
+								</div>
+							</div>
+							<div class="form-group">
+								<div class="col-sm-offset-2 col-sm-10 operateStyle">
+									<input type="button"  onclick="delay()" class="btn btn-success saveBtn" value="确认">
+									<input type="button" onclick="cancelAdd()" class="btn btn-default" value="取消">
+								</div>
+							</div>
+						</form>
+					</div>
+				</div>
+			</div>
+		</div><!-- /.modal-content -->
+	</div><!-- /.modal -->
+</div>
+<!-- 模态框(Modal) -->
+<div class="content-wrapper">
+	<section class="content-header">
+	   <h1>
+<!--		     错误爬虫列表-->
+		 <small>
+			 <button class="btn btn-success" onclick='applymodal()'>申请实例</button>
+			 <button class="btn btn-success" onclick='update()'>更新实例</button>
+			 <button class="btn btn-info" onclick='delaymodal("")'>一键延时</button>
+<!--			 <button class="btn btn-success" onclick='delaymodal()'>延时设置</button>-->
+			 <button class="btn btn-info" onclick='tasks()'>一键部署</button>
+			 <button class="btn btn-danger" onclick='allrelease()'>一键释放</button>
+			 <button class="btn btn-danger" onclick='deletevps()'>一键删除</button>
+		 </small>
+	   </h1>
+	<!--时间-->
+	   <ol class="breadcrumb">
+		 <li><a href="#"><i class="fa fa-dashboard"></i> 阿里实例</a></li>
+		 <li class="active">列表</li>
+	   </ol>
+	 </section>
+	<section id="taskerrmsg" class="content hide" style="min-height:0px;">
+		<h5>错误信息:</h5>
+	</section>
+	<section class="content">
+		<div class="box">
+		<div id="timeTopBox">
+		</div>
+			<div class="box-body">
+				<table id="errdata" class="table table-bordered table-striped">
+					<thead>
+						<tr>
+							<th>任务名称</th>
+							<th>实例名称</th>
+							<th>实例状态</th>
+							<th>实例ID</th>
+							<th>实例IP(内网/公网)</th>
+							<th>释放时间</th>
+							<th>运行状态</th>
+							<th>功能</th>
+							<th>操作</th>
+						</tr>
+					</thead>
+					<tbody>
+					</tbody>
+				</table>
+			</div>
+		</div>
+	</section>
+	<script>
+		$(function(){
+			common.setActive("index_vps");
+			vpstable = $('#errdata').DataTable({
+				"language": {
+					"url": "/js/dataTables.chinese.lang"
+				},
+				"ajax": {
+					"url": "/center/vps",
+					"type": "POST",
+				},
+				"columnDefs": [
+				],
+				"lengthChange":false,
+				"serverSide": true,
+				"searching": false,
+				"ordering": false,
+				"info": true,
+				"autoWidth": true,
+				"processing": true,
+				"columns": [
+					{"data": "TaskName"},
+					{"data": "InstanceName",render:function(val,a,row){
+						if(val == undefined){
+							val = ""
+						}
+						return val;
+					}},
+					{"data": "Status",render:function(val,a,row){
+						if(val == undefined){
+							val = ""
+						}
+						return val;
+					}},
+					{"data": "InstanceId"},
+					{"data": "_id",render:function(val,a,row){
+						if(val == undefined){
+							val = ""
+						}else{
+							val =  row.ip_nw+"/"+row.ip_ww;
+						}
+						return val;
+					}},
+					{"data": "AutoReleaseTime",render:function(val,a,row){
+						if(val == undefined){
+							val = ""
+						}
+						return val;
+					}},
+					{"data": "VpsState"},
+					{"data": "_id",render:function(val,a,row){
+						tmp = '<div>' +
+							'<a class="btn btn-sm btn-info" onclick="delaymodal(\''+row.InstanceId+'\')">延时</a>&nbsp;' +
+							'<a class="btn btn-sm btn-info" onclick="retrieveTask()">部署</a>' +
+							'</div>';
+						return tmp
+					}},
+					{"data": "_id",render:function(val,a,row){
+						tmp = '<div>' +
+							'<a class="btn btn-sm btn-danger" onclick="releasevps(\''+row.InstanceId+'\')">释放</a>&nbsp;' +
+							'<a class="btn btn-sm btn-danger" onclick="deletevps(\''+row.InstanceId+'\')">删除</a>'+
+							// '<a class="btn btn-sm btn-primary" onclick="checkMethod(\'' + row.s_projectid + '\',\'' + val + '\',\'' + row.s_sourceinfo + '\')">删除</a>&nbsp;&nbsp;' +
+						'</div>';
+						return tmp
+					}}
+				]
+			});
+		});
+		function delaymodal(instanceId) {
+			if(instanceId == ""){//一键延时
+				$("#delay").css("display","none");
+			}else{//单个实例延时
+				$("#delayid").val(instanceId)
+				$("#delay").css("display","");
+			}
+			$("#delayvps").modal("show");
+		}
+		function applymodal() {
+			$("#applyvps").modal("show");
+		}
+		function cancelAdd(){
+			$("#delayvps").modal("hide");
+			$("#applyvps").modal("hide");
+		}
+		//申请实例
+		function apply() {
+			let vpsname = $("#vpsname").val();
+			let vpsimage = $("#vpsimage").val();
+			let vpsnum = $("#vpsnum").val();
+			let vpsduration = $("#vpsduration").val();
+			if (vpsname == ""||vpsimage==""||vpsnum == ""||vpsduration == ""){
+				alert("请填写完整的信息!")
+				return
+			}
+			let vpstype = $("#vpstype").val();
+			let vpspublic = $("#vpspublic").val();
+			$.ajax({
+				url:"/center/vps/apply",
+				type:"post",
+				data:{"vpsname":vpsname,"vpsimage":vpsimage,"vpsnum":vpsnum,"vpsduration":vpsduration,"vpstype":vpstype,"vpspublic":vpspublic},
+				success:function (r){
+					if(r&&r.ok){
+						showTip("申请成功", 1000);
+						$("#applyvps").modal("hide");
+						setTimeout(function (){
+							window.location.reload();
+						}, 1000);
+					}else{
+						showTip("申请失败", 1000);
+					}
+				}
+			})
+		}
+		//更新实例
+		function update() {
+			showConfirm("确定更新实例?", function() {
+				$.ajax({
+					url:"/center/vps/update",
+					type:"post",
+					success:function (r){
+						if(r&&r.ok){
+							showTip("更新成功", 1000);
+							window.location.reload();
+						}else{
+							showTip("更新失败", 1000);
+						}
+					}
+				})
+			})
+		}
+		//延时
+		function delay() {
+			let delayid = $("#delayid").val();
+			let delaytime = $("#delaytime").val();
+			if (delaytime == ""){
+				alert("请填写完整的信息!")
+				return
+			}
+			$.ajax({
+				url:"/center/vps/delay",
+				type:"post",
+				data:{"delaytime":delaytime,"delayid":delayid},
+				success:function (r){
+					if(r&&r.ok){
+						showTip("设置成功", 1000);
+						$("#delayvps").modal("hide");
+						setTimeout(function (){
+							window.location.reload();
+						}, 1000);
+					}else{
+						$("#delayvps").modal("hide");
+						showMsg(r.msg, function () {
+						});
+					}
+				}
+			})
+		}
+		//释放
+		function releasevps(instanceId) {
+			showConfirm("确定释放实例?", function() {
+				$.ajax({
+					url:"/center/vps/release",
+					type:"post",
+					data:{"instanceId":instanceId},
+					success:function (r){
+						if(r&&r.ok){
+							showTip("释放成功",1000);
+							setTimeout(function (){
+								window.location.reload();
+							}, 1000);
+						}else{
+							showTip("释放失败", 2000);
+						}
+					}
+				})
+			})
+		}
+		//一键释放
+		function allrelease() {
+			showConfirm("确定释放所有实例?", function() {
+				$.ajax({
+					url:"/center/vps/allrelease",
+					type:"post",
+					success:function (r){
+						if(r&&r.ok){
+							showTip("释放成功", 1000);
+							setTimeout(function (){
+								window.location.reload();
+							}, 1000);
+						}else{
+							showMsg(r.msg, function () {
+							});
+						}
+					}
+				})
+			})
+		}
+		//删除
+		function deletevps(instanceId) {
+			showConfirm("确定删除实例信息?", function() {
+				$.ajax({
+					url:"/center/vps/delete",
+					type:"post",
+					data:{"instanceId":instanceId},
+					success:function (r){
+						if(r&&r.ok){
+							showTip("删除成功", 1000);
+							setTimeout(function (){
+								window.location.reload();
+							}, 1000);
+						}else{
+							showTip("删除失败", 1000);
+						}
+					}
+				})
+			})
+		}
+	</script>
+</div>
+{{include "bottom.html"}}