| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400 |
- // Copyright 2019 Yunion
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package nutanix
- import (
- "context"
- "fmt"
- "io"
- "net/http"
- "net/url"
- "time"
- "yunion.io/x/jsonutils"
- "yunion.io/x/log"
- "yunion.io/x/pkg/errors"
- "yunion.io/x/pkg/util/httputils"
- api "yunion.io/x/cloudmux/pkg/apis/compute"
- "yunion.io/x/cloudmux/pkg/cloudprovider"
- )
- const (
- NUTANIX_VERSION_V2 = "v2.0"
- NUTANIX_VERSION_V0_8 = "v0.8"
- NUTANIX_VERSION_V3 = "v3"
- CLOUD_PROVIDER_NUTANIX = api.CLOUD_PROVIDER_NUTANIX
- )
- type NutanixClientConfig struct {
- cpcfg cloudprovider.ProviderConfig
- username string
- password string
- host string
- port int
- debug bool
- }
- func NewNutanixClientConfig(host, username, password string, port int) *NutanixClientConfig {
- cfg := &NutanixClientConfig{
- host: host,
- username: username,
- password: password,
- port: port,
- }
- return cfg
- }
- func (cfg *NutanixClientConfig) CloudproviderConfig(cpcfg cloudprovider.ProviderConfig) *NutanixClientConfig {
- cfg.cpcfg = cpcfg
- return cfg
- }
- func (cfg *NutanixClientConfig) Debug(debug bool) *NutanixClientConfig {
- cfg.debug = debug
- return cfg
- }
- func (cfg NutanixClientConfig) Copy() NutanixClientConfig {
- return cfg
- }
- type SNutanixClient struct {
- *NutanixClientConfig
- }
- func NewNutanixClient(cfg *NutanixClientConfig) (*SNutanixClient, error) {
- client := &SNutanixClient{
- NutanixClientConfig: cfg,
- }
- return client, client.auth()
- }
- func (self *SNutanixClient) GetRegion() (*SRegion, error) {
- return &SRegion{cli: self}, nil
- }
- func (self *SNutanixClient) GetAccountId() string {
- return self.host
- }
- func (self *SNutanixClient) GetCapabilities() []string {
- return []string{
- cloudprovider.CLOUD_CAPABILITY_COMPUTE,
- cloudprovider.CLOUD_CAPABILITY_NETWORK,
- }
- }
- func (self *SNutanixClient) auth() error {
- _, err := self.list("clusters", nil, nil)
- return err
- }
- func (self *SNutanixClient) _getBaseDomain(version string) string {
- if len(version) == 0 {
- version = NUTANIX_VERSION_V2
- }
- return fmt.Sprintf("https://%s:%d/api/nutanix/%s", self.host, self.port, version)
- }
- func (self *SNutanixClient) getBaseDomain() string {
- return self._getBaseDomain("")
- }
- func (self *SNutanixClient) getBaseDomainV0_8() string {
- return self._getBaseDomain(NUTANIX_VERSION_V0_8)
- }
- func (cli *SNutanixClient) getDefaultClient(timeout time.Duration) *http.Client {
- client := httputils.GetDefaultClient()
- if timeout > 0 {
- client = httputils.GetTimeoutClient(timeout)
- }
- proxy := func(req *http.Request) (*url.URL, error) {
- req.SetBasicAuth(cli.username, cli.password)
- if cli.cpcfg.ProxyFunc != nil {
- cli.cpcfg.ProxyFunc(req)
- }
- return nil, nil
- }
- httputils.SetClientProxyFunc(client, proxy)
- ts, _ := client.Transport.(*http.Transport)
- client.Transport = cloudprovider.GetCheckTransport(ts, func(req *http.Request) (func(resp *http.Response) error, error) {
- if cli.cpcfg.ReadOnly {
- if req.Method == "GET" {
- return nil, nil
- }
- return nil, errors.Wrapf(cloudprovider.ErrAccountReadOnly, "%s %s", req.Method, req.URL.Path)
- }
- return nil, nil
- })
- return client
- }
- func (self *SNutanixClient) _list(res string, params url.Values) (jsonutils.JSONObject, error) {
- url := fmt.Sprintf("%s/%s", self.getBaseDomain(), res)
- if len(params) > 0 {
- url = fmt.Sprintf("%s?%s", url, params.Encode())
- }
- return self.jsonRequest(httputils.GET, url, nil)
- }
- func (self *SNutanixClient) _post(res string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
- url := fmt.Sprintf("%s/%s", self.getBaseDomain(), res)
- if body == nil {
- body = jsonutils.NewDict()
- }
- return self.jsonRequest(httputils.POST, url, body)
- }
- func (self *SNutanixClient) _update(res, id string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
- url := fmt.Sprintf("%s/%s/%s", self.getBaseDomain(), res, id)
- if body == nil {
- body = jsonutils.NewDict()
- }
- return self.jsonRequest(httputils.PUT, url, body)
- }
- func (self *SNutanixClient) _upload(res, id string, header http.Header, body io.Reader) (jsonutils.JSONObject, error) {
- url := fmt.Sprintf("%s/%s/%s", self.getBaseDomainV0_8(), res, id)
- return self.rawRequest(httputils.PUT, url, header, body)
- }
- func (self *SNutanixClient) upload(res, id string, header http.Header, body io.Reader) (jsonutils.JSONObject, error) {
- return self._upload(res, id, header, body)
- }
- func (self *SNutanixClient) _delete(res, id string) (jsonutils.JSONObject, error) {
- url := fmt.Sprintf("%s/%s/%s", self.getBaseDomain(), res, id)
- return self.jsonRequest(httputils.DELETE, url, nil)
- }
- func (self *SNutanixClient) list(res string, params url.Values, retVal interface{}) (int, error) {
- resp, err := self._list(res, params)
- if err != nil {
- return 0, errors.Wrapf(err, "get %s", res)
- }
- if retVal != nil {
- err = resp.Unmarshal(retVal, "entities")
- if err != nil {
- return 0, errors.Wrapf(err, "resp.Unmarshal")
- }
- }
- total, err := resp.Int("metadata", "total_entities")
- if err != nil {
- return 0, errors.Wrapf(err, "get metadata total_entities")
- }
- return int(total), nil
- }
- func (self *SNutanixClient) delete(res, id string) error {
- resp, err := self._delete(res, id)
- if err != nil {
- return errors.Wrapf(err, "delete %s", res)
- }
- if resp != nil && resp.Contains("task_uuid") {
- task := struct {
- TaskUUID string
- }{}
- resp.Unmarshal(&task)
- if len(task.TaskUUID) > 0 {
- _, err = self.wait(task.TaskUUID)
- if err != nil {
- return err
- }
- }
- }
- return nil
- }
- func (self *SNutanixClient) wait(taskId string) (string, error) {
- resId := ""
- err := cloudprovider.Wait(time.Second*5, time.Minute*10, func() (bool, error) {
- task, err := self.getTask(taskId)
- if err != nil {
- return false, err
- }
- for _, entity := range task.EntityList {
- if len(entity.EntityID) > 0 {
- resId = entity.EntityID
- }
- }
- log.Debugf("task %s %s status: %s", task.OperationType, task.UUID, task.ProgressStatus)
- if task.ProgressStatus == "Succeeded" {
- return true, nil
- }
- if task.ProgressStatus == "Failed" {
- return false, errors.Errorf("%s", jsonutils.Marshal(task.MetaResponse).String())
- }
- return false, nil
- })
- return resId, errors.Wrapf(err, "wait task %s", taskId)
- }
- func (self *SNutanixClient) update(res, id string, body jsonutils.JSONObject, retVal interface{}) error {
- resp, err := self._update(res, id, body)
- if err != nil {
- return errors.Wrapf(err, "update %s/%s %v", res, id, body)
- }
- task := struct {
- TaskUUID string
- }{}
- resp.Unmarshal(&task)
- if len(task.TaskUUID) > 0 {
- _, err = self.wait(task.TaskUUID)
- if err != nil {
- return err
- }
- }
- if retVal != nil {
- return resp.Unmarshal(retVal)
- }
- return nil
- }
- func (self *SNutanixClient) post(res string, body jsonutils.JSONObject, retVal interface{}) error {
- resp, err := self._post(res, body)
- if err != nil {
- return errors.Wrapf(err, "post %s %v", res, body)
- }
- if retVal != nil {
- if resp.Contains("entities") {
- err = resp.Unmarshal(retVal, "entities")
- } else {
- err = resp.Unmarshal(retVal)
- }
- return err
- }
- return nil
- }
- func (self *SNutanixClient) listAll(res string, params url.Values, retVal interface{}) error {
- if len(params) == 0 {
- params = url.Values{}
- }
- entities := []jsonutils.JSONObject{}
- page, count := 1, 1024
- for {
- params.Set("count", fmt.Sprintf("%d", count))
- params.Set("page", fmt.Sprintf("%d", page))
- resp, err := self._list(res, params)
- if err != nil {
- return errors.Wrapf(err, "list %s", res)
- }
- _entities, err := resp.GetArray("entities")
- if err != nil {
- return errors.Wrapf(err, "resp get entities")
- }
- entities = append(entities, _entities...)
- totalEntities, err := resp.Int("metadata", "total_entities")
- if err != nil {
- return errors.Wrapf(err, "get resp total_entities")
- }
- if int64(page*count) >= totalEntities {
- break
- }
- page++
- }
- return jsonutils.Update(retVal, entities)
- }
- func (self *SNutanixClient) get(res string, id string, params url.Values, retVal interface{}) error {
- url := fmt.Sprintf("%s/%s/%s", self.getBaseDomain(), res, id)
- if len(params) > 0 {
- url = fmt.Sprintf("%s?%s", url, params.Encode())
- }
- resp, err := self.jsonRequest(httputils.GET, url, nil)
- if err != nil {
- return errors.Wrapf(err, "get %s/%s", res, id)
- }
- if retVal != nil {
- return resp.Unmarshal(retVal)
- }
- return nil
- }
- func (cli *SNutanixClient) GetCloudRegionExternalIdPrefix() string {
- return fmt.Sprintf("%s/%s", CLOUD_PROVIDER_NUTANIX, cli.cpcfg.Id)
- }
- func (self *SNutanixClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
- subAccount := cloudprovider.SSubAccount{
- Id: self.GetAccountId(),
- Account: self.username,
- Name: self.cpcfg.Name,
- HealthStatus: api.CLOUD_PROVIDER_HEALTH_NORMAL,
- }
- return []cloudprovider.SSubAccount{subAccount}, nil
- }
- func (self *SNutanixClient) jsonRequest(method httputils.THttpMethod, url string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
- client := self.getDefaultClient(time.Duration(0))
- return _jsonRequest(client, method, url, nil, body, self.debug)
- }
- type sNutanixError struct {
- DetailedMessage string
- Message string
- ErrorCode struct {
- Code int
- HelpUrl string
- }
- }
- func (self *sNutanixError) Error() string {
- return jsonutils.Marshal(self).String()
- }
- func (self *sNutanixError) ParseErrorFromJsonResponse(statusCode int, status string, body jsonutils.JSONObject) error {
- if body != nil {
- body.Unmarshal(self)
- }
- if self.ErrorCode.Code == 1202 {
- return errors.Wrapf(cloudprovider.ErrNotFound, "%s", self.Error())
- }
- return self
- }
- func _jsonRequest(cli *http.Client, method httputils.THttpMethod, url string, header http.Header, body jsonutils.JSONObject, debug bool) (jsonutils.JSONObject, error) {
- client := httputils.NewJsonClient(cli)
- req := httputils.NewJsonRequest(method, url, body)
- ne := &sNutanixError{}
- _, resp, err := client.Send(context.Background(), req, ne, debug)
- return resp, err
- }
- func (self *SNutanixClient) rawRequest(method httputils.THttpMethod, url string, header http.Header, body io.Reader) (jsonutils.JSONObject, error) {
- client := self.getDefaultClient(time.Hour * 5)
- _resp, err := _rawRequest(client, method, url, header, body, false)
- _, resp, err := httputils.ParseJSONResponse("", _resp, err, self.debug)
- return resp, err
- }
- func _rawRequest(cli *http.Client, method httputils.THttpMethod, url string, header http.Header, body io.Reader, debug bool) (*http.Response, error) {
- return httputils.Request(cli, context.Background(), method, url, header, body, debug)
- }
- func (self *SNutanixClient) GetIRegions() ([]cloudprovider.ICloudRegion, error) {
- region := &SRegion{cli: self}
- return []cloudprovider.ICloudRegion{region}, nil
- }
- func (self *SNutanixClient) getTask(id string) (*STask, error) {
- task := &STask{}
- return task, self.get("tasks", id, nil, task)
- }
|