github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/nomad/namespace_endpoint.go (about)

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