base.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  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 modulebase
  15. import (
  16. "fmt"
  17. "io"
  18. "net/http"
  19. "net/url"
  20. "regexp"
  21. "strings"
  22. "time"
  23. "yunion.io/x/jsonutils"
  24. "yunion.io/x/log"
  25. "yunion.io/x/pkg/errors"
  26. "yunion.io/x/pkg/util/httputils"
  27. "yunion.io/x/pkg/util/printutils"
  28. "yunion.io/x/pkg/util/sets"
  29. "yunion.io/x/onecloud/pkg/httperrors"
  30. "yunion.io/x/onecloud/pkg/mcclient"
  31. )
  32. type BaseManager struct {
  33. serviceType string
  34. endpointType string
  35. version string
  36. // apiVersion string
  37. columns []string
  38. adminColumns []string
  39. specificMethods sets.String
  40. }
  41. func NewBaseManager(serviceType, endpointType, version string, columns, adminColumns []string) *BaseManager {
  42. return &BaseManager{
  43. serviceType: serviceType,
  44. endpointType: endpointType,
  45. version: version,
  46. // apiVersion: apiVersion,
  47. columns: columns,
  48. adminColumns: adminColumns,
  49. specificMethods: sets.NewString(),
  50. }
  51. }
  52. func (m *BaseManager) GetSpecificMethods() sets.String {
  53. return m.specificMethods
  54. }
  55. func (m *BaseManager) SetSpecificMethods(ms ...string) {
  56. m.specificMethods = sets.NewString(ms...)
  57. }
  58. func (this *BaseManager) GetColumns(session *mcclient.ClientSession) []string {
  59. cols := this.columns
  60. if session.HasSystemAdminPrivilege() && len(this.adminColumns) > 0 {
  61. cols = append(cols, this.adminColumns...)
  62. }
  63. return cols
  64. }
  65. func (this *BaseManager) SetVersion(v string) {
  66. this.version = v
  67. }
  68. /*func (this *BaseManager) GetApiVersion() string {
  69. return this.apiVersion
  70. }*/
  71. func (this *BaseManager) versionedURL(path string) string {
  72. offset := 0
  73. for ; path[offset] == '/'; offset++ {
  74. }
  75. var ret string
  76. if len(this.version) > 0 {
  77. ret = fmt.Sprintf("/%s/%s", this.version, path[offset:])
  78. } else {
  79. ret = fmt.Sprintf("/%s", path[offset:])
  80. }
  81. // log.Debugf("versionedURL %s %s => %s", this.version, path, ret)
  82. return ret
  83. }
  84. func (this *BaseManager) jsonRequest(session *mcclient.ClientSession,
  85. method httputils.THttpMethod, path string,
  86. header http.Header, body jsonutils.JSONObject) (http.Header, jsonutils.JSONObject, error) {
  87. hdr, resp, err := session.JSONVersionRequest(this.serviceType, this.endpointType,
  88. method, this.versionedURL(path),
  89. header, body)
  90. if err != nil {
  91. if e, ok := err.(*httputils.JSONClientError); ok && strings.Contains(e.Details, e.Request.Url) {
  92. switch e.Class {
  93. case errors.ErrConnectRefused.Error():
  94. return nil, nil, httperrors.NewServiceAbnormalError("%s service is abnormal, please check service status", this.serviceType)
  95. case errors.ErrNetwork.Error():
  96. return nil, nil, httperrors.NewServiceAbnormalError("%s service is abnormal or network error, please try again", this.serviceType)
  97. case errors.ErrDNS.Error():
  98. return nil, nil, httperrors.NewServiceAbnormalError("%s service dns resolve error, please check dns setting", this.serviceType)
  99. case errors.ErrTimeout.Error():
  100. return nil, nil, httperrors.NewServiceAbnormalError("%s service request timeout, please try again later", this.serviceType)
  101. }
  102. }
  103. return nil, nil, err
  104. }
  105. return hdr, resp, nil
  106. }
  107. func (this *BaseManager) rawRequest(session *mcclient.ClientSession,
  108. method httputils.THttpMethod, path string,
  109. header http.Header, body io.Reader) (*http.Response, error) {
  110. return session.RawVersionRequest(this.serviceType, this.endpointType,
  111. method, this.versionedURL(path),
  112. header, body)
  113. }
  114. /*func (this *BaseManager) GetBaseUrl(s *mcclient.ClientSession) (string, error) {
  115. return s.GetBaseUrl(this.serviceType, this.endpointType)
  116. }*/
  117. func (this *BaseManager) rawBaseUrlRequest(s *mcclient.ClientSession,
  118. method httputils.THttpMethod, path string,
  119. header http.Header, body io.Reader) (*http.Response, error) {
  120. baseUrlF := func(baseurl string) string {
  121. obj, _ := url.Parse(baseurl)
  122. lastSlashPos := strings.LastIndex(obj.Path, "/")
  123. if lastSlashPos >= 0 {
  124. lastSeg := obj.Path[lastSlashPos+1:]
  125. verReg := regexp.MustCompile(`^v\d+`)
  126. if verReg.MatchString(lastSeg) {
  127. obj.Path = obj.Path[:lastSlashPos]
  128. }
  129. }
  130. ret := obj.String()
  131. log.Debugf("baseurl %s ret %s", baseurl, ret)
  132. return ret
  133. }
  134. return s.RawBaseUrlRequest(
  135. this.serviceType, this.endpointType,
  136. method, this.versionedURL(path),
  137. header, body, baseUrlF)
  138. }
  139. /*type ListResult struct {
  140. Data []jsonutils.JSONObject `json:"data,allowempty"`
  141. Total int
  142. Limit int
  143. Offset int
  144. NextMarker string
  145. MarkerField string
  146. MarkerOrder string
  147. }*/
  148. func ListResult2JSONWithKey(result *printutils.ListResult, key string) jsonutils.JSONObject {
  149. obj := jsonutils.NewDict()
  150. if result.Total > 0 {
  151. obj.Add(jsonutils.NewInt(int64(result.Total)), "total")
  152. }
  153. if result.Totals != nil {
  154. obj.Add(result.Totals, "totals")
  155. }
  156. if result.Limit > 0 {
  157. obj.Add(jsonutils.NewInt(int64(result.Limit)), "limit")
  158. }
  159. if result.Offset > 0 {
  160. obj.Add(jsonutils.NewInt(int64(result.Offset)), "offset")
  161. }
  162. if len(result.NextMarker) > 0 || len(result.MarkerField) > 0 {
  163. obj.Add(jsonutils.NewString(result.NextMarker), "next_marker")
  164. }
  165. if len(result.MarkerField) > 0 {
  166. obj.Add(jsonutils.NewString(result.MarkerField), "marker_field")
  167. }
  168. if len(result.MarkerOrder) > 0 {
  169. obj.Add(jsonutils.NewString(result.MarkerOrder), "marker_order")
  170. }
  171. arr := jsonutils.NewArray(result.Data...)
  172. obj.Add(arr, key)
  173. return obj
  174. }
  175. func ListResult2JSON(result *printutils.ListResult) jsonutils.JSONObject {
  176. return ListResult2JSONWithKey(result, "data")
  177. }
  178. func JSON2ListResult(result jsonutils.JSONObject) *printutils.ListResult {
  179. total, _ := result.Int("total")
  180. limit, _ := result.Int("limit")
  181. offset, _ := result.Int("offset")
  182. nextMarker, _ := result.GetString("next_marker")
  183. markerField, _ := result.GetString("marker_field")
  184. markerOrder, _ := result.GetString("marker_order")
  185. data, _ := result.GetArray("data")
  186. if len(markerField) == 0 && total == 0 {
  187. total = int64(len(data))
  188. }
  189. totalJson, _ := result.Get("totals")
  190. return &printutils.ListResult{
  191. Data: data,
  192. Total: int(total), Limit: int(limit), Offset: int(offset),
  193. Totals: totalJson,
  194. NextMarker: nextMarker,
  195. MarkerField: markerField,
  196. MarkerOrder: markerOrder,
  197. }
  198. }
  199. func (this *BaseManager) _list(session *mcclient.ClientSession, path, responseKey string) (*printutils.ListResult, error) {
  200. _, body, err := this.jsonRequest(session, "GET", path, nil, nil)
  201. if err != nil {
  202. return nil, err
  203. }
  204. if body == nil {
  205. return nil, fmt.Errorf("empty response")
  206. }
  207. rets, err := body.GetArray(responseKey)
  208. if err != nil {
  209. return nil, errors.Wrapf(err, "key:%s", responseKey)
  210. }
  211. nextMarker, _ := body.GetString("next_marker")
  212. markerField, _ := body.GetString("marker_field")
  213. markerOrder, _ := body.GetString("marker_order")
  214. total, _ := body.Int("total")
  215. limit, _ := body.Int("limit")
  216. offset, _ := body.Int("offset")
  217. if len(nextMarker) == 0 && total == 0 {
  218. total = int64(len(rets))
  219. }
  220. totalJson, _ := body.Get("totals")
  221. return &printutils.ListResult{
  222. Data: rets,
  223. Total: int(total), Limit: int(limit), Offset: int(offset),
  224. Totals: totalJson,
  225. NextMarker: nextMarker,
  226. MarkerField: markerField,
  227. MarkerOrder: markerOrder,
  228. }, nil
  229. }
  230. func (this *BaseManager) _submit(session *mcclient.ClientSession, method httputils.THttpMethod, path string, body jsonutils.JSONObject, respKey string) (jsonutils.JSONObject, error) {
  231. hdr, resp, e := this.jsonRequest(session, method, path, nil, body)
  232. if e != nil {
  233. return nil, e
  234. }
  235. if method == "HEAD" {
  236. ret := jsonutils.NewDict()
  237. hdrPrefix := fmt.Sprintf("x-%s-", respKey)
  238. for k, v := range hdr {
  239. k = strings.ToLower(k)
  240. if strings.HasPrefix(k, hdrPrefix) && len(v) > 0 {
  241. if len(v) == 1 {
  242. ret.Add(jsonutils.NewString(v[0]), k)
  243. } else {
  244. ret.Add(jsonutils.NewStringArray(v), k)
  245. }
  246. }
  247. }
  248. return ret, nil
  249. }
  250. if resp == nil { // no reslt
  251. return jsonutils.NewDict(), nil
  252. }
  253. if len(respKey) == 0 {
  254. return resp, nil
  255. }
  256. ret, e := resp.Get(respKey)
  257. if e != nil {
  258. return nil, errors.Wrapf(e, "key:%s", respKey)
  259. }
  260. return ret, nil
  261. }
  262. /*type SubmitResult struct {
  263. Status int
  264. Id interface{}
  265. Data jsonutils.JSONObject
  266. }*/
  267. //type SubmitResult printutils.SubmitResult
  268. func SubmitResults2JSON(results []printutils.SubmitResult) jsonutils.JSONObject {
  269. arr := jsonutils.NewArray()
  270. now := time.Now().In(httperrors.GetTimeZone())
  271. for _, r := range results {
  272. obj := jsonutils.NewDict()
  273. obj.Add(jsonutils.NewInt(int64(r.Status)), "status")
  274. obj.Add(jsonutils.Marshal(r.Id), "id")
  275. obj.Add(r.Data, "data")
  276. if r.Status >= 400 {
  277. obj.Add(jsonutils.NewString(now.Format(time.RFC3339)), "data", "time")
  278. }
  279. arr.Add(obj)
  280. }
  281. body := jsonutils.NewDict()
  282. body.Add(arr, "data")
  283. return body
  284. }
  285. func SubmitResults2ListResult(results []printutils.SubmitResult) *printutils.ListResult {
  286. arr := make([]jsonutils.JSONObject, 0)
  287. for _, r := range results {
  288. if r.Status == 200 {
  289. arr = append(arr, r.Data)
  290. }
  291. }
  292. return &printutils.ListResult{Data: arr, Total: len(arr), Limit: 0, Offset: 0}
  293. }
  294. func (this *BaseManager) _batch(session *mcclient.ClientSession, method httputils.THttpMethod, path string, ids []string, body jsonutils.JSONObject, respKey string) []printutils.SubmitResult {
  295. return BatchDo(ids, func(id string) (jsonutils.JSONObject, error) {
  296. u := fmt.Sprintf(path, url.PathEscape(id))
  297. return this._submit(session, method, u, body, respKey)
  298. })
  299. }
  300. func addResult(results chan printutils.SubmitResult, id interface{}, r jsonutils.JSONObject, e error) {
  301. if e != nil {
  302. ecls, ok := e.(*httputils.JSONClientError)
  303. if ok {
  304. results <- printutils.SubmitResult{Status: ecls.Code, Id: id, Data: jsonutils.Marshal(ecls)}
  305. } else {
  306. results <- printutils.SubmitResult{Status: 400, Id: id, Data: jsonutils.NewString(e.Error())}
  307. }
  308. } else {
  309. results <- printutils.SubmitResult{Status: 200, Id: id, Data: r}
  310. }
  311. }
  312. func waitResults(results chan printutils.SubmitResult, length int) []printutils.SubmitResult {
  313. ret := make([]printutils.SubmitResult, length)
  314. for i := 0; i < length; i++ {
  315. ret[i] = <-results
  316. }
  317. return ret
  318. }
  319. func BatchDo(ids []string, do func(id string) (jsonutils.JSONObject, error)) []printutils.SubmitResult {
  320. results := make(chan printutils.SubmitResult, len(ids))
  321. for i := 0; i < len(ids); i++ {
  322. go func(id string) {
  323. r, e := do(id)
  324. addResult(results, id, r, e)
  325. }(ids[i])
  326. }
  327. return waitResults(results, len(ids))
  328. }
  329. func BatchParamsDo(
  330. ids []string, params []jsonutils.JSONObject,
  331. do func(id string, param jsonutils.JSONObject) (jsonutils.JSONObject, error),
  332. ) []printutils.SubmitResult {
  333. results := make(chan printutils.SubmitResult, len(ids))
  334. for i := 0; i < len(ids); i++ {
  335. go func(id string, param jsonutils.JSONObject) {
  336. r, e := do(id, param)
  337. addResult(results, id, r, e)
  338. }(ids[i], params[i])
  339. }
  340. return waitResults(results, len(ids))
  341. }
  342. func BatchDoClassAction(
  343. batchParams []jsonutils.JSONObject, do func(jsonutils.JSONObject) (jsonutils.JSONObject, error),
  344. ) []printutils.SubmitResult {
  345. results := make(chan printutils.SubmitResult, len(batchParams))
  346. for i := 0; i < len(batchParams); i++ {
  347. go func(params jsonutils.JSONObject) {
  348. r, e := do(params)
  349. addResult(results, params, r, e)
  350. }(batchParams[i])
  351. }
  352. return waitResults(results, len(batchParams))
  353. }
  354. func (this *BaseManager) _get(session *mcclient.ClientSession, path string, respKey string) (jsonutils.JSONObject, error) {
  355. /* _, body, err := this.jsonRequest(session, "GET", path, nil, nil)
  356. if err != nil {
  357. return nil, err
  358. }
  359. con, err := body.Get(responseKey)
  360. if err != nil {
  361. return nil, err
  362. }
  363. return con, nil */
  364. return this._submit(session, "GET", path, nil, respKey)
  365. }
  366. func (this *BaseManager) _head(session *mcclient.ClientSession, path string, respKey string) (jsonutils.JSONObject, error) {
  367. return this._submit(session, "HEAD", path, nil, respKey)
  368. }
  369. func (this *BaseManager) _post(session *mcclient.ClientSession, path string, body jsonutils.JSONObject, respKey string) (jsonutils.JSONObject, error) {
  370. return this._submit(session, "POST", path, body, respKey)
  371. }
  372. func (this *BaseManager) _put(session *mcclient.ClientSession, path string, body jsonutils.JSONObject, respKey string) (jsonutils.JSONObject, error) {
  373. return this._submit(session, "PUT", path, body, respKey)
  374. }
  375. func (this *BaseManager) _patch(session *mcclient.ClientSession, path string, body jsonutils.JSONObject, respKey string) (jsonutils.JSONObject, error) {
  376. return this._submit(session, "PATCH", path, body, respKey)
  377. }
  378. func (this *BaseManager) _delete(session *mcclient.ClientSession, path string, body jsonutils.JSONObject, respKey string) (jsonutils.JSONObject, error) {
  379. return this._submit(session, "DELETE", path, body, respKey)
  380. }