github.com/hernad/nomad@v1.6.112/nomad/namespace_endpoint.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package nomad
     5  
     6  import (
     7  	"fmt"
     8  	"time"
     9  
    10  	"github.com/armon/go-metrics"
    11  	"github.com/hashicorp/go-memdb"
    12  	"github.com/hashicorp/go-multierror"
    13  
    14  	"github.com/hernad/nomad/nomad/state"
    15  	"github.com/hernad/nomad/nomad/structs"
    16  )
    17  
    18  // Namespace endpoint is used for manipulating namespaces
    19  type Namespace struct {
    20  	srv *Server
    21  	ctx *RPCContext
    22  }
    23  
    24  func NewNamespaceEndpoint(srv *Server, ctx *RPCContext) *Namespace {
    25  	return &Namespace{srv: srv, ctx: ctx}
    26  }
    27  
    28  // UpsertNamespaces is used to upsert a set of namespaces
    29  func (n *Namespace) UpsertNamespaces(args *structs.NamespaceUpsertRequest,
    30  	reply *structs.GenericResponse) error {
    31  
    32  	authErr := n.srv.Authenticate(n.ctx, args)
    33  	args.Region = n.srv.config.AuthoritativeRegion
    34  	if done, err := n.srv.forward("Namespace.UpsertNamespaces", args, args, reply); done {
    35  		return err
    36  	}
    37  	n.srv.MeasureRPCRate("namespace", structs.RateMetricWrite, args)
    38  	if authErr != nil {
    39  		return structs.ErrPermissionDenied
    40  	}
    41  
    42  	defer metrics.MeasureSince([]string{"nomad", "namespace", "upsert_namespaces"}, time.Now())
    43  
    44  	// Check management permissions
    45  	if aclObj, err := n.srv.ResolveACL(args); err != nil {
    46  		return err
    47  	} else if aclObj != nil && !aclObj.IsManagement() {
    48  		return structs.ErrPermissionDenied
    49  	}
    50  
    51  	// Validate there is at least one namespace
    52  	if len(args.Namespaces) == 0 {
    53  		return fmt.Errorf("must specify at least one namespace")
    54  	}
    55  
    56  	// Validate the namespaces and set the hash
    57  	for _, ns := range args.Namespaces {
    58  		if err := ns.Validate(); err != nil {
    59  			return fmt.Errorf("Invalid namespace %q: %v", ns.Name, err)
    60  		}
    61  
    62  		ns.SetHash()
    63  	}
    64  
    65  	// Update via Raft
    66  	_, index, err := n.srv.raftApply(structs.NamespaceUpsertRequestType, args)
    67  	if err != nil {
    68  		return err
    69  	}
    70  
    71  	// Update the index
    72  	reply.Index = index
    73  	return nil
    74  }
    75  
    76  // DeleteNamespaces is used to delete a namespace
    77  func (n *Namespace) DeleteNamespaces(args *structs.NamespaceDeleteRequest, reply *structs.GenericResponse) error {
    78  
    79  	authErr := n.srv.Authenticate(n.ctx, args)
    80  	args.Region = n.srv.config.AuthoritativeRegion
    81  	if done, err := n.srv.forward("Namespace.DeleteNamespaces", args, args, reply); done {
    82  		return err
    83  	}
    84  	n.srv.MeasureRPCRate("namespace", structs.RateMetricWrite, args)
    85  	if authErr != nil {
    86  		return structs.ErrPermissionDenied
    87  	}
    88  	defer metrics.MeasureSince([]string{"nomad", "namespace", "delete_namespaces"}, time.Now())
    89  
    90  	// Check management permissions
    91  	if aclObj, err := n.srv.ResolveACL(args); err != nil {
    92  		return err
    93  	} else if aclObj != nil && !aclObj.IsManagement() {
    94  		return structs.ErrPermissionDenied
    95  	}
    96  
    97  	// Validate at least one namespace
    98  	if len(args.Namespaces) == 0 {
    99  		return fmt.Errorf("must specify at least one namespace to delete")
   100  	}
   101  
   102  	for _, ns := range args.Namespaces {
   103  		if ns == structs.DefaultNamespace {
   104  			return fmt.Errorf("can not delete default namespace")
   105  		}
   106  	}
   107  
   108  	// Check that the deleting namespaces do not have non-terminal jobs in both
   109  	// this region and all federated regions
   110  	var mErr multierror.Error
   111  	for _, ns := range args.Namespaces {
   112  		nonTerminal, err := n.nonTerminalNamespaces(args.AuthToken, ns)
   113  		if err != nil {
   114  			_ = multierror.Append(&mErr, err)
   115  		} else if len(nonTerminal) != 0 {
   116  			_ = multierror.Append(&mErr, fmt.Errorf("namespace %q has non-terminal jobs in regions: %v", ns, nonTerminal))
   117  		}
   118  	}
   119  
   120  	if err := mErr.ErrorOrNil(); err != nil {
   121  		return err
   122  	}
   123  
   124  	// Update via Raft
   125  	_, index, err := n.srv.raftApply(structs.NamespaceDeleteRequestType, args)
   126  	if err != nil {
   127  		return err
   128  	}
   129  
   130  	// Update the index
   131  	reply.Index = index
   132  	return nil
   133  }
   134  
   135  // nonTerminalNamespaces returns whether the set of regions in which the
   136  // namespaces contains non-terminal jobs, checking all federated regions
   137  // including this one.
   138  func (n *Namespace) nonTerminalNamespaces(authToken, namespace string) ([]string, error) {
   139  	regions := n.srv.Regions()
   140  	thisRegion := n.srv.Region()
   141  	terminal := make([]string, 0, len(regions))
   142  
   143  	// Check if this region is terminal
   144  	localTerminal, err := n.namespaceTerminalLocally(namespace)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  	if !localTerminal {
   149  		terminal = append(terminal, thisRegion)
   150  	}
   151  
   152  	for _, region := range regions {
   153  		if region == thisRegion {
   154  			continue
   155  		}
   156  
   157  		remoteTerminal, err := n.namespaceTerminalInRegion(authToken, namespace, region)
   158  		if err != nil {
   159  			return nil, err
   160  		}
   161  		if !remoteTerminal {
   162  			terminal = append(terminal, region)
   163  		}
   164  	}
   165  
   166  	return terminal, nil
   167  }
   168  
   169  // namespaceTerminalLocally returns if the namespace contains only terminal jobs
   170  // in the local region .
   171  func (n *Namespace) namespaceTerminalLocally(namespace string) (bool, error) {
   172  	snap, err := n.srv.fsm.State().Snapshot()
   173  	if err != nil {
   174  		return false, err
   175  	}
   176  
   177  	iter, err := snap.JobsByNamespace(nil, namespace)
   178  	if err != nil {
   179  		return false, err
   180  	}
   181  
   182  	for {
   183  		raw := iter.Next()
   184  		if raw == nil {
   185  			break
   186  		}
   187  
   188  		job := raw.(*structs.Job)
   189  		if job.Status != structs.JobStatusDead {
   190  			return false, nil
   191  		}
   192  	}
   193  
   194  	return true, nil
   195  }
   196  
   197  // namespaceTerminalInRegion returns if the namespace contains only terminal
   198  // jobs in the given region .
   199  func (n *Namespace) namespaceTerminalInRegion(authToken, namespace, region string) (bool, error) {
   200  	req := &structs.JobListRequest{
   201  		QueryOptions: structs.QueryOptions{
   202  			Region:     region,
   203  			Namespace:  namespace,
   204  			AllowStale: false,
   205  			AuthToken:  authToken,
   206  		},
   207  	}
   208  
   209  	var resp structs.JobListResponse
   210  	done, err := n.srv.forward("Job.List", req, req, &resp)
   211  	if !done {
   212  		return false, fmt.Errorf("unexpectedly did not forward Job.List to region %q", region)
   213  	} else if err != nil {
   214  		return false, err
   215  	}
   216  
   217  	for _, job := range resp.Jobs {
   218  		if job.Status != structs.JobStatusDead {
   219  			return false, nil
   220  		}
   221  	}
   222  
   223  	return true, nil
   224  }
   225  
   226  // ListNamespaces is used to list the namespaces
   227  func (n *Namespace) ListNamespaces(args *structs.NamespaceListRequest, reply *structs.NamespaceListResponse) error {
   228  
   229  	authErr := n.srv.Authenticate(n.ctx, args)
   230  	if done, err := n.srv.forward("Namespace.ListNamespaces", args, args, reply); done {
   231  		return err
   232  	}
   233  	n.srv.MeasureRPCRate("namespace", structs.RateMetricList, args)
   234  	if authErr != nil {
   235  		return structs.ErrPermissionDenied
   236  	}
   237  	defer metrics.MeasureSince([]string{"nomad", "namespace", "list_namespace"}, time.Now())
   238  
   239  	// Resolve token to acl to filter namespace list
   240  	aclObj, err := n.srv.ResolveACL(args)
   241  	if err != nil {
   242  		return err
   243  	}
   244  
   245  	// Setup the blocking query
   246  	opts := blockingOptions{
   247  		queryOpts: &args.QueryOptions,
   248  		queryMeta: &reply.QueryMeta,
   249  		run: func(ws memdb.WatchSet, s *state.StateStore) error {
   250  			// Iterate over all the namespaces
   251  			var err error
   252  			var iter memdb.ResultIterator
   253  			if prefix := args.QueryOptions.Prefix; prefix != "" {
   254  				iter, err = s.NamespacesByNamePrefix(ws, prefix)
   255  			} else {
   256  				iter, err = s.Namespaces(ws)
   257  			}
   258  			if err != nil {
   259  				return err
   260  			}
   261  
   262  			reply.Namespaces = nil
   263  			for {
   264  				raw := iter.Next()
   265  				if raw == nil {
   266  					break
   267  				}
   268  				ns := raw.(*structs.Namespace)
   269  
   270  				// Only return namespaces allowed by acl
   271  				if aclObj == nil || aclObj.AllowNamespace(ns.Name) {
   272  					reply.Namespaces = append(reply.Namespaces, ns)
   273  				}
   274  			}
   275  
   276  			// Use the last index that affected the namespace table
   277  			index, err := s.Index(state.TableNamespaces)
   278  			if err != nil {
   279  				return err
   280  			}
   281  
   282  			// Ensure we never set the index to zero, otherwise a blocking query cannot be used.
   283  			// We floor the index at one, since realistically the first write must have a higher index.
   284  			if index == 0 {
   285  				index = 1
   286  			}
   287  			reply.Index = index
   288  			return nil
   289  		}}
   290  	return n.srv.blockingRPC(&opts)
   291  }
   292  
   293  // GetNamespace is used to get a specific namespace
   294  func (n *Namespace) GetNamespace(args *structs.NamespaceSpecificRequest, reply *structs.SingleNamespaceResponse) error {
   295  
   296  	authErr := n.srv.Authenticate(n.ctx, args)
   297  	if done, err := n.srv.forward("Namespace.GetNamespace", args, args, reply); done {
   298  		return err
   299  	}
   300  	n.srv.MeasureRPCRate("namespace", structs.RateMetricRead, args)
   301  	if authErr != nil {
   302  		return structs.ErrPermissionDenied
   303  	}
   304  	defer metrics.MeasureSince([]string{"nomad", "namespace", "get_namespace"}, time.Now())
   305  
   306  	// Check capabilities for the given namespace permissions
   307  	if aclObj, err := n.srv.ResolveACL(args); err != nil {
   308  		return err
   309  	} else if aclObj != nil && !aclObj.AllowNamespace(args.Name) {
   310  		return structs.ErrPermissionDenied
   311  	}
   312  
   313  	// Setup the blocking query
   314  	opts := blockingOptions{
   315  		queryOpts: &args.QueryOptions,
   316  		queryMeta: &reply.QueryMeta,
   317  		run: func(ws memdb.WatchSet, s *state.StateStore) error {
   318  			// Look for the namespace
   319  			out, err := s.NamespaceByName(ws, args.Name)
   320  			if err != nil {
   321  				return err
   322  			}
   323  
   324  			// Setup the output
   325  			reply.Namespace = out
   326  			if out != nil {
   327  				reply.Index = out.ModifyIndex
   328  			} else {
   329  				// Use the last index that affected the namespace table
   330  				index, err := s.Index(state.TableNamespaces)
   331  				if err != nil {
   332  					return err
   333  				}
   334  
   335  				// Ensure we never set the index to zero, otherwise a blocking query cannot be used.
   336  				// We floor the index at one, since realistically the first write must have a higher index.
   337  				if index == 0 {
   338  					index = 1
   339  				}
   340  				reply.Index = index
   341  			}
   342  			return nil
   343  		}}
   344  	return n.srv.blockingRPC(&opts)
   345  }
   346  
   347  // GetNamespaces is used to get a set of namespaces
   348  func (n *Namespace) GetNamespaces(args *structs.NamespaceSetRequest, reply *structs.NamespaceSetResponse) error {
   349  
   350  	authErr := n.srv.Authenticate(n.ctx, args)
   351  	if done, err := n.srv.forward("Namespace.GetNamespaces", args, args, reply); done {
   352  		return err
   353  	}
   354  	n.srv.MeasureRPCRate("namespace", structs.RateMetricList, args)
   355  	if authErr != nil {
   356  		return structs.ErrPermissionDenied
   357  	}
   358  	defer metrics.MeasureSince([]string{"nomad", "namespace", "get_namespaces"}, time.Now())
   359  
   360  	// Check management permissions
   361  	if aclObj, err := n.srv.ResolveACL(args); err != nil {
   362  		return err
   363  	} else if aclObj != nil && !aclObj.IsManagement() {
   364  		return structs.ErrPermissionDenied
   365  	}
   366  
   367  	// Setup the blocking query
   368  	opts := blockingOptions{
   369  		queryOpts: &args.QueryOptions,
   370  		queryMeta: &reply.QueryMeta,
   371  		run: func(ws memdb.WatchSet, s *state.StateStore) error {
   372  			// Setup the output
   373  			reply.Namespaces = make(map[string]*structs.Namespace, len(args.Namespaces))
   374  
   375  			// Look for the namespace
   376  			for _, namespace := range args.Namespaces {
   377  				out, err := s.NamespaceByName(ws, namespace)
   378  				if err != nil {
   379  					return err
   380  				}
   381  				if out != nil {
   382  					reply.Namespaces[namespace] = out
   383  				}
   384  			}
   385  
   386  			// Use the last index that affected the policy table
   387  			index, err := s.Index(state.TableNamespaces)
   388  			if err != nil {
   389  				return err
   390  			}
   391  
   392  			// Ensure we never set the index to zero, otherwise a blocking query cannot be used.
   393  			// We floor the index at one, since realistically the first write must have a higher index.
   394  			if index == 0 {
   395  				index = 1
   396  			}
   397  			reply.Index = index
   398  			return nil
   399  		}}
   400  	return n.srv.blockingRPC(&opts)
   401  }