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, ®ions) 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, ®ions[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 }