ansibleplaybooks.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  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. "yunion.io/x/jsonutils"
  21. "yunion.io/x/log"
  22. "yunion.io/x/pkg/errors"
  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. )
  33. // low priority
  34. //
  35. // - retry times and interval
  36. // - timeout,
  37. // - copied playbook has no added value
  38. type SAnsiblePlaybook struct {
  39. db.SVirtualResourceBase
  40. Playbook *ansible.Playbook `length:"text" nullable:"false" create:"required" get:"user" update:"user"`
  41. Output string `length:"medium" get:"user"`
  42. StartTime time.Time `list:"user"`
  43. EndTime time.Time `list:"user"`
  44. }
  45. type SAnsiblePlaybookManager struct {
  46. db.SVirtualResourceBaseManager
  47. sessions ansible.SessionManager
  48. sessionsMux *sync.Mutex
  49. }
  50. var AnsiblePlaybookManager *SAnsiblePlaybookManager
  51. func init() {
  52. AnsiblePlaybookManager = &SAnsiblePlaybookManager{
  53. SVirtualResourceBaseManager: db.NewVirtualResourceBaseManager(
  54. SAnsiblePlaybook{},
  55. "ansibleplaybooks_tbl",
  56. "ansibleplaybook",
  57. "ansibleplaybooks",
  58. ),
  59. sessions: ansible.SessionManager{},
  60. sessionsMux: &sync.Mutex{},
  61. }
  62. AnsiblePlaybookManager.SetVirtualObject(AnsiblePlaybookManager)
  63. }
  64. func (man *SAnsiblePlaybookManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
  65. pbV := NewAnsiblePlaybookValidator("playbook", userCred)
  66. if err := pbV.Validate(ctx, data); err != nil {
  67. return nil, err
  68. }
  69. data.Set("status", jsonutils.NewString(api.AnsiblePlaybookStatusInit))
  70. var err error
  71. input := apis.VirtualResourceCreateInput{}
  72. err = data.Unmarshal(&input)
  73. if err != nil {
  74. return nil, httperrors.NewInternalServerError("unmarshal VirtualResourceCreateInput fail %s", err)
  75. }
  76. input, err = man.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input)
  77. if err != nil {
  78. return nil, err
  79. }
  80. data.Update(jsonutils.Marshal(input))
  81. return data, nil
  82. }
  83. func (apb *SAnsiblePlaybook) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
  84. apb.SVirtualResourceBase.PostCreate(ctx, userCred, ownerId, query, data)
  85. err := apb.runPlaybook(ctx, userCred)
  86. if err != nil {
  87. log.Errorf("postCreate: runPlaybook: %v", err)
  88. }
  89. }
  90. func (man *SAnsiblePlaybookManager) InitializeData() error {
  91. pbs := []SAnsiblePlaybook{}
  92. q := AnsiblePlaybookManager.Query()
  93. q = q.Filter(sqlchemy.Equals(q.Field("status"), api.AnsiblePlaybookStatusRunning))
  94. if err := db.FetchModelObjects(AnsiblePlaybookManager, q, &pbs); err != nil {
  95. return errors.Wrap(err, "fetch running playbooks")
  96. }
  97. for i := 0; i < len(pbs); i++ {
  98. pb := &pbs[i]
  99. _, err := db.Update(pb, func() error {
  100. pb.Status = api.AnsiblePlaybookStatusUnknown
  101. return nil
  102. })
  103. if err != nil {
  104. log.Errorf("set playbook %s(%s) to unknown state: %v", pb.Name, pb.Id, err)
  105. }
  106. }
  107. return nil
  108. }
  109. func (apb *SAnsiblePlaybook) ValidateDeleteCondition(ctx context.Context, info jsonutils.JSONObject) error {
  110. if apb.Status == api.AnsiblePlaybookStatusRunning {
  111. return httperrors.NewConflictError("playbook is in running state")
  112. }
  113. return nil
  114. }
  115. func (apb *SAnsiblePlaybook) ValidateUpdateCondition(ctx context.Context) error {
  116. if apb.Status == api.AnsiblePlaybookStatusRunning {
  117. return httperrors.NewConflictError("playbook is in running state")
  118. }
  119. return nil
  120. }
  121. func (apb *SAnsiblePlaybook) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
  122. pbV := NewAnsiblePlaybookValidator("playbook", userCred)
  123. if err := pbV.Validate(ctx, data); err != nil {
  124. return nil, err
  125. }
  126. apb.Playbook = pbV.Playbook // Update as a whole
  127. data.Set("status", jsonutils.NewString(api.AnsiblePlaybookStatusInit))
  128. return data, nil
  129. }
  130. func (apb *SAnsiblePlaybook) PostUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) {
  131. apb.SVirtualResourceBase.PostUpdate(ctx, userCred, query, data)
  132. err := apb.runPlaybook(ctx, userCred)
  133. if err != nil {
  134. log.Errorf("postUpdate: runPlaybook: %v", err)
  135. }
  136. }
  137. func (apb *SAnsiblePlaybook) PerformRun(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
  138. err := apb.runPlaybook(ctx, userCred)
  139. if err != nil {
  140. return nil, httperrors.NewConflictError("%s", err.Error())
  141. }
  142. return nil, nil
  143. }
  144. func (apb *SAnsiblePlaybook) PerformStop(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
  145. err := apb.stopPlaybook(ctx, userCred)
  146. if err != nil {
  147. return nil, httperrors.NewConflictError("%s", err.Error())
  148. }
  149. return nil, nil
  150. }
  151. func (apb *SAnsiblePlaybook) runPlaybook(ctx context.Context, userCred mcclient.TokenCredential) error {
  152. man := AnsiblePlaybookManager
  153. man.sessionsMux.Lock()
  154. defer man.sessionsMux.Unlock()
  155. if man.sessions.Has(apb.Id) {
  156. return fmt.Errorf("playbook is already running")
  157. }
  158. // init private key
  159. pb := apb.Playbook.Copy()
  160. if len(pb.PrivateKey) == 0 {
  161. if k, err := compute.Sshkeypairs.FetchPrivateKey(ctx, userCred); err != nil {
  162. return err
  163. } else {
  164. pb.PrivateKey = []byte(k)
  165. }
  166. }
  167. // init tmpdir clean policy
  168. if options.Options.KeepTmpdir {
  169. pb.CleanOnExit(false)
  170. }
  171. pb.OutputWriter(&ansiblePlaybookOutputWriter{apb})
  172. _, err := db.Update(apb, func() error {
  173. apb.StartTime = time.Now()
  174. apb.EndTime = time.Time{}
  175. apb.Output = ""
  176. apb.Status = api.AnsiblePlaybookStatusRunning
  177. return nil
  178. })
  179. if err != nil {
  180. log.Errorf("run playbook: update db failed before run: %v", err)
  181. }
  182. man.sessions.Add(apb.Id, pb)
  183. go func() {
  184. defer func() {
  185. man.sessionsMux.Lock()
  186. defer man.sessionsMux.Unlock()
  187. man.sessions.Remove(apb.Id)
  188. }()
  189. runErr := man.sessions.Run(apb.Id)
  190. _, err := db.Update(apb, func() error {
  191. err := man.sessions.Err(apb.Id)
  192. if err != nil {
  193. apb.Status = api.AnsiblePlaybookStatusCanceled
  194. } else if runErr != nil {
  195. log.Warningf("playbook %s(%s) failed: %v", apb.Name, apb.Id, runErr)
  196. apb.Status = api.AnsiblePlaybookStatusFailed
  197. } else {
  198. apb.Status = api.AnsiblePlaybookStatusSucceeded
  199. }
  200. apb.EndTime = time.Now()
  201. return nil
  202. })
  203. if err != nil {
  204. log.Errorf("updating ansible playbook failed: %v", err)
  205. }
  206. }()
  207. return nil
  208. }
  209. func (apb *SAnsiblePlaybook) stopPlaybook(ctx context.Context, userCred mcclient.TokenCredential) error {
  210. man := AnsiblePlaybookManager
  211. man.sessionsMux.Lock()
  212. defer man.sessionsMux.Unlock()
  213. if !man.sessions.Has(apb.Id) {
  214. if apb.Status == api.AnsiblePlaybookStatusRunning {
  215. _, err := db.Update(apb, func() error {
  216. apb.Status = api.AnsiblePlaybookStatusUnknown
  217. return nil
  218. })
  219. if err != nil {
  220. log.Errorf("updating ansible playbook status to unknown failed: %v", err)
  221. }
  222. }
  223. return fmt.Errorf("playbook is not running")
  224. }
  225. // the playbook will be removed from session map in runPlaybook() on return from run
  226. man.sessions.Stop(apb.Id)
  227. return nil
  228. }
  229. func (apb *SAnsiblePlaybook) getMaxOutputLength() int {
  230. return OutputMaxBytes
  231. }
  232. func (apb *SAnsiblePlaybook) getOutput() string {
  233. return apb.Output
  234. }
  235. func (apb *SAnsiblePlaybook) setOutput(s string) {
  236. apb.Output = s
  237. }