| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384 |
- // 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
- }
|