github.com/minio/console@v1.4.1/api/admin_tiers.go (about) 1 // This file is part of MinIO Console Server 2 // Copyright (c) 2021 MinIO, Inc. 3 // 4 // This program is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Affero General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // This program is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Affero General Public License for more details. 13 // 14 // You should have received a copy of the GNU Affero General Public License 15 // along with this program. If not, see <http://www.gnu.org/licenses/>. 16 17 package api 18 19 import ( 20 "context" 21 "encoding/base64" 22 "strconv" 23 24 "github.com/dustin/go-humanize" 25 "github.com/go-openapi/runtime/middleware" 26 "github.com/minio/console/api/operations" 27 tieringApi "github.com/minio/console/api/operations/tiering" 28 "github.com/minio/console/models" 29 "github.com/minio/madmin-go/v3" 30 ) 31 32 func registerAdminTiersHandlers(api *operations.ConsoleAPI) { 33 // return a list of notification endpoints 34 api.TieringTiersListHandler = tieringApi.TiersListHandlerFunc(func(params tieringApi.TiersListParams, session *models.Principal) middleware.Responder { 35 tierList, err := getTiersResponse(session, params) 36 if err != nil { 37 return tieringApi.NewTiersListDefault(err.Code).WithPayload(err.APIError) 38 } 39 return tieringApi.NewTiersListOK().WithPayload(tierList) 40 }) 41 // add a new tiers 42 api.TieringAddTierHandler = tieringApi.AddTierHandlerFunc(func(params tieringApi.AddTierParams, session *models.Principal) middleware.Responder { 43 err := getAddTierResponse(session, params) 44 if err != nil { 45 return tieringApi.NewAddTierDefault(err.Code).WithPayload(err.APIError) 46 } 47 return tieringApi.NewAddTierCreated() 48 }) 49 // get a tier 50 api.TieringGetTierHandler = tieringApi.GetTierHandlerFunc(func(params tieringApi.GetTierParams, session *models.Principal) middleware.Responder { 51 notifEndpoints, err := getGetTierResponse(session, params) 52 if err != nil { 53 return tieringApi.NewGetTierDefault(err.Code).WithPayload(err.APIError) 54 } 55 return tieringApi.NewGetTierOK().WithPayload(notifEndpoints) 56 }) 57 // edit credentials for a tier 58 api.TieringEditTierCredentialsHandler = tieringApi.EditTierCredentialsHandlerFunc(func(params tieringApi.EditTierCredentialsParams, session *models.Principal) middleware.Responder { 59 err := getEditTierCredentialsResponse(session, params) 60 if err != nil { 61 return tieringApi.NewEditTierCredentialsDefault(err.Code).WithPayload(err.APIError) 62 } 63 return tieringApi.NewEditTierCredentialsOK() 64 }) 65 } 66 67 // getNotificationEndpoints invokes admin info and returns a list of notification endpoints 68 func getTiers(ctx context.Context, client MinioAdmin) (*models.TierListResponse, error) { 69 tiers, err := client.listTiers(ctx) 70 if err != nil { 71 return nil, err 72 } 73 tiersInfo, err := client.tierStats(ctx) 74 if err != nil { 75 return nil, err 76 } 77 var tiersList []*models.Tier 78 for _, tierData := range tiers { 79 80 // Default Tier Stats 81 stats := madmin.TierStats{ 82 NumObjects: 0, 83 NumVersions: 0, 84 TotalSize: 0, 85 } 86 87 // We look for the correct tier stats & set the values. 88 for _, stat := range tiersInfo { 89 if stat.Name == tierData.Name { 90 stats = stat.Stats 91 break 92 } 93 } 94 switch tierData.Type { 95 case madmin.S3: 96 tiersList = append(tiersList, &models.Tier{ 97 Type: models.TierTypeS3, 98 S3: &models.TierS3{ 99 Accesskey: tierData.S3.AccessKey, 100 Bucket: tierData.S3.Bucket, 101 Endpoint: tierData.S3.Endpoint, 102 Name: tierData.Name, 103 Prefix: tierData.S3.Prefix, 104 Region: tierData.S3.Region, 105 Secretkey: tierData.S3.SecretKey, 106 Storageclass: tierData.S3.StorageClass, 107 Usage: humanize.IBytes(stats.TotalSize), 108 Objects: strconv.Itoa(stats.NumObjects), 109 Versions: strconv.Itoa(stats.NumVersions), 110 }, 111 Status: client.verifyTierStatus(ctx, tierData.Name) == nil, 112 }) 113 case madmin.MinIO: 114 tiersList = append(tiersList, &models.Tier{ 115 Type: models.TierTypeMinio, 116 Minio: &models.TierMinio{ 117 Accesskey: tierData.MinIO.AccessKey, 118 Bucket: tierData.MinIO.Bucket, 119 Endpoint: tierData.MinIO.Endpoint, 120 Name: tierData.Name, 121 Prefix: tierData.MinIO.Prefix, 122 Region: tierData.MinIO.Region, 123 Secretkey: tierData.MinIO.SecretKey, 124 Usage: humanize.IBytes(stats.TotalSize), 125 Objects: strconv.Itoa(stats.NumObjects), 126 Versions: strconv.Itoa(stats.NumVersions), 127 }, 128 Status: client.verifyTierStatus(ctx, tierData.Name) == nil, 129 }) 130 case madmin.GCS: 131 tiersList = append(tiersList, &models.Tier{ 132 Type: models.TierTypeGcs, 133 Gcs: &models.TierGcs{ 134 Bucket: tierData.GCS.Bucket, 135 Creds: tierData.GCS.Creds, 136 Endpoint: tierData.GCS.Endpoint, 137 Name: tierData.Name, 138 Prefix: tierData.GCS.Prefix, 139 Region: tierData.GCS.Region, 140 Usage: humanize.IBytes(stats.TotalSize), 141 Objects: strconv.Itoa(stats.NumObjects), 142 Versions: strconv.Itoa(stats.NumVersions), 143 }, 144 Status: client.verifyTierStatus(ctx, tierData.Name) == nil, 145 }) 146 case madmin.Azure: 147 tiersList = append(tiersList, &models.Tier{ 148 Type: models.TierTypeAzure, 149 Azure: &models.TierAzure{ 150 Accountkey: tierData.Azure.AccountKey, 151 Accountname: tierData.Azure.AccountName, 152 Bucket: tierData.Azure.Bucket, 153 Endpoint: tierData.Azure.Endpoint, 154 Name: tierData.Name, 155 Prefix: tierData.Azure.Prefix, 156 Region: tierData.Azure.Region, 157 Usage: humanize.IBytes(stats.TotalSize), 158 Objects: strconv.Itoa(stats.NumObjects), 159 Versions: strconv.Itoa(stats.NumVersions), 160 }, 161 Status: client.verifyTierStatus(ctx, tierData.Name) == nil, 162 }) 163 case madmin.Unsupported: 164 tiersList = append(tiersList, &models.Tier{ 165 Type: models.TierTypeUnsupported, 166 Status: client.verifyTierStatus(ctx, tierData.Name) == nil, 167 }) 168 } 169 } 170 // build response 171 return &models.TierListResponse{ 172 Items: tiersList, 173 }, nil 174 } 175 176 // getTiersResponse returns a response with a list of tiers 177 func getTiersResponse(session *models.Principal, params tieringApi.TiersListParams) (*models.TierListResponse, *CodedAPIError) { 178 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 179 defer cancel() 180 mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session) 181 if err != nil { 182 return nil, ErrorWithContext(ctx, err) 183 } 184 // create a minioClient interface implementation 185 // defining the client to be used 186 adminClient := AdminClient{Client: mAdmin} 187 // serialize output 188 tiersResp, err := getTiers(ctx, adminClient) 189 if err != nil { 190 return nil, ErrorWithContext(ctx, err) 191 } 192 return tiersResp, nil 193 } 194 195 func addTier(ctx context.Context, client MinioAdmin, params *tieringApi.AddTierParams) error { 196 var cfg *madmin.TierConfig 197 var err error 198 199 switch params.Body.Type { 200 201 case models.TierTypeS3: 202 cfg, err = madmin.NewTierS3( 203 params.Body.S3.Name, 204 params.Body.S3.Accesskey, 205 params.Body.S3.Secretkey, 206 params.Body.S3.Bucket, 207 madmin.S3Region(params.Body.S3.Region), 208 madmin.S3Prefix(params.Body.S3.Prefix), 209 madmin.S3Endpoint(params.Body.S3.Endpoint), 210 madmin.S3StorageClass(params.Body.S3.Storageclass), 211 ) 212 if err != nil { 213 return err 214 } 215 case models.TierTypeMinio: 216 cfg, err = madmin.NewTierMinIO( 217 params.Body.Minio.Name, 218 params.Body.Minio.Endpoint, 219 params.Body.Minio.Accesskey, 220 params.Body.Minio.Secretkey, 221 params.Body.Minio.Bucket, 222 madmin.MinIORegion(params.Body.Minio.Region), 223 madmin.MinIOPrefix(params.Body.Minio.Prefix), 224 ) 225 if err != nil { 226 return err 227 } 228 case models.TierTypeGcs: 229 gcsOpts := []madmin.GCSOptions{} 230 prefix := params.Body.Gcs.Prefix 231 if prefix != "" { 232 gcsOpts = append(gcsOpts, madmin.GCSPrefix(prefix)) 233 } 234 235 region := params.Body.Gcs.Region 236 if region != "" { 237 gcsOpts = append(gcsOpts, madmin.GCSRegion(region)) 238 } 239 base64Text := make([]byte, base64.StdEncoding.EncodedLen(len(params.Body.Gcs.Creds))) 240 l, _ := base64.StdEncoding.Decode(base64Text, []byte(params.Body.Gcs.Creds)) 241 242 cfg, err = madmin.NewTierGCS( 243 params.Body.Gcs.Name, 244 base64Text[:l], 245 params.Body.Gcs.Bucket, 246 gcsOpts..., 247 ) 248 if err != nil { 249 return err 250 } 251 case models.TierTypeAzure: 252 cfg, err = madmin.NewTierAzure( 253 params.Body.Azure.Name, 254 params.Body.Azure.Accountname, 255 params.Body.Azure.Accountkey, 256 params.Body.Azure.Bucket, 257 madmin.AzurePrefix(params.Body.Azure.Prefix), 258 madmin.AzureEndpoint(params.Body.Azure.Endpoint), 259 madmin.AzureRegion(params.Body.Azure.Region), 260 ) 261 if err != nil { 262 return err 263 } 264 case models.TierTypeUnsupported: 265 cfg = &madmin.TierConfig{ 266 Type: madmin.Unsupported, 267 } 268 269 } 270 271 err = client.addTier(ctx, cfg) 272 if err != nil { 273 return err 274 } 275 return nil 276 } 277 278 // getAddTierResponse returns the response of admin tier 279 func getAddTierResponse(session *models.Principal, params tieringApi.AddTierParams) *CodedAPIError { 280 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 281 defer cancel() 282 mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session) 283 if err != nil { 284 return ErrorWithContext(ctx, err) 285 } 286 // create a minioClient interface implementation 287 // defining the client to be used 288 adminClient := AdminClient{Client: mAdmin} 289 290 // serialize output 291 errTier := addTier(ctx, adminClient, ¶ms) 292 if errTier != nil { 293 return ErrorWithContext(ctx, errTier) 294 } 295 return nil 296 } 297 298 func getTier(ctx context.Context, client MinioAdmin, params *tieringApi.GetTierParams) (*models.Tier, error) { 299 tiers, err := client.listTiers(ctx) 300 if err != nil { 301 return nil, err 302 } 303 for i := range tiers { 304 switch tiers[i].Type { 305 case madmin.S3: 306 if params.Type != models.TierTypeS3 || tiers[i].Name != params.Name { 307 continue 308 } 309 return &models.Tier{ 310 Type: models.TierTypeS3, 311 S3: &models.TierS3{ 312 Accesskey: tiers[i].S3.AccessKey, 313 Bucket: tiers[i].S3.Bucket, 314 Endpoint: tiers[i].S3.Endpoint, 315 Name: tiers[i].Name, 316 Prefix: tiers[i].S3.Prefix, 317 Region: tiers[i].S3.Region, 318 Secretkey: tiers[i].S3.SecretKey, 319 Storageclass: tiers[i].S3.StorageClass, 320 }, 321 }, err 322 case madmin.GCS: 323 if params.Type != models.TierTypeGcs || tiers[i].Name != params.Name { 324 continue 325 } 326 return &models.Tier{ 327 Type: models.TierTypeGcs, 328 Gcs: &models.TierGcs{ 329 Bucket: tiers[i].GCS.Bucket, 330 Creds: tiers[i].GCS.Creds, 331 Endpoint: tiers[i].GCS.Endpoint, 332 Name: tiers[i].Name, 333 Prefix: tiers[i].GCS.Prefix, 334 Region: tiers[i].GCS.Region, 335 }, 336 }, nil 337 case madmin.Azure: 338 if params.Type != models.TierTypeAzure || tiers[i].Name != params.Name { 339 continue 340 } 341 return &models.Tier{ 342 Type: models.TierTypeAzure, 343 Azure: &models.TierAzure{ 344 Accountkey: tiers[i].Azure.AccountKey, 345 Accountname: tiers[i].Azure.AccountName, 346 Bucket: tiers[i].Azure.Bucket, 347 Endpoint: tiers[i].Azure.Endpoint, 348 Name: tiers[i].Name, 349 Prefix: tiers[i].Azure.Prefix, 350 Region: tiers[i].Azure.Region, 351 }, 352 }, nil 353 } 354 } 355 356 // build response 357 return nil, ErrNotFound 358 } 359 360 // getGetTierResponse returns a tier 361 func getGetTierResponse(session *models.Principal, params tieringApi.GetTierParams) (*models.Tier, *CodedAPIError) { 362 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 363 defer cancel() 364 mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session) 365 if err != nil { 366 return nil, ErrorWithContext(ctx, err) 367 } 368 // create a minioClient interface implementation 369 // defining the client to be used 370 adminClient := AdminClient{Client: mAdmin} 371 // serialize output 372 addTierResp, err := getTier(ctx, adminClient, ¶ms) 373 if err != nil { 374 return nil, ErrorWithContext(ctx, err) 375 } 376 return addTierResp, nil 377 } 378 379 func editTierCredentials(ctx context.Context, client MinioAdmin, params *tieringApi.EditTierCredentialsParams) error { 380 base64Text := make([]byte, base64.StdEncoding.EncodedLen(len(params.Body.Creds))) 381 l, err := base64.StdEncoding.Decode(base64Text, []byte(params.Body.Creds)) 382 if err != nil { 383 return err 384 } 385 386 creds := madmin.TierCreds{ 387 AccessKey: params.Body.AccessKey, 388 SecretKey: params.Body.SecretKey, 389 CredsJSON: base64Text[:l], 390 } 391 return client.editTierCreds(ctx, params.Name, creds) 392 } 393 394 // getEditTierCredentialsResponse returns the result of editing credentials for a tier 395 func getEditTierCredentialsResponse(session *models.Principal, params tieringApi.EditTierCredentialsParams) *CodedAPIError { 396 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 397 defer cancel() 398 mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session) 399 if err != nil { 400 return ErrorWithContext(ctx, err) 401 } 402 // create a minioClient interface implementation 403 // defining the client to be used 404 adminClient := AdminClient{Client: mAdmin} 405 // serialize output 406 err = editTierCredentials(ctx, adminClient, ¶ms) 407 if err != nil { 408 return ErrorWithContext(ctx, err) 409 } 410 return nil 411 }