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, &params)
   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, &params)
   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, &params)
   407  	if err != nil {
   408  		return ErrorWithContext(ctx, err)
   409  	}
   410  	return nil
   411  }