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 }