mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-05-08 14:37:55 +08:00
Script in devtool is a program or configuration that can be applied to the target host, currently only supports ansible playbook
346 lines
11 KiB
Go
346 lines
11 KiB
Go
// Copyright 2019 Yunion
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package 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
|
|
}
|