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

     1  package deploymentwatcher
     2  
     3  import (
     4  	"reflect"
     5  	"strings"
     6  	"sync"
     7  	"testing"
     8  
     9  	"github.com/hashicorp/nomad/nomad/state"
    10  	"github.com/hashicorp/nomad/nomad/structs"
    11  	mocker "github.com/stretchr/testify/mock"
    12  )
    13  
    14  type mockBackend struct {
    15  	mocker.Mock
    16  	index uint64
    17  	state *state.StateStore
    18  	l     sync.Mutex
    19  }
    20  
    21  func newMockBackend(t *testing.T) *mockBackend {
    22  	return &mockBackend{
    23  		index: 10000,
    24  		state: state.TestStateStore(t),
    25  	}
    26  }
    27  
    28  func (m *mockBackend) nextIndex() uint64 {
    29  	m.l.Lock()
    30  	defer m.l.Unlock()
    31  	i := m.index
    32  	m.index++
    33  	return i
    34  }
    35  
    36  func (m *mockBackend) UpdateAllocDesiredTransition(u *structs.AllocUpdateDesiredTransitionRequest) (uint64, error) {
    37  	m.Called(u)
    38  	i := m.nextIndex()
    39  	return i, m.state.UpdateAllocsDesiredTransitions(i, u.Allocs, u.Evals)
    40  }
    41  
    42  // matchUpdateAllocDesiredTransitions is used to match an upsert request
    43  func matchUpdateAllocDesiredTransitions(deploymentIDs []string) func(update *structs.AllocUpdateDesiredTransitionRequest) bool {
    44  	return func(update *structs.AllocUpdateDesiredTransitionRequest) bool {
    45  		if len(update.Evals) != len(deploymentIDs) {
    46  			return false
    47  		}
    48  
    49  		dmap := make(map[string]struct{}, len(deploymentIDs))
    50  		for _, d := range deploymentIDs {
    51  			dmap[d] = struct{}{}
    52  		}
    53  
    54  		for _, e := range update.Evals {
    55  			if _, ok := dmap[e.DeploymentID]; !ok {
    56  				return false
    57  			}
    58  
    59  			delete(dmap, e.DeploymentID)
    60  		}
    61  
    62  		return true
    63  	}
    64  }
    65  
    66  // matchUpdateAllocDesiredTransitionReschedule is used to match allocs that have their DesiredTransition set to Reschedule
    67  func matchUpdateAllocDesiredTransitionReschedule(allocIDs []string) func(update *structs.AllocUpdateDesiredTransitionRequest) bool {
    68  	return func(update *structs.AllocUpdateDesiredTransitionRequest) bool {
    69  		amap := make(map[string]struct{}, len(allocIDs))
    70  		for _, d := range allocIDs {
    71  			amap[d] = struct{}{}
    72  		}
    73  
    74  		for allocID, dt := range update.Allocs {
    75  			if _, ok := amap[allocID]; !ok {
    76  				return false
    77  			}
    78  			if !*dt.Reschedule {
    79  				return false
    80  			}
    81  		}
    82  
    83  		return true
    84  	}
    85  }
    86  
    87  func (m *mockBackend) UpsertJob(job *structs.Job) (uint64, error) {
    88  	m.Called(job)
    89  	i := m.nextIndex()
    90  	return i, m.state.UpsertJob(i, job)
    91  }
    92  
    93  func (m *mockBackend) UpdateDeploymentStatus(u *structs.DeploymentStatusUpdateRequest) (uint64, error) {
    94  	m.Called(u)
    95  	i := m.nextIndex()
    96  	return i, m.state.UpdateDeploymentStatus(i, u)
    97  }
    98  
    99  // matchDeploymentStatusUpdateConfig is used to configure the matching
   100  // function
   101  type matchDeploymentStatusUpdateConfig struct {
   102  	// DeploymentID is the expected ID
   103  	DeploymentID string
   104  
   105  	// Status is the desired status
   106  	Status string
   107  
   108  	// StatusDescription is the desired status description
   109  	StatusDescription string
   110  
   111  	// JobVersion marks whether we expect a roll back job at the given version
   112  	JobVersion *uint64
   113  
   114  	// Eval marks whether we expect an evaluation.
   115  	Eval bool
   116  }
   117  
   118  // matchDeploymentStatusUpdateRequest is used to match an update request
   119  func matchDeploymentStatusUpdateRequest(c *matchDeploymentStatusUpdateConfig) func(args *structs.DeploymentStatusUpdateRequest) bool {
   120  	return func(args *structs.DeploymentStatusUpdateRequest) bool {
   121  		if args.DeploymentUpdate.DeploymentID != c.DeploymentID {
   122  			return false
   123  		}
   124  
   125  		if args.DeploymentUpdate.Status != c.Status && args.DeploymentUpdate.StatusDescription != c.StatusDescription {
   126  			return false
   127  		}
   128  
   129  		if c.Eval && args.Eval == nil || !c.Eval && args.Eval != nil {
   130  			return false
   131  		}
   132  
   133  		if c.JobVersion != nil {
   134  			if args.Job == nil {
   135  				return false
   136  			} else if args.Job.Version != *c.JobVersion {
   137  				return false
   138  			}
   139  		} else if c.JobVersion == nil && args.Job != nil {
   140  			return false
   141  		}
   142  
   143  		return true
   144  	}
   145  }
   146  
   147  func (m *mockBackend) UpdateDeploymentPromotion(req *structs.ApplyDeploymentPromoteRequest) (uint64, error) {
   148  	m.Called(req)
   149  	i := m.nextIndex()
   150  	return i, m.state.UpdateDeploymentPromotion(i, req)
   151  }
   152  
   153  // matchDeploymentPromoteRequestConfig is used to configure the matching
   154  // function
   155  type matchDeploymentPromoteRequestConfig struct {
   156  	// Promotion holds the expected promote request
   157  	Promotion *structs.DeploymentPromoteRequest
   158  
   159  	// Eval marks whether we expect an evaluation.
   160  	Eval bool
   161  }
   162  
   163  // matchDeploymentPromoteRequest is used to match a promote request
   164  func matchDeploymentPromoteRequest(c *matchDeploymentPromoteRequestConfig) func(args *structs.ApplyDeploymentPromoteRequest) bool {
   165  	return func(args *structs.ApplyDeploymentPromoteRequest) bool {
   166  		if !reflect.DeepEqual(*c.Promotion, args.DeploymentPromoteRequest) {
   167  			return false
   168  		}
   169  
   170  		if c.Eval && args.Eval == nil || !c.Eval && args.Eval != nil {
   171  			return false
   172  		}
   173  
   174  		return true
   175  	}
   176  }
   177  func (m *mockBackend) UpdateDeploymentAllocHealth(req *structs.ApplyDeploymentAllocHealthRequest) (uint64, error) {
   178  	m.Called(req)
   179  	i := m.nextIndex()
   180  	return i, m.state.UpdateDeploymentAllocHealth(i, req)
   181  }
   182  
   183  // matchDeploymentAllocHealthRequestConfig is used to configure the matching
   184  // function
   185  type matchDeploymentAllocHealthRequestConfig struct {
   186  	// DeploymentID is the expected ID
   187  	DeploymentID string
   188  
   189  	// Healthy and Unhealthy contain the expected allocation IDs that are having
   190  	// their health set
   191  	Healthy, Unhealthy []string
   192  
   193  	// DeploymentUpdate holds the expected values of status and description. We
   194  	// don't check for exact match but string contains
   195  	DeploymentUpdate *structs.DeploymentStatusUpdate
   196  
   197  	// JobVersion marks whether we expect a roll back job at the given version
   198  	JobVersion *uint64
   199  
   200  	// Eval marks whether we expect an evaluation.
   201  	Eval bool
   202  }
   203  
   204  // matchDeploymentAllocHealthRequest is used to match an update request
   205  func matchDeploymentAllocHealthRequest(c *matchDeploymentAllocHealthRequestConfig) func(args *structs.ApplyDeploymentAllocHealthRequest) bool {
   206  	return func(args *structs.ApplyDeploymentAllocHealthRequest) bool {
   207  		if args.DeploymentID != c.DeploymentID {
   208  			return false
   209  		}
   210  
   211  		// Require a timestamp
   212  		if args.Timestamp.IsZero() {
   213  			return false
   214  		}
   215  
   216  		if len(c.Healthy) != len(args.HealthyAllocationIDs) {
   217  			return false
   218  		}
   219  		if len(c.Unhealthy) != len(args.UnhealthyAllocationIDs) {
   220  			return false
   221  		}
   222  
   223  		hmap, umap := make(map[string]struct{}, len(c.Healthy)), make(map[string]struct{}, len(c.Unhealthy))
   224  		for _, h := range c.Healthy {
   225  			hmap[h] = struct{}{}
   226  		}
   227  		for _, u := range c.Unhealthy {
   228  			umap[u] = struct{}{}
   229  		}
   230  
   231  		for _, h := range args.HealthyAllocationIDs {
   232  			if _, ok := hmap[h]; !ok {
   233  				return false
   234  			}
   235  		}
   236  		for _, u := range args.UnhealthyAllocationIDs {
   237  			if _, ok := umap[u]; !ok {
   238  				return false
   239  			}
   240  		}
   241  
   242  		if c.DeploymentUpdate != nil {
   243  			if args.DeploymentUpdate == nil {
   244  				return false
   245  			}
   246  
   247  			if !strings.Contains(args.DeploymentUpdate.Status, c.DeploymentUpdate.Status) {
   248  				return false
   249  			}
   250  			if !strings.Contains(args.DeploymentUpdate.StatusDescription, c.DeploymentUpdate.StatusDescription) {
   251  				return false
   252  			}
   253  		} else if args.DeploymentUpdate != nil {
   254  			return false
   255  		}
   256  
   257  		if c.Eval && args.Eval == nil || !c.Eval && args.Eval != nil {
   258  			return false
   259  		}
   260  
   261  		if (c.JobVersion != nil && (args.Job == nil || args.Job.Version != *c.JobVersion)) || c.JobVersion == nil && args.Job != nil {
   262  			return false
   263  		}
   264  
   265  		return true
   266  	}
   267  }