ucloud.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  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 ucloud
  15. import (
  16. "bytes"
  17. "fmt"
  18. "io/ioutil"
  19. "net/http"
  20. "strings"
  21. "yunion.io/x/jsonutils"
  22. "yunion.io/x/log"
  23. "yunion.io/x/pkg/errors"
  24. api "yunion.io/x/cloudmux/pkg/apis/compute"
  25. "yunion.io/x/cloudmux/pkg/cloudprovider"
  26. )
  27. /*
  28. UCLOUD 项目:https://docs.ucloud.cn/management_monitor/uproject/projects
  29. 项目可认为是云账户下承载资源的容器,当您注册一个UCloud云账户后,系统会默认创建一个项目,您属于的资源都落在此项目下。如您有新的业务要使用云服务,可创建一个新项目,并将新业务部署在新项目下,实现业务之间的网络与逻辑隔离。
  30. 1、项目之间默认网络与逻辑隔离,即项目A的主机无法绑定项目B的EIP,默认也无法与项目B的主机内网通信。但联通项目后,uhost、udb、umem可实现内网通信。
  31. 2、资源不能在项目间迁移,即项目A内的主机无法迁移至项目B,因其不在一个基础网络内,且逻辑上也是隔离的。但诸如自主镜像等静态资源,您可以提交工单申请迁移至其他项目。
  32. 3、只有云账户本身,才能删除项目,且必须是项目被没有资源、没有任何子成员、未与其他项目联通的情况下才可删除。
  33. UCloud DiskType貌似也是一个奇葩的存在
  34. // https://docs.ucloud.cn/api/uhost-api/disk_type
  35. 1.在主机创建查询接口中 DISK type 对应 CLOUD_SSD|CLOUD_NORMAL|...
  36. 2.在数据盘创建中对应 DataDisk|SSDDataDisk
  37. 3.在数据盘查询接口请求中对应 DataDisk|SystemDisk 。在结果中对应DataDisk|SSDDataDisk|SSDSystemDisk|SystemDisk
  38. 目前存在的问题:
  39. 1.很多国外区域都需要单独申请开通权限才能使用。onecloud有可能调度到未开通权限区域导致失败。
  40. */
  41. const (
  42. CLOUD_PROVIDER_UCLOUD = api.CLOUD_PROVIDER_UCLOUD
  43. CLOUD_PROVIDER_UCLOUD_CN = "UCloud"
  44. UCLOUD_DEFAULT_REGION = "cn-bj2"
  45. UCLOUD_API_VERSION = "2019-02-28"
  46. )
  47. type UcloudClientConfig struct {
  48. cpcfg cloudprovider.ProviderConfig
  49. accessKeyId string
  50. accessKeySecret string
  51. projectId string
  52. debug bool
  53. }
  54. func NewUcloudClientConfig(accessKeyId, accessKeySecret string) *UcloudClientConfig {
  55. cfg := &UcloudClientConfig{
  56. accessKeyId: accessKeyId,
  57. accessKeySecret: accessKeySecret,
  58. }
  59. return cfg
  60. }
  61. func (cfg *UcloudClientConfig) CloudproviderConfig(cpcfg cloudprovider.ProviderConfig) *UcloudClientConfig {
  62. cfg.cpcfg = cpcfg
  63. return cfg
  64. }
  65. func (cfg *UcloudClientConfig) ProjectId(projectId string) *UcloudClientConfig {
  66. cfg.projectId = projectId
  67. return cfg
  68. }
  69. func (cfg *UcloudClientConfig) Debug(debug bool) *UcloudClientConfig {
  70. cfg.debug = debug
  71. return cfg
  72. }
  73. type SUcloudClient struct {
  74. *UcloudClientConfig
  75. iregions []cloudprovider.ICloudRegion
  76. iBuckets []cloudprovider.ICloudBucket
  77. httpClient *http.Client
  78. }
  79. // 进行资源操作时参数account 对应数据库cloudprovider表中的account字段,由accessKey和projectID两部分组成,通过"/"分割。
  80. // 初次导入Subaccount时,参数account对应cloudaccounts表中的account字段,即accesskey。此时projectID为空,只能进行同步子账号(项目)、查询region列表等projectId无关的操作。
  81. func NewUcloudClient(cfg *UcloudClientConfig) (*SUcloudClient, error) {
  82. httpClient := cfg.cpcfg.AdaptiveTimeoutHttpClient()
  83. ts, _ := httpClient.Transport.(*http.Transport)
  84. httpClient.Transport = cloudprovider.GetCheckTransport(ts, func(req *http.Request) (func(resp *http.Response) error, error) {
  85. if cfg.cpcfg.ReadOnly {
  86. if req.ContentLength > 0 {
  87. body, err := ioutil.ReadAll(req.Body)
  88. if err != nil {
  89. return nil, errors.Wrapf(err, "ioutil.ReadAll")
  90. }
  91. req.Body = ioutil.NopCloser(bytes.NewBuffer(body))
  92. obj, err := jsonutils.Parse(body)
  93. if err != nil {
  94. return nil, errors.Wrapf(err, "Parse request body")
  95. }
  96. action, err := obj.GetString("Action")
  97. if err != nil {
  98. return nil, errors.Wrapf(err, "Get request action")
  99. }
  100. for _, prefix := range []string{"Get", "Describe", "List"} {
  101. if strings.HasPrefix(action, prefix) {
  102. return nil, nil
  103. }
  104. }
  105. return nil, errors.Wrapf(cloudprovider.ErrAccountReadOnly, "%s %s", req.Method, req.URL.Path)
  106. }
  107. }
  108. return nil, nil
  109. })
  110. client := SUcloudClient{
  111. UcloudClientConfig: cfg,
  112. httpClient: httpClient,
  113. }
  114. err := client.fetchRegions()
  115. if err != nil {
  116. return nil, err
  117. }
  118. err = client.fetchBuckets()
  119. if err != nil {
  120. return nil, err
  121. }
  122. return &client, nil
  123. }
  124. func (self *SUcloudClient) UpdateAccount(accessKey, secret string) error {
  125. if self.accessKeyId != accessKey || self.accessKeySecret != secret {
  126. self.accessKeyId = accessKey
  127. self.accessKeySecret = secret
  128. return self.fetchRegions()
  129. } else {
  130. return nil
  131. }
  132. }
  133. func (self *SUcloudClient) commonParams(params SParams, action string) (string, SParams) {
  134. resultKey, exists := UCLOUD_API_RESULT_KEYS[action]
  135. if !exists || len(resultKey) == 0 {
  136. // default key for describe actions
  137. if strings.HasPrefix(action, "Describe") {
  138. resultKey = "DataSet"
  139. }
  140. }
  141. if len(self.projectId) > 0 {
  142. params.Set("ProjectId", self.projectId)
  143. }
  144. params.Set("PublicKey", self.accessKeyId)
  145. return resultKey, params
  146. }
  147. func (self *SUcloudClient) DoListAll(action string, params SParams, result interface{}) error {
  148. resultKey, params := self.commonParams(params, action)
  149. return DoListAll(self, action, params, resultKey, result)
  150. }
  151. func (self *SUcloudClient) DoListPart(action string, limit int, offset int, params SParams, result interface{}) (int, int, error) {
  152. resultKey, params := self.commonParams(params, action)
  153. params.SetPagination(limit, offset)
  154. return doListPart(self, action, params, resultKey, result)
  155. }
  156. func (self *SUcloudClient) DoAction(action string, params SParams, result interface{}) error {
  157. resultKey, params := self.commonParams(params, action)
  158. err := DoAction(self, action, params, resultKey, result)
  159. if err != nil {
  160. return err
  161. }
  162. return nil
  163. }
  164. func (self *SUcloudClient) fetchRegions() error {
  165. type Region struct {
  166. RegionID int64 `json:"RegionId"`
  167. RegionName string `json:"RegionName"`
  168. IsDefault bool `json:"IsDefault"`
  169. BitMaps string `json:"BitMaps"`
  170. Region string `json:"Region"`
  171. Zone string `json:"Zone"`
  172. }
  173. params := NewUcloudParams()
  174. regions := make([]Region, 0)
  175. err := self.DoListAll("GetRegion", params, &regions)
  176. if err != nil {
  177. return err
  178. }
  179. regionSet := make(map[string]string, 0)
  180. for _, region := range regions {
  181. regionSet[region.Region] = region.Region
  182. }
  183. sregions := make([]SRegion, len(regionSet))
  184. self.iregions = make([]cloudprovider.ICloudRegion, len(regionSet))
  185. i := 0
  186. for regionId := range regionSet {
  187. sregions[i].client = self
  188. sregions[i].RegionID = regionId
  189. self.iregions[i] = &sregions[i]
  190. i += 1
  191. }
  192. return nil
  193. }
  194. func (client *SUcloudClient) invalidateIBuckets() {
  195. client.iBuckets = nil
  196. }
  197. func (client *SUcloudClient) getIBuckets() ([]cloudprovider.ICloudBucket, error) {
  198. if client.iBuckets == nil {
  199. err := client.fetchBuckets()
  200. if err != nil {
  201. return nil, errors.Wrap(err, "fetchBuckets")
  202. }
  203. }
  204. return client.iBuckets, nil
  205. }
  206. func (client *SUcloudClient) fetchBuckets() error {
  207. buckets := make([]SBucket, 0)
  208. offset := 0
  209. limit := 50
  210. for {
  211. parts, err := client.listBuckets("", offset, limit)
  212. if err != nil {
  213. return errors.Wrap(err, "client.listBuckets")
  214. }
  215. if len(parts) > 0 {
  216. buckets = append(buckets, parts...)
  217. }
  218. if len(parts) < limit {
  219. break
  220. } else {
  221. offset += limit
  222. }
  223. }
  224. ret := make([]cloudprovider.ICloudBucket, 0)
  225. for i := range buckets {
  226. region, err := client.getIRegionByRegionId(buckets[i].Region)
  227. if err != nil {
  228. log.Errorf("fail to find iregion %s", buckets[i].Region)
  229. continue
  230. }
  231. buckets[i].region = region.(*SRegion)
  232. ret = append(ret, &buckets[i])
  233. }
  234. client.iBuckets = ret
  235. return nil
  236. }
  237. func (self *SUcloudClient) GetRegions() []SRegion {
  238. regions := make([]SRegion, len(self.iregions))
  239. for i := 0; i < len(regions); i += 1 {
  240. region := self.iregions[i].(*SRegion)
  241. regions[i] = *region
  242. }
  243. return regions
  244. }
  245. func (self *SUcloudClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
  246. projects, err := self.FetchProjects()
  247. if err != nil {
  248. return nil, err
  249. }
  250. subAccounts := make([]cloudprovider.SSubAccount, 0)
  251. for _, project := range projects {
  252. subAccount := cloudprovider.SSubAccount{}
  253. subAccount.Id = project.ProjectID
  254. subAccount.Name = fmt.Sprintf("%s-%s", self.cpcfg.Name, project.ProjectName)
  255. // ucloud账号ID中可能包含/。因此使用::作为分割符号
  256. subAccount.Account = fmt.Sprintf("%s::%s", self.accessKeyId, project.ProjectID)
  257. subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL
  258. subAccounts = append(subAccounts, subAccount)
  259. }
  260. return subAccounts, nil
  261. }
  262. func (self *SUcloudClient) GetAccountId() string {
  263. return "" // no account ID found for ucloud
  264. }
  265. func (self *SUcloudClient) GetIRegions() ([]cloudprovider.ICloudRegion, error) {
  266. return self.iregions, nil
  267. }
  268. func removeDigit(idstr string) string {
  269. for len(idstr) > 0 && idstr[len(idstr)-1] >= '0' && idstr[len(idstr)-1] <= '9' {
  270. idstr = idstr[:len(idstr)-1]
  271. }
  272. return idstr
  273. }
  274. func (self *SUcloudClient) getIRegionByRegionId(id string) (cloudprovider.ICloudRegion, error) {
  275. for i := 0; i < len(self.iregions); i += 1 {
  276. if self.iregions[i].GetId() == id {
  277. return self.iregions[i], nil
  278. }
  279. }
  280. // retry
  281. for i := 0; i < len(self.iregions); i += 1 {
  282. rid := removeDigit(self.iregions[i].GetId())
  283. rid2 := removeDigit(id)
  284. if rid == rid2 {
  285. return self.iregions[i], nil
  286. }
  287. }
  288. return nil, cloudprovider.ErrNotFound
  289. }
  290. func (self *SUcloudClient) GetIRegionById(id string) (cloudprovider.ICloudRegion, error) {
  291. for i := 0; i < len(self.iregions); i += 1 {
  292. if self.iregions[i].GetGlobalId() == id {
  293. return self.iregions[i], nil
  294. }
  295. }
  296. return nil, cloudprovider.ErrNotFound
  297. }
  298. func (self *SUcloudClient) GetRegion(regionId string) *SRegion {
  299. if len(regionId) == 0 {
  300. regionId = UCLOUD_DEFAULT_REGION
  301. }
  302. for i := 0; i < len(self.iregions); i += 1 {
  303. if self.iregions[i].GetId() == regionId {
  304. return self.iregions[i].(*SRegion)
  305. }
  306. }
  307. return nil
  308. }
  309. func (self *SUcloudClient) GetIHostById(id string) (cloudprovider.ICloudHost, error) {
  310. for i := 0; i < len(self.iregions); i += 1 {
  311. ihost, err := self.iregions[i].GetIHostById(id)
  312. if err == nil {
  313. return ihost, nil
  314. } else if errors.Cause(err) != cloudprovider.ErrNotFound {
  315. return nil, err
  316. }
  317. }
  318. return nil, cloudprovider.ErrNotFound
  319. }
  320. func (self *SUcloudClient) GetIVpcById(id string) (cloudprovider.ICloudVpc, error) {
  321. for i := 0; i < len(self.iregions); i += 1 {
  322. ihost, err := self.iregions[i].GetIVpcById(id)
  323. if err == nil {
  324. return ihost, nil
  325. } else if errors.Cause(err) != cloudprovider.ErrNotFound {
  326. return nil, err
  327. }
  328. }
  329. return nil, cloudprovider.ErrNotFound
  330. }
  331. func (self *SUcloudClient) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) {
  332. for i := 0; i < len(self.iregions); i += 1 {
  333. ihost, err := self.iregions[i].GetIStorageById(id)
  334. if err == nil {
  335. return ihost, nil
  336. } else if errors.Cause(err) != cloudprovider.ErrNotFound {
  337. return nil, err
  338. }
  339. }
  340. return nil, cloudprovider.ErrNotFound
  341. }
  342. func (self *SUcloudClient) GetCapabilities() []string {
  343. caps := []string{
  344. // cloudprovider.CLOUD_CAPABILITY_PROJECT,
  345. cloudprovider.CLOUD_CAPABILITY_COMPUTE,
  346. cloudprovider.CLOUD_CAPABILITY_NETWORK,
  347. cloudprovider.CLOUD_CAPABILITY_SECURITY_GROUP,
  348. cloudprovider.CLOUD_CAPABILITY_EIP,
  349. // cloudprovider.CLOUD_CAPABILITY_LOADBALANCER,
  350. // cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE,
  351. // cloudprovider.CLOUD_CAPABILITY_RDS,
  352. // cloudprovider.CLOUD_CAPABILITY_CACHE,
  353. // cloudprovider.CLOUD_CAPABILITY_EVENT,
  354. }
  355. return caps
  356. }