github.com/hernad/nomad@v1.6.112/nomad/scaling_endpoint.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package nomad 5 6 import ( 7 "strings" 8 "time" 9 10 "github.com/armon/go-metrics" 11 "github.com/hashicorp/go-hclog" 12 "github.com/hashicorp/go-memdb" 13 14 "github.com/hernad/nomad/acl" 15 "github.com/hernad/nomad/helper" 16 "github.com/hernad/nomad/nomad/state" 17 "github.com/hernad/nomad/nomad/structs" 18 ) 19 20 // Scaling endpoint is used for listing and retrieving scaling policies 21 type Scaling struct { 22 srv *Server 23 ctx *RPCContext 24 logger hclog.Logger 25 } 26 27 func NewScalingEndpoint(srv *Server, ctx *RPCContext) *Scaling { 28 return &Scaling{srv: srv, ctx: ctx, logger: srv.logger.Named("scaling")} 29 } 30 31 // ListPolicies is used to list the policies 32 func (p *Scaling) ListPolicies(args *structs.ScalingPolicyListRequest, reply *structs.ScalingPolicyListResponse) error { 33 34 authErr := p.srv.Authenticate(p.ctx, args) 35 if done, err := p.srv.forward("Scaling.ListPolicies", args, args, reply); done { 36 return err 37 } 38 p.srv.MeasureRPCRate("scaling", structs.RateMetricList, args) 39 if authErr != nil { 40 return structs.ErrPermissionDenied 41 } 42 defer metrics.MeasureSince([]string{"nomad", "scaling", "list_policies"}, time.Now()) 43 44 if args.RequestNamespace() == structs.AllNamespacesSentinel { 45 return p.listAllNamespaces(args, reply) 46 } 47 48 if aclObj, err := p.srv.ResolveACL(args); err != nil { 49 return err 50 } else if aclObj != nil { 51 hasListScalingPolicies := aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityListScalingPolicies) 52 hasListAndReadJobs := aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityListJobs) && 53 aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) 54 if !(hasListScalingPolicies || hasListAndReadJobs) { 55 return structs.ErrPermissionDenied 56 } 57 } 58 59 // Setup the blocking query 60 opts := blockingOptions{ 61 queryOpts: &args.QueryOptions, 62 queryMeta: &reply.QueryMeta, 63 run: func(ws memdb.WatchSet, state *state.StateStore) error { 64 // Iterate over all the policies 65 var err error 66 var iter memdb.ResultIterator 67 if prefix := args.QueryOptions.Prefix; prefix != "" { 68 iter, err = state.ScalingPoliciesByIDPrefix(ws, args.RequestNamespace(), prefix) 69 } else if job := args.Job; job != "" { 70 iter, err = state.ScalingPoliciesByJob(ws, args.RequestNamespace(), job, args.Type) 71 } else { 72 iter, err = state.ScalingPoliciesByNamespace(ws, args.Namespace, args.Type) 73 } 74 75 if err != nil { 76 return err 77 } 78 79 // Convert all the policies to a list stub 80 reply.Policies = nil 81 for raw := iter.Next(); raw != nil; raw = iter.Next() { 82 policy := raw.(*structs.ScalingPolicy) 83 reply.Policies = append(reply.Policies, policy.Stub()) 84 } 85 86 // Use the last index that affected the policy table 87 index, err := state.Index("scaling_policy") 88 if err != nil { 89 return err 90 } 91 92 // Don't return index zero, otherwise a blocking query cannot be used. 93 if index == 0 { 94 index = 1 95 } 96 reply.Index = index 97 return nil 98 }} 99 return p.srv.blockingRPC(&opts) 100 } 101 102 // GetPolicy is used to get a specific policy 103 func (p *Scaling) GetPolicy(args *structs.ScalingPolicySpecificRequest, 104 reply *structs.SingleScalingPolicyResponse) error { 105 106 authErr := p.srv.Authenticate(p.ctx, args) 107 if done, err := p.srv.forward("Scaling.GetPolicy", args, args, reply); done { 108 return err 109 } 110 p.srv.MeasureRPCRate("scaling", structs.RateMetricRead, args) 111 if authErr != nil { 112 return structs.ErrPermissionDenied 113 } 114 defer metrics.MeasureSince([]string{"nomad", "scaling", "get_policy"}, time.Now()) 115 116 // Check for list-job permissions 117 if aclObj, err := p.srv.ResolveACL(args); err != nil { 118 return err 119 } else if aclObj != nil { 120 hasReadScalingPolicy := aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadScalingPolicy) 121 hasListAndReadJobs := aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityListJobs) && 122 aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) 123 if !(hasReadScalingPolicy || hasListAndReadJobs) { 124 return structs.ErrPermissionDenied 125 } 126 } 127 128 // Setup the blocking query 129 opts := blockingOptions{ 130 queryOpts: &args.QueryOptions, 131 queryMeta: &reply.QueryMeta, 132 run: func(ws memdb.WatchSet, state *state.StateStore) error { 133 // Iterate over all the policies 134 p, err := state.ScalingPolicyByID(ws, args.ID) 135 if err != nil { 136 return err 137 } 138 139 reply.Policy = p 140 141 // If the state lookup returned a policy object, use the modify 142 // index for the response. Otherwise, use the index table to supply 143 // this, ensuring a non-zero value. 144 if p != nil { 145 reply.Index = p.ModifyIndex 146 } else { 147 index, err := state.Index("scaling_policy") 148 if err != nil { 149 return err 150 } 151 reply.Index = helper.Max(1, index) 152 } 153 return nil 154 }} 155 return p.srv.blockingRPC(&opts) 156 } 157 158 func (p *Scaling) listAllNamespaces(args *structs.ScalingPolicyListRequest, reply *structs.ScalingPolicyListResponse) error { 159 // Check for list-job permissions 160 aclObj, err := p.srv.ResolveACL(args) 161 if err != nil { 162 return err 163 } 164 prefix := args.QueryOptions.Prefix 165 allow := func(ns string) bool { 166 return aclObj.AllowNsOp(ns, acl.NamespaceCapabilityListScalingPolicies) || 167 (aclObj.AllowNsOp(ns, acl.NamespaceCapabilityListJobs) && aclObj.AllowNsOp(ns, acl.NamespaceCapabilityReadJob)) 168 } 169 170 // Setup the blocking query 171 opts := blockingOptions{ 172 queryOpts: &args.QueryOptions, 173 queryMeta: &reply.QueryMeta, 174 run: func(ws memdb.WatchSet, state *state.StateStore) error { 175 // check if user has permission to all namespaces 176 allowedNSes, err := allowedNSes(aclObj, state, allow) 177 if err == structs.ErrPermissionDenied { 178 // return empty if token isn't authorized for any namespace 179 reply.Policies = []*structs.ScalingPolicyListStub{} 180 return nil 181 } else if err != nil { 182 return err 183 } 184 185 // Capture all the policies 186 var iter memdb.ResultIterator 187 if args.Type != "" { 188 iter, err = state.ScalingPoliciesByTypePrefix(ws, args.Type) 189 } else { 190 iter, err = state.ScalingPolicies(ws) 191 } 192 if err != nil { 193 return err 194 } 195 196 var policies []*structs.ScalingPolicyListStub 197 for raw := iter.Next(); raw != nil; raw = iter.Next() { 198 policy := raw.(*structs.ScalingPolicy) 199 if allowedNSes != nil && !allowedNSes[policy.Target[structs.ScalingTargetNamespace]] { 200 // not permitted to this name namespace 201 continue 202 } 203 if prefix != "" && !strings.HasPrefix(policy.ID, prefix) { 204 continue 205 } 206 policies = append(policies, policy.Stub()) 207 } 208 reply.Policies = policies 209 210 // Use the last index that affected the policies table or summary 211 index, err := state.Index("scaling_policy") 212 if err != nil { 213 return err 214 } 215 reply.Index = helper.Max(1, index) 216 217 // Set the query response 218 p.srv.setQueryMeta(&reply.QueryMeta) 219 return nil 220 }} 221 return p.srv.blockingRPC(&opts) 222 }