yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/objectstore/ceph/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 ceph
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"io"
    21  	"net/http"
    22  	"strings"
    23  
    24  	"yunion.io/x/jsonutils"
    25  	"yunion.io/x/log"
    26  	"yunion.io/x/pkg/errors"
    27  	"yunion.io/x/pkg/tristate"
    28  
    29  	"yunion.io/x/onecloud/pkg/httperrors"
    30  	"yunion.io/x/onecloud/pkg/util/httputils"
    31  	"yunion.io/x/onecloud/pkg/util/s3auth"
    32  )
    33  
    34  type SCephAdminApi struct {
    35  	adminPath string
    36  	accessKey string
    37  	secret    string
    38  	endpoint  string
    39  	client    *http.Client
    40  	debug     bool
    41  }
    42  
    43  func newCephAdminApi(ak, sk, ep string, debug bool, adminPath string) *SCephAdminApi {
    44  	if adminPath == "" {
    45  		adminPath = "admin"
    46  	}
    47  	return &SCephAdminApi{
    48  		adminPath: adminPath,
    49  		accessKey: ak,
    50  		secret:    sk,
    51  		endpoint:  ep,
    52  		// ceph use no timeout client so as to download/upload large files
    53  		client: httputils.GetAdaptiveTimeoutClient(),
    54  		debug:  debug,
    55  	}
    56  }
    57  
    58  func getJsonBodyReader(body jsonutils.JSONObject) io.Reader {
    59  	var reqBody io.Reader
    60  	if body != nil {
    61  		reqBody = strings.NewReader(body.String())
    62  	}
    63  	return reqBody
    64  }
    65  
    66  func (api *SCephAdminApi) httpClient() *http.Client {
    67  	return api.client
    68  }
    69  
    70  func (api *SCephAdminApi) jsonRequest(ctx context.Context, method httputils.THttpMethod, path string, hdr http.Header, body jsonutils.JSONObject) (http.Header, jsonutils.JSONObject, error) {
    71  	urlStr := strings.TrimRight(api.endpoint, "/") + "/" + strings.TrimLeft(path, "/")
    72  	req, err := http.NewRequest(string(method), urlStr, getJsonBodyReader(body))
    73  	if err != nil {
    74  		return nil, nil, errors.Wrap(err, "http.NewRequest")
    75  	}
    76  	if hdr != nil {
    77  		for k, vs := range hdr {
    78  			for _, v := range vs {
    79  				req.Header.Add(k, v)
    80  			}
    81  		}
    82  	}
    83  
    84  	newReq := s3auth.SignV4(*req, api.accessKey, api.secret, "cn-beijing", getJsonBodyReader(body))
    85  
    86  	resp, err := api.client.Do(newReq)
    87  
    88  	var bodyStr string
    89  	if body != nil {
    90  		bodyStr = body.String()
    91  	}
    92  	return httputils.ParseJSONResponse(bodyStr, resp, err, api.debug)
    93  }
    94  
    95  func (api *SCephAdminApi) GetUsage(ctx context.Context, uid string) (jsonutils.JSONObject, error) {
    96  	path := fmt.Sprintf("/%s/usage?format=json&uid=%s&show-entries=False&show-summary=True", api.adminPath, uid)
    97  	_, resp, err := api.jsonRequest(ctx, httputils.GET, path, nil, nil)
    98  	return resp, err
    99  }
   100  
   101  // {"caps":[{"perm":"*","type":"buckets"},{"perm":"*","type":"usage"},{"perm":"*","type":"users"}],
   102  // "display_name":"First User","email":"",
   103  // "keys":[{"access_key":"70TTA6IU5D9LQ0IVA3Z6","secret_key":"AK3Kd9L1elPin9wEXNRuY9L2yxZ5U3mGsGYoyIxL","user":"testuser"}],
   104  // "max_buckets":1000,"subusers":[],"suspended":0,"swift_keys":[],"tenant":"","user_id":"testuser"}
   105  
   106  type SUserInfo struct {
   107  	Caps        []SUserCapability
   108  	DisplayName string
   109  	Email       string
   110  	UserId      string
   111  	Tenant      string
   112  	Suspended   int
   113  	SubUsers    []string
   114  	MaxBuckets  int
   115  	Keys        []SUserAccessKey
   116  }
   117  
   118  type SUserCapability struct {
   119  	Perm string
   120  	Type string
   121  }
   122  
   123  type SUserAccessKey struct {
   124  	AccessKey string
   125  	SecretKey string
   126  	User      string
   127  }
   128  
   129  func (api *SCephAdminApi) GetUserInfo(ctx context.Context, uid string) (*SUserInfo, error) {
   130  	path := fmt.Sprintf("/%s/user?format=json&uid=%s", api.adminPath, uid)
   131  	_, resp, err := api.jsonRequest(ctx, httputils.GET, path, nil, nil)
   132  	if err != nil {
   133  		if httputils.ErrorCode(err) == 403 {
   134  			msg := `add users read cap by: radosgw-admin caps add --uid %s --caps="users=read"`
   135  			return nil, errors.Wrapf(httperrors.ErrForbidden, msg, uid)
   136  		}
   137  		return nil, errors.Wrap(err, "api.jsonRequest")
   138  	}
   139  	usrInfo := SUserInfo{}
   140  	err = resp.Unmarshal(&usrInfo)
   141  	if err != nil {
   142  		return nil, errors.Wrap(err, "resp.Unmarshal")
   143  	}
   144  	return &usrInfo, nil
   145  }
   146  
   147  type SQuotaQuery struct {
   148  	QuotaType string `json:"quota-type"`
   149  	Uid       string `json:"uid"`
   150  	Bucket    string `json:"bucket"`
   151  }
   152  
   153  type SQuota struct {
   154  	Enabled    tristate.TriState `json:"enabled"`
   155  	CheckOnRaw bool              `json:"check_on_raw,omitfalse"`
   156  	MaxSize    int64             `json:"max_size,omitzero"`
   157  	MaxSizeKB  int64             `json:"max_size_kb,omitzero"`
   158  	MaxObjects int               `json:"max_objects,omitzero"`
   159  }
   160  
   161  func (q SQuotaQuery) Query() string {
   162  	return "quota&format=json&" + jsonutils.Marshal(q).QueryString()
   163  }
   164  
   165  func (q *SQuotaQuery) SetBucket(uid string, level string, bucket string) {
   166  	q.Uid = uid
   167  	q.Bucket = bucket
   168  	q.QuotaType = level
   169  }
   170  
   171  func (api *SCephAdminApi) GetUserQuota(ctx context.Context, uid string) (*SQuota, *SQuota, error) {
   172  	userQuota, err := api.getQuota(ctx, uid, "user", "")
   173  	if err != nil {
   174  		return nil, nil, errors.Wrap(err, "api.getQuota user level")
   175  	}
   176  	bucketQuota, err := api.getQuota(ctx, uid, "bucket", "")
   177  	if err != nil {
   178  		return nil, nil, errors.Wrap(err, "api.getQuota bucket level")
   179  	}
   180  	return userQuota, bucketQuota, nil
   181  }
   182  
   183  // ceph目前不支持设置bucket的quota,因此返回全局的bucket quota
   184  func (api *SCephAdminApi) GetBucketQuota(ctx context.Context, uid string, bucket string) (*SQuota, error) {
   185  	/*quota, err := api.getQuota(ctx, uid, "", bucket)
   186  	if err == nil {
   187  		return quota, nil
   188  	}*/
   189  	return api.getQuota(ctx, uid, "bucket", "")
   190  }
   191  
   192  func (api *SCephAdminApi) getQuota(ctx context.Context, uid string, level string, bucket string) (*SQuota, error) {
   193  	query := SQuotaQuery{}
   194  	query.SetBucket(uid, level, bucket)
   195  	var path string
   196  	if len(bucket) > 0 {
   197  		path = fmt.Sprintf("/%s/bucket?%s", api.adminPath, query.Query())
   198  	} else {
   199  		path = fmt.Sprintf("/%s/user?%s", api.adminPath, query.Query())
   200  	}
   201  	_, resp, err := api.jsonRequest(ctx, httputils.GET, path, nil, nil)
   202  	if err != nil {
   203  		if httputils.ErrorCode(err) == 403 {
   204  			var msg string
   205  			if len(bucket) > 0 {
   206  				msg = `add buckets read cap by: radosgw-admin caps add --uid %s --caps="buckets=read"`
   207  			} else {
   208  				msg = `add users read cap by: radosgw-admin caps add --uid %s --caps="users=read"`
   209  			}
   210  			return nil, errors.Wrapf(httperrors.ErrForbidden, msg, uid)
   211  		}
   212  		return nil, errors.Wrap(err, "api.jsonRequest")
   213  	}
   214  	if resp == nil {
   215  		return nil, httperrors.ErrNotSupported
   216  	}
   217  	quota := SQuota{}
   218  	err = resp.Unmarshal(&quota)
   219  	if err != nil {
   220  		return nil, errors.Wrap(err, "resp.Unmarshal")
   221  	}
   222  	return &quota, nil
   223  }
   224  
   225  func (api *SCephAdminApi) SetUserQuota(ctx context.Context, uid string, sizeBytes int64, objects int) error {
   226  	_, err := api.setQuota(ctx, uid, "user", "", sizeBytes, objects)
   227  	return errors.Wrap(err, "api.setQuota")
   228  }
   229  
   230  func (api *SCephAdminApi) SetAllBucketQuota(ctx context.Context, uid string, sizeBytes int64, objects int) error {
   231  	_, err := api.setQuota(ctx, uid, "bucket", "", sizeBytes, objects)
   232  	return errors.Wrap(err, "api.setQuota")
   233  }
   234  
   235  // ceph目前不支持设置quota,因此返回全局的bucket quota
   236  func (api *SCephAdminApi) SetBucketQuota(ctx context.Context, uid string, bucket string, sizeBytes int64, objects int) error {
   237  	var err error
   238  	_, err = api.setQuota(ctx, uid, "", bucket, sizeBytes, objects)
   239  	if err == nil {
   240  		return nil
   241  	}
   242  	_, err = api.setQuota(ctx, uid, "bucket", "", sizeBytes, objects)
   243  	if err == nil {
   244  		return nil
   245  	}
   246  	_, err = api.setQuota(ctx, uid, "user", "", sizeBytes, objects)
   247  	if err == nil {
   248  		return nil
   249  	}
   250  	return errors.Wrap(err, "api.setQuota")
   251  }
   252  
   253  func (api *SCephAdminApi) setQuota(ctx context.Context, uid string, level string, bucket string, sizeBytes int64, objects int) (*SQuota, error) {
   254  	query := SQuotaQuery{}
   255  	query.SetBucket(uid, level, bucket)
   256  	quota := SQuota{
   257  		MaxSize:    sizeBytes,
   258  		MaxObjects: objects,
   259  	}
   260  	if sizeBytes <= 0 && objects <= 0 {
   261  		quota.Enabled = tristate.False
   262  	} else {
   263  		quota.Enabled = tristate.True
   264  	}
   265  	var path string
   266  	if len(bucket) > 0 {
   267  		path = fmt.Sprintf("/%s/bucket?%s", api.adminPath, query.Query())
   268  	} else {
   269  		path = fmt.Sprintf("/%s/user?%s", api.adminPath, query.Query())
   270  	}
   271  	body := jsonutils.Marshal(quota)
   272  	log.Debugf("request %s %s %s", httputils.PUT, path, body)
   273  	_, resp, err := api.jsonRequest(ctx, httputils.PUT, path, nil, body)
   274  	if err != nil {
   275  		if httputils.ErrorCode(err) == 403 {
   276  			var msg string
   277  			if len(bucket) > 0 {
   278  				msg = `add buckets write cap by: radosgw-admin caps add --uid %s --caps="buckets=write"`
   279  			} else {
   280  				msg = `add users write cap by: radosgw-admin caps add --uid %s --caps="users=write"`
   281  			}
   282  			return nil, errors.Wrapf(httperrors.ErrForbidden, msg, uid)
   283  		}
   284  		return nil, errors.Wrap(err, "api.jsonRequest")
   285  	}
   286  	log.Debugf("%s", resp)
   287  	quota = SQuota{}
   288  	err = resp.Unmarshal(&quota)
   289  	if err != nil {
   290  		return nil, errors.Wrap(err, "resp.Unmarshal")
   291  	}
   292  	return &quota, nil
   293  }