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  }