mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-05-08 06:31:00 +08:00
* feat: add pywinrm to ansibleserver-base * feat(devtool,ansibleserver): support install monitor agent to windows * feat: modify Dockerfil.file-repo Co-authored-by: rain <mac@Rains-MBP.lan>
385 lines
11 KiB
Go
385 lines
11 KiB
Go
// Copyright 2019 Yunion
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package utils
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
"time"
|
|
|
|
"yunion.io/x/jsonutils"
|
|
"yunion.io/x/log"
|
|
"yunion.io/x/pkg/errors"
|
|
|
|
cloudproxy_api "yunion.io/x/onecloud/pkg/apis/cloudproxy"
|
|
comapi "yunion.io/x/onecloud/pkg/apis/compute"
|
|
"yunion.io/x/onecloud/pkg/mcclient"
|
|
"yunion.io/x/onecloud/pkg/mcclient/modules/cloudproxy"
|
|
modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
|
|
)
|
|
|
|
var ErrServerNotSshable = errors.Error("server is not sshable")
|
|
|
|
type SSHable struct {
|
|
Ok bool
|
|
Reason string
|
|
|
|
User string
|
|
Host string
|
|
Port int
|
|
Password string
|
|
|
|
ServerName string
|
|
ServerHypervisor string
|
|
OsType string
|
|
|
|
ProxyEndpointId string
|
|
ProxyAgentId string
|
|
ProxyForwardId string
|
|
}
|
|
|
|
func checkSshableForOtherCloud(session *mcclient.ClientSession, serverId string) (SSHable, error) {
|
|
data, err := modules.Servers.GetSpecific(session, serverId, "sshable", nil)
|
|
if err != nil {
|
|
return SSHable{}, errors.Wrapf(err, "unable to get sshable info of server %s", serverId)
|
|
}
|
|
log.Infof("data to sshable: %v", data)
|
|
var sshableOutput comapi.GuestSshableOutput
|
|
err = data.Unmarshal(&sshableOutput)
|
|
if err != nil {
|
|
return SSHable{}, errors.Wrapf(err, "unable to marshal output of server sshable: %s", data)
|
|
}
|
|
sshable := SSHable{
|
|
User: sshableOutput.User,
|
|
}
|
|
reasons := make([]string, 0, len(sshableOutput.MethodTried))
|
|
for _, methodTried := range sshableOutput.MethodTried {
|
|
if !methodTried.Sshable {
|
|
reasons = append(reasons, methodTried.Reason)
|
|
continue
|
|
}
|
|
sshable.Ok = true
|
|
switch methodTried.Method {
|
|
case comapi.MethodDirect, comapi.MethodEIP, comapi.MethodDNAT:
|
|
sshable.Host = methodTried.Host
|
|
sshable.Port = methodTried.Port
|
|
case comapi.MethodProxyForward:
|
|
sshable.ProxyAgentId = methodTried.ForwardDetails.ProxyAgentId
|
|
sshable.ProxyEndpointId = methodTried.ForwardDetails.ProxyEndpointId
|
|
}
|
|
}
|
|
if !sshable.Ok {
|
|
sshable.Reason = strings.Join(reasons, "; ")
|
|
}
|
|
return sshable, nil
|
|
}
|
|
|
|
type SServerInfo struct {
|
|
Id string
|
|
Ip string
|
|
VpcId string
|
|
Hypervisor string
|
|
}
|
|
|
|
func CheckSshableForYunionCloud(session *mcclient.ClientSession, serverInfo SServerInfo) (sshable SSHable, cleanFunc func() error, err error) {
|
|
sshable, clean, err := checkSshableForYunionCloud(session, serverInfo, false)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if clean {
|
|
cleanFunc = func() error {
|
|
proxyAddr := sshable.Host
|
|
proxyPort := sshable.Port
|
|
params := jsonutils.NewDict()
|
|
params.Set("proto", jsonutils.NewString("tcp"))
|
|
params.Set("proxy_addr", jsonutils.NewString(proxyAddr))
|
|
params.Set("proxy_port", jsonutils.NewInt(int64(proxyPort)))
|
|
_, err := modules.Servers.PerformAction(session, serverInfo.Id, "close-forward", params)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "unable to close forward(addr %q, port %d, proto %q) for server %s", proxyAddr, proxyPort, "tcp", serverInfo.Id)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func getLoginInfo(session *mcclient.ClientSession, serverId string) (username string, password string, err error) {
|
|
ret, err := modules.Servers.GetLoginInfo(session, serverId, jsonutils.NewDict())
|
|
if err != nil {
|
|
return
|
|
}
|
|
username, _ = ret.GetString("username")
|
|
password, _ = ret.GetString("password")
|
|
return
|
|
}
|
|
|
|
func checkSshableForYunionCloud(session *mcclient.ClientSession, serverInfo SServerInfo, isWindows bool) (sshable SSHable, clean bool, err error) {
|
|
port, err := getServerSshport(session, serverInfo.Id, isWindows)
|
|
if err != nil {
|
|
err = errors.Wrapf(err, "unable to get ssh port of server %s", serverInfo.Id)
|
|
return
|
|
}
|
|
ip := serverInfo.Ip
|
|
username, password := "cloudroot", ""
|
|
if isWindows {
|
|
username, password, err = getLoginInfo(session, serverInfo.Id)
|
|
if err != nil {
|
|
return
|
|
}
|
|
sshable.OsType = "Windows"
|
|
}
|
|
sshable.User = username
|
|
sshable.Password = password
|
|
sshable.Port = port
|
|
sshable.Host = ip
|
|
if serverInfo.Hypervisor == comapi.HYPERVISOR_BAREMETAL || serverInfo.VpcId == "" || serverInfo.VpcId == comapi.DEFAULT_VPC_ID {
|
|
sshable.Ok = true
|
|
return
|
|
}
|
|
lfParams := jsonutils.NewDict()
|
|
lfParams.Set("proto", jsonutils.NewString("tcp"))
|
|
lfParams.Set("port", jsonutils.NewInt(int64(port)))
|
|
lfParams.Set("addr", jsonutils.NewString(ip))
|
|
data, err := modules.Servers.PerformAction(session, serverInfo.Id, "list-forward", lfParams)
|
|
if err != nil {
|
|
err = errors.Wrapf(err, "unable to List Forward for server %s", serverInfo.Id)
|
|
return
|
|
}
|
|
var openForward bool
|
|
var forwards []jsonutils.JSONObject
|
|
if !data.Contains("forwards") {
|
|
openForward = true
|
|
} else {
|
|
forwards, err = data.GetArray("forwards")
|
|
if err != nil {
|
|
err = errors.Wrap(err, "parse response of List Forward")
|
|
return
|
|
}
|
|
openForward = len(forwards) == 0
|
|
}
|
|
|
|
var forward jsonutils.JSONObject
|
|
if openForward {
|
|
forward, err = modules.Servers.PerformAction(session, serverInfo.Id, "open-forward", lfParams)
|
|
if err != nil {
|
|
err = errors.Wrapf(err, "unable to Open Forward for server %s", serverInfo.Id)
|
|
return
|
|
}
|
|
clean = true
|
|
} else {
|
|
forward = forwards[0]
|
|
}
|
|
proxyAddr, _ := forward.GetString("proxy_addr")
|
|
proxyPort, _ := forward.Int("proxy_port")
|
|
// register
|
|
sshable.Port = int(proxyPort)
|
|
sshable.Host = proxyAddr
|
|
sshable.Ok = true
|
|
return
|
|
}
|
|
|
|
func checkSshableForYunionCloudWithDetail(session *mcclient.ClientSession, serverDetail *comapi.ServerDetails) (sshable SSHable, clean bool, err error) {
|
|
if serverDetail.IPs == "" {
|
|
err = fmt.Errorf("empty ips for server %s", serverDetail.Id)
|
|
return
|
|
}
|
|
isWindows := false
|
|
if serverDetail.OsType == "Windows" {
|
|
isWindows = true
|
|
}
|
|
ips := strings.Split(serverDetail.IPs, ",")
|
|
ip := strings.TrimSpace(ips[0])
|
|
return checkSshableForYunionCloud(session, SServerInfo{
|
|
Ip: ip,
|
|
Id: serverDetail.Id,
|
|
Hypervisor: serverDetail.Hypervisor,
|
|
VpcId: serverDetail.VpcId,
|
|
}, isWindows)
|
|
}
|
|
|
|
func CheckSSHable(session *mcclient.ClientSession, serverId string) (sshable SSHable, cleanFunc func() error, err error) {
|
|
params := jsonutils.NewDict()
|
|
params.Set("details", jsonutils.JSONTrue)
|
|
data, err := modules.Servers.GetById(session, serverId, params)
|
|
if err != nil {
|
|
err = errors.Wrapf(err, "unable to fetch server %s", serverId)
|
|
return
|
|
}
|
|
var serverDetail comapi.ServerDetails
|
|
err = data.Unmarshal(&serverDetail)
|
|
if err != nil {
|
|
err = errors.Wrapf(err, "unable to unmarshal %q to ServerDetails", data)
|
|
return
|
|
}
|
|
|
|
// check sshable
|
|
var clean bool
|
|
if serverDetail.Hypervisor == comapi.HYPERVISOR_KVM || serverDetail.Hypervisor == comapi.HYPERVISOR_BAREMETAL {
|
|
sshable, clean, err = checkSshableForYunionCloudWithDetail(session, &serverDetail)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if clean {
|
|
cleanFunc = func() error {
|
|
proxyAddr := sshable.Host
|
|
proxyPort := sshable.Port
|
|
params := jsonutils.NewDict()
|
|
params.Set("proto", jsonutils.NewString("tcp"))
|
|
params.Set("proxy_addr", jsonutils.NewString(proxyAddr))
|
|
params.Set("proxy_port", jsonutils.NewInt(int64(proxyPort)))
|
|
_, err := modules.Servers.PerformAction(session, serverDetail.Id, "close-forward", params)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "unable to close forward(addr %q, port %d, proto %q) for server %s", proxyAddr, proxyPort, "tcp", serverDetail.Id)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
} else {
|
|
sshable, err = checkSshableForOtherCloud(session, serverDetail.Id)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
if !sshable.Ok {
|
|
err = ErrServerNotSshable
|
|
if len(sshable.Reason) > 0 {
|
|
err = errors.Wrap(err, sshable.Reason)
|
|
}
|
|
return
|
|
}
|
|
sshable.ServerName = serverDetail.Name
|
|
sshable.ServerHypervisor = serverDetail.Hypervisor
|
|
// make sure user
|
|
if sshable.User == "" {
|
|
switch {
|
|
case serverDetail.Hypervisor == comapi.HYPERVISOR_KVM:
|
|
sshable.User = "root"
|
|
default:
|
|
sshable.User = "cloudroot"
|
|
}
|
|
}
|
|
|
|
var forwardId string
|
|
if len(sshable.ProxyEndpointId) == 0 {
|
|
return
|
|
} else {
|
|
var sshport int
|
|
sshport, err = getServerSshport(session, serverDetail.Id, false)
|
|
if err != nil {
|
|
err = errors.Wrapf(err, "unable to get sshport of server %s", serverDetail.Id)
|
|
}
|
|
// create local forward
|
|
createP := jsonutils.NewDict()
|
|
createP.Set("type", jsonutils.NewString(cloudproxy_api.FORWARD_TYPE_LOCAL))
|
|
createP.Set("remote_port", jsonutils.NewInt(int64(sshport)))
|
|
createP.Set("server_id", jsonutils.NewString(serverDetail.Id))
|
|
|
|
var forward jsonutils.JSONObject
|
|
|
|
forward, err = cloudproxy.Forwards.PerformClassAction(session, "create-from-server", createP)
|
|
if err != nil {
|
|
err = errors.Wrapf(err, "fail to create local forward from server %q", serverDetail.Id)
|
|
return
|
|
}
|
|
|
|
cleanFunc = func() error {
|
|
return clearLocalForward(session, forwardId)
|
|
}
|
|
|
|
var agent jsonutils.JSONObject
|
|
port, _ := forward.Int("bind_port")
|
|
forwardId, _ = forward.GetString("id")
|
|
agentId, _ := forward.GetString("proxy_agent_id")
|
|
agent, err = cloudproxy.ProxyAgents.Get(session, agentId, nil)
|
|
if err != nil {
|
|
err = errors.Wrapf(err, "fail to get proxy agent %q", agentId)
|
|
return
|
|
}
|
|
address, _ := agent.GetString("advertise_addr")
|
|
// check proxy forward
|
|
if ok := ensureLocalForwardWork(address, int(port)); !ok {
|
|
err = errors.Error("The created local forward is actually not usable")
|
|
return
|
|
}
|
|
sshable.Host = address
|
|
sshable.Port = int(port)
|
|
sshable.ProxyForwardId = forwardId
|
|
}
|
|
return
|
|
}
|
|
|
|
func GetCleanFunc(session *mcclient.ClientSession, hypervisor, serverId, host, forward string, port int) func() error {
|
|
if hypervisor == comapi.HYPERVISOR_KVM || hypervisor == comapi.HYPERVISOR_BAREMETAL {
|
|
return func() error {
|
|
proxyAddr := host
|
|
proxyPort := port
|
|
params := jsonutils.NewDict()
|
|
params.Set("proto", jsonutils.NewString("tcp"))
|
|
params.Set("proxy_addr", jsonutils.NewString(proxyAddr))
|
|
params.Set("proxy_port", jsonutils.NewInt(int64(proxyPort)))
|
|
_, err := modules.Servers.PerformAction(session, serverId, "close-forward", params)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "unable to close forward(addr %q, port %d, proto %q) for server %s", proxyAddr, proxyPort, "tcp", serverId)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
return func() error {
|
|
return clearLocalForward(session, forward)
|
|
}
|
|
}
|
|
|
|
func getServerSshport(session *mcclient.ClientSession, serverId string, isWindows bool) (int, error) {
|
|
if isWindows {
|
|
return 5985, nil
|
|
}
|
|
data, err := modules.Servers.GetSpecific(session, serverId, "sshport", nil)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
port, _ := data.Int("port")
|
|
if port == 0 {
|
|
port = 22
|
|
}
|
|
return int(port), nil
|
|
}
|
|
|
|
func clearLocalForward(s *mcclient.ClientSession, forwardId string) error {
|
|
if len(forwardId) == 0 {
|
|
return nil
|
|
}
|
|
_, err := cloudproxy.Forwards.Delete(s, forwardId, nil)
|
|
return err
|
|
}
|
|
|
|
func ensureLocalForwardWork(host string, port int) bool {
|
|
maxWaitTimes, wt := 10, 1*time.Second
|
|
waitTimes := 1
|
|
address := fmt.Sprintf("%s:%d", host, port)
|
|
for waitTimes < maxWaitTimes {
|
|
_, err := net.DialTimeout("tcp", address, 1*time.Second)
|
|
if err == nil {
|
|
return true
|
|
}
|
|
log.Debugf("no.%d times, try to connect to %s failed: %s", waitTimes, address, err)
|
|
time.Sleep(wt)
|
|
waitTimes += 1
|
|
wt += 1 * time.Second
|
|
}
|
|
return false
|
|
}
|