yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/google/google.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 google
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"io"
    21  	"io/ioutil"
    22  	"net/http"
    23  	"net/url"
    24  	"strings"
    25  	"time"
    26  	"unicode"
    27  
    28  	"golang.org/x/oauth2"
    29  	"golang.org/x/oauth2/google"
    30  	"golang.org/x/oauth2/jwt"
    31  
    32  	"yunion.io/x/jsonutils"
    33  	"yunion.io/x/log"
    34  	"yunion.io/x/pkg/errors"
    35  
    36  	api "yunion.io/x/cloudmux/pkg/apis/compute"
    37  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    38  	"yunion.io/x/onecloud/pkg/util/httputils"
    39  )
    40  
    41  const (
    42  	CLOUD_PROVIDER_GOOGLE    = api.CLOUD_PROVIDER_GOOGLE
    43  	CLOUD_PROVIDER_GOOGLE_CN = "谷歌云"
    44  
    45  	GOOGLE_DEFAULT_REGION = "asia-east1"
    46  
    47  	GOOGLE_API_VERSION         = "v1"
    48  	GOOGLE_MANAGER_API_VERSION = "v1"
    49  
    50  	GOOGLE_STORAGE_API_VERSION    = "v1"
    51  	GOOGLE_CLOUDBUILD_API_VERSION = "v1"
    52  	GOOGLE_BILLING_API_VERSION    = "v1"
    53  	GOOGLE_MONITOR_API_VERSION    = "v3"
    54  	GOOGLE_DBINSTANCE_API_VERSION = "v1beta4"
    55  	GOOGLE_IAM_API_VERSION        = "v1"
    56  
    57  	GOOGLE_BIGQUERY_API_VERSION = "v2"
    58  
    59  	GOOGLE_MANAGER_DOMAIN        = "https://cloudresourcemanager.googleapis.com"
    60  	GOOGLE_COMPUTE_DOMAIN        = "https://www.googleapis.com/compute"
    61  	GOOGLE_STORAGE_DOMAIN        = "https://storage.googleapis.com/storage"
    62  	GOOGLE_CLOUDBUILD_DOMAIN     = "https://cloudbuild.googleapis.com"
    63  	GOOGLE_STORAGE_UPLOAD_DOMAIN = "https://www.googleapis.com/upload/storage"
    64  	GOOGLE_BILLING_DOMAIN        = "https://cloudbilling.googleapis.com"
    65  	GOOGLE_MONITOR_DOMAIN        = "https://monitoring.googleapis.com"
    66  	GOOGLE_DBINSTANCE_DOMAIN     = "https://www.googleapis.com/sql"
    67  	GOOGLE_IAM_DOMAIN            = "https://iam.googleapis.com"
    68  	GOOGLE_BIGQUERY_DOMAIN       = "https://bigquery.googleapis.com/bigquery"
    69  
    70  	MAX_RETRY = 3
    71  )
    72  
    73  var (
    74  	MultiRegions []string = []string{"us", "eu", "asia"}
    75  	DualRegions  []string = []string{"nam4", "eur4"}
    76  )
    77  
    78  type GoogleClientConfig struct {
    79  	cpcfg cloudprovider.ProviderConfig
    80  
    81  	projectId    string
    82  	clientEmail  string
    83  	privateKeyId string
    84  	privateKey   string
    85  
    86  	debug bool
    87  }
    88  
    89  func NewGoogleClientConfig(projectId, clientEmail, privateKeyId, privateKey string) *GoogleClientConfig {
    90  	privateKey = strings.Replace(privateKey, "\\n", "\n", -1)
    91  	cfg := &GoogleClientConfig{
    92  		projectId:    projectId,
    93  		clientEmail:  clientEmail,
    94  		privateKeyId: privateKeyId,
    95  		privateKey:   privateKey,
    96  	}
    97  	return cfg
    98  }
    99  
   100  func (cfg *GoogleClientConfig) CloudproviderConfig(cpcfg cloudprovider.ProviderConfig) *GoogleClientConfig {
   101  	cfg.cpcfg = cpcfg
   102  	return cfg
   103  }
   104  
   105  func (cfg *GoogleClientConfig) Debug(debug bool) *GoogleClientConfig {
   106  	cfg.debug = debug
   107  	return cfg
   108  }
   109  
   110  type SGoogleClient struct {
   111  	*GoogleClientConfig
   112  
   113  	iregions        []cloudprovider.ICloudRegion
   114  	images          []SImage
   115  	snapshots       map[string][]SSnapshot
   116  	globalnetworks  []SGlobalNetwork
   117  	resourcepolices []SResourcePolicy
   118  
   119  	client   *http.Client
   120  	iBuckets []cloudprovider.ICloudBucket
   121  }
   122  
   123  func NewGoogleClient(cfg *GoogleClientConfig) (*SGoogleClient, error) {
   124  	client := SGoogleClient{
   125  		GoogleClientConfig: cfg,
   126  	}
   127  	conf := &jwt.Config{
   128  		Email:        cfg.clientEmail,
   129  		PrivateKeyID: cfg.privateKeyId,
   130  		PrivateKey:   []byte(cfg.privateKey),
   131  		Scopes: []string{
   132  			"https://www.googleapis.com/auth/cloud-platform",
   133  			"https://www.googleapis.com/auth/compute",
   134  			"https://www.googleapis.com/auth/compute.readonly",
   135  			"https://www.googleapis.com/auth/cloud-platform.read-only",
   136  			"https://www.googleapis.com/auth/cloudplatformprojects",
   137  			"https://www.googleapis.com/auth/cloudplatformprojects.readonly",
   138  
   139  			"https://www.googleapis.com/auth/devstorage.full_control",
   140  			"https://www.googleapis.com/auth/devstorage.read_write",
   141  		},
   142  		TokenURL: google.JWTTokenURL,
   143  	}
   144  
   145  	httpClient := cfg.cpcfg.AdaptiveTimeoutHttpClient()
   146  	ts, _ := httpClient.Transport.(*http.Transport)
   147  	httpClient.Transport = cloudprovider.GetCheckTransport(ts, func(req *http.Request) (func(resp *http.Response), error) {
   148  		service := strings.Split(req.URL.Host, ".")[0]
   149  		if service == "www" {
   150  			service = strings.Split(req.URL.Path, "/")[0]
   151  		}
   152  		method, path := req.Method, req.URL.Path
   153  		respCheck := func(resp *http.Response) {
   154  			if resp.StatusCode == 403 {
   155  				if cfg.cpcfg.UpdatePermission != nil {
   156  					cfg.cpcfg.UpdatePermission(service, fmt.Sprintf("%s %s", method, path))
   157  				}
   158  			}
   159  		}
   160  		if cfg.cpcfg.ReadOnly {
   161  			if req.Method == "GET" {
   162  				return respCheck, nil
   163  			}
   164  			return nil, errors.Wrapf(cloudprovider.ErrAccountReadOnly, "%s %s", req.Method, req.URL.Path)
   165  		}
   166  		return respCheck, nil
   167  	})
   168  
   169  	ctx := context.Background()
   170  	ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient)
   171  
   172  	client.client = conf.Client(ctx)
   173  	return &client, client.fetchRegions()
   174  }
   175  
   176  func (self *SGoogleClient) GetAccountId() string {
   177  	return self.clientEmail
   178  }
   179  
   180  func (self *SGoogleClient) fetchRegions() error {
   181  	regions := []SRegion{}
   182  	err := self.ecsListAll("regions", nil, &regions)
   183  	if err != nil {
   184  		return err
   185  	}
   186  
   187  	self.iregions = []cloudprovider.ICloudRegion{}
   188  	for i := 0; i < len(regions); i++ {
   189  		regions[i].client = self
   190  		self.iregions = append(self.iregions, &regions[i])
   191  	}
   192  
   193  	objectstoreCapability := []string{
   194  		cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE,
   195  	}
   196  	for _, region := range MultiRegions {
   197  		_region := SRegion{
   198  			Name:         region,
   199  			client:       self,
   200  			capabilities: objectstoreCapability,
   201  		}
   202  		self.iregions = append(self.iregions, &_region)
   203  	}
   204  	for _, region := range DualRegions {
   205  		_region := SRegion{
   206  			Name:         region,
   207  			client:       self,
   208  			capabilities: objectstoreCapability,
   209  		}
   210  		self.iregions = append(self.iregions, &_region)
   211  	}
   212  	return nil
   213  }
   214  
   215  func (self *SGoogleClient) fetchBuckets() error {
   216  	buckets := []SBucket{}
   217  	params := map[string]string{
   218  		"project": self.projectId,
   219  	}
   220  	err := self.storageListAll("b", params, &buckets)
   221  	if err != nil {
   222  		return errors.Wrap(err, "storageList")
   223  	}
   224  	self.iBuckets = []cloudprovider.ICloudBucket{}
   225  	for i := range buckets {
   226  		region := self.GetRegion(buckets[i].GetLocation())
   227  		if region == nil {
   228  			log.Errorf("failed to found region for bucket %s", buckets[i].GetName())
   229  			continue
   230  		}
   231  		buckets[i].region = region
   232  		self.iBuckets = append(self.iBuckets, &buckets[i])
   233  	}
   234  	return nil
   235  }
   236  
   237  func (self *SGoogleClient) getIBuckets() ([]cloudprovider.ICloudBucket, error) {
   238  	if self.iBuckets == nil {
   239  		err := self.fetchBuckets()
   240  		if err != nil {
   241  			return nil, errors.Wrap(err, "fetchBuckets")
   242  		}
   243  	}
   244  	return self.iBuckets, nil
   245  }
   246  
   247  func jsonRequest(client *http.Client, method httputils.THttpMethod, domain, apiVersion, resource string, params map[string]string, body jsonutils.JSONObject, debug bool) (jsonutils.JSONObject, error) {
   248  	resource = strings.TrimPrefix(resource, fmt.Sprintf("%s/%s/", domain, apiVersion))
   249  	if len(resource) == 0 {
   250  		return nil, cloudprovider.ErrNotFound
   251  	}
   252  	_url := fmt.Sprintf("%s/%s/%s", domain, apiVersion, resource)
   253  	values := url.Values{}
   254  	for k, v := range params {
   255  		values.Set(k, v)
   256  	}
   257  	if len(values) > 0 {
   258  		_url = fmt.Sprintf("%s?%s", _url, values.Encode())
   259  	}
   260  	return _jsonRequest(client, method, _url, body, debug)
   261  }
   262  
   263  func (self *SGoogleClient) ecsGet(resourceType, id string, retval interface{}) error {
   264  	params := map[string]string{
   265  		"filter": fmt.Sprintf(`id="%s"`, id),
   266  	}
   267  	resource := fmt.Sprintf("aggregated/%s", resourceType)
   268  	if strings.Contains(resourceType, "/") {
   269  		resource = resourceType
   270  	}
   271  	resp, err := self.ecsList(resource, params)
   272  	if err != nil {
   273  		return errors.Wrapf(err, "ecsList")
   274  	}
   275  	if resp.Contains("items") {
   276  		if strings.HasPrefix(resource, "aggregated/") {
   277  			items, err := resp.GetMap("items")
   278  			if err != nil {
   279  				return errors.Wrapf(err, "resp.GetMap(items)")
   280  			}
   281  			for _, values := range items {
   282  				if values.Contains(resourceType) {
   283  					arr, err := values.GetArray(resourceType)
   284  					if err != nil {
   285  						return errors.Wrapf(err, "v.GetArray(%s)", resourceType)
   286  					}
   287  					for i := range arr {
   288  						if _id, _ := arr[i].GetString("id"); _id == id {
   289  							return arr[i].Unmarshal(retval)
   290  						}
   291  					}
   292  
   293  				}
   294  			}
   295  		} else if strings.HasPrefix(resource, "global/") {
   296  			items, err := resp.GetArray("items")
   297  			if err != nil {
   298  				return errors.Wrapf(err, "resp.GetMap(items)")
   299  			}
   300  			for i := range items {
   301  				if _id, _ := items[i].GetString("id"); _id == id {
   302  					return items[i].Unmarshal(retval)
   303  				}
   304  			}
   305  		}
   306  	}
   307  	return errors.Wrapf(cloudprovider.ErrNotFound, id)
   308  }
   309  
   310  func (self *SGoogleClient) ecsList(resource string, params map[string]string) (jsonutils.JSONObject, error) {
   311  	return self._ecsList("GET", resource, params)
   312  }
   313  
   314  func (self *SGoogleClient) _ecsList(method, resource string, params map[string]string) (jsonutils.JSONObject, error) {
   315  	resource = fmt.Sprintf("projects/%s/%s", self.projectId, resource)
   316  	return jsonRequest(self.client, httputils.THttpMethod(method), GOOGLE_COMPUTE_DOMAIN, GOOGLE_API_VERSION, resource, params, nil, self.debug)
   317  }
   318  
   319  func (self *SGoogleClient) managerList(resource string, params map[string]string) (jsonutils.JSONObject, error) {
   320  	return jsonRequest(self.client, "GET", GOOGLE_MANAGER_DOMAIN, GOOGLE_MANAGER_API_VERSION, resource, params, nil, self.debug)
   321  }
   322  
   323  func (self *SGoogleClient) managerGet(resource string) (jsonutils.JSONObject, error) {
   324  	return jsonRequest(self.client, "GET", GOOGLE_MANAGER_DOMAIN, GOOGLE_MANAGER_API_VERSION, resource, nil, nil, self.debug)
   325  }
   326  
   327  func (self *SGoogleClient) managerPost(resource string, params map[string]string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
   328  	return jsonRequest(self.client, "POST", GOOGLE_MANAGER_DOMAIN, GOOGLE_MANAGER_API_VERSION, resource, params, body, self.debug)
   329  }
   330  
   331  func (self *SGoogleClient) iamPost(resource string, params map[string]string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
   332  	return jsonRequest(self.client, "POST", GOOGLE_IAM_DOMAIN, GOOGLE_IAM_API_VERSION, resource, params, body, self.debug)
   333  }
   334  
   335  func (self *SGoogleClient) iamList(resource string, params map[string]string) (jsonutils.JSONObject, error) {
   336  	return jsonRequest(self.client, "GET", GOOGLE_IAM_DOMAIN, GOOGLE_IAM_API_VERSION, resource, params, nil, self.debug)
   337  }
   338  
   339  func (self *SGoogleClient) iamGet(resource string) (jsonutils.JSONObject, error) {
   340  	return jsonRequest(self.client, "GET", GOOGLE_IAM_DOMAIN, GOOGLE_IAM_API_VERSION, resource, nil, nil, self.debug)
   341  }
   342  
   343  func (self *SGoogleClient) iamPatch(resource string, params map[string]string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
   344  	return jsonRequest(self.client, "PATCH", GOOGLE_IAM_DOMAIN, GOOGLE_IAM_API_VERSION, resource, params, body, self.debug)
   345  }
   346  
   347  func (self *SGoogleClient) iamDelete(id string, retval interface{}) error {
   348  	resp, err := jsonRequest(self.client, "DELETE", GOOGLE_IAM_DOMAIN, GOOGLE_IAM_API_VERSION, id, nil, nil, self.debug)
   349  	if err != nil {
   350  		return err
   351  	}
   352  	if retval != nil {
   353  		return resp.Unmarshal(retval)
   354  	}
   355  	return nil
   356  }
   357  
   358  func (self *SGoogleClient) iamListAll(resource string, params map[string]string, retval interface{}) error {
   359  	if params == nil {
   360  		params = map[string]string{}
   361  	}
   362  	items := jsonutils.NewArray()
   363  	nextPageToken := ""
   364  	params["pageSize"] = "100"
   365  	for {
   366  		params["pageToken"] = nextPageToken
   367  		resp, err := self.iamList(resource, params)
   368  		if err != nil {
   369  			return errors.Wrap(err, "iamList")
   370  		}
   371  		if resp.Contains("roles") {
   372  			_items, err := resp.GetArray("roles")
   373  			if err != nil {
   374  				return errors.Wrap(err, "resp.GetArray")
   375  			}
   376  			items.Add(_items...)
   377  		}
   378  		nextPageToken, _ = resp.GetString("nextPageToken")
   379  		if len(nextPageToken) == 0 {
   380  			break
   381  		}
   382  	}
   383  	return items.Unmarshal(retval)
   384  }
   385  
   386  func (self *SGoogleClient) ecsListAll(resource string, params map[string]string, retval interface{}) error {
   387  	return self._ecsListAll("GET", resource, params, retval)
   388  }
   389  
   390  func (self *SGoogleClient) _ecsListAll(method string, resource string, params map[string]string, retval interface{}) error {
   391  	if params == nil {
   392  		params = map[string]string{}
   393  	}
   394  	items := jsonutils.NewArray()
   395  	nextPageToken := ""
   396  	params["maxResults"] = "500"
   397  	for {
   398  		params["pageToken"] = nextPageToken
   399  		resp, err := self._ecsList(method, resource, params)
   400  		if err != nil {
   401  			return errors.Wrap(err, "ecsList")
   402  		}
   403  		if resp.Contains("items") {
   404  			_items, err := resp.GetArray("items")
   405  			if err != nil {
   406  				return errors.Wrap(err, "resp.GetArray")
   407  			}
   408  			items.Add(_items...)
   409  		}
   410  		nextPageToken, _ = resp.GetString("nextPageToken")
   411  		if len(nextPageToken) == 0 {
   412  			break
   413  		}
   414  	}
   415  	return items.Unmarshal(retval)
   416  }
   417  
   418  func (self *SGoogleClient) ecsDelete(id string, retval interface{}) error {
   419  	resp, err := jsonRequest(self.client, "DELETE", GOOGLE_COMPUTE_DOMAIN, GOOGLE_API_VERSION, id, nil, nil, self.debug)
   420  	if err != nil {
   421  		return err
   422  	}
   423  	if retval != nil {
   424  		return resp.Unmarshal(retval)
   425  	}
   426  	return nil
   427  }
   428  
   429  func (self *SGoogleClient) ecsPatch(resource string, action string, params map[string]string, body jsonutils.JSONObject) (string, error) {
   430  	if len(action) > 0 {
   431  		resource = fmt.Sprintf("%s/%s", resource, action)
   432  	}
   433  	resp, err := jsonRequest(self.client, "PATCH", GOOGLE_COMPUTE_DOMAIN, GOOGLE_API_VERSION, resource, params, body, self.debug)
   434  	if err != nil {
   435  		return "", err
   436  	}
   437  	selfLink, _ := resp.GetString("selfLink")
   438  	return selfLink, nil
   439  }
   440  
   441  func (self *SGoogleClient) ecsDo(resource string, action string, params map[string]string, body jsonutils.JSONObject) (string, error) {
   442  	resource = fmt.Sprintf("%s/%s", resource, action)
   443  	resp, err := jsonRequest(self.client, "POST", GOOGLE_COMPUTE_DOMAIN, GOOGLE_API_VERSION, resource, params, body, self.debug)
   444  	if err != nil {
   445  		return "", err
   446  	}
   447  	selfLink, _ := resp.GetString("selfLink")
   448  	return selfLink, nil
   449  }
   450  
   451  func (self *SGoogleClient) rdsDelete(id string, retval interface{}) error {
   452  	resp, err := jsonRequest(self.client, "DELETE", GOOGLE_DBINSTANCE_DOMAIN, GOOGLE_DBINSTANCE_API_VERSION, id, nil, nil, self.debug)
   453  	if err != nil {
   454  		return err
   455  	}
   456  	if retval != nil {
   457  		return resp.Unmarshal(retval)
   458  	}
   459  	return nil
   460  }
   461  
   462  func (self *SGoogleClient) rdsDo(resource string, action string, params map[string]string, body jsonutils.JSONObject) (string, error) {
   463  	resource = fmt.Sprintf("%s/%s", resource, action)
   464  	resp, err := jsonRequest(self.client, "POST", GOOGLE_DBINSTANCE_DOMAIN, GOOGLE_DBINSTANCE_API_VERSION, resource, params, body, self.debug)
   465  	if err != nil {
   466  		return "", err
   467  	}
   468  	selfLink, _ := resp.GetString("selfLink")
   469  	return selfLink, nil
   470  }
   471  
   472  func (self *SGoogleClient) rdsPatch(resource string, body jsonutils.JSONObject) (string, error) {
   473  	resp, err := jsonRequest(self.client, "PATCH", GOOGLE_DBINSTANCE_DOMAIN, GOOGLE_DBINSTANCE_API_VERSION, resource, nil, body, self.debug)
   474  	if err != nil {
   475  		return "", err
   476  	}
   477  	selfLink, _ := resp.GetString("selfLink")
   478  	return selfLink, nil
   479  }
   480  
   481  func (self *SGoogleClient) rdsUpdate(resource string, params map[string]string, body jsonutils.JSONObject) (string, error) {
   482  	resource = fmt.Sprintf("projects/%s/%s", self.projectId, resource)
   483  	resp, err := jsonRequest(self.client, "PUT", GOOGLE_DBINSTANCE_DOMAIN, GOOGLE_DBINSTANCE_API_VERSION, resource, params, body, self.debug)
   484  	if err != nil {
   485  		return "", err
   486  	}
   487  	selfLink, _ := resp.GetString("selfLink")
   488  	return selfLink, nil
   489  }
   490  
   491  func (self *SGoogleClient) checkAndSetName(body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
   492  	name, _ := body.GetString("name")
   493  	if len(name) > 0 {
   494  		generateName := ""
   495  		for _, s := range strings.ToLower(name) {
   496  			if unicode.IsLetter(s) || unicode.IsDigit(s) || s == '-' {
   497  				generateName = fmt.Sprintf("%s%c", generateName, s)
   498  			}
   499  		}
   500  		if strings.HasSuffix(generateName, "-") {
   501  			generateName += "1"
   502  		}
   503  		if name != generateName {
   504  			err := jsonutils.Update(body, map[string]string{"name": generateName})
   505  			if err != nil {
   506  				return nil, fmt.Errorf("faild to generate google name from %s -> %s", name, generateName)
   507  			}
   508  		}
   509  	}
   510  	return body, nil
   511  }
   512  
   513  func (self *SGoogleClient) ecsInsert(resource string, body jsonutils.JSONObject, retval interface{}) error {
   514  	resource = fmt.Sprintf("projects/%s/%s", self.projectId, resource)
   515  	var err error
   516  	body, err = self.checkAndSetName(body)
   517  	if err != nil {
   518  		return errors.Wrap(err, "checkAndSetName")
   519  	}
   520  	resp, err := jsonRequest(self.client, "POST", GOOGLE_COMPUTE_DOMAIN, GOOGLE_API_VERSION, resource, nil, body, self.debug)
   521  	if err != nil {
   522  		return err
   523  	}
   524  	if retval != nil {
   525  		return resp.Unmarshal(retval)
   526  	}
   527  	return nil
   528  }
   529  
   530  func (self *SGoogleClient) storageInsert(resource string, body jsonutils.JSONObject, retval interface{}) error {
   531  	resp, err := jsonRequest(self.client, "POST", GOOGLE_STORAGE_DOMAIN, GOOGLE_STORAGE_API_VERSION, resource, nil, body, self.debug)
   532  	if err != nil {
   533  		return err
   534  	}
   535  	if retval != nil {
   536  		return resp.Unmarshal(retval)
   537  	}
   538  	return nil
   539  }
   540  
   541  func (self *SGoogleClient) storageUpload(resource string, header http.Header, body io.Reader) (*http.Response, error) {
   542  	resp, err := rawRequest(self.client, "POST", GOOGLE_STORAGE_UPLOAD_DOMAIN, GOOGLE_STORAGE_API_VERSION, resource, header, body, self.debug)
   543  	if err != nil {
   544  		return nil, errors.Wrap(err, "rawRequest")
   545  	}
   546  	if resp.StatusCode >= 400 {
   547  		msg, _ := ioutil.ReadAll(resp.Body)
   548  		defer resp.Body.Close()
   549  		return nil, fmt.Errorf("StatusCode: %d %s", resp.StatusCode, string(msg))
   550  	}
   551  	return resp, nil
   552  }
   553  
   554  func (self *SGoogleClient) storageUploadPart(resource string, header http.Header, body io.Reader) (*http.Response, error) {
   555  	resp, err := rawRequest(self.client, "PUT", GOOGLE_STORAGE_UPLOAD_DOMAIN, GOOGLE_STORAGE_API_VERSION, resource, header, body, self.debug)
   556  	if err != nil {
   557  		return nil, errors.Wrap(err, "rawRequest")
   558  	}
   559  	if resp.StatusCode >= 400 {
   560  		msg, _ := ioutil.ReadAll(resp.Body)
   561  		defer resp.Body.Close()
   562  		return nil, fmt.Errorf("StatusCode: %d %s", resp.StatusCode, string(msg))
   563  	}
   564  	return resp, nil
   565  }
   566  
   567  func (self *SGoogleClient) storageAbortUpload(resource string) error {
   568  	resp, err := rawRequest(self.client, "DELETE", GOOGLE_STORAGE_UPLOAD_DOMAIN, GOOGLE_STORAGE_API_VERSION, resource, nil, nil, self.debug)
   569  	if err != nil {
   570  		return errors.Wrap(err, "rawRequest")
   571  	}
   572  	defer resp.Body.Close()
   573  	if resp.StatusCode >= 400 {
   574  		msg, _ := ioutil.ReadAll(resp.Body)
   575  		return fmt.Errorf("StatusCode: %d %s", resp.StatusCode, string(msg))
   576  	}
   577  	return nil
   578  }
   579  
   580  func (self *SGoogleClient) storageDownload(resource string, header http.Header) (io.ReadCloser, error) {
   581  	resp, err := rawRequest(self.client, "GET", GOOGLE_STORAGE_DOMAIN, GOOGLE_STORAGE_API_VERSION, resource, header, nil, self.debug)
   582  	if err != nil {
   583  		return nil, errors.Wrap(err, "rawRequest")
   584  	}
   585  	defer resp.Body.Close()
   586  	if resp.StatusCode >= 400 {
   587  		msg, _ := ioutil.ReadAll(resp.Body)
   588  		return nil, fmt.Errorf("StatusCode: %d %s", resp.StatusCode, string(msg))
   589  	}
   590  	return resp.Body, err
   591  }
   592  
   593  func (self *SGoogleClient) storageList(resource string, params map[string]string) (jsonutils.JSONObject, error) {
   594  	return jsonRequest(self.client, "GET", GOOGLE_STORAGE_DOMAIN, GOOGLE_STORAGE_API_VERSION, resource, params, nil, self.debug)
   595  }
   596  
   597  func (self *SGoogleClient) storageListAll(resource string, params map[string]string, retval interface{}) error {
   598  	if params == nil {
   599  		params = map[string]string{}
   600  	}
   601  	items := jsonutils.NewArray()
   602  	nextPageToken := ""
   603  	params["maxResults"] = "500"
   604  	for {
   605  		params["pageToken"] = nextPageToken
   606  		resp, err := self.storageList(resource, params)
   607  		if err != nil {
   608  			return errors.Wrap(err, "storageList")
   609  		}
   610  		if resp.Contains("items") {
   611  			_items, err := resp.GetArray("items")
   612  			if err != nil {
   613  				return errors.Wrap(err, "resp.GetArray")
   614  			}
   615  			items.Add(_items...)
   616  		}
   617  		nextPageToken, _ = resp.GetString("nextPageToken")
   618  		if len(nextPageToken) == 0 {
   619  			break
   620  		}
   621  	}
   622  	return items.Unmarshal(retval)
   623  }
   624  
   625  func (self *SGoogleClient) storageGet(resource string, retval interface{}) error {
   626  	resp, err := jsonRequest(self.client, "GET", GOOGLE_STORAGE_DOMAIN, GOOGLE_STORAGE_API_VERSION, resource, nil, nil, self.debug)
   627  	if err != nil {
   628  		return err
   629  	}
   630  	if retval != nil {
   631  		err = resp.Unmarshal(retval)
   632  		if err != nil {
   633  			return errors.Wrap(err, "resp.Unmarshal")
   634  		}
   635  	}
   636  	return nil
   637  }
   638  
   639  func (self *SGoogleClient) storagePut(resource string, body jsonutils.JSONObject, retval interface{}) error {
   640  	resp, err := jsonRequest(self.client, "PUT", GOOGLE_STORAGE_DOMAIN, GOOGLE_STORAGE_API_VERSION, resource, nil, body, self.debug)
   641  	if err != nil {
   642  		return err
   643  	}
   644  	if retval != nil {
   645  		err = resp.Unmarshal(retval)
   646  		if err != nil {
   647  			return errors.Wrap(err, "resp.Unmarshal")
   648  		}
   649  	}
   650  	return nil
   651  }
   652  
   653  func (self *SGoogleClient) storageDelete(id string, retval interface{}) error {
   654  	resp, err := jsonRequest(self.client, "DELETE", GOOGLE_STORAGE_DOMAIN, GOOGLE_STORAGE_API_VERSION, id, nil, nil, self.debug)
   655  	if err != nil {
   656  		return err
   657  	}
   658  	if retval != nil {
   659  		return resp.Unmarshal(retval)
   660  	}
   661  	return nil
   662  }
   663  
   664  func (self *SGoogleClient) storageDo(resource string, action string, params map[string]string, body jsonutils.JSONObject) (string, error) {
   665  	resource = fmt.Sprintf("%s/%s", resource, action)
   666  	resp, err := jsonRequest(self.client, "POST", GOOGLE_STORAGE_DOMAIN, GOOGLE_STORAGE_API_VERSION, resource, params, body, self.debug)
   667  	if err != nil {
   668  		return "", err
   669  	}
   670  	selfLink, _ := resp.GetString("selfLink")
   671  	return selfLink, nil
   672  }
   673  
   674  func (self *SGoogleClient) cloudbuildGet(resource string, retval interface{}) error {
   675  	resp, err := jsonRequest(self.client, "GET", GOOGLE_CLOUDBUILD_DOMAIN, GOOGLE_CLOUDBUILD_API_VERSION, resource, nil, nil, self.debug)
   676  	if err != nil {
   677  		return err
   678  	}
   679  	if retval != nil {
   680  		err = resp.Unmarshal(retval)
   681  		if err != nil {
   682  			return errors.Wrap(err, "resp.Unmarshal")
   683  		}
   684  	}
   685  	return nil
   686  }
   687  
   688  func (self *SGoogleClient) cloudbuildInsert(resource string, body jsonutils.JSONObject, retval interface{}) error {
   689  	resp, err := jsonRequest(self.client, "POST", GOOGLE_CLOUDBUILD_DOMAIN, GOOGLE_CLOUDBUILD_API_VERSION, resource, nil, body, self.debug)
   690  	if err != nil {
   691  		return err
   692  	}
   693  	if retval != nil {
   694  		return resp.Unmarshal(retval)
   695  	}
   696  	return nil
   697  }
   698  
   699  func (self *SGoogleClient) rdsInsert(resource string, body jsonutils.JSONObject, retval interface{}) error {
   700  	resource = fmt.Sprintf("projects/%s/%s", self.projectId, resource)
   701  	var err error
   702  	body, err = self.checkAndSetName(body)
   703  	if err != nil {
   704  		return errors.Wrap(err, "checkAndSetName")
   705  	}
   706  	resp, err := jsonRequest(self.client, "POST", GOOGLE_DBINSTANCE_DOMAIN, GOOGLE_DBINSTANCE_API_VERSION, resource, nil, body, self.debug)
   707  	if err != nil {
   708  		return err
   709  	}
   710  	if retval != nil {
   711  		return resp.Unmarshal(retval)
   712  	}
   713  	return nil
   714  }
   715  
   716  func (self *SGoogleClient) rdsGet(resource string, retval interface{}) error {
   717  	resp, err := jsonRequest(self.client, "GET", GOOGLE_DBINSTANCE_DOMAIN, GOOGLE_DBINSTANCE_API_VERSION, resource, nil, nil, self.debug)
   718  	if err != nil {
   719  		return err
   720  	}
   721  	if retval != nil {
   722  		err = resp.Unmarshal(retval)
   723  		if err != nil {
   724  			return errors.Wrap(err, "resp.Unmarshal")
   725  		}
   726  	}
   727  	return nil
   728  }
   729  
   730  func (self *SGoogleClient) rdsList(resource string, params map[string]string) (jsonutils.JSONObject, error) {
   731  	resource = fmt.Sprintf("projects/%s/%s", self.projectId, resource)
   732  	return jsonRequest(self.client, "GET", GOOGLE_DBINSTANCE_DOMAIN, GOOGLE_DBINSTANCE_API_VERSION, resource, params, nil, self.debug)
   733  }
   734  
   735  func (self *SGoogleClient) rdsListAll(resource string, params map[string]string, retval interface{}) error {
   736  	if params == nil {
   737  		params = map[string]string{}
   738  	}
   739  	items := jsonutils.NewArray()
   740  	nextPageToken := ""
   741  	params["maxResults"] = "500"
   742  	for {
   743  		params["pageToken"] = nextPageToken
   744  		resp, err := self.rdsList(resource, params)
   745  		if err != nil {
   746  			return errors.Wrap(err, "rdsList")
   747  		}
   748  		if resp.Contains("items") {
   749  			_items, err := resp.GetArray("items")
   750  			if err != nil {
   751  				return errors.Wrap(err, "resp.GetArray")
   752  			}
   753  			items.Add(_items...)
   754  		}
   755  		nextPageToken, _ = resp.GetString("nextPageToken")
   756  		if len(nextPageToken) == 0 {
   757  			break
   758  		}
   759  	}
   760  	return items.Unmarshal(retval)
   761  }
   762  
   763  func (self *SGoogleClient) billingList(resource string, params map[string]string) (jsonutils.JSONObject, error) {
   764  	return jsonRequest(self.client, "GET", GOOGLE_BILLING_DOMAIN, GOOGLE_BILLING_API_VERSION, resource, params, nil, self.debug)
   765  }
   766  
   767  func (self *SGoogleClient) billingListAll(resource string, params map[string]string, retval interface{}) error {
   768  	if params == nil {
   769  		params = map[string]string{}
   770  	}
   771  	items := jsonutils.NewArray()
   772  	nextPageToken := ""
   773  	params["pageSize"] = "5000"
   774  	for {
   775  		params["pageToken"] = nextPageToken
   776  		resp, err := self.billingList(resource, params)
   777  		if err != nil {
   778  			return errors.Wrap(err, "billingList")
   779  		}
   780  		if resp.Contains("skus") {
   781  			_items, err := resp.GetArray("skus")
   782  			if err != nil {
   783  				return errors.Wrap(err, "resp.GetArray")
   784  			}
   785  			items.Add(_items...)
   786  		}
   787  		nextPageToken, _ = resp.GetString("nextPageToken")
   788  		if len(nextPageToken) == 0 {
   789  			break
   790  		}
   791  	}
   792  	return items.Unmarshal(retval)
   793  }
   794  
   795  func (self *SGoogleClient) monitorList(resource string, params map[string]string) (jsonutils.JSONObject, error) {
   796  	return jsonRequest(self.client, "GET", GOOGLE_MONITOR_DOMAIN, GOOGLE_MONITOR_API_VERSION, resource, params, nil, self.debug)
   797  }
   798  
   799  func (self *SGoogleClient) monitorListAll(resource string, params map[string]string) (*jsonutils.JSONArray, error) {
   800  	if params == nil {
   801  		params = map[string]string{}
   802  	}
   803  	timeSeries := jsonutils.NewArray()
   804  	nextPageToken := ""
   805  	params["pageSize"] = "5000"
   806  	for {
   807  		params["pageToken"] = nextPageToken
   808  		resp, err := self.monitorList(resource, params)
   809  		if err != nil {
   810  			return nil, errors.Wrap(err, "monitorList")
   811  		}
   812  		if resp.Contains("timeSeries") {
   813  			_series, err := resp.GetArray("timeSeries")
   814  			if err != nil {
   815  				return nil, errors.Wrap(err, "resp.GetTimeSeries")
   816  			}
   817  			timeSeries.Add(_series...)
   818  		}
   819  		nextPageToken, _ = resp.GetString("nextPageToken")
   820  		if len(nextPageToken) == 0 {
   821  			break
   822  		}
   823  	}
   824  	return timeSeries, nil
   825  }
   826  
   827  func rawRequest(client *http.Client, method httputils.THttpMethod, domain, apiVersion string, resource string, header http.Header, body io.Reader, debug bool) (*http.Response, error) {
   828  	resource = strings.TrimPrefix(resource, fmt.Sprintf("%s/%s/", domain, apiVersion))
   829  	resource = fmt.Sprintf("%s/%s/%s", domain, apiVersion, resource)
   830  	return httputils.Request(client, context.Background(), method, resource, header, body, debug)
   831  }
   832  
   833  /*
   834    "error": {
   835      "code": 400,
   836      "message": "Request contains an invalid argument.",
   837      "status": "INVALID_ARGUMENT",
   838      "details": [
   839        {
   840          "@type": "type.googleapis.com/google.cloudresourcemanager.v1.ProjectIamPolicyError",
   841          "type": "SOLO_MUST_INVITE_OWNERS",
   842          "member": "user:test",
   843          "role": "roles/owner"
   844        }
   845      ]
   846    }
   847  */
   848  
   849  type gError struct {
   850  	ErrorInfo struct {
   851  		Code    int
   852  		Message string
   853  		Status  string
   854  		Details jsonutils.JSONObject
   855  	} `json:"error"`
   856  	Class string
   857  }
   858  
   859  func (g *gError) Error() string {
   860  	return jsonutils.Marshal(g).String()
   861  }
   862  
   863  func (g *gError) ParseErrorFromJsonResponse(statusCode int, body jsonutils.JSONObject) error {
   864  	if body != nil {
   865  		body.Unmarshal(g)
   866  	}
   867  	if g.ErrorInfo.Code == 0 {
   868  		g.ErrorInfo.Code = statusCode
   869  	}
   870  	if g.ErrorInfo.Details == nil {
   871  		g.ErrorInfo.Details = body
   872  	}
   873  	if len(g.Class) == 0 {
   874  		g.Class = http.StatusText(statusCode)
   875  	}
   876  	if statusCode == 404 {
   877  		return errors.Wrap(cloudprovider.ErrNotFound, g.Error())
   878  	}
   879  	return g
   880  }
   881  
   882  func _jsonRequest(cli *http.Client, method httputils.THttpMethod, url string, body jsonutils.JSONObject, debug bool) (jsonutils.JSONObject, error) {
   883  	client := httputils.NewJsonClient(cli)
   884  	req := httputils.NewJsonRequest(method, url, body)
   885  	var err error
   886  	var data jsonutils.JSONObject
   887  	for i := 0; i < MAX_RETRY; i++ {
   888  		var ge gError
   889  		_, data, err = client.Send(context.Background(), req, &ge, debug)
   890  		if err == nil {
   891  			return data, nil
   892  		}
   893  		if body != nil {
   894  			log.Errorf("%s %s params: %s error: %v", method, url, body.PrettyString(), err)
   895  		} else {
   896  			log.Errorf("%s %s error: %v", method, url, err)
   897  		}
   898  		retry := false
   899  		for _, msg := range []string{
   900  			"EOF",
   901  			"i/o timeout",
   902  			"TLS handshake timeout",
   903  			"connection reset by peer",
   904  		} {
   905  			if strings.Index(err.Error(), msg) >= 0 {
   906  				retry = true
   907  				break
   908  			}
   909  		}
   910  		if !retry {
   911  			return nil, err
   912  		}
   913  		time.Sleep(time.Second * 10)
   914  	}
   915  	return nil, err
   916  }
   917  
   918  func (self *SGoogleClient) GetRegion(regionId string) *SRegion {
   919  	if len(regionId) == 0 {
   920  		regionId = GOOGLE_DEFAULT_REGION
   921  	}
   922  	for i := 0; i < len(self.iregions); i++ {
   923  		if self.iregions[i].GetId() == regionId {
   924  			return self.iregions[i].(*SRegion)
   925  		}
   926  	}
   927  	return nil
   928  }
   929  
   930  func (client *SGoogleClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
   931  	projects, err := client.GetProjects()
   932  	if err != nil {
   933  		return nil, errors.Wrap(err, "GetProjects")
   934  	}
   935  	accounts := []cloudprovider.SSubAccount{}
   936  	for _, project := range projects {
   937  		subAccount := cloudprovider.SSubAccount{}
   938  		subAccount.Name = project.Name
   939  		subAccount.Account = fmt.Sprintf("%s/%s", project.ProjectId, client.clientEmail)
   940  		if project.LifecycleState == "ACTIVE" {
   941  			subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL
   942  		} else {
   943  			subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_ARREARS
   944  		}
   945  		accounts = append(accounts, subAccount)
   946  	}
   947  	return accounts, nil
   948  }
   949  
   950  func (self *SGoogleClient) GetIRegionById(id string) (cloudprovider.ICloudRegion, error) {
   951  	for i := 0; i < len(self.iregions); i++ {
   952  		if self.iregions[i].GetGlobalId() == id {
   953  			return self.iregions[i].(*SRegion), nil
   954  		}
   955  	}
   956  	return nil, cloudprovider.ErrNotFound
   957  }
   958  
   959  func (self *SGoogleClient) GetIRegions() []cloudprovider.ICloudRegion {
   960  	return self.iregions
   961  }
   962  
   963  func (self *SGoogleClient) fetchGlobalNetwork() ([]SGlobalNetwork, error) {
   964  	if len(self.globalnetworks) > 0 {
   965  		return self.globalnetworks, nil
   966  	}
   967  	globalnetworks, err := self.GetGlobalNetworks(0, "")
   968  	if err != nil {
   969  		return nil, err
   970  	}
   971  	self.globalnetworks = globalnetworks
   972  	return globalnetworks, nil
   973  }
   974  
   975  func (self *SGoogleClient) GetRegions() []SRegion {
   976  	regions := make([]SRegion, len(self.iregions))
   977  	for i := 0; i < len(regions); i++ {
   978  		region := self.iregions[i].(*SRegion)
   979  		regions[i] = *region
   980  	}
   981  	return regions
   982  }
   983  
   984  func (self *SGoogleClient) GetIProjects() ([]cloudprovider.ICloudProject, error) {
   985  	projects, err := self.GetProjects()
   986  	if err != nil {
   987  		return nil, err
   988  	}
   989  
   990  	iprojects := []cloudprovider.ICloudProject{}
   991  	for i := range projects {
   992  		iprojects = append(iprojects, &projects[i])
   993  	}
   994  	return iprojects, nil
   995  }
   996  
   997  func (self *SGoogleClient) GetCapabilities() []string {
   998  	caps := []string{
   999  		cloudprovider.CLOUD_CAPABILITY_PROJECT,
  1000  		cloudprovider.CLOUD_CAPABILITY_COMPUTE,
  1001  		cloudprovider.CLOUD_CAPABILITY_NETWORK,
  1002  		cloudprovider.CLOUD_CAPABILITY_EIP,
  1003  		cloudprovider.CLOUD_CAPABILITY_LOADBALANCER,
  1004  		cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE,
  1005  		cloudprovider.CLOUD_CAPABILITY_RDS,
  1006  		// cloudprovider.CLOUD_CAPABILITY_CACHE,
  1007  		// cloudprovider.CLOUD_CAPABILITY_EVENT,
  1008  		cloudprovider.CLOUD_CAPABILITY_CLOUDID,
  1009  		cloudprovider.CLOUD_CAPABILITY_QUOTA + cloudprovider.READ_ONLY_SUFFIX,
  1010  	}
  1011  	return caps
  1012  }
  1013  
  1014  func (self *SGoogleClient) GetSamlSpInitiatedLoginUrl(idpName string) string {
  1015  	// GOOGLE只支持一个IDP, 可以将organization名字存储在idpName里,避免因为权限不足,无法获取organization名称
  1016  	if len(idpName) == 0 {
  1017  		orgs, _ := self.ListOrganizations()
  1018  		if len(orgs) != 1 {
  1019  			log.Warningf("Organization count %d != 1, require assign the service account to ONE organization with organization viewer/admin role", len(orgs))
  1020  		} else {
  1021  			idpName = orgs[0].DisplayName
  1022  		}
  1023  	}
  1024  	if len(idpName) == 0 {
  1025  		log.Errorf("no valid organization name for this GCP account")
  1026  		return ""
  1027  	}
  1028  	return fmt.Sprintf("https://www.google.com/a/%s/ServiceLogin?continue=https://console.cloud.google.com", idpName)
  1029  }