diff --git a/build/build.sh b/build/build.sh index 604e1997..9e765ac2 100755 --- a/build/build.sh +++ b/build/build.sh @@ -60,7 +60,7 @@ function build() { architects=("amd64" "386") for arch in "${architects[@]}"; do # TODO support arm, mips ... - env GOOS=linux GOARCH=${arch} go build --ldflags="-s -w" -o $ROOT/installers/installer-helper-linux-${arch} $ROOT/../cmd/installer-helper/main.go + env GOOS=linux GOARCH=${arch} go build --ldflags="-s -w" -o $ROOT/installers/edge-installer-helper-linux-${arch} $ROOT/../cmd/installer-helper/main.go done # building api node diff --git a/build/installers/.gitignore b/build/installers/.gitignore index 6997afc8..9949c9a7 100644 --- a/build/installers/.gitignore +++ b/build/installers/.gitignore @@ -1 +1 @@ -installer-* \ No newline at end of file +edge-* \ No newline at end of file diff --git a/internal/db/models/api_node_dao.go b/internal/db/models/api_node_dao.go index 3e2cfe9d..a03a1577 100644 --- a/internal/db/models/api_node_dao.go +++ b/internal/db/models/api_node_dao.go @@ -171,6 +171,19 @@ func (this *APINodeDAO) FindAllEnabledAPINodes() (result []*APINode, err error) return } +// 列出所有可用而且启用的API节点 +func (this *APINodeDAO) FindAllEnabledAndOnAPINodes() (result []*APINode, err error) { + _, err = this.Query(). + Attr("clusterId", 0). // 非集群专用 + Attr("isOn", true). + State(APINodeStateEnabled). + Desc("order"). + AscPk(). + Slice(&result). + FindAll() + return +} + // 计算API节点数量 func (this *APINodeDAO) CountAllEnabledAPINodes() (int64, error) { return this.Query(). diff --git a/internal/db/models/node_cluster_dao.go b/internal/db/models/node_cluster_dao.go index 1963f6ce..76c92152 100644 --- a/internal/db/models/node_cluster_dao.go +++ b/internal/db/models/node_cluster_dao.go @@ -268,6 +268,14 @@ func (this *NodeClusterDAO) FindAllEnabledClustersWithGrantId(grantId int64) (re return } +// 查找集群的认证ID +func (this *NodeClusterDAO) FindClusterGrantId(clusterId int64) (int64, error) { + return this.Query(). + Pk(clusterId). + Result("grantId"). + FindInt64Col(0) +} + // 生成唯一ID func (this *NodeClusterDAO) genUniqueId() (string, error) { for { diff --git a/internal/db/models/node_dao.go b/internal/db/models/node_dao.go index dbf2861c..85221a3b 100644 --- a/internal/db/models/node_dao.go +++ b/internal/db/models/node_dao.go @@ -376,21 +376,33 @@ func (this *NodeDAO) UpdateNodeIsInstalled(nodeId int64, isInstalled bool) error // 查询节点的安装状态 func (this *NodeDAO) FindNodeInstallStatus(nodeId int64) (*NodeInstallStatus, error) { - installStatus, err := this.Query(). + node, err := this.Query(). Pk(nodeId). - Result("installStatus"). - FindStringCol("") + Result("installStatus", "isInstalled"). + Find() if err != nil { return nil, err } + if node == nil { + return nil, errors.New("not found") + } + installStatus := node.(*Node).InstallStatus + isInstalled := node.(*Node).IsInstalled == 1 if len(installStatus) == 0 { return NewNodeInstallStatus(), nil } status := &NodeInstallStatus{} err = json.Unmarshal([]byte(installStatus), status) - return status, err + if err != nil { + return nil, err + } + if isInstalled { + status.IsFinished = true + status.IsOk = true + } + return status, nil } // 修改节点的安装状态 @@ -524,6 +536,18 @@ func (this *NodeDAO) FindAllEnabledNodesWithGrantId(grantId int64) (result []*No return } +// 查找所有未安装的节点 +func (this *NodeDAO) FindAllNotInstalledNodesWithClusterId(clusterId int64) (result []*Node, err error) { + _, err = this.Query(). + State(NodeStateEnabled). + Attr("clusterId", clusterId). + Attr("isInstalled", false). + DescPk(). + Slice(&result). + FindAll() + return +} + // 生成唯一ID func (this *NodeDAO) genUniqueId() (string, error) { for { diff --git a/internal/db/models/node_install_status.go b/internal/db/models/node_install_status.go index 145941b8..c54456b0 100644 --- a/internal/db/models/node_install_status.go +++ b/internal/db/models/node_install_status.go @@ -6,6 +6,7 @@ type NodeInstallStatus struct { IsFinished bool `json:"isFinished"` // 是否已结束 IsOk bool `json:"isOk"` // 是否正确安装 Error string `json:"error"` // 错误信息 + ErrorCode string `json:"errorCode"` // 错误代号 UpdatedAt int64 `json:"updatedAt"` // 更新时间,安装过程中需要每隔N秒钟更新这个状态,以便于让系统知道安装仍在进行中 Steps []*NodeInstallStatusStep `json:"steps"` // 步骤 } diff --git a/internal/errors/detailed_error.go b/internal/errors/detailed_error.go new file mode 100644 index 00000000..d2e12edd --- /dev/null +++ b/internal/errors/detailed_error.go @@ -0,0 +1,21 @@ +package errors + +type DetailedError struct { + msg string + code string +} + +func (this *DetailedError) Error() string { + return this.msg +} + +func (this *DetailedError) Code() string { + return this.code +} + +func NewDetailedError(code string, error string) *DetailedError { + return &DetailedError{ + msg: error, + code: code, + } +} diff --git a/internal/installers/installer_base.go b/internal/installers/installer_base.go index eb334d9e..6c184e88 100644 --- a/internal/installers/installer_base.go +++ b/internal/installers/installer_base.go @@ -151,12 +151,12 @@ func (this *BaseInstaller) InstallHelper(targetDir string) (env *Env, err error) archName = "386" } - exeName := "installer-helper-" + osName + "-" + archName + exeName := "edge-installer-helper-" + osName + "-" + archName exePath := Tea.Root + "/installers/" + exeName err = this.client.Copy(exePath, targetDir+"/"+exeName, 0777) if err != nil { - return env, err + return env, errors.New("copy '" + exeName + "' to '" + targetDir + "' failed: " + err.Error()) } env = &Env{ diff --git a/internal/installers/installer_node.go b/internal/installers/installer_node.go index d8337c72..21bbc656 100644 --- a/internal/installers/installer_node.go +++ b/internal/installers/installer_node.go @@ -23,6 +23,15 @@ func (this *NodeInstaller) Install(dir string, params interface{}) error { return errors.New("params validation: " + err.Error()) } + // 检查目标目录是否存在 + _, err = this.client.Stat(dir) + if err != nil { + err = this.client.MkdirAll(dir) + if err != nil { + return errors.New("create directory '" + dir + "' failed: " + err.Error()) + } + } + // 安装助手 env, err := this.InstallHelper(dir) if err != nil { diff --git a/internal/installers/queue.go b/internal/installers/queue.go index d3e3af10..1d0e2afc 100644 --- a/internal/installers/queue.go +++ b/internal/installers/queue.go @@ -5,8 +5,8 @@ import ( "fmt" "github.com/TeaOSLab/EdgeAPI/internal/db/models" "github.com/TeaOSLab/EdgeAPI/internal/utils" + "github.com/TeaOSLab/EdgeAPI/internal/utils/numberutils" "github.com/iwind/TeaGo/logs" - "strconv" "time" ) @@ -51,7 +51,7 @@ func (this *Queue) InstallNodeProcess(nodeId int64) error { }() // 开始安装 - err = this.InstallNode(nodeId) + err = this.InstallNode(nodeId, installStatus) // 安装结束 installStatus.IsRunning = false @@ -78,13 +78,13 @@ func (this *Queue) InstallNodeProcess(nodeId int64) error { } // 安装边缘节点 -func (this *Queue) InstallNode(nodeId int64) error { +func (this *Queue) InstallNode(nodeId int64, installStatus *models.NodeInstallStatus) error { node, err := models.SharedNodeDAO.FindEnabledNode(nodeId) if err != nil { return err } if node == nil { - return errors.New("can not find node, ID:'" + strconv.FormatInt(nodeId, 10) + "'") + return errors.New("can not find node, ID:'" + numberutils.FormatInt64(nodeId) + "'") } // 登录信息 @@ -93,6 +93,7 @@ func (this *Queue) InstallNode(nodeId int64) error { return err } if login == nil { + installStatus.ErrorCode = "EMPTY_LOGIN" return errors.New("can not find node login information") } loginParams, err := login.DecodeSSHParams() @@ -100,12 +101,35 @@ func (this *Queue) InstallNode(nodeId int64) error { return err } + if len(loginParams.Host) == 0 { + installStatus.ErrorCode = "EMPTY_SSH_HOST" + return errors.New("ssh host should not be empty") + } + + if loginParams.Port <= 0 { + installStatus.ErrorCode = "EMPTY_SSH_PORT" + return errors.New("ssh port is invalid") + } + + if loginParams.GrantId == 0 { + // 从集群中读取 + grantId, err := models.SharedNodeClusterDAO.FindClusterGrantId(int64(node.ClusterId)) + if err != nil { + return err + } + if grantId == 0 { + installStatus.ErrorCode = "EMPTY_GRANT" + return errors.New("can not find node grant") + } + loginParams.GrantId = grantId + } grant, err := models.SharedNodeGrantDAO.FindEnabledNodeGrant(loginParams.GrantId) if err != nil { return err } if grant == nil { - return errors.New("can not find user grant with id '" + strconv.FormatInt(loginParams.GrantId, 10) + "'") + installStatus.ErrorCode = "EMPTY_GRANT" + return errors.New("can not find user grant with id '" + numberutils.FormatInt64(loginParams.GrantId) + "'") } // 安装目录 @@ -121,12 +145,13 @@ func (this *Queue) InstallNode(nodeId int64) error { } installDir = cluster.InstallDir if len(installDir) == 0 { - return errors.New("unable to find installation dir") + // 默认是 $登录用户/edge-node + installDir = "/" + grant.Username + "/edge-node" } } // API终端 - apiNodes, err := models.SharedAPINodeDAO.FindAllEnabledAPINodes() + apiNodes, err := models.SharedAPINodeDAO.FindAllEnabledAndOnAPINodes() if err != nil { return err } diff --git a/internal/rpc/services/service_node.go b/internal/rpc/services/service_node.go index 88d7b3fa..0e0a499d 100644 --- a/internal/rpc/services/service_node.go +++ b/internal/rpc/services/service_node.go @@ -139,6 +139,7 @@ func (this *NodeService) ListEnabledNodesMatch(ctx context.Context, req *pb.List IsFinished: installStatus.IsFinished, IsOk: installStatus.IsOk, Error: installStatus.Error, + ErrorCode: installStatus.ErrorCode, UpdatedAt: installStatus.UpdatedAt, } } @@ -295,6 +296,7 @@ func (this *NodeService) FindEnabledNode(ctx context.Context, req *pb.FindEnable IsFinished: installStatus.IsFinished, IsOk: installStatus.IsOk, Error: installStatus.Error, + ErrorCode: installStatus.ErrorCode, UpdatedAt: installStatus.UpdatedAt, } } @@ -481,3 +483,131 @@ func (this *NodeService) FindAllEnabledNodesWithGrantId(ctx context.Context, req return &pb.FindAllEnabledNodesWithGrantIdResponse{Nodes: result}, nil } + +// 列出所有未安装的节点 +func (this *NodeService) FindAllNotInstalledNodesWithClusterId(ctx context.Context, req *pb.FindAllNotInstalledNodesWithClusterIdRequest) (*pb.FindAllNotInstalledNodesWithClusterIdResponse, error) { + // 校验请求 + _, _, err := rpcutils.ValidateRequest(ctx, rpcutils.UserTypeAdmin) + if err != nil { + return nil, err + } + + nodes, err := models.SharedNodeDAO.FindAllNotInstalledNodesWithClusterId(req.ClusterId) + if err != nil { + return nil, err + } + result := []*pb.Node{} + for _, node := range nodes { + // 认证信息 + login, err := models.SharedNodeLoginDAO.FindEnabledNodeLoginWithNodeId(int64(node.Id)) + if err != nil { + return nil, err + } + var pbLogin *pb.NodeLogin = nil + if login != nil { + pbLogin = &pb.NodeLogin{ + Id: int64(login.Id), + Name: login.Name, + Type: login.Type, + Params: []byte(login.Params), + } + } + + // IP信息 + addresses, err := models.SharedNodeIPAddressDAO.FindAllEnabledAddressesWithNode(int64(node.Id)) + if err != nil { + return nil, err + } + + pbAddresses := []*pb.NodeIPAddress{} + for _, address := range addresses { + pbAddresses = append(pbAddresses, &pb.NodeIPAddress{ + Id: int64(address.Id), + NodeId: int64(address.NodeId), + Name: address.Name, + Ip: address.Ip, + Description: address.Description, + State: int64(address.State), + Order: int64(address.Order), + CanAccess: address.CanAccess == 1, + }) + } + + // 安装信息 + installStatus, err := node.DecodeInstallStatus() + if err != nil { + return nil, err + } + pbInstallStatus := &pb.NodeInstallStatus{} + if installStatus != nil { + pbInstallStatus = &pb.NodeInstallStatus{ + IsRunning: installStatus.IsRunning, + IsFinished: installStatus.IsFinished, + IsOk: installStatus.IsOk, + Error: installStatus.Error, + ErrorCode: installStatus.ErrorCode, + UpdatedAt: installStatus.UpdatedAt, + } + } + + result = append(result, &pb.Node{ + Id: int64(node.Id), + Name: node.Name, + Version: int64(node.Version), + IsInstalled: node.IsInstalled == 1, + Status: node.Status, + IsOn: node.IsOn == 1, + Login: pbLogin, + IpAddresses: pbAddresses, + InstallStatus: pbInstallStatus, + }) + } + return &pb.FindAllNotInstalledNodesWithClusterIdResponse{Nodes: result}, nil +} + +// 读取节点安装状态 +func (this *NodeService) FindNodeInstallStatus(ctx context.Context, req *pb.FindNodeInstallStatusRequest) (*pb.FindNodeInstallStatusResponse, error) { + // 校验请求 + _, _, err := rpcutils.ValidateRequest(ctx, rpcutils.UserTypeAdmin) + if err != nil { + return nil, err + } + + installStatus, err := models.SharedNodeDAO.FindNodeInstallStatus(req.NodeId) + if err != nil { + return nil, err + } + if installStatus == nil { + return &pb.FindNodeInstallStatusResponse{InstallStatus: nil}, nil + } + + pbInstallStatus := &pb.NodeInstallStatus{ + IsRunning: installStatus.IsRunning, + IsFinished: installStatus.IsFinished, + IsOk: installStatus.IsOk, + Error: installStatus.Error, + ErrorCode: installStatus.ErrorCode, + UpdatedAt: installStatus.UpdatedAt, + } + return &pb.FindNodeInstallStatusResponse{InstallStatus: pbInstallStatus}, nil +} + +// 修改节点登录信息 +func (this *NodeService) UpdateNodeLogin(ctx context.Context, req *pb.UpdateNodeLoginRequest) (*pb.RPCUpdateSuccess, error) { + // 校验请求 + _, _, err := rpcutils.ValidateRequest(ctx, rpcutils.UserTypeAdmin) + if err != nil { + return nil, err + } + + if req.Login.Id <= 0 { + _, err := models.SharedNodeLoginDAO.CreateNodeLogin(req.NodeId, req.Login.Name, req.Login.Type, req.Login.Params) + if err != nil { + return nil, err + } + } + + err = models.SharedNodeLoginDAO.UpdateNodeLogin(req.Login.Id, req.Login.Name, req.Login.Type, req.Login.Params) + + return rpcutils.RPCUpdateSuccess() +}