ansibleplaybooks_v2.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  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. "fmt"
  18. "sync"
  19. "time"
  20. "github.com/pkg/errors"
  21. "yunion.io/x/jsonutils"
  22. "yunion.io/x/log"
  23. "yunion.io/x/sqlchemy"
  24. "yunion.io/x/onecloud/pkg/ansibleserver/options"
  25. "yunion.io/x/onecloud/pkg/apis"
  26. api "yunion.io/x/onecloud/pkg/apis/ansible"
  27. "yunion.io/x/onecloud/pkg/cloudcommon/db"
  28. "yunion.io/x/onecloud/pkg/httperrors"
  29. "yunion.io/x/onecloud/pkg/mcclient"
  30. "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
  31. "yunion.io/x/onecloud/pkg/util/ansible"
  32. "yunion.io/x/onecloud/pkg/util/ansiblev2"
  33. )
  34. // This is at the moment for internal use only. Update is not allowed
  35. type SAnsiblePlaybookV2 struct {
  36. db.SVirtualResourceBase
  37. Playbook string `length:"text" nullable:"false" create:"required" get:"user"`
  38. Inventory string `length:"text" nullable:"false" create:"required" get:"user"`
  39. Requirements string `length:"text" nullable:"false" create:"optional" get:"user"`
  40. Files string `length:"text" nullable:"false" create:"optional" get:"user"`
  41. Output string `length:"medium" get:"user"`
  42. StartTime time.Time `list:"user"`
  43. EndTime time.Time `list:"user"`
  44. CreatorMark string `length:"32" nullable:"false" create:"optional" get:"user"`
  45. }
  46. type SAnsiblePlaybookV2Manager struct {
  47. db.SVirtualResourceBaseManager
  48. sessions ansible.SessionManager
  49. sessionsMux *sync.Mutex
  50. }
  51. var AnsiblePlaybookV2Manager *SAnsiblePlaybookV2Manager
  52. func init() {
  53. AnsiblePlaybookV2Manager = &SAnsiblePlaybookV2Manager{
  54. SVirtualResourceBaseManager: db.NewVirtualResourceBaseManager(
  55. SAnsiblePlaybookV2{},
  56. "ansibleplaybooks_v2_tbl",
  57. "ansibleplaybook_v2",
  58. "ansibleplaybooks_v2",
  59. ),
  60. sessions: ansible.SessionManager{},
  61. sessionsMux: &sync.Mutex{},
  62. }
  63. AnsiblePlaybookV2Manager.SetVirtualObject(AnsiblePlaybookV2Manager)
  64. }
  65. func (man *SAnsiblePlaybookV2Manager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
  66. data.Set("status", jsonutils.NewString(api.AnsiblePlaybookStatusInit))
  67. input := apis.VirtualResourceCreateInput{}
  68. err := data.Unmarshal(&input)
  69. if err != nil {
  70. return nil, httperrors.NewInternalServerError("unmarshal StandaloneResourceCreateInput fail %s", err)
  71. }
  72. input, err = man.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input)
  73. if err != nil {
  74. return nil, err
  75. }
  76. data.Update(jsonutils.Marshal(input))
  77. return data, nil
  78. }
  79. func (apb *SAnsiblePlaybookV2) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
  80. apb.SVirtualResourceBase.PostCreate(ctx, userCred, ownerId, query, data)
  81. err := apb.runPlaybook(ctx, userCred)
  82. if err != nil {
  83. log.Errorf("postCreate: runPlaybook: %v", err)
  84. }
  85. }
  86. func (man *SAnsiblePlaybookV2Manager) InitializeData() error {
  87. pbs := []SAnsiblePlaybookV2{}
  88. q := AnsiblePlaybookV2Manager.Query()
  89. q = q.Filter(sqlchemy.Equals(q.Field("status"), api.AnsiblePlaybookStatusRunning))
  90. if err := db.FetchModelObjects(AnsiblePlaybookV2Manager, q, &pbs); err != nil {
  91. return errors.WithMessage(err, "fetch running playbooks")
  92. }
  93. for i := 0; i < len(pbs); i++ {
  94. pb := &pbs[i]
  95. _, err := db.Update(pb, func() error {
  96. pb.Status = api.AnsiblePlaybookStatusUnknown
  97. return nil
  98. })
  99. if err != nil {
  100. log.Errorf("set playbook %s(%s) to unknown state: %v", pb.Name, pb.Id, err)
  101. }
  102. }
  103. return nil
  104. }
  105. func (apb *SAnsiblePlaybookV2) ValidateDeleteCondition(ctx context.Context, info jsonutils.JSONObject) error {
  106. if apb.Status == api.AnsiblePlaybookStatusRunning {
  107. return httperrors.NewConflictError("playbook is in running state")
  108. }
  109. return nil
  110. }
  111. func (apb *SAnsiblePlaybookV2) PerformRun(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
  112. err := apb.runPlaybook(ctx, userCred)
  113. if err != nil {
  114. return nil, httperrors.NewConflictError("%s", err.Error())
  115. }
  116. return nil, nil
  117. }
  118. func (apb *SAnsiblePlaybookV2) PerformStop(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
  119. err := apb.stopPlaybook(ctx, userCred)
  120. if err != nil {
  121. return nil, httperrors.NewConflictError("%s", err.Error())
  122. }
  123. return nil, nil
  124. }
  125. func (apb *SAnsiblePlaybookV2) runPlaybook(ctx context.Context, userCred mcclient.TokenCredential) error {
  126. man := AnsiblePlaybookV2Manager
  127. man.sessionsMux.Lock()
  128. defer man.sessionsMux.Unlock()
  129. if man.sessions.Has(apb.Id) {
  130. return fmt.Errorf("playbook is already running")
  131. }
  132. // hack: force Sleep 50s to wait some host ssh service started
  133. time.Sleep(50 * time.Second)
  134. var (
  135. privateKey string
  136. err error
  137. files = map[string][]byte{}
  138. )
  139. if apb.Files != "" {
  140. obj, err := jsonutils.ParseString(apb.Files)
  141. if err != nil {
  142. return fmt.Errorf("playbook files json: parse: %v", err)
  143. }
  144. filesJ, err := obj.GetMap()
  145. if err != nil {
  146. return fmt.Errorf("playbook files json: get map: %v", err)
  147. }
  148. for name, obj := range filesJ {
  149. content, err := obj.GetString()
  150. if err != nil {
  151. return fmt.Errorf("playbook files json: get content %s: %v", name, err)
  152. }
  153. files[name] = []byte(content)
  154. }
  155. }
  156. // init private key
  157. if privateKey, err = compute.Sshkeypairs.FetchPrivateKey(ctx, userCred); err != nil {
  158. return err
  159. }
  160. _, err = db.Update(apb, func() error {
  161. apb.StartTime = time.Now()
  162. apb.EndTime = time.Time{}
  163. apb.Output = ""
  164. apb.Status = api.AnsiblePlaybookStatusRunning
  165. return nil
  166. })
  167. if err != nil {
  168. log.Errorf("run playbook: update db failed before run: %v", err)
  169. }
  170. sess := ansiblev2.NewSession().
  171. Inventory(apb.Inventory).
  172. Playbook(apb.Playbook).
  173. PrivateKey(privateKey).
  174. Requirements(apb.Requirements).
  175. Files(files).
  176. OutputWriter(&ansiblePlaybookOutputWriter{apb}).
  177. KeepTmpdir(options.Options.KeepTmpdir).
  178. RolePublic(options.Options.RolePublic).
  179. Timeout(options.Options.Timeout)
  180. man.sessions.Add(apb.Id, sess)
  181. // NOTE host state check? run only on online hosts and running guests, skip others
  182. go func() {
  183. defer func() {
  184. man.sessionsMux.Lock()
  185. defer man.sessionsMux.Unlock()
  186. man.sessions.Remove(apb.Id)
  187. }()
  188. runErr := man.sessions.Run(apb.Id)
  189. _, err := db.Update(apb, func() error {
  190. err := man.sessions.Err(apb.Id)
  191. if err != nil {
  192. apb.Status = api.AnsiblePlaybookStatusCanceled
  193. } else if runErr != nil {
  194. log.Warningf("playbook %s(%s) failed: %v", apb.Name, apb.Id, runErr)
  195. apb.Status = api.AnsiblePlaybookStatusFailed
  196. } else {
  197. apb.Status = api.AnsiblePlaybookStatusSucceeded
  198. }
  199. apb.EndTime = time.Now()
  200. return nil
  201. })
  202. if err != nil {
  203. log.Errorf("updating ansible playbook failed: %v", err)
  204. }
  205. }()
  206. return nil
  207. }
  208. func (apb *SAnsiblePlaybookV2) stopPlaybook(ctx context.Context, userCred mcclient.TokenCredential) error {
  209. man := AnsiblePlaybookV2Manager
  210. man.sessionsMux.Lock()
  211. defer man.sessionsMux.Unlock()
  212. if !man.sessions.Has(apb.Id) {
  213. return fmt.Errorf("playbook is not running")
  214. }
  215. // the playbook will be removed from session map in runPlaybook() on return from run
  216. man.sessions.Stop(apb.Id)
  217. return nil
  218. }
  219. func (apb *SAnsiblePlaybookV2) getMaxOutputLength() int {
  220. return OutputMaxBytes
  221. }
  222. func (apb *SAnsiblePlaybookV2) getOutput() string {
  223. return apb.Output
  224. }
  225. func (apb *SAnsiblePlaybookV2) setOutput(s string) {
  226. apb.Output = s
  227. }