| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444 |
- // 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"
- "path/filepath"
- "reflect"
- "strings"
- "github.com/pkg/errors"
- "yunion.io/x/jsonutils"
- "yunion.io/x/pkg/gotypes"
- "yunion.io/x/pkg/util/regutils"
- "yunion.io/x/pkg/utils"
- compute_apis "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"
- "yunion.io/x/onecloud/pkg/mcclient/auth"
- ansible_model "yunion.io/x/onecloud/pkg/mcclient/models"
- ansible_modules "yunion.io/x/onecloud/pkg/mcclient/modules/ansible"
- compute_modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
- "yunion.io/x/onecloud/pkg/util/ansible"
- )
- type SLoadbalancerAgentDeployment struct {
- Host string
- AnsiblePlaybook string
- AnsiblePlaybookUndeployment string
- }
- func (p *SLoadbalancerAgentDeployment) String() string {
- return jsonutils.Marshal(p).String()
- }
- func (p *SLoadbalancerAgentDeployment) IsZero() bool {
- if *p == (SLoadbalancerAgentDeployment{}) {
- return true
- }
- return false
- }
- func (lbagent *SLoadbalancerAgent) deploy(ctx context.Context, userCred mcclient.TokenCredential, input *compute_apis.LoadbalancerAgentDeployInput) (*ansible.Playbook, error) {
- pb := &ansible.Playbook{
- Inventory: ansible.Inventory{
- Hosts: []ansible.Host{input.Host},
- },
- Modules: []ansible.Module{
- {
- Name: "group",
- Args: []string{
- "name=yunion",
- "state=present",
- },
- },
- {
- Name: "user",
- Args: []string{
- "name=yunion",
- "state=present",
- "group=yunion",
- },
- },
- {
- Name: "file",
- Args: []string{
- "path=/etc/yunion",
- "state=directory",
- "owner=yunion",
- "group=yunion",
- "mode=755",
- },
- },
- {
- Name: "template",
- Args: []string{
- "src=lbagentConfTmpl",
- "dest=/etc/yunion/lbagent.conf",
- "owner=yunion",
- "group=yunion",
- "mode=600",
- },
- },
- },
- Files: map[string][]byte{
- "lbagentConfTmpl": []byte(lbagentConfTmpl),
- },
- }
- switch input.DeployMethod {
- case compute_apis.DeployMethodYum:
- fallthrough
- default:
- if v, ok := input.Host.GetVar("repo_base_url"); !ok || v == "" {
- return nil, httperrors.NewBadRequestError("use yum requires valid repo_base_url")
- }
- if v, ok := input.Host.GetVar("repo_sslverify"); !ok || v == "" {
- input.Host.SetVar("repo_sslverify", "0")
- }
- pb.Files["yunionRepoTmpl"] = []byte(yunionRepoTmpl)
- pb.Modules = append(pb.Modules,
- ansible.Module{
- Name: "template",
- Args: []string{
- "src=yunionRepoTmpl",
- "dest=/etc/yum.repos.d/yunion.repo",
- "owner=root",
- "group=root",
- "mode=644",
- },
- },
- ansible.Module{
- Name: "yum",
- Args: []string{
- "name=yunion-lbagent",
- "state=latest",
- "update_cache=yes",
- },
- },
- )
- case compute_apis.DeployMethodCopy:
- // glob for rpms
- basenames := []string{
- "telegraf",
- "gobetween",
- "keepalived",
- "haproxy",
- "openvswitch",
- "openvswitch-ovn-host",
- "yunion-lbagent",
- }
- mods := []ansible.Module{}
- for _, basename := range basenames {
- pattern := filepath.Join("/opt/yunion/upgrade/rpms", basename+"-*.rpm")
- matches, err := filepath.Glob(pattern)
- if err != nil {
- return nil, errors.WithMessagef(err, "glob error %s", pattern)
- }
- if len(matches) == 0 {
- return nil, errors.Errorf("no match for %q", pattern)
- }
- path := matches[len(matches)-1]
- name := filepath.Base(path)
- destPath := filepath.Join("/tmp", name)
- mods = append(mods,
- ansible.Module{
- Name: "copy",
- Args: []string{
- "src=" + path,
- "dest=" + destPath,
- },
- },
- ansible.Module{
- Name: "yum",
- Args: []string{
- "name=" + destPath,
- "state=installed",
- "update_cache=yes",
- // disablerepo
- // enablerepo
- },
- },
- ansible.Module{
- Name: "file",
- Args: []string{
- "name=" + destPath,
- "state=absent",
- },
- },
- )
- }
- pb.Modules = append(pb.Modules, mods...)
- }
- pb.Modules = append(pb.Modules,
- ansible.Module{
- Name: "systemd",
- Args: []string{
- "name=yunion-lbagent",
- "enabled=yes",
- "state=restarted",
- "daemon_reload=yes",
- },
- },
- )
- return pb, nil
- }
- func (lbagent *SLoadbalancerAgent) undeploy(ctx context.Context, userCred mcclient.TokenCredential, host ansible.Host) (*ansible.Playbook, error) {
- pb := &ansible.Playbook{
- Inventory: ansible.Inventory{
- Hosts: []ansible.Host{host},
- },
- Modules: []ansible.Module{
- {
- Name: "shell",
- Args: []string{
- "systemctl disable --now yunion-lbagent; true",
- },
- },
- {
- Name: "shell",
- Args: []string{
- `pkill keepalived; pkill telegraf; pkill gobetween; pkill haproxy; true`,
- },
- },
- {
- Name: "package",
- Args: []string{
- "name=yunion-lbagent",
- "state=absent",
- },
- },
- },
- }
- // we leave alone
- //
- // - /etc/yum.repos.d/yunion.repo
- // - /etc/yunion/lbagent.conf
- // - state of packages keepalived, haproxy, gobetween, telegraf
- //
- // This decision is unlikely to cause harm. These content are likely still needed by users
- return pb, nil
- }
- func (lbagent *SLoadbalancerAgent) validateHost(ctx context.Context, userCred mcclient.TokenCredential, host *ansible.Host) error {
- name := strings.TrimSpace(host.Name)
- if len(name) == 0 {
- return httperrors.NewBadRequestError("empty host name")
- }
- switch {
- case regutils.MatchIP4Addr(name):
- case strings.HasPrefix(name, "host:"):
- name = strings.TrimSpace(name[len("host:"):])
- obj, err := db.FetchByIdOrName(ctx, HostManager, userCred, name)
- if err != nil {
- return httperrors.NewNotFoundError("find host %s: %v", name, err)
- }
- host := obj.(*SHost)
- if host.IsManaged() {
- return httperrors.NewBadRequestError("lbagent cannot be deployed on managed host")
- }
- case strings.HasPrefix(name, "server:"):
- name = name[len("server:"):]
- fallthrough
- default:
- obj, err := db.FetchByIdOrName(ctx, GuestManager, userCred, name)
- if err != nil {
- return httperrors.NewNotFoundError("find guest %s: %v", name, err)
- }
- guest := obj.(*SGuest)
- region, err := guest.GetRegion()
- if err != nil {
- return errors.Wrapf(err, "GetRegion")
- }
- if utils.IsInStringArray(region.Provider, compute_apis.PUBLIC_CLOUD_PROVIDERS) {
- return httperrors.NewBadRequestError("lbagent cannot be deployed on public guests")
- }
- if guest.Status != compute_apis.VM_RUNNING {
- return httperrors.NewBadRequestError("server is in %q state, want %q",
- guest.Status, compute_apis.VM_RUNNING)
- }
- // Better make this explicit in the API
- if guest.SrcIpCheck.Bool() || guest.SrcMacCheck.Bool() {
- sess := auth.GetSession(ctx, userCred, "")
- params := jsonutils.NewDict()
- params.Set("src_ip_check", jsonutils.JSONFalse)
- params.Set("src_mac_check", jsonutils.JSONFalse)
- _, err := compute_modules.Servers.PerformAction(sess, guest.Id, "modify-src-check", params)
- if err != nil {
- return errors.Wrapf(err, "turn off src check of guest %s(%s)", guest.Name, guest.Id)
- }
- }
- }
- return nil
- }
- func (lbagent *SLoadbalancerAgent) PerformDeploy(
- ctx context.Context,
- userCred mcclient.TokenCredential,
- query jsonutils.JSONObject,
- input *compute_apis.LoadbalancerAgentDeployInput,
- ) (*compute_apis.LoadbalancerAgentDeployInput, error) {
- /*host := input.Host
- for _, k := range []string{"user", "pass", "proj"} {
- if v, ok := host.GetVar(k); !ok {
- return nil, httperrors.NewBadRequestError("host missing %s field", k)
- } else if v == "" {
- return nil, httperrors.NewBadRequestError("empty host %s field", k)
- }
- }
- authURL := options.Options.AuthURL
- {
- cli := mcclient.NewClient(options.Options.AuthURL, 10, false, true, "", "")
- token, err := cli.Authenticate(host.Vars["user"], host.Vars["pass"], "", host.Vars["proj"], "")
- if err != nil {
- return nil, httperrors.NewBadRequestError("authenticate error: %v", err)
- }
- if !token.HasSystemAdminPrivilege() {
- return nil, httperrors.NewBadRequestError("user must have system admin privileges")
- }
- s := cli.NewSession(ctx, options.Options.Region, "", identity_apis.EndpointInterfacePublic, token)
- authURL, err = s.GetServiceURL(
- identity_apis.SERVICE_TYPE,
- identity_apis.EndpointInterfacePublic)
- if err != nil {
- return nil, httperrors.NewClientError("get %s service %s url: %v",
- identity_apis.SERVICE_TYPE,
- identity_apis.EndpointInterfacePublic,
- err)
- }
- }
- if err := lbagent.validateHost(ctx, userCred, &host); err != nil {
- return nil, err
- }
- host.SetVar("region", options.Options.Region)
- host.SetVar("auth_uri", authURL)
- host.SetVar("id", lbagent.Id)
- host.SetVar("ansible_become", "yes")
- pb, err := lbagent.deploy(ctx, userCred, input)
- if err != nil {
- return nil, err
- }
- pbId := ""
- if lbagent.Deployment != nil && lbagent.Deployment.AnsiblePlaybook != "" {
- pbId = lbagent.Deployment.AnsiblePlaybook
- }
- pbModel, err := lbagent.updateOrCreatePbModel(ctx, userCred, pbId, lbagent.Name+"-"+lbagent.Id[:7], pb)
- if err != nil {
- return nil, err
- }
- logclient.AddActionLogWithContext(ctx, lbagent, "提交部署任务", pbModel, userCred, true)
- if _, err := db.Update(lbagent, func() error {
- lbagent.Deployment = &SLoadbalancerAgentDeployment{
- Host: input.Host.Name,
- AnsiblePlaybook: pbModel.Id,
- }
- return nil
- }); err != nil {
- return nil, err
- }
- return nil, err*/
- return nil, errors.Wrap(httperrors.ErrNotSupported, "deprecated")
- }
- func (lbagent *SLoadbalancerAgent) updateOrCreatePbModel(ctx context.Context,
- userCred mcclient.TokenCredential,
- pbId string,
- pbName string,
- pb *ansible.Playbook,
- ) (*ansible_model.AnsiblePlaybook, error) {
- cliSess := auth.GetSession(ctx, userCred, "")
- pbModel, err := ansible_modules.AnsiblePlaybooks.UpdateOrCreatePbModel(
- ctx, cliSess, pbId, pbName, pb,
- )
- return pbModel, err
- }
- func (lbagent *SLoadbalancerAgent) PerformUndeploy(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
- /*deployment := lbagent.Deployment
- if deployment == nil || deployment.Host == "" {
- return nil, httperrors.NewConflictError("No previous deployment info available")
- }
- host := ansible.Host{
- Name: deployment.Host,
- Vars: map[string]string{
- "ansible_become": "yes",
- },
- }
- pb, err := lbagent.undeploy(ctx, userCred, host)
- if err != nil {
- return nil, err
- }
- pbModel, err := lbagent.updateOrCreatePbModel(ctx, userCred,
- deployment.AnsiblePlaybookUndeployment,
- lbagent.Name+"-"+lbagent.Id[:7]+"-undeploy",
- pb)
- if err != nil {
- return nil, err
- }
- logclient.AddActionLogWithContext(ctx, lbagent, "提交下线任务", pbModel, userCred, true)
- if _, err := db.Update(lbagent, func() error {
- lbagent.Deployment.AnsiblePlaybookUndeployment = pbModel.Id
- return nil
- }); err != nil {
- return nil, err
- }
- return nil, nil*/
- return nil, errors.Wrap(httperrors.ErrNotSupported, "deprecated")
- }
- const (
- lbagentConfTmpl = `
- region = '{{ region }}'
- auth_uri = '{{ auth_uri }}'
- admin_user = '{{ user }}'
- admin_password = '{{ pass }}'
- admin_tenant_name = '{{ proj }}'
- session_endpoint_type = 'public'
- data_preserve_n = 10
- base_data_dir = "/opt/cloud/workspace/lbagent"
- api_lbagent_id = '{{ id }}'
- api_lbagent_hb_interval = 60
- api_sync_interval = 5
- api_list_batch_size = 2048
- `
- yunionRepoTmpl = `
- [yunion]
- name=Packages for Yunion- $basearch
- baseurl={{ repo_base_url }}
- failovermethod=priority
- enabled=1
- gpgcheck=0
- sslverify={{ repo_sslverify }}
- `
- )
- func init() {
- gotypes.RegisterSerializable(reflect.TypeOf(&SLoadbalancerAgentDeployment{}), func() gotypes.ISerializable {
- return &SLoadbalancerAgentDeployment{}
- })
- }
|