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 := "apb.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 "apb.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 = "apb.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 }