ansibleplaybook_instance.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  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. "sync"
  18. "time"
  19. "yunion.io/x/jsonutils"
  20. "yunion.io/x/log"
  21. "yunion.io/x/pkg/appctx"
  22. "yunion.io/x/pkg/errors"
  23. "yunion.io/x/sqlchemy"
  24. "yunion.io/x/onecloud/pkg/ansibleserver/options"
  25. api "yunion.io/x/onecloud/pkg/apis/ansible"
  26. "yunion.io/x/onecloud/pkg/cloudcommon/db"
  27. "yunion.io/x/onecloud/pkg/cloudcommon/workmanager"
  28. "yunion.io/x/onecloud/pkg/mcclient"
  29. "yunion.io/x/onecloud/pkg/mcclient/auth"
  30. "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
  31. "yunion.io/x/onecloud/pkg/mcclient/modules/devtool"
  32. "yunion.io/x/onecloud/pkg/util/ansible"
  33. "yunion.io/x/onecloud/pkg/util/ansiblev2"
  34. )
  35. type SAnsiblePlaybookInstance struct {
  36. db.SStatusStandaloneResourceBase
  37. ReferenceId string `width:"36" nullable:"false" get:"user" list:"user"`
  38. Inventory string `length:"text" nullable:"false" get:"user" list:"user"`
  39. Params jsonutils.JSONObject
  40. Output string `length:"medium" get:"user" list:"user"`
  41. StartTime time.Time `list:"user" get:"user"`
  42. EndTime time.Time `list:"user" get:"user"`
  43. }
  44. type SAnsiblePlaybookInstanceManager struct {
  45. db.SStatusStandaloneResourceBaseManager
  46. sessions ansible.SessionManager
  47. sessionsMux *sync.Mutex
  48. }
  49. var AnsiblePlaybookInstanceManager *SAnsiblePlaybookInstanceManager
  50. func init() {
  51. AnsiblePlaybookInstanceManager = &SAnsiblePlaybookInstanceManager{
  52. SStatusStandaloneResourceBaseManager: db.NewStatusStandaloneResourceBaseManager(
  53. SAnsiblePlaybookInstance{},
  54. "ansibleplaybook_instance_tbl",
  55. "ansibleplaybookinstance",
  56. "ansibleplaybookinstances",
  57. ),
  58. sessions: ansible.SessionManager{},
  59. }
  60. AnsiblePlaybookInstanceManager.SetVirtualObject(AnsiblePlaybookInstanceManager)
  61. }
  62. func (aim *SAnsiblePlaybookInstanceManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input api.AnsiblePlaybookInstanceListInput) (*sqlchemy.SQuery, error) {
  63. if len(input.AnsiblePlayboookReferenceId) > 0 {
  64. q = q.Equals("reference_id", input.AnsiblePlayboookReferenceId)
  65. }
  66. return aim.SStatusStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, input.StatusStandaloneResourceListInput)
  67. }
  68. func (aim *SAnsiblePlaybookInstanceManager) createInstance(ctx context.Context, referenceId string, host api.AnsibleHost, params jsonutils.JSONObject) (*SAnsiblePlaybookInstance, error) {
  69. // build inventory
  70. inv := ansiblev2.NewInventory()
  71. vars := map[string]interface{}{
  72. "ansible_user": host.User,
  73. "ansible_host": host.IP,
  74. "ansible_port": host.Port,
  75. }
  76. if host.OsType == "Windows" {
  77. vars["ansible_password"] = host.Password
  78. vars["ansible_connection"] = "winrm"
  79. vars["ansible_winrm_server_cert_validation"] = "ignore"
  80. vars["ansible_winrm_transport"] = "ntlm"
  81. vars["ansible_become"] = false
  82. }
  83. h := ansiblev2.NewHost()
  84. h.Vars = vars
  85. inv.SetHost(host.Name, h)
  86. ai := &SAnsiblePlaybookInstance{
  87. ReferenceId: referenceId,
  88. Params: params,
  89. Inventory: inv.String(),
  90. }
  91. err := aim.TableSpec().Insert(ctx, ai)
  92. if err != nil {
  93. return nil, errors.Wrapf(err, "unable to create AnsiblePlaybookInstance")
  94. }
  95. ai.SetModelManager(aim, ai)
  96. return ai, nil
  97. }
  98. func (ai *SAnsiblePlaybookInstance) PerformRun(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input jsonutils.JSONObject) (jsonutils.JSONObject, error) {
  99. return nil, ai.runPlaybook(ctx, userCred, nil)
  100. }
  101. func (ai *SAnsiblePlaybookInstance) runPlaybook(ctx context.Context, userCred mcclient.TokenCredential, ar *SAnsiblePlaybookReference) error {
  102. man := AnsiblePlaybookInstanceManager
  103. if man.sessions.Has(ai.Id) {
  104. return errors.Error("playbook is already running")
  105. }
  106. if ar == nil {
  107. obj, err := AnsiblePlaybookReferenceManager.FetchById(ai.ReferenceId)
  108. if err != nil {
  109. return errors.Wrapf(err, "unable to fetch ansibleplaybook reference %s", ai.ReferenceId)
  110. }
  111. ar = obj.(*SAnsiblePlaybookReference)
  112. }
  113. var (
  114. privateKey string
  115. err error
  116. )
  117. if privateKey, err = compute.Sshkeypairs.FetchPrivateKey(ctx, userCred); err != nil {
  118. return err
  119. }
  120. _, err = db.Update(ai, func() error {
  121. ai.StartTime = time.Now()
  122. ai.EndTime = time.Time{}
  123. ai.Output = ""
  124. ai.Status = api.AnsiblePlaybookStatusRunning
  125. return nil
  126. })
  127. if err != nil {
  128. return errors.Wrap(err, "unable to update ansibleplaybookinstance")
  129. }
  130. // merge configs
  131. dp, params := ar.DefaultParams.(*jsonutils.JSONDict), ai.Params.(*jsonutils.JSONDict)
  132. for _, k := range dp.SortedKeys() {
  133. v, _ := dp.Get(k)
  134. params.Set(k, v)
  135. }
  136. ar.DefaultParams.(*jsonutils.JSONDict).SortedKeys()
  137. sess := ansiblev2.NewOfflineSession().
  138. Inventory(ai.Inventory).
  139. PrivateKey(privateKey).
  140. ConfigYaml(params.YAMLString()).
  141. PlaybookPath(ar.PlaybookPath).
  142. OutputWriter(&ansiblePlaybookOutputWriter{ai}).
  143. KeepTmpdir(options.Options.KeepTmpdir)
  144. man.sessions.Add(ai.Id, sess)
  145. // NOTE host state check? run only on online hosts and running guests, skip others
  146. run := func(ctx context.Context, data interface{}) (jsonutils.JSONObject, error) {
  147. defer func() {
  148. man.sessions.Remove(ai.Id)
  149. }()
  150. runErr := man.sessions.Run(ai.Id)
  151. // TODO: try to close local forwarding?
  152. _, err := db.Update(ai, func() error {
  153. err := man.sessions.Err(ai.Id)
  154. if err != nil {
  155. ai.Status = api.AnsiblePlaybookStatusCanceled
  156. } else if runErr != nil {
  157. log.Warningf("playbook %s(%s) failed: %v", ai.Name, ai.Id, runErr)
  158. ai.Status = api.AnsiblePlaybookStatusFailed
  159. } else {
  160. ai.Status = api.AnsiblePlaybookStatusSucceeded
  161. }
  162. ai.EndTime = time.Now()
  163. return nil
  164. })
  165. if err != nil {
  166. log.Errorf("updating ansible playbook failed: %v", err)
  167. }
  168. return nil, runErr
  169. }
  170. PlaybookWorker.DelayTask(ctx, run, nil)
  171. return nil
  172. }
  173. func (ai *SAnsiblePlaybookInstance) stopPlaybook(ctx context.Context, userCred mcclient.TokenCredential) error {
  174. man := AnsiblePlaybookInstanceManager
  175. if !man.sessions.Has(ai.Id) {
  176. return errors.Error("playbook is not running")
  177. }
  178. // the playbook will be removed from session map in runPlaybook() on return from run
  179. man.sessions.Stop(ai.Id)
  180. return nil
  181. }
  182. func (ai *SAnsiblePlaybookInstance) getMaxOutputLength() int {
  183. return OutputMaxBytes
  184. }
  185. func (ai *SAnsiblePlaybookInstance) getOutput() string {
  186. return ai.Output
  187. }
  188. func (ai *SAnsiblePlaybookInstance) setOutput(s string) {
  189. ai.Output = s
  190. }
  191. var PlaybookWorker *workmanager.SWorkManager
  192. func taskFailed(ctx context.Context, reason string) {
  193. if taskId := ctx.Value(appctx.APP_CONTEXT_KEY_TASK_ID); taskId != nil {
  194. session := auth.GetAdminSessionWithInternal(ctx, "")
  195. devtool.DevtoolTasks.TaskFailed2(session, taskId.(string), reason)
  196. } else {
  197. log.Warningf("Reqeuest task failed missing task id, with reason: %s", reason)
  198. }
  199. }
  200. func taskCompleted(ctx context.Context, data jsonutils.JSONObject) {
  201. if taskId := ctx.Value(appctx.APP_CONTEXT_KEY_TASK_ID); taskId != nil {
  202. session := auth.GetAdminSessionWithInternal(ctx, "")
  203. devtool.DevtoolTasks.TaskComplete(session, taskId.(string), data)
  204. } else {
  205. log.Warningf("Reqeuest task failed missing task id, with data: %v", data)
  206. }
  207. }
  208. func InitPlaybookWorker() {
  209. PlaybookWorker = workmanager.NewWorkManger("PlaybookWorker", taskFailed, taskCompleted, options.Options.PlaybookWorkerCount)
  210. }