github.com/hhrutter/nomad@v0.6.0-rc2.0.20170723054333-80c4b03f0705/nomad/deploymentwatcher/testutil_test.go (about)

     1  package deploymentwatcher
     2  
     3  import (
     4  	"log"
     5  	"os"
     6  	"reflect"
     7  	"strings"
     8  	"sync"
     9  	"testing"
    10  
    11  	memdb "github.com/hashicorp/go-memdb"
    12  	"github.com/hashicorp/nomad/nomad/state"
    13  	"github.com/hashicorp/nomad/nomad/structs"
    14  	mocker "github.com/stretchr/testify/mock"
    15  )
    16  
    17  func testLogger() *log.Logger {
    18  	return log.New(os.Stderr, "", log.LstdFlags|log.Lmicroseconds)
    19  }
    20  
    21  type mockBackend struct {
    22  	mocker.Mock
    23  	index uint64
    24  	state *state.StateStore
    25  	l     sync.Mutex
    26  }
    27  
    28  func newMockBackend(t *testing.T) *mockBackend {
    29  	state, err := state.NewStateStore(os.Stderr)
    30  	if err != nil {
    31  		t.Fatalf("err: %v", err)
    32  	}
    33  	if state == nil {
    34  		t.Fatalf("missing state")
    35  	}
    36  	return &mockBackend{
    37  		index: 10000,
    38  		state: state,
    39  	}
    40  }
    41  
    42  func (m *mockBackend) nextIndex() uint64 {
    43  	m.l.Lock()
    44  	defer m.l.Unlock()
    45  	i := m.index
    46  	m.index++
    47  	return i
    48  }
    49  
    50  func (m *mockBackend) UpsertEvals(evals []*structs.Evaluation) (uint64, error) {
    51  	m.Called(evals)
    52  	i := m.nextIndex()
    53  	return i, m.state.UpsertEvals(i, evals)
    54  }
    55  
    56  // matchUpsertEvals is used to match an upsert request
    57  func matchUpsertEvals(deploymentIDs []string) func(evals []*structs.Evaluation) bool {
    58  	return func(evals []*structs.Evaluation) bool {
    59  		if len(evals) != len(deploymentIDs) {
    60  			return false
    61  		}
    62  
    63  		dmap := make(map[string]struct{}, len(deploymentIDs))
    64  		for _, d := range deploymentIDs {
    65  			dmap[d] = struct{}{}
    66  		}
    67  
    68  		for _, e := range evals {
    69  			if _, ok := dmap[e.DeploymentID]; !ok {
    70  				return false
    71  			}
    72  
    73  			delete(dmap, e.DeploymentID)
    74  		}
    75  
    76  		return true
    77  	}
    78  }
    79  
    80  func (m *mockBackend) UpsertJob(job *structs.Job) (uint64, error) {
    81  	m.Called(job)
    82  	i := m.nextIndex()
    83  	return i, m.state.UpsertJob(i, job)
    84  }
    85  
    86  func (m *mockBackend) UpdateDeploymentStatus(u *structs.DeploymentStatusUpdateRequest) (uint64, error) {
    87  	m.Called(u)
    88  	i := m.nextIndex()
    89  	return i, m.state.UpdateDeploymentStatus(i, u)
    90  }
    91  
    92  // matchDeploymentStatusUpdateConfig is used to configure the matching
    93  // function
    94  type matchDeploymentStatusUpdateConfig struct {
    95  	// DeploymentID is the expected ID
    96  	DeploymentID string
    97  
    98  	// Status is the desired status
    99  	Status string
   100  
   101  	// StatusDescription is the desired status description
   102  	StatusDescription string
   103  
   104  	// JobVersion marks whether we expect a roll back job at the given version
   105  	JobVersion *uint64
   106  
   107  	// Eval marks whether we expect an evaluation.
   108  	Eval bool
   109  }
   110  
   111  // matchDeploymentStatusUpdateRequest is used to match an update request
   112  func matchDeploymentStatusUpdateRequest(c *matchDeploymentStatusUpdateConfig) func(args *structs.DeploymentStatusUpdateRequest) bool {
   113  	return func(args *structs.DeploymentStatusUpdateRequest) bool {
   114  		if args.DeploymentUpdate.DeploymentID != c.DeploymentID {
   115  			testLogger().Printf("deployment ids dont match")
   116  			return false
   117  		}
   118  
   119  		if args.DeploymentUpdate.Status != c.Status && args.DeploymentUpdate.StatusDescription != c.StatusDescription {
   120  			testLogger().Printf("status's dont match")
   121  			return false
   122  		}
   123  
   124  		if c.Eval && args.Eval == nil || !c.Eval && args.Eval != nil {
   125  			testLogger().Printf("evals dont match")
   126  			return false
   127  		}
   128  
   129  		if c.JobVersion != nil {
   130  			if args.Job == nil {
   131  				return false
   132  			} else if args.Job.Version != *c.JobVersion {
   133  				return false
   134  			}
   135  		} else if c.JobVersion == nil && args.Job != nil {
   136  			return false
   137  		}
   138  
   139  		return true
   140  	}
   141  }
   142  
   143  func (m *mockBackend) UpdateDeploymentPromotion(req *structs.ApplyDeploymentPromoteRequest) (uint64, error) {
   144  	m.Called(req)
   145  	i := m.nextIndex()
   146  	return i, m.state.UpdateDeploymentPromotion(i, req)
   147  }
   148  
   149  // matchDeploymentPromoteRequestConfig is used to configure the matching
   150  // function
   151  type matchDeploymentPromoteRequestConfig struct {
   152  	// Promotion holds the expected promote request
   153  	Promotion *structs.DeploymentPromoteRequest
   154  
   155  	// Eval marks whether we expect an evaluation.
   156  	Eval bool
   157  }
   158  
   159  // matchDeploymentPromoteRequest is used to match a promote request
   160  func matchDeploymentPromoteRequest(c *matchDeploymentPromoteRequestConfig) func(args *structs.ApplyDeploymentPromoteRequest) bool {
   161  	return func(args *structs.ApplyDeploymentPromoteRequest) bool {
   162  		if !reflect.DeepEqual(*c.Promotion, args.DeploymentPromoteRequest) {
   163  			return false
   164  		}
   165  
   166  		if c.Eval && args.Eval == nil || !c.Eval && args.Eval != nil {
   167  			return false
   168  		}
   169  
   170  		return true
   171  	}
   172  }
   173  func (m *mockBackend) UpdateDeploymentAllocHealth(req *structs.ApplyDeploymentAllocHealthRequest) (uint64, error) {
   174  	m.Called(req)
   175  	i := m.nextIndex()
   176  	return i, m.state.UpdateDeploymentAllocHealth(i, req)
   177  }
   178  
   179  // matchDeploymentAllocHealthRequestConfig is used to configure the matching
   180  // function
   181  type matchDeploymentAllocHealthRequestConfig struct {
   182  	// DeploymentID is the expected ID
   183  	DeploymentID string
   184  
   185  	// Healthy and Unhealthy contain the expected allocation IDs that are having
   186  	// their health set
   187  	Healthy, Unhealthy []string
   188  
   189  	// DeploymentUpdate holds the expected values of status and description. We
   190  	// don't check for exact match but string contains
   191  	DeploymentUpdate *structs.DeploymentStatusUpdate
   192  
   193  	// JobVersion marks whether we expect a roll back job at the given version
   194  	JobVersion *uint64
   195  
   196  	// Eval marks whether we expect an evaluation.
   197  	Eval bool
   198  }
   199  
   200  // matchDeploymentAllocHealthRequest is used to match an update request
   201  func matchDeploymentAllocHealthRequest(c *matchDeploymentAllocHealthRequestConfig) func(args *structs.ApplyDeploymentAllocHealthRequest) bool {
   202  	return func(args *structs.ApplyDeploymentAllocHealthRequest) bool {
   203  		if args.DeploymentID != c.DeploymentID {
   204  			return false
   205  		}
   206  
   207  		if len(c.Healthy) != len(args.HealthyAllocationIDs) {
   208  			return false
   209  		}
   210  		if len(c.Unhealthy) != len(args.UnhealthyAllocationIDs) {
   211  			return false
   212  		}
   213  
   214  		hmap, umap := make(map[string]struct{}, len(c.Healthy)), make(map[string]struct{}, len(c.Unhealthy))
   215  		for _, h := range c.Healthy {
   216  			hmap[h] = struct{}{}
   217  		}
   218  		for _, u := range c.Unhealthy {
   219  			umap[u] = struct{}{}
   220  		}
   221  
   222  		for _, h := range args.HealthyAllocationIDs {
   223  			if _, ok := hmap[h]; !ok {
   224  				return false
   225  			}
   226  		}
   227  		for _, u := range args.UnhealthyAllocationIDs {
   228  			if _, ok := umap[u]; !ok {
   229  				return false
   230  			}
   231  		}
   232  
   233  		if c.DeploymentUpdate != nil {
   234  			if args.DeploymentUpdate == nil {
   235  				return false
   236  			}
   237  
   238  			if !strings.Contains(args.DeploymentUpdate.Status, c.DeploymentUpdate.Status) {
   239  				return false
   240  			}
   241  			if !strings.Contains(args.DeploymentUpdate.StatusDescription, c.DeploymentUpdate.StatusDescription) {
   242  				return false
   243  			}
   244  		} else if args.DeploymentUpdate != nil {
   245  			return false
   246  		}
   247  
   248  		if c.Eval && args.Eval == nil || !c.Eval && args.Eval != nil {
   249  			return false
   250  		}
   251  
   252  		if (c.JobVersion != nil && (args.Job == nil || args.Job.Version != *c.JobVersion)) || c.JobVersion == nil && args.Job != nil {
   253  			return false
   254  		}
   255  
   256  		return true
   257  	}
   258  }
   259  
   260  func (m *mockBackend) Evaluations(args *structs.JobSpecificRequest, reply *structs.JobEvaluationsResponse) error {
   261  	rargs := m.Called(args, reply)
   262  	return rargs.Error(0)
   263  }
   264  
   265  func (m *mockBackend) evaluationsFromState(in mocker.Arguments) {
   266  	args, reply := in.Get(0).(*structs.JobSpecificRequest), in.Get(1).(*structs.JobEvaluationsResponse)
   267  	ws := memdb.NewWatchSet()
   268  	evals, _ := m.state.EvalsByJob(ws, args.JobID)
   269  	reply.Evaluations = evals
   270  	reply.Index, _ = m.state.Index("evals")
   271  }
   272  
   273  func (m *mockBackend) Allocations(args *structs.DeploymentSpecificRequest, reply *structs.AllocListResponse) error {
   274  	rargs := m.Called(args, reply)
   275  	return rargs.Error(0)
   276  }
   277  
   278  func (m *mockBackend) allocationsFromState(in mocker.Arguments) {
   279  	args, reply := in.Get(0).(*structs.DeploymentSpecificRequest), in.Get(1).(*structs.AllocListResponse)
   280  	ws := memdb.NewWatchSet()
   281  	allocs, _ := m.state.AllocsByDeployment(ws, args.DeploymentID)
   282  
   283  	var stubs []*structs.AllocListStub
   284  	for _, a := range allocs {
   285  		stubs = append(stubs, a.Stub())
   286  	}
   287  
   288  	reply.Allocations = stubs
   289  	reply.Index, _ = m.state.Index("allocs")
   290  }
   291  
   292  func (m *mockBackend) List(args *structs.DeploymentListRequest, reply *structs.DeploymentListResponse) error {
   293  	rargs := m.Called(args, reply)
   294  	return rargs.Error(0)
   295  }
   296  
   297  func (m *mockBackend) listFromState(in mocker.Arguments) {
   298  	reply := in.Get(1).(*structs.DeploymentListResponse)
   299  	ws := memdb.NewWatchSet()
   300  	iter, _ := m.state.Deployments(ws)
   301  
   302  	var deploys []*structs.Deployment
   303  	for {
   304  		raw := iter.Next()
   305  		if raw == nil {
   306  			break
   307  		}
   308  
   309  		deploys = append(deploys, raw.(*structs.Deployment))
   310  	}
   311  
   312  	reply.Deployments = deploys
   313  	reply.Index, _ = m.state.Index("deployment")
   314  }
   315  
   316  func (m *mockBackend) GetDeployment(args *structs.DeploymentSpecificRequest, reply *structs.SingleDeploymentResponse) error {
   317  	rargs := m.Called(args, reply)
   318  	return rargs.Error(0)
   319  }
   320  
   321  func (m *mockBackend) GetJobVersions(args *structs.JobVersionsRequest, reply *structs.JobVersionsResponse) error {
   322  	rargs := m.Called(args, reply)
   323  	return rargs.Error(0)
   324  }
   325  
   326  func (m *mockBackend) getJobVersionsFromState(in mocker.Arguments) {
   327  	args, reply := in.Get(0).(*structs.JobVersionsRequest), in.Get(1).(*structs.JobVersionsResponse)
   328  	ws := memdb.NewWatchSet()
   329  	versions, _ := m.state.JobVersionsByID(ws, args.JobID)
   330  	reply.Versions = versions
   331  	reply.Index, _ = m.state.Index("jobs")
   332  }
   333  
   334  func (m *mockBackend) GetJob(args *structs.JobSpecificRequest, reply *structs.SingleJobResponse) error {
   335  	rargs := m.Called(args, reply)
   336  	return rargs.Error(0)
   337  }
   338  
   339  func (m *mockBackend) getJobFromState(in mocker.Arguments) {
   340  	args, reply := in.Get(0).(*structs.JobSpecificRequest), in.Get(1).(*structs.SingleJobResponse)
   341  	ws := memdb.NewWatchSet()
   342  	job, _ := m.state.JobByID(ws, args.JobID)
   343  	reply.Job = job
   344  	reply.Index, _ = m.state.Index("jobs")
   345  }
   346  
   347  // matchDeploymentSpecificRequest is used to match that a deployment specific
   348  // request is for the passed deployment id
   349  func matchDeploymentSpecificRequest(dID string) func(args *structs.DeploymentSpecificRequest) bool {
   350  	return func(args *structs.DeploymentSpecificRequest) bool {
   351  		return args.DeploymentID == dID
   352  	}
   353  }
   354  
   355  // matchJobSpecificRequest is used to match that a job specific
   356  // request is for the passed job id
   357  func matchJobSpecificRequest(jID string) func(args *structs.JobSpecificRequest) bool {
   358  	return func(args *structs.JobSpecificRequest) bool {
   359  		return args.JobID == jID
   360  	}
   361  }
   362  
   363  // matchJobVersionsRequest is used to match that a job version
   364  // request is for the passed job id
   365  func matchJobVersionsRequest(jID string) func(args *structs.JobVersionsRequest) bool {
   366  	return func(args *structs.JobVersionsRequest) bool {
   367  		return args.JobID == jID
   368  	}
   369  }