Files
cloudpods/pkg/multicloud/qcloud/qcloud.go
2022-04-02 14:10:42 +08:00

1133 lines
37 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 qcloud
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
sdkerrors "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
tchttp "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
qvpc "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc/v20170312"
"github.com/tencentyun/cos-go-sdk-v5"
"github.com/tencentyun/cos-go-sdk-v5/debug"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/timeutils"
"yunion.io/x/pkg/utils"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/util/httputils"
)
const (
CLOUD_PROVIDER_QCLOUD = api.CLOUD_PROVIDER_QCLOUD
CLOUD_PROVIDER_QCLOUD_CN = "腾讯云"
CLOUD_PROVIDER_QCLOUD_EN = "QCloud"
QCLOUD_DEFAULT_REGION = "ap-beijing"
QCLOUD_API_VERSION = "2017-03-12"
QCLOUD_CLB_API_VERSION = "2018-03-17"
QCLOUD_BILLING_API_VERSION = "2018-07-09"
QCLOUD_AUDIT_API_VERSION = "2019-03-19"
QCLOUD_CAM_API_VERSION = "2019-01-16"
QCLOUD_CDB_API_VERSION = "2017-03-20"
QCLOUD_MARIADB_API_VERSION = "2017-03-12"
QCLOUD_POSTGRES_API_VERSION = "2017-03-12"
QCLOUD_SQLSERVER_API_VERSION = "2018-03-28"
QCLOUD_REDIS_API_VERSION = "2018-04-12"
QCLOUD_MEMCACHED_API_VERSION = "2019-03-18"
QCLOUD_SSL_API_VERSION = "2019-12-05"
QCLOUD_CDN_API_VERSION = "2018-06-06"
QCLOUD_MONGODB_API_VERSION = "2019-07-25"
QCLOUD_ES_API_VERSION = "2018-04-16"
QCLOUD_DCDB_API_VERSION = "2018-04-11"
QCLOUD_KAFKA_API_VERSION = "2019-08-19"
QCLOUD_DNS_API_VERSION = "2021-03-23"
)
type QcloudClientConfig struct {
cpcfg cloudprovider.ProviderConfig
secretId string
secretKey string
appId string
debug bool
}
func NewQcloudClientConfig(secretId, secretKey string) *QcloudClientConfig {
cfg := &QcloudClientConfig{
secretId: secretId,
secretKey: secretKey,
}
return cfg
}
func (cfg *QcloudClientConfig) CloudproviderConfig(cpcfg cloudprovider.ProviderConfig) *QcloudClientConfig {
cfg.cpcfg = cpcfg
return cfg
}
func (cfg *QcloudClientConfig) AppId(appId string) *QcloudClientConfig {
cfg.appId = appId
return cfg
}
func (cfg *QcloudClientConfig) Debug(debug bool) *QcloudClientConfig {
cfg.debug = debug
return cfg
}
type SQcloudClient struct {
*QcloudClientConfig
uin string
ownerId string
ownerName string
iregions []cloudprovider.ICloudRegion
ibuckets []cloudprovider.ICloudBucket
}
func NewQcloudClient(cfg *QcloudClientConfig) (*SQcloudClient, error) {
client := SQcloudClient{
QcloudClientConfig: cfg,
}
err := client.fetchRegions()
if err != nil {
return nil, errors.Wrap(err, "fetchRegions")
}
// err = client.verifyAppId()
// if err != nil {
// return nil, errors.Wrap(err, "verifyAppId")
// }
err = client.fetchBuckets()
if err != nil {
return nil, errors.Wrap(err, "fetchBuckets")
}
if client.debug {
log.Debugf("ownerID: %s ownerName: %s", client.ownerId, client.ownerName)
}
return &client, nil
}
// 默认接口请求频率限制20次/秒
// 部分接口支持金融区地域。由于金融区和非金融区是隔离不互通的,因此当公共参数 Region 为金融区地域(例如 ap-shanghai-fsi需要同时指定带金融区地域的域名最好和 Region 的地域保持一致例如clb.ap-shanghai-fsi.tencentcloudapi.com
// https://cloud.tencent.com/document/product/416/6479
func apiDomain(product string, params map[string]string) string {
regionId, _ := params["Region"]
return apiDomainByRegion(product, regionId)
}
func apiDomainByRegion(product, regionId string) string {
if strings.HasSuffix(regionId, "-fsi") {
return product + "." + regionId + ".tencentcloudapi.com"
} else {
return product + ".tencentcloudapi.com"
}
}
func jsonRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool, retry bool) (jsonutils.JSONObject, error) {
domain := apiDomain("cvm", params)
return _jsonRequest(client, domain, QCLOUD_API_VERSION, apiName, params, updateFunc, debug, retry)
}
func vpcRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
domain := apiDomain("vpc", params)
return _jsonRequest(client, domain, QCLOUD_API_VERSION, apiName, params, updateFunc, debug, true)
}
func auditRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
domain := apiDomain("cloudaudit", params)
return _jsonRequest(client, domain, QCLOUD_AUDIT_API_VERSION, apiName, params, updateFunc, debug, true)
}
func cbsRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
domain := apiDomain("cbs", params)
return _jsonRequest(client, domain, QCLOUD_API_VERSION, apiName, params, updateFunc, debug, true)
}
func accountRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
domain := "account.api.qcloud.com"
return _phpJsonRequest(client, &wssJsonResponse{}, domain, "/v2/index.php", "", apiName, params, updateFunc, debug)
}
// es
func esRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
domain := apiDomain("es", params)
return _jsonRequest(client, domain, QCLOUD_ES_API_VERSION, apiName, params, updateFunc, debug, true)
}
// kafka
func kafkaRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
domain := apiDomain("ckafka", params)
return _jsonRequest(client, domain, QCLOUD_KAFKA_API_VERSION, apiName, params, updateFunc, debug, true)
}
// redis
func redisRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
domain := apiDomain("redis", params)
return _jsonRequest(client, domain, QCLOUD_REDIS_API_VERSION, apiName, params, updateFunc, debug, true)
}
// tdsql
func dcdbRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
domain := apiDomain("dcdb", params)
return _jsonRequest(client, domain, QCLOUD_DCDB_API_VERSION, apiName, params, updateFunc, debug, true)
}
// mongodb
func mongodbRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
domain := apiDomain("mongodb", params)
return _jsonRequest(client, domain, QCLOUD_MONGODB_API_VERSION, apiName, params, updateFunc, debug, true)
}
// memcached
func memcachedRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
domain := apiDomain("memcached", params)
return _jsonRequest(client, domain, QCLOUD_MEMCACHED_API_VERSION, apiName, params, updateFunc, debug, true)
}
// loadbalancer服务 api 3.0
func clbRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
domain := apiDomain("clb", params)
return _jsonRequest(client, domain, QCLOUD_CLB_API_VERSION, apiName, params, updateFunc, debug, true)
}
// loadbalancer服务 api 2017
func lbRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
domain := "lb.api.qcloud.com"
return _phpJsonRequest(client, &lbJsonResponse{}, domain, "/v2/index.php", "", apiName, params, updateFunc, debug)
}
// cdb
func cdbRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
domain := apiDomain("cdb", params)
return _jsonRequest(client, domain, QCLOUD_CDB_API_VERSION, apiName, params, updateFunc, debug, true)
}
// mariadb
func mariadbRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
domain := apiDomain("mariadb", params)
return _jsonRequest(client, domain, QCLOUD_MARIADB_API_VERSION, apiName, params, updateFunc, debug, true)
}
// postgres
func postgresRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
domain := apiDomain("postgres", params)
return _jsonRequest(client, domain, QCLOUD_POSTGRES_API_VERSION, apiName, params, updateFunc, debug, true)
}
// sqlserver
func sqlserverRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
domain := apiDomain("sqlserver", params)
return _jsonRequest(client, domain, QCLOUD_SQLSERVER_API_VERSION, apiName, params, updateFunc, debug, true)
}
// deprecated: ssl 证书服务
func wssRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
domain := "wss.api.qcloud.com"
return _phpJsonRequest(client, &wssJsonResponse{}, domain, "/v2/index.php", "", apiName, params, updateFunc, debug)
}
// ssl 证书服务
func sslRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
domain := "ssl.tencentcloudapi.com"
return _jsonRequest(client, domain, QCLOUD_SSL_API_VERSION, apiName, params, updateFunc, debug, true)
}
// dnspod 解析服务
func dnsRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
domain := "dnspod.tencentcloudapi.com"
return _jsonRequest(client, domain, QCLOUD_DNS_API_VERSION, apiName, params, updateFunc, debug, true)
}
// 2017版API
func vpc2017Request(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
domain := "vpc.api.qcloud.com"
return _phpJsonRequest(client, &vpc2017JsonResponse{}, domain, "/v2/index.php", "", apiName, params, updateFunc, debug)
}
func billingRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
domain := "billing.tencentcloudapi.com"
return _jsonRequest(client, domain, QCLOUD_BILLING_API_VERSION, apiName, params, updateFunc, debug, true)
}
func camRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
domain := "cam.tencentcloudapi.com"
return _jsonRequest(client, domain, QCLOUD_CAM_API_VERSION, apiName, params, updateFunc, debug, true)
}
func monitorRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string),
debug bool) (jsonutils.JSONObject, error) {
domain := "monitor.tencentcloudapi.com"
return _jsonRequest(client, domain, QCLOUD_API_VERSION_METRICS, apiName, params, updateFunc, debug, true)
}
func cdnRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string),
debug bool) (jsonutils.JSONObject, error) {
domain := "cdn.tencentcloudapi.com"
return _jsonRequest(client, domain, QCLOUD_CDN_API_VERSION, apiName, params, updateFunc, debug, true)
}
// ============phpJsonRequest============
type qcloudResponse interface {
tchttp.Response
GetResponse() *interface{}
}
type phpJsonRequest struct {
tchttp.BaseRequest
Path string
}
func (r *phpJsonRequest) GetUrl() string {
url := r.BaseRequest.GetUrl()
if url == "" {
return url
}
index := strings.Index(url, "?")
if index == -1 {
// POST request
return strings.TrimSuffix(url, "/") + r.Path
}
p1, p2 := url[:index], url[index:]
p1 = strings.TrimSuffix(p1, "/")
return p1 + r.Path + p2
}
func (r *phpJsonRequest) GetPath() string {
return r.Path
}
// 2017vpc专用response
type vpc2017JsonResponse struct {
Code int `json:"code"`
CodeDesc string `json:"codeDesc"`
Message string `json:"message"`
TotalCount int `json:"totalCount"`
TaskId int64 `json:"taskId"`
PeeringConnectionId string `json:"peeringConnectionId"`
Response *interface{} `json:"data"`
}
func (r *vpc2017JsonResponse) ParseErrorFromHTTPResponse(body []byte) (err error) {
resp := &vpc2017JsonResponse{}
err = json.Unmarshal(body, resp)
if err != nil {
return
}
if resp.Code != 0 {
return sdkerrors.NewTencentCloudSDKError(resp.CodeDesc, resp.Message, "")
}
return nil
}
func (r *vpc2017JsonResponse) GetResponse() *interface{} {
if r.Response == nil {
result, _ := jsonutils.Parse([]byte(`{"data":[],"totalCount":0}`))
resultMap := result.(*jsonutils.JSONDict)
resultMap.Update(jsonutils.Marshal(r))
return func(resp interface{}) *interface{} {
return &resp
}(resultMap)
}
return func(resp interface{}) *interface{} {
return &resp
}(jsonutils.Marshal(r))
}
// SSL证书专用response
type wssJsonResponse struct {
Code int `json:"code"`
CodeDesc string `json:"codeDesc"`
ProjectId int `json:"projectId"`
Message string `json:"message"`
Response *interface{} `json:"data"`
}
func (r *wssJsonResponse) ParseErrorFromHTTPResponse(body []byte) (err error) {
resp := &wssJsonResponse{}
err = json.Unmarshal(body, resp)
if err != nil {
return
}
if resp.Code != 0 {
return sdkerrors.NewTencentCloudSDKError(resp.CodeDesc, resp.Message, "")
}
return nil
}
func (r *wssJsonResponse) GetResponse() *interface{} {
if r.Response == nil {
return func(resp interface{}) *interface{} {
return &resp
}(jsonutils.Marshal(r))
}
return r.Response
}
// 2017版负载均衡API专用response
type lbJsonResponse struct {
Response map[string]interface{}
}
func (r *lbJsonResponse) ParseErrorFromHTTPResponse(body []byte) (err error) {
resp := &wssJsonResponse{}
err = json.Unmarshal(body, resp)
if err != nil {
return
}
if resp.Code != 0 {
return sdkerrors.NewTencentCloudSDKError(resp.CodeDesc, resp.Message, "")
}
// hook 由于目前只能从这个方法中拿到原始的body.这里将原始body hook 到 Response
err = json.Unmarshal(body, &r.Response)
if err != nil {
return
}
return nil
}
func (r *lbJsonResponse) GetResponse() *interface{} {
return func(resp interface{}) *interface{} {
return &resp
}(r.Response)
}
// 3.0版本通用response
type QcloudResponse struct {
*tchttp.BaseResponse
Response *interface{} `json:"Response"`
}
func (r *QcloudResponse) GetResponse() *interface{} {
return r.Response
}
func _jsonRequest(client *common.Client, domain string, version string, apiName string, params map[string]string, updateFun func(string, string), debug bool, retry bool) (jsonutils.JSONObject, error) {
req := &tchttp.BaseRequest{}
if region, ok := params["Region"]; ok {
client = client.Init(region)
}
_profile := profile.NewClientProfile()
_profile.SignMethod = common.SHA256
client.WithProfile(_profile)
service := strings.Split(domain, ".")[0]
req.Init().WithApiInfo(service, version, apiName)
req.SetDomain(domain)
for k, v := range params {
if strings.HasSuffix(k, "Ids.0") && len(v) == 0 {
return nil, errors.Wrapf(cloudprovider.ErrNotFound, "%s = %s", k, v)
}
req.GetParams()[k] = v
}
resp := &QcloudResponse{
BaseResponse: &tchttp.BaseResponse{},
}
ret, err := _baseJsonRequest(client, req, resp, debug, retry)
if err != nil {
if errors.Cause(err) == httperrors.ErrNoPermission && updateFun != nil {
updateFun(service, apiName)
}
return nil, err
}
return ret, nil
}
// 老版本腾讯云api。 适用于类似 https://cvm.api.qcloud.com/v2/index.php 这样的带/v2/index.php路径的接口
// todo: 添加自定义response参数
func _phpJsonRequest(client *common.Client, resp qcloudResponse, domain string, path string, version string, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
req := &phpJsonRequest{Path: path}
if region, ok := params["Region"]; ok {
client = client.Init(region)
}
_profile := profile.NewClientProfile()
_profile.SignMethod = common.SHA256
client.WithProfile(_profile)
service := strings.Split(domain, ".")[0]
req.Init().WithApiInfo(service, version, apiName)
req.SetDomain(domain)
for k, v := range params {
if strings.HasSuffix(k, "Ids.0") && len(v) == 0 {
return nil, cloudprovider.ErrNotFound
}
req.GetParams()[k] = v
}
ret, err := _baseJsonRequest(client, req, resp, debug, true)
if err != nil {
if errors.Cause(err) == httperrors.ErrNoPermission && updateFunc != nil {
updateFunc(service, apiName)
}
return nil, err
}
return ret, nil
}
func _baseJsonRequest(client *common.Client, req tchttp.Request, resp qcloudResponse, debug bool, retry bool) (jsonutils.JSONObject, error) {
tryMax := 1
if retry {
tryMax = 3
}
var err error
for i := 1; i <= tryMax; i++ {
err = client.Send(req, resp)
if err == nil {
break
}
needRetry := false
e, ok := err.(*sdkerrors.TencentCloudSDKError)
if ok {
if strings.HasPrefix(e.Code, "UnauthorizedOperation.") ||
strings.HasPrefix(e.Code, "AuthFailure.") ||
utils.IsInStringArray(e.Code, []string{
"SecretidNotAuthAccessResource",
"UnauthorizedOperation",
"InvalidParameter.PermissionDenied",
"AuthFailure",
}) {
return nil, errors.Wrapf(httperrors.ErrNoPermission, err.Error())
}
if utils.IsInStringArray(e.Code, []string{
"AuthFailure.SecretIdNotFound",
"AuthFailure.SignatureFailure",
}) {
return nil, errors.Wrapf(httperrors.ErrInvalidAccessKey, err.Error())
}
if utils.IsInStringArray(e.Code, []string{
"InvalidParameter.RoleNotExist",
"ResourceNotFound",
"FailedOperation.CertificateNotFound",
}) {
return nil, errors.Wrapf(cloudprovider.ErrNotFound, err.Error())
}
if e.Code == "UnsupportedRegion" {
return nil, cloudprovider.ErrNotSupported
}
if utils.IsInStringArray(e.Code, []string{
"InternalError",
"MutexOperation.TaskRunning", // Code=DesOperation.MutexTaskRunning, Message=Mutex task is running, please try later
"InvalidInstance.NotSupported", // Code=InvalidInstance.NotSupported, Message=The request does not support the instances `ins-bg54517v` which are in operation or in a special state., 重装系统后立即关机有可能会引发 Code=InvalidInstance.NotSupported 错误, 重试可以避免任务失败
"InvalidAddressId.StatusNotPermit", // Code=InvalidAddressId.StatusNotPermit, Message=The status `"UNBINDING"` for AddressId `"eip-m3kix9kx"` is not permit to do this operation., EIP删除需要重试
"RequestLimitExceeded",
"OperationDenied.InstanceOperationInProgress", // 调整配置后开机 Code=OperationDenied.InstanceOperationInProgress, Message=实例`['ins-nksicizg']`操作进行中,请等待, RequestId=c9951005-b22c-43c1-84aa-d923d49addcf
}) {
needRetry = true
}
}
if !needRetry {
for _, msg := range []string{
"EOF",
"TLS handshake timeout",
"try later",
"i/o timeout",
} {
if strings.Contains(err.Error(), msg) {
needRetry = true
break
}
}
}
if needRetry {
log.Errorf("request url %s\nparams: %s\nerror: %v\ntry after %d seconds", req.GetDomain(), jsonutils.Marshal(req.GetParams()).PrettyString(), err, i*10)
time.Sleep(time.Second * time.Duration(i*10))
continue
}
log.Errorf("request url: %s\nparams: %s\nresponse: %v\nerror: %v", req.GetDomain(), jsonutils.Marshal(req.GetParams()).PrettyString(), resp.GetResponse(), err)
return nil, err
}
if debug {
log.Debugf("request: %s", req.GetParams())
response := resp.GetResponse()
if response != nil {
log.Debugf("response: %s", jsonutils.Marshal(response).PrettyString())
}
}
if err != nil {
return nil, err
}
return jsonutils.Marshal(resp.GetResponse()), nil
}
func (client *SQcloudClient) GetRegions() []SRegion {
regions := make([]SRegion, len(client.iregions))
for i := 0; i < len(regions); i++ {
region := client.iregions[i].(*SRegion)
regions[i] = *region
}
return regions
}
func (client *SQcloudClient) getDefaultClient() (*common.Client, error) {
return client.getSdkClient(QCLOUD_DEFAULT_REGION)
}
func (client *SQcloudClient) getSdkClient(regionId string) (*common.Client, error) {
cli, err := common.NewClientWithSecretId(client.secretId, client.secretKey, regionId)
if err != nil {
return nil, err
}
httpClient := client.cpcfg.AdaptiveTimeoutHttpClient()
cli.WithHttpTransport(httpClient.Transport)
return cli, nil
}
func (client *SQcloudClient) getVpcClient(regionId string) (*qvpc.Client, error) {
cli, err := client.getSdkClient(regionId)
if err != nil {
return nil, err
}
vpcClient := &qvpc.Client{
Client: *cli,
}
cpf := profile.NewClientProfile()
cpf.HttpProfile.Endpoint = apiDomainByRegion("vpc", regionId)
vpcClient.WithProfile(cpf)
return vpcClient, nil
}
func (client *SQcloudClient) vpcRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
cli, err := client.getDefaultClient()
if err != nil {
return nil, err
}
return vpcRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
}
func (client *SQcloudClient) auditRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
cli, err := client.getDefaultClient()
if err != nil {
return nil, err
}
return auditRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
}
func (client *SQcloudClient) cbsRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
cli, err := client.getDefaultClient()
if err != nil {
return nil, err
}
return cbsRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
}
func (client *SQcloudClient) accountRequestRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
cli, err := client.getDefaultClient()
if err != nil {
return nil, err
}
return accountRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
}
func (client *SQcloudClient) clbRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
cli, err := client.getDefaultClient()
if err != nil {
return nil, err
}
return clbRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
}
func (client *SQcloudClient) lbRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
cli, err := client.getDefaultClient()
if err != nil {
return nil, err
}
return lbRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
}
func (client *SQcloudClient) cdbRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
cli, err := client.getDefaultClient()
if err != nil {
return nil, err
}
return cdbRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
}
func (client *SQcloudClient) esRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
cli, err := client.getDefaultClient()
if err != nil {
return nil, err
}
return esRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
}
func (client *SQcloudClient) kafkaRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
cli, err := client.getDefaultClient()
if err != nil {
return nil, err
}
return kafkaRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
}
func (client *SQcloudClient) redisRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
cli, err := client.getDefaultClient()
if err != nil {
return nil, err
}
return redisRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
}
func (client *SQcloudClient) dcdbRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
cli, err := client.getDefaultClient()
if err != nil {
return nil, err
}
return dcdbRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
}
func (client *SQcloudClient) mongodbRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
cli, err := client.getDefaultClient()
if err != nil {
return nil, err
}
return mongodbRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
}
func (client *SQcloudClient) memcachedRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
cli, err := client.getDefaultClient()
if err != nil {
return nil, err
}
return memcachedRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
}
func (client *SQcloudClient) mariadbRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
cli, err := client.getDefaultClient()
if err != nil {
return nil, err
}
return mariadbRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
}
func (client *SQcloudClient) postgresRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
cli, err := client.getDefaultClient()
if err != nil {
return nil, err
}
return postgresRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
}
func (client *SQcloudClient) sqlserverRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
cli, err := client.getDefaultClient()
if err != nil {
return nil, err
}
return sqlserverRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
}
// deprecated
func (client *SQcloudClient) wssRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
cli, err := client.getDefaultClient()
if err != nil {
return nil, err
}
return wssRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
}
func (client *SQcloudClient) sslRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
cli, err := client.getDefaultClient()
if err != nil {
return nil, err
}
return sslRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
}
func (client *SQcloudClient) dnsRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
cli, err := client.getDefaultClient()
if err != nil {
return nil, err
}
return dnsRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
}
func (client *SQcloudClient) vpc2017Request(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
cli, err := client.getDefaultClient()
if err != nil {
return nil, err
}
return vpc2017Request(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
}
func (client *SQcloudClient) billingRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
cli, err := client.getDefaultClient()
if err != nil {
return nil, err
}
return billingRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
}
func (client *SQcloudClient) camRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
cli, err := client.getDefaultClient()
if err != nil {
return nil, err
}
return camRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
}
func (client *SQcloudClient) cdnRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
cli, err := client.getDefaultClient()
if err != nil {
return nil, err
}
return cdnRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
}
func (client *SQcloudClient) jsonRequest(apiName string, params map[string]string, retry bool) (jsonutils.JSONObject, error) {
cli, err := client.getDefaultClient()
if err != nil {
return nil, err
}
return jsonRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug, retry)
}
func (client *SQcloudClient) fetchRegions() error {
body, err := client.jsonRequest("DescribeRegions", nil, true)
if err != nil {
log.Errorf("fetchRegions fail %s", err)
return err
}
regions := make([]SRegion, 0)
err = body.Unmarshal(&regions, "RegionSet")
if err != nil {
log.Errorf("unmarshal json error %s", err)
return err
}
client.iregions = make([]cloudprovider.ICloudRegion, len(regions))
for i := 0; i < len(regions); i++ {
regions[i].client = client
client.iregions[i] = &regions[i]
}
return nil
}
func (client *SQcloudClient) getCosClient(bucket *SBucket) (*cos.Client, error) {
var baseUrl *cos.BaseURL
if bucket != nil {
u, _ := url.Parse(bucket.getBucketUrl())
baseUrl = &cos.BaseURL{
BucketURL: u,
}
}
cosClient := cos.NewClient(
baseUrl,
&http.Client{
Transport: &cos.AuthorizationTransport{
SecretID: client.secretId,
SecretKey: client.secretKey,
Transport: &debug.DebugRequestTransport{
RequestHeader: client.debug,
RequestBody: client.debug,
ResponseHeader: client.debug,
ResponseBody: client.debug,
Transport: &http.Transport{
Proxy: client.cpcfg.ProxyFunc,
},
},
},
},
)
return cosClient, nil
}
func (self *SQcloudClient) invalidateIBuckets() {
self.ibuckets = nil
}
func (self *SQcloudClient) getIBuckets() ([]cloudprovider.ICloudBucket, error) {
if self.ibuckets == nil {
err := self.fetchBuckets()
if err != nil {
return nil, errors.Wrap(err, "fetchBuckets")
}
}
return self.ibuckets, nil
}
func (client *SQcloudClient) verifyAppId() error {
region, err := client.getDefaultRegion()
if err != nil {
return errors.Wrap(err, "getDefaultRegion")
}
bucket := SBucket{
region: region,
Name: "yuniondocument",
}
cli, err := client.getCosClient(&bucket)
if err != nil {
return errors.Wrap(err, "getCosClient")
}
resp, err := cli.Bucket.Head(context.Background())
if resp != nil {
defer httputils.CloseResponse(resp.Response)
if resp.StatusCode < 400 || resp.StatusCode == 404 {
return nil
}
return errors.Error(fmt.Sprintf("invalid AppId: %d", resp.StatusCode))
}
return errors.Wrap(err, "Head")
}
func (client *SQcloudClient) fetchBuckets() error {
coscli, err := client.getCosClient(nil)
if err != nil {
return errors.Wrap(err, "GetCosClient")
}
s, _, err := coscli.Service.Get(context.Background())
if err != nil {
return errors.Wrap(err, "coscli.Service.Get")
}
client.ownerId = s.Owner.ID
client.ownerName = s.Owner.DisplayName
ret := make([]cloudprovider.ICloudBucket, 0)
for i := range s.Buckets {
bInfo := s.Buckets[i]
createAt, _ := timeutils.ParseTimeStr(bInfo.CreationDate)
slashPos := strings.LastIndexByte(bInfo.Name, '-')
appId := bInfo.Name[slashPos+1:]
if appId != client.appId {
log.Errorf("[%s %s] Inconsistent appId: %s expect %s", bInfo.Name, bInfo.Region, appId, client.appId)
}
name := bInfo.Name[:slashPos]
region, err := client.getIRegionByRegionId(bInfo.Region)
var zone cloudprovider.ICloudZone
if err != nil {
log.Errorf("fail to find region %s", bInfo.Region)
// possibly a zone, try zone
regionStr := func() string {
info := strings.Split(bInfo.Region, "-")
if num, _ := strconv.Atoi(info[len(info)-1]); num > 0 {
return strings.TrimSuffix(bInfo.Region, fmt.Sprintf("-%d", num))
}
return bInfo.Region
}()
region, err = client.getIRegionByRegionId(regionStr)
if err != nil {
log.Errorf("fail to find region %s", regionStr)
continue
}
zone, _ = region.(*SRegion).getZoneById(bInfo.Region)
log.Debugf("find zonal bucket %s", zone.GetId())
}
b := SBucket{
region: region.(*SRegion),
appId: appId,
Name: name,
Location: bInfo.Region,
CreateDate: createAt,
}
if zone != nil {
b.zone = zone.(*SZone)
}
ret = append(ret, &b)
}
client.ibuckets = ret
return nil
}
func (client *SQcloudClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
err := client.fetchRegions()
if err != nil {
return nil, err
}
subAccount := cloudprovider.SSubAccount{}
subAccount.Name = client.cpcfg.Name
subAccount.Account = client.secretId
subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL
subAccount.DefaultProjectId = "0"
if len(client.appId) > 0 {
subAccount.Account = fmt.Sprintf("%s/%s", client.secretId, client.appId)
}
return []cloudprovider.SSubAccount{subAccount}, nil
}
func (client *SQcloudClient) GetAccountId() string {
return client.ownerName
}
func (client *SQcloudClient) GetIamLoginUrl() string {
return fmt.Sprintf("https://cloud.tencent.com/login/subAccount?account=%s", client.ownerName)
}
func (client *SQcloudClient) GetIRegions() []cloudprovider.ICloudRegion {
return client.iregions
}
func (client *SQcloudClient) getDefaultRegion() (*SRegion, error) {
iregion, err := client.getIRegionByRegionId(QCLOUD_DEFAULT_REGION)
if err != nil {
return nil, errors.Wrap(err, "getIRegionByRegionId")
}
return iregion.(*SRegion), nil
}
func (client *SQcloudClient) getIRegionByRegionId(id string) (cloudprovider.ICloudRegion, error) {
for i := 0; i < len(client.iregions); i++ {
if client.iregions[i].GetId() == id {
return client.iregions[i], nil
}
}
return nil, cloudprovider.ErrNotFound
}
func (client *SQcloudClient) GetIRegionById(id string) (cloudprovider.ICloudRegion, error) {
for i := 0; i < len(client.iregions); i++ {
if client.iregions[i].GetGlobalId() == id {
return client.iregions[i], nil
}
}
return nil, cloudprovider.ErrNotFound
}
func (client *SQcloudClient) GetRegion(regionId string) *SRegion {
for i := 0; i < len(client.iregions); i++ {
if client.iregions[i].GetId() == regionId {
return client.iregions[i].(*SRegion)
}
}
return nil
}
func (client *SQcloudClient) GetIHostById(id string) (cloudprovider.ICloudHost, error) {
for i := 0; i < len(client.iregions); i++ {
ihost, err := client.iregions[i].GetIHostById(id)
if err == nil {
return ihost, nil
} else if errors.Cause(err) != cloudprovider.ErrNotFound {
return nil, err
}
}
return nil, cloudprovider.ErrNotFound
}
func (client *SQcloudClient) GetIVpcById(id string) (cloudprovider.ICloudVpc, error) {
for i := 0; i < len(client.iregions); i++ {
ihost, err := client.iregions[i].GetIVpcById(id)
if err == nil {
return ihost, nil
} else if errors.Cause(err) != cloudprovider.ErrNotFound {
return nil, err
}
}
return nil, cloudprovider.ErrNotFound
}
func (client *SQcloudClient) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) {
for i := 0; i < len(client.iregions); i++ {
ihost, err := client.iregions[i].GetIStorageById(id)
if err == nil {
return ihost, nil
} else if errors.Cause(err) != cloudprovider.ErrNotFound {
return nil, err
}
}
return nil, cloudprovider.ErrNotFound
}
type SAccountBalance struct {
AvailableAmount float64
AvailableCashAmount float64
CreditAmount float64
MybankCreditAmount float64
Currency string
}
func (client *SQcloudClient) QueryAccountBalance() (*SAccountBalance, error) {
balance := SAccountBalance{}
body, err := client.billingRequest("DescribeAccountBalance", nil)
if err != nil {
if isError(err, []string{"UnauthorizedOperation.NotFinanceAuth"}) {
return nil, cloudprovider.ErrNoBalancePermission
}
log.Errorf("DescribeAccountBalance fail %s", err)
return nil, err
}
log.Debugf("%s", body)
balanceCent, _ := body.Float("Balance")
balance.AvailableAmount = balanceCent / 100.0
return &balance, nil
}
func (client *SQcloudClient) GetIProjects() ([]cloudprovider.ICloudProject, error) {
projects, err := client.GetProjects()
if err != nil {
return nil, errors.Wrap(err, "GetProjects")
}
projects = append(projects, SProject{ProjectId: "0", ProjectName: "默认项目"})
iprojects := []cloudprovider.ICloudProject{}
for i := 0; i < len(projects); i++ {
projects[i].client = client
iprojects = append(iprojects, &projects[i])
}
return iprojects, nil
}
func (self *SQcloudClient) GetCapabilities() []string {
caps := []string{
cloudprovider.CLOUD_CAPABILITY_PROJECT,
cloudprovider.CLOUD_CAPABILITY_COMPUTE,
cloudprovider.CLOUD_CAPABILITY_NETWORK,
cloudprovider.CLOUD_CAPABILITY_LOADBALANCER,
cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE,
cloudprovider.CLOUD_CAPABILITY_RDS,
cloudprovider.CLOUD_CAPABILITY_CACHE,
cloudprovider.CLOUD_CAPABILITY_EVENT,
cloudprovider.CLOUD_CAPABILITY_CLOUDID,
cloudprovider.CLOUD_CAPABILITY_DNSZONE,
cloudprovider.CLOUD_CAPABILITY_PUBLIC_IP,
cloudprovider.CLOUD_CAPABILITY_INTERVPCNETWORK,
cloudprovider.CLOUD_CAPABILITY_SAML_AUTH,
cloudprovider.CLOUD_CAPABILITY_QUOTA + cloudprovider.READ_ONLY_SUFFIX,
cloudprovider.CLOUD_CAPABILITY_MONGO_DB + cloudprovider.READ_ONLY_SUFFIX,
cloudprovider.CLOUD_CAPABILITY_ES + cloudprovider.READ_ONLY_SUFFIX,
cloudprovider.CLOUD_CAPABILITY_KAFKA + cloudprovider.READ_ONLY_SUFFIX,
cloudprovider.CLOUD_CAPABILITY_CDN + cloudprovider.READ_ONLY_SUFFIX,
}
return caps
}