baidu.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  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 baidu
  15. import (
  16. "context"
  17. "crypto/tls"
  18. "fmt"
  19. "io"
  20. "net/http"
  21. "net/url"
  22. "strings"
  23. "sync"
  24. "time"
  25. "yunion.io/x/jsonutils"
  26. "yunion.io/x/pkg/errors"
  27. "yunion.io/x/pkg/gotypes"
  28. "yunion.io/x/pkg/util/httputils"
  29. api "yunion.io/x/cloudmux/pkg/apis/compute"
  30. "yunion.io/x/cloudmux/pkg/cloudprovider"
  31. )
  32. const (
  33. CLOUD_PROVIDER_BAIDU_CN = "百度云"
  34. BAIDU_DEFAULT_REGION = "bj"
  35. ISO8601 = "2006-01-02T15:04:05Z"
  36. SERVICE_STS = "sts"
  37. SERVICE_BBC = "bbc"
  38. SERVICE_BCC = "bcc"
  39. SERVICE_BOS = "bos"
  40. SERVICE_EIP = "eip"
  41. SERVICE_BCM = "bcm"
  42. SERVICE_BILLING = "billing"
  43. )
  44. type BaiduClientConfig struct {
  45. cpcfg cloudprovider.ProviderConfig
  46. accessKeyId string
  47. accessKeySecret string
  48. debug bool
  49. }
  50. type SBaiduClient struct {
  51. *BaiduClientConfig
  52. client *http.Client
  53. lock sync.Mutex
  54. ctx context.Context
  55. ownerId string
  56. }
  57. func NewBaiduClientConfig(accessKeyId, accessKeySecret string) *BaiduClientConfig {
  58. cfg := &BaiduClientConfig{
  59. accessKeyId: accessKeyId,
  60. accessKeySecret: accessKeySecret,
  61. }
  62. return cfg
  63. }
  64. func (cfg *BaiduClientConfig) Debug(debug bool) *BaiduClientConfig {
  65. cfg.debug = debug
  66. return cfg
  67. }
  68. func (cfg *BaiduClientConfig) CloudproviderConfig(cpcfg cloudprovider.ProviderConfig) *BaiduClientConfig {
  69. cfg.cpcfg = cpcfg
  70. return cfg
  71. }
  72. func NewBaiduClient(cfg *BaiduClientConfig) (*SBaiduClient, error) {
  73. client := &SBaiduClient{
  74. BaiduClientConfig: cfg,
  75. ctx: context.Background(),
  76. }
  77. client.ctx = context.WithValue(client.ctx, "time", time.Now())
  78. _, err := client.getOwnerId()
  79. return client, err
  80. }
  81. func (cli *SBaiduClient) GetRegions() ([]SRegion, error) {
  82. resp, err := cli.post(SERVICE_BCC, "", "v2/region/describeRegions", nil, nil)
  83. if err != nil {
  84. return nil, err
  85. }
  86. ret := []SRegion{}
  87. err = resp.Unmarshal(&ret, "regions")
  88. if err != nil {
  89. return nil, err
  90. }
  91. for i := range ret {
  92. ret[i].client = cli
  93. }
  94. return ret, nil
  95. }
  96. func (cli *SBaiduClient) GetRegion(id string) (*SRegion, error) {
  97. regions, err := cli.GetRegions()
  98. if err != nil {
  99. return nil, err
  100. }
  101. for i := range regions {
  102. if regions[i].GetId() == id || regions[i].GetGlobalId() == id {
  103. return &regions[i], nil
  104. }
  105. }
  106. return nil, errors.Wrapf(cloudprovider.ErrNotFound, "%s", id)
  107. }
  108. func (cli *SBaiduClient) getUrl(service, regionId, bucketName, resource string) (string, error) {
  109. if len(regionId) == 0 {
  110. regionId = BAIDU_DEFAULT_REGION
  111. }
  112. switch service {
  113. case SERVICE_BBC:
  114. return fmt.Sprintf("https://bbc.%s.baidubce.com/%s", regionId, strings.TrimPrefix(resource, "/")), nil
  115. case SERVICE_BCC:
  116. return fmt.Sprintf("https://bcc.%s.baidubce.com/%s", regionId, strings.TrimPrefix(resource, "/")), nil
  117. case SERVICE_BOS:
  118. if len(bucketName) > 0 {
  119. return fmt.Sprintf("https://%s.%s.bcebos.com/%s", bucketName, regionId, strings.TrimPrefix(resource, "/")), nil
  120. }
  121. return fmt.Sprintf("https://%s.bcebos.com/%s", regionId, strings.TrimPrefix(resource, "/")), nil
  122. case SERVICE_BILLING:
  123. return fmt.Sprintf("https://billing.baidubce.com/%s", strings.TrimPrefix(resource, "/")), nil
  124. case SERVICE_STS:
  125. return fmt.Sprintf("https://sts.bj.baidubce.com/v1/%s", strings.TrimPrefix(resource, "/")), nil
  126. case SERVICE_EIP:
  127. return fmt.Sprintf("https://eip.%s.baidubce.com/%s", regionId, strings.TrimPrefix(resource, "/")), nil
  128. case SERVICE_BCM:
  129. return fmt.Sprintf("http://bcm.%s.baidubce.com/%s", regionId, strings.TrimPrefix(resource, "/")), nil
  130. default:
  131. return "", errors.Wrapf(cloudprovider.ErrNotSupported, "%s", service)
  132. }
  133. }
  134. func (cli *SBaiduClient) getDefaultClient() *http.Client {
  135. cli.lock.Lock()
  136. defer cli.lock.Unlock()
  137. if !gotypes.IsNil(cli.client) {
  138. return cli.client
  139. }
  140. cli.client = httputils.GetAdaptiveTimeoutClient()
  141. httputils.SetClientProxyFunc(cli.client, cli.cpcfg.ProxyFunc)
  142. ts, _ := cli.client.Transport.(*http.Transport)
  143. ts.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
  144. cli.client.Transport = cloudprovider.GetCheckTransport(ts, func(req *http.Request) (func(resp *http.Response) error, error) {
  145. if cli.cpcfg.ReadOnly {
  146. if req.Method == "GET" {
  147. return nil, nil
  148. }
  149. return nil, errors.Wrapf(cloudprovider.ErrAccountReadOnly, "%s %s", req.Method, req.URL.Path)
  150. }
  151. return nil, nil
  152. })
  153. return cli.client
  154. }
  155. func (cli *SBaiduClient) bosList(regionId, bucketName, resource string, params url.Values) (jsonutils.JSONObject, error) {
  156. resp, err := cli.raw_request(httputils.GET, SERVICE_BOS, regionId, bucketName, resource, params, nil, nil)
  157. if err != nil {
  158. return nil, err
  159. }
  160. _, ret, err := httputils.ParseJSONResponse("", resp, err, cli.debug)
  161. if err != nil {
  162. return nil, errors.Wrapf(err, "ParseJSONResponse")
  163. }
  164. if gotypes.IsNil(ret) {
  165. return jsonutils.NewDict(), nil
  166. }
  167. return ret, nil
  168. }
  169. func (cli *SBaiduClient) bosDelete(regionId, bucketName, resource string, params url.Values) (jsonutils.JSONObject, error) {
  170. resp, err := cli.raw_request(httputils.DELETE, SERVICE_BOS, regionId, bucketName, resource, params, nil, nil)
  171. if err != nil {
  172. return nil, err
  173. }
  174. _, ret, err := httputils.ParseJSONResponse("", resp, err, cli.debug)
  175. if err != nil {
  176. return nil, errors.Wrapf(err, "ParseJSONResponse")
  177. }
  178. if gotypes.IsNil(ret) {
  179. return jsonutils.NewDict(), nil
  180. }
  181. return ret, nil
  182. }
  183. func (cli *SBaiduClient) bosUpdate(regionId, bucketName, resource string, params url.Values, body map[string]interface{}) (jsonutils.JSONObject, error) {
  184. var bodyReader io.Reader = nil
  185. if !gotypes.IsNil(body) {
  186. bodyReader = strings.NewReader(jsonutils.Marshal(body).String())
  187. }
  188. resp, err := cli.raw_request(httputils.PUT, SERVICE_BOS, regionId, bucketName, resource, params, nil, bodyReader)
  189. if err != nil {
  190. return nil, err
  191. }
  192. _, ret, err := httputils.ParseJSONResponse("", resp, err, cli.debug)
  193. if err != nil {
  194. return nil, errors.Wrapf(err, "ParseJSONResponse")
  195. }
  196. if gotypes.IsNil(ret) {
  197. return jsonutils.NewDict(), nil
  198. }
  199. return ret, nil
  200. }
  201. func (cli *SBaiduClient) bosRequest(method httputils.THttpMethod, regionId, bucketName, resource string, params url.Values, header http.Header, body io.Reader) (*http.Response, error) {
  202. return cli.raw_request(method, SERVICE_BOS, regionId, bucketName, resource, params, header, body)
  203. }
  204. func (cli *SBaiduClient) eipList(regionId, resource string, params url.Values) (jsonutils.JSONObject, error) {
  205. return cli.list(SERVICE_EIP, regionId, resource, params)
  206. }
  207. func (cli *SBaiduClient) eipPost(regionId, resource string, params url.Values, body map[string]interface{}) (jsonutils.JSONObject, error) {
  208. return cli.post(SERVICE_EIP, regionId, resource, params, body)
  209. }
  210. func (cli *SBaiduClient) eipDelete(regionId, resource string, params url.Values) (jsonutils.JSONObject, error) {
  211. return cli.delete(SERVICE_EIP, regionId, resource, params)
  212. }
  213. func (cli *SBaiduClient) eipUpdate(regionId, resource string, params url.Values, body map[string]interface{}) (jsonutils.JSONObject, error) {
  214. return cli.update(SERVICE_EIP, regionId, resource, params, body)
  215. }
  216. func (cli *SBaiduClient) bccList(regionId, resource string, params url.Values) (jsonutils.JSONObject, error) {
  217. return cli.list(SERVICE_BCC, regionId, resource, params)
  218. }
  219. func (cli *SBaiduClient) bccPost(regionId, resource string, params url.Values, body map[string]interface{}) (jsonutils.JSONObject, error) {
  220. return cli.post(SERVICE_BCC, regionId, resource, params, body)
  221. }
  222. func (cli *SBaiduClient) bccDelete(regionId, resource string, params url.Values) (jsonutils.JSONObject, error) {
  223. return cli.delete(SERVICE_BCC, regionId, resource, params)
  224. }
  225. func (cli *SBaiduClient) bccUpdate(regionId, resource string, params url.Values, body map[string]interface{}) (jsonutils.JSONObject, error) {
  226. return cli.update(SERVICE_BCC, regionId, resource, params, body)
  227. }
  228. func (cli *SBaiduClient) bcmPost(regionId, resource string, params url.Values, body map[string]interface{}) (jsonutils.JSONObject, error) {
  229. return cli.post(SERVICE_BCM, regionId, resource, params, body)
  230. }
  231. func (cli *SBaiduClient) list(service, regionId, resource string, params url.Values) (jsonutils.JSONObject, error) {
  232. return cli.request(httputils.GET, service, regionId, resource, params, nil)
  233. }
  234. func (cli *SBaiduClient) update(service, regionId, resource string, params url.Values, body map[string]interface{}) (jsonutils.JSONObject, error) {
  235. return cli.request(httputils.PUT, service, regionId, resource, params, body)
  236. }
  237. func (cli *SBaiduClient) delete(service, regionId, resource string, params url.Values) (jsonutils.JSONObject, error) {
  238. return cli.request(httputils.DELETE, service, regionId, resource, params, nil)
  239. }
  240. func (cli *SBaiduClient) post(service, regionId, resource string, params url.Values, body map[string]interface{}) (jsonutils.JSONObject, error) {
  241. return cli.request(httputils.POST, service, regionId, resource, params, body)
  242. }
  243. func (cli *SBaiduClient) request(method httputils.THttpMethod, service, regionId, resource string, params url.Values, body map[string]interface{}) (jsonutils.JSONObject, error) {
  244. var bodyReader io.Reader = nil
  245. if !gotypes.IsNil(body) {
  246. bodyReader = strings.NewReader(jsonutils.Marshal(body).String())
  247. }
  248. header := http.Header{}
  249. header.Set("Content-Type", "application/json; charset=utf-8")
  250. resp, err := cli.raw_request(method, service, regionId, "", resource, params, header, bodyReader)
  251. if err != nil {
  252. return nil, err
  253. }
  254. _, ret, err := httputils.ParseJSONResponse("", resp, err, cli.debug)
  255. if err != nil {
  256. return nil, errors.Wrapf(err, "ParseJSONResponse")
  257. }
  258. if gotypes.IsNil(ret) {
  259. return jsonutils.NewDict(), nil
  260. }
  261. return ret, nil
  262. }
  263. func (cli *SBaiduClient) raw_request(method httputils.THttpMethod, service, regionId, bucketName, resource string, params url.Values, header http.Header, body io.Reader) (*http.Response, error) {
  264. uri, err := cli.getUrl(service, regionId, bucketName, resource)
  265. if err != nil {
  266. return nil, err
  267. }
  268. if len(params) > 0 {
  269. uri = fmt.Sprintf("%s?%s", uri, params.Encode())
  270. }
  271. if gotypes.IsNil(header) {
  272. header = http.Header{}
  273. }
  274. burl, err := url.Parse(uri)
  275. if err != nil {
  276. return nil, err
  277. }
  278. header.Set("x-bce-date", time.Now().UTC().Format(ISO8601))
  279. header.Set("host", burl.Host)
  280. signature, err := cli.sign(burl, string(method), header)
  281. if err != nil {
  282. return nil, errors.Wrapf(err, "sign raw request")
  283. }
  284. header.Set("Authorization", signature)
  285. resp, err := httputils.Request(cli.getDefaultClient(), cli.ctx, method, uri, header, body, cli.debug)
  286. if err != nil {
  287. return nil, err
  288. }
  289. return resp, nil
  290. }
  291. func (cli *SBaiduClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
  292. subAccount := cloudprovider.SSubAccount{}
  293. subAccount.Id = cli.GetAccountId()
  294. subAccount.Name = cli.cpcfg.Name
  295. subAccount.Account = cli.accessKeyId
  296. subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL
  297. return []cloudprovider.SSubAccount{subAccount}, nil
  298. }
  299. func (cli *SBaiduClient) getOwnerId() (string, error) {
  300. if len(cli.ownerId) > 0 {
  301. return cli.ownerId, nil
  302. }
  303. session, err := cli.GetSessionToken()
  304. if err != nil {
  305. return "", err
  306. }
  307. cli.ownerId = session.UserId
  308. return cli.ownerId, nil
  309. }
  310. func (cli *SBaiduClient) GetAccountId() string {
  311. ownerId, _ := cli.getOwnerId()
  312. return ownerId
  313. }
  314. type CashBalance struct {
  315. CashBalance float64
  316. }
  317. func (cli *SBaiduClient) QueryBalance() (*CashBalance, error) {
  318. resp, err := cli.post("billing", "", "/v1/finance/cash/balance", nil, nil)
  319. if err != nil {
  320. return nil, err
  321. }
  322. ret := &CashBalance{}
  323. err = resp.Unmarshal(ret)
  324. if err != nil {
  325. return nil, errors.Wrapf(err, "resp.Unmarshal")
  326. }
  327. return ret, nil
  328. }
  329. func (cli *SBaiduClient) GetCapabilities() []string {
  330. caps := []string{
  331. cloudprovider.CLOUD_CAPABILITY_COMPUTE,
  332. cloudprovider.CLOUD_CAPABILITY_NETWORK,
  333. cloudprovider.CLOUD_CAPABILITY_SECURITY_GROUP,
  334. cloudprovider.CLOUD_CAPABILITY_EIP,
  335. cloudprovider.CLOUD_CAPABILITY_SNAPSHOT_POLICY,
  336. cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE,
  337. }
  338. return caps
  339. }