yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/objectstore/xsky/adminapi.go (about)

     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  
    15  package xsky
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"io"
    21  	"math/rand"
    22  	"net/http"
    23  	"strings"
    24  	"time"
    25  
    26  	"yunion.io/x/jsonutils"
    27  	"yunion.io/x/log"
    28  	"yunion.io/x/pkg/errors"
    29  
    30  	"yunion.io/x/onecloud/pkg/httperrors"
    31  	"yunion.io/x/onecloud/pkg/util/httputils"
    32  
    33  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    34  )
    35  
    36  type SXskyAdminApi struct {
    37  	endpoint string
    38  	username string
    39  	password string
    40  	token    *sLoginResponse
    41  	client   *http.Client
    42  	debug    bool
    43  }
    44  
    45  func newXskyAdminApi(user, passwd, ep string, debug bool) *SXskyAdminApi {
    46  	return &SXskyAdminApi{
    47  		endpoint: ep,
    48  		username: user,
    49  		password: passwd,
    50  		// xsky use notimeout client so as to download/upload large files
    51  		client: httputils.GetAdaptiveTimeoutClient(),
    52  		debug:  debug,
    53  	}
    54  }
    55  
    56  func getJsonBodyReader(body jsonutils.JSONObject) io.Reader {
    57  	var reqBody io.Reader
    58  	if body != nil {
    59  		reqBody = strings.NewReader(body.String())
    60  	}
    61  	return reqBody
    62  }
    63  
    64  func (api *SXskyAdminApi) httpClient() *http.Client {
    65  	return api.client
    66  }
    67  
    68  func (api *SXskyAdminApi) jsonRequest(ctx context.Context, method httputils.THttpMethod, path string, hdr http.Header, body jsonutils.JSONObject) (http.Header, jsonutils.JSONObject, error) {
    69  	urlStr := strings.TrimRight(api.endpoint, "/") + "/" + strings.TrimLeft(path, "/")
    70  	req, err := http.NewRequest(string(method), urlStr, getJsonBodyReader(body))
    71  	if err != nil {
    72  		return nil, nil, errors.Wrap(err, "http.NewRequest")
    73  	}
    74  	if hdr != nil {
    75  		for k, vs := range hdr {
    76  			for _, v := range vs {
    77  				req.Header.Add(k, v)
    78  			}
    79  		}
    80  	}
    81  
    82  	if api.isValidToken() {
    83  		req.Header.Set("xms-auth-token", api.token.Token.Uuid)
    84  	}
    85  
    86  	if api.debug {
    87  		log.Debugf("request: %s %s %s %s", method, urlStr, req.Header, body)
    88  	}
    89  	resp, err := api.client.Do(req)
    90  
    91  	var bodyStr string
    92  	if body != nil {
    93  		bodyStr = body.String()
    94  	}
    95  	return httputils.ParseJSONResponse(bodyStr, resp, err, api.debug)
    96  }
    97  
    98  type sLoginResponse struct {
    99  	Token struct {
   100  		Create  time.Time
   101  		Expires time.Time
   102  		Roles   []string
   103  		User    struct {
   104  			Create             time.Time
   105  			Name               string
   106  			Email              string
   107  			Enabled            bool
   108  			Id                 int
   109  			PasswordLastUpdate time.Time
   110  		}
   111  		Uuid  string
   112  		Valid bool
   113  	}
   114  }
   115  
   116  func (api *SXskyAdminApi) isValidToken() bool {
   117  	if api.token != nil && len(api.token.Token.Uuid) > 0 && api.token.Token.Expires.After(time.Now()) {
   118  		return true
   119  	} else {
   120  		return false
   121  	}
   122  }
   123  
   124  func (api *SXskyAdminApi) auth(ctx context.Context) (*sLoginResponse, error) {
   125  	input := STokenCreateReq{}
   126  	input.Auth.Identity.Password.User.Name = api.username
   127  	input.Auth.Identity.Password.User.Password = api.password
   128  
   129  	_, resp, err := api.jsonRequest(ctx, httputils.POST, "/api/v1/auth/tokens", nil, jsonutils.Marshal(input))
   130  	if err != nil {
   131  		return nil, errors.Wrap(err, "api.jsonRequest")
   132  	}
   133  	output := sLoginResponse{}
   134  	err = resp.Unmarshal(&output)
   135  	if err != nil {
   136  		return nil, errors.Wrap(err, "resp.Unmarshal")
   137  	}
   138  	return &output, err
   139  }
   140  
   141  type STokenCreateReq struct {
   142  	Auth STokenCreateReqAuth `json:"auth"`
   143  }
   144  
   145  type STokenCreateReqAuth struct {
   146  	Identity STokenCreateReqAuthIdentity `json:"identity"`
   147  }
   148  
   149  type STokenCreateReqAuthIdentity struct {
   150  	// password for auth
   151  	Password SAuthPasswordReq `json:"password,omitempty"`
   152  	// token for auth
   153  	Token SAuthTokenReq `json:"token,omitempty"`
   154  }
   155  
   156  type SAuthPasswordReq struct {
   157  	User SAuthPasswordReqUser `json:"user"`
   158  }
   159  
   160  type SAuthPasswordReqUser struct {
   161  	// user email for auth
   162  	Email string `json:"email,omitempty"`
   163  	// user id for auth
   164  	Id int64 `json:"id,omitzero"`
   165  	// user name or email for auth
   166  	Name string `json:"name,omitempty"`
   167  	// password for auth
   168  	Password string `json:"password"`
   169  }
   170  
   171  type SAuthTokenReq struct {
   172  	// uuid of authorized token
   173  	Uuid string `json:"uuid"`
   174  }
   175  
   176  func (api *SXskyAdminApi) authRequest(ctx context.Context, method httputils.THttpMethod, path string, hdr http.Header, body jsonutils.JSONObject) (http.Header, jsonutils.JSONObject, error) {
   177  	if !api.isValidToken() {
   178  		loginResp, err := api.auth(ctx)
   179  		if err != nil {
   180  			return nil, nil, errors.Wrap(err, "api.auth")
   181  		}
   182  		api.token = loginResp
   183  	}
   184  	return api.jsonRequest(ctx, method, path, hdr, body)
   185  }
   186  
   187  type sUser struct {
   188  	BucketNum             int
   189  	BucketQuotaMaxObjects int
   190  	BucketQuotaMaxSize    int64
   191  	Create                time.Time
   192  	DisplayName           string
   193  	Email                 string
   194  	Id                    int
   195  	MaxBuckets            int
   196  	Name                  string
   197  	OpMask                string
   198  	Status                string
   199  	Suspended             bool
   200  	Update                time.Time
   201  	UserQuotaMaxObjects   int
   202  	UserQuotaMaxSize      int64
   203  	samples               []sSample
   204  	Keys                  []sKey
   205  }
   206  
   207  func (u sUser) getMinKey() string {
   208  	minKey := ""
   209  	for i := range u.Keys {
   210  		if len(minKey) == 0 || minKey > u.Keys[i].AccessKey {
   211  			minKey = u.Keys[i].AccessKey
   212  		}
   213  	}
   214  	return minKey
   215  }
   216  
   217  type sKey struct {
   218  	AccessKey string
   219  	Create    time.Time
   220  	Id        int
   221  	Reserved  bool
   222  	SecretKey string
   223  	Status    string
   224  	Type      string
   225  	Update    time.Time
   226  	User      struct {
   227  		Id   int
   228  		Name string
   229  	}
   230  }
   231  
   232  type sSample struct {
   233  	AllocatedObjects    int
   234  	AllocatedSize       int64
   235  	Create              time.Time
   236  	DelOpsPm            int
   237  	RxBandwidthKbyte    int
   238  	RxOpsPm             int
   239  	TxBandwidthKbyte    int
   240  	TxOpsPm             int
   241  	TotalDelOps         int
   242  	TotalDelSuccessOps  int
   243  	TotalRxBytes        int64
   244  	TotalRxOps          int
   245  	TotalRxSuccessOps   int
   246  	TotalTxBytes        int64
   247  	TotalTxOps          int
   248  	TotalTxSuccessKbyte int
   249  }
   250  
   251  type sPaging struct {
   252  	Count      int
   253  	Limit      int
   254  	Offset     int
   255  	TotalCount int
   256  }
   257  
   258  type sUsersResponse struct {
   259  	OsUsers []sUser
   260  	Paging  sPaging
   261  }
   262  
   263  func (api *SXskyAdminApi) getUsers(ctx context.Context) ([]sUser, error) {
   264  	usrs := make([]sUser, 0)
   265  	totalCount := 0
   266  	for totalCount <= 0 || len(usrs) < totalCount {
   267  		_, resp, err := api.authRequest(ctx, httputils.GET, fmt.Sprintf("/api/v1/os-users/?limit=1000&offset=%d", len(usrs)), nil, nil)
   268  		if err != nil {
   269  			return nil, errors.Wrap(err, "api.authRequest")
   270  		}
   271  		output := sUsersResponse{}
   272  		err = resp.Unmarshal(&output)
   273  		if err != nil {
   274  			return nil, errors.Wrap(err, "resp.Unmarshal")
   275  		}
   276  		usrs = append(usrs, output.OsUsers...)
   277  		totalCount = output.Paging.TotalCount
   278  	}
   279  	return usrs, nil
   280  }
   281  
   282  func (api *SXskyAdminApi) findUserByAccessKey(ctx context.Context, accessKey string) (*sUser, *sKey, error) {
   283  	usrs, err := api.getUsers(ctx)
   284  	if err != nil {
   285  		return nil, nil, errors.Wrap(err, "api.getUsers")
   286  	}
   287  	for i := range usrs {
   288  		for j := range usrs[i].Keys {
   289  			if usrs[i].Keys[j].AccessKey == accessKey {
   290  				return &usrs[i], &usrs[i].Keys[j], nil
   291  			}
   292  		}
   293  	}
   294  	return nil, nil, httperrors.ErrNotFound
   295  }
   296  
   297  func (api *SXskyAdminApi) findFirstUserWithAccessKey(ctx context.Context) (*sUser, *sKey, error) {
   298  	usrs, err := api.getUsers(ctx)
   299  	if err != nil {
   300  		return nil, nil, errors.Wrap(err, "api.getUsers")
   301  	}
   302  	for i := range usrs {
   303  		if len(usrs[i].Keys) > 0 {
   304  			return &usrs[i], &usrs[i].Keys[0], nil
   305  		}
   306  	}
   307  	return nil, nil, httperrors.ErrNotFound
   308  }
   309  
   310  type sBucket struct {
   311  	ActionStatus       string
   312  	AllUserPermission  string
   313  	AuthUserPermission string
   314  	BucketPolicy       string
   315  	Create             time.Time
   316  	Flag               struct {
   317  		Versioned         bool
   318  		VersionsSuspended bool
   319  		Worm              bool
   320  	}
   321  	Id int
   322  	// LifeCycle
   323  	MetadataSearchEnabled bool
   324  	Name                  string
   325  	NfsClientNum          int
   326  	OsReplicationPathNum  int
   327  	OsReplicationZoneNum  int
   328  	// osZone
   329  	OsZoneUuid string
   330  	Owner      struct {
   331  		Id   string
   332  		Name string
   333  	}
   334  	OwnerPermission string
   335  	Policy          sPolicy
   336  	PolicyEnabled   bool
   337  	QuotaMaxObjects int
   338  	QuotaMaxSize    int64
   339  	// RemteClusters
   340  	ReplicationUuid string
   341  	Samples         []sSample
   342  	Shards          int
   343  	Status          string
   344  	Update          time.Time
   345  	Virtual         bool
   346  	// NfsGatewayMaps
   347  }
   348  
   349  type sPolicy struct {
   350  	BucketNum int
   351  	Compress  bool
   352  	Create    time.Time
   353  	Crypto    bool
   354  	DataPool  struct {
   355  		Id   int
   356  		Name string
   357  	}
   358  	Default     bool
   359  	Description string
   360  	Id          int
   361  	IndexPool   struct {
   362  		Id   int
   363  		Name string
   364  	}
   365  	Name                string
   366  	ObjectSizeThreshold int64
   367  	PolicyName          string
   368  	Status              string
   369  	Update              time.Time
   370  }
   371  
   372  type sBucketsResponse struct {
   373  	OsBuckets []sBucket
   374  	Paging    sPaging
   375  }
   376  
   377  func (api *SXskyAdminApi) getBuckets(ctx context.Context) ([]sBucket, error) {
   378  	buckets := make([]sBucket, 0)
   379  	totalCount := 0
   380  	for totalCount <= 0 || len(buckets) < totalCount {
   381  		_, resp, err := api.authRequest(ctx, httputils.GET, fmt.Sprintf("/api/v1/os-buckets/?limit=1000&offset=%d", len(buckets)), nil, nil)
   382  		if err != nil {
   383  			return nil, errors.Wrap(err, "api.authRequest")
   384  		}
   385  		output := sBucketsResponse{}
   386  		err = resp.Unmarshal(&output)
   387  		if err != nil {
   388  			return nil, errors.Wrap(err, "resp.Unmarshal")
   389  		}
   390  		buckets = append(buckets, output.OsBuckets...)
   391  		totalCount = output.Paging.TotalCount
   392  	}
   393  	return buckets, nil
   394  }
   395  
   396  func (api *SXskyAdminApi) getBucketByName(ctx context.Context, name string) (*sBucket, error) {
   397  	buckets, err := api.getBuckets(ctx)
   398  	if err != nil {
   399  		return nil, errors.Wrap(err, "api.getBuckets")
   400  	}
   401  	for i := range buckets {
   402  		if buckets[i].Name == name {
   403  			return &buckets[i], nil
   404  		}
   405  	}
   406  	return nil, cloudprovider.ErrNotFound
   407  }
   408  
   409  type sBucketQuotaInput struct {
   410  	OsBucket struct {
   411  		QuotaMaxSize    int64
   412  		QuotaMaxObjects int
   413  	}
   414  }
   415  
   416  func (api *SXskyAdminApi) setBucketQuota(ctx context.Context, bucketId int, input sBucketQuotaInput) error {
   417  	_, _, err := api.authRequest(ctx, httputils.PATCH, fmt.Sprintf("/api/v1/os-buckets/%d", bucketId), nil, jsonutils.Marshal(&input))
   418  	if err != nil {
   419  		return errors.Wrap(err, "api.authRequest")
   420  	}
   421  	return nil
   422  }
   423  
   424  type sS3LbGroup struct {
   425  	ActionStatus    string
   426  	Create          time.Time
   427  	Description     string
   428  	HttpsPort       int
   429  	Id              int
   430  	Name            string
   431  	Port            int
   432  	Roles           []string
   433  	SearchHttpsPort int
   434  	SearchPort      int
   435  	Status          string
   436  	SyncPort        int
   437  	Update          time.Time
   438  	S3LoadBalancers []sS3LoadBalancer `json:"s3_load_balancers"`
   439  }
   440  
   441  type sS3LoadBalancer struct {
   442  	Create      time.Time
   443  	Description string
   444  	Group       struct {
   445  		Id     int
   446  		Name   string
   447  		Status string
   448  	}
   449  	Host struct {
   450  		AdminIp string
   451  		Id      int
   452  		Name    string
   453  	}
   454  	HttpsPort     int
   455  	Id            int
   456  	InterfaceName string
   457  	Ip            string
   458  	Name          string
   459  	Port          int
   460  	Roles         []string
   461  	Samples       []struct {
   462  		ActiveAconnects     int
   463  		CpuUtil             float64
   464  		Create              time.Time
   465  		DownBandwidthKbytes int64
   466  		FailureRequests     int
   467  		MemUsagePercent     float64
   468  		SuccessRequests     int
   469  		UpBandwidthKbyte    int64
   470  	}
   471  	SearchHttpsPort int
   472  	SearchPort      int
   473  	SslCertificate  interface{}
   474  	Status          string
   475  	SyncPort        int
   476  	Update          time.Time
   477  	Vip             string
   478  	VipMask         int
   479  	Vips            string
   480  }
   481  
   482  func (lb sS3LoadBalancer) GetGatewayEndpoint() string {
   483  	if lb.SslCertificate == nil {
   484  		return fmt.Sprintf("http://%s:%d", lb.Vip, lb.Port)
   485  	} else {
   486  		return fmt.Sprintf("https://%s:%d", lb.Vip, lb.HttpsPort)
   487  	}
   488  }
   489  
   490  type sS3LbGroupResponse struct {
   491  	S3LoadBalancerGroups []sS3LbGroup `json:"s3_load_balancer_groups"`
   492  	Paging               sPaging
   493  }
   494  
   495  func (api *SXskyAdminApi) getS3LbGroup(ctx context.Context) ([]sS3LbGroup, error) {
   496  	lbGroups := make([]sS3LbGroup, 0)
   497  	totalCount := 0
   498  	for totalCount <= 0 || len(lbGroups) < totalCount {
   499  		_, resp, err := api.authRequest(ctx, httputils.GET, fmt.Sprintf("/api/v1/s3-load-balancer-groups/?limit=1000&offset=%d", len(lbGroups)), nil, nil)
   500  		if err != nil {
   501  			return nil, errors.Wrap(err, "api.authRequest")
   502  		}
   503  		output := sS3LbGroupResponse{}
   504  		err = resp.Unmarshal(&output)
   505  		if err != nil {
   506  			return nil, errors.Wrap(err, "resp.Unmarshal")
   507  		}
   508  		lbGroups = append(lbGroups, output.S3LoadBalancerGroups...)
   509  		totalCount = output.Paging.TotalCount
   510  	}
   511  	return lbGroups, nil
   512  }
   513  
   514  func (api *SXskyAdminApi) getS3GatewayEndpoint(ctx context.Context) (string, error) {
   515  	s3LbGrps, err := api.getS3LbGroup(ctx)
   516  	if err != nil {
   517  		return "", errors.Wrap(err, "api.getS3LbGroup")
   518  	}
   519  	lbs := make([]sS3LoadBalancer, 0)
   520  	for i := range s3LbGrps {
   521  		lbs = append(lbs, s3LbGrps[i].S3LoadBalancers...)
   522  	}
   523  	if len(lbs) == 0 {
   524  		return "", errors.Wrap(httperrors.ErrNotFound, "empty S3 Lb group")
   525  	}
   526  	lb := lbs[rand.Intn(len(lbs))]
   527  	return lb.GetGatewayEndpoint(), nil
   528  }