Add scaling-down scenario to integration test
This commit is contained in:
		
							parent
							
								
									3818e584ec
								
							
						
					
					
						commit
						4733edc20d
					
				|  | @ -19,18 +19,32 @@ import ( | ||||||
| 	actionsv1alpha1 "github.com/summerwind/actions-runner-controller/api/v1alpha1" | 	actionsv1alpha1 "github.com/summerwind/actions-runner-controller/api/v1alpha1" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | type testEnvironment struct { | ||||||
|  | 	Namespace *corev1.Namespace | ||||||
|  | 	Responses *fake.FixedResponses | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	workflowRunsFor3Replicas = `{"total_count": 5, "workflow_runs":[{"status":"queued"}, {"status":"queued"}, {"status":"in_progress"}, {"status":"in_progress"}, {"status":"completed"}]}"` | ||||||
|  | 	workflowRunsFor1Replicas = `{"total_count": 6, "workflow_runs":[{"status":"queued"}, {"status":"completed"}, {"status":"completed"}, {"status":"completed"}, {"status":"completed"}]}"` | ||||||
|  | ) | ||||||
|  | 
 | ||||||
| // SetupIntegrationTest will set up a testing environment.
 | // SetupIntegrationTest will set up a testing environment.
 | ||||||
| // This includes:
 | // This includes:
 | ||||||
| // * creating a Namespace to be used during the test
 | // * creating a Namespace to be used during the test
 | ||||||
| // * starting all the reconcilers
 | // * starting all the reconcilers
 | ||||||
| // * stopping all the reconcilers after the test ends
 | // * stopping all the reconcilers after the test ends
 | ||||||
| // Call this function at the start of each of your tests.
 | // Call this function at the start of each of your tests.
 | ||||||
| func SetupIntegrationTest(ctx context.Context) *corev1.Namespace { | func SetupIntegrationTest(ctx context.Context) *testEnvironment { | ||||||
| 	var stopCh chan struct{} | 	var stopCh chan struct{} | ||||||
| 	ns := &corev1.Namespace{} | 	ns := &corev1.Namespace{} | ||||||
| 
 | 
 | ||||||
| 	workflowRuns := `{"total_count": 5, "workflow_runs":[{"status":"queued"}, {"status":"queued"}, {"status":"in_progress"}, {"status":"in_progress"}, {"status":"completed"}]}"` | 	responses := &fake.FixedResponses{} | ||||||
| 	server := fake.NewServer(fake.WithListRepositoryWorkflowRunsResponse(200, workflowRuns)) | 	responses.ListRepositoryWorkflowRuns = &fake.Handler{ | ||||||
|  | 		Status: 200, | ||||||
|  | 		Body:   workflowRunsFor3Replicas, | ||||||
|  | 	} | ||||||
|  | 	server := fake.NewServer(fake.WithFixedResponses(responses)) | ||||||
| 
 | 
 | ||||||
| 	BeforeEach(func() { | 	BeforeEach(func() { | ||||||
| 		stopCh = make(chan struct{}) | 		stopCh = make(chan struct{}) | ||||||
|  | @ -91,12 +105,14 @@ func SetupIntegrationTest(ctx context.Context) *corev1.Namespace { | ||||||
| 		Expect(err).NotTo(HaveOccurred(), "failed to delete test namespace") | 		Expect(err).NotTo(HaveOccurred(), "failed to delete test namespace") | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	return ns | 	return &testEnvironment{Namespace: ns, Responses: responses} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var _ = Context("Inside of a new namespace", func() { | var _ = Context("Inside of a new namespace", func() { | ||||||
| 	ctx := context.TODO() | 	ctx := context.TODO() | ||||||
| 	ns := SetupIntegrationTest(ctx) | 	env := SetupIntegrationTest(ctx) | ||||||
|  | 	ns := env.Namespace | ||||||
|  | 	responses := env.Responses | ||||||
| 
 | 
 | ||||||
| 	Describe("when no existing resources exist", func() { | 	Describe("when no existing resources exist", func() { | ||||||
| 
 | 
 | ||||||
|  | @ -199,8 +215,9 @@ var _ = Context("Inside of a new namespace", func() { | ||||||
| 					time.Second*5, time.Millisecond*500).Should(BeEquivalentTo(2)) | 					time.Second*5, time.Millisecond*500).Should(BeEquivalentTo(2)) | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  | 			// Scale-up to 3 replicas
 | ||||||
| 			{ | 			{ | ||||||
| 				rs := &actionsv1alpha1.HorizontalRunnerAutoscaler{ | 				hra := &actionsv1alpha1.HorizontalRunnerAutoscaler{ | ||||||
| 					ObjectMeta: metav1.ObjectMeta{ | 					ObjectMeta: metav1.ObjectMeta{ | ||||||
| 						Name:      name, | 						Name:      name, | ||||||
| 						Namespace: ns.Name, | 						Namespace: ns.Name, | ||||||
|  | @ -216,9 +233,9 @@ var _ = Context("Inside of a new namespace", func() { | ||||||
| 					}, | 					}, | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				err := k8sClient.Create(ctx, rs) | 				err := k8sClient.Create(ctx, hra) | ||||||
| 
 | 
 | ||||||
| 				Expect(err).NotTo(HaveOccurred(), "failed to create test RunnerDeployment resource") | 				Expect(err).NotTo(HaveOccurred(), "failed to create test HorizontalRunnerAutoscaler resource") | ||||||
| 
 | 
 | ||||||
| 				runnerSets := actionsv1alpha1.RunnerReplicaSetList{Items: []actionsv1alpha1.RunnerReplicaSet{}} | 				runnerSets := actionsv1alpha1.RunnerReplicaSetList{Items: []actionsv1alpha1.RunnerReplicaSet{}} | ||||||
| 
 | 
 | ||||||
|  | @ -249,6 +266,43 @@ var _ = Context("Inside of a new namespace", func() { | ||||||
| 					}, | 					}, | ||||||
| 					time.Second*5, time.Millisecond*500).Should(BeEquivalentTo(3)) | 					time.Second*5, time.Millisecond*500).Should(BeEquivalentTo(3)) | ||||||
| 			} | 			} | ||||||
|  | 
 | ||||||
|  | 			// Scale-down to 1 replica
 | ||||||
|  | 			{ | ||||||
|  | 				responses.ListRepositoryWorkflowRuns.Body = workflowRunsFor1Replicas | ||||||
|  | 
 | ||||||
|  | 				var hra actionsv1alpha1.HorizontalRunnerAutoscaler | ||||||
|  | 
 | ||||||
|  | 				err := k8sClient.Get(ctx, types.NamespacedName{Namespace: ns.Name, Name: name}, &hra) | ||||||
|  | 
 | ||||||
|  | 				Expect(err).NotTo(HaveOccurred(), "failed to get test HorizontalRunnerAutoscaler resource") | ||||||
|  | 
 | ||||||
|  | 				hra.Annotations = map[string]string{ | ||||||
|  | 					"force-update": "1", | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				err = k8sClient.Update(ctx, &hra) | ||||||
|  | 
 | ||||||
|  | 				Expect(err).NotTo(HaveOccurred(), "failed to get test HorizontalRunnerAutoscaler resource") | ||||||
|  | 
 | ||||||
|  | 				Eventually( | ||||||
|  | 					func() int { | ||||||
|  | 						var runnerSets actionsv1alpha1.RunnerReplicaSetList | ||||||
|  | 
 | ||||||
|  | 						err := k8sClient.List(ctx, &runnerSets, client.InNamespace(ns.Name)) | ||||||
|  | 						if err != nil { | ||||||
|  | 							logf.Log.Error(err, "list runner sets") | ||||||
|  | 						} | ||||||
|  | 
 | ||||||
|  | 						if len(runnerSets.Items) == 0 { | ||||||
|  | 							logf.Log.Info("No runnerreplicasets exist yet") | ||||||
|  | 							return -1 | ||||||
|  | 						} | ||||||
|  | 
 | ||||||
|  | 						return *runnerSets.Items[0].Spec.Replicas | ||||||
|  | 					}, | ||||||
|  | 					time.Second*5, time.Millisecond*500).Should(BeEquivalentTo(1)) | ||||||
|  | 			} | ||||||
| 		}) | 		}) | ||||||
| 	}) | 	}) | ||||||
| }) | }) | ||||||
|  |  | ||||||
|  | @ -21,111 +21,116 @@ const ( | ||||||
| ` | ` | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type handler struct { | type Handler struct { | ||||||
| 	Status int | 	Status int | ||||||
| 	Body   string | 	Body   string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (h *handler) ServeHTTP(w http.ResponseWriter, req *http.Request) { | func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) { | ||||||
| 	w.WriteHeader(h.Status) | 	w.WriteHeader(h.Status) | ||||||
| 	fmt.Fprintf(w, h.Body) | 	fmt.Fprintf(w, h.Body) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | type ServerConfig struct { | ||||||
|  | 	*FixedResponses | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // NewServer creates a fake server for running unit tests
 | // NewServer creates a fake server for running unit tests
 | ||||||
| func NewServer(opts ...Option) *httptest.Server { | func NewServer(opts ...Option) *httptest.Server { | ||||||
| 	var responses FixedResponses | 	config := ServerConfig{ | ||||||
| 
 | 		FixedResponses: &FixedResponses{}, | ||||||
| 	for _, o := range opts { |  | ||||||
| 		o(&responses) |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	routes := map[string]handler{ | 	for _, o := range opts { | ||||||
|  | 		o(&config) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	routes := map[string]*Handler{ | ||||||
| 		// For CreateRegistrationToken
 | 		// For CreateRegistrationToken
 | ||||||
| 		"/repos/test/valid/actions/runners/registration-token": handler{ | 		"/repos/test/valid/actions/runners/registration-token": &Handler{ | ||||||
| 			Status: http.StatusCreated, | 			Status: http.StatusCreated, | ||||||
| 			Body:   fmt.Sprintf("{\"token\": \"%s\", \"expires_at\": \"%s\"}", RegistrationToken, time.Now().Add(time.Hour*1).Format(time.RFC3339)), | 			Body:   fmt.Sprintf("{\"token\": \"%s\", \"expires_at\": \"%s\"}", RegistrationToken, time.Now().Add(time.Hour*1).Format(time.RFC3339)), | ||||||
| 		}, | 		}, | ||||||
| 		"/repos/test/invalid/actions/runners/registration-token": handler{ | 		"/repos/test/invalid/actions/runners/registration-token": &Handler{ | ||||||
| 			Status: http.StatusOK, | 			Status: http.StatusOK, | ||||||
| 			Body:   fmt.Sprintf("{\"token\": \"%s\", \"expires_at\": \"%s\"}", RegistrationToken, time.Now().Add(time.Hour*1).Format(time.RFC3339)), | 			Body:   fmt.Sprintf("{\"token\": \"%s\", \"expires_at\": \"%s\"}", RegistrationToken, time.Now().Add(time.Hour*1).Format(time.RFC3339)), | ||||||
| 		}, | 		}, | ||||||
| 		"/repos/test/error/actions/runners/registration-token": handler{ | 		"/repos/test/error/actions/runners/registration-token": &Handler{ | ||||||
| 			Status: http.StatusBadRequest, | 			Status: http.StatusBadRequest, | ||||||
| 			Body:   "", | 			Body:   "", | ||||||
| 		}, | 		}, | ||||||
| 		"/orgs/test/actions/runners/registration-token": handler{ | 		"/orgs/test/actions/runners/registration-token": &Handler{ | ||||||
| 			Status: http.StatusCreated, | 			Status: http.StatusCreated, | ||||||
| 			Body:   fmt.Sprintf("{\"token\": \"%s\", \"expires_at\": \"%s\"}", RegistrationToken, time.Now().Add(time.Hour*1).Format(time.RFC3339)), | 			Body:   fmt.Sprintf("{\"token\": \"%s\", \"expires_at\": \"%s\"}", RegistrationToken, time.Now().Add(time.Hour*1).Format(time.RFC3339)), | ||||||
| 		}, | 		}, | ||||||
| 		"/orgs/invalid/actions/runners/registration-token": handler{ | 		"/orgs/invalid/actions/runners/registration-token": &Handler{ | ||||||
| 			Status: http.StatusOK, | 			Status: http.StatusOK, | ||||||
| 			Body:   fmt.Sprintf("{\"token\": \"%s\", \"expires_at\": \"%s\"}", RegistrationToken, time.Now().Add(time.Hour*1).Format(time.RFC3339)), | 			Body:   fmt.Sprintf("{\"token\": \"%s\", \"expires_at\": \"%s\"}", RegistrationToken, time.Now().Add(time.Hour*1).Format(time.RFC3339)), | ||||||
| 		}, | 		}, | ||||||
| 		"/orgs/error/actions/runners/registration-token": handler{ | 		"/orgs/error/actions/runners/registration-token": &Handler{ | ||||||
| 			Status: http.StatusBadRequest, | 			Status: http.StatusBadRequest, | ||||||
| 			Body:   "", | 			Body:   "", | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		// For ListRunners
 | 		// For ListRunners
 | ||||||
| 		"/repos/test/valid/actions/runners": handler{ | 		"/repos/test/valid/actions/runners": &Handler{ | ||||||
| 			Status: http.StatusOK, | 			Status: http.StatusOK, | ||||||
| 			Body:   RunnersListBody, | 			Body:   RunnersListBody, | ||||||
| 		}, | 		}, | ||||||
| 		"/repos/test/invalid/actions/runners": handler{ | 		"/repos/test/invalid/actions/runners": &Handler{ | ||||||
| 			Status: http.StatusNoContent, | 			Status: http.StatusNoContent, | ||||||
| 			Body:   "", | 			Body:   "", | ||||||
| 		}, | 		}, | ||||||
| 		"/repos/test/error/actions/runners": handler{ | 		"/repos/test/error/actions/runners": &Handler{ | ||||||
| 			Status: http.StatusBadRequest, | 			Status: http.StatusBadRequest, | ||||||
| 			Body:   "", | 			Body:   "", | ||||||
| 		}, | 		}, | ||||||
| 		"/orgs/test/actions/runners": handler{ | 		"/orgs/test/actions/runners": &Handler{ | ||||||
| 			Status: http.StatusOK, | 			Status: http.StatusOK, | ||||||
| 			Body:   RunnersListBody, | 			Body:   RunnersListBody, | ||||||
| 		}, | 		}, | ||||||
| 		"/orgs/invalid/actions/runners": handler{ | 		"/orgs/invalid/actions/runners": &Handler{ | ||||||
| 			Status: http.StatusNoContent, | 			Status: http.StatusNoContent, | ||||||
| 			Body:   "", | 			Body:   "", | ||||||
| 		}, | 		}, | ||||||
| 		"/orgs/error/actions/runners": handler{ | 		"/orgs/error/actions/runners": &Handler{ | ||||||
| 			Status: http.StatusBadRequest, | 			Status: http.StatusBadRequest, | ||||||
| 			Body:   "", | 			Body:   "", | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		// For RemoveRunner
 | 		// For RemoveRunner
 | ||||||
| 		"/repos/test/valid/actions/runners/1": handler{ | 		"/repos/test/valid/actions/runners/1": &Handler{ | ||||||
| 			Status: http.StatusNoContent, | 			Status: http.StatusNoContent, | ||||||
| 			Body:   "", | 			Body:   "", | ||||||
| 		}, | 		}, | ||||||
| 		"/repos/test/invalid/actions/runners/1": handler{ | 		"/repos/test/invalid/actions/runners/1": &Handler{ | ||||||
| 			Status: http.StatusOK, | 			Status: http.StatusOK, | ||||||
| 			Body:   "", | 			Body:   "", | ||||||
| 		}, | 		}, | ||||||
| 		"/repos/test/error/actions/runners/1": handler{ | 		"/repos/test/error/actions/runners/1": &Handler{ | ||||||
| 			Status: http.StatusBadRequest, | 			Status: http.StatusBadRequest, | ||||||
| 			Body:   "", | 			Body:   "", | ||||||
| 		}, | 		}, | ||||||
| 		"/orgs/test/actions/runners/1": handler{ | 		"/orgs/test/actions/runners/1": &Handler{ | ||||||
| 			Status: http.StatusNoContent, | 			Status: http.StatusNoContent, | ||||||
| 			Body:   "", | 			Body:   "", | ||||||
| 		}, | 		}, | ||||||
| 		"/orgs/invalid/actions/runners/1": handler{ | 		"/orgs/invalid/actions/runners/1": &Handler{ | ||||||
| 			Status: http.StatusOK, | 			Status: http.StatusOK, | ||||||
| 			Body:   "", | 			Body:   "", | ||||||
| 		}, | 		}, | ||||||
| 		"/orgs/error/actions/runners/1": handler{ | 		"/orgs/error/actions/runners/1": &Handler{ | ||||||
| 			Status: http.StatusBadRequest, | 			Status: http.StatusBadRequest, | ||||||
| 			Body:   "", | 			Body:   "", | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		// For auto-scaling based on the number of queued(pending) workflow runs
 | 		// For auto-scaling based on the number of queued(pending) workflow runs
 | ||||||
| 		"/repos/test/valid/actions/runs": responses.listRepositoryWorkflowRuns.handler(), | 		"/repos/test/valid/actions/runs": config.FixedResponses.ListRepositoryWorkflowRuns, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	mux := http.NewServeMux() | 	mux := http.NewServeMux() | ||||||
| 	for path, handler := range routes { | 	for path, handler := range routes { | ||||||
| 		h := handler | 		mux.Handle(path, handler) | ||||||
| 		mux.Handle(path, &h) |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return httptest.NewServer(mux) | 	return httptest.NewServer(mux) | ||||||
|  |  | ||||||
|  | @ -1,28 +1,22 @@ | ||||||
| package fake | package fake | ||||||
| 
 | 
 | ||||||
| type FixedResponses struct { | type FixedResponses struct { | ||||||
| 	listRepositoryWorkflowRuns FixedResponse | 	ListRepositoryWorkflowRuns *Handler | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type FixedResponse struct { | type Option func(*ServerConfig) | ||||||
| 	Status int |  | ||||||
| 	Body   string |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (r FixedResponse) handler() handler { |  | ||||||
| 	return handler{ |  | ||||||
| 		Status: r.Status, |  | ||||||
| 		Body:   r.Body, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type Option func(responses *FixedResponses) |  | ||||||
| 
 | 
 | ||||||
| func WithListRepositoryWorkflowRunsResponse(status int, body string) Option { | func WithListRepositoryWorkflowRunsResponse(status int, body string) Option { | ||||||
| 	return func(r *FixedResponses) { | 	return func(c *ServerConfig) { | ||||||
| 		r.listRepositoryWorkflowRuns = FixedResponse{ | 		c.FixedResponses.ListRepositoryWorkflowRuns = &Handler{ | ||||||
| 			Status: status, | 			Status: status, | ||||||
| 			Body:   body, | 			Body:   body, | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func WithFixedResponses(responses *FixedResponses) Option { | ||||||
|  | 	return func(c *ServerConfig) { | ||||||
|  | 		c.FixedResponses = responses | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue