yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/nutanix/nutanix.go (about)

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