nutanix.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  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 nutanix
  15. import (
  16. "context"
  17. "fmt"
  18. "io"
  19. "net/http"
  20. "net/url"
  21. "time"
  22. "yunion.io/x/jsonutils"
  23. "yunion.io/x/log"
  24. "yunion.io/x/pkg/errors"
  25. "yunion.io/x/pkg/util/httputils"
  26. api "yunion.io/x/cloudmux/pkg/apis/compute"
  27. "yunion.io/x/cloudmux/pkg/cloudprovider"
  28. )
  29. const (
  30. NUTANIX_VERSION_V2 = "v2.0"
  31. NUTANIX_VERSION_V0_8 = "v0.8"
  32. NUTANIX_VERSION_V3 = "v3"
  33. CLOUD_PROVIDER_NUTANIX = api.CLOUD_PROVIDER_NUTANIX
  34. )
  35. type NutanixClientConfig struct {
  36. cpcfg cloudprovider.ProviderConfig
  37. username string
  38. password string
  39. host string
  40. port int
  41. debug bool
  42. }
  43. func NewNutanixClientConfig(host, username, password string, port int) *NutanixClientConfig {
  44. cfg := &NutanixClientConfig{
  45. host: host,
  46. username: username,
  47. password: password,
  48. port: port,
  49. }
  50. return cfg
  51. }
  52. func (cfg *NutanixClientConfig) CloudproviderConfig(cpcfg cloudprovider.ProviderConfig) *NutanixClientConfig {
  53. cfg.cpcfg = cpcfg
  54. return cfg
  55. }
  56. func (cfg *NutanixClientConfig) Debug(debug bool) *NutanixClientConfig {
  57. cfg.debug = debug
  58. return cfg
  59. }
  60. func (cfg NutanixClientConfig) Copy() NutanixClientConfig {
  61. return cfg
  62. }
  63. type SNutanixClient struct {
  64. *NutanixClientConfig
  65. }
  66. func NewNutanixClient(cfg *NutanixClientConfig) (*SNutanixClient, error) {
  67. client := &SNutanixClient{
  68. NutanixClientConfig: cfg,
  69. }
  70. return client, client.auth()
  71. }
  72. func (self *SNutanixClient) GetRegion() (*SRegion, error) {
  73. return &SRegion{cli: self}, nil
  74. }
  75. func (self *SNutanixClient) GetAccountId() string {
  76. return self.host
  77. }
  78. func (self *SNutanixClient) GetCapabilities() []string {
  79. return []string{
  80. cloudprovider.CLOUD_CAPABILITY_COMPUTE,
  81. cloudprovider.CLOUD_CAPABILITY_NETWORK,
  82. }
  83. }
  84. func (self *SNutanixClient) auth() error {
  85. _, err := self.list("clusters", nil, nil)
  86. return err
  87. }
  88. func (self *SNutanixClient) _getBaseDomain(version string) string {
  89. if len(version) == 0 {
  90. version = NUTANIX_VERSION_V2
  91. }
  92. return fmt.Sprintf("https://%s:%d/api/nutanix/%s", self.host, self.port, version)
  93. }
  94. func (self *SNutanixClient) getBaseDomain() string {
  95. return self._getBaseDomain("")
  96. }
  97. func (self *SNutanixClient) getBaseDomainV0_8() string {
  98. return self._getBaseDomain(NUTANIX_VERSION_V0_8)
  99. }
  100. func (cli *SNutanixClient) getDefaultClient(timeout time.Duration) *http.Client {
  101. client := httputils.GetDefaultClient()
  102. if timeout > 0 {
  103. client = httputils.GetTimeoutClient(timeout)
  104. }
  105. proxy := func(req *http.Request) (*url.URL, error) {
  106. req.SetBasicAuth(cli.username, cli.password)
  107. if cli.cpcfg.ProxyFunc != nil {
  108. cli.cpcfg.ProxyFunc(req)
  109. }
  110. return nil, nil
  111. }
  112. httputils.SetClientProxyFunc(client, proxy)
  113. ts, _ := client.Transport.(*http.Transport)
  114. client.Transport = cloudprovider.GetCheckTransport(ts, func(req *http.Request) (func(resp *http.Response) error, error) {
  115. if cli.cpcfg.ReadOnly {
  116. if req.Method == "GET" {
  117. return nil, nil
  118. }
  119. return nil, errors.Wrapf(cloudprovider.ErrAccountReadOnly, "%s %s", req.Method, req.URL.Path)
  120. }
  121. return nil, nil
  122. })
  123. return client
  124. }
  125. func (self *SNutanixClient) _list(res string, params url.Values) (jsonutils.JSONObject, error) {
  126. url := fmt.Sprintf("%s/%s", self.getBaseDomain(), res)
  127. if len(params) > 0 {
  128. url = fmt.Sprintf("%s?%s", url, params.Encode())
  129. }
  130. return self.jsonRequest(httputils.GET, url, nil)
  131. }
  132. func (self *SNutanixClient) _post(res string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
  133. url := fmt.Sprintf("%s/%s", self.getBaseDomain(), res)
  134. if body == nil {
  135. body = jsonutils.NewDict()
  136. }
  137. return self.jsonRequest(httputils.POST, url, body)
  138. }
  139. func (self *SNutanixClient) _update(res, id string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
  140. url := fmt.Sprintf("%s/%s/%s", self.getBaseDomain(), res, id)
  141. if body == nil {
  142. body = jsonutils.NewDict()
  143. }
  144. return self.jsonRequest(httputils.PUT, url, body)
  145. }
  146. func (self *SNutanixClient) _upload(res, id string, header http.Header, body io.Reader) (jsonutils.JSONObject, error) {
  147. url := fmt.Sprintf("%s/%s/%s", self.getBaseDomainV0_8(), res, id)
  148. return self.rawRequest(httputils.PUT, url, header, body)
  149. }
  150. func (self *SNutanixClient) upload(res, id string, header http.Header, body io.Reader) (jsonutils.JSONObject, error) {
  151. return self._upload(res, id, header, body)
  152. }
  153. func (self *SNutanixClient) _delete(res, id string) (jsonutils.JSONObject, error) {
  154. url := fmt.Sprintf("%s/%s/%s", self.getBaseDomain(), res, id)
  155. return self.jsonRequest(httputils.DELETE, url, nil)
  156. }
  157. func (self *SNutanixClient) list(res string, params url.Values, retVal interface{}) (int, error) {
  158. resp, err := self._list(res, params)
  159. if err != nil {
  160. return 0, errors.Wrapf(err, "get %s", res)
  161. }
  162. if retVal != nil {
  163. err = resp.Unmarshal(retVal, "entities")
  164. if err != nil {
  165. return 0, errors.Wrapf(err, "resp.Unmarshal")
  166. }
  167. }
  168. total, err := resp.Int("metadata", "total_entities")
  169. if err != nil {
  170. return 0, errors.Wrapf(err, "get metadata total_entities")
  171. }
  172. return int(total), nil
  173. }
  174. func (self *SNutanixClient) delete(res, id string) error {
  175. resp, err := self._delete(res, id)
  176. if err != nil {
  177. return errors.Wrapf(err, "delete %s", res)
  178. }
  179. if resp != nil && resp.Contains("task_uuid") {
  180. task := struct {
  181. TaskUUID string
  182. }{}
  183. resp.Unmarshal(&task)
  184. if len(task.TaskUUID) > 0 {
  185. _, err = self.wait(task.TaskUUID)
  186. if err != nil {
  187. return err
  188. }
  189. }
  190. }
  191. return nil
  192. }
  193. func (self *SNutanixClient) wait(taskId string) (string, error) {
  194. resId := ""
  195. err := cloudprovider.Wait(time.Second*5, time.Minute*10, func() (bool, error) {
  196. task, err := self.getTask(taskId)
  197. if err != nil {
  198. return false, err
  199. }
  200. for _, entity := range task.EntityList {
  201. if len(entity.EntityID) > 0 {
  202. resId = entity.EntityID
  203. }
  204. }
  205. log.Debugf("task %s %s status: %s", task.OperationType, task.UUID, task.ProgressStatus)
  206. if task.ProgressStatus == "Succeeded" {
  207. return true, nil
  208. }
  209. if task.ProgressStatus == "Failed" {
  210. return false, errors.Errorf("%s", jsonutils.Marshal(task.MetaResponse).String())
  211. }
  212. return false, nil
  213. })
  214. return resId, errors.Wrapf(err, "wait task %s", taskId)
  215. }
  216. func (self *SNutanixClient) update(res, id string, body jsonutils.JSONObject, retVal interface{}) error {
  217. resp, err := self._update(res, id, body)
  218. if err != nil {
  219. return errors.Wrapf(err, "update %s/%s %v", res, id, body)
  220. }
  221. task := struct {
  222. TaskUUID string
  223. }{}
  224. resp.Unmarshal(&task)
  225. if len(task.TaskUUID) > 0 {
  226. _, err = self.wait(task.TaskUUID)
  227. if err != nil {
  228. return err
  229. }
  230. }
  231. if retVal != nil {
  232. return resp.Unmarshal(retVal)
  233. }
  234. return nil
  235. }
  236. func (self *SNutanixClient) post(res string, body jsonutils.JSONObject, retVal interface{}) error {
  237. resp, err := self._post(res, body)
  238. if err != nil {
  239. return errors.Wrapf(err, "post %s %v", res, body)
  240. }
  241. if retVal != nil {
  242. if resp.Contains("entities") {
  243. err = resp.Unmarshal(retVal, "entities")
  244. } else {
  245. err = resp.Unmarshal(retVal)
  246. }
  247. return err
  248. }
  249. return nil
  250. }
  251. func (self *SNutanixClient) listAll(res string, params url.Values, retVal interface{}) error {
  252. if len(params) == 0 {
  253. params = url.Values{}
  254. }
  255. entities := []jsonutils.JSONObject{}
  256. page, count := 1, 1024
  257. for {
  258. params.Set("count", fmt.Sprintf("%d", count))
  259. params.Set("page", fmt.Sprintf("%d", page))
  260. resp, err := self._list(res, params)
  261. if err != nil {
  262. return errors.Wrapf(err, "list %s", res)
  263. }
  264. _entities, err := resp.GetArray("entities")
  265. if err != nil {
  266. return errors.Wrapf(err, "resp get entities")
  267. }
  268. entities = append(entities, _entities...)
  269. totalEntities, err := resp.Int("metadata", "total_entities")
  270. if err != nil {
  271. return errors.Wrapf(err, "get resp total_entities")
  272. }
  273. if int64(page*count) >= totalEntities {
  274. break
  275. }
  276. page++
  277. }
  278. return jsonutils.Update(retVal, entities)
  279. }
  280. func (self *SNutanixClient) get(res string, id string, params url.Values, retVal interface{}) error {
  281. url := fmt.Sprintf("%s/%s/%s", self.getBaseDomain(), res, id)
  282. if len(params) > 0 {
  283. url = fmt.Sprintf("%s?%s", url, params.Encode())
  284. }
  285. resp, err := self.jsonRequest(httputils.GET, url, nil)
  286. if err != nil {
  287. return errors.Wrapf(err, "get %s/%s", res, id)
  288. }
  289. if retVal != nil {
  290. return resp.Unmarshal(retVal)
  291. }
  292. return nil
  293. }
  294. func (cli *SNutanixClient) GetCloudRegionExternalIdPrefix() string {
  295. return fmt.Sprintf("%s/%s", CLOUD_PROVIDER_NUTANIX, cli.cpcfg.Id)
  296. }
  297. func (self *SNutanixClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
  298. subAccount := cloudprovider.SSubAccount{
  299. Id: self.GetAccountId(),
  300. Account: self.username,
  301. Name: self.cpcfg.Name,
  302. HealthStatus: api.CLOUD_PROVIDER_HEALTH_NORMAL,
  303. }
  304. return []cloudprovider.SSubAccount{subAccount}, nil
  305. }
  306. func (self *SNutanixClient) jsonRequest(method httputils.THttpMethod, url string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
  307. client := self.getDefaultClient(time.Duration(0))
  308. return _jsonRequest(client, method, url, nil, body, self.debug)
  309. }
  310. type sNutanixError struct {
  311. DetailedMessage string
  312. Message string
  313. ErrorCode struct {
  314. Code int
  315. HelpUrl string
  316. }
  317. }
  318. func (self *sNutanixError) Error() string {
  319. return jsonutils.Marshal(self).String()
  320. }
  321. func (self *sNutanixError) ParseErrorFromJsonResponse(statusCode int, status string, body jsonutils.JSONObject) error {
  322. if body != nil {
  323. body.Unmarshal(self)
  324. }
  325. if self.ErrorCode.Code == 1202 {
  326. return errors.Wrapf(cloudprovider.ErrNotFound, "%s", self.Error())
  327. }
  328. return self
  329. }
  330. func _jsonRequest(cli *http.Client, method httputils.THttpMethod, url string, header http.Header, body jsonutils.JSONObject, debug bool) (jsonutils.JSONObject, error) {
  331. client := httputils.NewJsonClient(cli)
  332. req := httputils.NewJsonRequest(method, url, body)
  333. ne := &sNutanixError{}
  334. _, resp, err := client.Send(context.Background(), req, ne, debug)
  335. return resp, err
  336. }
  337. func (self *SNutanixClient) rawRequest(method httputils.THttpMethod, url string, header http.Header, body io.Reader) (jsonutils.JSONObject, error) {
  338. client := self.getDefaultClient(time.Hour * 5)
  339. _resp, err := _rawRequest(client, method, url, header, body, false)
  340. _, resp, err := httputils.ParseJSONResponse("", _resp, err, self.debug)
  341. return resp, err
  342. }
  343. func _rawRequest(cli *http.Client, method httputils.THttpMethod, url string, header http.Header, body io.Reader, debug bool) (*http.Response, error) {
  344. return httputils.Request(cli, context.Background(), method, url, header, body, debug)
  345. }
  346. func (self *SNutanixClient) GetIRegions() ([]cloudprovider.ICloudRegion, error) {
  347. region := &SRegion{cli: self}
  348. return []cloudprovider.ICloudRegion{region}, nil
  349. }
  350. func (self *SNutanixClient) getTask(id string) (*STask, error) {
  351. task := &STask{}
  352. return task, self.get("tasks", id, nil, task)
  353. }