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