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 }