From 5151880ec589f824dec5425fbc3ea17599f401aa Mon Sep 17 00:00:00 2001 From: rainzm Date: Sat, 4 Sep 2021 18:03:07 +0800 Subject: [PATCH] feat(devtool): add sshinfo and serviceurl --- cmd/climc/shell/devtool/serviceurl.go | 29 ++ cmd/climc/shell/devtool/sshinfo.go | 29 ++ pkg/apis/devtool/script.go | 9 + pkg/apis/devtool/serviceurl.go | 23 ++ pkg/apis/devtool/sshinfo.go | 23 ++ pkg/devtool/models/service_url.go | 95 +++++ pkg/devtool/models/sshinfo.go | 109 ++++++ pkg/devtool/service/handler.go | 2 + pkg/devtool/tasks/apply_script_task.go | 253 +------------ pkg/devtool/tasks/serviceurl_create_task.go | 78 ++++ pkg/devtool/tasks/sshinfo_create_task.go | 67 ++++ pkg/devtool/tasks/sshinfo_delete_task.go | 54 +++ pkg/devtool/utils/influxdb_url.go | 342 +----------------- pkg/devtool/utils/service_url.go | 380 ++++++++++++++++++++ pkg/devtool/utils/ssh.go | 298 +++++++++++++++ pkg/mcclient/modules/mod_devtools.go | 18 + pkg/mcclient/options/devtool/serviceurl.go | 58 +++ pkg/mcclient/options/devtool/sshinfo.go | 51 +++ 18 files changed, 1342 insertions(+), 576 deletions(-) create mode 100644 cmd/climc/shell/devtool/serviceurl.go create mode 100644 cmd/climc/shell/devtool/sshinfo.go create mode 100644 pkg/apis/devtool/serviceurl.go create mode 100644 pkg/apis/devtool/sshinfo.go create mode 100644 pkg/devtool/models/service_url.go create mode 100644 pkg/devtool/models/sshinfo.go create mode 100644 pkg/devtool/tasks/serviceurl_create_task.go create mode 100644 pkg/devtool/tasks/sshinfo_create_task.go create mode 100644 pkg/devtool/tasks/sshinfo_delete_task.go create mode 100644 pkg/devtool/utils/service_url.go create mode 100644 pkg/devtool/utils/ssh.go create mode 100644 pkg/mcclient/options/devtool/serviceurl.go create mode 100644 pkg/mcclient/options/devtool/sshinfo.go diff --git a/cmd/climc/shell/devtool/serviceurl.go b/cmd/climc/shell/devtool/serviceurl.go new file mode 100644 index 0000000000..38aba0d05c --- /dev/null +++ b/cmd/climc/shell/devtool/serviceurl.go @@ -0,0 +1,29 @@ +// 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 devtool + +import ( + "yunion.io/x/onecloud/cmd/climc/shell" + "yunion.io/x/onecloud/pkg/mcclient/modules" + options "yunion.io/x/onecloud/pkg/mcclient/options/devtool" +) + +func init() { + cmd := shell.NewResourceCmd(&modules.DevToolServiceUrls).WithKeyword("devtool-serviceurl") + cmd.List(new(options.ServiceUrlListOptions)) + cmd.Create(new(options.ServiceUrlCreateOptions)) + cmd.Show(new(options.ServiceUrlOptions)) + cmd.Delete(new(options.ServiceUrlOptions)) +} diff --git a/cmd/climc/shell/devtool/sshinfo.go b/cmd/climc/shell/devtool/sshinfo.go new file mode 100644 index 0000000000..b39b792f83 --- /dev/null +++ b/cmd/climc/shell/devtool/sshinfo.go @@ -0,0 +1,29 @@ +// 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 devtool + +import ( + "yunion.io/x/onecloud/cmd/climc/shell" + "yunion.io/x/onecloud/pkg/mcclient/modules" + options "yunion.io/x/onecloud/pkg/mcclient/options/devtool" +) + +func init() { + cmd := shell.NewResourceCmd(&modules.DevToolSshInfos).WithKeyword("devtool-sshinfo") + cmd.List(new(options.SshInfoListOptions)) + cmd.Create(new(options.SshInfoCreateOptions)) + cmd.Show(new(options.SshInfoOptions)) + cmd.Delete(new(options.SshInfoOptions)) +} diff --git a/pkg/apis/devtool/script.go b/pkg/apis/devtool/script.go index cea18a8586..48584de577 100644 --- a/pkg/apis/devtool/script.go +++ b/pkg/apis/devtool/script.go @@ -87,3 +87,12 @@ type SApplyInfo struct { ServerId string TryTimes int } + +type DevtoolManagerServiceUrlInput struct { + ServiceName string + ServerId string +} + +type DevtoolManagerServiceUrlOutput struct { + ServiceUrl string +} diff --git a/pkg/apis/devtool/serviceurl.go b/pkg/apis/devtool/serviceurl.go new file mode 100644 index 0000000000..437dac29c9 --- /dev/null +++ b/pkg/apis/devtool/serviceurl.go @@ -0,0 +1,23 @@ +// 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 devtool + +const ( + SERVICEURL_STATUS_CREATING = "creating" + SERVICEURL_STATUS_READY = "ready" + SERVICEURL_STATUS_DELETING = "deleting" + SERVICEURL_STATUS_CREATE_FAILED = "create_failed" + SERVICEURL_STATUS_DELETE_FAILED = "delete_failed" +) diff --git a/pkg/apis/devtool/sshinfo.go b/pkg/apis/devtool/sshinfo.go new file mode 100644 index 0000000000..51af07f794 --- /dev/null +++ b/pkg/apis/devtool/sshinfo.go @@ -0,0 +1,23 @@ +// 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 devtool + +const ( + SSHINFO_STATUS_CREATING = "creating" + SSHINFO_STATUS_READY = "ready" + SSHINFO_STATUS_DELETING = "deleting" + SSHINFO_STATUS_CREATE_FAILED = "create_failed" + SSHINFO_STATUS_DELETE_FAILED = "delete_failed" +) diff --git a/pkg/devtool/models/service_url.go b/pkg/devtool/models/service_url.go new file mode 100644 index 0000000000..97815175d3 --- /dev/null +++ b/pkg/devtool/models/service_url.go @@ -0,0 +1,95 @@ +// 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 models + +import ( + "context" + "reflect" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/gotypes" + + api "yunion.io/x/onecloud/pkg/apis/devtool" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/mcclient" +) + +type SServiceUrl struct { + db.SStatusStandaloneResourceBase + Service string `width:"32" charset:"ascii" list:"user" create:"required"` + ServerId string `width:"128" charset:"ascii" list:"user" create:"required"` + Url string `wdith:"32" charset:"ascii" list:"user"` + ServerAnsibleInfo *SServerAnisbleInfo + FailedReason string +} + +type SServiceUrlManager struct { + db.SStatusStandaloneResourceBaseManager +} + +type SServerAnisbleInfo struct { + User string `json:"user"` + IP string `json:"ip"` + Port int `json:"port"` + Name string `json:"name"` +} + +var ServiceUrlManager *SServiceUrlManager + +func init() { + gotypes.RegisterSerializable(reflect.TypeOf(&SServerAnisbleInfo{}), func() gotypes.ISerializable { + return &SServerAnisbleInfo{} + }) + ServiceUrlManager = &SServiceUrlManager{ + SStatusStandaloneResourceBaseManager: db.NewStatusStandaloneResourceBaseManager( + SServiceUrl{}, + "serviceurl_tbl", + "serviceurl", + "serviceurls", + ), + } + ServiceUrlManager.SetVirtualObject(ServiceUrlManager) +} + +func (ai *SServerAnisbleInfo) String() string { + return jsonutils.Marshal(ai).String() +} + +func (ai *SServerAnisbleInfo) IsZero() bool { + return ai == nil +} + +func (su *SServiceUrl) MarkCreateFailed(reason string) { + _, err := db.Update(su, func() error { + su.Status = api.SERVICEURL_STATUS_CREATE_FAILED + su.FailedReason = reason + return nil + }) + if err != nil { + log.Errorf("unable to mark createfailed for sshinfo: %v", err) + } +} + +func (su *SServiceUrl) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) { + su.SetStatus(userCred, api.SERVICEURL_STATUS_CREATING, "") + + task, err := taskman.TaskManager.NewTask(ctx, "ServiceUrlCreateTask", su, userCred, nil, "", "") + if err != nil { + log.Errorf("start ServiceUrlCreateTask failed: %v", err) + } + task.ScheduleRun(nil) +} diff --git a/pkg/devtool/models/sshinfo.go b/pkg/devtool/models/sshinfo.go new file mode 100644 index 0000000000..967db9240e --- /dev/null +++ b/pkg/devtool/models/sshinfo.go @@ -0,0 +1,109 @@ +// 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 models + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/tristate" + + api "yunion.io/x/onecloud/pkg/apis/devtool" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/mcclient" +) + +type SSshInfo struct { + db.SStatusStandaloneResourceBase + ServerId string `width:"128" charset:"ascii" list:"user" create:"required"` + ServerName string `width:"128" charset:"utf8" list:"user" create:"optional"` + ServerHypervisor string `width:"16" charset:"ascii" create:"optional"` + ForwardId string `width:"128" charset:"ascii" create:"optional"` + User string `width:"36" list:"user" create:"optional"` + Host string `width:"36" charset:"ascii" list:"user" create:"optional"` + Port int `width:"8" charset:"ascii" list:"user" create:"optional"` + NeedClean tristate.TriState + FailedReason string +} + +type SSshInfoManager struct { + db.SStatusStandaloneResourceBaseManager +} + +var SshInfoManager *SSshInfoManager + +func init() { + SshInfoManager = &SSshInfoManager{ + SStatusStandaloneResourceBaseManager: db.NewStatusStandaloneResourceBaseManager( + SSshInfo{}, + "sshinfo_tbl", + "sshinfo", + "sshinfos", + ), + } + SshInfoManager.SetVirtualObject(SshInfoManager) +} + +func (si *SSshInfo) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) { + si.SetStatus(userCred, api.SSHINFO_STATUS_CREATING, "") + + task, err := taskman.TaskManager.NewTask(ctx, "SshInfoCreateTask", si, userCred, nil, "", "") + if err != nil { + log.Errorf("start SshInfoCreateTask failed: %v", err) + } + task.ScheduleRun(nil) +} + +func (si *SSshInfo) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { + return nil +} + +func (si *SSshInfo) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error { + return si.SStatusStandaloneResourceBase.Delete(ctx, userCred) +} + +func (si *SSshInfo) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + si.SetStatus(userCred, api.SSHINFO_STATUS_DELETING, "") + task, err := taskman.TaskManager.NewTask(ctx, "SshInfoDeleteTask", si, userCred, nil, "", "") + if err != nil { + log.Errorf("start SshInfoDeleteTask failed: %v", err) + } + task.ScheduleRun(nil) + return nil +} + +func (si *SSshInfo) MarkCreateFailed(reason string) { + _, err := db.Update(si, func() error { + si.Status = api.SSHINFO_STATUS_CREATE_FAILED + si.FailedReason = reason + return nil + }) + if err != nil { + log.Errorf("unable to mark createfailed for sshinfo: %v", err) + } +} + +func (si *SSshInfo) MarkDeleteFailed(reason string) { + _, err := db.Update(si, func() error { + si.Status = api.SSHINFO_STATUS_DELETE_FAILED + si.FailedReason = reason + return nil + }) + if err != nil { + log.Errorf("unable to mark deletefailed for sshinfo: %v", err) + } +} diff --git a/pkg/devtool/service/handler.go b/pkg/devtool/service/handler.go index 303e17b969..df7bda3fa8 100644 --- a/pkg/devtool/service/handler.go +++ b/pkg/devtool/service/handler.go @@ -46,6 +46,8 @@ func InitHandlers(app *appsrv.Application) { models.ScriptManager, models.ScriptApplyManager, models.ScriptApplyRecordManager, + models.SshInfoManager, + models.ServiceUrlManager, } { db.RegisterModelManager(manager) handler := db.NewModelHandler(manager) diff --git a/pkg/devtool/tasks/apply_script_task.go b/pkg/devtool/tasks/apply_script_task.go index 682214df4a..fadbd1eb70 100644 --- a/pkg/devtool/tasks/apply_script_task.go +++ b/pkg/devtool/tasks/apply_script_task.go @@ -16,18 +16,12 @@ package tasks import ( "context" - "fmt" - "net" - "strings" - "time" "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/errors" ansible_api "yunion.io/x/onecloud/pkg/apis/ansible" - cloudproxy_api "yunion.io/x/onecloud/pkg/apis/cloudproxy" - comapi "yunion.io/x/onecloud/pkg/apis/compute" devtool_api "yunion.io/x/onecloud/pkg/apis/devtool" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" @@ -36,7 +30,6 @@ import ( "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/auth" "yunion.io/x/onecloud/pkg/mcclient/modules" - "yunion.io/x/onecloud/pkg/mcclient/modules/cloudproxy" ) type ApplyScriptTask struct { @@ -48,8 +41,6 @@ func init() { taskman.RegisterTask(ApplyScriptTask{}) } -var ErrServerNotSshable = errors.Error("server is not sshable") - func (self *ApplyScriptTask) registerClean(clean func()) { self.cleanFunc = clean } @@ -65,7 +56,7 @@ func (self *ApplyScriptTask) taskFailed(ctx context.Context, sa *models.SScriptA self.clean() var failCode string switch errors.Cause(err) { - case ErrServerNotSshable: + case utils.ErrServerNotSshable: failCode = devtool_api.SCRIPT_APPLY_RECORD_FAILCODE_SSHABLE case utils.ErrCannotReachInfluxbd: failCode = devtool_api.SCRIPT_APPLY_RECORD_FAILCODE_INFLUXDB @@ -115,95 +106,30 @@ func (self *ApplyScriptTask) OnInit(ctx context.Context, obj db.IStandaloneModel return } session := auth.GetAdminSession(ctx, "", "") - params := jsonutils.NewDict() - params.Set("details", jsonutils.JSONTrue) - data, err := modules.Servers.GetById(session, sa.GuestId, params) - if err != nil { - self.taskFailed(ctx, sa, sar, errors.Wrapf(err, "unable to fetch server %s", sa.GuestId)) - return - } - var serverDetail comapi.ServerDetails - err = data.Unmarshal(&serverDetail) - if err != nil { - self.taskFailed(ctx, sa, sar, errors.Wrapf(err, "unable to unmarshal %q to ServerDetails", data)) - return - } - + sshable, cleanFunc, err := utils.CheckSSHable(session, sa.GuestId) // check sshable - sshable, err := self.checkSshable(session, &serverDetail) if err != nil { self.taskFailed(ctx, sa, sar, err) return } - if !sshable.ok { - var err error = ErrServerNotSshable - if len(sshable.reason) > 0 { - err = errors.Wrap(err, sshable.reason) - } - self.taskFailed(ctx, sa, sar, err) - return - } - // make sure user - var user string - switch { - case sshable.user != "": - user = sshable.user - case serverDetail.Hypervisor == comapi.HYPERVISOR_KVM: - user = "root" - default: - user = "cloudroot" - } - - var host ansible_api.AnsibleHost - var forwardId string - if len(sshable.proxyEndpointId) == 0 { - host = ansible_api.AnsibleHost{ - User: user, - IP: sshable.host, - Port: sshable.port, - Name: serverDetail.Name, - } - } else { - // create local forward - createP := jsonutils.NewDict() - createP.Set("type", jsonutils.NewString(cloudproxy_api.FORWARD_TYPE_LOCAL)) - createP.Set("remote_port", jsonutils.NewInt(22)) - createP.Set("server_id", jsonutils.NewString(serverDetail.Id)) - - forward, err := cloudproxy.Forwards.PerformClassAction(session, "create-from-server", createP) - if err != nil { - self.taskFailed(ctx, sa, sar, errors.Wrapf(err, "fail to create local forward from server %q", serverDetail.Id)) - return - } - + if cleanFunc != nil { self.registerClean(func() { - self.clearLocalForward(session, forwardId) + err := cleanFunc() + if err != nil { + log.Errorf("unable to clean: %v", err) + } }) + } - 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 { - self.taskFailed(ctx, sa, sar, errors.Wrapf(err, "fail to get proxy agent %q", agentId)) - return - } - address, _ := agent.GetString("advertise_addr") - // check proxy forward - if ok := self.ensureLocalForwardWork(address, int(port)); !ok { - self.taskFailed(ctx, sa, sar, errors.Error("The created local forward is actually not usable")) - return - } - host = ansible_api.AnsibleHost{ - User: user, - IP: address, - Port: int(port), - Name: serverDetail.Name, - } + host := ansible_api.AnsibleHost{ + User: sshable.User, + IP: sshable.Host, + Port: sshable.Port, + Name: sshable.ServerName, } // genrate args - params = jsonutils.NewDict() + params := jsonutils.NewDict() if len(sa.ArgsGenerator) == 0 { params.Set("args", sa.Args) } else { @@ -211,7 +137,7 @@ func (self *ApplyScriptTask) OnInit(ctx context.Context, obj db.IStandaloneModel if !ok { params.Set("args", sa.Args) } - arg, err := generator(ctx, sa.GuestId, sshable.proxyEndpointId, &host) + arg, err := generator(ctx, sa.GuestId, sshable.ProxyEndpointId, &host) if err != nil { self.taskFailed(ctx, sa, sar, err) return @@ -224,7 +150,6 @@ func (self *ApplyScriptTask) OnInit(ctx context.Context, obj db.IStandaloneModel // fetch ansible playbook reference id updateData := jsonutils.NewDict() updateData.Set("script_apply_record_id", jsonutils.NewString(sar.GetId())) - updateData.Set("proxy_forward_id", jsonutils.NewString(forwardId)) self.SetStage("OnAnsiblePlaybookComplete", updateData) // Inject Task Header @@ -238,154 +163,6 @@ func (self *ApplyScriptTask) OnInit(ctx context.Context, obj db.IStandaloneModel } } -type sSSHable struct { - ok bool - reason string - - user string - - proxyEndpointId string - proxyAgentId string - - host string - port int -} - -func (self *ApplyScriptTask) checkSshableForYunionCloud(session *mcclient.ClientSession, serverDetail *comapi.ServerDetails) (sSSHable, error) { - if serverDetail.IPs == "" { - return sSSHable{}, fmt.Errorf("empty ips for server %s", serverDetail.Id) - } - ips := strings.Split(serverDetail.IPs, ",") - ip := strings.TrimSpace(ips[0]) - if serverDetail.Hypervisor == comapi.HYPERVISOR_BAREMETAL || serverDetail.VpcId == "" || serverDetail.VpcId == comapi.DEFAULT_VPC_ID { - return sSSHable{ - ok: true, - user: "cloudroot", - host: ip, - port: 22, - }, nil - } - lfParams := jsonutils.NewDict() - lfParams.Set("proto", jsonutils.NewString("tcp")) - lfParams.Set("port", jsonutils.NewInt(22)) - data, err := modules.Servers.PerformAction(session, serverDetail.Id, "list-forward", lfParams) - if err != nil { - return sSSHable{}, errors.Wrapf(err, "unable to List Forward for server %s", serverDetail.Id) - } - var openForward bool - var forwards []jsonutils.JSONObject - if !data.Contains("forwards") { - openForward = true - } else { - forwards, err = data.GetArray("forwards") - if err != nil { - return sSSHable{}, errors.Wrap(err, "parse response of List Forward") - } - openForward = len(forwards) == 0 - } - - var forward jsonutils.JSONObject - if openForward { - forward, err = modules.Servers.PerformAction(session, serverDetail.Id, "open-forward", lfParams) - if err != nil { - return sSSHable{}, errors.Wrapf(err, "unable to Open Forward for server %s", serverDetail.Id) - } - // register - self.registerClean(func() { - proxyAddr, _ := forward.GetString("proxy_addr") - proxyPort, _ := forward.Int("proxy_port") - params := jsonutils.NewDict() - params.Set("proto", jsonutils.NewString("tcp")) - params.Set("proxy_addr", jsonutils.NewString(proxyAddr)) - params.Set("proxy_port", jsonutils.NewInt(proxyPort)) - _, err := modules.Servers.PerformAction(session, serverDetail.Id, "close-forward", params) - if err != nil { - log.Errorf("unable to close forward(addr %q, port %d, proto %q) for server %s: %v", proxyAddr, proxyPort, "tcp", serverDetail.Id, err) - } - }) - } else { - forward = forwards[0] - } - proxyAddr, _ := forward.GetString("proxy_addr") - proxyPort, _ := forward.Int("proxy_port") - // register - return sSSHable{ - ok: true, - user: "cloudroot", - host: proxyAddr, - port: int(proxyPort), - }, nil -} - -func (self *ApplyScriptTask) checkSshable(session *mcclient.ClientSession, serverDetail *comapi.ServerDetails) (sSSHable, error) { - if serverDetail.Hypervisor == comapi.HYPERVISOR_KVM || serverDetail.Hypervisor == comapi.HYPERVISOR_BAREMETAL { - return self.checkSshableForYunionCloud(session, serverDetail) - } - return self.checkSshableForOtherCloud(session, serverDetail.Id) -} - -func (self *ApplyScriptTask) checkSshableForOtherCloud(session *mcclient.ClientSession, serverId string) (sSSHable, error) { - data, err := modules.Servers.GetSpecific(session, serverId, "sshable", nil) - if err != nil { - return sSSHable{}, errors.Wrapf(err, "unable to get sshable info of server %s", serverId) - } - var sshableOutput comapi.GuestSshableOutput - err = data.Unmarshal(&sshableOutput) - if err != nil { - return sSSHable{}, errors.Wrapf(err, "unable to marshal output of server sshable: %s", data) - } - sshable := sSSHable{ - 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 -} - -func (self *ApplyScriptTask) clearLocalForward(s *mcclient.ClientSession, forwardId string) { - if len(forwardId) == 0 { - return - } - _, err := cloudproxy.Forwards.Delete(s, forwardId, nil) - if err != nil { - log.Errorf("unable to delete proxy forward %s", forwardId) - } -} - -func (self *ApplyScriptTask) 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 -} - func mapStringSlice(f func(string) string, a []string) []string { for i := range a { a[i] = f(a[i]) diff --git a/pkg/devtool/tasks/serviceurl_create_task.go b/pkg/devtool/tasks/serviceurl_create_task.go new file mode 100644 index 0000000000..fe594c6c12 --- /dev/null +++ b/pkg/devtool/tasks/serviceurl_create_task.go @@ -0,0 +1,78 @@ +// 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 tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/apis/ansible" + "yunion.io/x/onecloud/pkg/apis/devtool" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/devtool/models" + "yunion.io/x/onecloud/pkg/devtool/utils" +) + +type ServiceUrlCreateTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(ServiceUrlCreateTask{}) +} + +func (self *ServiceUrlCreateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + serviceUrl := obj.(*models.SServiceUrl) + info, err := utils.GetServerInfo(ctx, serviceUrl.ServerId) + if err != nil { + serviceUrl.MarkCreateFailed(fmt.Sprintf("unable to get serverInfo of server %s: %v", serviceUrl.ServerId, err)) + self.SetStageFailed(ctx, nil) + return + } + + url, err := utils.GetServiceUrl(ctx, serviceUrl.Service) + if err != nil { + serviceUrl.MarkCreateFailed(err.Error()) + self.SetStageFailed(ctx, nil) + return + } + url, err = utils.FindValidServiceUrl(ctx, utils.Service{ + Url: url, + Name: serviceUrl.Service, + }, "", info, &ansible.AnsibleHost{ + User: serviceUrl.ServerAnsibleInfo.User, + IP: serviceUrl.ServerAnsibleInfo.IP, + Port: serviceUrl.ServerAnsibleInfo.Port, + Name: serviceUrl.ServerAnsibleInfo.Name, + }) + if err != nil { + serviceUrl.MarkCreateFailed(err.Error()) + self.SetStageFailed(ctx, nil) + return + } + _, err = db.Update(serviceUrl, func() error { + serviceUrl.Url = url + serviceUrl.Status = devtool.SERVICEURL_STATUS_READY + return nil + }) + if err != nil { + log.Errorf("unable to update serviceurl: %v", err) + } + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/devtool/tasks/sshinfo_create_task.go b/pkg/devtool/tasks/sshinfo_create_task.go new file mode 100644 index 0000000000..2df1370c8a --- /dev/null +++ b/pkg/devtool/tasks/sshinfo_create_task.go @@ -0,0 +1,67 @@ +// 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 tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/tristate" + + "yunion.io/x/onecloud/pkg/apis/devtool" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/devtool/models" + "yunion.io/x/onecloud/pkg/devtool/utils" + "yunion.io/x/onecloud/pkg/mcclient/auth" +) + +type SshInfoCreateTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(SshInfoCreateTask{}) +} + +func (self *SshInfoCreateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + sshInfo := obj.(*models.SSshInfo) + serverId := sshInfo.ServerId + session := auth.GetSession(ctx, self.GetUserCred(), "", "") + sshable, cleanFunc, err := utils.CheckSSHable(session, serverId) + if err != nil { + sshInfo.MarkCreateFailed(err.Error()) + self.SetStageFailed(ctx, nil) + return + } + _, err = db.Update(sshInfo, func() error { + if cleanFunc != nil { + sshInfo.NeedClean = tristate.True + } + sshInfo.ServerName = sshable.ServerName + sshInfo.ServerHypervisor = sshable.ServerHypervisor + sshInfo.Host = sshable.Host + sshInfo.User = sshable.User + sshInfo.Port = sshable.Port + sshInfo.ForwardId = sshable.ProxyForwardId + sshInfo.Status = devtool.SSHINFO_STATUS_READY + return nil + }) + if err != nil { + log.Errorf("unable to update sshinfo: %v", err) + } + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/devtool/tasks/sshinfo_delete_task.go b/pkg/devtool/tasks/sshinfo_delete_task.go new file mode 100644 index 0000000000..8ea4722bc1 --- /dev/null +++ b/pkg/devtool/tasks/sshinfo_delete_task.go @@ -0,0 +1,54 @@ +// 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 tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/devtool/models" + "yunion.io/x/onecloud/pkg/devtool/utils" + "yunion.io/x/onecloud/pkg/mcclient/auth" +) + +type SshInfoDeleteTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(SshInfoDeleteTask{}) +} + +func (self *SshInfoDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + sshInfo := obj.(*models.SSshInfo) + if sshInfo.NeedClean.IsFalse() { + sshInfo.RealDelete(ctx, self.GetUserCred()) + self.SetStageComplete(ctx, nil) + return + } + session := auth.GetSession(ctx, self.GetUserCred(), "", "") + clean := utils.GetCleanFunc(session, sshInfo.ServerHypervisor, sshInfo.ServerId, sshInfo.Host, sshInfo.ForwardId, sshInfo.Port) + err := clean() + if err != nil { + sshInfo.MarkDeleteFailed(err.Error()) + self.SetStageFailed(ctx, nil) + return + } + sshInfo.RealDelete(ctx, self.GetUserCred()) + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/devtool/utils/influxdb_url.go b/pkg/devtool/utils/influxdb_url.go index b25d6bfd12..a04bb72871 100644 --- a/pkg/devtool/utils/influxdb_url.go +++ b/pkg/devtool/utils/influxdb_url.go @@ -16,354 +16,19 @@ package utils import ( "context" - "fmt" - "net/url" - "time" - "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/errors" - "yunion.io/x/pkg/util/sets" ansible_api "yunion.io/x/onecloud/pkg/apis/ansible" - proxy_api "yunion.io/x/onecloud/pkg/apis/cloudproxy" comapi "yunion.io/x/onecloud/pkg/apis/compute" - "yunion.io/x/onecloud/pkg/cloudcommon/db" - "yunion.io/x/onecloud/pkg/httperrors" - "yunion.io/x/onecloud/pkg/mcclient/auth" - "yunion.io/x/onecloud/pkg/mcclient/modules" - "yunion.io/x/onecloud/pkg/mcclient/modules/cloudproxy" - "yunion.io/x/onecloud/pkg/util/ansible" - "yunion.io/x/onecloud/pkg/util/httputils" ) -type sServerInfo struct { - ServerId string - VpcId string - NetworkIds []string - serverDetails *comapi.ServerDetails -} - type sProxyEndpoint struct { Id string Address string } -func proxyEndpoints(ctx context.Context, proxyEndpointId string, info sServerInfo) ([]sProxyEndpoint, error) { - pes := make([]sProxyEndpoint, 0) - session := auth.GetAdminSession(ctx, "", "") - if len(proxyEndpointId) > 0 { - ep, err := cloudproxy.ProxyEndpoints.Get(session, proxyEndpointId, nil) - if err != nil { - return nil, errors.Wrapf(err, "unable to get proxy endpoint %s", proxyEndpointId) - } - address, _ := ep.GetString("intranet_ip_addr") - pes = append(pes, sProxyEndpoint{proxyEndpointId, address}) - return pes, nil - } - proxyEndpointIds := sets.NewString() - for _, netId := range info.NetworkIds { - filter := jsonutils.NewDict() - filter.Set("network_id", jsonutils.NewString(netId)) - filter.Set("scope", jsonutils.NewString("system")) - lr, err := cloudproxy.ProxyEndpoints.List(session, filter) - if err != nil { - return nil, errors.Wrapf(err, "unable to list proxy endpoint in network %q", netId) - } - for i := range lr.Data { - proxyEndpointId, _ := lr.Data[i].GetString("id") - address, _ := lr.Data[i].GetString("intranet_ip_addr") - if proxyEndpointIds.Has(proxyEndpointId) { - continue - } - pes = append(pes, sProxyEndpoint{proxyEndpointId, address}) - proxyEndpointIds.Insert(proxyEndpointId) - } - } - filter := jsonutils.NewDict() - filter.Set("vpc_id", jsonutils.NewString(info.VpcId)) - filter.Set("scope", jsonutils.NewString("system")) - lr, err := cloudproxy.ProxyEndpoints.List(session, filter) - if err != nil { - return nil, errors.Wrapf(err, "unable to list proxy endpoint in vpc %q", info.VpcId) - } - for i := range lr.Data { - proxyEndpointId, _ := lr.Data[i].GetString("id") - address, _ := lr.Data[i].GetString("intranet_ip_addr") - if proxyEndpointIds.Has(proxyEndpointId) { - continue - } - pes = append(pes, sProxyEndpoint{proxyEndpointId, address}) - proxyEndpointIds.Insert(proxyEndpointId) - } - return pes, nil -} - -func getServerInfo(ctx context.Context, serverId string) (sServerInfo, error) { - // check server - session := auth.GetAdminSession(ctx, "", "") - data, err := modules.Servers.Get(session, serverId, nil) - if err != nil { - if httputils.ErrorCode(err) == 404 { - return sServerInfo{}, httperrors.NewInputParameterError("no such server %s", serverId) - } - return sServerInfo{}, fmt.Errorf("unable to get server %s: %s", serverId, httputils.ErrorMsg(err)) - } - info := sServerInfo{} - var serverDetails comapi.ServerDetails - err = data.Unmarshal(&serverDetails) - if err != nil { - return info, errors.Wrap(err, "unable to unmarshal serverDetails") - } - if serverDetails.Status != comapi.VM_RUNNING { - return info, httperrors.NewInputParameterError("can only apply scripts to %s server", comapi.VM_RUNNING) - } - info.serverDetails = &serverDetails - info.ServerId = serverDetails.Id - - networkIds := sets.NewString() - for _, nic := range serverDetails.Nics { - networkIds.Insert(nic.NetworkId) - info.VpcId = nic.VpcId - } - info.NetworkIds = networkIds.UnsortedList() - return info, nil -} - -func convertInfluxdbUrl(ctx context.Context, pUrl string, endpointId string) (port int64, recycle func() error, err error) { - session := auth.AdminSessionWithInternal(ctx, "", "", "") - filter := jsonutils.NewDict() - filter.Set("proxy_endpoint_id", jsonutils.NewString(endpointId)) - filter.Set("opaque", jsonutils.NewString(pUrl)) - filter.Set("scope", jsonutils.NewString("system")) - lr, err := cloudproxy.Forwards.List(session, filter) - if err != nil { - return 0, nil, errors.Wrap(err, "failed to list forward") - } - var forwardId string - var lastSeen string - if len(lr.Data) > 0 { - port, _ = lr.Data[0].Int("bind_port") - forwardId, _ = lr.Data[0].GetString("id") - lastSeen, _ = lr.Data[0].GetString("last_seen") - } else { - var rUrl *url.URL - rUrl, err = url.Parse(pUrl) - if err != nil { - err = errors.Wrap(err, "invalid influxdbUrl?") - return - } - // create one - createP := jsonutils.NewDict() - createP.Set("proxy_endpoint", jsonutils.NewString(endpointId)) - createP.Set("type", jsonutils.NewString(proxy_api.FORWARD_TYPE_REMOTE)) - createP.Set("remote_addr", jsonutils.NewString(rUrl.Hostname())) - createP.Set("remote_port", jsonutils.NewString(rUrl.Port())) - createP.Set("generate_name", jsonutils.NewString("influxdb proxy")) - createP.Set("opaque", jsonutils.NewString(pUrl)) - var forward jsonutils.JSONObject - forward, err = cloudproxy.Forwards.Create(session, createP) - if err != nil { - err = errors.Wrapf(err, "unable to create forward with create params %s", createP.String()) - return - } - forwardId, _ = forward.GetString("id") - lastSeen, _ = forward.GetString("last_seen") - recycle = func() error { - _, err := cloudproxy.Forwards.Delete(session, forwardId, nil) - return err - } - port, _ = forward.Int("bind_port") - } - // wait forward last seen not empty - times, waitTime := 0, time.Second - var data jsonutils.JSONObject - for lastSeen == "" && times < 10 { - time.Sleep(waitTime) - times += 1 - waitTime += time.Second * time.Duration(times) - data, err = cloudproxy.Forwards.GetSpecific(session, forwardId, "lastseen", nil) - if err != nil { - err = errors.Wrapf(err, "unable to check last_seen for forward %s", forwardId) - return - } - log.Infof("data of last seen: %s", data) - lastSeen, _ = data.GetString("last_seen") - } - if lastSeen == "" { - err = errors.Wrapf(err, "last_seen of forward %s always is empty, something wrong", forwardId) - } - return -} - -func checkProxyEndpoint(ctx context.Context, influxdbUrl, proxyEndpointId, address string, host *ansible_api.AnsibleHost) (string, error) { - port, recycle, err := convertInfluxdbUrl(ctx, influxdbUrl, proxyEndpointId) - if err != nil { - return "", err - } - nUrl := fmt.Sprintf("https://%s:%d", address, port) - ok, err := checkUrl(ctx, nUrl, host) - if err != nil { - return "", errors.Wrapf(err, "check url %q", nUrl) - } - if !ok { - if recycle != nil { - err := recycle() - if err != nil { - return "", errors.Wrapf(err, "unble to recycle remote forward of proxyEndpoint %s", proxyEndpointId) - } - } - return "", nil - } - return nUrl, nil -} - -func influxdbUrlViaProxyEndpoint(ctx context.Context, influxdbUrl, proxyEndpointId string, info sServerInfo, host *ansible_api.AnsibleHost) (string, error) { - if len(proxyEndpointId) > 0 { - pes, err := proxyEndpoints(ctx, proxyEndpointId, info) - if err != nil { - return "", err - } - url, err := checkProxyEndpoint(ctx, influxdbUrl, proxyEndpointId, pes[0].Address, host) - if err != nil { - return "", err - } - if len(url) > 0 { - return url, nil - } - } - pes, err := proxyEndpoints(ctx, "", info) - if err != nil { - return "", err - } - for _, pe := range pes { - url, err := checkProxyEndpoint(ctx, influxdbUrl, pe.Id, pe.Address, host) - if err != nil { - return "", err - } - if len(url) > 0 { - return url, nil - } - } - return "", nil -} - -func influxdbUrlDirect(ctx context.Context, influxdbUrl, proxyEndpointId string, info sServerInfo, host *ansible_api.AnsibleHost) (string, error) { - // check direct - ok, err := checkUrl(ctx, influxdbUrl, host) - if err != nil { - return "", err - } - if ok { - return influxdbUrl, nil - } - return "", nil -} - -func findValidInfluxdbUrl(ctx context.Context, influxdbUrl, proxyEndpointId string, info sServerInfo, host *ansible_api.AnsibleHost) (string, error) { - findFuncs := []func(ctx context.Context, influxdbUrl, proxyEndpointId string, info sServerInfo, host *ansible_api.AnsibleHost) (string, error){} - if info.serverDetails.Hypervisor == comapi.HYPERVISOR_KVM || info.serverDetails.Hypervisor == comapi.HYPERVISOR_BAREMETAL { - findFuncs = append(findFuncs, influxdbUrlDirect, influxdbUrlViaProxyEndpoint) - } else { - findFuncs = append(findFuncs, influxdbUrlViaProxyEndpoint, influxdbUrlDirect) - } - for _, find := range findFuncs { - url, err := find(ctx, influxdbUrl, proxyEndpointId, info, host) - if err != nil { - return "", err - } - if len(url) > 0 { - return url, nil - } - } - return "", nil -} - -func checkUrl(ctx context.Context, url string, host *ansible_api.AnsibleHost) (bool, error) { - session := auth.GetAdminSession(ctx, "", "") - ahost := ansible.Host{} - ahost.Name = host.IP - ahost.Vars = map[string]string{ - "ansible_port": fmt.Sprintf("%d", host.Port), - "ansible_user": host.User, - } - mod := ansible.Module{ - Name: "uri", - Args: []string{ - fmt.Sprintf("url=%s/ping", url), - "method=GET", - "status_code=204", - "validate_certs=no", - }, - } - - playbook := ansible.NewPlaybook() - playbook.Inventory = ansible.Inventory{ - Hosts: []ansible.Host{ - ahost, - }, - } - playbook.Modules = []ansible.Module{ - mod, - } - apCreateInput := ansible_api.AnsiblePlaybookCreateInput{ - Name: db.DefaultUUIDGenerator(), - Playbook: *playbook, - } - apb, err := modules.AnsiblePlaybooks.Create(session, apCreateInput.JSON(apCreateInput)) - if err != nil { - return false, errors.Wrap(err, "create ansible playbook") - } - id, _ := apb.GetString("id") - defer func() { - _, err := modules.AnsiblePlaybooks.Delete(session, id, nil) - if err != nil { - log.Errorf("unable to delete ansibleplaybook %s: %v", id, err) - } - }() - times, waitTimes := 0, time.Second - for times < 10 { - time.Sleep(waitTimes) - times++ - waitTimes += time.Second * time.Duration(times) - apd, err := modules.AnsiblePlaybooks.GetSpecific(session, id, "status", nil) - if err != nil { - return false, errors.Wrapf(err, "unable to get ansibleplaybook %s status", id) - } - status, _ := apd.GetString("status") - switch status { - case ansible_api.AnsiblePlaybookStatusInit, ansible_api.AnsiblePlaybookStatusRunning: - continue - case ansible_api.AnsiblePlaybookStatusFailed, ansible_api.AnsiblePlaybookStatusCanceled, ansible_api.AnsiblePlaybookStatusUnknown: - return false, nil - case ansible_api.AnsiblePlaybookStatusSucceeded: - return true, nil - } - } - return false, nil -} - -var influxdbUrl string - -func getInfluxdbUrl(ctx context.Context) (string, error) { - if len(influxdbUrl) > 0 { - return influxdbUrl, nil - } - session := auth.GetAdminSession(ctx, "", "") - params := jsonutils.NewDict() - params.Set("interface", jsonutils.NewString("public")) - params.Set("service", jsonutils.NewString("influxdb")) - ret, err := modules.EndpointsV3.List(session, params) - if err != nil { - return "", err - } - if len(ret.Data) == 0 { - return "", fmt.Errorf("no sucn endpoint with 'internal' interface and 'influxdb' service") - } - url, _ := ret.Data[0].GetString("url") - return url, nil -} - var ErrCannotReachInfluxbd = errors.Error("no suitable network to reach influxdb") func GetArgs(ctx context.Context, serverId, proxyEndpointId string, others interface{}) (map[string]interface{}, error) { @@ -371,16 +36,17 @@ func GetArgs(ctx context.Context, serverId, proxyEndpointId string, others inter if !ok { return nil, errors.Error("unknown others, want *AnsibleHost") } - info, err := getServerInfo(ctx, serverId) + info, err := GetServerInfo(ctx, serverId) if err != nil { return nil, errors.Wrapf(err, "unable to get serverInfo of server %s", serverId) } - influxdbUrl, err := getInfluxdbUrl(ctx) + influxdbUrl, err := GetServiceUrl(ctx, "influxdb") if err != nil { return nil, errors.Wrap(err, "unable to get influxdbUrl") } - influxdbUrl, err = findValidInfluxdbUrl(ctx, influxdbUrl, proxyEndpointId, info, host) + log.Infof("influxdbUrl: %s", influxdbUrl) + influxdbUrl, err = FindValidServiceUrl(ctx, Service{"influxdb", influxdbUrl}, proxyEndpointId, info, host) if err != nil { return nil, errors.Wrapf(err, "unable to convertInfluxdbUrl %s", influxdbUrl) } diff --git a/pkg/devtool/utils/service_url.go b/pkg/devtool/utils/service_url.go new file mode 100644 index 0000000000..c983c43abb --- /dev/null +++ b/pkg/devtool/utils/service_url.go @@ -0,0 +1,380 @@ +// 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 ( + "context" + "fmt" + "net/url" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/sets" + + ansible_api "yunion.io/x/onecloud/pkg/apis/ansible" + proxy_api "yunion.io/x/onecloud/pkg/apis/cloudproxy" + comapi "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/modules/cloudproxy" + "yunion.io/x/onecloud/pkg/util/ansible" + "yunion.io/x/onecloud/pkg/util/httputils" +) + +type Service struct { + Name string + Url string +} + +func serviceComplete(service Service) (completeUrl string, expectedCode int) { + switch service.Name { + case "influxdb": + return fmt.Sprintf("%s/ping", service.Url), 204 + case "repo": + return service.Url, 200 + default: + return service.Url, 200 + } +} + +func proxyEndpoints(ctx context.Context, proxyEndpointId string, info sServerInfo) ([]sProxyEndpoint, error) { + pes := make([]sProxyEndpoint, 0) + session := auth.GetAdminSession(ctx, "", "") + if len(proxyEndpointId) > 0 { + ep, err := cloudproxy.ProxyEndpoints.Get(session, proxyEndpointId, nil) + if err != nil { + return nil, errors.Wrapf(err, "unable to get proxy endpoint %s", proxyEndpointId) + } + address, _ := ep.GetString("intranet_ip_addr") + pes = append(pes, sProxyEndpoint{proxyEndpointId, address}) + return pes, nil + } + proxyEndpointIds := sets.NewString() + for _, netId := range info.NetworkIds { + filter := jsonutils.NewDict() + filter.Set("network_id", jsonutils.NewString(netId)) + filter.Set("scope", jsonutils.NewString("system")) + lr, err := cloudproxy.ProxyEndpoints.List(session, filter) + if err != nil { + return nil, errors.Wrapf(err, "unable to list proxy endpoint in network %q", netId) + } + for i := range lr.Data { + proxyEndpointId, _ := lr.Data[i].GetString("id") + address, _ := lr.Data[i].GetString("intranet_ip_addr") + if proxyEndpointIds.Has(proxyEndpointId) { + continue + } + pes = append(pes, sProxyEndpoint{proxyEndpointId, address}) + proxyEndpointIds.Insert(proxyEndpointId) + } + } + filter := jsonutils.NewDict() + filter.Set("vpc_id", jsonutils.NewString(info.VpcId)) + filter.Set("scope", jsonutils.NewString("system")) + lr, err := cloudproxy.ProxyEndpoints.List(session, filter) + if err != nil { + return nil, errors.Wrapf(err, "unable to list proxy endpoint in vpc %q", info.VpcId) + } + for i := range lr.Data { + proxyEndpointId, _ := lr.Data[i].GetString("id") + address, _ := lr.Data[i].GetString("intranet_ip_addr") + if proxyEndpointIds.Has(proxyEndpointId) { + continue + } + pes = append(pes, sProxyEndpoint{proxyEndpointId, address}) + proxyEndpointIds.Insert(proxyEndpointId) + } + return pes, nil +} + +func serviceUrlDirect(ctx context.Context, service Service, proxyEndpointId string, info sServerInfo, host *ansible_api.AnsibleHost) (string, error) { + url, code := serviceComplete(service) + ok, err := checkUrl(ctx, url, code, host) + if err != nil { + return "", err + } + if ok { + return service.Url, nil + } + return "", nil +} + +func GetServerInfo(ctx context.Context, serverId string) (sServerInfo, error) { + // check server + session := auth.GetAdminSession(ctx, "", "") + data, err := modules.Servers.Get(session, serverId, nil) + if err != nil { + if httputils.ErrorCode(err) == 404 { + return sServerInfo{}, httperrors.NewInputParameterError("no such server %s", serverId) + } + return sServerInfo{}, fmt.Errorf("unable to get server %s: %s", serverId, httputils.ErrorMsg(err)) + } + info := sServerInfo{} + var serverDetails comapi.ServerDetails + err = data.Unmarshal(&serverDetails) + if err != nil { + return info, errors.Wrap(err, "unable to unmarshal serverDetails") + } + if serverDetails.Status != comapi.VM_RUNNING { + return info, httperrors.NewInputParameterError("can only apply scripts to %s server", comapi.VM_RUNNING) + } + info.serverDetails = &serverDetails + info.ServerId = serverDetails.Id + + networkIds := sets.NewString() + for _, nic := range serverDetails.Nics { + networkIds.Insert(nic.NetworkId) + info.VpcId = nic.VpcId + } + info.NetworkIds = networkIds.UnsortedList() + return info, nil +} + +func serviceUrlViaProxyEndpoint(ctx context.Context, service Service, proxyEndpointId string, info sServerInfo, host *ansible_api.AnsibleHost) (string, error) { + if len(proxyEndpointId) > 0 { + pes, err := proxyEndpoints(ctx, proxyEndpointId, info) + if err != nil { + return "", err + } + url, err := checkProxyEndpoint(ctx, service, proxyEndpointId, pes[0].Address, host) + if err != nil { + return "", err + } + if len(url) > 0 { + return url, nil + } + } + pes, err := proxyEndpoints(ctx, "", info) + if err != nil { + return "", err + } + for _, pe := range pes { + url, err := checkProxyEndpoint(ctx, service, pe.Id, pe.Address, host) + if err != nil { + return "", err + } + if len(url) > 0 { + return url, nil + } + } + return "", nil +} + +func FindValidServiceUrl(ctx context.Context, service Service, proxyEndpointId string, info sServerInfo, host *ansible_api.AnsibleHost) (string, error) { + findFuncs := []func(ctx context.Context, service Service, proxyEndpointId string, info sServerInfo, host *ansible_api.AnsibleHost) (string, error){} + if info.serverDetails.Hypervisor == comapi.HYPERVISOR_KVM || info.serverDetails.Hypervisor == comapi.HYPERVISOR_BAREMETAL { + findFuncs = append(findFuncs, serviceUrlDirect, serviceUrlViaProxyEndpoint) + } else { + findFuncs = append(findFuncs, serviceUrlViaProxyEndpoint, serviceUrlDirect) + } + for _, find := range findFuncs { + url, err := find(ctx, service, proxyEndpointId, info, host) + if err != nil { + return "", err + } + if len(url) > 0 { + return url, nil + } + } + return "", nil +} + +func checkProxyEndpoint(ctx context.Context, service Service, proxyEndpointId, address string, host *ansible_api.AnsibleHost) (string, error) { + port, recycle, err := convertServiceUrl(ctx, service, proxyEndpointId) + if err != nil { + return "", err + } + url, code := serviceComplete(service) + nUrl := fmt.Sprintf("https://%s:%d", address, port) + ok, err := checkUrl(ctx, url, code, host) + if err != nil { + return "", errors.Wrapf(err, "check url %q", nUrl) + } + if !ok { + if recycle != nil { + err := recycle() + if err != nil { + return "", errors.Wrapf(err, "unble to recycle remote forward of proxyEndpoint %s", proxyEndpointId) + } + } + return "", nil + } + return nUrl, nil +} + +type sServerInfo struct { + ServerId string + VpcId string + NetworkIds []string + serverDetails *comapi.ServerDetails +} + +var serviceUrls map[string]string = map[string]string{} + +func GetServiceUrl(ctx context.Context, serviceName string) (string, error) { + if url, ok := serviceUrls[serviceName]; ok { + return url, nil + } + session := auth.GetAdminSession(ctx, "", "") + params := jsonutils.NewDict() + params.Set("interface", jsonutils.NewString("public")) + params.Set("service", jsonutils.NewString(serviceName)) + ret, err := modules.EndpointsV3.List(session, params) + if err != nil { + return "", err + } + log.Infof("params to list endpoint: %v", params) + log.Infof("ret to list endpoint: %s", ret) + if len(ret.Data) == 0 { + return "", fmt.Errorf("no sucn endpoint with 'internal' interface and 'influxdb' service") + } + url, _ := ret.Data[0].GetString("url") + serviceUrls[serviceName] = url + return url, nil +} + +func convertServiceUrl(ctx context.Context, service Service, endpointId string) (port int64, recycle func() error, err error) { + session := auth.AdminSessionWithInternal(ctx, "", "", "") + filter := jsonutils.NewDict() + filter.Set("proxy_endpoint_id", jsonutils.NewString(endpointId)) + filter.Set("opaque", jsonutils.NewString(service.Url)) + filter.Set("scope", jsonutils.NewString("system")) + lr, err := cloudproxy.Forwards.List(session, filter) + if err != nil { + return 0, nil, errors.Wrap(err, "failed to list forward") + } + var forwardId string + var lastSeen string + if len(lr.Data) > 0 { + port, _ = lr.Data[0].Int("bind_port") + forwardId, _ = lr.Data[0].GetString("id") + lastSeen, _ = lr.Data[0].GetString("last_seen") + } else { + var rUrl *url.URL + rUrl, err = url.Parse(service.Url) + if err != nil { + err = errors.Wrap(err, "invalid serviceUrl?") + return + } + // create one + createP := jsonutils.NewDict() + createP.Set("proxy_endpoint", jsonutils.NewString(endpointId)) + createP.Set("type", jsonutils.NewString(proxy_api.FORWARD_TYPE_REMOTE)) + createP.Set("remote_addr", jsonutils.NewString(rUrl.Hostname())) + createP.Set("remote_port", jsonutils.NewString(rUrl.Port())) + createP.Set("generate_name", jsonutils.NewString(service.Name+" proxy")) + createP.Set("opaque", jsonutils.NewString(service.Url)) + var forward jsonutils.JSONObject + forward, err = cloudproxy.Forwards.Create(session, createP) + if err != nil { + err = errors.Wrapf(err, "unable to create forward with create params %s", createP.String()) + return + } + forwardId, _ = forward.GetString("id") + lastSeen, _ = forward.GetString("last_seen") + recycle = func() error { + _, err := cloudproxy.Forwards.Delete(session, forwardId, nil) + return err + } + port, _ = forward.Int("bind_port") + } + // wait forward last seen not empty + times, waitTime := 0, time.Second + var data jsonutils.JSONObject + for lastSeen == "" && times < 10 { + time.Sleep(waitTime) + times += 1 + waitTime += time.Second * time.Duration(times) + data, err = cloudproxy.Forwards.GetSpecific(session, forwardId, "lastseen", nil) + if err != nil { + err = errors.Wrapf(err, "unable to check last_seen for forward %s", forwardId) + return + } + log.Infof("data of last seen: %s", data) + lastSeen, _ = data.GetString("last_seen") + } + if lastSeen == "" { + err = errors.Wrapf(err, "last_seen of forward %s always is empty, something wrong", forwardId) + } + return +} + +func checkUrl(ctx context.Context, completeUrl string, expectedCode int, host *ansible_api.AnsibleHost) (bool, error) { + session := auth.GetAdminSession(ctx, "", "") + ahost := ansible.Host{} + ahost.Name = host.IP + ahost.Vars = map[string]string{ + "ansible_port": fmt.Sprintf("%d", host.Port), + "ansible_user": host.User, + } + mod := ansible.Module{ + Name: "uri", + Args: []string{ + fmt.Sprintf("url=%s", completeUrl), + "method=GET", + fmt.Sprintf("status_code=%d", expectedCode), + "validate_certs=no", + }, + } + + playbook := ansible.NewPlaybook() + playbook.Inventory = ansible.Inventory{ + Hosts: []ansible.Host{ + ahost, + }, + } + playbook.Modules = []ansible.Module{ + mod, + } + apCreateInput := ansible_api.AnsiblePlaybookCreateInput{ + Name: db.DefaultUUIDGenerator(), + Playbook: *playbook, + } + apb, err := modules.AnsiblePlaybooks.Create(session, apCreateInput.JSON(apCreateInput)) + if err != nil { + return false, errors.Wrap(err, "create ansible playbook") + } + id, _ := apb.GetString("id") + defer func() { + _, err := modules.AnsiblePlaybooks.Delete(session, id, nil) + if err != nil { + log.Errorf("unable to delete ansibleplaybook %s: %v", id, err) + } + }() + times, waitTimes := 0, time.Second + for times < 10 { + time.Sleep(waitTimes) + times++ + waitTimes += time.Second * time.Duration(times) + apd, err := modules.AnsiblePlaybooks.GetSpecific(session, id, "status", nil) + if err != nil { + return false, errors.Wrapf(err, "unable to get ansibleplaybook %s status", id) + } + status, _ := apd.GetString("status") + switch status { + case ansible_api.AnsiblePlaybookStatusInit, ansible_api.AnsiblePlaybookStatusRunning: + continue + case ansible_api.AnsiblePlaybookStatusFailed, ansible_api.AnsiblePlaybookStatusCanceled, ansible_api.AnsiblePlaybookStatusUnknown: + return false, nil + case ansible_api.AnsiblePlaybookStatusSucceeded: + return true, nil + } + } + return false, nil +} diff --git a/pkg/devtool/utils/ssh.go b/pkg/devtool/utils/ssh.go new file mode 100644 index 0000000000..0937950c25 --- /dev/null +++ b/pkg/devtool/utils/ssh.go @@ -0,0 +1,298 @@ +// 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" + "yunion.io/x/onecloud/pkg/mcclient/modules/cloudproxy" +) + +var ErrServerNotSshable = errors.Error("server is not sshable") + +type SSHable struct { + Ok bool + Reason string + + User string + Host string + Port int + + ServerName string + ServerHypervisor 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 +} + +func checkSshableForYunionCloud(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 + } + ips := strings.Split(serverDetail.IPs, ",") + ip := strings.TrimSpace(ips[0]) + if serverDetail.Hypervisor == comapi.HYPERVISOR_BAREMETAL || serverDetail.VpcId == "" || serverDetail.VpcId == comapi.DEFAULT_VPC_ID { + sshable = SSHable{ + Ok: true, + User: "cloudroot", + Host: ip, + Port: 22, + } + return + } + lfParams := jsonutils.NewDict() + lfParams.Set("proto", jsonutils.NewString("tcp")) + lfParams.Set("port", jsonutils.NewInt(22)) + data, err := modules.Servers.PerformAction(session, serverDetail.Id, "list-forward", lfParams) + if err != nil { + err = errors.Wrapf(err, "unable to List Forward for server %s", serverDetail.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, serverDetail.Id, "open-forward", lfParams) + if err != nil { + err = errors.Wrapf(err, "unable to Open Forward for server %s", serverDetail.Id) + return + } + clean = true + } else { + forward = forwards[0] + } + proxyAddr, _ := forward.GetString("proxy_addr") + proxyPort, _ := forward.Int("proxy_port") + // register + sshable = SSHable{ + Ok: true, + User: "cloudroot", + Host: proxyAddr, + Port: int(proxyPort), + } + return +} + +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 = checkSshableForYunionCloud(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 { + // create local forward + createP := jsonutils.NewDict() + createP.Set("type", jsonutils.NewString(cloudproxy_api.FORWARD_TYPE_LOCAL)) + createP.Set("remote_port", jsonutils.NewInt(22)) + 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 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 +} diff --git a/pkg/mcclient/modules/mod_devtools.go b/pkg/mcclient/modules/mod_devtools.go index 47efe8c752..4646e1b91f 100644 --- a/pkg/mcclient/modules/mod_devtools.go +++ b/pkg/mcclient/modules/mod_devtools.go @@ -23,6 +23,8 @@ var ( DevToolTemplates modulebase.ResourceManager DevToolScripts modulebase.ResourceManager DevToolScriptApplyRecords modulebase.ResourceManager + DevToolSshInfos modulebase.ResourceManager + DevToolServiceUrls modulebase.ResourceManager ) func init() { @@ -57,4 +59,20 @@ func init() { []string{}, ) registerCompute(&DevToolScriptApplyRecords) + + DevToolSshInfos = NewDevtoolManager( + "sshinfo", + "sshinfos", + []string{"Id", "Server_Id", "Server_Name", "Server_Hypervisor", "Forward_Id", "User", "Host", "Port", "Need_Clean", "Failed_Reason"}, + []string{}, + ) + registerCompute(&DevToolSshInfos) + + DevToolServiceUrls = NewDevtoolManager( + "serviceurl", + "serviceurls", + []string{"Id", "Service", "Server_Id", "Url", "Server_Ansible_Info", "Failed_Reason"}, + []string{}, + ) + registerCompute(&DevToolServiceUrls) } diff --git a/pkg/mcclient/options/devtool/serviceurl.go b/pkg/mcclient/options/devtool/serviceurl.go new file mode 100644 index 0000000000..b15a6fd6f2 --- /dev/null +++ b/pkg/mcclient/options/devtool/serviceurl.go @@ -0,0 +1,58 @@ +// 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 devtool + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +type ServiceUrlCreateOptions struct { + Service string `help:"service name"` + ServerId string `help:"server id"` + ServerAnsibleInfo SServerAnisbleInfo +} + +type SServerAnisbleInfo struct { + User string `json:"user"` + IP string `json:"ip"` + Port int `json:"port"` + Name string `json:"name"` +} + +func (so *ServiceUrlCreateOptions) Params() (jsonutils.JSONObject, error) { + return jsonutils.Marshal(so), nil +} + +type ServiceUrlListOptions struct { + options.BaseListOptions +} + +func (so *ServiceUrlListOptions) Params() (jsonutils.JSONObject, error) { + return options.ListStructToParams(so) +} + +type ServiceUrlOptions struct { + ID string `help:"id or name of sshinfo"` +} + +func (so *ServiceUrlOptions) GetId() string { + return so.ID +} + +func (so *ServiceUrlOptions) Params() (jsonutils.JSONObject, error) { + return nil, nil +} diff --git a/pkg/mcclient/options/devtool/sshinfo.go b/pkg/mcclient/options/devtool/sshinfo.go new file mode 100644 index 0000000000..46512ec0cc --- /dev/null +++ b/pkg/mcclient/options/devtool/sshinfo.go @@ -0,0 +1,51 @@ +// 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 devtool + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +type SshInfoCreateOptions struct { + ServerId string `help:"server id"` +} + +func (so *SshInfoCreateOptions) Params() (jsonutils.JSONObject, error) { + body := jsonutils.Marshal(so) + body.(*jsonutils.JSONDict).Set("generate_name", jsonutils.NewString(so.ServerId)) + return body, nil +} + +type SshInfoListOptions struct { + options.BaseListOptions +} + +func (so *SshInfoListOptions) Params() (jsonutils.JSONObject, error) { + return options.ListStructToParams(so) +} + +type SshInfoOptions struct { + ID string `help:"id or name of sshinfo"` +} + +func (so *SshInfoOptions) GetId() string { + return so.ID +} + +func (so *SshInfoOptions) Params() (jsonutils.JSONObject, error) { + return nil, nil +}