github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/client/allocrunner/checks_hook_test.go (about)

     1  package allocrunner
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"net/http"
     7  	"net/http/httptest"
     8  	"strings"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/hashicorp/go-hclog"
    13  	"github.com/hashicorp/nomad/ci"
    14  	"github.com/hashicorp/nomad/client/allocrunner/interfaces"
    15  	"github.com/hashicorp/nomad/client/serviceregistration/checks/checkstore"
    16  	"github.com/hashicorp/nomad/client/state"
    17  	"github.com/hashicorp/nomad/helper/testlog"
    18  	"github.com/hashicorp/nomad/nomad/mock"
    19  	"github.com/hashicorp/nomad/nomad/structs"
    20  	"github.com/hashicorp/nomad/testutil"
    21  	"github.com/shoenig/test/must"
    22  )
    23  
    24  var (
    25  	_ interfaces.RunnerPrerunHook  = (*checksHook)(nil)
    26  	_ interfaces.RunnerUpdateHook  = (*checksHook)(nil)
    27  	_ interfaces.RunnerPreKillHook = (*checksHook)(nil)
    28  )
    29  
    30  func makeCheckStore(logger hclog.Logger) checkstore.Shim {
    31  	db := state.NewMemDB(logger)
    32  	checkStore := checkstore.NewStore(logger, db)
    33  	return checkStore
    34  }
    35  
    36  func allocWithNomadChecks(addr, port string, onGroup bool) *structs.Allocation {
    37  	alloc := mock.Alloc()
    38  	group := alloc.Job.LookupTaskGroup(alloc.TaskGroup)
    39  
    40  	task := "task-one"
    41  	if onGroup {
    42  		task = ""
    43  	}
    44  
    45  	services := []*structs.Service{
    46  		{
    47  			Name:        "service-one",
    48  			TaskName:    "web",
    49  			PortLabel:   port,
    50  			AddressMode: "auto",
    51  			Address:     addr,
    52  			Provider:    "nomad",
    53  			Checks: []*structs.ServiceCheck{
    54  				{
    55  					Name:        "check-ok",
    56  					Type:        "http",
    57  					Path:        "/",
    58  					Protocol:    "http",
    59  					PortLabel:   port,
    60  					AddressMode: "auto",
    61  					Interval:    250 * time.Millisecond,
    62  					Timeout:     1 * time.Second,
    63  					Method:      "GET",
    64  					TaskName:    task,
    65  				},
    66  				{
    67  					Name:        "check-error",
    68  					Type:        "http",
    69  					Path:        "/fail",
    70  					Protocol:    "http",
    71  					PortLabel:   port,
    72  					AddressMode: "auto",
    73  					Interval:    250 * time.Millisecond,
    74  					Timeout:     1 * time.Second,
    75  					Method:      "GET",
    76  					TaskName:    task,
    77  				},
    78  				{
    79  					Name:        "check-hang",
    80  					Type:        "http",
    81  					Path:        "/hang",
    82  					Protocol:    "http",
    83  					PortLabel:   port,
    84  					AddressMode: "auto",
    85  					Interval:    250 * time.Millisecond,
    86  					Timeout:     500 * time.Millisecond,
    87  					Method:      "GET",
    88  					TaskName:    task,
    89  				},
    90  			},
    91  		},
    92  	}
    93  
    94  	switch onGroup {
    95  	case true:
    96  		group.Tasks[0].Services = nil
    97  		group.Services = services
    98  	case false:
    99  		group.Services = nil
   100  		group.Tasks[0].Services = services
   101  	}
   102  	return alloc
   103  }
   104  
   105  func allocWithDifferentNomadChecks(id, addr, port string) *structs.Allocation {
   106  	alloc := allocWithNomadChecks(addr, port, true)
   107  	alloc.ID = id
   108  	group := alloc.Job.LookupTaskGroup(alloc.TaskGroup)
   109  
   110  	group.Services[0].Checks[2].Path = "/" // the hanging check is now ok
   111  
   112  	// append 4th check, this one is failing
   113  	group.Services[0].Checks = append(group.Services[0].Checks, &structs.ServiceCheck{
   114  		Name:        "check-error-2",
   115  		Type:        "http",
   116  		Path:        "/fail",
   117  		Protocol:    "http",
   118  		PortLabel:   port,
   119  		AddressMode: "auto",
   120  		Interval:    250 * time.Millisecond,
   121  		Timeout:     1 * time.Second,
   122  		Method:      "GET",
   123  	})
   124  	return alloc
   125  }
   126  
   127  var checkHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   128  	switch r.URL.Path {
   129  	case "/fail":
   130  		w.WriteHeader(500)
   131  		_, _ = io.WriteString(w, "500 problem")
   132  	case "/hang":
   133  		time.Sleep(2 * time.Second)
   134  		_, _ = io.WriteString(w, "too slow")
   135  	default:
   136  		w.WriteHeader(200)
   137  		_, _ = io.WriteString(w, "200 ok")
   138  	}
   139  })
   140  
   141  func TestCheckHook_Checks_ResultsSet(t *testing.T) {
   142  	ci.Parallel(t)
   143  
   144  	logger := testlog.HCLogger(t)
   145  
   146  	// create an http server with various responses
   147  	ts := httptest.NewServer(checkHandler)
   148  	defer ts.Close()
   149  
   150  	cases := []struct {
   151  		name    string
   152  		onGroup bool
   153  	}{
   154  		{name: "group-level", onGroup: true},
   155  		{name: "task-level", onGroup: false},
   156  	}
   157  
   158  	for _, tc := range cases {
   159  		checkStore := makeCheckStore(logger)
   160  
   161  		// get the address and port for http server
   162  		tokens := strings.Split(ts.URL, ":")
   163  		addr, port := strings.TrimPrefix(tokens[1], "//"), tokens[2]
   164  
   165  		network := mock.NewNetworkStatus(addr)
   166  
   167  		alloc := allocWithNomadChecks(addr, port, tc.onGroup)
   168  
   169  		h := newChecksHook(logger, alloc, checkStore, network)
   170  
   171  		// initialize is called; observers are created but not started yet
   172  		must.MapEmpty(t, h.observers)
   173  
   174  		// calling pre-run starts the observers
   175  		err := h.Prerun()
   176  		must.NoError(t, err)
   177  
   178  		testutil.WaitForResultUntil(
   179  			2*time.Second,
   180  			func() (bool, error) {
   181  				results := checkStore.List(alloc.ID)
   182  				passing, failing, pending := 0, 0, 0
   183  				for _, result := range results {
   184  					switch result.Status {
   185  					case structs.CheckSuccess:
   186  						passing++
   187  					case structs.CheckFailure:
   188  						failing++
   189  					case structs.CheckPending:
   190  						pending++
   191  					}
   192  				}
   193  				if passing != 1 || failing != 2 || pending != 0 {
   194  					fmt.Printf("results %v\n", results)
   195  					return false, fmt.Errorf(
   196  						"expected 1 passing, 2 failing, 0 pending, got %d passing, %d failing, %d pending",
   197  						passing, failing, pending,
   198  					)
   199  				}
   200  				return true, nil
   201  			},
   202  			func(err error) {
   203  				t.Fatalf(err.Error())
   204  			},
   205  		)
   206  
   207  		h.PreKill() // stop observers, cleanup
   208  
   209  		// assert shim no longer contains results for the alloc
   210  		results := checkStore.List(alloc.ID)
   211  		must.MapEmpty(t, results)
   212  	}
   213  }
   214  
   215  func TestCheckHook_Checks_UpdateSet(t *testing.T) {
   216  	ci.Parallel(t)
   217  
   218  	logger := testlog.HCLogger(t)
   219  
   220  	// create an http server with various responses
   221  	ts := httptest.NewServer(checkHandler)
   222  	defer ts.Close()
   223  
   224  	// get the address and port for http server
   225  	tokens := strings.Split(ts.URL, ":")
   226  	addr, port := strings.TrimPrefix(tokens[1], "//"), tokens[2]
   227  
   228  	shim := makeCheckStore(logger)
   229  
   230  	network := mock.NewNetworkStatus(addr)
   231  
   232  	alloc := allocWithNomadChecks(addr, port, true)
   233  
   234  	h := newChecksHook(logger, alloc, shim, network)
   235  
   236  	// calling pre-run starts the observers
   237  	err := h.Prerun()
   238  	must.NoError(t, err)
   239  
   240  	// initial set of checks
   241  	testutil.WaitForResultUntil(
   242  		2*time.Second,
   243  		func() (bool, error) {
   244  			results := shim.List(alloc.ID)
   245  			passing, failing, pending := 0, 0, 0
   246  			for _, result := range results {
   247  				switch result.Status {
   248  				case structs.CheckSuccess:
   249  					passing++
   250  				case structs.CheckFailure:
   251  					failing++
   252  				case structs.CheckPending:
   253  					pending++
   254  				}
   255  			}
   256  			if passing != 1 || failing != 2 || pending != 0 {
   257  				fmt.Printf("results %v\n", results)
   258  				return false, fmt.Errorf(
   259  					"(initial set) expected 1 passing, 2 failing, 0 pending, got %d passing, %d failing, %d pending",
   260  					passing, failing, pending,
   261  				)
   262  			}
   263  			return true, nil
   264  		},
   265  		func(err error) {
   266  			t.Fatalf(err.Error())
   267  		},
   268  	)
   269  
   270  	request := &interfaces.RunnerUpdateRequest{
   271  		Alloc: allocWithDifferentNomadChecks(alloc.ID, addr, port),
   272  	}
   273  
   274  	err = h.Update(request)
   275  	must.NoError(t, err)
   276  
   277  	// updated set of checks
   278  	testutil.WaitForResultUntil(
   279  		2*time.Second,
   280  		func() (bool, error) {
   281  			results := shim.List(alloc.ID)
   282  			passing, failing, pending := 0, 0, 0
   283  			for _, result := range results {
   284  				switch result.Status {
   285  				case structs.CheckSuccess:
   286  					passing++
   287  				case structs.CheckFailure:
   288  					failing++
   289  				case structs.CheckPending:
   290  					pending++
   291  				}
   292  			}
   293  			if passing != 2 || failing != 2 || pending != 0 {
   294  				fmt.Printf("results %v\n", results)
   295  				return false, fmt.Errorf(
   296  					"(updated set) expected 2 passing, 2 failing, 0 pending, got %d passing, %d failing, %d pending",
   297  					passing, failing, pending,
   298  				)
   299  			}
   300  			return true, nil
   301  		},
   302  		func(err error) {
   303  			t.Fatalf(err.Error())
   304  		},
   305  	)
   306  
   307  	h.PreKill() // stop observers, cleanup
   308  
   309  	// assert shim no longer contains results for the alloc
   310  	results := shim.List(alloc.ID)
   311  	must.MapEmpty(t, results)
   312  }