request, err := http.NewRequest("PUT", serverURL+"/blobs/a5/123-456-789", putPayload)
			Expect(err).ToNot(HaveOccurred())

			httpResponse, err := httpClient.Do(request)
			defer httpResponse.Body.Close()

			Expect(err).ToNot(HaveOccurred())
			Expect(httpResponse.StatusCode).To(Equal(201))
			contents, err := fs.ReadFileString("/var/vcap/micro_bosh/data/cache/123-456-789")
			Expect(err).ToNot(HaveOccurred())
			Expect(contents).To(Equal("Updated data"))
		})

		Context("when manager errors", func() {
			It("returns a 500", func() {
				fs.WriteToFileError = errors.New("oops")

				putBody := `Updated data`
				putPayload := strings.NewReader(putBody)

				waitForServerToStart(serverURL, "blobs", httpClient)

				request, err := http.NewRequest("PUT", serverURL+"/blobs/a5/123-456-789", putPayload)
				Expect(err).ToNot(HaveOccurred())

				httpResponse, err := httpClient.Do(request)
				defer httpResponse.Body.Close()
				Expect(httpResponse.StatusCode).To(Equal(500))

				responseBody, err := ioutil.ReadAll(httpResponse.Body)
				Expect(err).ToNot(HaveOccurred())
func init() {
	Describe("concreteV1Service", func() {
		var (
			fs       *fakesys.FakeFileSystem
			platform *fakeplatform.FakePlatform
			specPath = "/spec.json"
			service  V1Service
		)

		BeforeEach(func() {
			fs = fakesys.NewFakeFileSystem()
			platform = fakeplatform.NewFakePlatform()
			service = NewConcreteV1Service(fs, platform, specPath)
		})

		Describe("Get", func() {
			Context("when filesystem has a spec file", func() {
				BeforeEach(func() {
					fs.WriteFileString(specPath, `{"deployment":"fake-deployment-name"}`)
				})

				It("reads spec from filesystem", func() {
					spec, err := service.Get()
					Expect(err).ToNot(HaveOccurred())
					Expect(spec).To(Equal(V1ApplySpec{Deployment: "fake-deployment-name"}))
				})

				It("returns error if reading spec from filesystem errs", func() {
					fs.ReadFileError = errors.New("fake-read-error")

					spec, err := service.Get()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-read-error"))
					Expect(spec).To(Equal(V1ApplySpec{}))
				})
			})

			Context("when filesystem does not have a spec file", func() {
				It("reads spec from filesystem", func() {
					spec, err := service.Get()
					Expect(err).ToNot(HaveOccurred())
					Expect(spec).To(Equal(V1ApplySpec{}))
				})
			})
		})

		Describe("Set", func() {
			newSpec := V1ApplySpec{Deployment: "fake-deployment-name"}

			It("writes spec to filesystem", func() {
				err := service.Set(newSpec)
				Expect(err).ToNot(HaveOccurred())

				specPathStats := fs.GetFileTestStat(specPath)
				Expect(specPathStats).ToNot(BeNil())
				boshassert.MatchesJSONBytes(GinkgoT(), newSpec, specPathStats.Content)
			})

			It("returns error if writing spec to filesystem errs", func() {
				fs.WriteToFileError = errors.New("fake-write-error")

				err := service.Set(newSpec)
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-write-error"))
			})
		})

		Describe("ResolveDynamicNetworks", func() {
			Context("when there is are no dynamic networks", func() {
				unresolvedSpec := V1ApplySpec{
					Deployment: "fake-deployment",
					NetworkSpecs: map[string]NetworkSpec{
						"fake-net": NetworkSpec{
							Fields: map[string]interface{}{"ip": "fake-net-ip"},
						},
					},
				}

				It("returns spec without modifying any networks", func() {
					spec, err := service.ResolveDynamicNetworks(unresolvedSpec)
					Expect(err).ToNot(HaveOccurred())
					Expect(spec).To(Equal(V1ApplySpec{
						Deployment: "fake-deployment",
						NetworkSpecs: map[string]NetworkSpec{
							"fake-net": NetworkSpec{
								Fields: map[string]interface{}{"ip": "fake-net-ip"},
							},
						},
					}))
				})
			})

			Context("when there is one dynamic network", func() {
				unresolvedSpec := V1ApplySpec{
					Deployment: "fake-deployment",
					NetworkSpecs: map[string]NetworkSpec{
						"fake-net1": NetworkSpec{
							Fields: map[string]interface{}{
								"ip":      "fake-net1-ip",
								"netmask": "fake-net1-netmask",
								"gateway": "fake-net1-gateway",
							},
						},
						"fake-net2": NetworkSpec{
							Fields: map[string]interface{}{
								"type":    NetworkSpecTypeDynamic,
								"ip":      "fake-net2-ip",
								"netmask": "fake-net2-netmask",
								"gateway": "fake-net2-gateway",
							},
						},
					},
				}

				Context("when default network can be retrieved", func() {
					BeforeEach(func() {
						platform.GetDefaultNetworkNetwork = boshsettings.Network{
							IP:      "fake-resolved-ip",
							Netmask: "fake-resolved-netmask",
							Gateway: "fake-resolved-gateway",
						}
					})

					It("returns spec with modified dynamic network and keeping everything else the same", func() {
						spec, err := service.ResolveDynamicNetworks(unresolvedSpec)
						Expect(err).ToNot(HaveOccurred())
						Expect(spec).To(Equal(V1ApplySpec{
							Deployment: "fake-deployment",
							NetworkSpecs: map[string]NetworkSpec{
								"fake-net1": NetworkSpec{
									Fields: map[string]interface{}{
										"ip":      "fake-net1-ip",
										"netmask": "fake-net1-netmask",
										"gateway": "fake-net1-gateway",
									},
								},
								"fake-net2": NetworkSpec{
									Fields: map[string]interface{}{
										"type":    NetworkSpecTypeDynamic,
										"ip":      "fake-resolved-ip",
										"netmask": "fake-resolved-netmask",
										"gateway": "fake-resolved-gateway",
									},
								},
							},
						}))
					})
				})

				Context("when default network fails to be retrieved", func() {
					BeforeEach(func() {
						platform.GetDefaultNetworkErr = errors.New("fake-get-default-network-err")
					})

					It("returns error", func() {
						spec, err := service.ResolveDynamicNetworks(unresolvedSpec)
						Expect(err).To(HaveOccurred())
						Expect(err.Error()).To(ContainSubstring("fake-get-default-network-err"))
						Expect(spec).To(Equal(V1ApplySpec{}))
					})
				})
			})

			Context("when there is are multiple dynamic networks", func() {
				unresolvedSpec := V1ApplySpec{
					NetworkSpecs: map[string]NetworkSpec{
						"fake-net1": NetworkSpec{
							Fields: map[string]interface{}{"type": NetworkSpecTypeDynamic},
						},
						"fake-net2": NetworkSpec{
							Fields: map[string]interface{}{"type": NetworkSpecTypeDynamic},
						},
					},
				}

				It("returns error because multiple dynamic networks are not supported", func() {
					spec, err := service.ResolveDynamicNetworks(unresolvedSpec)
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("Multiple dynamic networks are not supported"))
					Expect(spec).To(Equal(V1ApplySpec{}))
				})
			})
		})
	})
}
func init() {
	Describe("concreteManagerProvider", func() {
		Describe("NewManager", func() {
			It("returns manager with tasks.json as its tasks path", func() {
				logger := boshlog.NewLogger(boshlog.LEVEL_NONE)
				fs := fakesys.NewFakeFileSystem()

				taskInfo := boshtask.TaskInfo{
					TaskId:  "fake-task-id",
					Method:  "fake-method",
					Payload: []byte("fake-payload"),
				}

				manager := boshtask.NewManagerProvider().NewManager(logger, fs, "/dir/path")
				err := manager.AddTaskInfo(taskInfo)
				Expect(err).ToNot(HaveOccurred())

				// Check expected file location with another manager
				otherManager := boshtask.NewManager(logger, fs, "/dir/path/tasks.json")

				taskInfos, err := otherManager.GetTaskInfos()
				Expect(err).ToNot(HaveOccurred())
				Expect(taskInfos).To(Equal([]boshtask.TaskInfo{taskInfo}))
			})
		})
	})

	Describe("concreteManager", func() {
		var (
			logger  boshlog.Logger
			fs      *fakesys.FakeFileSystem
			manager boshtask.Manager
		)

		BeforeEach(func() {
			logger = boshlog.NewLogger(boshlog.LEVEL_NONE)
			fs = fakesys.NewFakeFileSystem()
			manager = boshtask.NewManager(logger, fs, "/dir/path")
		})

		Describe("GetTaskInfos", func() {
			It("can load multiple tasks", func() {
				err := manager.AddTaskInfo(boshtask.TaskInfo{
					TaskId:  "fake-task-id-1",
					Method:  "fake-method-1",
					Payload: []byte("fake-payload-1"),
				})
				Expect(err).ToNot(HaveOccurred())

				err = manager.AddTaskInfo(boshtask.TaskInfo{
					TaskId:  "fake-task-id-2",
					Method:  "fake-method-2",
					Payload: []byte("fake-payload-2"),
				})
				Expect(err).ToNot(HaveOccurred())

				// Make sure we are not getting cached copy of taskInfos
				reloadedManager := boshtask.NewManager(logger, fs, "/dir/path")

				taskInfos, err := reloadedManager.GetTaskInfos()
				Expect(err).ToNot(HaveOccurred())
				Expect(taskInfos).To(Equal([]boshtask.TaskInfo{
					boshtask.TaskInfo{
						TaskId:  "fake-task-id-1",
						Method:  "fake-method-1",
						Payload: []byte("fake-payload-1"),
					},
					boshtask.TaskInfo{
						TaskId:  "fake-task-id-2",
						Method:  "fake-method-2",
						Payload: []byte("fake-payload-2"),
					},
				}))
			})

			It("succeeds when there is no tasks (file is not present)", func() {
				taskInfos, err := manager.GetTaskInfos()
				Expect(err).ToNot(HaveOccurred())
				Expect(len(taskInfos)).To(Equal(0))
			})

			It("returns an error when failing to load tasks from the file that exists", func() {
				err := manager.AddTaskInfo(boshtask.TaskInfo{
					TaskId:  "fake-task-id-2",
					Method:  "fake-method-2",
					Payload: []byte("fake-payload-2"),
				})
				Expect(err).ToNot(HaveOccurred())

				fs.ReadFileError = errors.New("fake-read-error")

				_, err = manager.GetTaskInfos()
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-read-error"))
			})
		})

		Describe("AddTaskInfo", func() {
			It("can add multiple tasks", func() {
				err := manager.AddTaskInfo(boshtask.TaskInfo{
					TaskId:  "fake-task-id-1",
					Method:  "fake-method-1",
					Payload: []byte("fake-payload-1"),
				})
				Expect(err).ToNot(HaveOccurred())

				err = manager.AddTaskInfo(boshtask.TaskInfo{
					TaskId:  "fake-task-id-2",
					Method:  "fake-method-2",
					Payload: []byte("fake-payload-2"),
				})
				Expect(err).ToNot(HaveOccurred())

				content, err := fs.ReadFile("/dir/path")
				Expect(err).ToNot(HaveOccurred())

				var decodedMap map[string]boshtask.TaskInfo

				err = json.Unmarshal(content, &decodedMap)
				Expect(err).ToNot(HaveOccurred())
				Expect(decodedMap).To(Equal(map[string]boshtask.TaskInfo{
					"fake-task-id-1": boshtask.TaskInfo{
						TaskId:  "fake-task-id-1",
						Method:  "fake-method-1",
						Payload: []byte("fake-payload-1"),
					},
					"fake-task-id-2": boshtask.TaskInfo{
						TaskId:  "fake-task-id-2",
						Method:  "fake-method-2",
						Payload: []byte("fake-payload-2"),
					},
				}))
			})

			It("returns an error when failing to save task", func() {
				fs.WriteToFileError = errors.New("fake-write-error")

				err := manager.AddTaskInfo(boshtask.TaskInfo{
					TaskId:  "fake-task-id",
					Method:  "fake-method",
					Payload: []byte("fake-payload"),
				})
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-write-error"))
			})
		})

		Describe("RemoveTaskInfo", func() {
			BeforeEach(func() {
				err := manager.AddTaskInfo(boshtask.TaskInfo{
					TaskId:  "fake-task-id-1",
					Method:  "fake-method-1",
					Payload: []byte("fake-payload-1"),
				})
				Expect(err).ToNot(HaveOccurred())

				err = manager.AddTaskInfo(boshtask.TaskInfo{
					TaskId:  "fake-task-id-2",
					Method:  "fake-method-2",
					Payload: []byte("fake-payload-2"),
				})
				Expect(err).ToNot(HaveOccurred())
			})

			It("removes the task", func() {
				err := manager.RemoveTaskInfo("fake-task-id-1")
				Expect(err).ToNot(HaveOccurred())

				content, err := fs.ReadFile("/dir/path")
				Expect(err).ToNot(HaveOccurred())

				var decodedMap map[string]boshtask.TaskInfo

				err = json.Unmarshal(content, &decodedMap)
				Expect(err).ToNot(HaveOccurred())
				Expect(decodedMap).To(Equal(map[string]boshtask.TaskInfo{
					"fake-task-id-2": boshtask.TaskInfo{
						TaskId:  "fake-task-id-2",
						Method:  "fake-method-2",
						Payload: []byte("fake-payload-2"),
					},
				}))
			})

			It("does not return error when removing task that does not exist", func() {
				err := manager.RemoveTaskInfo("fake-unknown-task-id")
				Expect(err).ToNot(HaveOccurred())
			})

			It("returns an error when failing to remove task", func() {
				fs.WriteToFileError = errors.New("fake-write-error")

				err := manager.RemoveTaskInfo("fake-task-id")
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-write-error"))
			})
		})
	})
}
func init() {
	Describe("concreteServiceProvider", func() {
		var (
			platform *fakeplatform.FakePlatform
		)

		Describe("NewService", func() {
			It("returns service with settings.json as its settings path", func() {
				// Cannot compare fetcher functions since function comparison is problematic
				fs := fakesys.NewFakeFileSystem()
				logger := boshlog.NewLogger(boshlog.LevelNone)
				service := NewServiceProvider().NewService(fs, "/setting/path", nil, platform, logger)
				Expect(service).To(Equal(NewService(fs, "/setting/path/settings.json", nil, platform, logger)))
			})
		})
	})

	Describe("concreteService", func() {
		var (
			fs       *fakesys.FakeFileSystem
			platform *fakeplatform.FakePlatform
		)

		BeforeEach(func() {
			fs = fakesys.NewFakeFileSystem()
			platform = fakeplatform.NewFakePlatform()
		})

		buildService := func(fetcher SettingsFetcher) (Service, *fakesys.FakeFileSystem) {
			logger := boshlog.NewLogger(boshlog.LevelNone)
			service := NewService(fs, "/setting/path", fetcher, platform, logger)
			return service, fs
		}

		Describe("LoadSettings", func() {
			var (
				fetchedSettings Settings
				fetcherFuncErr  error
				service         Service
			)

			BeforeEach(func() {
				fetchedSettings = Settings{}
				fetcherFuncErr = nil
			})

			JustBeforeEach(func() {
				fetcherFunc := func() (Settings, error) { return fetchedSettings, fetcherFuncErr }
				service, fs = buildService(fetcherFunc)
			})

			Context("when settings fetcher succeeds fetching settings", func() {
				BeforeEach(func() {
					fetchedSettings = Settings{AgentID: "some-new-agent-id"}
				})

				Context("when settings contain at most one dynamic network", func() {
					BeforeEach(func() {
						fetchedSettings.Networks = Networks{
							"fake-net-1": Network{Type: NetworkTypeDynamic},
						}
					})

					It("updates the service with settings from the fetcher", func() {
						err := service.LoadSettings()
						Expect(err).NotTo(HaveOccurred())
						Expect(service.GetSettings().AgentID).To(Equal("some-new-agent-id"))
					})

					It("persists settings to the settings file", func() {
						err := service.LoadSettings()
						Expect(err).NotTo(HaveOccurred())

						json, err := json.Marshal(fetchedSettings)
						Expect(err).NotTo(HaveOccurred())

						fileContent, err := fs.ReadFile("/setting/path")
						Expect(err).NotTo(HaveOccurred())
						Expect(fileContent).To(Equal(json))
					})

					It("returns any error from writing to the setting file", func() {
						fs.WriteToFileError = errors.New("fs-write-file-error")

						err := service.LoadSettings()
						Expect(err).To(HaveOccurred())
						Expect(err.Error()).To(ContainSubstring("fs-write-file-error"))
					})
				})

				Context("when settings contain multiple dynamic networks", func() {
					BeforeEach(func() {
						fetchedSettings.Networks = Networks{
							"fake-net-1": Network{Type: NetworkTypeDynamic},
							"fake-net-2": Network{Type: NetworkTypeDynamic},
						}
					})

					It("returns error because multiple dynamic networks are not supported", func() {
						err := service.LoadSettings()
						Expect(err).To(HaveOccurred())
						Expect(err.Error()).To(ContainSubstring("Multiple dynamic networks are not supported"))
					})
				})
			})

			Context("when settings fetcher fails fetching settings", func() {
				BeforeEach(func() {
					fetcherFuncErr = errors.New("fake-fetch-error")
				})

				Context("when a settings file exists", func() {
					Context("when settings contain at most one dynamic network", func() {
						BeforeEach(func() {
							fs.WriteFile("/setting/path", []byte(`{
								"agent_id":"some-agent-id",
								"networks": {"fake-net-1": {"type": "dynamic"}}
							}`))
						})

						It("returns settings from the settings file", func() {
							err := service.LoadSettings()
							Expect(err).ToNot(HaveOccurred())
							Expect(service.GetSettings()).To(Equal(Settings{
								AgentID: "some-agent-id",
								Networks: Networks{
									"fake-net-1": Network{Type: NetworkTypeDynamic},
								},
							}))
						})
					})

					Context("when settings contain multiple dynamic networks", func() {
						BeforeEach(func() {
							fs.WriteFile("/setting/path", []byte(`{
								"agent_id":"some-agent-id",
								"networks": {
									"fake-net-1": {"type": "dynamic"},
									"fake-net-2": {"type": "dynamic"}
								}
							}`))
						})

						It("returns error because multiple dynamic networks are not supported", func() {
							err := service.LoadSettings()
							Expect(err).To(HaveOccurred())
							Expect(err.Error()).To(ContainSubstring("Multiple dynamic networks are not supported"))
						})
					})
				})

				Context("when non-unmarshallable settings file exists", func() {
					It("returns any error from the fetcher", func() {
						fs.WriteFile("/setting/path", []byte(`$%^&*(`))

						err := service.LoadSettings()
						Expect(err).To(HaveOccurred())
						Expect(err.Error()).To(ContainSubstring("fake-fetch-error"))

						Expect(service.GetSettings()).To(Equal(Settings{}))
					})
				})

				Context("when no settings file exists", func() {
					It("returns any error from the fetcher", func() {
						err := service.LoadSettings()
						Expect(err).To(HaveOccurred())
						Expect(err.Error()).To(ContainSubstring("fake-fetch-error"))

						Expect(service.GetSettings()).To(Equal(Settings{}))
					})
				})
			})
		})

		Describe("InvalidateSettings", func() {
			It("removes the settings file", func() {
				service, fs := buildService(nil)

				fs.WriteFile("/setting/path", []byte(`{}`))

				err := service.InvalidateSettings()
				Expect(err).ToNot(HaveOccurred())

				Expect(fs.FileExists("/setting/path")).To(BeFalse())
			})

			It("returns err if removing settings file errored", func() {
				service, fs := buildService(nil)

				fs.RemoveAllError = errors.New("fs-remove-all-error")

				err := service.InvalidateSettings()
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fs-remove-all-error"))
			})
		})

		Describe("GetSettings", func() {
			var (
				loadedSettings Settings
				service        Service
			)

			BeforeEach(func() {
				loadedSettings = Settings{AgentID: "some-agent-id"}
			})

			JustBeforeEach(func() {
				service, _ = buildService(func() (Settings, error) { return loadedSettings, nil })
				err := service.LoadSettings()
				Expect(err).NotTo(HaveOccurred())
			})

			Context("when there is are no dynamic networks", func() {
				It("returns settings without modifying any networks", func() {
					Expect(service.GetSettings()).To(Equal(loadedSettings))
				})

				It("does not try to determine default network", func() {
					_ = service.GetSettings()
					Expect(platform.GetDefaultNetworkCalled).To(BeFalse())
				})
			})

			Context("when there is one dynamic network", func() {
				BeforeEach(func() {
					loadedSettings = Settings{
						Networks: map[string]Network{
							"fake-net1": Network{
								IP:      "fake-net1-ip",
								Netmask: "fake-net1-netmask",
								Gateway: "fake-net1-gateway",
							},
							"fake-net2": Network{
								Type:    "dynamic",
								IP:      "fake-net2-ip",
								Netmask: "fake-net2-netmask",
								Gateway: "fake-net2-gateway",
								DNS:     []string{"fake-net2-dns"},
							},
						},
					}
				})

				Context("when default network can be retrieved", func() {
					BeforeEach(func() {
						platform.GetDefaultNetworkNetwork = Network{
							IP:      "fake-resolved-ip",
							Netmask: "fake-resolved-netmask",
							Gateway: "fake-resolved-gateway",
						}
					})

					It("returns settings with resolved dynamic network ip, netmask, gateway and keeping everything else the same", func() {
						settings := service.GetSettings()
						Expect(settings).To(Equal(Settings{
							Networks: map[string]Network{
								"fake-net1": Network{
									IP:      "fake-net1-ip",
									Netmask: "fake-net1-netmask",
									Gateway: "fake-net1-gateway",
								},
								"fake-net2": Network{
									Type:    "dynamic",
									IP:      "fake-resolved-ip",
									Netmask: "fake-resolved-netmask",
									Gateway: "fake-resolved-gateway",
									DNS:     []string{"fake-net2-dns"},
								},
							},
						}))
					})
				})

				Context("when default network fails to be retrieved", func() {
					BeforeEach(func() {
						platform.GetDefaultNetworkErr = errors.New("fake-get-default-network-err")
					})

					It("returns error", func() {
						settings := service.GetSettings()
						Expect(settings).To(Equal(loadedSettings))
					})
				})
			})
		})
	})
}
func init() {
	Describe("monitJobSupervisor", func() {
		var (
			fs                    *fakesys.FakeFileSystem
			runner                *fakesys.FakeCmdRunner
			client                *fakemonit.FakeMonitClient
			logger                boshlog.Logger
			dirProvider           boshdir.DirectoriesProvider
			jobFailuresServerPort int
			monit                 JobSupervisor
		)

		BeforeEach(func() {
			fs = &fakesys.FakeFileSystem{}
			runner = &fakesys.FakeCmdRunner{}
			client = fakemonit.NewFakeMonitClient()
			logger = boshlog.NewLogger(boshlog.LevelNone)
			dirProvider = boshdir.NewDirectoriesProvider("/var/vcap")
			jobFailuresServerPort = getJobFailureServerPort()

			monit = NewMonitJobSupervisor(
				fs,
				runner,
				client,
				logger,
				dirProvider,
				jobFailuresServerPort,
				0*time.Millisecond,
			)
		})

		It("waits until the job is reloaded", func() {
			client.Incarnations = []int{1, 1, 1, 2, 3}
			client.StatusStatus = fakemonit.FakeMonitStatus{
				Services: []boshmonit.Service{
					boshmonit.Service{Monitored: true, Status: "failing"},
					boshmonit.Service{Monitored: true, Status: "running"},
				},
				Incarnation: 1,
			}

			err := monit.Reload()

			Expect(err).ToNot(HaveOccurred())
			Expect(1).To(Equal(len(runner.RunCommands)))
			Expect(runner.RunCommands[0]).To(Equal([]string{"monit", "reload"}))
			Expect(client.StatusCalledTimes).To(Equal(4))
		})

		It("stops trying to reload after 60 attempts", func() {
			for i := 0; i < 61; i++ {
				client.Incarnations = append(client.Incarnations, 1)
			}

			client.StatusStatus = fakemonit.FakeMonitStatus{
				Services: []boshmonit.Service{
					boshmonit.Service{Monitored: true, Status: "failing"},
					boshmonit.Service{Monitored: true, Status: "running"},
				},
				Incarnation: 1,
			}

			err := monit.Reload()

			Expect(err).To(HaveOccurred())
			Expect(1).To(Equal(len(runner.RunCommands)))
			Expect(runner.RunCommands[0]).To(Equal([]string{"monit", "reload"}))
			Expect(client.StatusCalledTimes).To(Equal(60))
		})

		It("start starts each monit service in group vcap", func() {
			client.ServicesInGroupServices = []string{"fake-service"}

			err := monit.Start()
			Expect(err).ToNot(HaveOccurred())

			Expect("vcap").To(Equal(client.ServicesInGroupName))
			Expect(1).To(Equal(len(client.StartServiceNames)))
			Expect("fake-service").To(Equal(client.StartServiceNames[0]))
		})

		It("stop stops each monit service in group vcap", func() {
			client.ServicesInGroupServices = []string{"fake-service"}

			err := monit.Stop()
			Expect(err).ToNot(HaveOccurred())

			Expect("vcap").To(Equal(client.ServicesInGroupName))
			Expect(1).To(Equal(len(client.StopServiceNames)))
			Expect("fake-service").To(Equal(client.StopServiceNames[0]))
		})

		It("status returns running when all services are monitored and running", func() {
			client.StatusStatus = fakemonit.FakeMonitStatus{
				Services: []boshmonit.Service{
					boshmonit.Service{Monitored: true, Status: "running"},
					boshmonit.Service{Monitored: true, Status: "running"},
				},
			}

			status := monit.Status()
			Expect("running").To(Equal(status))
		})

		It("status returns failing when all services are monitored and at least one service is failing", func() {
			client.StatusStatus = fakemonit.FakeMonitStatus{
				Services: []boshmonit.Service{
					boshmonit.Service{Monitored: true, Status: "failing"},
					boshmonit.Service{Monitored: true, Status: "running"},
				},
			}

			status := monit.Status()
			Expect("failing").To(Equal(status))
		})

		It("status returns failing when at least one service is not monitored", func() {
			client.StatusStatus = fakemonit.FakeMonitStatus{
				Services: []boshmonit.Service{
					boshmonit.Service{Monitored: false, Status: "running"},
					boshmonit.Service{Monitored: true, Status: "running"},
				},
			}

			status := monit.Status()
			Expect("failing").To(Equal(status))
		})

		It("status returns start when at least one service is starting", func() {
			client.StatusStatus = fakemonit.FakeMonitStatus{
				Services: []boshmonit.Service{
					boshmonit.Service{Monitored: true, Status: "failing"},
					boshmonit.Service{Monitored: true, Status: "starting"},
					boshmonit.Service{Monitored: true, Status: "running"},
				},
			}

			status := monit.Status()
			Expect("starting").To(Equal(status))
		})

		It("status returns unknown when error", func() {
			client.StatusErr = errors.New("fake-monit-client-error")

			status := monit.Status()
			Expect("unknown").To(Equal(status))
		})

		It("monitor job failures", func() {
			var handledAlert boshalert.MonitAlert

			failureHandler := func(alert boshalert.MonitAlert) (err error) {
				handledAlert = alert
				return
			}

			go monit.MonitorJobFailures(failureHandler)

			msg := `Message-id: <1304319946.0@localhost>
    Service: nats
    Event: does not exist
    Action: restart
    Date: Sun, 22 May 2011 20:07:41 +0500
    Description: process is not running`

			err := doJobFailureEmail(msg, jobFailuresServerPort)
			Expect(err).ToNot(HaveOccurred())

			assert.Equal(GinkgoT(), handledAlert, boshalert.MonitAlert{
				ID:          "1304319946.0@localhost",
				Service:     "nats",
				Event:       "does not exist",
				Action:      "restart",
				Date:        "Sun, 22 May 2011 20:07:41 +0500",
				Description: "process is not running",
			})
		})

		It("monitor job failures ignores other emails", func() {
			var didHandleAlert bool

			failureHandler := func(alert boshalert.MonitAlert) (err error) {
				didHandleAlert = true
				return
			}

			go monit.MonitorJobFailures(failureHandler)

			msg := `Hi! How'sit goin`

			err := doJobFailureEmail(msg, jobFailuresServerPort)
			Expect(err).ToNot(HaveOccurred())
			Expect(didHandleAlert).To(BeFalse())
		})

		Describe("AddJob", func() {
			BeforeEach(func() {
				fs.WriteFileString("/some/config/path", "fake-config")
			})

			Context("when reading configuration from config path succeeds", func() {
				Context("when writing job configuration succeeds", func() {
					It("returns no error because monit can track added job in jobs directory", func() {
						err := monit.AddJob("router", 0, "/some/config/path")
						Expect(err).ToNot(HaveOccurred())

						writtenConfig, err := fs.ReadFileString(
							dirProvider.MonitJobsDir() + "/0000_router.monitrc")
						Expect(err).ToNot(HaveOccurred())
						Expect(writtenConfig).To(Equal("fake-config"))
					})
				})

				Context("when writing job configuration fails", func() {
					It("returns error", func() {
						fs.WriteToFileError = errors.New("fake-write-error")

						err := monit.AddJob("router", 0, "/some/config/path")
						Expect(err).To(HaveOccurred())
						Expect(err.Error()).To(ContainSubstring("fake-write-error"))
					})
				})
			})

			Context("when reading configuration from config path fails", func() {
				It("returns error", func() {
					fs.ReadFileError = errors.New("fake-read-error")

					err := monit.AddJob("router", 0, "/some/config/path")
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-read-error"))
				})
			})
		})

		Describe("RemoveAllJobs", func() {
			Context("when jobs directory removal succeeds", func() {
				It("does not return error because all jobs are removed from monit", func() {
					jobsDir := dirProvider.MonitJobsDir()
					jobBasename := "/0000_router.monitrc"
					fs.WriteFileString(jobsDir+jobBasename, "fake-added-job")

					err := monit.RemoveAllJobs()
					Expect(err).ToNot(HaveOccurred())

					Expect(fs.FileExists(jobsDir)).To(BeFalse())
					Expect(fs.FileExists(jobsDir + jobBasename)).To(BeFalse())
				})
			})

			Context("when jobs directory removal fails", func() {
				It("returns error if removing jobs directory fails", func() {
					fs.RemoveAllError = errors.New("fake-remove-all-error")

					err := monit.RemoveAllJobs()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-remove-all-error"))
				})
			})
		})

		Describe("Unmonitor", func() {
			BeforeEach(func() {
				client.ServicesInGroupServices = []string{"fake-srv-1", "fake-srv-2", "fake-srv-3"}
				client.UnmonitorServiceErrs = []error{nil, nil, nil}
			})

			Context("when all services succeed to be unmonitored", func() {
				It("returns no error because all services got unmonitored", func() {
					err := monit.Unmonitor()
					Expect(err).ToNot(HaveOccurred())

					Expect(client.ServicesInGroupName).To(Equal("vcap"))
					Expect(client.UnmonitorServiceNames).To(Equal(
						[]string{"fake-srv-1", "fake-srv-2", "fake-srv-3"}))
				})
			})

			Context("when at least one service fails to be unmonitored", func() {
				BeforeEach(func() {
					client.UnmonitorServiceErrs = []error{
						nil, errors.New("fake-unmonitor-error"), nil,
					}
				})

				It("returns first unmonitor error", func() {
					err := monit.Unmonitor()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-unmonitor-error"))
				})

				It("only tries to unmonitor services before the first unmonitor error", func() {
					err := monit.Unmonitor()
					Expect(err).To(HaveOccurred())
					Expect(client.ServicesInGroupName).To(Equal("vcap"))
					Expect(client.UnmonitorServiceNames).To(Equal([]string{"fake-srv-1", "fake-srv-2"}))
				})
			})

			Context("when failed retrieving list of services", func() {
				It("returns error", func() {
					client.ServicesInGroupErr = errors.New("fake-services-error")

					err := monit.Unmonitor()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-services-error"))
				})
			})
		})
	})
}
func init() {
	Describe("concreteV1Service", func() {
		var (
			fs       *fakesys.FakeFileSystem
			specPath = "/spec.json"
			service  V1Service
		)

		BeforeEach(func() {
			fs = fakesys.NewFakeFileSystem()
			service = NewConcreteV1Service(fs, specPath)
		})

		Describe("Get", func() {
			Context("when filesystem has a spec file", func() {
				BeforeEach(func() {
					fs.WriteFileString(specPath, `{"deployment":"fake-deployment-name"}`)
				})

				It("reads spec from filesystem", func() {
					spec, err := service.Get()
					Expect(err).ToNot(HaveOccurred())
					Expect(spec).To(Equal(V1ApplySpec{Deployment: "fake-deployment-name"}))
				})

				It("returns error if reading spec from filesystem errs", func() {
					fs.ReadFileError = errors.New("fake-read-error")

					spec, err := service.Get()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-read-error"))
					Expect(spec).To(Equal(V1ApplySpec{}))
				})
			})

			Context("when filesystem does not have a spec file", func() {
				It("reads spec from filesystem", func() {
					spec, err := service.Get()
					Expect(err).ToNot(HaveOccurred())
					Expect(spec).To(Equal(V1ApplySpec{}))
				})
			})
		})

		Describe("Set", func() {
			newSpec := V1ApplySpec{Deployment: "fake-deployment-name"}

			It("writes spec to filesystem", func() {
				err := service.Set(newSpec)
				Expect(err).ToNot(HaveOccurred())

				specPathStats := fs.GetFileTestStat(specPath)
				Expect(specPathStats).ToNot(BeNil())
				boshassert.MatchesJSONBytes(GinkgoT(), newSpec, specPathStats.Content)
			})

			It("returns error if writing spec to filesystem errs", func() {
				fs.WriteToFileError = errors.New("fake-write-error")

				err := service.Set(newSpec)
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-write-error"))
			})
		})

		Describe("PopulateDynamicNetworks", func() {
			Context("when there are no dynamic networks", func() {
				unresolvedSpec := V1ApplySpec{
					Deployment: "fake-deployment",
					NetworkSpecs: map[string]NetworkSpec{
						"fake-net": NetworkSpec{
							Fields: map[string]interface{}{"ip": "fake-net-ip"},
						},
					},
				}

				It("returns spec without modifying any networks", func() {
					spec, err := service.PopulateDynamicNetworks(unresolvedSpec, boshsettings.Settings{})
					Expect(err).ToNot(HaveOccurred())
					Expect(spec).To(Equal(V1ApplySpec{
						Deployment: "fake-deployment",
						NetworkSpecs: map[string]NetworkSpec{
							"fake-net": NetworkSpec{
								Fields: map[string]interface{}{"ip": "fake-net-ip"},
							},
						},
					}))
				})
			})

			Context("when there are dynamic networks", func() {
				unresolvedSpec := V1ApplySpec{
					Deployment: "fake-deployment",
					NetworkSpecs: map[string]NetworkSpec{
						"fake-net1": NetworkSpec{
							Fields: map[string]interface{}{
								"ip":      "fake-net1-ip",
								"netmask": "fake-net1-netmask",
								"gateway": "fake-net1-gateway",
							},
						},
						"fake-net2": NetworkSpec{
							Fields: map[string]interface{}{
								"type":    NetworkSpecTypeDynamic,
								"ip":      "fake-net2-ip",
								"netmask": "fake-net2-netmask",
								"gateway": "fake-net2-gateway",
							},
						},
						"fake-net3": NetworkSpec{
							Fields: map[string]interface{}{
								"type":    NetworkSpecTypeDynamic,
								"ip":      "fake-net3-ip",
								"netmask": "fake-net3-netmask",
								"gateway": "fake-net3-gateway",
							},
						},
					},
				}

				Context("when associated network is in settings", func() {
					settings := boshsettings.Settings{
						Networks: boshsettings.Networks{
							"fake-net1": boshsettings.Network{
								IP:      "fake-resolved1-ip",
								Netmask: "fake-resolved1-netmask",
								Gateway: "fake-resolved1-gateway",
							},
							"fake-net2": boshsettings.Network{
								IP:      "fake-resolved2-ip",
								Netmask: "fake-resolved2-netmask",
								Gateway: "fake-resolved2-gateway",
							},
							"fake-net3": boshsettings.Network{
								IP:      "fake-resolved3-ip",
								Netmask: "fake-resolved3-netmask",
								Gateway: "fake-resolved3-gateway",
							},
						},
					}

					It("returns spec with modified dynamic networks and keeping everything else the same", func() {
						spec, err := service.PopulateDynamicNetworks(unresolvedSpec, settings)
						Expect(err).ToNot(HaveOccurred())
						Expect(spec).To(Equal(V1ApplySpec{
							Deployment: "fake-deployment",
							NetworkSpecs: map[string]NetworkSpec{
								"fake-net1": NetworkSpec{
									Fields: map[string]interface{}{ // ip info not replaced
										"ip":      "fake-net1-ip",
										"netmask": "fake-net1-netmask",
										"gateway": "fake-net1-gateway",
									},
								},
								"fake-net2": NetworkSpec{
									Fields: map[string]interface{}{
										"type":    NetworkSpecTypeDynamic,
										"ip":      "fake-resolved2-ip",
										"netmask": "fake-resolved2-netmask",
										"gateway": "fake-resolved2-gateway",
									},
								},
								"fake-net3": NetworkSpec{
									Fields: map[string]interface{}{
										"type":    NetworkSpecTypeDynamic,
										"ip":      "fake-resolved3-ip",
										"netmask": "fake-resolved3-netmask",
										"gateway": "fake-resolved3-gateway",
									},
								},
							},
						}))
					})
				})

				Context("when associated network cannot be found in settings", func() {
					It("returns error", func() {
						spec, err := service.PopulateDynamicNetworks(unresolvedSpec, boshsettings.Settings{})
						Expect(err).To(Equal(errors.New("Network fake-net2 is not found in settings")))
						Expect(spec).To(Equal(V1ApplySpec{}))
					})
				})
			})
		})
	})
}
func init() {
	Describe("concreteV1Service", func() {
		var (
			fs       *fakesys.FakeFileSystem
			specPath string
			service  V1Service
		)

		BeforeEach(func() {
			fs = fakesys.NewFakeFileSystem()
			specPath = "/spec.json"
			service = NewConcreteV1Service(fs, specPath)
		})

		Describe("Get", func() {
			Context("when filesystem has a spec file", func() {
				BeforeEach(func() {
					fs.WriteFileString(specPath, `{"deployment":"fake-deployment-name"}`)
				})

				It("reads spec from filesystem", func() {
					spec, err := service.Get()
					Expect(err).ToNot(HaveOccurred())
					Expect(spec).To(Equal(V1ApplySpec{Deployment: "fake-deployment-name"}))
				})

				It("returns error if reading spec from filesystem errs", func() {
					fs.ReadFileError = errors.New("fake-read-error")

					spec, err := service.Get()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-read-error"))
					Expect(spec).To(Equal(V1ApplySpec{}))
				})
			})

			Context("when filesystem does not have a spec file", func() {
				It("reads spec from filesystem", func() {
					spec, err := service.Get()
					Expect(err).ToNot(HaveOccurred())
					Expect(spec).To(Equal(V1ApplySpec{}))
				})
			})
		})

		Describe("Set", func() {
			newSpec := V1ApplySpec{Deployment: "fake-deployment-name"}

			It("writes spec to filesystem", func() {
				err := service.Set(newSpec)
				Expect(err).ToNot(HaveOccurred())

				specPathStats := fs.GetFileTestStat(specPath)
				Expect(specPathStats).ToNot(BeNil())
				boshassert.MatchesJSONBytes(GinkgoT(), newSpec, specPathStats.Content)
			})

			It("returns error if writing spec to filesystem errs", func() {
				fs.WriteToFileError = errors.New("fake-write-error")

				err := service.Set(newSpec)
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-write-error"))
			})
		})

	})
}
		Context("when reading configuration from config path succeeds", func() {
			Context("when writing job configuration succeeds", func() {
				It("returns no error because monit can track added job in jobs directory", func() {
					err := monit.AddJob("router", 0, "/some/config/path")
					Expect(err).ToNot(HaveOccurred())

					writtenConfig, err := fs.ReadFileString(
						dirProvider.MonitJobsDir() + "/0000_router.monitrc")
					Expect(err).ToNot(HaveOccurred())
					Expect(writtenConfig).To(Equal("fake-config"))
				})
			})

			Context("when writing job configuration fails", func() {
				It("returns error", func() {
					fs.WriteToFileError = errors.New("fake-write-error")

					err := monit.AddJob("router", 0, "/some/config/path")
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-write-error"))
				})
			})
		})

		Context("when reading configuration from config path fails", func() {
			It("returns error", func() {
				fs.ReadFileError = errors.New("fake-read-error")

				err := monit.AddJob("router", 0, "/some/config/path")
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-read-error"))