github.com/zoomfoo/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/nomad/deployment_endpoint.go (about)

     1  package nomad
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	metrics "github.com/armon/go-metrics"
     8  	memdb "github.com/hashicorp/go-memdb"
     9  	"github.com/hashicorp/nomad/acl"
    10  	"github.com/hashicorp/nomad/nomad/state"
    11  	"github.com/hashicorp/nomad/nomad/structs"
    12  )
    13  
    14  // Deployment endpoint is used for manipulating deployments
    15  type Deployment struct {
    16  	srv *Server
    17  }
    18  
    19  // GetDeployment is used to request information about a specific deployment
    20  func (d *Deployment) GetDeployment(args *structs.DeploymentSpecificRequest,
    21  	reply *structs.SingleDeploymentResponse) error {
    22  	if done, err := d.srv.forward("Deployment.GetDeployment", args, args, reply); done {
    23  		return err
    24  	}
    25  	defer metrics.MeasureSince([]string{"nomad", "deployment", "get_deployment"}, time.Now())
    26  
    27  	// Check namespace read-job permissions
    28  	if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil {
    29  		return err
    30  	} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) {
    31  		return structs.ErrPermissionDenied
    32  	}
    33  
    34  	// Setup the blocking query
    35  	opts := blockingOptions{
    36  		queryOpts: &args.QueryOptions,
    37  		queryMeta: &reply.QueryMeta,
    38  		run: func(ws memdb.WatchSet, state *state.StateStore) error {
    39  			// Verify the arguments
    40  			if args.DeploymentID == "" {
    41  				return fmt.Errorf("missing deployment ID")
    42  			}
    43  
    44  			// Look for the deployment
    45  			out, err := state.DeploymentByID(ws, args.DeploymentID)
    46  			if err != nil {
    47  				return err
    48  			}
    49  
    50  			// Setup the output
    51  			reply.Deployment = out
    52  			if out != nil {
    53  				reply.Index = out.ModifyIndex
    54  			} else {
    55  				// Use the last index that affected the deployments table
    56  				index, err := state.Index("deployment")
    57  				if err != nil {
    58  					return err
    59  				}
    60  				reply.Index = index
    61  			}
    62  
    63  			// Set the query response
    64  			d.srv.setQueryMeta(&reply.QueryMeta)
    65  			return nil
    66  		}}
    67  	return d.srv.blockingRPC(&opts)
    68  }
    69  
    70  // Fail is used to force fail a deployment
    71  func (d *Deployment) Fail(args *structs.DeploymentFailRequest, reply *structs.DeploymentUpdateResponse) error {
    72  	if done, err := d.srv.forward("Deployment.Fail", args, args, reply); done {
    73  		return err
    74  	}
    75  	defer metrics.MeasureSince([]string{"nomad", "deployment", "fail"}, time.Now())
    76  
    77  	// Check namespace submit-job permissions
    78  	if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil {
    79  		return err
    80  	} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilitySubmitJob) {
    81  		return structs.ErrPermissionDenied
    82  	}
    83  
    84  	// Validate the arguments
    85  	if args.DeploymentID == "" {
    86  		return fmt.Errorf("missing deployment ID")
    87  	}
    88  
    89  	// Lookup the deployment
    90  	snap, err := d.srv.fsm.State().Snapshot()
    91  	if err != nil {
    92  		return err
    93  	}
    94  
    95  	ws := memdb.NewWatchSet()
    96  	deploy, err := snap.DeploymentByID(ws, args.DeploymentID)
    97  	if err != nil {
    98  		return err
    99  	}
   100  	if deploy == nil {
   101  		return fmt.Errorf("deployment not found")
   102  	}
   103  
   104  	if !deploy.Active() {
   105  		return fmt.Errorf("can't fail terminal deployment")
   106  	}
   107  
   108  	// Call into the deployment watcher
   109  	return d.srv.deploymentWatcher.FailDeployment(args, reply)
   110  }
   111  
   112  // Pause is used to pause a deployment
   113  func (d *Deployment) Pause(args *structs.DeploymentPauseRequest, reply *structs.DeploymentUpdateResponse) error {
   114  	if done, err := d.srv.forward("Deployment.Pause", args, args, reply); done {
   115  		return err
   116  	}
   117  	defer metrics.MeasureSince([]string{"nomad", "deployment", "pause"}, time.Now())
   118  
   119  	// Check namespace submit-job permissions
   120  	if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil {
   121  		return err
   122  	} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilitySubmitJob) {
   123  		return structs.ErrPermissionDenied
   124  	}
   125  
   126  	// Validate the arguments
   127  	if args.DeploymentID == "" {
   128  		return fmt.Errorf("missing deployment ID")
   129  	}
   130  
   131  	// Lookup the deployment
   132  	snap, err := d.srv.fsm.State().Snapshot()
   133  	if err != nil {
   134  		return err
   135  	}
   136  
   137  	ws := memdb.NewWatchSet()
   138  	deploy, err := snap.DeploymentByID(ws, args.DeploymentID)
   139  	if err != nil {
   140  		return err
   141  	}
   142  	if deploy == nil {
   143  		return fmt.Errorf("deployment not found")
   144  	}
   145  
   146  	if !deploy.Active() {
   147  		if args.Pause {
   148  			return fmt.Errorf("can't pause terminal deployment")
   149  		}
   150  
   151  		return fmt.Errorf("can't resume terminal deployment")
   152  	}
   153  
   154  	// Call into the deployment watcher
   155  	return d.srv.deploymentWatcher.PauseDeployment(args, reply)
   156  }
   157  
   158  // Promote is used to promote canaries in a deployment
   159  func (d *Deployment) Promote(args *structs.DeploymentPromoteRequest, reply *structs.DeploymentUpdateResponse) error {
   160  	if done, err := d.srv.forward("Deployment.Promote", args, args, reply); done {
   161  		return err
   162  	}
   163  	defer metrics.MeasureSince([]string{"nomad", "deployment", "promote"}, time.Now())
   164  
   165  	// Check namespace submit-job permissions
   166  	if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil {
   167  		return err
   168  	} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilitySubmitJob) {
   169  		return structs.ErrPermissionDenied
   170  	}
   171  
   172  	// Validate the arguments
   173  	if args.DeploymentID == "" {
   174  		return fmt.Errorf("missing deployment ID")
   175  	}
   176  
   177  	// Lookup the deployment
   178  	snap, err := d.srv.fsm.State().Snapshot()
   179  	if err != nil {
   180  		return err
   181  	}
   182  
   183  	ws := memdb.NewWatchSet()
   184  	deploy, err := snap.DeploymentByID(ws, args.DeploymentID)
   185  	if err != nil {
   186  		return err
   187  	}
   188  	if deploy == nil {
   189  		return fmt.Errorf("deployment not found")
   190  	}
   191  
   192  	if !deploy.Active() {
   193  		return fmt.Errorf("can't promote terminal deployment")
   194  	}
   195  
   196  	// Call into the deployment watcher
   197  	return d.srv.deploymentWatcher.PromoteDeployment(args, reply)
   198  }
   199  
   200  // SetAllocHealth is used to set the health of allocations that are part of the
   201  // deployment.
   202  func (d *Deployment) SetAllocHealth(args *structs.DeploymentAllocHealthRequest, reply *structs.DeploymentUpdateResponse) error {
   203  	if done, err := d.srv.forward("Deployment.SetAllocHealth", args, args, reply); done {
   204  		return err
   205  	}
   206  	defer metrics.MeasureSince([]string{"nomad", "deployment", "set_alloc_health"}, time.Now())
   207  
   208  	// Check namespace submit-job permissions
   209  	if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil {
   210  		return err
   211  	} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilitySubmitJob) {
   212  		return structs.ErrPermissionDenied
   213  	}
   214  
   215  	// Validate the arguments
   216  	if args.DeploymentID == "" {
   217  		return fmt.Errorf("missing deployment ID")
   218  	}
   219  
   220  	if len(args.HealthyAllocationIDs)+len(args.UnhealthyAllocationIDs) == 0 {
   221  		return fmt.Errorf("must specify at least one healthy/unhealthy allocation ID")
   222  	}
   223  
   224  	// Lookup the deployment
   225  	snap, err := d.srv.fsm.State().Snapshot()
   226  	if err != nil {
   227  		return err
   228  	}
   229  
   230  	ws := memdb.NewWatchSet()
   231  	deploy, err := snap.DeploymentByID(ws, args.DeploymentID)
   232  	if err != nil {
   233  		return err
   234  	}
   235  	if deploy == nil {
   236  		return fmt.Errorf("deployment not found")
   237  	}
   238  
   239  	if !deploy.Active() {
   240  		return fmt.Errorf("can't set health of allocations for a terminal deployment")
   241  	}
   242  
   243  	// Call into the deployment watcher
   244  	return d.srv.deploymentWatcher.SetAllocHealth(args, reply)
   245  }
   246  
   247  // List returns the list of deployments in the system
   248  func (d *Deployment) List(args *structs.DeploymentListRequest, reply *structs.DeploymentListResponse) error {
   249  	if done, err := d.srv.forward("Deployment.List", args, args, reply); done {
   250  		return err
   251  	}
   252  	defer metrics.MeasureSince([]string{"nomad", "deployment", "list"}, time.Now())
   253  
   254  	// Check namespace read-job permissions
   255  	if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil {
   256  		return err
   257  	} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) {
   258  		return structs.ErrPermissionDenied
   259  	}
   260  
   261  	// Setup the blocking query
   262  	opts := blockingOptions{
   263  		queryOpts: &args.QueryOptions,
   264  		queryMeta: &reply.QueryMeta,
   265  		run: func(ws memdb.WatchSet, state *state.StateStore) error {
   266  			// Capture all the deployments
   267  			var err error
   268  			var iter memdb.ResultIterator
   269  			if prefix := args.QueryOptions.Prefix; prefix != "" {
   270  				iter, err = state.DeploymentsByIDPrefix(ws, args.RequestNamespace(), prefix)
   271  			} else {
   272  				iter, err = state.DeploymentsByNamespace(ws, args.RequestNamespace())
   273  			}
   274  			if err != nil {
   275  				return err
   276  			}
   277  
   278  			var deploys []*structs.Deployment
   279  			for {
   280  				raw := iter.Next()
   281  				if raw == nil {
   282  					break
   283  				}
   284  				deploy := raw.(*structs.Deployment)
   285  				deploys = append(deploys, deploy)
   286  			}
   287  			reply.Deployments = deploys
   288  
   289  			// Use the last index that affected the deployment table
   290  			index, err := state.Index("deployment")
   291  			if err != nil {
   292  				return err
   293  			}
   294  			reply.Index = index
   295  
   296  			// Set the query response
   297  			d.srv.setQueryMeta(&reply.QueryMeta)
   298  			return nil
   299  		}}
   300  	return d.srv.blockingRPC(&opts)
   301  }
   302  
   303  // Allocations returns the list of allocations that are a part of the deployment
   304  func (d *Deployment) Allocations(args *structs.DeploymentSpecificRequest, reply *structs.AllocListResponse) error {
   305  	if done, err := d.srv.forward("Deployment.Allocations", args, args, reply); done {
   306  		return err
   307  	}
   308  	defer metrics.MeasureSince([]string{"nomad", "deployment", "allocations"}, time.Now())
   309  
   310  	// Check namespace read-job permissions
   311  	if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil {
   312  		return err
   313  	} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) {
   314  		return structs.ErrPermissionDenied
   315  	}
   316  
   317  	// Setup the blocking query
   318  	opts := blockingOptions{
   319  		queryOpts: &args.QueryOptions,
   320  		queryMeta: &reply.QueryMeta,
   321  		run: func(ws memdb.WatchSet, state *state.StateStore) error {
   322  			// Capture all the allocations
   323  			allocs, err := state.AllocsByDeployment(ws, args.DeploymentID)
   324  			if err != nil {
   325  				return err
   326  			}
   327  
   328  			stubs := make([]*structs.AllocListStub, 0, len(allocs))
   329  			for _, alloc := range allocs {
   330  				stubs = append(stubs, alloc.Stub())
   331  			}
   332  			reply.Allocations = stubs
   333  
   334  			// Use the last index that affected the jobs table
   335  			index, err := state.Index("allocs")
   336  			if err != nil {
   337  				return err
   338  			}
   339  			reply.Index = index
   340  
   341  			// Set the query response
   342  			d.srv.setQueryMeta(&reply.QueryMeta)
   343  			return nil
   344  		}}
   345  	return d.srv.blockingRPC(&opts)
   346  }
   347  
   348  // Reap is used to cleanup terminal deployments
   349  func (d *Deployment) Reap(args *structs.DeploymentDeleteRequest,
   350  	reply *structs.GenericResponse) error {
   351  	if done, err := d.srv.forward("Deployment.Reap", args, args, reply); done {
   352  		return err
   353  	}
   354  	defer metrics.MeasureSince([]string{"nomad", "deployment", "reap"}, time.Now())
   355  
   356  	// Update via Raft
   357  	_, index, err := d.srv.raftApply(structs.DeploymentDeleteRequestType, args)
   358  	if err != nil {
   359  		return err
   360  	}
   361  
   362  	// Update the index
   363  	reply.Index = index
   364  	return nil
   365  }