github.com/hernad/nomad@v1.6.112/nomad/deploymentwatcher/testutil_test.go (about)

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