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