loadbalanceragents_deploy.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package models
  15. import (
  16. "context"
  17. "path/filepath"
  18. "reflect"
  19. "strings"
  20. "github.com/pkg/errors"
  21. "yunion.io/x/jsonutils"
  22. "yunion.io/x/pkg/gotypes"
  23. "yunion.io/x/pkg/util/regutils"
  24. "yunion.io/x/pkg/utils"
  25. compute_apis "yunion.io/x/onecloud/pkg/apis/compute"
  26. "yunion.io/x/onecloud/pkg/cloudcommon/db"
  27. "yunion.io/x/onecloud/pkg/httperrors"
  28. "yunion.io/x/onecloud/pkg/mcclient"
  29. "yunion.io/x/onecloud/pkg/mcclient/auth"
  30. ansible_model "yunion.io/x/onecloud/pkg/mcclient/models"
  31. ansible_modules "yunion.io/x/onecloud/pkg/mcclient/modules/ansible"
  32. compute_modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
  33. "yunion.io/x/onecloud/pkg/util/ansible"
  34. )
  35. type SLoadbalancerAgentDeployment struct {
  36. Host string
  37. AnsiblePlaybook string
  38. AnsiblePlaybookUndeployment string
  39. }
  40. func (p *SLoadbalancerAgentDeployment) String() string {
  41. return jsonutils.Marshal(p).String()
  42. }
  43. func (p *SLoadbalancerAgentDeployment) IsZero() bool {
  44. if *p == (SLoadbalancerAgentDeployment{}) {
  45. return true
  46. }
  47. return false
  48. }
  49. func (lbagent *SLoadbalancerAgent) deploy(ctx context.Context, userCred mcclient.TokenCredential, input *compute_apis.LoadbalancerAgentDeployInput) (*ansible.Playbook, error) {
  50. pb := &ansible.Playbook{
  51. Inventory: ansible.Inventory{
  52. Hosts: []ansible.Host{input.Host},
  53. },
  54. Modules: []ansible.Module{
  55. {
  56. Name: "group",
  57. Args: []string{
  58. "name=yunion",
  59. "state=present",
  60. },
  61. },
  62. {
  63. Name: "user",
  64. Args: []string{
  65. "name=yunion",
  66. "state=present",
  67. "group=yunion",
  68. },
  69. },
  70. {
  71. Name: "file",
  72. Args: []string{
  73. "path=/etc/yunion",
  74. "state=directory",
  75. "owner=yunion",
  76. "group=yunion",
  77. "mode=755",
  78. },
  79. },
  80. {
  81. Name: "template",
  82. Args: []string{
  83. "src=lbagentConfTmpl",
  84. "dest=/etc/yunion/lbagent.conf",
  85. "owner=yunion",
  86. "group=yunion",
  87. "mode=600",
  88. },
  89. },
  90. },
  91. Files: map[string][]byte{
  92. "lbagentConfTmpl": []byte(lbagentConfTmpl),
  93. },
  94. }
  95. switch input.DeployMethod {
  96. case compute_apis.DeployMethodYum:
  97. fallthrough
  98. default:
  99. if v, ok := input.Host.GetVar("repo_base_url"); !ok || v == "" {
  100. return nil, httperrors.NewBadRequestError("use yum requires valid repo_base_url")
  101. }
  102. if v, ok := input.Host.GetVar("repo_sslverify"); !ok || v == "" {
  103. input.Host.SetVar("repo_sslverify", "0")
  104. }
  105. pb.Files["yunionRepoTmpl"] = []byte(yunionRepoTmpl)
  106. pb.Modules = append(pb.Modules,
  107. ansible.Module{
  108. Name: "template",
  109. Args: []string{
  110. "src=yunionRepoTmpl",
  111. "dest=/etc/yum.repos.d/yunion.repo",
  112. "owner=root",
  113. "group=root",
  114. "mode=644",
  115. },
  116. },
  117. ansible.Module{
  118. Name: "yum",
  119. Args: []string{
  120. "name=yunion-lbagent",
  121. "state=latest",
  122. "update_cache=yes",
  123. },
  124. },
  125. )
  126. case compute_apis.DeployMethodCopy:
  127. // glob for rpms
  128. basenames := []string{
  129. "telegraf",
  130. "gobetween",
  131. "keepalived",
  132. "haproxy",
  133. "openvswitch",
  134. "openvswitch-ovn-host",
  135. "yunion-lbagent",
  136. }
  137. mods := []ansible.Module{}
  138. for _, basename := range basenames {
  139. pattern := filepath.Join("/opt/yunion/upgrade/rpms", basename+"-*.rpm")
  140. matches, err := filepath.Glob(pattern)
  141. if err != nil {
  142. return nil, errors.WithMessagef(err, "glob error %s", pattern)
  143. }
  144. if len(matches) == 0 {
  145. return nil, errors.Errorf("no match for %q", pattern)
  146. }
  147. path := matches[len(matches)-1]
  148. name := filepath.Base(path)
  149. destPath := filepath.Join("/tmp", name)
  150. mods = append(mods,
  151. ansible.Module{
  152. Name: "copy",
  153. Args: []string{
  154. "src=" + path,
  155. "dest=" + destPath,
  156. },
  157. },
  158. ansible.Module{
  159. Name: "yum",
  160. Args: []string{
  161. "name=" + destPath,
  162. "state=installed",
  163. "update_cache=yes",
  164. // disablerepo
  165. // enablerepo
  166. },
  167. },
  168. ansible.Module{
  169. Name: "file",
  170. Args: []string{
  171. "name=" + destPath,
  172. "state=absent",
  173. },
  174. },
  175. )
  176. }
  177. pb.Modules = append(pb.Modules, mods...)
  178. }
  179. pb.Modules = append(pb.Modules,
  180. ansible.Module{
  181. Name: "systemd",
  182. Args: []string{
  183. "name=yunion-lbagent",
  184. "enabled=yes",
  185. "state=restarted",
  186. "daemon_reload=yes",
  187. },
  188. },
  189. )
  190. return pb, nil
  191. }
  192. func (lbagent *SLoadbalancerAgent) undeploy(ctx context.Context, userCred mcclient.TokenCredential, host ansible.Host) (*ansible.Playbook, error) {
  193. pb := &ansible.Playbook{
  194. Inventory: ansible.Inventory{
  195. Hosts: []ansible.Host{host},
  196. },
  197. Modules: []ansible.Module{
  198. {
  199. Name: "shell",
  200. Args: []string{
  201. "systemctl disable --now yunion-lbagent; true",
  202. },
  203. },
  204. {
  205. Name: "shell",
  206. Args: []string{
  207. `pkill keepalived; pkill telegraf; pkill gobetween; pkill haproxy; true`,
  208. },
  209. },
  210. {
  211. Name: "package",
  212. Args: []string{
  213. "name=yunion-lbagent",
  214. "state=absent",
  215. },
  216. },
  217. },
  218. }
  219. // we leave alone
  220. //
  221. // - /etc/yum.repos.d/yunion.repo
  222. // - /etc/yunion/lbagent.conf
  223. // - state of packages keepalived, haproxy, gobetween, telegraf
  224. //
  225. // This decision is unlikely to cause harm. These content are likely still needed by users
  226. return pb, nil
  227. }
  228. func (lbagent *SLoadbalancerAgent) validateHost(ctx context.Context, userCred mcclient.TokenCredential, host *ansible.Host) error {
  229. name := strings.TrimSpace(host.Name)
  230. if len(name) == 0 {
  231. return httperrors.NewBadRequestError("empty host name")
  232. }
  233. switch {
  234. case regutils.MatchIP4Addr(name):
  235. case strings.HasPrefix(name, "host:"):
  236. name = strings.TrimSpace(name[len("host:"):])
  237. obj, err := db.FetchByIdOrName(ctx, HostManager, userCred, name)
  238. if err != nil {
  239. return httperrors.NewNotFoundError("find host %s: %v", name, err)
  240. }
  241. host := obj.(*SHost)
  242. if host.IsManaged() {
  243. return httperrors.NewBadRequestError("lbagent cannot be deployed on managed host")
  244. }
  245. case strings.HasPrefix(name, "server:"):
  246. name = name[len("server:"):]
  247. fallthrough
  248. default:
  249. obj, err := db.FetchByIdOrName(ctx, GuestManager, userCred, name)
  250. if err != nil {
  251. return httperrors.NewNotFoundError("find guest %s: %v", name, err)
  252. }
  253. guest := obj.(*SGuest)
  254. region, err := guest.GetRegion()
  255. if err != nil {
  256. return errors.Wrapf(err, "GetRegion")
  257. }
  258. if utils.IsInStringArray(region.Provider, compute_apis.PUBLIC_CLOUD_PROVIDERS) {
  259. return httperrors.NewBadRequestError("lbagent cannot be deployed on public guests")
  260. }
  261. if guest.Status != compute_apis.VM_RUNNING {
  262. return httperrors.NewBadRequestError("server is in %q state, want %q",
  263. guest.Status, compute_apis.VM_RUNNING)
  264. }
  265. // Better make this explicit in the API
  266. if guest.SrcIpCheck.Bool() || guest.SrcMacCheck.Bool() {
  267. sess := auth.GetSession(ctx, userCred, "")
  268. params := jsonutils.NewDict()
  269. params.Set("src_ip_check", jsonutils.JSONFalse)
  270. params.Set("src_mac_check", jsonutils.JSONFalse)
  271. _, err := compute_modules.Servers.PerformAction(sess, guest.Id, "modify-src-check", params)
  272. if err != nil {
  273. return errors.Wrapf(err, "turn off src check of guest %s(%s)", guest.Name, guest.Id)
  274. }
  275. }
  276. }
  277. return nil
  278. }
  279. func (lbagent *SLoadbalancerAgent) PerformDeploy(
  280. ctx context.Context,
  281. userCred mcclient.TokenCredential,
  282. query jsonutils.JSONObject,
  283. input *compute_apis.LoadbalancerAgentDeployInput,
  284. ) (*compute_apis.LoadbalancerAgentDeployInput, error) {
  285. /*host := input.Host
  286. for _, k := range []string{"user", "pass", "proj"} {
  287. if v, ok := host.GetVar(k); !ok {
  288. return nil, httperrors.NewBadRequestError("host missing %s field", k)
  289. } else if v == "" {
  290. return nil, httperrors.NewBadRequestError("empty host %s field", k)
  291. }
  292. }
  293. authURL := options.Options.AuthURL
  294. {
  295. cli := mcclient.NewClient(options.Options.AuthURL, 10, false, true, "", "")
  296. token, err := cli.Authenticate(host.Vars["user"], host.Vars["pass"], "", host.Vars["proj"], "")
  297. if err != nil {
  298. return nil, httperrors.NewBadRequestError("authenticate error: %v", err)
  299. }
  300. if !token.HasSystemAdminPrivilege() {
  301. return nil, httperrors.NewBadRequestError("user must have system admin privileges")
  302. }
  303. s := cli.NewSession(ctx, options.Options.Region, "", identity_apis.EndpointInterfacePublic, token)
  304. authURL, err = s.GetServiceURL(
  305. identity_apis.SERVICE_TYPE,
  306. identity_apis.EndpointInterfacePublic)
  307. if err != nil {
  308. return nil, httperrors.NewClientError("get %s service %s url: %v",
  309. identity_apis.SERVICE_TYPE,
  310. identity_apis.EndpointInterfacePublic,
  311. err)
  312. }
  313. }
  314. if err := lbagent.validateHost(ctx, userCred, &host); err != nil {
  315. return nil, err
  316. }
  317. host.SetVar("region", options.Options.Region)
  318. host.SetVar("auth_uri", authURL)
  319. host.SetVar("id", lbagent.Id)
  320. host.SetVar("ansible_become", "yes")
  321. pb, err := lbagent.deploy(ctx, userCred, input)
  322. if err != nil {
  323. return nil, err
  324. }
  325. pbId := ""
  326. if lbagent.Deployment != nil && lbagent.Deployment.AnsiblePlaybook != "" {
  327. pbId = lbagent.Deployment.AnsiblePlaybook
  328. }
  329. pbModel, err := lbagent.updateOrCreatePbModel(ctx, userCred, pbId, lbagent.Name+"-"+lbagent.Id[:7], pb)
  330. if err != nil {
  331. return nil, err
  332. }
  333. logclient.AddActionLogWithContext(ctx, lbagent, "提交部署任务", pbModel, userCred, true)
  334. if _, err := db.Update(lbagent, func() error {
  335. lbagent.Deployment = &SLoadbalancerAgentDeployment{
  336. Host: input.Host.Name,
  337. AnsiblePlaybook: pbModel.Id,
  338. }
  339. return nil
  340. }); err != nil {
  341. return nil, err
  342. }
  343. return nil, err*/
  344. return nil, errors.Wrap(httperrors.ErrNotSupported, "deprecated")
  345. }
  346. func (lbagent *SLoadbalancerAgent) updateOrCreatePbModel(ctx context.Context,
  347. userCred mcclient.TokenCredential,
  348. pbId string,
  349. pbName string,
  350. pb *ansible.Playbook,
  351. ) (*ansible_model.AnsiblePlaybook, error) {
  352. cliSess := auth.GetSession(ctx, userCred, "")
  353. pbModel, err := ansible_modules.AnsiblePlaybooks.UpdateOrCreatePbModel(
  354. ctx, cliSess, pbId, pbName, pb,
  355. )
  356. return pbModel, err
  357. }
  358. func (lbagent *SLoadbalancerAgent) PerformUndeploy(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
  359. /*deployment := lbagent.Deployment
  360. if deployment == nil || deployment.Host == "" {
  361. return nil, httperrors.NewConflictError("No previous deployment info available")
  362. }
  363. host := ansible.Host{
  364. Name: deployment.Host,
  365. Vars: map[string]string{
  366. "ansible_become": "yes",
  367. },
  368. }
  369. pb, err := lbagent.undeploy(ctx, userCred, host)
  370. if err != nil {
  371. return nil, err
  372. }
  373. pbModel, err := lbagent.updateOrCreatePbModel(ctx, userCred,
  374. deployment.AnsiblePlaybookUndeployment,
  375. lbagent.Name+"-"+lbagent.Id[:7]+"-undeploy",
  376. pb)
  377. if err != nil {
  378. return nil, err
  379. }
  380. logclient.AddActionLogWithContext(ctx, lbagent, "提交下线任务", pbModel, userCred, true)
  381. if _, err := db.Update(lbagent, func() error {
  382. lbagent.Deployment.AnsiblePlaybookUndeployment = pbModel.Id
  383. return nil
  384. }); err != nil {
  385. return nil, err
  386. }
  387. return nil, nil*/
  388. return nil, errors.Wrap(httperrors.ErrNotSupported, "deprecated")
  389. }
  390. const (
  391. lbagentConfTmpl = `
  392. region = '{{ region }}'
  393. auth_uri = '{{ auth_uri }}'
  394. admin_user = '{{ user }}'
  395. admin_password = '{{ pass }}'
  396. admin_tenant_name = '{{ proj }}'
  397. session_endpoint_type = 'public'
  398. data_preserve_n = 10
  399. base_data_dir = "/opt/cloud/workspace/lbagent"
  400. api_lbagent_id = '{{ id }}'
  401. api_lbagent_hb_interval = 60
  402. api_sync_interval = 5
  403. api_list_batch_size = 2048
  404. `
  405. yunionRepoTmpl = `
  406. [yunion]
  407. name=Packages for Yunion- $basearch
  408. baseurl={{ repo_base_url }}
  409. failovermethod=priority
  410. enabled=1
  411. gpgcheck=0
  412. sslverify={{ repo_sslverify }}
  413. `
  414. )
  415. func init() {
  416. gotypes.RegisterSerializable(reflect.TypeOf(&SLoadbalancerAgentDeployment{}), func() gotypes.ISerializable {
  417. return &SLoadbalancerAgentDeployment{}
  418. })
  419. }