func (dispatcher concreteActionDispatcher) Dispatch(req boshhandler.Request) (resp boshhandler.Response) { action, err := dispatcher.actionFactory.Create(req.Method) switch { case err != nil: resp = boshhandler.NewExceptionResponse("unknown message %s", req.Method) dispatcher.logger.Error("Action Dispatcher", "Unknown action %s", req.Method) case action.IsAsynchronous(): task := dispatcher.taskService.StartTask(func() (value interface{}, err error) { value, err = dispatcher.actionRunner.Run(action, req.GetPayload()) return }) resp = boshhandler.NewValueResponse(boshtask.TaskStateValue{ AgentTaskId: task.Id, State: task.State, }) default: value, err := dispatcher.actionRunner.Run(action, req.GetPayload()) if err != nil { err = bosherr.WrapError(err, "Action Failed %s", req.Method) resp = boshhandler.NewExceptionResponse(err.Error()) dispatcher.logger.Error("Action Dispatcher", err.Error()) return } resp = boshhandler.NewValueResponse(value) } return }
func (d *dummyNatsJobSupervisor) statusHandler(req boshhandler.Request) boshhandler.Response { switch req.Method { case "set_dummy_status": // Do not unmarshal message until determining its method var body map[string]string err := json.Unmarshal(req.GetPayload(), &body) if err != nil { return boshhandler.NewExceptionResponse(err.Error()) } d.status = body["status"] if d.status == "failing" && d.jobFailureHandler != nil { d.jobFailureHandler(boshalert.MonitAlert{ ID: "fake-monit-alert", Service: "fake-monit-service", Event: "failing", Action: "start", Date: "Sun, 22 May 2011 20:07:41 +0500", Description: "fake-monit-description", }) } return boshhandler.NewValueResponse("ok") default: return nil } }
func init() { Describe("Testing with Ginkgo", func() { It("run sets the dispatcher as message handler", func() { deps, agent := buildAgent() deps.actionDispatcher.DispatchResp = boshhandler.NewValueResponse("pong") err := agent.Run() assert.NoError(GinkgoT(), err) assert.True(GinkgoT(), deps.handler.ReceivedRun) req := boshhandler.NewRequest("reply to me!", "some action", []byte("some payload")) resp := deps.handler.Func(req) assert.Equal(GinkgoT(), deps.actionDispatcher.DispatchReq, req) assert.Equal(GinkgoT(), resp, deps.actionDispatcher.DispatchResp) }) It("run sets up heartbeats", func() { deps, agent := buildAgent() deps.platform.FakeVitalsService.GetVitals = boshvitals.Vitals{ Load: []string{"a", "b", "c"}, } err := agent.Run() assert.NoError(GinkgoT(), err) assert.False(GinkgoT(), deps.handler.TickHeartbeatsSent) assert.True(GinkgoT(), deps.handler.InitialHeartbeatSent) assert.Equal(GinkgoT(), "heartbeat", deps.handler.SendToHealthManagerTopic) time.Sleep(5 * time.Millisecond) assert.True(GinkgoT(), deps.handler.TickHeartbeatsSent) hb := deps.handler.SendToHealthManagerPayload.(boshmbus.Heartbeat) assert.Equal(GinkgoT(), deps.platform.FakeVitalsService.GetVitals, hb.Vitals) }) It("run sets the callback for job failures monitoring", func() { deps, agent := buildAgent() builtAlert := boshalert.Alert{Id: "some built alert id"} deps.alertBuilder.BuildAlert = builtAlert err := agent.Run() assert.NoError(GinkgoT(), err) assert.NotEqual(GinkgoT(), deps.handler.SendToHealthManagerTopic, "alert") failureAlert := boshalert.MonitAlert{Id: "some random id"} deps.jobSupervisor.OnJobFailure(failureAlert) assert.Equal(GinkgoT(), deps.alertBuilder.BuildInput, failureAlert) assert.Equal(GinkgoT(), deps.handler.SendToHealthManagerTopic, "alert") assert.Equal(GinkgoT(), deps.handler.SendToHealthManagerPayload, builtAlert) }) }) }
func (dispatcher concreteActionDispatcher) dispatchAsynchronousAction( action boshaction.Action, req boshhandler.Request, ) boshhandler.Response { dispatcher.logger.Info(actionDispatcherLogTag, "Running async action %s", req.Method) var task boshtask.Task var err error runTask := func() (interface{}, error) { return dispatcher.actionRunner.Run(action, req.GetPayload()) } cancelTask := func(_ boshtask.Task) error { return action.Cancel() } // Certain long-running tasks (e.g. configure_networks) must be resumed // after agent restart so that API consumers do not need to know // if agent is restarted midway through the task. if action.IsPersistent() { dispatcher.logger.Info(actionDispatcherLogTag, "Running persistent action %s", req.Method) task, err = dispatcher.taskService.CreateTask(runTask, cancelTask, dispatcher.removeTaskInfo) if err != nil { err = bosherr.WrapError(err, "Create Task Failed %s", req.Method) dispatcher.logger.Error(actionDispatcherLogTag, err.Error()) return boshhandler.NewExceptionResponse(err) } taskInfo := boshtask.TaskInfo{ TaskID: task.ID, Method: req.Method, Payload: req.GetPayload(), } err = dispatcher.taskManager.AddTaskInfo(taskInfo) if err != nil { err = bosherr.WrapError(err, "Action Failed %s", req.Method) dispatcher.logger.Error(actionDispatcherLogTag, err.Error()) return boshhandler.NewExceptionResponse(err) } } else { task, err = dispatcher.taskService.CreateTask(runTask, cancelTask, nil) if err != nil { err = bosherr.WrapError(err, "Create Task Failed %s", req.Method) dispatcher.logger.Error(actionDispatcherLogTag, err.Error()) return boshhandler.NewExceptionResponse(err) } } dispatcher.taskService.StartTask(task) return boshhandler.NewValueResponse(boshtask.TaskStateValue{ AgentTaskID: task.ID, State: task.State, }) }
func startServer() (serverURL string, handler HttpsHandler, fs *fakesys.FakeFileSystem) { serverURL = "https://*****:*****@127.0.0.1:6900" mbusUrl, _ := url.Parse(serverURL) logger := boshlog.NewLogger(boshlog.LEVEL_NONE) fs = fakesys.NewFakeFileSystem() dirProvider := boshdir.NewDirectoriesProvider("/var/vcap") handler = NewHttpsHandler(mbusUrl, logger, fs, dirProvider) go handler.Start(func(req boshhandler.Request) (resp boshhandler.Response) { receivedRequest = req return boshhandler.NewValueResponse("expected value") }) return }
func (dispatcher concreteActionDispatcher) dispatchSynchronousAction( action boshaction.Action, req boshhandler.Request, ) boshhandler.Response { dispatcher.logger.Info(actionDispatcherLogTag, "Running sync action %s", req.Method) value, err := dispatcher.actionRunner.Run(action, req.GetPayload()) if err != nil { err = bosherr.WrapError(err, "Action Failed %s", req.Method) dispatcher.logger.Error(actionDispatcherLogTag, err.Error()) return boshhandler.NewExceptionResponse(err) } return boshhandler.NewValueResponse(value) }
func TestRunSetsTheDispatcherAsMessageHandler(t *testing.T) { deps, agent := buildAgent() deps.actionDispatcher.DispatchResp = boshhandler.NewValueResponse("pong") err := agent.Run() assert.NoError(t, err) assert.True(t, deps.handler.ReceivedRun) req := boshhandler.NewRequest("reply to me!", "some action", []byte("some payload")) resp := deps.handler.Func(req) assert.Equal(t, deps.actionDispatcher.DispatchReq, req) assert.Equal(t, resp, deps.actionDispatcher.DispatchResp) }
func TestNatsHandlerStart(t *testing.T) { settings := &fakesettings.FakeSettingsService{ AgentId: "my-agent-id", MbusUrl: "nats://*****:*****@127.0.0.1:1234", } client, handler := buildNatsClientAndHandler(settings) var receivedRequest boshhandler.Request handler.Start(func(req boshhandler.Request) (resp boshhandler.Response) { receivedRequest = req return boshhandler.NewValueResponse("expected value") }) defer handler.Stop() // check connection assert.NotNil(t, client.ConnectedConnectionProvider) // check subscriptions assert.Equal(t, len(client.Subscriptions), 1) subscriptions := client.Subscriptions["agent.my-agent-id"] assert.Equal(t, len(subscriptions), 1) // test subscription callback expectedPayload := []byte(`{"method":"ping","arguments":["foo","bar"], "reply_to": "reply to me!"}`) subscription := client.Subscriptions["agent.my-agent-id"][0] subscription.Callback(&yagnats.Message{ Subject: "agent.my-agent-id", Payload: expectedPayload, }) // request received assert.Equal(t, receivedRequest, boshhandler.Request{ ReplyTo: "reply to me!", Method: "ping", Payload: expectedPayload, }) // response sent assert.Equal(t, len(client.PublishedMessages), 1) messages := client.PublishedMessages["reply to me!"] assert.Equal(t, len(messages), 1) message := messages[0] assert.Equal(t, []byte(`{"value":"expected value"}`), message.Payload) }
func TestDispatchHandlesSynchronousAction(t *testing.T) { logger, taskService, actionFactory, actionRunner := getActionDispatcherDependencies() // when action is successful actionFactory.CreateAction = &fakeaction.TestAction{ Asynchronous: false, } actionRunner.RunValue = "some value" dispatcher := NewActionDispatcher(logger, taskService, actionFactory, actionRunner) req := boshhandler.NewRequest("reply to me!", "some action", []byte("some payload")) resp := dispatcher.Dispatch(req) assert.Equal(t, req.Method, actionFactory.CreateMethod) assert.Equal(t, req.GetPayload(), actionRunner.RunPayload) assert.Equal(t, boshhandler.NewValueResponse("some value"), resp) }
func init() { Describe("Agent", func() { var ( agent Agent logger boshlog.Logger handler *fakembus.FakeHandler platform *fakeplatform.FakePlatform actionDispatcher *FakeActionDispatcher alertBuilder *fakealert.FakeAlertBuilder alertSender AlertSender jobSupervisor *fakejobsuper.FakeJobSupervisor specService *fakeas.FakeV1Service ) BeforeEach(func() { logger = boshlog.NewLogger(boshlog.LevelDebug) handler = &fakembus.FakeHandler{} platform = fakeplatform.NewFakePlatform() actionDispatcher = &FakeActionDispatcher{} alertBuilder = fakealert.NewFakeAlertBuilder() alertSender = NewAlertSender(handler, alertBuilder) jobSupervisor = fakejobsuper.NewFakeJobSupervisor() specService = fakeas.NewFakeV1Service() agent = New(logger, handler, platform, actionDispatcher, alertSender, jobSupervisor, specService, 5*time.Millisecond) }) Describe("Run", func() { It("lets dispatcher handle requests arriving via handler", func() { err := agent.Run() Expect(err).ToNot(HaveOccurred()) expectedResp := boshhandler.NewValueResponse("pong") actionDispatcher.DispatchResp = expectedResp req := boshhandler.NewRequest("fake-reply", "fake-action", []byte("fake-payload")) resp := handler.RunFunc(req) Expect(actionDispatcher.DispatchReq).To(Equal(req)) Expect(resp).To(Equal(expectedResp)) }) It("resumes persistent actions *before* dispatching new requests", func() { resumedBeforeStartingToDispatch := false handler.RunCallBack = func() { resumedBeforeStartingToDispatch = actionDispatcher.ResumedPreviouslyDispatchedTasks } err := agent.Run() Expect(err).ToNot(HaveOccurred()) Expect(resumedBeforeStartingToDispatch).To(BeTrue()) }) Context("when heartbeats can be sent", func() { BeforeEach(func() { handler.KeepOnRunning() }) BeforeEach(func() { jobName := "fake-job" jobIndex := 1 specService.Spec = boshas.V1ApplySpec{ JobSpec: boshas.JobSpec{Name: &jobName}, Index: &jobIndex, } jobSupervisor.StatusStatus = "fake-state" platform.FakeVitalsService.GetVitals = boshvitals.Vitals{ Load: []string{"a", "b", "c"}, } }) expectedJobName := "fake-job" expectedJobIndex := 1 expectedHb := boshmbus.Heartbeat{ Job: &expectedJobName, Index: &expectedJobIndex, JobState: "fake-state", Vitals: boshvitals.Vitals{Load: []string{"a", "b", "c"}}, } It("sends initial heartbeat", func() { // Configure periodic heartbeat every 5 hours // so that we are sure that we will not receive it agent = New(logger, handler, platform, actionDispatcher, alertSender, jobSupervisor, specService, 5*time.Hour) // Immediately exit after sending initial heartbeat handler.SendToHealthManagerErr = errors.New("stop") err := agent.Run() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("stop")) Expect(handler.HMRequests()).To(Equal([]fakembus.HMRequest{ fakembus.HMRequest{Topic: "heartbeat", Payload: expectedHb}, })) }) It("sends periodic heartbeats", func() { sentRequests := 0 handler.SendToHealthManagerCallBack = func(_ fakembus.HMRequest) { sentRequests++ if sentRequests == 3 { handler.SendToHealthManagerErr = errors.New("stop") } } err := agent.Run() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("stop")) Expect(handler.HMRequests()).To(Equal([]fakembus.HMRequest{ fakembus.HMRequest{Topic: "heartbeat", Payload: expectedHb}, fakembus.HMRequest{Topic: "heartbeat", Payload: expectedHb}, fakembus.HMRequest{Topic: "heartbeat", Payload: expectedHb}, })) }) }) Context("when the agent fails to get job spec for a heartbeat", func() { BeforeEach(func() { specService.GetErr = errors.New("fake-spec-service-error") handler.KeepOnRunning() }) It("returns the error", func() { err := agent.Run() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-spec-service-error")) }) }) Context("when the agent fails to get vitals for a heartbeat", func() { BeforeEach(func() { platform.FakeVitalsService.GetErr = errors.New("fake-vitals-service-error") handler.KeepOnRunning() }) It("returns the error", func() { err := agent.Run() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-vitals-service-error")) }) }) It("sends job failure alerts to health manager", func() { handler.KeepOnRunning() failureAlert := boshalert.MonitAlert{ID: "fake-monit-alert"} jobSupervisor.JobFailureAlert = &failureAlert builtAlert := boshalert.Alert{ID: "fake-built-alert"} alertBuilder.BuildAlert = builtAlert // Immediately exit from Run() after alert is sent handler.SendToHealthManagerCallBack = func(hmRequest fakembus.HMRequest) { if hmRequest.Topic == "alert" { handler.SendToHealthManagerErr = errors.New("stop") } } err := agent.Run() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("stop")) Expect(alertBuilder.BuildInput).To(Equal(failureAlert)) // Check for inclusion because heartbeats might have been received Expect(handler.HMRequests()).To(ContainElement( fakembus.HMRequest{Topic: "alert", Payload: builtAlert}, )) }) }) }) }
fs *fakesys.FakeFileSystem receivedRequest boshhandler.Request httpClient http.Client ) BeforeEach(func() { serverURL = "https://*****:*****@127.0.0.1:6900" mbusURL, _ := url.Parse(serverURL) logger := boshlog.NewLogger(boshlog.LevelNone) fs = fakesys.NewFakeFileSystem() dirProvider := boshdir.NewDirectoriesProvider("/var/vcap") handler = NewHTTPSHandler(mbusURL, logger, fs, dirProvider) go handler.Start(func(req boshhandler.Request) (resp boshhandler.Response) { receivedRequest = req return boshhandler.NewValueResponse("expected value") }) httpTransport := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} httpClient = http.Client{Transport: httpTransport} }) AfterEach(func() { handler.Stop() time.Sleep(1 * time.Millisecond) }) Describe("POST /agent", func() { It("receives request and responds", func() { postBody := `{"method":"ping","arguments":["foo","bar"], "reply_to": "reply to me!"}` postPayload := strings.NewReader(postBody)
func init() { Describe("Testing with Ginkgo", func() { It("nats handler start", func() { settings := &fakesettings.FakeSettingsService{ AgentId: "my-agent-id", MbusUrl: "nats://*****:*****@127.0.0.1:1234", } client, handler := buildNatsClientAndHandler(settings) var receivedRequest boshhandler.Request handler.Start(func(req boshhandler.Request) (resp boshhandler.Response) { receivedRequest = req return boshhandler.NewValueResponse("expected value") }) defer handler.Stop() Expect(client.ConnectedConnectionProvider).ToNot(BeNil()) Expect(len(client.Subscriptions)).To(Equal(1)) subscriptions := client.Subscriptions["agent.my-agent-id"] Expect(len(subscriptions)).To(Equal(1)) expectedPayload := []byte(`{"method":"ping","arguments":["foo","bar"], "reply_to": "reply to me!"}`) subscription := client.Subscriptions["agent.my-agent-id"][0] subscription.Callback(&yagnats.Message{ Subject: "agent.my-agent-id", Payload: expectedPayload, }) assert.Equal(GinkgoT(), receivedRequest, boshhandler.Request{ ReplyTo: "reply to me!", Method: "ping", Payload: expectedPayload, }) Expect(len(client.PublishedMessages)).To(Equal(1)) messages := client.PublishedMessages["reply to me!"] Expect(len(messages)).To(Equal(1)) message := messages[0] Expect([]byte(`{"value":"expected value"}`)).To(Equal(message.Payload)) }) It("nats send periodic heartbeat", func() { settings := &fakesettings.FakeSettingsService{ AgentId: "my-agent-id", MbusUrl: "nats://*****:*****@127.0.0.1:1234", } client, handler := buildNatsClientAndHandler(settings) errChan := make(chan error, 1) jobName := "foo" jobIndex := 0 expectedHeartbeat := Heartbeat{Job: &jobName, Index: &jobIndex} go func() { errChan <- handler.SendToHealthManager("heartbeat", expectedHeartbeat) }() var err error select { case err = <-errChan: } Expect(err).ToNot(HaveOccurred()) Expect(len(client.PublishedMessages)).To(Equal(1)) messages := client.PublishedMessages["hm.agent.heartbeat.my-agent-id"] Expect(len(messages)).To(Equal(1)) message := messages[0] expectedJson, _ := json.Marshal(expectedHeartbeat) Expect(string(expectedJson)).To(Equal(string(message.Payload))) }) It("nats handler connection info", func() { settings := &fakesettings.FakeSettingsService{MbusUrl: "nats://*****:*****@127.0.0.1:1234"} client, handler := buildNatsClientAndHandler(settings) err := handler.Start(func(req boshhandler.Request) (res boshhandler.Response) { return }) Expect(err).ToNot(HaveOccurred()) defer handler.Stop() Expect(client.ConnectedConnectionProvider).ToNot(BeNil()) connInfo := client.ConnectedConnectionProvider expectedConnInfo := &yagnats.ConnectionInfo{ Addr: "127.0.0.1:1234", Username: "******", Password: "******", } Expect(connInfo).To(Equal(expectedConnInfo)) }) It("nats handler connection info does not err when no username and password", func() { settings := &fakesettings.FakeSettingsService{MbusUrl: "nats://127.0.0.1:1234"} _, handler := buildNatsClientAndHandler(settings) err := handler.Start(func(req boshhandler.Request) (res boshhandler.Response) { return }) Expect(err).ToNot(HaveOccurred()) defer handler.Stop() }) It("nats handler connection info errs when has username without password", func() { settings := &fakesettings.FakeSettingsService{MbusUrl: "nats://[email protected]:1234"} _, handler := buildNatsClientAndHandler(settings) err := handler.Start(func(req boshhandler.Request) (res boshhandler.Response) { return }) Expect(err).To(HaveOccurred()) defer handler.Stop() }) }) }
func init() { Describe("Testing with Ginkgo", func() { It("dispatch responds with exception when the method is unknown", func() { logger, taskService, actionFactory, actionRunner := getActionDispatcherDependencies() req := boshhandler.NewRequest("reply to me", "gibberish", []byte{}) actionFactory.CreateErr = true dispatcher := NewActionDispatcher(logger, taskService, actionFactory, actionRunner) resp := dispatcher.Dispatch(req) boshassert.MatchesJsonString(GinkgoT(), resp, `{"exception":{"message":"unknown message gibberish"}}`) assert.Equal(GinkgoT(), actionFactory.CreateMethod, "gibberish") }) It("dispatch handles synchronous action", func() { logger, taskService, actionFactory, actionRunner := getActionDispatcherDependencies() actionFactory.CreateAction = &fakeaction.TestAction{ Asynchronous: false, } actionRunner.RunValue = "some value" dispatcher := NewActionDispatcher(logger, taskService, actionFactory, actionRunner) req := boshhandler.NewRequest("reply to me!", "some action", []byte("some payload")) resp := dispatcher.Dispatch(req) assert.Equal(GinkgoT(), req.Method, actionFactory.CreateMethod) assert.Equal(GinkgoT(), req.GetPayload(), actionRunner.RunPayload) assert.Equal(GinkgoT(), boshhandler.NewValueResponse("some value"), resp) }) It("dispatch handles synchronous action when err", func() { logger, taskService, actionFactory, actionRunner := getActionDispatcherDependencies() actionFactory.CreateAction = &fakeaction.TestAction{} actionRunner.RunErr = errors.New("some error") dispatcher := NewActionDispatcher(logger, taskService, actionFactory, actionRunner) req := boshhandler.NewRequest("reply to me!", "some action", []byte("some payload")) resp := dispatcher.Dispatch(req) expectedJson := fmt.Sprintf("{\"exception\":{\"message\":\"Action Failed %s: some error\"}}", req.Method) boshassert.MatchesJsonString(GinkgoT(), resp, expectedJson) assert.Equal(GinkgoT(), actionFactory.CreateMethod, "some action") }) It("dispatch handles asynchronous action", func() { logger, taskService, actionFactory, actionRunner := getActionDispatcherDependencies() taskService.StartTaskStartedTask = boshtask.Task{Id: "found-57-id", State: boshtask.TaskStateDone} actionFactory.CreateAction = &fakeaction.TestAction{ Asynchronous: true, } actionRunner.RunValue = "some-task-result-value" dispatcher := NewActionDispatcher(logger, taskService, actionFactory, actionRunner) req := boshhandler.NewRequest("reply to me!", "some async action", []byte("some payload")) resp := dispatcher.Dispatch(req) boshassert.MatchesJsonString(GinkgoT(), resp, `{"value":{"agent_task_id":"found-57-id","state":"done"}}`) value, err := taskService.StartTaskFunc() assert.NoError(GinkgoT(), err) assert.Equal(GinkgoT(), "some-task-result-value", value) assert.Equal(GinkgoT(), req.Method, actionFactory.CreateMethod) assert.Equal(GinkgoT(), req.GetPayload(), actionRunner.RunPayload) assert.Equal(GinkgoT(), actionFactory.CreateMethod, "some async action") }) }) }
func init() { Describe("Agent", func() { var ( agent Agent logger boshlog.Logger handler *fakembus.FakeHandler platform *fakeplatform.FakePlatform actionDispatcher *FakeActionDispatcher alertBuilder *fakealert.FakeAlertBuilder jobSupervisor *fakejobsuper.FakeJobSupervisor specService *fakeas.FakeV1Service ) BeforeEach(func() { logger = boshlog.NewLogger(boshlog.LEVEL_NONE) handler = &fakembus.FakeHandler{} platform = fakeplatform.NewFakePlatform() actionDispatcher = &FakeActionDispatcher{} alertBuilder = fakealert.NewFakeAlertBuilder() jobSupervisor = fakejobsuper.NewFakeJobSupervisor() specService = fakeas.NewFakeV1Service() agent = New(logger, handler, platform, actionDispatcher, alertBuilder, jobSupervisor, specService, 5*time.Millisecond) }) Describe("Run", func() { It("sets the dispatcher as message handler", func() { actionDispatcher.DispatchResp = boshhandler.NewValueResponse("pong") err := agent.Run() Expect(err).ToNot(HaveOccurred()) Expect(handler.ReceivedRun).To(BeTrue()) req := boshhandler.NewRequest("fake-reply", "fake-action", []byte("fake-payload")) resp := handler.Func(req) Expect(req).To(Equal(actionDispatcher.DispatchReq)) Expect(actionDispatcher.DispatchResp).To(Equal(resp)) }) It("resumes persistent actions *before* dispatching new requests", func() { resumedBefore := false handler.RunFunc = func() { resumedBefore = actionDispatcher.ResumedPreviouslyDispatchedTasks } err := agent.Run() Expect(err).ToNot(HaveOccurred()) Expect(resumedBefore).To(BeTrue()) }) Context("when heartbeats can be sent", func() { BeforeEach(func() { jobName := "fake-job" jobIndex := 1 specService.Spec = boshas.V1ApplySpec{ JobSpec: boshas.JobSpec{Name: &jobName}, Index: &jobIndex, } jobSupervisor.StatusStatus = "fake-state" platform.FakeVitalsService.GetVitals = boshvitals.Vitals{ Load: []string{"a", "b", "c"}, } }) expectedJobName := "fake-job" expectedJobIndex := 1 expectedHb := boshmbus.Heartbeat{ Job: &expectedJobName, Index: &expectedJobIndex, JobState: "fake-state", Vitals: boshvitals.Vitals{Load: []string{"a", "b", "c"}}, } It("sends initial heartbeat", func() { err := agent.Run() Expect(err).ToNot(HaveOccurred()) Expect(handler.InitialHeartbeatSent).To(BeTrue()) Expect(handler.TickHeartbeatsSent).To(BeFalse()) Expect(handler.SendToHealthManagerTopic).To(Equal("heartbeat")) Expect(handler.SendToHealthManagerPayload.(boshmbus.Heartbeat)).To(Equal(expectedHb)) }) It("sends periodic heartbeats", func() { err := agent.Run() Expect(err).ToNot(HaveOccurred()) Expect(handler.TickHeartbeatsSent).To(BeFalse()) time.Sleep(5 * time.Millisecond) Expect(handler.TickHeartbeatsSent).To(BeTrue()) Expect(handler.SendToHealthManagerTopic).To(Equal("heartbeat")) Expect(handler.SendToHealthManagerPayload.(boshmbus.Heartbeat)).To(Equal(expectedHb)) }) }) Context("when the agent fails to get job spec for a heartbeat", func() { BeforeEach(func() { block := make(chan error) handler.RunFunc = func() { <-block } specService.GetErr = errors.New("fake-spec-service-error") }) It("returns the error", func() { err := agent.Run() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-spec-service-error")) }) }) Context("when the agent fails to get vitals for a heartbeat", func() { BeforeEach(func() { block := make(chan error) handler.RunFunc = func() { <-block } platform.FakeVitalsService.GetErr = errors.New("fake-vitals-service-error") }) It("returns the error", func() { err := agent.Run() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-vitals-service-error")) }) }) It("sets the callback for job failures monitoring", func() { builtAlert := boshalert.Alert{Id: "some built alert id"} alertBuilder.BuildAlert = builtAlert err := agent.Run() Expect(err).ToNot(HaveOccurred()) Expect(handler.SendToHealthManagerTopic).ToNot(Equal("alert")) failureAlert := boshalert.MonitAlert{Id: "some random id"} jobSupervisor.OnJobFailure(failureAlert) Expect(failureAlert).To(Equal(alertBuilder.BuildInput)) Expect(handler.SendToHealthManagerTopic).To(Equal("alert")) Expect(builtAlert).To(Equal(handler.SendToHealthManagerPayload)) }) }) }) }
func (dispatcher concreteActionDispatcher) Dispatch(req boshhandler.Request) boshhandler.Response { action, err := dispatcher.actionFactory.Create(req.Method) switch { case err != nil: dispatcher.logger.Error("Action Dispatcher", "Unknown action %s", req.Method) return boshhandler.NewExceptionResponse("unknown message %s", req.Method) case action.IsAsynchronous(): dispatcher.logger.Error("Action Dispatcher", "Running async action %s", req.Method) var task boshtask.Task runTask := func() (interface{}, error) { return dispatcher.actionRunner.Run(action, req.GetPayload()) } // Certain long-running tasks (e.g. configure_networks) must be resumed // after agent restart so that API consumers do not need to know // if agent is restarted midway through the task. if action.IsPersistent() { dispatcher.logger.Error("Action Dispatcher", "Running persistent action %s", req.Method) task, err = dispatcher.taskService.CreateTask(runTask, dispatcher.removeTaskInfo) if err != nil { err = bosherr.WrapError(err, "Create Task Failed %s", req.Method) dispatcher.logger.Error("Action Dispatcher", err.Error()) return boshhandler.NewExceptionResponse(err.Error()) } taskInfo := boshtask.TaskInfo{ TaskId: task.Id, Method: req.Method, Payload: req.GetPayload(), } err = dispatcher.taskManager.AddTaskInfo(taskInfo) if err != nil { err = bosherr.WrapError(err, "Action Failed %s", req.Method) dispatcher.logger.Error("Action Dispatcher", err.Error()) return boshhandler.NewExceptionResponse(err.Error()) } } else { task, err = dispatcher.taskService.CreateTask(runTask, nil) if err != nil { err = bosherr.WrapError(err, "Create Task Failed %s", req.Method) dispatcher.logger.Error("Action Dispatcher", err.Error()) return boshhandler.NewExceptionResponse(err.Error()) } } dispatcher.taskService.StartTask(task) return boshhandler.NewValueResponse(boshtask.TaskStateValue{ AgentTaskId: task.Id, State: task.State, }) default: dispatcher.logger.Debug("Action Dispatcher", "Running sync action %s", req.Method) value, err := dispatcher.actionRunner.Run(action, req.GetPayload()) if err != nil { err = bosherr.WrapError(err, "Action Failed %s", req.Method) dispatcher.logger.Error("Action Dispatcher", err.Error()) return boshhandler.NewExceptionResponse(err.Error()) } return boshhandler.NewValueResponse(value) } }
func init() { Describe("natsHandler", func() { var ( client *fakeyagnats.FakeYagnats logger boshlog.Logger handler boshhandler.Handler ) BeforeEach(func() { settings := &fakesettings.FakeSettingsService{ AgentID: "my-agent-id", MbusURL: "nats://*****:*****@127.0.0.1:1234", } logger = boshlog.NewLogger(boshlog.LevelNone) client = fakeyagnats.New() handler = NewNatsHandler(settings, logger, client) }) Describe("Start", func() { It("starts", func() { var receivedRequest boshhandler.Request handler.Start(func(req boshhandler.Request) (resp boshhandler.Response) { receivedRequest = req return boshhandler.NewValueResponse("expected value") }) defer handler.Stop() Expect(client.ConnectedConnectionProvider).ToNot(BeNil()) Expect(len(client.Subscriptions)).To(Equal(1)) subscriptions := client.Subscriptions["agent.my-agent-id"] Expect(len(subscriptions)).To(Equal(1)) expectedPayload := []byte(`{"method":"ping","arguments":["foo","bar"], "reply_to": "reply to me!"}`) subscription := client.Subscriptions["agent.my-agent-id"][0] subscription.Callback(&yagnats.Message{ Subject: "agent.my-agent-id", Payload: expectedPayload, }) Expect(receivedRequest).To(Equal(boshhandler.Request{ ReplyTo: "reply to me!", Method: "ping", Payload: expectedPayload, })) Expect(len(client.PublishedMessages)).To(Equal(1)) messages := client.PublishedMessages["reply to me!"] Expect(len(messages)).To(Equal(1)) Expect(messages[0].Payload).To(Equal([]byte(`{"value":"expected value"}`))) }) It("does not respond if the response is nil", func() { err := handler.Start(func(req boshhandler.Request) (resp boshhandler.Response) { return nil }) Expect(err).ToNot(HaveOccurred()) defer handler.Stop() subscription := client.Subscriptions["agent.my-agent-id"][0] subscription.Callback(&yagnats.Message{ Subject: "agent.my-agent-id", Payload: []byte(`{"method":"ping","arguments":["foo","bar"], "reply_to": "reply to me!"}`), }) Expect(len(client.PublishedMessages)).To(Equal(0)) }) It("responds with an error if the response is bigger than 1MB", func() { err := handler.Start(func(req boshhandler.Request) (resp boshhandler.Response) { // gets inflated by json.Marshal when enveloping size := 0 switch req.Method { case "small": size = 1024*1024 - 12 case "big": size = 1024 * 1024 default: panic("unknown request size") } chars := make([]byte, size) for i := range chars { chars[i] = 'A' } return boshhandler.NewValueResponse(string(chars)) }) Expect(err).ToNot(HaveOccurred()) defer handler.Stop() subscription := client.Subscriptions["agent.my-agent-id"][0] subscription.Callback(&yagnats.Message{ Subject: "agent.my-agent-id", Payload: []byte(`{"method":"small","arguments":[], "reply_to": "fake-reply-to"}`), }) subscription.Callback(&yagnats.Message{ Subject: "agent.my-agent-id", Payload: []byte(`{"method":"big","arguments":[], "reply_to": "fake-reply-to"}`), }) Expect(len(client.PublishedMessages)).To(Equal(1)) messages := client.PublishedMessages["fake-reply-to"] Expect(len(messages)).To(Equal(2)) Expect(messages[0].Payload).To(MatchRegexp("value")) Expect(messages[1].Payload).To(Equal([]byte( `{"exception":{"message":"Response exceeded maximum size allowed to be sent over NATS"}}`))) }) It("can add additional handler funcs to receive requests", func() { var firstHandlerReq, secondHandlerRequest boshhandler.Request handler.Start(func(req boshhandler.Request) (resp boshhandler.Response) { firstHandlerReq = req return boshhandler.NewValueResponse("first-handler-resp") }) defer handler.Stop() handler.RegisterAdditionalHandlerFunc(func(req boshhandler.Request) (resp boshhandler.Response) { secondHandlerRequest = req return boshhandler.NewValueResponse("second-handler-resp") }) expectedPayload := []byte(`{"method":"ping","arguments":["foo","bar"], "reply_to": "fake-reply-to"}`) subscription := client.Subscriptions["agent.my-agent-id"][0] subscription.Callback(&yagnats.Message{ Subject: "agent.my-agent-id", Payload: expectedPayload, }) // Expected requests received by both handlers Expect(firstHandlerReq).To(Equal(boshhandler.Request{ ReplyTo: "fake-reply-to", Method: "ping", Payload: expectedPayload, })) Expect(secondHandlerRequest).To(Equal(boshhandler.Request{ ReplyTo: "fake-reply-to", Method: "ping", Payload: expectedPayload, })) // Bosh handler responses were sent Expect(len(client.PublishedMessages)).To(Equal(1)) messages := client.PublishedMessages["fake-reply-to"] Expect(len(messages)).To(Equal(2)) Expect(messages[0].Payload).To(Equal([]byte(`{"value":"first-handler-resp"}`))) Expect(messages[1].Payload).To(Equal([]byte(`{"value":"second-handler-resp"}`))) }) It("has the correct connection info", func() { err := handler.Start(func(req boshhandler.Request) (res boshhandler.Response) { return }) Expect(err).ToNot(HaveOccurred()) defer handler.Stop() Expect(client.ConnectedConnectionProvider).To(Equal(&yagnats.ConnectionInfo{ Addr: "127.0.0.1:1234", Username: "******", Password: "******", })) }) It("does not err when no username and password", func() { settings := &fakesettings.FakeSettingsService{MbusURL: "nats://127.0.0.1:1234"} handler = NewNatsHandler(settings, logger, client) err := handler.Start(func(req boshhandler.Request) (res boshhandler.Response) { return }) Expect(err).ToNot(HaveOccurred()) defer handler.Stop() }) It("errs when has username without password", func() { settings := &fakesettings.FakeSettingsService{MbusURL: "nats://[email protected]:1234"} handler = NewNatsHandler(settings, logger, client) err := handler.Start(func(req boshhandler.Request) (res boshhandler.Response) { return }) Expect(err).To(HaveOccurred()) defer handler.Stop() }) }) Describe("SendToHealthManager", func() { It("sends periodic heartbeats", func() { errCh := make(chan error, 1) jobName := "foo" jobIndex := 0 expectedHeartbeat := Heartbeat{Job: &jobName, Index: &jobIndex} go func() { errCh <- handler.SendToHealthManager("heartbeat", expectedHeartbeat) }() var err error select { case err = <-errCh: } Expect(err).ToNot(HaveOccurred()) Expect(len(client.PublishedMessages)).To(Equal(1)) messages := client.PublishedMessages["hm.agent.heartbeat.my-agent-id"] Expect(len(messages)).To(Equal(1)) message := messages[0] expectedJSON, _ := json.Marshal(expectedHeartbeat) Expect(string(expectedJSON)).To(Equal(string(message.Payload))) }) }) }) }
func init() { Describe("actionDispatcher", func() { var ( logger boshlog.Logger taskService *faketask.FakeService taskManager *faketask.FakeManager actionFactory *fakeaction.FakeFactory actionRunner *fakeaction.FakeRunner dispatcher ActionDispatcher ) BeforeEach(func() { logger = boshlog.NewLogger(boshlog.LEVEL_NONE) taskService = faketask.NewFakeService() taskManager = faketask.NewFakeManager() actionFactory = fakeaction.NewFakeFactory() actionRunner = &fakeaction.FakeRunner{} dispatcher = NewActionDispatcher(logger, taskService, taskManager, actionFactory, actionRunner) }) It("responds with exception when the method is unknown", func() { actionFactory.RegisterActionErr("fake-action", errors.New("fake-create-error")) req := boshhandler.NewRequest("fake-reply", "fake-action", []byte{}) resp := dispatcher.Dispatch(req) boshassert.MatchesJsonString(GinkgoT(), resp, `{"exception":{"message":"unknown message fake-action"}}`) }) Context("when action is synchronous", func() { var ( req boshhandler.Request ) BeforeEach(func() { req = boshhandler.NewRequest("fake-reply", "fake-action", []byte("fake-payload")) actionFactory.RegisterAction("fake-action", &fakeaction.TestAction{Asynchronous: false}) }) It("handles synchronous action", func() { actionRunner.RunValue = "fake-value" resp := dispatcher.Dispatch(req) Expect(req.GetPayload()).To(Equal(actionRunner.RunPayload)) Expect(boshhandler.NewValueResponse("fake-value")).To(Equal(resp)) }) It("handles synchronous action when err", func() { actionRunner.RunErr = errors.New("fake-run-error") resp := dispatcher.Dispatch(req) expectedJson := fmt.Sprintf("{\"exception\":{\"message\":\"Action Failed %s: fake-run-error\"}}", req.Method) boshassert.MatchesJsonString(GinkgoT(), resp, expectedJson) }) }) Context("when action is asynchronous", func() { var ( req boshhandler.Request action *fakeaction.TestAction ) BeforeEach(func() { req = boshhandler.NewRequest("fake-reply", "fake-action", []byte("fake-payload")) action = &fakeaction.TestAction{Asynchronous: true} actionFactory.RegisterAction("fake-action", action) }) Context("when action is not persistent", func() { BeforeEach(func() { action.Persistent = false }) It("responds with task id and state", func() { resp := dispatcher.Dispatch(req) boshassert.MatchesJsonString(GinkgoT(), resp, `{"value":{"agent_task_id":"fake-generated-task-id","state":"running"}}`) }) It("starts running created task", func() { dispatcher.Dispatch(req) Expect(len(taskService.StartedTasks)).To(Equal(1)) Expect(taskService.StartedTasks["fake-generated-task-id"]).ToNot(BeNil()) }) It("returns create task error", func() { taskService.CreateTaskErr = errors.New("fake-create-task-error") resp := dispatcher.Dispatch(req) respJson, err := json.Marshal(resp) Expect(err).ToNot(HaveOccurred()) Expect(string(respJson)).To(ContainSubstring("fake-create-task-error")) }) It("return run value to the task", func() { actionRunner.RunValue = "fake-value" dispatcher.Dispatch(req) value, err := taskService.StartedTasks["fake-generated-task-id"].TaskFunc() Expect(value).To(Equal("fake-value")) Expect(err).ToNot(HaveOccurred()) Expect(actionRunner.RunAction).To(Equal(action)) Expect(string(actionRunner.RunPayload)).To(Equal("fake-payload")) }) It("returns run error to the task", func() { actionRunner.RunErr = errors.New("fake-run-error") dispatcher.Dispatch(req) value, err := taskService.StartedTasks["fake-generated-task-id"].TaskFunc() Expect(value).To(BeNil()) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-run-error")) Expect(actionRunner.RunAction).To(Equal(action)) Expect(string(actionRunner.RunPayload)).To(Equal("fake-payload")) }) It("does not add task to task manager since it should not be resumed if agent is restarted", func() { dispatcher.Dispatch(req) taskInfos, _ := taskManager.GetTaskInfos() Expect(taskInfos).To(BeEmpty()) }) It("does not do anything after task finishes", func() { dispatcher.Dispatch(req) Expect(taskService.StartedTasks["fake-generated-task-id"].TaskEndFunc).To(BeNil()) }) }) Context("when action is persistent", func() { BeforeEach(func() { action.Persistent = true }) It("responds with task id and state", func() { resp := dispatcher.Dispatch(req) boshassert.MatchesJsonString(GinkgoT(), resp, `{"value":{"agent_task_id":"fake-generated-task-id","state":"running"}}`) }) It("starts running created task", func() { dispatcher.Dispatch(req) Expect(len(taskService.StartedTasks)).To(Equal(1)) Expect(taskService.StartedTasks["fake-generated-task-id"]).ToNot(BeNil()) }) It("returns create task error", func() { taskService.CreateTaskErr = errors.New("fake-create-task-error") resp := dispatcher.Dispatch(req) respJson, err := json.Marshal(resp) Expect(err).ToNot(HaveOccurred()) Expect(string(respJson)).To(ContainSubstring("fake-create-task-error")) }) It("return run value to the task", func() { actionRunner.RunValue = "fake-value" dispatcher.Dispatch(req) value, err := taskService.StartedTasks["fake-generated-task-id"].TaskFunc() Expect(value).To(Equal("fake-value")) Expect(err).ToNot(HaveOccurred()) Expect(actionRunner.RunAction).To(Equal(action)) Expect(string(actionRunner.RunPayload)).To(Equal("fake-payload")) }) It("returns run error to the task", func() { actionRunner.RunErr = errors.New("fake-run-error") dispatcher.Dispatch(req) value, err := taskService.StartedTasks["fake-generated-task-id"].TaskFunc() Expect(value).To(BeNil()) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-run-error")) Expect(actionRunner.RunAction).To(Equal(action)) Expect(string(actionRunner.RunPayload)).To(Equal("fake-payload")) }) It("adds task to task manager before task starts so that it could be resumed if agent is restarted", func() { dispatcher.Dispatch(req) taskInfos, _ := taskManager.GetTaskInfos() Expect(taskInfos).To(Equal([]boshtask.TaskInfo{ boshtask.TaskInfo{ TaskId: "fake-generated-task-id", Method: "fake-action", Payload: []byte("fake-payload"), }, })) }) It("removes task from task manager after task finishes", func() { dispatcher.Dispatch(req) taskService.StartedTasks["fake-generated-task-id"].TaskEndFunc(boshtask.Task{Id: "fake-generated-task-id"}) taskInfos, _ := taskManager.GetTaskInfos() Expect(taskInfos).To(BeEmpty()) }) It("does not start running created task if task manager cannot add task", func() { taskManager.AddTaskInfoErr = errors.New("fake-add-task-info-error") resp := dispatcher.Dispatch(req) boshassert.MatchesJsonString(GinkgoT(), resp, `{"exception":{"message":"Action Failed fake-action: fake-add-task-info-error"}}`) Expect(len(taskService.StartedTasks)).To(Equal(0)) }) }) }) Describe("ResumePreviouslyDispatchedTasks", func() { var firstAction, secondAction *fakeaction.TestAction BeforeEach(func() { err := taskManager.AddTaskInfo(boshtask.TaskInfo{ TaskId: "fake-task-id-1", Method: "fake-action-1", Payload: []byte("fake-task-payload-1"), }) Expect(err).ToNot(HaveOccurred()) err = taskManager.AddTaskInfo(boshtask.TaskInfo{ TaskId: "fake-task-id-2", Method: "fake-action-2", Payload: []byte("fake-task-payload-2"), }) Expect(err).ToNot(HaveOccurred()) firstAction = &fakeaction.TestAction{} secondAction = &fakeaction.TestAction{} }) It("calls resume on each task that was saved in a task manager", func() { actionFactory.RegisterAction("fake-action-1", firstAction) actionFactory.RegisterAction("fake-action-2", secondAction) dispatcher.ResumePreviouslyDispatchedTasks() Expect(len(taskService.StartedTasks)).To(Equal(2)) { // Check that first task executes first action actionRunner.ResumeValue = "fake-resume-value-1" value, err := taskService.StartedTasks["fake-task-id-1"].TaskFunc() Expect(err).ToNot(HaveOccurred()) Expect(value).To(Equal("fake-resume-value-1")) Expect(actionRunner.ResumeAction).To(Equal(firstAction)) Expect(string(actionRunner.ResumePayload)).To(Equal("fake-task-payload-1")) } { // Check that second task executes second action actionRunner.ResumeValue = "fake-resume-value-2" value, err := taskService.StartedTasks["fake-task-id-2"].TaskFunc() Expect(err).ToNot(HaveOccurred()) Expect(value).To(Equal("fake-resume-value-2")) Expect(actionRunner.ResumeAction).To(Equal(secondAction)) Expect(string(actionRunner.ResumePayload)).To(Equal("fake-task-payload-2")) } }) It("removes tasks from task manager after each task finishes", func() { actionFactory.RegisterAction("fake-action-1", firstAction) actionFactory.RegisterAction("fake-action-2", secondAction) dispatcher.ResumePreviouslyDispatchedTasks() Expect(len(taskService.StartedTasks)).To(Equal(2)) // Simulate all tasks ending taskService.StartedTasks["fake-task-id-1"].TaskEndFunc(boshtask.Task{Id: "fake-task-id-1"}) taskService.StartedTasks["fake-task-id-2"].TaskEndFunc(boshtask.Task{Id: "fake-task-id-2"}) taskInfos, err := taskManager.GetTaskInfos() Expect(err).ToNot(HaveOccurred()) Expect(taskInfos).To(BeEmpty()) }) It("return resume error to each task", func() { actionFactory.RegisterAction("fake-action-1", firstAction) actionFactory.RegisterAction("fake-action-2", secondAction) dispatcher.ResumePreviouslyDispatchedTasks() Expect(len(taskService.StartedTasks)).To(Equal(2)) { // Check that first task propagates its resume error actionRunner.ResumeErr = errors.New("fake-resume-error-1") value, err := taskService.StartedTasks["fake-task-id-1"].TaskFunc() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-resume-error-1")) Expect(value).To(BeNil()) Expect(actionRunner.ResumeAction).To(Equal(firstAction)) Expect(string(actionRunner.ResumePayload)).To(Equal("fake-task-payload-1")) } { // Check that second task propagates its resume error actionRunner.ResumeErr = errors.New("fake-resume-error-2") value, err := taskService.StartedTasks["fake-task-id-2"].TaskFunc() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-resume-error-2")) Expect(value).To(BeNil()) Expect(actionRunner.ResumeAction).To(Equal(secondAction)) Expect(string(actionRunner.ResumePayload)).To(Equal("fake-task-payload-2")) } }) It("ignores actions that cannot be created and removes them from task manager", func() { actionFactory.RegisterActionErr("fake-action-1", errors.New("fake-action-error-1")) actionFactory.RegisterAction("fake-action-2", secondAction) dispatcher.ResumePreviouslyDispatchedTasks() Expect(len(taskService.StartedTasks)).To(Equal(1)) { // Check that first action is removed from task manager taskInfos, err := taskManager.GetTaskInfos() Expect(err).ToNot(HaveOccurred()) Expect(taskInfos).To(Equal([]boshtask.TaskInfo{ boshtask.TaskInfo{ TaskId: "fake-task-id-2", Method: "fake-action-2", Payload: []byte("fake-task-payload-2"), }, })) } { // Check that second task executes second action taskService.StartedTasks["fake-task-id-2"].TaskFunc() Expect(actionRunner.ResumeAction).To(Equal(secondAction)) Expect(string(actionRunner.ResumePayload)).To(Equal("fake-task-payload-2")) } }) }) }) }