From 2f516ee5061ed604d401bd7a0aedd9a529231def Mon Sep 17 00:00:00 2001 From: Qiu Jian Date: Tue, 17 Sep 2019 01:52:07 +0800 Subject: [PATCH] feature: support xsky object storage --- cmd/s3cli/main.go | 3 + pkg/apis/compute/cloudaccount_const.go | 1 + pkg/multicloud/loader/loader.go | 1 + pkg/multicloud/objectstore/xsky/adminapi.go | 505 ++++++++++++++++++ pkg/multicloud/objectstore/xsky/bucket.go | 82 +++ .../objectstore/xsky/provider/provider.go | 56 ++ pkg/multicloud/objectstore/xsky/xsky.go | 139 +++++ 7 files changed, 787 insertions(+) create mode 100644 pkg/multicloud/objectstore/xsky/adminapi.go create mode 100644 pkg/multicloud/objectstore/xsky/bucket.go create mode 100644 pkg/multicloud/objectstore/xsky/provider/provider.go create mode 100644 pkg/multicloud/objectstore/xsky/xsky.go diff --git a/cmd/s3cli/main.go b/cmd/s3cli/main.go index 9381beb31c..935c4716c5 100644 --- a/cmd/s3cli/main.go +++ b/cmd/s3cli/main.go @@ -26,6 +26,7 @@ import ( "yunion.io/x/onecloud/pkg/multicloud/objectstore/ceph" _ "yunion.io/x/onecloud/pkg/multicloud/objectstore/shell" "yunion.io/x/onecloud/pkg/util/shellutils" + "yunion.io/x/onecloud/pkg/multicloud/objectstore/xsky" ) type BaseOptions struct { @@ -94,6 +95,8 @@ func newClient(options *BaseOptions) (cloudprovider.ICloudRegion, error) { if options.Backend == api.CLOUD_PROVIDER_CEPH { return ceph.NewCephRados("", "", options.AccessUrl, options.AccessKey, options.Secret, options.Debug) + } else if options.Backend == api.CLOUD_PROVIDER_XSKY { + return xsky.NewXskyClient("", "", options.AccessUrl, options.AccessKey, options.Secret, options.Debug) } return objectstore.NewObjectStoreClient("", "", options.AccessUrl, options.AccessKey, options.Secret, options.Debug) } diff --git a/pkg/apis/compute/cloudaccount_const.go b/pkg/apis/compute/cloudaccount_const.go index eaae00ef95..bdb7ae0c5e 100644 --- a/pkg/apis/compute/cloudaccount_const.go +++ b/pkg/apis/compute/cloudaccount_const.go @@ -42,6 +42,7 @@ const ( CLOUD_PROVIDER_GENERICS3 = "S3" CLOUD_PROVIDER_CEPH = "Ceph" + CLOUD_PROVIDER_XSKY = "Xsky" CLOUD_PROVIDER_HEALTH_NORMAL = "normal" // 远端处于健康状态 CLOUD_PROVIDER_HEALTH_INSUFFICIENT = "insufficient" // 不足按需资源余额 diff --git a/pkg/multicloud/loader/loader.go b/pkg/multicloud/loader/loader.go index ef3642522b..70438dea45 100644 --- a/pkg/multicloud/loader/loader.go +++ b/pkg/multicloud/loader/loader.go @@ -34,6 +34,7 @@ import ( // object storages _ "yunion.io/x/onecloud/pkg/multicloud/objectstore/ceph/provider" + _ "yunion.io/x/onecloud/pkg/multicloud/objectstore/xsky/provider" _ "yunion.io/x/onecloud/pkg/multicloud/objectstore/provider" ) diff --git a/pkg/multicloud/objectstore/xsky/adminapi.go b/pkg/multicloud/objectstore/xsky/adminapi.go new file mode 100644 index 0000000000..8c85991052 --- /dev/null +++ b/pkg/multicloud/objectstore/xsky/adminapi.go @@ -0,0 +1,505 @@ +// 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 xsky + +import ( + "context" + "fmt" + "io" + "net/http" + "strings" + "time" + "math/rand" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/util/httputils" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SXskyAdminApi struct { + endpoint string + username string + password string + token *sLoginResponse + client *http.Client + debug bool +} + +func newXskyAdminApi(user, passwd, ep string, debug bool) *SXskyAdminApi { + return &SXskyAdminApi{ + endpoint: ep, + username: user, + password: passwd, + client: httputils.GetDefaultClient(), + debug: debug, + } +} + +func getJsonBodyReader(body jsonutils.JSONObject) io.Reader { + var reqBody io.Reader + if body != nil { + reqBody = strings.NewReader(body.String()) + } + return reqBody +} + +func (api *SXskyAdminApi) jsonRequest(ctx context.Context, method httputils.THttpMethod, path string, hdr http.Header, body jsonutils.JSONObject) (http.Header, jsonutils.JSONObject, error) { + urlStr := strings.TrimRight(api.endpoint, "/") + "/" + strings.TrimLeft(path, "/") + req, err := http.NewRequest(string(method), urlStr, getJsonBodyReader(body)) + if err != nil { + return nil, nil, errors.Wrap(err, "http.NewRequest") + } + if hdr != nil { + for k, vs := range hdr { + for _, v := range vs { + req.Header.Add(k, v) + } + } + } + + if api.isValidToken() { + req.Header.Set("xms-auth-token", api.token.Token.Uuid) + } + + if api.debug { + log.Debugf("request: %s %s %s %s", method, urlStr, req.Header, body) + } + resp, err := api.client.Do(req) + + return httputils.ParseJSONResponse(resp, err, api.debug) +} + +type sLoginResponse struct { + Token struct { + Create time.Time + Expires time.Time + Roles []string + User struct { + Create time.Time + Name string + Email string + Enabled bool + Id int + PasswordLastUpdate time.Time + } + Uuid string + Valid bool + } +} + +func (api *SXskyAdminApi) isValidToken() bool { + if api.token != nil && len(api.token.Token.Uuid) > 0 && api.token.Token.Expires.After(time.Now()) { + return true + } else { + return false + } +} + +func (api *SXskyAdminApi) auth(ctx context.Context) (*sLoginResponse, error) { + input := STokenCreateReq{} + input.Auth.Identity.Password.User.Name = api.username + input.Auth.Identity.Password.User.Password = api.password + + _, resp, err := api.jsonRequest(ctx, httputils.POST, "/api/v1/auth/tokens", nil, jsonutils.Marshal(input)) + if err != nil { + return nil, errors.Wrap(err, "api.jsonRequest") + } + output := sLoginResponse{} + err = resp.Unmarshal(&output) + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return &output, err +} + +type STokenCreateReq struct { + Auth STokenCreateReqAuth `json:"auth"` +} + +type STokenCreateReqAuth struct { + Identity STokenCreateReqAuthIdentity `json:"identity"` +} + +type STokenCreateReqAuthIdentity struct { + // password for auth + Password SAuthPasswordReq `json:"password,omitempty"` + // token for auth + Token SAuthTokenReq `json:"token,omitempty"` +} + +type SAuthPasswordReq struct { + User SAuthPasswordReqUser `json:"user"` +} + +type SAuthPasswordReqUser struct { + // user email for auth + Email string `json:"email,omitempty"` + // user id for auth + Id int64 `json:"id,omitzero"` + // user name or email for auth + Name string `json:"name,omitempty"` + // password for auth + Password string `json:"password"` +} + +type SAuthTokenReq struct { + // uuid of authorized token + Uuid string `json:"uuid"` +} + +func (api *SXskyAdminApi) authRequest(ctx context.Context, method httputils.THttpMethod, path string, hdr http.Header, body jsonutils.JSONObject) (http.Header, jsonutils.JSONObject, error) { + if !api.isValidToken() { + loginResp, err := api.auth(ctx) + if err != nil { + return nil, nil, errors.Wrap(err, "api.auth") + } + api.token = loginResp + } + return api.jsonRequest(ctx, method, path, hdr, body) +} + +type sUser struct { + BucketNum int + BucketQuotaMaxObjects int + BucketQuotaMaxSize int64 + Create time.Time + DisplayName string + Email string + Id int + MaxBuckets int + Name string + OpMask string + Status string + Suspended bool + Update time.Time + UserQuotaMaxObjects int + UserQuotaMaxSize int64 + samples []sSample + Keys []sKey +} + +func (u sUser) getMinKey() string { + minKey := "" + for i := range u.Keys { + if len(minKey) == 0 || minKey > u.Keys[i].AccessKey { + minKey = u.Keys[i].AccessKey + } + } + return minKey +} + +type sKey struct { + AccessKey string + Create time.Time + Id int + Reserved bool + SecretKey string + Status string + Type string + Update time.Time + User struct { + Id int + Name string + } +} + +type sSample struct { + AllocatedObjects int + AllocatedSize int64 + Create time.Time + DelOpsPm int + RxBandwidthKbyte int + RxOpsPm int + TxBandwidthKbyte int + TxOpsPm int + TotalDelOps int + TotalDelSuccessOps int + TotalRxBytes int64 + TotalRxOps int + TotalRxSuccessOps int + TotalTxBytes int64 + TotalTxOps int + TotalTxSuccessKbyte int +} + +type sPaging struct { + Count int + Limit int + Offset int + TotalCount int +} + +type sUsersResponse struct { + OsUsers []sUser + Paging sPaging +} + +func (api *SXskyAdminApi) getUsers(ctx context.Context) ([]sUser, error) { + usrs := make([]sUser, 0) + totalCount := 0 + for totalCount <= 0 || len(usrs) < totalCount { + _, resp, err := api.authRequest(ctx, httputils.GET, fmt.Sprintf("/api/v1/os-users/?limit=1000&offset=%d", len(usrs)), nil, nil) + if err != nil { + return nil, errors.Wrap(err, "api.authRequest") + } + output := sUsersResponse{} + err = resp.Unmarshal(&output) + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + usrs = append(usrs, output.OsUsers...) + totalCount = output.Paging.TotalCount + } + return usrs, nil +} + +func (api *SXskyAdminApi) findUserByAccessKey(ctx context.Context, accessKey string) (*sUser, *sKey, error) { + usrs, err := api.getUsers(ctx) + if err != nil { + return nil, nil, errors.Wrap(err, "api.getUsers") + } + for i := range usrs { + for j := range usrs[i].Keys { + if usrs[i].Keys[j].AccessKey == accessKey { + return &usrs[i], &usrs[i].Keys[j], nil + } + } + } + return nil, nil, httperrors.ErrNotFound +} + +type sBucket struct { + ActionStatus string + AllUserPermission string + AuthUserPermission string + BucketPolicy string + Create time.Time + Flag struct { + Versioned bool + VersionsSuspended bool + Worm bool + } + Id int + // LifeCycle + MetadataSearchEnabled bool + Name string + NfsClientNum int + OsReplicationPathNum int + OsReplicationZoneNum int + // osZone + OsZoneUuid string + Owner struct { + Id string + Name string + } + OwnerPermission string + Policy sPolicy + PolicyEnabled bool + QuotaMaxObjects int + QuotaMaxSize int64 + // RemteClusters + ReplicationUuid string + Samples []sSample + Shards int + Status string + Update time.Time + Virtual bool + // NfsGatewayMaps +} + +type sPolicy struct { + BucketNum int + Compress bool + Create time.Time + Crypto bool + DataPool struct { + Id int + Name string + } + Default bool + Description string + Id int + IdexPool struct { + Id int + Name string + } + Name string + ObjectSizeThreshold int64 + PolicyName string + Status string + Update time.Time +} + +type sBucketsResponse struct { + OsBuckets []sBucket + Paging sPaging +} + +func (api *SXskyAdminApi) getBuckets(ctx context.Context) ([]sBucket, error) { + buckets := make([]sBucket, 0) + totalCount := 0 + for totalCount <= 0 || len(buckets) < totalCount { + _, resp, err := api.authRequest(ctx, httputils.GET, fmt.Sprintf("/api/v1/os-buckets/?limit=1000&offset=%d", len(buckets)), nil, nil) + if err != nil { + return nil, errors.Wrap(err, "api.authRequest") + } + output := sBucketsResponse{} + err = resp.Unmarshal(&output) + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + buckets = append(buckets, output.OsBuckets...) + totalCount = output.Paging.TotalCount + } + return buckets, nil +} + +func (api *SXskyAdminApi) getBucketByName(ctx context.Context, name string) (*sBucket, error) { + buckets, err := api.getBuckets(ctx) + if err != nil { + return nil, errors.Wrap(err, "api.getBuckets") + } + for i := range buckets { + if buckets[i].Name == name { + return &buckets[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +type sBucketQuotaInput struct { + OsBucket struct { + QuotaMaxSize int64 + QuotaMaxObjects int + } +} + +func (api *SXskyAdminApi) setBucketQuota(ctx context.Context, bucketId int, input sBucketQuotaInput) error { + _, _, err := api.authRequest(ctx, httputils.PATCH, fmt.Sprintf("/api/v1/os-buckets/%d", bucketId), nil, jsonutils.Marshal(&input)) + if err != nil { + return errors.Wrap(err, "api.authRequest") + } + return nil +} + +type sS3LbGroup struct { + ActionStatus string + Create time.Time + Description string + HttpsPort int + Id int + Name string + Port int + Roles []string + SearchHttpsPort int + SearchPort int + Status string + SyncPort int + Update time.Time + S3LoadBalancers []sS3LoadBalancer `json:"s3_load_balancers"` +} + +type sS3LoadBalancer struct { + Create time.Time + Description string + Group struct { + Id int + Name string + Status string + } + Host struct { + AdminIp string + Id int + Name string + } + HttpsPort int + Id int + InterfaceName string + Ip string + Name string + Port int + Roles []string + Samples []struct { + ActiveAconnects int + CpuUtil float64 + Create time.Time + DownBandwidthKbytes int64 + FailureRequests int + MemUsagePercent float64 + SuccessRequests int + UpBandwidthKbyte int64 + } + SearchHttpsPort int + SearchPort int + SslCertificate interface{} + Status string + SyncPort int + Update time.Time + Vip string + VipMask int + Vips string +} + +func (lb sS3LoadBalancer) GetGatewayEndpoint() string { + if lb.SslCertificate == nil { + return fmt.Sprintf("http://%s:%d", lb.Vip, lb.Port) + } else { + return fmt.Sprintf("https://%s:%d", lb.Vip, lb.HttpsPort) + } +} + +type sS3LbGroupResponse struct { + S3LoadBalancerGroups []sS3LbGroup `json:"s3_load_balancer_groups"` + Paging sPaging +} + +func (api *SXskyAdminApi) getS3LbGroup(ctx context.Context) ([]sS3LbGroup, error) { + lbGroups := make([]sS3LbGroup, 0) + totalCount := 0 + for totalCount <= 0 || len(lbGroups) < totalCount { + _, resp, err := api.authRequest(ctx, httputils.GET, fmt.Sprintf("/api/v1/s3-load-balancer-groups/?limit=1000&offset=%d", len(lbGroups)), nil, nil) + if err != nil { + return nil, errors.Wrap(err, "api.authRequest") + } + output := sS3LbGroupResponse{} + err = resp.Unmarshal(&output) + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + lbGroups = append(lbGroups, output.S3LoadBalancerGroups...) + totalCount = output.Paging.TotalCount + } + return lbGroups, nil +} + +func (api *SXskyAdminApi) getS3GatewayEndpoint(ctx context.Context) (string, error) { + s3LbGrps, err := api.getS3LbGroup(ctx) + if err != nil { + return "", errors.Wrap(err, "api.getS3LbGroup") + } + lbs := make([]sS3LoadBalancer, 0) + for i := range s3LbGrps { + lbs = append(lbs, s3LbGrps[i].S3LoadBalancers...) + } + if len(lbs) == 0 { + return "", errors.Wrap(httperrors.ErrNotFound, "empty S3 Lb group") + } + lb := lbs[rand.Intn(len(lbs))] + return lb.GetGatewayEndpoint(), nil +} diff --git a/pkg/multicloud/objectstore/xsky/bucket.go b/pkg/multicloud/objectstore/xsky/bucket.go new file mode 100644 index 0000000000..c72d1edff1 --- /dev/null +++ b/pkg/multicloud/objectstore/xsky/bucket.go @@ -0,0 +1,82 @@ +// 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 xsky + +import ( + "context" + "strconv" + "time" + + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud/objectstore" +) + +type SXskyBucket struct { + *objectstore.SBucket + + client *SXskyClient +} + +func (b *SXskyBucket) GetStats() cloudprovider.SBucketStats { + _, hdr, _ := b.GetIBucketProvider().S3Client().BucketExists(b.Name) + if hdr != nil { + sizeBytesStr := hdr.Get("X-Rgw-Bytes-Used") + sizeBytes, _ := strconv.ParseInt(sizeBytesStr, 10, 64) + objCntStr := hdr.Get("X-Rgw-Object-Count") + objCnt, _ := strconv.ParseInt(objCntStr, 10, 64) + return cloudprovider.SBucketStats{ + SizeBytes: sizeBytes, + ObjectCount: int(objCnt), + } + } + return b.SBucket.GetStats() +} + +func (b *SXskyBucket) GetLimit() cloudprovider.SBucketStats { + limit := cloudprovider.SBucketStats{} + bucket, err := b.client.adminApi.getBucketByName(context.Background(), b.Name) + if err != nil { + log.Errorf("b.client.adminApi.getBucketByName error %s", err) + } else { + limit.SizeBytes = bucket.QuotaMaxSize + limit.ObjectCount = bucket.QuotaMaxObjects + } + return limit +} + +func (b *SXskyBucket) SetLimit(limit cloudprovider.SBucketStats) error { + bucket, err := b.client.adminApi.getBucketByName(context.Background(), b.Name) + if err != nil { + return errors.Wrap(err, "b.client.adminApi.getBucketByName") + } + input := sBucketQuotaInput{} + input.OsBucket.QuotaMaxObjects = limit.ObjectCount + input.OsBucket.QuotaMaxSize = limit.SizeBytes + err = b.client.adminApi.setBucketQuota(context.Background(), bucket.Id, input) + if err != nil { + return errors.Wrap(err, "b.client.adminApi.setBucketQuota") + } + cloudprovider.Wait(time.Second, 30*time.Second, func() (bool, error) { + target := b.GetLimit() + if target.SizeBytes == limit.SizeBytes && target.ObjectCount == limit.ObjectCount { + return true, nil + } + return false, nil + }) + return nil +} diff --git a/pkg/multicloud/objectstore/xsky/provider/provider.go b/pkg/multicloud/objectstore/xsky/provider/provider.go new file mode 100644 index 0000000000..457ce83ba2 --- /dev/null +++ b/pkg/multicloud/objectstore/xsky/provider/provider.go @@ -0,0 +1,56 @@ +// 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 provider + +import ( + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + s3provider "yunion.io/x/onecloud/pkg/multicloud/objectstore/provider" + "yunion.io/x/onecloud/pkg/multicloud/objectstore/xsky" +) + +type SXskyProviderFactory struct { + s3provider.SObjectStoreProviderFactory +} + +func (self *SXskyProviderFactory) GetId() string { + return api.CLOUD_PROVIDER_XSKY +} + +func (self *SXskyProviderFactory) GetName() string { + return api.CLOUD_PROVIDER_XSKY +} + +func (self *SXskyProviderFactory) GetProvider(providerId, providerName, url, account, secret string) (cloudprovider.ICloudProvider, error) { + client, err := xsky.NewXskyClient(providerId, providerName, url, account, secret, false) + if err != nil { + return nil, err + } + return s3provider.NewObjectStoreProvider(self, client), nil +} + +func (self *SXskyProviderFactory) GetClientRC(url, account, secret string) (map[string]string, error) { + return map[string]string{ + "S3_ACCESS_KEY": account, + "S3_SECRET": secret, + "S3_ACCESS_URL": url, + "S3_BACKEND": api.CLOUD_PROVIDER_XSKY, + }, nil +} + +func init() { + factory := SXskyProviderFactory{} + cloudprovider.RegisterFactory(&factory) +} diff --git a/pkg/multicloud/objectstore/xsky/xsky.go b/pkg/multicloud/objectstore/xsky/xsky.go new file mode 100644 index 0000000000..a58c635d7d --- /dev/null +++ b/pkg/multicloud/objectstore/xsky/xsky.go @@ -0,0 +1,139 @@ +// 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 xsky + +import ( + "context" + "fmt" + "strings" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/s3cli" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud/objectstore" +) + +type SXskyClient struct { + *objectstore.SObjectStoreClient + + adminApi *SXskyAdminApi + + adminUser *sUser +} + +func parseAccount(account string) (user string, accessKey string) { + accountInfo := strings.Split(account, "/") + user = accountInfo[0] + if len(accountInfo) > 1 { + accessKey = strings.Join(accountInfo[1:], "/") + } + return +} + +func NewXskyClient(providerId string, providerName string, endpoint string, account string, password string, isDebug bool) (*SXskyClient, error) { + usrname, accessKey := parseAccount(account) + adminApi := newXskyAdminApi(usrname, password, endpoint, isDebug) + gwEp, err := adminApi.getS3GatewayEndpoint(context.Background()) + if err != nil { + return nil, errors.Wrap(err, "adminApi.getS3GatewayIP") + } + + var s3store *objectstore.SObjectStoreClient + var adminUser *sUser + if len(accessKey) > 0 { + usr, key, err := adminApi.findUserByAccessKey(context.Background(), accessKey) + if err != nil { + return nil, errors.Wrap(err, "adminApi.findUserByAccessKey") + } + + adminUser = usr + + s3store, err = objectstore.NewObjectStoreClientAndFetch(providerId, providerName, gwEp, accessKey, key.SecretKey, isDebug, false) + if err != nil { + return nil, errors.Wrap(err, "NewObjectStoreClient") + } + } + + client := SXskyClient{ + SObjectStoreClient: s3store, + adminApi: adminApi, + adminUser: adminUser, + } + + client.SetVirtualObject(&client) + + if s3store != nil { + err = client.FetchBuckets() + if err != nil { + return nil, errors.Wrap(err, "fetchBuckets") + } + } + + return &client, nil +} + +func (cli *SXskyClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) { + usrs, err := cli.adminApi.getUsers(context.Background()) + if err != nil { + return nil, errors.Wrap(err, "api.getUsers") + } + subAccounts := make([]cloudprovider.SSubAccount, 0) + for i := range usrs { + ak := usrs[i].getMinKey() + if len(ak) > 0 { + subAccount := cloudprovider.SSubAccount{ + Account: fmt.Sprintf("%s/%s", cli.adminApi.username, ak), + Name: usrs[i].Name, + HealthStatus: api.CLOUD_PROVIDER_HEALTH_NORMAL, + } + subAccounts = append(subAccounts, subAccount) + } + } + return subAccounts, nil +} + +func (cli *SXskyClient) GetAccountId() string { + return cli.adminApi.username +} + +func (cli *SXskyClient) GetVersion() string { + return "" +} + +func (cli *SXskyClient) About() jsonutils.JSONObject { + about := jsonutils.NewDict() + if cli.adminUser != nil { + about.Add(jsonutils.Marshal(cli.adminUser), "admin_user") + } + return about +} + +func (cli *SXskyClient) GetProvider() string { + return api.CLOUD_PROVIDER_XSKY +} + +func (cli *SXskyClient) NewBucket(bucket s3cli.BucketInfo) cloudprovider.ICloudBucket { + if cli.SObjectStoreClient == nil { + return nil + } + generalBucket := cli.SObjectStoreClient.NewBucket(bucket) + return &SXskyBucket{ + SBucket: generalBucket.(*objectstore.SBucket), + client: cli, + } +}