feat(devtool): add script

Script in devtool is a program or configuration that can be applied to the target host,
currently only supports ansible playbook
This commit is contained in:
rainzm
2021-01-09 17:01:28 +08:00
parent f7add55303
commit 1cc917e98a
18 changed files with 1599 additions and 3 deletions
+1
View File
@@ -23,6 +23,7 @@ import (
_ "yunion.io/x/onecloud/cmd/climc/shell/cloudnet"
_ "yunion.io/x/onecloud/cmd/climc/shell/cloudproxy"
_ "yunion.io/x/onecloud/cmd/climc/shell/compute"
_ "yunion.io/x/onecloud/cmd/climc/shell/devtool"
_ "yunion.io/x/onecloud/cmd/climc/shell/etcd"
_ "yunion.io/x/onecloud/cmd/climc/shell/events"
_ "yunion.io/x/onecloud/cmd/climc/shell/identity"
+27
View File
@@ -0,0 +1,27 @@
// 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/util/printutils"
)
var (
R = shell.R
printList = printutils.PrintJSONList
printObject = printutils.PrintJSONObject
printBatchResults = printutils.PrintJSONBatchResults
)
+222
View File
@@ -0,0 +1,222 @@
// 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 (
"fmt"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
"yunion.io/x/onecloud/pkg/mcclient/modules"
"yunion.io/x/onecloud/pkg/mcclient/options"
)
func paramValidator(param jsonutils.JSONObject) (bool, error) {
// TODO 移到 server 端
log.Infof("paramValidator: param: %+v", param)
interval, err := param.Int("interval")
if err != nil {
return true, nil
}
day, err := param.Int("day")
if err != nil {
return true, nil
}
if interval == 0 && day == 0 {
return false, fmt.Errorf("interval and day can not be 0 at the same time")
}
return true, nil
}
func init() {
type CronjobListOptions struct {
options.BaseListOptions
Name string `help:"cloud region ID or Name" json:"-"`
}
R(&CronjobListOptions{}, "devtoolcronjob-list", "List Devtool Cronjobs", func(s *mcclient.ClientSession, args *CronjobListOptions) error {
params, err := options.ListStructToParams(args)
if err != nil {
return err
}
var result *modulebase.ListResult
result, err = modules.DevToolCronjobs.List(s, params)
printList(result, modules.DevToolCronjobs.GetColumns(s))
return nil
})
type CronjobCreateOptions struct {
NAME string `help:"Ansible Playbook ID or Name" json:"-"`
Day int `help:"Cronjob runs at given day" default:"0"`
Hour int `help:"Cronjob runs at given hour" default:"0"`
Min int `help:"Cronjob runs at given min" default:"0"`
Sec int `help:"Cronjob runs at given sec" default:"0"`
Interval int64 `help:"Cronjob runs at given interval" default:"0"`
Start bool `help:"start job when created" default:"false"`
Enabled bool `help:"Set job status enabled" default:"false"`
}
R(
&CronjobCreateOptions{},
"devtoolcronjob-create",
"Create a cronjob repo component",
func(s *mcclient.ClientSession, args *CronjobCreateOptions) error {
result, err := modules.AnsiblePlaybooks.Get(s, args.NAME, nil)
if err != nil {
return err
}
ansiblePlaybookName, err := result.GetString("name")
if err != nil {
return err
}
ansiblePlaybookID, err := result.GetString("id")
if err != nil {
return err
}
params := jsonutils.NewDict()
params.Add(jsonutils.NewString(ansiblePlaybookName), "name")
params.Add(jsonutils.NewString(ansiblePlaybookID), "ansible_playbook_id")
if args.Start {
params.Add(jsonutils.JSONTrue, "start")
}
if args.Enabled {
params.Add(jsonutils.JSONTrue, "enabled")
} else if args.Interval > 0 {
params.Add(jsonutils.NewInt(int64(args.Interval)), "interval")
} else {
params.Add(jsonutils.NewInt(int64(0)), "interval")
params.Add(jsonutils.NewInt(int64(args.Day)), "day")
params.Add(jsonutils.NewInt(int64(args.Hour)), "hour")
params.Add(jsonutils.NewInt(int64(args.Min)), "min")
params.Add(jsonutils.NewInt(int64(args.Sec)), "sec")
}
ok, err := paramValidator(params)
if err != nil || !ok {
log.Infof("paramValidator error %s", err)
return err
}
cronjob, err := modules.DevToolCronjobs.Create(s, params)
if err != nil {
log.Errorf("modules.DevToolCronjobs.Create error %s", err)
return err
}
printObject(cronjob)
return nil
},
)
type DevToolCronjobShowOptions struct {
ID string `help:"ID or Name of the DevToolCronjob to show"`
}
R(&DevToolCronjobShowOptions{}, "devtoolcronjob-show", "Show cronjob details", func(s *mcclient.ClientSession, args *DevToolCronjobShowOptions) error {
result, err := modules.DevToolCronjobs.Get(s, args.ID, nil)
if err != nil {
return err
}
printObject(result)
return nil
})
type DevToolCronjobUpdateOptions struct {
ID string `help:"ID or Name of DevToolCronjob to update"`
Day int `help:"Cronjob runs at given day" json:"-" default:"-1"`
Hour int `help:"Cronjob runs at given hour" json:"-" default:"-1"`
Min int `help:"Cronjob runs at given min" default:"-1"`
Sec int `help:"Cronjob runs at given sec" default:"-1"`
Interval int `help:"Cronjob runs at given interval" default:"-1"`
Start bool `help:"start job when created"`
Stop bool `help:"start job when created"`
Enable bool `help:"Set job status enabled"`
Disable bool `help:"Set job status enabled"`
}
R(&DevToolCronjobUpdateOptions{}, "devtoolcronjob-update", "Update DevToolCronjob", func(s *mcclient.ClientSession, args *DevToolCronjobUpdateOptions) error {
result, err := modules.DevToolCronjobs.Get(s, args.ID, nil)
if err != nil {
return err
}
params := jsonutils.NewDict()
interval, _ := result.Int("interval")
day, _ := result.Int("day")
params.Add(jsonutils.NewString(args.ID), "id")
params.Add(jsonutils.NewInt(int64(interval)), "interval")
params.Add(jsonutils.NewInt(int64(day)), "day")
log.Infof("DevToolCronjobUpdateOptions args: %+v", args)
if args.Interval >= 0 {
params.Add(jsonutils.NewInt(int64(args.Interval)), "interval")
if args.Interval > 0 {
params.Add(jsonutils.NewInt(0), "day")
}
} else if args.Day >= 0 {
params.Add(jsonutils.NewInt(int64(args.Day)), "day")
if args.Day > 0 {
params.Add(jsonutils.NewInt(0), "interval")
}
}
if args.Hour >= 0 {
params.Add(jsonutils.NewInt(int64(args.Hour)), "hour")
}
if args.Min >= 0 {
params.Add(jsonutils.NewInt(int64(args.Min)), "min")
}
if args.Sec >= 0 {
params.Add(jsonutils.NewInt(int64(args.Sec)), "sec")
}
ok, err := paramValidator(params)
if err != nil || !ok {
return err
}
if args.Start && args.Stop {
return fmt.Errorf("can not set job start and stop at the same time")
} else if args.Start {
params.Add(jsonutils.JSONTrue, "start")
} else if args.Stop {
params.Add(jsonutils.JSONFalse, "start")
}
if args.Enable && args.Disable {
return fmt.Errorf("can not set job enabled and disabled at the same time")
} else if args.Enable {
params.Add(jsonutils.JSONTrue, "enabled")
} else if args.Disable {
params.Add(jsonutils.JSONFalse, "enabled")
}
result, err = modules.DevToolCronjobs.Update(s, args.ID, params)
if err != nil {
return err
}
printObject(result)
return nil
})
R(&DevToolCronjobShowOptions{}, "devtoolcronjob-delete", "Delete DevToolCronjob", func(s *mcclient.ClientSession, args *DevToolCronjobShowOptions) error {
result, err := modules.DevToolCronjobs.Delete(s, args.ID, nil)
if err != nil {
return err
}
printObject(result)
return nil
})
}
+154
View File
@@ -0,0 +1,154 @@
// 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/log"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
"yunion.io/x/onecloud/pkg/mcclient/modules"
"yunion.io/x/onecloud/pkg/mcclient/options"
)
func init() {
printAnsiblePlaybookObject := func(obj jsonutils.JSONObject) {
dict := obj.(*jsonutils.JSONDict)
pbJson, err := dict.Get("playbook")
if err != nil {
printObject(obj)
return
}
pbStr := pbJson.YAMLString()
dict.Set("playbook", jsonutils.NewString(pbStr))
printObject(obj)
}
type TemplateListOptions struct {
options.BaseListOptions
Name string `help:"cloud region ID or Name" json:"-"`
}
R(&TemplateListOptions{}, "devtooltemplate-list", "List Devtool Templates", func(s *mcclient.ClientSession, args *TemplateListOptions) error {
params, err := options.ListStructToParams(args)
if err != nil {
return err
}
var result *modulebase.ListResult
result, err = modules.DevToolTemplates.List(s, params)
printList(result, modules.DevToolTemplates.GetColumns(s))
return nil
})
R(
&options.DevtoolTemplateCreateOptions{},
"devtooltemplate-create",
"Create a template repo component",
func(s *mcclient.ClientSession, opts *options.DevtoolTemplateCreateOptions) error {
params, err := opts.Params()
if err != nil {
return err
}
log.Infof("devtool playbook create opts: %+v", params)
apb, err := modules.DevToolTemplates.Create(s, params)
if err != nil {
return err
}
printAnsiblePlaybookObject(apb)
return nil
},
)
R(
&options.DevtoolTemplateIdOptions{},
"devtooltemplate-show",
"Show devtool template",
func(s *mcclient.ClientSession, opts *options.DevtoolTemplateIdOptions) error {
apb, err := modules.DevToolTemplates.Get(s, opts.ID, nil)
if err != nil {
return err
}
printAnsiblePlaybookObject(apb)
return nil
},
)
R(
&options.DevtoolTemplateBindingOptions{},
"devtooltemplate-bind",
"Binding devtool template to a host/vm",
func(s *mcclient.ClientSession, opts *options.DevtoolTemplateBindingOptions) error {
params := jsonutils.NewDict()
params.Set("server_id", jsonutils.NewString(opts.ServerID))
apb, err := modules.DevToolTemplates.PerformAction(s, opts.ID, "bind", params)
if err != nil {
return err
}
printAnsiblePlaybookObject(apb)
return nil
},
)
R(
&options.DevtoolTemplateBindingOptions{},
"devtooltemplate-unbind",
"UnBinding devtool template to a host/vm",
func(s *mcclient.ClientSession, opts *options.DevtoolTemplateBindingOptions) error {
params := jsonutils.NewDict()
params.Set("server_id", jsonutils.NewString(opts.ServerID))
apb, err := modules.DevToolTemplates.PerformAction(s, opts.ID, "unbind", params)
if err != nil {
return err
}
printAnsiblePlaybookObject(apb)
return nil
},
)
R(
&options.DevtoolTemplateIdOptions{},
"devtooltemplate-delete",
"Delete devtool template",
func(s *mcclient.ClientSession, opts *options.DevtoolTemplateIdOptions) error {
apb, err := modules.DevToolTemplates.Delete(s, opts.ID, nil)
if err != nil {
return err
}
printAnsiblePlaybookObject(apb)
return nil
},
)
R(
&options.DevtoolTemplateUpdateOptions{},
"devtooltemplate-update",
"Update ansible playbook",
func(s *mcclient.ClientSession, opts *options.DevtoolTemplateUpdateOptions) error {
params, err := opts.Params()
if err != nil {
return err
}
apb, err := modules.DevToolTemplates.Update(s, opts.ID, params)
if err != nil {
return err
}
printAnsiblePlaybookObject(apb)
return nil
},
)
}
+29
View File
@@ -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.DevToolScripts).WithKeyword("devtool-script")
cmd.List(new(options.ScriptListOptions))
cmd.Perform("apply", new(options.ScriptApplyOptions))
cmd1 := shell.NewResourceCmd(&modules.DevToolScriptApplyRecords).WithKeyword("devtool-script-record")
cmd1.List(new(options.ScriptApplyRecordListOptions))
}
+15
View File
@@ -0,0 +1,15 @@
// 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/pkg/apis/devtool"
+67
View File
@@ -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 devtool
import "yunion.io/x/onecloud/pkg/apis"
type ScriptApplyInput struct {
// description: server id
// required: true
// example: b48c5c84-9952-4394-8ca9-c3b84e946a03
ServerID string
// description: whether to use eip first
// example: true
EipFirst bool
// description: Id of proxyEndpoint
// example: cf1d1a0f-9b9d-4629-8036-af3ed87c0821
ProxyEndpointId string
// description: whether to automatically select proxy endpoint
AutoChooseProxyEndpoint bool
}
type ScriptApplyOutput struct {
// description: Instantiation of script apply
// example: cf1d1a0f-9b9d-4629-8036-af3ed87c0821
ScriptApplyId string
}
type ScriptApplyRecoredListInput struct {
apis.StatusStandaloneResourceListInput
// description: Id of Script
// example: cc2e2ba6-e33d-4be3-8e2d-4d2aa843dd03
ScriptId string
}
type ScriptCreateInput struct {
apis.SharableVirtualResourceCreateInput
// description: Id or Name of ansible playbook reference
// example: cf1d1a0f-9b9d-4629-8036-af3ed87c0821
PlaybookReference string
// description: The script may fail to execute, MaxTryTime represents the maximum number of attempts to execute
MaxTryTimes int
}
type ScriptDetails struct {
apis.SharableVirtualResourceDetails
SScript
ApplyInfos []SApplyInfo
}
type SApplyInfo struct {
ServerId string
EipFirst bool
ProxyEndpointId string
TryTimes int
}
+30
View File
@@ -0,0 +1,30 @@
// 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 (
SCRIPT_APPLY_STATUS_APPLYING = "applying"
SCRIPT_APPLY_STATUS_APPLY_FAILED = "apply_failed"
SCRIPT_APPLY_STATUS_READY = "ready"
SCRIPT_APPLY_RECORD_APPLYING = "applying"
SCRIPT_APPLY_RECORD_SUCCEED = "succeed"
SCRIPT_APPLY_RECORD_FAILED = "failed"
SCRIPT_NAME = "monitor agent"
SERVICE_TYPE = "devtool"
SCRIPT_STATUS_READY = "ready"
)
+78
View File
@@ -0,0 +1,78 @@
// 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.
// Code generated by model-api-gen. DO NOT EDIT.
package devtool
import (
time "time"
"yunion.io/x/onecloud/pkg/apis"
ansible "yunion.io/x/onecloud/pkg/util/ansible"
)
// SCronjob is an autogenerated struct via yunion.io/x/onecloud/pkg/devtool/models.SCronjob.
type SCronjob struct {
SVSCronjob
AnsiblePlaybookID string `json:"ansible_playbook_id"`
TemplateID string `json:"template_id"`
ServerID string `json:"server_id"`
apis.SVirtualResourceBase
}
// SDevtoolTemplate is an autogenerated struct via yunion.io/x/onecloud/pkg/devtool/models.SDevtoolTemplate.
type SDevtoolTemplate struct {
SVSCronjob
Playbook *ansible.Playbook `json:"playbook"`
apis.SVirtualResourceBase
}
// SScript is an autogenerated struct via yunion.io/x/onecloud/pkg/devtool/models.SScript.
type SScript struct {
apis.SVirtualResourceBase
// remote
Type string `json:"type"`
PlaybookReference string `json:"playbook_reference"`
MaxTryTimes int `json:"max_try_times"`
}
// SScriptApply is an autogenerated struct via yunion.io/x/onecloud/pkg/devtool/models.SScriptApply.
type SScriptApply struct {
apis.SStatusStandaloneResourceBase
ScriptId string `json:"script_id"`
GuestId string `json:"guest_id"`
EipFirst *bool `json:"eip_first,omitempty"`
ProxyEndpointId string `json:"proxy_endpoint_id"`
TryTimes int `json:"try_times"`
}
// SScriptApplyRecord is an autogenerated struct via yunion.io/x/onecloud/pkg/devtool/models.SScriptApplyRecord.
type SScriptApplyRecord struct {
apis.SStatusStandaloneResourceBase
ScriptId string `json:"script_id"`
ServerId string `json:"server_id"`
StartTime time.Time `json:"start_time"`
EndTime time.Time `json:"end_time"`
Reason string `json:"reason"`
}
// SVSCronjob is an autogenerated struct via yunion.io/x/onecloud/pkg/devtool/models.SVSCronjob.
type SVSCronjob struct {
Day int `json:"day"`
Hour int `json:"hour"`
Min int `json:"min"`
Sec int `json:"sec"`
Interval int64 `json:"interval"`
Start bool `json:"start"`
Enabled bool `json:"enabled"`
}
+345
View File
@@ -0,0 +1,345 @@
// 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"
"fmt"
"net/url"
"sync"
"github.com/coredns/coredns/plugin/pkg/log"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/sets"
proxy_api "yunion.io/x/onecloud/pkg/apis/cloudproxy"
comapi "yunion.io/x/onecloud/pkg/apis/compute"
api "yunion.io/x/onecloud/pkg/apis/devtool"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/httperrors"
"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"
"yunion.io/x/onecloud/pkg/util/httputils"
"yunion.io/x/onecloud/pkg/util/stringutils2"
)
type SScript struct {
db.SSharableVirtualResourceBase
// remote
Type string `width:"16" nullable:"false"`
PlaybookReferenceId string `width:"128" nullable:"false"`
MaxTryTimes int `default:"1"`
}
type SScriptManager struct {
db.SSharableVirtualResourceBaseManager
}
var ScriptManager *SScriptManager
func init() {
ScriptManager = &SScriptManager{
SSharableVirtualResourceBaseManager: db.NewSharableVirtualResourceBaseManager(
SScript{},
"script_tbl",
"script",
"scripts",
),
}
ScriptManager.SetVirtualObject(ScriptManager)
registerArgGenerator(MonitorAgent, getArgs)
}
type argGenerator func(ctx context.Context, input api.ScriptApplyInput, details *comapi.ServerDetails) (map[string]interface{}, error)
var argGenerators = &sync.Map{}
func registerArgGenerator(name string, ag argGenerator) {
argGenerators.Store(name, ag)
}
func getArgGenerator(name string) (argGenerator, bool) {
v, ok := argGenerators.Load(name)
if !ok {
return nil, ok
}
return v.(argGenerator), ok
}
func convertInfluxdbUrl(ctx context.Context, pUrl string, endpointId string) (string, 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 "", errors.Wrap(err, "failed to list forward")
}
var port int64
if len(lr.Data) > 0 {
port, _ = lr.Data[0].Int("bind_port")
} else {
rUrl, err := url.Parse(pUrl)
if err != nil {
return "", errors.Wrap(err, "invalid influxdbUrl?")
}
// 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))
forward, err := cloudproxy.Forwards.Create(session, createP)
if err != nil {
return "", errors.Wrapf(err, "unable to create forward with create params %s", createP.String())
}
port, _ = forward.Int("bind_port")
}
// fetch proxy_endpoint address
ep, err := cloudproxy.ProxyEndpoints.Get(session, endpointId, nil)
if err != nil {
return "", errors.Wrapf(err, "unable to get proxy endpoint %s", endpointId)
}
address, _ := ep.GetString("intranet_ip_addr")
return fmt.Sprintf("https://%s:%d", address, port), nil
}
func getArgs(ctx context.Context, input api.ScriptApplyInput, detail *comapi.ServerDetails) (map[string]interface{}, error) {
influxdbUrl, err := getInfluxdbUrl(ctx)
if err != nil {
return nil, errors.Wrap(err, "unable to get influxdbUrl")
}
// convert influxdbUrl
if len(input.ProxyEndpointId) > 0 {
influxdbUrl, err = convertInfluxdbUrl(ctx, influxdbUrl, input.ProxyEndpointId)
if err != nil {
return nil, errors.Wrapf(err, "unable to convertInfluxdbUrl %s", influxdbUrl)
}
}
vmId := detail.Id
tenantId := detail.ProjectId
domainId := detail.DomainId
ret := map[string]interface{}{
"influxdb_url": influxdbUrl,
"influxdb_name": "telegraf",
"onecloud_vm_id": vmId,
"onecloud_tenant_id": tenantId,
"onecloud_domain_id": domainId,
}
return ret, 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 MonitorAgent = "monitor agent"
func (sm *SScriptManager) InitializeData() error {
q := sm.Query().Equals("playbook_reference", MonitorAgent)
n, err := q.CountWithError()
if err != nil {
return err
}
if n > 0 {
return nil
}
s := SScript{
PlaybookReferenceId: MonitorAgent,
}
s.ProjectId = "system"
s.IsPublic = true
s.PublicScope = "system"
err = sm.TableSpec().Insert(context.Background(), &s)
if err != nil {
return err
}
return nil
}
func (sm *SScriptManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.ScriptCreateInput) (api.ScriptCreateInput, error) {
// check ansible playbook reference
session := auth.GetSessionWithInternal(ctx, userCred, "", "")
pr, err := modules.AnsiblePlaybookReference.Get(session, input.PlaybookReference, nil)
if err != nil {
return input, errors.Wrapf(err, "unable to get AnsiblePlaybookReference %q", input.PlaybookReference)
}
id, _ := pr.GetString("id")
input.PlaybookReference = id
return input, nil
}
func (s *SScript) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
s.Status = api.SCRIPT_STATUS_READY
s.PlaybookReferenceId, _ = data.GetString("playbook_reference")
return nil
}
func (sm *SScriptManager) FetchCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, objs []interface{}, fields stringutils2.SSortedStrings, isList bool) []api.ScriptDetails {
vDetails := sm.SSharableVirtualResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
details := make([]api.ScriptDetails, len(objs))
for i := range details {
details[i].SharableVirtualResourceDetails = vDetails[i]
script := objs[i].(*SScript)
ais, err := script.ApplyInfos()
if err != nil {
log.Errorf("unable to get ApplyInfos of script %s: %v", script.Id, err)
}
details[i].ApplyInfos = ais
}
return details
}
func (s *SScript) ApplyInfos() ([]api.SApplyInfo, error) {
q := ScriptApplyManager.Query().Equals("script_id", s.Id)
var sa []SScriptApply
err := db.FetchModelObjects(ScriptApplyManager, q, &sa)
if err != nil {
return nil, err
}
ai := make([]api.SApplyInfo, len(sa))
for i := range ai {
ai[i].ServerId = sa[i].GuestId
ai[i].EipFirst = sa[i].EipFirst.Bool()
ai[i].ProxyEndpointId = sa[i].ProxyEndpointId
ai[i].TryTimes = sa[i].TryTimes
}
return ai, nil
}
func (s *SScript) AllowPerformApply(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool {
return s.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, s, "apply")
}
func (s *SScript) PerformApply(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ScriptApplyInput) (api.ScriptApplyOutput, error) {
output := api.ScriptApplyOutput{}
serverInfo, err := s.checkServer(ctx, userCred, input.ServerID)
if err != nil {
return output, err
}
// select proxyEndpoint automatically
if len(input.ProxyEndpointId) == 0 && input.AutoChooseProxyEndpoint {
var proxyEndpointId string
// find suitable proxyEndpoint
// network first
session := auth.GetAdminSession(ctx, "", "")
for _, netId := range serverInfo.NetworkIds {
filter := jsonutils.NewDict()
filter.Set("network_id", jsonutils.NewString(netId))
lr, err := cloudproxy.ProxyEndpoints.List(session, filter)
if err != nil {
return output, errors.Wrapf(err, "unable to list proxy endpoint in network %q", netId)
}
if len(lr.Data) == 0 {
continue
}
proxyEndpointId, _ = lr.Data[0].GetString("id")
break
}
if len(proxyEndpointId) == 0 {
filter := jsonutils.NewDict()
filter.Set("vpc_id", jsonutils.NewString(serverInfo.VpcId))
lr, err := cloudproxy.ProxyEndpoints.List(session, filter)
if err != nil {
return output, errors.Wrapf(err, "unable to list proxy endpoint in vpc %q", serverInfo.VpcId)
}
if len(lr.Data) > 0 {
// TODO Choose strictly
proxyEndpointId, _ = lr.Data[0].GetString("id")
}
}
if len(proxyEndpointId) == 0 {
return output, httperrors.NewInputParameterError("can't find suitable proxy endpoint for server %s, please connect with admin to create one", serverInfo.serverDetails.Name)
}
input.ProxyEndpointId = proxyEndpointId
}
ag, _ := getArgGenerator(MonitorAgent)
args, err := ag(ctx, input, serverInfo.serverDetails)
if err != nil {
return output, errors.Wrapf(err, "unable to get args of server %s", serverInfo.ServerId)
}
sa, err := ScriptApplyManager.createScriptApply(ctx, s.Id, serverInfo.ServerId, input.ProxyEndpointId, input.EipFirst, args)
if err != nil {
return output, errors.Wrapf(err, "unable to apply script to server %s", serverInfo.ServerId)
}
err = sa.StartApply(ctx, userCred)
if err != nil {
return output, errors.Wrapf(err, "unable to apply script to server %s", serverInfo.ServerId)
}
output.ScriptApplyId = sa.Id
return output, nil
}
type sServerInfo struct {
ServerId string
VpcId string
NetworkIds []string
serverDetails *comapi.ServerDetails
}
func (s *SScript) checkServer(ctx context.Context, userCred mcclient.TokenCredential, serverId string) (sServerInfo, error) {
session := auth.GetSessionWithInternal(ctx, userCred, "", "")
// check server
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
}
+178
View File
@@ -0,0 +1,178 @@
// 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"
"fmt"
"sync"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/tristate"
"yunion.io/x/pkg/util/sets"
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 SScriptApply struct {
db.SStatusStandaloneResourceBase
ScriptId string `width:"36" nullable:"false" index:"true"`
GuestId string `width:"36" nullable:"false" index:"true"`
EipFirst tristate.TriState
//
Args jsonutils.JSONObject
ProxyEndpointId string `width:"36" nullable:"false"`
TryTimes int
}
type SScriptApplyManager struct {
db.SStatusStandaloneResourceBaseManager
Session *sScriptApplySession
}
var ScriptApplyManager *SScriptApplyManager
func init() {
ScriptApplyManager = &SScriptApplyManager{
SStatusStandaloneResourceBaseManager: db.NewStatusStandaloneResourceBaseManager(
SScriptApply{},
"scriptapply_tbl",
"scriptapply",
"scirptapplys",
),
Session: newScriptApplySession(),
}
ScriptApplyManager.SetVirtualObject(ScriptApplyManager)
}
func (sam *SScriptApplyManager) createScriptApply(ctx context.Context, scriptId, guestId, proxyEndpointId string, eipFirst bool, args map[string]interface{}) (*SScriptApply, error) {
sa := &SScriptApply{
ScriptId: scriptId,
GuestId: guestId,
EipFirst: tristate.NewFromBool(eipFirst),
ProxyEndpointId: proxyEndpointId,
Args: jsonutils.Marshal(args),
}
err := ScriptApplyManager.TableSpec().Insert(ctx, sa)
sa.SetModelManager(ScriptApplyManager, sa)
return sa, err
}
func (sa *SScriptApply) StartApply(ctx context.Context, userCred mcclient.TokenCredential) (err error) {
if ok := ScriptApplyManager.Session.CheckAndSet(sa.Id); !ok {
return fmt.Errorf("script %s is applying to server %s", sa.ScriptId, sa.GuestId)
}
defer func() {
if err != nil {
ScriptApplyManager.Session.Remove(sa.Id)
}
}()
// check try times
script, err := sa.Script()
if err != nil {
return err
}
if sa.TryTimes >= script.MaxTryTimes {
return fmt.Errorf("The times to try has exceeded the maximum times %d setted by the script", script.MaxTryTimes)
}
_, err = db.Update(sa, func() error {
sa.TryTimes += 1
sa.Status = api.SCRIPT_APPLY_STATUS_APPLYING
return nil
})
if err != nil {
return errors.Wrap(err, "unable to update scriptapply")
}
err = sa.startApplyScriptTask(ctx, userCred, "")
if err != nil {
f := false
_, err = ScriptApplyRecordManager.createRecordWithResult(ctx, sa.ScriptId, sa.GuestId, &f, fmt.Sprintf("unabel to start ApplyScriptTask: %v", err))
if err != nil {
return errors.Wrap(err, "unable to record")
}
ScriptApplyManager.Session.Remove(sa.Id)
}
return nil
}
func (sa *SScriptApply) startApplyScriptTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
task, err := taskman.TaskManager.NewTask(ctx, "ApplyScriptTask", sa, userCred, nil, "", parentTaskId)
if err != nil {
return err
}
task.ScheduleRun(nil)
return nil
}
func (sa *SScriptApply) StopApply(userCred mcclient.TokenCredential, record *SScriptApplyRecord, success bool, reason string) error {
var status string
if success {
status = api.SCRIPT_APPLY_STATUS_READY
if record != nil {
record.Succeed(reason)
}
} else {
status = api.SCRIPT_APPLY_RECORD_FAILED
if record != nil {
record.Fail(reason)
}
}
sa.SetStatus(userCred, status, "")
ScriptApplyManager.Session.Remove(sa.Id)
return nil
}
func (sa *SScriptApply) Script() (*SScript, error) {
obj, err := ScriptManager.FetchById(sa.ScriptId)
if err != nil {
return nil, errors.Wrapf(err, "unable to fetch Script %s", sa.Id)
}
s := obj.(*SScript)
s.SetModelManager(ScriptManager, s)
return s, nil
}
type sScriptApplySession struct {
mux *sync.Mutex
applyingOnes sets.String
}
func newScriptApplySession() *sScriptApplySession {
return &sScriptApplySession{
mux: &sync.Mutex{},
applyingOnes: sets.NewString(),
}
}
func (sas *sScriptApplySession) CheckAndSet(id string) bool {
sas.mux.Lock()
defer sas.mux.Unlock()
if sas.applyingOnes.Has(id) {
return false
}
sas.applyingOnes.Insert(id)
return true
}
func (sas *sScriptApplySession) Remove(id string) {
sas.mux.Lock()
defer sas.mux.Unlock()
sas.applyingOnes.Delete(id)
}
+143
View File
@@ -0,0 +1,143 @@
// 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"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/sqlchemy"
api "yunion.io/x/onecloud/pkg/apis/devtool"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/rbacutils"
)
type SScriptApplyRecord struct {
db.SStatusStandaloneResourceBase
ScriptId string `width:"36" charset:"ascii" nullable:"true" list:"user" index:"true"`
ServerId string `width:"36" charset:"ascii" nullable:"true" list:"user"`
StartTime time.Time `list:"user"`
EndTime time.Time `list:"user"`
Reason string `list:"user"`
}
type SScriptApplyRecordManager struct {
db.SStatusStandaloneResourceBaseManager
}
var ScriptApplyRecordManager *SScriptApplyRecordManager
func init() {
ScriptApplyRecordManager = &SScriptApplyRecordManager{
SStatusStandaloneResourceBaseManager: db.NewStatusStandaloneResourceBaseManager(
SScriptApplyRecord{},
"scriptapplyrecord_tbl",
"scriptapplyrecord",
"scriptapplyrecords",
),
}
ScriptApplyRecordManager.SetVirtualObject(ScriptApplyRecordManager)
}
func (sarm *SScriptApplyRecordManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input api.ScriptApplyRecoredListInput) (*sqlchemy.SQuery, error) {
q, err := sarm.SStatusStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, input.StatusStandaloneResourceListInput)
if err != nil {
return q, err
}
if len(input.ScriptId) > 0 {
q = q.Equals("script_id", input.ScriptId)
}
return q, nil
}
func (sarm *SScriptApplyRecordManager) CreateRecord(ctx context.Context, scriptId, serverId string) (*SScriptApplyRecord, error) {
return sarm.createRecordWithResult(ctx, scriptId, serverId, nil, "")
}
func (sarm *SScriptApplyRecordManager) createRecordWithResult(ctx context.Context, scriptId, serverId string, success *bool, reason string) (*SScriptApplyRecord, error) {
now := time.Now()
sar := &SScriptApplyRecord{
StartTime: now,
ScriptId: scriptId,
ServerId: serverId,
}
if success == nil {
sar.Status = api.SCRIPT_APPLY_RECORD_APPLYING
} else if *success {
sar.Status = api.SCRIPT_APPLY_RECORD_SUCCEED
} else if !*success {
sar.Status = api.SCRIPT_APPLY_RECORD_FAILED
}
sar.Reason = reason
err := sarm.TableSpec().Insert(ctx, sar)
if err != nil {
return nil, err
}
sar.SetModelManager(sarm, sar)
return sar, nil
}
func (sarm *SScriptApplyRecordManager) NamespaceScope() rbacutils.TRbacScope {
return rbacutils.ScopeProject
}
func (sarm *SScriptApplyRecordManager) ResourceScope() rbacutils.TRbacScope {
return rbacutils.ScopeProject
}
func (sarm *SScriptApplyRecordManager) FileterByOwner(q *sqlchemy.SQuery, owner mcclient.IIdentityProvider, scope rbacutils.TRbacScope) *sqlchemy.SQuery {
if owner != nil {
switch scope {
case rbacutils.ScopeProject, rbacutils.ScopeDomain:
scriptQ := ScriptManager.Query("id", "domain_id").SubQuery()
q = q.Join(scriptQ, sqlchemy.Equals(q.Field("script_id"), scriptQ.Field("id")))
q = q.Filter(sqlchemy.Equals(scriptQ.Field("domain_id"), owner.GetProjectDomainId()))
}
}
return q
}
func (sarm *SScriptApplyRecordManager) FetchOwnerId(ctx context.Context, data jsonutils.JSONObject) (mcclient.IIdentityProvider, error) {
return db.FetchDomainInfo(ctx, data)
}
func (sar *SScriptApplyRecord) GetOwnerId() mcclient.IIdentityProvider {
obj, _ := ScriptManager.FetchById(sar.ScriptId)
if obj == nil {
return nil
}
return obj.GetOwnerId()
}
func (sar *SScriptApplyRecord) SetResult(status, reason string) error {
_, err := db.Update(sar, func() error {
sar.Status = status
sar.Reason = reason
sar.EndTime = time.Now()
return nil
})
return err
}
func (sar *SScriptApplyRecord) Fail(reason string) error {
return sar.SetResult(api.SCRIPT_APPLY_RECORD_FAILED, reason)
}
func (sar *SScriptApplyRecord) Succeed(reason string) error {
return sar.SetResult(api.SCRIPT_APPLY_RECORD_SUCCEED, reason)
}
+4
View File
@@ -30,6 +30,7 @@ func InitHandlers(app *appsrv.Application) {
taskman.TaskManager,
taskman.SubTaskManager,
taskman.TaskObjectManager,
db.SharedResourceManager,
db.UserCacheManager,
db.TenantCacheManager,
} {
@@ -42,6 +43,9 @@ func InitHandlers(app *appsrv.Application) {
models.CronjobManager,
models.DevtoolTemplateManager,
models.ScriptManager,
models.ScriptApplyManager,
models.ScriptApplyRecordManager,
} {
db.RegisterModelManager(manager)
handler := db.NewModelHandler(manager)
+2 -1
View File
@@ -21,6 +21,7 @@ import (
"yunion.io/x/log"
api "yunion.io/x/onecloud/pkg/apis/devtool"
"yunion.io/x/onecloud/pkg/cloudcommon"
app_common "yunion.io/x/onecloud/pkg/cloudcommon/app"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
@@ -36,7 +37,7 @@ func StartService() {
commonOpts := &opts.CommonOptions
dbOpts := &options.Options.DBOptions
baseOpts := &opts.BaseOptions
common_options.ParseOptions(opts, os.Args, "devtool.conf", "devtool")
common_options.ParseOptions(opts, os.Args, "devtool.conf", api.SERVICE_TYPE)
app_common.InitAuth(commonOpts, func() {
log.Infof("Auth complete!!")
+218
View File
@@ -0,0 +1,218 @@
// 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"
"net"
"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"
"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/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 {
taskman.STask
}
func init() {
taskman.RegisterTask(ApplyScriptTask{})
}
func (self *ApplyScriptTask) taskFailed(ctx context.Context, sa *models.SScriptApply, sar *models.SScriptApplyRecord, err error) {
err = sa.StopApply(self.UserCred, sar, false, err.Error())
if err != nil {
log.Errorf("unable to StopApply script %s to server %s", sa.ScriptId, sa.GuestId)
self.SetStageFailed(ctx, jsonutils.NewString(err.Error()))
return
}
// restart
err = sa.StartApply(ctx, self.UserCred)
if err != nil {
log.Errorf("unable to StartApply script %s to server %s", sa.ScriptId, sa.GuestId)
}
self.SetStageFailed(ctx, jsonutils.NewString(err.Error()))
}
func (self *ApplyScriptTask) taskSuccess(ctx context.Context, sa *models.SScriptApply, sar *models.SScriptApplyRecord) {
err := sa.StopApply(self.UserCred, sar, true, "")
if err != nil {
log.Errorf("unable to StopApply script %s to server %s", sa.ScriptId, sa.GuestId)
self.SetStageComplete(ctx, nil)
}
}
func (self *ApplyScriptTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) {
sa := obj.(*models.SScriptApply)
// create record
sar, err := models.ScriptApplyRecordManager.CreateRecord(ctx, sa.ScriptId, sa.GuestId)
if err != nil {
self.taskFailed(ctx, sa, nil, err)
return
}
s, err := sa.Script()
if err != nil {
self.taskFailed(ctx, sa, sar, err)
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
}
// make sure user
var user string
if serverDetail.Hypervisor == comapi.HYPERVISOR_KVM {
user = "root"
} else {
user = "cloudroot"
}
// 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
}
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.clearLocalForward(session, forwardId)
self.taskFailed(ctx, sa, sar, errors.Wrapf(err, "fail to get proxy agent %q", agentId))
return
}
address, _ := agent.GetString("advertise_addr")
host := ansible_api.AnsibleHost{
User: user,
IP: address,
Port: int(port),
Name: serverDetail.Name,
}
params = jsonutils.NewDict()
params.Set("args", sa.Args)
params.Set("host", jsonutils.Marshal(host))
// 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))
// check proxy forward
if ok := self.ensureLocalForwardWork(address, int(port)); !ok {
self.clearLocalForward(session, forwardId)
self.taskFailed(ctx, sa, sar, errors.Wrapf(err, "The created local forward is actually not usable"))
return
}
self.SetStage("OnAnsiblePlaybookComplete", updateData)
// Inject Task Header
session.Header = self.GetTaskRequestHeader()
_, err = modules.AnsiblePlaybookReference.PerformAction(session, s.PlaybookReferenceId, "run", params)
if err != nil {
self.clearLocalForward(session, forwardId)
self.taskFailed(ctx, sa, sar, errors.Wrapf(err, "can't run ansible playbook reference %s", s.PlaybookReferenceId))
return
}
}
func (self *ApplyScriptTask) clearLocalForward(s *mcclient.ClientSession, forwardId string) {
_, 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
}
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])
}
return a
}
func (self *ApplyScriptTask) OnAnsiblePlaybookComplete(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) {
// try to delete local forward
session := auth.GetAdminSession(ctx, "", "")
forwardId, _ := self.Params.GetString("proxy_forward_id")
self.clearLocalForward(session, forwardId)
sa := obj.(*models.SScriptApply)
sarId, _ := self.Params.GetString("script_apply_record_id")
osar, err := models.ScriptApplyRecordManager.FetchById(sarId)
if err != nil {
log.Errorf("unable to fetch script apply record %s", sarId)
self.taskSuccess(ctx, sa, nil)
}
self.taskSuccess(ctx, sa, osar.(*models.SScriptApplyRecord))
}
func (self *ApplyScriptTask) OnAnsiblePlaybookCompleteFailed(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) {
// try to delete local forward
session := auth.GetAdminSession(ctx, "", "")
forwardId, _ := self.Params.GetString("proxy_forward_id")
_, err := cloudproxy.Forwards.Delete(session, forwardId, nil)
if err != nil {
log.Errorf("unable to delete proxy forward %s", forwardId)
}
sa := obj.(*models.SScriptApply)
sarId, _ := self.Params.GetString("script_apply_record_id")
osar, err := models.ScriptApplyRecordManager.FetchById(sarId)
if err != nil {
log.Errorf("unable to fetch script apply record %s", sarId)
self.taskSuccess(ctx, sa, nil)
}
self.taskFailed(ctx, sa, osar.(*models.SScriptApplyRecord), errors.Error(body.String()))
}
+19 -2
View File
@@ -19,8 +19,10 @@ import (
)
var (
DevToolCronjobs modulebase.ResourceManager
DevToolTemplates modulebase.ResourceManager
DevToolCronjobs modulebase.ResourceManager
DevToolTemplates modulebase.ResourceManager
DevToolScripts modulebase.ResourceManager
DevToolScriptApplyRecords modulebase.ResourceManager
)
func init() {
@@ -40,4 +42,19 @@ func init() {
[]string{"is_system"},
)
registerCompute(&DevToolTemplates)
DevToolScripts = NewDevtoolManager(
"script",
"scripts",
[]string{"Id", "Name", "Type", "Playbook_Reference", "Max_Try_Times"},
[]string{},
)
registerCompute(&DevToolScripts)
DevToolScriptApplyRecords = NewDevtoolManager(
"scriptapplyrecord",
"scriptapplyrecords",
[]string{"Script_Id", "Server_Id", "Start_Time", "End_Time", "Reason", "Status"},
[]string{},
)
registerCompute(&DevToolScriptApplyRecords)
}
+1
View File
@@ -0,0 +1 @@
package devtool // import "yunion.io/x/onecloud/pkg/mcclient/options/devtool"
+66
View File
@@ -0,0 +1,66 @@
// 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 ScriptListOptions struct {
options.BaseListOptions
}
func (so *ScriptListOptions) Params() (jsonutils.JSONObject, error) {
return options.ListStructToParams(so)
}
type ScriptOptions struct {
ID string `help:"id or name of script"`
}
func (so *ScriptOptions) GetId() string {
return so.ID
}
func (so *ScriptOptions) Params() (jsonutils.JSONObject, error) {
return nil, nil
}
type SscriptApplyOptions struct {
SERVERID string `help:"server id" json:"server_id"`
EipFirst bool `help:"whether to use eip first"`
ProxyEndpointId string `help:"proxy endpoint id"`
AutoChooseProxyEndpoint bool `help:"automatically choose proxy endpoint"`
}
type ScriptApplyOptions struct {
ScriptOptions
SscriptApplyOptions
}
func (so *ScriptApplyOptions) Params() (jsonutils.JSONObject, error) {
return jsonutils.Marshal(so.SscriptApplyOptions), nil
}
type ScriptApplyRecordListOptions struct {
options.BaseListOptions
ScriptId string
}
func (so *ScriptApplyRecordListOptions) Params() (jsonutils.JSONObject, error) {
return options.ListStructToParams(so)
}