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("a) 219 if err != nil { 220 return nil, errors.Wrap(err, "resp.Unmarshal") 221 } 222 return "a, 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("a) 289 if err != nil { 290 return nil, errors.Wrap(err, "resp.Unmarshal") 291 } 292 return "a, nil 293 }