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 }