实现远程升级节点

This commit is contained in:
刘祥超
2020-10-28 12:35:49 +08:00
parent 693409495e
commit 3e520c4863
16 changed files with 299 additions and 29 deletions

View File

@@ -45,6 +45,7 @@ func (this *CreateBatchAction) RunPost(params struct {
IpList string IpList string
Must *actions.Must Must *actions.Must
CSRF *actionutils.CSRF
}) { }) {
if params.ClusterId <= 0 { if params.ClusterId <= 0 {
this.Fail("请选择正确的集群") this.Fail("请选择正确的集群")

View File

@@ -17,6 +17,8 @@ func init() {
GetPost("/installNodes", new(InstallNodesAction)). GetPost("/installNodes", new(InstallNodesAction)).
GetPost("/installRemote", new(InstallRemoteAction)). GetPost("/installRemote", new(InstallRemoteAction)).
Post("/installStatus", new(InstallStatusAction)). Post("/installStatus", new(InstallStatusAction)).
GetPost("/upgradeRemote", new(UpgradeRemoteAction)).
Post("/upgradeStatus", new(UpgradeStatusAction)).
GetPost("/delete", new(DeleteAction)). GetPost("/delete", new(DeleteAction)).
GetPost("/createNode", new(CreateNodeAction)). GetPost("/createNode", new(CreateNodeAction)).
GetPost("/createBatch", new(CreateBatchAction)). GetPost("/createBatch", new(CreateBatchAction)).

View File

@@ -1,7 +1,6 @@
package cluster package cluster
import ( import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
@@ -20,18 +19,7 @@ func (this *InstallNodesAction) Init() {
func (this *InstallNodesAction) RunGet(params struct { func (this *InstallNodesAction) RunGet(params struct {
ClusterId int64 ClusterId int64
}) { }) {
this.Data["leftMenuItems"] = []maps.Map{ this.Data["leftMenuItems"] = LeftMenuItemsForInstall(params.ClusterId, "register")
{
"name": "自动注册",
"url": "/clusters/cluster/installNodes?clusterId=" + numberutils.FormatInt64(params.ClusterId),
"isActive": true,
},
{
"name": "远程安装",
"url": "/clusters/cluster/installRemote?clusterId=" + numberutils.FormatInt64(params.ClusterId),
"isActive": false,
},
}
clusterResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeCluster(this.AdminContext(), &pb.FindEnabledNodeClusterRequest{ClusterId: params.ClusterId}) clusterResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeCluster(this.AdminContext(), &pb.FindEnabledNodeClusterRequest{ClusterId: params.ClusterId})
if err != nil { if err != nil {

View File

@@ -2,7 +2,6 @@ package cluster
import ( import (
"encoding/json" "encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
@@ -21,18 +20,7 @@ func (this *InstallRemoteAction) Init() {
func (this *InstallRemoteAction) RunGet(params struct { func (this *InstallRemoteAction) RunGet(params struct {
ClusterId int64 ClusterId int64
}) { }) {
this.Data["leftMenuItems"] = []maps.Map{ this.Data["leftMenuItems"] = LeftMenuItemsForInstall(params.ClusterId, "install")
{
"name": "自动注册",
"url": "/clusters/cluster/installNodes?clusterId=" + numberutils.FormatInt64(params.ClusterId),
"isActive": false,
},
{
"name": "远程安装",
"url": "/clusters/cluster/installRemote?clusterId=" + numberutils.FormatInt64(params.ClusterId),
"isActive": true,
},
}
nodesResp, err := this.RPC().NodeRPC().FindAllNotInstalledNodesWithClusterId(this.AdminContext(), &pb.FindAllNotInstalledNodesWithClusterIdRequest{ClusterId: params.ClusterId}) nodesResp, err := this.RPC().NodeRPC().FindAllNotInstalledNodesWithClusterId(this.AdminContext(), &pb.FindAllNotInstalledNodesWithClusterIdRequest{ClusterId: params.ClusterId})
if err != nil { if err != nil {

View File

@@ -0,0 +1,70 @@
package cluster
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type UpgradeRemoteAction struct {
actionutils.ParentAction
}
func (this *UpgradeRemoteAction) Init() {
this.Nav("", "", "")
}
func (this *UpgradeRemoteAction) RunGet(params struct {
ClusterId int64
}) {
this.Data["leftMenuItems"] = LeftMenuItemsForInstall(params.ClusterId, "upgrade")
nodes := []maps.Map{}
resp, err := this.RPC().NodeRPC().FindAllUpgradeNodesWithClusterId(this.AdminContext(), &pb.FindAllUpgradeNodesWithClusterIdRequest{ClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
for _, node := range resp.Nodes {
loginParams := maps.Map{}
if node.Node.Login != nil && len(node.Node.Login.Params) > 0 {
err := json.Unmarshal(node.Node.Login.Params, &loginParams)
if err != nil {
this.ErrorPage(err)
return
}
}
nodes = append(nodes, maps.Map{
"id": node.Node.Id,
"name": node.Node.Name,
"os": node.Os,
"arch": node.Arch,
"oldVersion": node.OldVersion,
"newVersion": node.NewVersion,
"login": node.Node.Login,
"loginParams": loginParams,
"addresses": node.Node.IpAddresses,
"installStatus": node.Node.InstallStatus,
})
}
this.Data["nodes"] = nodes
this.Show()
}
func (this *UpgradeRemoteAction) RunPost(params struct {
NodeId int64
Must *actions.Must
}) {
_, err := this.RPC().NodeRPC().UpgradeNode(this.AdminContext(), &pb.UpgradeNodeRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,35 @@
package cluster
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type UpgradeStatusAction struct {
actionutils.ParentAction
}
func (this *UpgradeStatusAction) RunPost(params struct {
NodeId int64
}) {
resp, err := this.RPC().NodeRPC().FindNodeInstallStatus(this.AdminContext(), &pb.FindNodeInstallStatusRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
if resp.InstallStatus == nil {
this.Data["status"] = nil
this.Success()
}
this.Data["status"] = maps.Map{
"isRunning": resp.InstallStatus.IsRunning,
"isFinished": resp.InstallStatus.IsFinished,
"isOk": resp.InstallStatus.IsOk,
"error": resp.InstallStatus.Error,
"errorCode": resp.InstallStatus.ErrorCode,
}
this.Success()
}

View File

@@ -0,0 +1,27 @@
package cluster
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/iwind/TeaGo/maps"
)
// 安装升级相关的左侧菜单
func LeftMenuItemsForInstall(clusterId int64, selectedItem string) []maps.Map {
return []maps.Map{
{
"name": "自动注册",
"url": "/clusters/cluster/installNodes?clusterId=" + numberutils.FormatInt64(clusterId),
"isActive": selectedItem == "register",
},
{
"name": "远程安装",
"url": "/clusters/cluster/installRemote?clusterId=" + numberutils.FormatInt64(clusterId),
"isActive": selectedItem == "install",
},
{
"name": "远程升级",
"url": "/clusters/cluster/upgradeRemote?clusterId=" + numberutils.FormatInt64(clusterId),
"isActive": selectedItem == "upgrade",
},
}
}

View File

@@ -1,5 +1,5 @@
<second-menu> <second-menu>
<menu-item :href="'/clusters/cluster?clusterId=' + clusterId" code="index">节点列表</menu-item> <menu-item :href="'/clusters/cluster?clusterId=' + clusterId" code="index">节点列表</menu-item>
<menu-item :href="'/clusters/cluster/createNode?clusterId=' + clusterId" code="create">创建节点</menu-item> <menu-item :href="'/clusters/cluster/createNode?clusterId=' + clusterId" code="create">创建节点</menu-item>
<menu-item :href="'/clusters/cluster/installNodes?clusterId=' + clusterId" code="install">安装节点</menu-item> <menu-item :href="'/clusters/cluster/installNodes?clusterId=' + clusterId" code="install">安装升级</menu-item>
</second-menu> </second-menu>

View File

@@ -4,6 +4,7 @@
<div class="right-box"> <div class="right-box">
<form class="ui form" data-tea-action="$" data-tea-success="success"> <form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="clusterId" :value="clusterId"/> <input type="hidden" name="clusterId" :value="clusterId"/>
<table class="ui table definition selectable"> <table class="ui table definition selectable">
<tr> <tr>

View File

@@ -18,7 +18,9 @@
</tr> </tr>
</thead> </thead>
<tr v-for="node in nodes"> <tr v-for="node in nodes">
<td>{{node.name}}</td> <td>
<a :href="'/clusters/cluster/node?clusterId=' + clusterId + '&nodeId=' + node.id">{{node.name}}</a>
</td>
<td> <td>
<span v-for="addr in node.addresses" v-if="addr.canAccess" class="ui label tiny">{{addr.ip}}</span> <span v-for="addr in node.addresses" v-if="addr.canAccess" class="ui label tiny">{{addr.ip}}</span>
</td> </td>
@@ -36,6 +38,7 @@
<span v-if="!node.installStatus.isOk" class="red">安装过程中发生错误:{{node.installStatus.error}}</span> <span v-if="!node.installStatus.isOk" class="red">安装过程中发生错误:{{node.installStatus.error}}</span>
</div> </div>
</div> </div>
<span v-else class="disabled">等待安装</span>
</td> </td>
<td> <td>
<a href="" @click.prevent="installNode(node)" v-if="!isInstalling">安装</a> <a href="" @click.prevent="installNode(node)" v-if="!isInstalling">安装</a>

View File

@@ -85,7 +85,7 @@
<td class="title">是否在运行</td> <td class="title">是否在运行</td>
<td> <td>
<div v-if="node.status.isActive"> <div v-if="node.status.isActive">
<span class="green">运行中</span> &nbsp; <span class="green">运行中</span> &nbsp;
<a href="" @click.prevent="stopNode()" v-if="!isStopping"><span>[通过SSH停止]</span></a> <a href="" @click.prevent="stopNode()" v-if="!isStopping"><span>[通过SSH停止]</span></a>
<span v-if="isStopping">[停止中...]</span> <span v-if="isStopping">[停止中...]</span>
</div> </div>

View File

@@ -0,0 +1,7 @@
.left-box {
top: 10em;
}
.right-box {
top: 10em;
}
/*# sourceMappingURL=upgradeRemote.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["upgradeRemote.less"],"names":[],"mappings":"AAAA;EACC,SAAA;;AAGD;EACC,SAAA","file":"upgradeRemote.css"}

View File

@@ -0,0 +1,53 @@
{$layout}
{$template "menu"}
{$template "/left_menu"}
<div class="right-box">
<p class="comment" v-if="nodes.length == 0">暂时没有需要升级的节点。</p>
<div v-if="nodes.length > 0">
<h3>所有需要升级的节点</h3>
<table class="ui table selectable">
<thead>
<tr>
<th>节点名</th>
<th>访问IP</th>
<th>SSH地址</th>
<th>版本变化</th>
<th class="four wide">节点状态</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="node in nodes">
<td>
<a :href="'/clusters/cluster/node?clusterId=' + clusterId + '&nodeId=' + node.id">{{node.name}}</a>
</td>
<td>
<span v-for="addr in node.addresses" v-if="addr.canAccess" class="ui label tiny">{{addr.ip}}</span>
</td>
<td>
<span v-if="node.login != null && node.login.type == 'ssh' && node.loginParams != null && node.loginParams.host != null && node.loginParams.host.length > 0">
{{node.loginParams.host}}:{{node.loginParams.port}}
</span>
<span v-else class="disabled">没有设置</span>
</td>
<td>v{{node.oldVersion}} -&gt; v{{node.newVersion}}</td>
<td>
<div v-if="node.installStatus != null && (node.installStatus.isRunning || node.installStatus.isFinished)">
<div v-if="node.installStatus.isRunning" class="blue">升级中...</div>
<div v-if="node.installStatus.isFinished">
<span v-if="node.installStatus.isOk" class="green">已升级成功</span>
<span v-if="!node.installStatus.isOk" class="red">升级过程中发生错误:{{node.installStatus.error}}</span>
</div>
</div>
<span v-else class="disabled">等待升级</span>
</td>
<td>
<a href="" @click.prevent="installNode(node)" v-if="!isInstalling">升级</a>
<span v-if="isInstalling && node.isInstalling">升级中...</span>
<span v-if="isInstalling && !node.isInstalling" class="disabled">升级</span>
</td>
</tr>
</table>
</div>
</div>

View File

@@ -0,0 +1,87 @@
Tea.context(function () {
this.isInstalling = false
let installingNode = null
this.$delay(function () {
this.reload()
})
this.installNode = function (node) {
let that = this
teaweb.confirm("确定要开始升级此节点吗?", function () {
installingNode = node
that.isInstalling = true
node.isInstalling = true
that.$post("$")
.params({
nodeId: node.id
})
})
}
this.reload = function () {
let that = this
if (installingNode != null) {
this.$post("/clusters/cluster/upgradeStatus")
.params({
nodeId: installingNode.id
})
.success(function (resp) {
if (resp.data.status != null) {
installingNode.installStatus = resp.data.status
if (installingNode.installStatus.isFinished) {
if (installingNode.installStatus.isOk) {
installingNode = null
teaweb.success("升级成功", function () {
window.location.reload()
})
} else {
let nodeId = installingNode.id
let errMsg = installingNode.installStatus.error
that.isInstalling = false
installingNode.isInstalling = false
installingNode = null
switch (resp.data.status.errorCode) {
case "EMPTY_LOGIN":
case "EMPTY_SSH_HOST":
case "EMPTY_SSH_PORT":
case "EMPTY_GRANT":
teaweb.warn("需要填写SSH登录信息", function () {
teaweb.popup("/clusters/cluster/updateNodeSSH?nodeId=" + nodeId, {
callback: function () {
teaweb.reload()
}
})
})
return
case "CREATE_ROOT_DIRECTORY_FAILED":
teaweb.warn("创建根目录失败,请检查目录权限或者手工创建:" + errMsg)
return
case "INSTALL_HELPER_FAILED":
teaweb.warn("安装助手失败:" + errMsg)
return
case "TEST_FAILED":
teaweb.warn("环境测试失败:" + errMsg)
return
case "RPC_TEST_FAILED":
teaweb.confirm("html:要升级的节点到API服务之间的RPC通讯测试失败具体错误" + errMsg + "<br/>现在修改API信息", function () {
window.location = "/api"
})
return
default:
teaweb.warn("升级失败:" + errMsg)
}
}
}
}
})
.done(function () {
setTimeout(this.reload, 3000)
})
} else {
setTimeout(this.reload, 3000)
}
}
})

View File

@@ -0,0 +1,7 @@
.left-box {
top: 10em;
}
.right-box {
top: 10em;
}