github.com/zorawar87/trillian@v1.2.1/quota/etcd/quotaapi/quota_server.go (about)

     1  // Copyright 2017 Google Inc. All Rights Reserved.
     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 quotaapi provides a Quota admin server implementation.
    16  package quotaapi
    17  
    18  import (
    19  	"context"
    20  
    21  	"github.com/coreos/etcd/clientv3"
    22  	"github.com/golang/glog"
    23  	"github.com/golang/protobuf/ptypes/empty"
    24  	"github.com/google/trillian/quota/etcd/quotapb"
    25  	"github.com/google/trillian/quota/etcd/storage"
    26  	"github.com/google/trillian/quota/etcd/storagepb"
    27  	"google.golang.org/genproto/protobuf/field_mask"
    28  	"google.golang.org/grpc/codes"
    29  	"google.golang.org/grpc/status"
    30  )
    31  
    32  // Server is a quotapb.QuotaServer implementation backed by etcd.
    33  type Server struct {
    34  	qs *storage.QuotaStorage
    35  }
    36  
    37  // NewServer returns a new Server instance backed by client.
    38  func NewServer(client *clientv3.Client) *Server {
    39  	return &Server{qs: &storage.QuotaStorage{Client: client}}
    40  }
    41  
    42  // CreateConfig implements quotapb.QuotaServer.CreateConfig.
    43  func (s *Server) CreateConfig(ctx context.Context, req *quotapb.CreateConfigRequest) (*quotapb.Config, error) {
    44  	if req.Config == nil {
    45  		return nil, status.Errorf(codes.InvalidArgument, "config is required")
    46  	}
    47  	if err := validateName(req.Name); err != nil {
    48  		return nil, err
    49  	}
    50  	req.Config.Name = req.Name
    51  
    52  	var alreadyExists bool
    53  	updated, err := s.qs.UpdateConfigs(ctx, false /* reset */, func(cfgs *storagepb.Configs) {
    54  		if _, alreadyExists = findByName(req.Name, cfgs); alreadyExists {
    55  			return
    56  		}
    57  		cfgs.Configs = append(cfgs.Configs, convertToStorage(req.Config))
    58  	})
    59  	switch {
    60  	case alreadyExists:
    61  		return nil, status.Errorf(codes.AlreadyExists, "%q already exists", req.Name)
    62  	case err != nil:
    63  		return nil, err
    64  	}
    65  	return s.getConfig(ctx, req.Name, updated, codes.Internal)
    66  }
    67  
    68  // DeleteConfig implements quotapb.QuotaServer.DeleteConfig.
    69  func (s *Server) DeleteConfig(ctx context.Context, req *quotapb.DeleteConfigRequest) (*empty.Empty, error) {
    70  	if err := validateName(req.Name); err != nil {
    71  		return nil, err
    72  	}
    73  
    74  	notFound := false
    75  	_, err := s.qs.UpdateConfigs(ctx, false /* reset */, func(cfgs *storagepb.Configs) {
    76  		for i, cfg := range cfgs.Configs {
    77  			if cfg.Name == req.Name {
    78  				cfgs.Configs = append(cfgs.Configs[:i], cfgs.Configs[i+1:]...)
    79  				return
    80  			}
    81  		}
    82  		notFound = true
    83  	})
    84  	if notFound {
    85  		return nil, status.Errorf(codes.NotFound, "%q not found", req.Name)
    86  	}
    87  	return &empty.Empty{}, err
    88  }
    89  
    90  // GetConfig implements quotapb.QuotaServer.GetConfig.
    91  func (s *Server) GetConfig(ctx context.Context, req *quotapb.GetConfigRequest) (*quotapb.Config, error) {
    92  	if err := validateName(req.Name); err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	cfgs, err := s.qs.Configs(ctx)
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  	return s.getConfig(ctx, req.Name, cfgs, codes.NotFound)
   101  }
   102  
   103  // getConfig finds the Config named "name" on "cfgs", converts it to API and returns it.
   104  // If the config cannot be found an error with code "code" is returned.
   105  func (s *Server) getConfig(ctx context.Context, name string, cfgs *storagepb.Configs, code codes.Code) (*quotapb.Config, error) {
   106  	storedCfg, ok := findByName(name, cfgs)
   107  	if !ok {
   108  		return nil, status.Errorf(code, "%q not found", name)
   109  	}
   110  	cfg := convertToAPI(storedCfg)
   111  
   112  	tokens, err := s.qs.Peek(ctx, []string{cfg.Name})
   113  	if err == nil {
   114  		cfg.CurrentTokens = tokens[cfg.Name]
   115  	} else {
   116  		glog.Warningf("Unexpected error peeking token count for %q: %v", cfg.Name, err)
   117  	}
   118  
   119  	return cfg, nil
   120  }
   121  
   122  // ListConfigs implements quotapb.QuotaServer.ListConfigs.
   123  func (s *Server) ListConfigs(ctx context.Context, req *quotapb.ListConfigsRequest) (*quotapb.ListConfigsResponse, error) {
   124  	nfs := make([]nameFilter, 0, len(req.Names))
   125  	for _, name := range req.Names {
   126  		nf, err := newNameFilter(name)
   127  		if err != nil {
   128  			return nil, status.Error(codes.InvalidArgument, err.Error())
   129  		}
   130  		nfs = append(nfs, nf)
   131  	}
   132  
   133  	cfgs, err := s.qs.Configs(ctx)
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  	resp := &quotapb.ListConfigsResponse{}
   138  	for _, cfg := range cfgs.Configs {
   139  		if listMatches(nfs, cfg) {
   140  			resp.Configs = append(resp.Configs, listView(req.View, cfg))
   141  		}
   142  	}
   143  
   144  	// Peek token counts for FULL view
   145  	if req.View == quotapb.ListConfigsRequest_FULL {
   146  		names := make([]string, 0, len(resp.Configs))
   147  		for _, cfg := range resp.Configs {
   148  			names = append(names, cfg.Name)
   149  		}
   150  		tokens, err := s.qs.Peek(ctx, names)
   151  		if err == nil {
   152  			for _, cfg := range resp.Configs {
   153  				cfg.CurrentTokens = tokens[cfg.Name]
   154  			}
   155  		} else {
   156  			glog.Infof("Error peeking token counts for %v: %v", names, err)
   157  		}
   158  	}
   159  	return resp, nil
   160  }
   161  
   162  func listMatches(nfs []nameFilter, cfg *storagepb.Config) bool {
   163  	if len(nfs) == 0 {
   164  		return true // Match all
   165  	}
   166  	for _, nf := range nfs {
   167  		if nf.matches(cfg.Name) {
   168  			return true
   169  		}
   170  	}
   171  	return false
   172  }
   173  
   174  func listView(view quotapb.ListConfigsRequest_ListView, src *storagepb.Config) *quotapb.Config {
   175  	switch view {
   176  	case quotapb.ListConfigsRequest_BASIC:
   177  		return &quotapb.Config{Name: src.Name}
   178  	default:
   179  		return convertToAPI(src)
   180  	}
   181  }
   182  
   183  // UpdateConfig implements quotapb.QuotaServer.UpdateConfig.
   184  func (s *Server) UpdateConfig(ctx context.Context, req *quotapb.UpdateConfigRequest) (*quotapb.Config, error) {
   185  	cfg, mask := req.Config, req.UpdateMask
   186  	hasConfig := cfg != nil
   187  	hasMask := mask != nil && len(mask.Paths) > 0
   188  	switch {
   189  	case req.ResetQuota && !hasConfig && !hasMask:
   190  		// For convenience, reset-only requests are allowed.
   191  		cfg = &quotapb.Config{}
   192  		mask = &field_mask.FieldMask{}
   193  	case !hasConfig:
   194  		return nil, status.Errorf(codes.InvalidArgument, "config must be specified")
   195  	case !hasMask:
   196  		return nil, status.Errorf(codes.InvalidArgument, "update_mask must be specified")
   197  	}
   198  	if err := validateName(req.Name); err != nil {
   199  		return nil, err
   200  	}
   201  	if err := validateMask(mask); err != nil {
   202  		return nil, status.Errorf(codes.InvalidArgument, "invalid update_mask: %v", err)
   203  	}
   204  
   205  	var notFound bool
   206  	updated, err := s.qs.UpdateConfigs(ctx, req.ResetQuota, func(cfgs *storagepb.Configs) {
   207  		existingCfg, ok := findByName(req.Name, cfgs)
   208  		if !ok {
   209  			notFound = true
   210  			return
   211  		}
   212  		applyMask(cfg, existingCfg, mask)
   213  	})
   214  	switch {
   215  	case notFound:
   216  		return nil, status.Errorf(codes.NotFound, "%q not found", req.Name)
   217  	case err != nil:
   218  		return nil, err
   219  	}
   220  	return s.getConfig(ctx, req.Name, updated, codes.Internal)
   221  }
   222  
   223  func findByName(name string, cfgs *storagepb.Configs) (*storagepb.Config, bool) {
   224  	for _, cfg := range cfgs.Configs {
   225  		if cfg.Name == name {
   226  			return cfg, true
   227  		}
   228  	}
   229  	return nil, false
   230  }
   231  
   232  func validateName(name string) error {
   233  	switch {
   234  	case name == "":
   235  		return status.Errorf(codes.InvalidArgument, "name is required")
   236  	case !storage.IsNameValid(name):
   237  		return status.Errorf(codes.InvalidArgument, "invalid name: %q", name)
   238  	}
   239  	return nil
   240  }