github.com/bigcommerce/nomad@v0.9.3-bc/nomad/deployment_endpoint.go (about)

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