apsara.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  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. package apsara
  15. import (
  16. "bytes"
  17. "crypto/tls"
  18. "encoding/base64"
  19. "fmt"
  20. "io/ioutil"
  21. "net/http"
  22. "net/url"
  23. "strings"
  24. "time"
  25. "github.com/aliyun/alibaba-cloud-sdk-go/sdk"
  26. "github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
  27. "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
  28. "github.com/aliyun/aliyun-oss-go-sdk/oss"
  29. "github.com/pkg/errors"
  30. "yunion.io/x/jsonutils"
  31. "yunion.io/x/log"
  32. "yunion.io/x/pkg/util/httputils"
  33. v "yunion.io/x/pkg/util/version"
  34. "yunion.io/x/pkg/utils"
  35. api "yunion.io/x/cloudmux/pkg/apis/compute"
  36. "yunion.io/x/cloudmux/pkg/cloudprovider"
  37. )
  38. const (
  39. CLOUD_PROVIDER_APSARA = api.CLOUD_PROVIDER_APSARA
  40. CLOUD_PROVIDER_APSARA_CN = "阿里云专有云"
  41. CLOUD_PROVIDER_APSARA_EN = "Aliyun Apsara"
  42. APSARA_API_VERSION = "2014-05-26"
  43. APSARA_API_VERSION_VPC = "2016-04-28"
  44. APSARA_API_VERSION_LB = "2014-05-15"
  45. APSARA_API_VERSION_KVS = "2015-01-01"
  46. APSARA_API_VERSION_TRIAL = "2017-12-04"
  47. APSARA_BSS_API_VERSION = "2017-12-14"
  48. APSARA_RAM_API_VERSION = "2015-05-01"
  49. APSARA_API_VERION_RDS = "2014-08-15"
  50. APSARA_ASCM_API_VERSION = "2019-05-10"
  51. APSARA_STS_API_VERSION = "2015-04-01"
  52. APSARA_OTS_API_VERSION = "2016-06-20"
  53. APSARA_PRODUCT_METRICS = "Cms"
  54. APSARA_PRODUCT_RDS = "Rds"
  55. APSARA_PRODUCT_VPC = "Vpc"
  56. APSARA_PRODUCT_KVSTORE = "R-kvstore"
  57. APSARA_PRODUCT_SLB = "Slb"
  58. APSARA_PRODUCT_ECS = "Ecs"
  59. APSARA_PRODUCT_ACTION_TRIAL = "actiontrail"
  60. APSARA_PRODUCT_STS = "Sts"
  61. APSARA_PRODUCT_RAM = "Ram"
  62. APSARA_PRODUCT_ASCM = "ascm"
  63. APSARA_PRODUCT_OTS = "ots"
  64. )
  65. type ApsaraClientConfig struct {
  66. cpcfg cloudprovider.ProviderConfig
  67. accessKey string
  68. accessSecret string
  69. organizationId string
  70. debug bool
  71. }
  72. func NewApsaraClientConfig(accessKey, accessSecret string, endpoint string) *ApsaraClientConfig {
  73. cfg := &ApsaraClientConfig{
  74. accessKey: accessKey,
  75. accessSecret: accessSecret,
  76. organizationId: "1",
  77. }
  78. if info := strings.Split(accessKey, "/"); len(info) == 2 {
  79. cfg.accessKey, cfg.organizationId = info[0], info[1]
  80. }
  81. return cfg
  82. }
  83. func (cfg *ApsaraClientConfig) CloudproviderConfig(cpcfg cloudprovider.ProviderConfig) *ApsaraClientConfig {
  84. cfg.cpcfg = cpcfg
  85. return cfg
  86. }
  87. func (cfg *ApsaraClientConfig) Debug(debug bool) *ApsaraClientConfig {
  88. cfg.debug = debug
  89. return cfg
  90. }
  91. func (cfg ApsaraClientConfig) Copy() ApsaraClientConfig {
  92. return cfg
  93. }
  94. type SApsaraClient struct {
  95. *ApsaraClientConfig
  96. ownerId string
  97. ownerName string
  98. departments []string
  99. iregions []cloudprovider.ICloudRegion
  100. }
  101. func NewApsaraClient(cfg *ApsaraClientConfig) (*SApsaraClient, error) {
  102. client := SApsaraClient{
  103. ApsaraClientConfig: cfg,
  104. }
  105. err := client.fetchRegions()
  106. if err != nil {
  107. return nil, errors.Wrap(err, "fetchRegions")
  108. }
  109. return &client, nil
  110. }
  111. func (self *SApsaraClient) getDomain(product string) string {
  112. return self.cpcfg.URL
  113. }
  114. func productRequest(client *sdk.Client, product, domain, apiVersion, apiName string, params map[string]string, debug bool) (jsonutils.JSONObject, error) {
  115. params["Product"] = product
  116. return jsonRequest(client, domain, apiVersion, apiName, params, debug)
  117. }
  118. func jsonRequest(client *sdk.Client, domain, apiVersion, apiName string, params map[string]string, debug bool) (jsonutils.JSONObject, error) {
  119. if debug {
  120. log.Debugf("request %s %s %s %s", domain, apiVersion, apiName, params)
  121. }
  122. var resp jsonutils.JSONObject
  123. var err error
  124. for i := 1; i < 4; i++ {
  125. resp, err = _jsonRequest(client, domain, apiVersion, apiName, params)
  126. retry := false
  127. if err != nil {
  128. for _, code := range []string{
  129. "InvalidAccessKeyId.NotFound",
  130. } {
  131. if strings.Contains(err.Error(), code) {
  132. return nil, err
  133. }
  134. }
  135. for _, code := range []string{"404 Not Found", "EntityNotExist.Role", "EntityNotExist.Group"} {
  136. if strings.Contains(err.Error(), code) {
  137. return nil, errors.Wrapf(cloudprovider.ErrNotFound, "%s", err.Error())
  138. }
  139. }
  140. for _, code := range []string{
  141. "EOF",
  142. "i/o timeout",
  143. "TLS handshake timeout",
  144. "connection reset by peer",
  145. "server misbehaving",
  146. "SignatureNonceUsed",
  147. "InvalidInstance.NotSupported",
  148. "try later",
  149. "BackendServer.configuring",
  150. "Another operation is being performed", //Another operation is being performed on the DB instance or the DB instance is faulty(赋予RDS账号权限)
  151. } {
  152. if strings.Contains(err.Error(), code) {
  153. retry = true
  154. break
  155. }
  156. }
  157. }
  158. if retry {
  159. if debug {
  160. log.Debugf("Retry %d...", i)
  161. }
  162. time.Sleep(time.Second * time.Duration(i*10))
  163. continue
  164. }
  165. if debug {
  166. log.Debugf("Response: %s", resp)
  167. }
  168. return resp, err
  169. }
  170. return resp, errors.Wrapf(err, "jsonRequest")
  171. }
  172. func _jsonRequest(client *sdk.Client, domain string, version string, apiName string, params map[string]string) (jsonutils.JSONObject, error) {
  173. req := requests.NewCommonRequest()
  174. req.Domain = domain
  175. req.Version = version
  176. req.ApiName = apiName
  177. req.Scheme = "http"
  178. req.Method = "POST"
  179. if strings.HasPrefix(domain, "public.") {
  180. req.Scheme = "https"
  181. }
  182. id := ""
  183. if params != nil {
  184. for k, v := range params {
  185. if strings.HasPrefix(k, "x-acs-") {
  186. req.GetHeaders()[k] = v
  187. continue
  188. }
  189. req.QueryParams[k] = v
  190. if strings.ToLower(k) != "regionid" && strings.HasSuffix(k, "Id") {
  191. id = v
  192. }
  193. }
  194. }
  195. req.GetHeaders()["User-Agent"] = "vendor/yunion-OneCloud@" + v.Get().GitVersion
  196. if strings.HasPrefix(apiName, "Describe") && len(id) > 0 {
  197. req.GetHeaders()["x-acs-instanceId"] = id
  198. }
  199. resp, err := processCommonRequest(client, req)
  200. if err != nil {
  201. return nil, errors.Wrapf(err, "processCommonRequest(%s, %s)", apiName, params)
  202. }
  203. body, err := jsonutils.Parse(resp.GetHttpContentBytes())
  204. if err != nil {
  205. return nil, errors.Wrapf(err, "jsonutils.Parse")
  206. }
  207. //{"Code":"InvalidInstanceType.ValueNotSupported","HostId":"ecs.apsaracs.com","Message":"The specified instanceType beyond the permitted range.","RequestId":"0042EE30-0EDF-48A7-A414-56229D4AD532"}
  208. //{"Code":"200","Message":"successful","PageNumber":1,"PageSize":50,"RequestId":"BB4C970C-0E23-48DC-A3B0-EB21FFC70A29","RouterTableList":{"RouterTableListType":[{"CreationTime":"2017-03-19T13:37:40Z","Description":"","ResourceGroupId":"rg-acfmwie3cqoobmi","RouteTableId":"vtb-j6c60lectdi80rk5xz43g","RouteTableName":"","RouteTableType":"System","RouterId":"vrt-j6c00qrol733dg36iq4qj","RouterType":"VRouter","VSwitchIds":{"VSwitchId":["vsw-j6c3gig5ub4fmi2veyrus"]},"VpcId":"vpc-j6c86z3sh8ufhgsxwme0q"}]},"Success":true,"TotalCount":1}
  209. if body.Contains("Code") {
  210. code, _ := body.GetString("Code")
  211. if len(code) > 0 && !utils.IsInStringArray(code, []string{"200"}) {
  212. return nil, fmt.Errorf("%s", body.String())
  213. }
  214. }
  215. if body.Contains("errorKey") {
  216. return nil, errors.Errorf("%s", body.String())
  217. }
  218. return body, nil
  219. }
  220. func (self *SApsaraClient) getDefaultClient(regionId string) (*sdk.Client, error) {
  221. if len(self.iregions) > 0 && len(regionId) == 0 {
  222. regionId = self.iregions[0].GetId()
  223. }
  224. transport := httputils.GetTransport(true)
  225. transport.Proxy = self.cpcfg.ProxyFunc
  226. transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
  227. client, err := sdk.NewClientWithOptions(
  228. regionId,
  229. &sdk.Config{
  230. HttpTransport: transport,
  231. Transport: cloudprovider.GetCheckTransport(transport, func(req *http.Request) (func(resp *http.Response) error, error) {
  232. params, err := url.ParseQuery(req.URL.RawQuery)
  233. if err != nil {
  234. return nil, errors.Wrapf(err, "ParseQuery(%s)", req.URL.RawQuery)
  235. }
  236. action := params.Get("OpenApiAction")
  237. if len(action) == 0 {
  238. action = params.Get("Action")
  239. }
  240. service := strings.ToLower(params.Get("Product"))
  241. respCheck := func(resp *http.Response) error {
  242. if self.cpcfg.UpdatePermission != nil {
  243. body, err := ioutil.ReadAll(resp.Body)
  244. if err != nil {
  245. return nil
  246. }
  247. resp.Body = ioutil.NopCloser(bytes.NewBuffer(body))
  248. obj, err := jsonutils.Parse(body)
  249. if err != nil {
  250. return nil
  251. }
  252. ret := struct {
  253. AsapiErrorCode string `json:"asapiErrorCode"`
  254. Code string
  255. }{}
  256. obj.Unmarshal(&ret)
  257. if ret.Code == "403" ||
  258. strings.Contains(ret.AsapiErrorCode, "NoPermission") ||
  259. utils.HasPrefix(ret.Code, "Forbidden") ||
  260. utils.HasPrefix(ret.Code, "NoPermission") {
  261. self.cpcfg.UpdatePermission(service, action)
  262. }
  263. }
  264. return nil
  265. }
  266. if self.cpcfg.ReadOnly && len(action) > 0 {
  267. for _, prefix := range []string{"Get", "List", "Describe"} {
  268. if strings.HasPrefix(action, prefix) {
  269. return respCheck, nil
  270. }
  271. }
  272. return nil, errors.Wrapf(cloudprovider.ErrAccountReadOnly, "%s", action)
  273. }
  274. return respCheck, nil
  275. }),
  276. },
  277. &credentials.BaseCredential{
  278. AccessKeyId: self.accessKey,
  279. AccessKeySecret: self.accessSecret,
  280. },
  281. )
  282. return client, err
  283. }
  284. func (self *SApsaraClient) ascmRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
  285. cli, err := self.getDefaultClient("")
  286. if err != nil {
  287. return nil, err
  288. }
  289. return productRequest(cli, APSARA_PRODUCT_ASCM, self.cpcfg.URL, APSARA_ASCM_API_VERSION, apiName, params, self.debug)
  290. }
  291. func (self *SApsaraClient) ecsRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
  292. cli, err := self.getDefaultClient("")
  293. if err != nil {
  294. return nil, err
  295. }
  296. domain := self.getDomain(APSARA_PRODUCT_ECS)
  297. return productRequest(cli, APSARA_PRODUCT_ECS, domain, APSARA_API_VERSION, apiName, params, self.debug)
  298. }
  299. func (self *SApsaraClient) getAccountInfo() string {
  300. account := map[string]string{
  301. "aliyunPk": "",
  302. "accountStructure": "",
  303. "parentPk": "26842",
  304. "accessKeyId": self.accessKey,
  305. "accessKeySecret": self.accessSecret,
  306. "partnerPk": "",
  307. "sourceIp": "",
  308. "securityToken": "",
  309. }
  310. return base64.StdEncoding.EncodeToString([]byte(jsonutils.Marshal(account).String()))
  311. }
  312. func (self *SApsaraClient) ossRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
  313. cli, err := self.getDefaultClient("")
  314. if err != nil {
  315. return nil, err
  316. }
  317. //pm := map[string]string{}
  318. //for k, v := range params {
  319. // if k != "RegionId" {
  320. // pm[k] = v
  321. // delete(params, k)
  322. // }
  323. //}
  324. //if len(pm) > 0 {
  325. // params["Params"] = jsonutils.Marshal(pm).String()
  326. //}
  327. if _, ok := params["RegionId"]; !ok {
  328. params["RegionId"] = self.cpcfg.RegionId
  329. }
  330. params["ProductName"] = "oss"
  331. params["OpenApiAction"] = apiName
  332. return productRequest(cli, "OneRouter", self.cpcfg.URL, "2018-12-12", "DoOpenApi", params, self.debug)
  333. }
  334. func (self *SApsaraClient) trialRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
  335. cli, err := self.getDefaultClient("")
  336. if err != nil {
  337. return nil, err
  338. }
  339. domain := self.getDomain(APSARA_PRODUCT_ACTION_TRIAL)
  340. return productRequest(cli, APSARA_PRODUCT_ACTION_TRIAL, domain, APSARA_API_VERSION_TRIAL, apiName, params, self.debug)
  341. }
  342. func (self *SApsaraClient) fetchRegions() error {
  343. params := map[string]string{"AcceptLanguage": "zh-CN"}
  344. if len(self.cpcfg.RegionId) > 0 {
  345. params["RegionId"] = self.cpcfg.RegionId
  346. }
  347. body, err := self.ecsRequest("DescribeRegions", params)
  348. if err != nil {
  349. return errors.Wrapf(err, "DescribeRegions")
  350. }
  351. regions := make([]SRegion, 0)
  352. err = body.Unmarshal(&regions, "Regions", "Region")
  353. if err != nil {
  354. return errors.Wrapf(err, "body.Unmarshal")
  355. }
  356. self.iregions = make([]cloudprovider.ICloudRegion, len(regions))
  357. for i := 0; i < len(regions); i += 1 {
  358. regions[i].client = self
  359. self.iregions[i] = &regions[i]
  360. }
  361. return nil
  362. }
  363. // https://help.apsara.com/document_detail/31837.html?spm=a2c4g.11186623.2.6.XqEgD1
  364. func (client *SApsaraClient) getOssClient(endpoint string) (*oss.Client, error) {
  365. // NOTE
  366. //
  367. // oss package as of version 20181116160301-c6838fdc33ed does not
  368. // respect http.ProxyFromEnvironment.
  369. //
  370. // The ClientOption Proxy, AuthProxy lacks the feature NO_PROXY has
  371. // which can be used to whitelist ips, domains from http_proxy,
  372. // https_proxy setting
  373. // oss use no timeout client so as to send/download large files
  374. httpClient := client.cpcfg.AdaptiveTimeoutHttpClient()
  375. transport, _ := httpClient.Transport.(*http.Transport)
  376. httpClient.Transport = cloudprovider.GetCheckTransport(transport, func(req *http.Request) (func(resp *http.Response) error, error) {
  377. if client.cpcfg.ReadOnly {
  378. if req.Method == "GET" || req.Method == "HEAD" {
  379. return nil, nil
  380. }
  381. return nil, errors.Wrapf(cloudprovider.ErrAccountReadOnly, "%s %s", req.Method, req.URL.Path)
  382. }
  383. return nil, nil
  384. })
  385. cliOpts := []oss.ClientOption{
  386. oss.HTTPClient(httpClient),
  387. }
  388. cli, err := oss.New(endpoint, client.accessKey, client.accessSecret, cliOpts...)
  389. if err != nil {
  390. return nil, errors.Wrap(err, "oss.New")
  391. }
  392. return cli, nil
  393. }
  394. func (self *SApsaraClient) GetRegions() []SRegion {
  395. regions := make([]SRegion, len(self.iregions))
  396. for i := 0; i < len(regions); i += 1 {
  397. region := self.iregions[i].(*SRegion)
  398. regions[i] = *region
  399. }
  400. return regions
  401. }
  402. func (self *SApsaraClient) GetProvider() string {
  403. return self.cpcfg.Vendor
  404. }
  405. func (self *SApsaraClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
  406. err := self.fetchRegions()
  407. if err != nil {
  408. return nil, err
  409. }
  410. subAccount := cloudprovider.SSubAccount{}
  411. subAccount.Id = self.GetAccountId()
  412. subAccount.Name = self.cpcfg.Name
  413. subAccount.Account = self.accessKey
  414. if self.organizationId != "1" {
  415. subAccount.Account = fmt.Sprintf("%s/%s", self.accessKey, self.organizationId)
  416. }
  417. subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL
  418. return []cloudprovider.SSubAccount{subAccount}, nil
  419. }
  420. func (self *SApsaraClient) GetAccountId() string {
  421. return self.cpcfg.URL
  422. }
  423. func (self *SApsaraClient) GetIRegions() ([]cloudprovider.ICloudRegion, error) {
  424. return self.iregions, nil
  425. }
  426. func (self *SApsaraClient) GetIRegionById(id string) (cloudprovider.ICloudRegion, error) {
  427. for i := 0; i < len(self.iregions); i += 1 {
  428. if self.iregions[i].GetGlobalId() == id {
  429. return self.iregions[i], nil
  430. }
  431. }
  432. return nil, cloudprovider.ErrNotFound
  433. }
  434. func (self *SApsaraClient) GetRegion(regionId string) *SRegion {
  435. for i := 0; i < len(self.iregions); i += 1 {
  436. if self.iregions[i].GetId() == regionId {
  437. return self.iregions[i].(*SRegion)
  438. }
  439. }
  440. return nil
  441. }
  442. func (self *SApsaraClient) GetIHostById(id string) (cloudprovider.ICloudHost, error) {
  443. for i := 0; i < len(self.iregions); i += 1 {
  444. ihost, err := self.iregions[i].GetIHostById(id)
  445. if err == nil {
  446. return ihost, nil
  447. } else if err != cloudprovider.ErrNotFound {
  448. return nil, err
  449. }
  450. }
  451. return nil, cloudprovider.ErrNotFound
  452. }
  453. func (self *SApsaraClient) GetIVpcById(id string) (cloudprovider.ICloudVpc, error) {
  454. for i := 0; i < len(self.iregions); i += 1 {
  455. ihost, err := self.iregions[i].GetIVpcById(id)
  456. if err == nil {
  457. return ihost, nil
  458. } else if err != cloudprovider.ErrNotFound {
  459. return nil, err
  460. }
  461. }
  462. return nil, cloudprovider.ErrNotFound
  463. }
  464. func (self *SApsaraClient) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) {
  465. for i := 0; i < len(self.iregions); i += 1 {
  466. ihost, err := self.iregions[i].GetIStorageById(id)
  467. if err == nil {
  468. return ihost, nil
  469. } else if err != cloudprovider.ErrNotFound {
  470. return nil, err
  471. }
  472. }
  473. return nil, cloudprovider.ErrNotFound
  474. }
  475. func (region *SApsaraClient) GetCapabilities() []string {
  476. caps := []string{
  477. cloudprovider.CLOUD_CAPABILITY_PROJECT,
  478. cloudprovider.CLOUD_CAPABILITY_COMPUTE,
  479. cloudprovider.CLOUD_CAPABILITY_NETWORK,
  480. cloudprovider.CLOUD_CAPABILITY_SECURITY_GROUP,
  481. cloudprovider.CLOUD_CAPABILITY_EIP,
  482. cloudprovider.CLOUD_CAPABILITY_LOADBALANCER,
  483. cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE,
  484. cloudprovider.CLOUD_CAPABILITY_RDS,
  485. cloudprovider.CLOUD_CAPABILITY_CACHE,
  486. cloudprovider.CLOUD_CAPABILITY_QUOTA + cloudprovider.READ_ONLY_SUFFIX,
  487. cloudprovider.CLOUD_CAPABILITY_IPV6_GATEWAY + cloudprovider.READ_ONLY_SUFFIX,
  488. cloudprovider.CLOUD_CAPABILITY_TABLESTORE + cloudprovider.READ_ONLY_SUFFIX,
  489. cloudprovider.CLOUD_CAPABILITY_SNAPSHOT_POLICY,
  490. }
  491. return caps
  492. }