err  error
	)

	BeforeEach(func() {
		fs = fakesys.NewFakeFileSystem()
		path = "/agent_state.json"
		s, err = platform.NewBootstrapState(fs, path)
		Expect(err).NotTo(HaveOccurred())
	})

	Describe("SaveState", func() {
		It("saves the state file with the appropriate properties", func() {
			s.Linux = platform.LinuxState{HostsConfigured: true}
			s.SaveState()

			contents, readerr := fs.ReadFile(path)

			Expect(readerr).ToNot(HaveOccurred())
			Expect(string(contents)).To(Equal(`{"Linux":{"hosts_configured":true}}`))
		})

		It("saves the state file with the properties passed in", func() {
			s.Linux = platform.LinuxState{HostsConfigured: true}
			s.SaveState()

			contents, readerr := fs.ReadFile(path)

			Expect(readerr).ToNot(HaveOccurred())
			Expect(string(contents)).To(Equal(`{"Linux":{"hosts_configured":true}}`))
		})
Example #2
0
func init() {
	Describe("settingsService", func() {
		var (
			fs                         *fakesys.FakeFileSystem
			fakeDefaultNetworkResolver *fakenet.FakeDefaultNetworkResolver
			fakeSettingsSource         *fakes.FakeSettingsSource
		)

		BeforeEach(func() {
			fs = fakesys.NewFakeFileSystem()
			fakeDefaultNetworkResolver = &fakenet.FakeDefaultNetworkResolver{}
			fakeSettingsSource = &fakes.FakeSettingsSource{}
		})

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

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

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

			JustBeforeEach(func() {
				fakeSettingsSource.SettingsValue = fetchedSettings
				fakeSettingsSource.SettingsErr = fetcherFuncErr
				service, fs = buildService()
			})

			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.json")
						Expect(err).NotTo(HaveOccurred())
						Expect(fileContent).To(Equal(json))
					})

					It("returns any error from writing to the setting file", func() {
						fs.WriteFileError = 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 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.json", []byte(`{
								"agent_id":"some-agent-id",
								"networks": {"fake-net-1": {"type": "dynamic"}}
							}`))

							fakeDefaultNetworkResolver.GetDefaultNetworkNetwork = Network{
								IP:      "fake-resolved-ip",
								Netmask: "fake-resolved-netmask",
								Gateway: "fake-resolved-gateway",
							}
						})

						It("returns settings from the settings file with resolved network", 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,
										IP:       "fake-resolved-ip",
										Netmask:  "fake-resolved-netmask",
										Gateway:  "fake-resolved-gateway",
										Resolved: true,
									},
								},
							}))
						})
					})
				})

				Context("when non-unmarshallable settings file exists", func() {
					It("returns any error from the fetcher", func() {
						fs.WriteFile("/setting/path.json", []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() {
				fakeSettingsSource.SettingsValue = Settings{}
				fakeSettingsSource.SettingsErr = nil
				service, fs := buildService()

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

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

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

			It("returns err if removing settings file errored", func() {
				fakeSettingsSource.SettingsValue = Settings{}
				fakeSettingsSource.SettingsErr = nil
				service, fs := buildService()

				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() {
				fakeSettingsSource.SettingsValue = loadedSettings
				fakeSettingsSource.SettingsErr = nil
				service, _ = buildService()
				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(fakeDefaultNetworkResolver.GetDefaultNetworkCalled).To(BeFalse())
				})
			})

			Context("when there is network that needs to be resolved (ip, netmask, or mac are not set)", func() {
				BeforeEach(func() {
					loadedSettings = Settings{
						Networks: map[string]Network{
							"fake-net1": Network{
								IP:      "fake-net1-ip",
								Netmask: "fake-net1-netmask",
								Mac:     "fake-net1-mac",
								Gateway: "fake-net1-gateway",
							},
							"fake-net2": Network{
								Gateway: "fake-net2-gateway",
								DNS:     []string{"fake-net2-dns"},
							},
						},
					}
				})

				Context("when default network can be retrieved", func() {
					BeforeEach(func() {
						fakeDefaultNetworkResolver.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",
									Mac:     "fake-net1-mac",
									Gateway: "fake-net1-gateway",
								},
								"fake-net2": Network{
									IP:       "fake-resolved-ip",
									Netmask:  "fake-resolved-netmask",
									Gateway:  "fake-resolved-gateway",
									DNS:      []string{"fake-net2-dns"},
									Resolved: true,
								},
							},
						}))
					})
				})

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

					It("returns error", func() {
						settings := service.GetSettings()
						Expect(settings).To(Equal(loadedSettings))
					})
				})
			})
		})
	})
}
Example #3
0
						Records: [][2]string{
							{"fake-ip0", "fake-name0"},
							{"fake-ip1", "fake-name1"},
						},
					}))
				})

				Context("local DNS state operations", func() {
					Context("when loading succeeds", func() {
						Context("when saving succeeds", func() {
							It("saves the new local DNS state", func() {
								response, err := action.Run("fake-blobstore-id", "fake-fingerprint", 3)
								Expect(err).ToNot(HaveOccurred())
								Expect(response).To(Equal("synced"))

								contents, err := fakeFileSystem.ReadFile(stateFilePath)
								Expect(err).ToNot(HaveOccurred())
								localDNSState := state.LocalDNSState{}
								err = json.Unmarshal(contents, &localDNSState)
								Expect(err).ToNot(HaveOccurred())
								Expect(localDNSState).To(Equal(state.LocalDNSState{Version: 3}))
							})
						})

						Context("when loading fails", func() {
							BeforeEach(func() {
								fakeFileSystem.RegisterReadFileError(stateFilePath, errors.New("fake-read-error"))
							})

							It("returns an error", func() {
								_, err := action.Run("fake-blobstore-id", "fake-fingerprint", 3)
func describeUbuntuNetManager() {
	var (
		fs                            *fakesys.FakeFileSystem
		cmdRunner                     *fakesys.FakeCmdRunner
		ipResolver                    *fakeip.FakeResolver
		addressBroadcaster            *fakearp.FakeAddressBroadcaster
		interfaceAddrsProvider        *fakeip.FakeInterfaceAddressesProvider
		netManager                    UbuntuNetManager
		interfaceConfigurationCreator InterfaceConfigurationCreator
	)

	writeNetworkDevice := func(iface string, macAddress string, isPhysical bool) string {
		interfacePath := fmt.Sprintf("/sys/class/net/%s", iface)
		fs.WriteFile(interfacePath, []byte{})
		if isPhysical {
			fs.WriteFile(fmt.Sprintf("/sys/class/net/%s/device", iface), []byte{})
		}
		fs.WriteFileString(fmt.Sprintf("/sys/class/net/%s/address", iface), fmt.Sprintf("%s\n", macAddress))

		return interfacePath
	}

	stubInterfacesWithVirtual := func(physicalInterfaces map[string]boshsettings.Network, virtualInterfaces []string) {
		interfacePaths := []string{}

		for iface, networkSettings := range physicalInterfaces {
			interfacePaths = append(interfacePaths, writeNetworkDevice(iface, networkSettings.Mac, true))
		}

		for _, iface := range virtualInterfaces {
			interfacePaths = append(interfacePaths, writeNetworkDevice(iface, "virtual", false))
		}

		fs.SetGlob("/sys/class/net/*", interfacePaths)
	}

	stubInterfaces := func(physicalInterfaces map[string]boshsettings.Network) {
		stubInterfacesWithVirtual(physicalInterfaces, nil)
	}

	BeforeEach(func() {
		fs = fakesys.NewFakeFileSystem()
		cmdRunner = fakesys.NewFakeCmdRunner()
		ipResolver = &fakeip.FakeResolver{}
		logger := boshlog.NewLogger(boshlog.LevelNone)
		interfaceConfigurationCreator = NewInterfaceConfigurationCreator(logger)
		addressBroadcaster = &fakearp.FakeAddressBroadcaster{}
		interfaceAddrsProvider = &fakeip.FakeInterfaceAddressesProvider{}
		interfaceAddrsValidator := boship.NewInterfaceAddressesValidator(interfaceAddrsProvider)
		dnsValidator := NewDNSValidator(fs)
		netManager = NewUbuntuNetManager(
			fs,
			cmdRunner,
			ipResolver,
			interfaceConfigurationCreator,
			interfaceAddrsValidator,
			dnsValidator,
			addressBroadcaster,
			logger,
		).(UbuntuNetManager)
	})

	Describe("ComputeNetworkConfig", func() {
		Context("when there is one manual network and neither is marked as default for DNS", func() {
			It("should use the manual network for DNS", func() {
				networks := boshsettings.Networks{
					"manual": factory.Network{DNS: &[]string{"8.8.8.8"}}.Build(),
				}
				stubInterfaces(networks)
				_, _, dnsServers, err := netManager.ComputeNetworkConfig(networks)
				Expect(err).ToNot(HaveOccurred())
				Expect(dnsServers).To(Equal([]string{"8.8.8.8"}))
			})
		})

		Context("when there is a vip network and a manual network and neither is marked as default for DNS", func() {
			It("should use the manual network for DNS", func() {
				networks := boshsettings.Networks{
					"vip":    boshsettings.Network{Type: "vip"},
					"manual": factory.Network{Type: "manual", DNS: &[]string{"8.8.8.8"}}.Build(),
				}
				stubInterfaces(networks)
				_, _, dnsServers, err := netManager.ComputeNetworkConfig(networks)
				Expect(err).ToNot(HaveOccurred())
				Expect(dnsServers).To(Equal([]string{"8.8.8.8"}))
			})
		})
		Context("when there is a vip network and a manual network and the manual network is marked as default for DNS", func() {
			It("should use the manual network for DNS", func() {
				networks := boshsettings.Networks{
					"vip":    boshsettings.Network{Type: "vip"},
					"manual": factory.Network{Type: "manual", DNS: &[]string{"8.8.8.8"}, Default: []string{"dns"}}.Build(),
				}
				stubInterfaces(networks)
				_, _, dnsServers, err := netManager.ComputeNetworkConfig(networks)
				Expect(err).ToNot(HaveOccurred())
				Expect(dnsServers).To(Equal([]string{"8.8.8.8"}))
			})
		})

		Context("when specified more than one DNS", func() {
			It("extracts all DNS servers from the network configured as default DNS", func() {
				networks := boshsettings.Networks{
					"default": factory.Network{
						IP:      "10.10.0.32",
						Netmask: "255.255.255.0",
						Mac:     "aa::bb::cc",
						Default: []string{"dns", "gateway"},
						DNS:     &[]string{"54.209.78.6", "127.0.0.5"},
						Gateway: "10.10.0.1",
					}.Build(),
				}
				stubInterfaces(networks)
				staticInterfaceConfigurations, dhcpInterfaceConfigurations, dnsServers, err := netManager.ComputeNetworkConfig(networks)
				Expect(err).ToNot(HaveOccurred())

				Expect(staticInterfaceConfigurations).To(Equal([]StaticInterfaceConfiguration{
					{
						Name:                "default",
						Address:             "10.10.0.32",
						Netmask:             "255.255.255.0",
						Network:             "10.10.0.0",
						IsDefaultForGateway: true,
						Broadcast:           "10.10.0.255",
						Mac:                 "aa::bb::cc",
						Gateway:             "10.10.0.1",
					},
				}))
				Expect(dhcpInterfaceConfigurations).To(BeEmpty())
				Expect(dnsServers).To(Equal([]string{"54.209.78.6", "127.0.0.5"}))
			})
		})
	})

	Describe("SetupNetworking", func() {
		var (
			dhcpNetwork                                  boshsettings.Network
			staticNetwork                                boshsettings.Network
			expectedNetworkConfigurationForStaticAndDhcp string
			expectedResolvConfHead                       string
		)

		BeforeEach(func() {
			dhcpNetwork = boshsettings.Network{
				Type:    "dynamic",
				Default: []string{"dns"},
				DNS:     []string{"8.8.8.8", "9.9.9.9"},
				Mac:     "fake-dhcp-mac-address",
			}
			staticNetwork = boshsettings.Network{
				Type:    "manual",
				IP:      "1.2.3.4",
				Default: []string{"gateway"},
				Netmask: "255.255.255.0",
				Gateway: "3.4.5.6",
				Mac:     "fake-static-mac-address",
			}
			interfaceAddrsProvider.GetInterfaceAddresses = []boship.InterfaceAddress{
				boship.NewSimpleInterfaceAddress("ethstatic", "1.2.3.4"),
			}
			fs.WriteFileString("/etc/resolv.conf", `
nameserver 8.8.8.8
nameserver 9.9.9.9
`)
			expectedNetworkConfigurationForStaticAndDhcp = `# Generated by bosh-agent
auto lo
iface lo inet loopback

auto ethdhcp
iface ethdhcp inet dhcp

auto ethstatic
iface ethstatic inet static
    address 1.2.3.4
    network 1.2.3.0
    netmask 255.255.255.0
    broadcast 1.2.3.255
    gateway 3.4.5.6

dns-nameservers 8.8.8.8 9.9.9.9`
		})

		Context("networks is preconfigured", func() {
			var networks boshsettings.Networks
			BeforeEach(func() {
				dhcpNetwork.Preconfigured = true
				staticNetwork.Preconfigured = true
				networks = boshsettings.Networks{
					"first":  dhcpNetwork,
					"second": staticNetwork,
				}

				Expect(networks.IsPreconfigured()).To(BeTrue())
			})

			Context("when there are configured DNS servers", func() {
				BeforeEach(func() {
					networks = boshsettings.Networks{
						"first": dhcpNetwork,
					}
				})

				It("writes DNS to /etc/resolvconf/resolv.conf.d/base", func() {
					err := netManager.SetupNetworking(networks, nil)
					Expect(err).ToNot(HaveOccurred())

					resolvConfBase := fs.GetFileTestStat("/etc/resolvconf/resolv.conf.d/base")
					Expect(resolvConfBase).ToNot(BeNil())

					expectedResolvConfBase := `# Generated by bosh-agent
nameserver 8.8.8.8
nameserver 9.9.9.9
`
					Expect(resolvConfBase.StringContents()).To(Equal(expectedResolvConfBase))
				})

				Context("when writing to ../resolv.conf.d/base fails", func() {
					It("fails reporting the error", func() {
						fs.WriteFileError = errors.New("fake-write-file-error")

						err := netManager.SetupNetworking(networks, nil)
						Expect(err).To(HaveOccurred())
						Expect(err.Error()).To(ContainSubstring("Writing to /etc/resolvconf/resolv.conf.d/base"))
					})
				})
			})

			Context("when no DNS servers are configured", func() {
				BeforeEach(func() {
					dhcpNetwork.DNS = []string{}
					networks = boshsettings.Networks{
						"first":  dhcpNetwork,
						"second": staticNetwork,
					}
				})

				Context("when could not read link /etc/resolv.conf", func() {
					It("fails reporting error", func() {
						fs.ReadLinkError = errors.New("fake-read-link-error")

						err := netManager.SetupNetworking(networks, nil)
						Expect(err).To(HaveOccurred())
						Expect(err.Error()).To(ContainSubstring("Reading /etc/resolv.conf symlink"))
					})
				})

				Context("when /etc/resolv.conf is no symlink", func() {
					BeforeEach(func() {
						err := fs.Symlink("/etc/resolv.conf", "/etc/resolv.conf")
						Expect(err).ToNot(HaveOccurred())

						err = fs.WriteFileString("/etc/resolv.conf", "fake-content")
						Expect(err).ToNot(HaveOccurred())
					})

					It("copies /etc/resolv.conf to .../resolv.conf.d/base", func() {
						err := netManager.SetupNetworking(networks, nil)
						Expect(err).ToNot(HaveOccurred())

						contents, err := fs.ReadFile("/etc/resolvconf/resolv.conf.d/base")
						Expect(err).ToNot(HaveOccurred())
						Expect(string(contents)).To(Equal("fake-content"))
					})

					Context("when copying fails", func() {
						It("fails reporting the error", func() {
							fs.CopyFileError = errors.New("fake-copy-error")

							err := netManager.SetupNetworking(networks, nil)
							Expect(err).To(HaveOccurred())
							Expect(err.Error()).To(ContainSubstring("Copying /etc/resolv.conf for backwards compat"))
						})
					})
				})
			})

			It("forces /etc/resolv.conf to be a symlink", func() {
				err := netManager.SetupNetworking(networks, nil)
				Expect(err).ToNot(HaveOccurred())
				Expect(fs.ReadLink("/etc/resolv.conf")).To(Equal("/run/resolvconf/resolv.conf"))
			})

			Context("when symlink command fails", func() {
				BeforeEach(func() {
					fs.SymlinkError = errors.New("fake-symlink-error")
				})

				It("fails reporting error", func() {
					err := netManager.SetupNetworking(networks, nil)
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("Setting up /etc/resolv.conf symlink"))
				})
			})

			It("writes dns servers in /etc/resolvconf/resolv.conf.d/base", func() {
				dhcpNetwork.Preconfigured = true
				staticNetwork.Preconfigured = true
				networks := boshsettings.Networks{
					"first":  dhcpNetwork,
					"second": staticNetwork,
				}

				Expect(networks.IsPreconfigured()).To(BeTrue())

				err := netManager.SetupNetworking(networks, nil)
				Expect(err).ToNot(HaveOccurred())

				resolvConfHead := fs.GetFileTestStat("/etc/resolvconf/resolv.conf.d/base")
				Expect(resolvConfHead).ToNot(BeNil())

				expectedResolvConfHead = `# Generated by bosh-agent
nameserver 8.8.8.8
nameserver 9.9.9.9
`
				Expect(resolvConfHead.StringContents()).To(Equal(expectedResolvConfHead))
			})

			It("run resolvconf -u to update resolv.conf", func() {
				dhcpNetwork.Preconfigured = true
				staticNetwork.Preconfigured = true
				networks := boshsettings.Networks{
					"first":  dhcpNetwork,
					"second": staticNetwork,
				}

				err := netManager.SetupNetworking(networks, nil)
				Expect(err).ToNot(HaveOccurred())

				Expect(len(cmdRunner.RunCommands)).To(Equal(1))
				Expect(cmdRunner.RunCommands[0]).To(Equal([]string{"resolvconf", "-u"}))
			})
		})

		It("writes interfaces in /etc/network/interfaces in alphabetic order", func() {
			anotherDHCPNetwork := boshsettings.Network{
				Type:    "dynamic",
				Default: []string{"dns"},
				DNS:     []string{"8.8.8.8", "9.9.9.9"},
				Mac:     "fake-another-mac-address",
			}

			stubInterfaces(map[string]boshsettings.Network{
				"ethstatic": staticNetwork,
				"ethdhcp1":  dhcpNetwork,
				"ethdhcp0":  anotherDHCPNetwork,
			})

			err := netManager.SetupNetworking(boshsettings.Networks{
				"dhcp-network-1": dhcpNetwork,
				"dhcp-network-2": anotherDHCPNetwork,
				"static-network": staticNetwork,
			}, nil)
			Expect(err).ToNot(HaveOccurred())

			networkConfig := fs.GetFileTestStat("/etc/network/interfaces")
			Expect(networkConfig).ToNot(BeNil())

			expectedNetworkConfigurationForStaticAndDhcp = `# Generated by bosh-agent
auto lo
iface lo inet loopback

auto ethdhcp0
iface ethdhcp0 inet dhcp

auto ethdhcp1
iface ethdhcp1 inet dhcp

auto ethstatic
iface ethstatic inet static
    address 1.2.3.4
    network 1.2.3.0
    netmask 255.255.255.0
    broadcast 1.2.3.255
    gateway 3.4.5.6

dns-nameservers 8.8.8.8 9.9.9.9`
			Expect(networkConfig.StringContents()).To(Equal(expectedNetworkConfigurationForStaticAndDhcp))
		})

		It("configures gateway, broadcast and dns for default network only", func() {
			staticNetwork = boshsettings.Network{
				Type:    "manual",
				IP:      "1.2.3.4",
				Netmask: "255.255.255.0",
				Gateway: "3.4.5.6",
				Mac:     "fake-static-mac-address",
			}
			secondStaticNetwork := boshsettings.Network{
				Type:    "manual",
				IP:      "5.6.7.8",
				Netmask: "255.255.255.0",
				Gateway: "6.7.8.9",
				Mac:     "second-fake-static-mac-address",
				DNS:     []string{"8.8.8.8"},
				Default: []string{"gateway", "dns"},
			}

			stubInterfaces(map[string]boshsettings.Network{
				"eth0": staticNetwork,
				"eth1": secondStaticNetwork,
			})

			interfaceAddrsProvider.GetInterfaceAddresses = []boship.InterfaceAddress{
				boship.NewSimpleInterfaceAddress("eth0", "1.2.3.4"),
				boship.NewSimpleInterfaceAddress("eth1", "5.6.7.8"),
			}

			err := netManager.SetupNetworking(boshsettings.Networks{
				"static-1": staticNetwork,
				"static-2": secondStaticNetwork,
			}, nil)
			Expect(err).ToNot(HaveOccurred())

			networkConfig := fs.GetFileTestStat("/etc/network/interfaces")
			Expect(networkConfig).ToNot(BeNil())
			Expect(networkConfig.StringContents()).To(Equal(`# Generated by bosh-agent
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static
    address 1.2.3.4
    network 1.2.3.0
    netmask 255.255.255.0

auto eth1
iface eth1 inet static
    address 5.6.7.8
    network 5.6.7.0
    netmask 255.255.255.0
    broadcast 5.6.7.255
    gateway 6.7.8.9

dns-nameservers 8.8.8.8`))

		})

		It("writes /etc/network/interfaces without dns-namservers if there are no dns servers", func() {
			staticNetworkWithoutDNS := boshsettings.Network{
				Type:    "manual",
				IP:      "1.2.3.4",
				Default: []string{"gateway"},
				Netmask: "255.255.255.0",
				Gateway: "3.4.5.6",
				Mac:     "fake-static-mac-address",
			}

			stubInterfaces(map[string]boshsettings.Network{
				"ethstatic": staticNetworkWithoutDNS,
			})

			err := netManager.SetupNetworking(boshsettings.Networks{"static-network": staticNetworkWithoutDNS}, nil)
			Expect(err).ToNot(HaveOccurred())

			networkConfig := fs.GetFileTestStat("/etc/network/interfaces")
			Expect(networkConfig).ToNot(BeNil())
			Expect(networkConfig.StringContents()).To(Equal(`# Generated by bosh-agent
auto lo
iface lo inet loopback

auto ethstatic
iface ethstatic inet static
    address 1.2.3.4
    network 1.2.3.0
    netmask 255.255.255.0
    broadcast 1.2.3.255
    gateway 3.4.5.6
`))
		})

		It("returns errors from glob /sys/class/net/", func() {
			fs.GlobErr = errors.New("fs-glob-error")
			err := netManager.SetupNetworking(boshsettings.Networks{"dhcp-network": dhcpNetwork, "static-network": staticNetwork}, nil)
			Expect(err).To(HaveOccurred())
			Expect(err.Error()).To(ContainSubstring("fs-glob-error"))
		})

		It("returns errors from writing the network configuration", func() {
			stubInterfaces(map[string]boshsettings.Network{
				"dhcp":   dhcpNetwork,
				"static": staticNetwork,
			})
			fs.WriteFileError = errors.New("fs-write-file-error")
			err := netManager.SetupNetworking(boshsettings.Networks{"dhcp-network": dhcpNetwork, "static-network": staticNetwork}, nil)
			Expect(err).To(HaveOccurred())
			Expect(err.Error()).To(ContainSubstring("fs-write-file-error"))
		})

		It("returns errors when it can't creating network interface configurations", func() {
			stubInterfaces(map[string]boshsettings.Network{
				"ethdhcp":   dhcpNetwork,
				"ethstatic": staticNetwork,
			})
			staticNetwork.Netmask = "not an ip" //will cause InterfaceConfigurationCreator to fail
			err := netManager.SetupNetworking(boshsettings.Networks{"static-network": staticNetwork}, nil)
			Expect(err).To(HaveOccurred())
			Expect(err.Error()).To(ContainSubstring("Creating interface configurations"))
		})

		It("writes a dhcp configuration if there are dhcp networks", func() {
			stubInterfaces(map[string]boshsettings.Network{
				"ethdhcp":   dhcpNetwork,
				"ethstatic": staticNetwork,
			})

			err := netManager.SetupNetworking(boshsettings.Networks{"dhcp-network": dhcpNetwork, "static-network": staticNetwork}, nil)
			Expect(err).ToNot(HaveOccurred())

			dhcpConfig := fs.GetFileTestStat("/etc/dhcp/dhclient.conf")
			Expect(dhcpConfig).ToNot(BeNil())
			Expect(dhcpConfig.StringContents()).To(Equal(`# Generated by bosh-agent

option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;

send host-name "<hostname>";

request subnet-mask, broadcast-address, time-offset, routers,
	domain-name, domain-name-servers, domain-search, host-name,
	netbios-name-servers, netbios-scope, interface-mtu,
	rfc3442-classless-static-routes, ntp-servers;

prepend domain-name-servers 8.8.8.8, 9.9.9.9;
`))

		})

		It("writes a dhcp configuration without prepended dns servers if there are no dns servers specified", func() {
			dhcpNetworkWithoutDNS := boshsettings.Network{
				Type: "dynamic",
				Mac:  "fake-dhcp-mac-address",
			}

			stubInterfaces(map[string]boshsettings.Network{
				"ethdhcp": dhcpNetwork,
			})

			err := netManager.SetupNetworking(boshsettings.Networks{"dhcp-network": dhcpNetworkWithoutDNS}, nil)
			Expect(err).ToNot(HaveOccurred())

			dhcpConfig := fs.GetFileTestStat("/etc/dhcp/dhclient.conf")
			Expect(dhcpConfig).ToNot(BeNil())
			Expect(dhcpConfig.StringContents()).To(Equal(`# Generated by bosh-agent

option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;

send host-name "<hostname>";

request subnet-mask, broadcast-address, time-offset, routers,
	domain-name, domain-name-servers, domain-search, host-name,
	netbios-name-servers, netbios-scope, interface-mtu,
	rfc3442-classless-static-routes, ntp-servers;

`))

		})

		It("returns an error if it can't write a dhcp configuration", func() {
			stubInterfaces(map[string]boshsettings.Network{
				"ethdhcp":   dhcpNetwork,
				"ethstatic": staticNetwork,
			})

			fs.WriteFileErrors["/etc/dhcp/dhclient.conf"] = errors.New("dhclient.conf-write-error")

			err := netManager.SetupNetworking(boshsettings.Networks{"dhcp-network": dhcpNetwork, "static-network": staticNetwork}, nil)
			Expect(err).To(HaveOccurred())
			Expect(err.Error()).To(ContainSubstring("dhclient.conf-write-error"))
		})

		It("doesn't write a dhcp configuration if there are no dhcp networks", func() {
			stubInterfaces(map[string]boshsettings.Network{
				"ethstatic": staticNetwork,
			})

			err := netManager.SetupNetworking(boshsettings.Networks{"static-network": staticNetwork}, nil)
			Expect(err).ToNot(HaveOccurred())

			dhcpConfig := fs.GetFileTestStat("/etc/dhcp/dhclient.conf")
			Expect(dhcpConfig).To(BeNil())
		})

		It("restarts the networks if /etc/network/interfaces changes", func() {
			initialDhcpConfig := `# Generated by bosh-agent

option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;

send host-name "<hostname>";

request subnet-mask, broadcast-address, time-offset, routers,
	domain-name, domain-name-servers, domain-search, host-name,
	netbios-name-servers, netbios-scope, interface-mtu,
	rfc3442-classless-static-routes, ntp-servers;

prepend domain-name-servers 8.8.8.8, 9.9.9.9;
`

			stubInterfaces(map[string]boshsettings.Network{
				"ethdhcp":   dhcpNetwork,
				"ethstatic": staticNetwork,
			})

			fs.WriteFileString("/etc/dhcp/dhclient.conf", initialDhcpConfig)

			err := netManager.SetupNetworking(boshsettings.Networks{"dhcp-network": dhcpNetwork, "static-network": staticNetwork}, nil)
			Expect(err).ToNot(HaveOccurred())

			Expect(len(cmdRunner.RunCommands)).To(Equal(5))
			Expect(cmdRunner.RunCommands[0]).To(Equal([]string{"pkill", "dhclient"}))
			Expect(cmdRunner.RunCommands[1:3]).To(ContainElement([]string{"resolvconf", "-d", "ethdhcp.dhclient"}))
			Expect(cmdRunner.RunCommands[1:3]).To(ContainElement([]string{"resolvconf", "-d", "ethstatic.dhclient"}))
			Expect(cmdRunner.RunCommands[3]).To(Equal([]string{"ifdown", "--force", "ethdhcp", "ethstatic"}))
			Expect(cmdRunner.RunCommands[4]).To(Equal([]string{"ifup", "--force", "ethdhcp", "ethstatic"}))
		})

		It("doesn't restart the networks if /etc/network/interfaces and /etc/dhcp/dhclient.conf don't change", func() {
			initialDhcpConfig := `# Generated by bosh-agent

option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;

send host-name "<hostname>";

request subnet-mask, broadcast-address, time-offset, routers,
	domain-name, domain-name-servers, domain-search, host-name,
	netbios-name-servers, netbios-scope, interface-mtu,
	rfc3442-classless-static-routes, ntp-servers;

prepend domain-name-servers 8.8.8.8, 9.9.9.9;
`
			stubInterfaces(map[string]boshsettings.Network{
				"ethdhcp":   dhcpNetwork,
				"ethstatic": staticNetwork,
			})

			fs.WriteFileString("/etc/network/interfaces", expectedNetworkConfigurationForStaticAndDhcp)
			fs.WriteFileString("/etc/dhcp/dhclient.conf", initialDhcpConfig)

			err := netManager.SetupNetworking(boshsettings.Networks{"dhcp-network": dhcpNetwork, "static-network": staticNetwork}, nil)
			Expect(err).ToNot(HaveOccurred())

			networkConfig := fs.GetFileTestStat("/etc/network/interfaces")
			Expect(networkConfig.StringContents()).To(Equal(expectedNetworkConfigurationForStaticAndDhcp))
			dhcpConfig := fs.GetFileTestStat("/etc/dhcp/dhclient.conf")
			Expect(dhcpConfig.StringContents()).To(Equal(initialDhcpConfig))

			Expect(len(cmdRunner.RunCommands)).To(Equal(0))
		})

		It("restarts the networks if /etc/dhcp/dhclient.conf changes", func() {
			stubInterfaces(map[string]boshsettings.Network{
				"ethdhcp":   dhcpNetwork,
				"ethstatic": staticNetwork,
			})

			fs.WriteFileString("/etc/network/interfaces", expectedNetworkConfigurationForStaticAndDhcp)

			err := netManager.SetupNetworking(boshsettings.Networks{"dhcp-network": dhcpNetwork, "static-network": staticNetwork}, nil)
			Expect(err).ToNot(HaveOccurred())

			networkConfig := fs.GetFileTestStat("/etc/network/interfaces")
			Expect(networkConfig.StringContents()).To(Equal(expectedNetworkConfigurationForStaticAndDhcp))

			Expect(len(cmdRunner.RunCommands)).To(Equal(5))
			Expect(cmdRunner.RunCommands[0]).To(Equal([]string{"pkill", "dhclient"}))
			Expect(cmdRunner.RunCommands[1:3]).To(ContainElement([]string{"resolvconf", "-d", "ethdhcp.dhclient"}))
			Expect(cmdRunner.RunCommands[1:3]).To(ContainElement([]string{"resolvconf", "-d", "ethstatic.dhclient"}))
			Expect(cmdRunner.RunCommands[3]).To(Equal([]string{"ifdown", "--force", "ethdhcp", "ethstatic"}))
			Expect(cmdRunner.RunCommands[4]).To(Equal([]string{"ifup", "--force", "ethdhcp", "ethstatic"}))
		})

		It("broadcasts MAC addresses for all interfaces", func() {
			stubInterfaces(map[string]boshsettings.Network{
				"ethdhcp":   dhcpNetwork,
				"ethstatic": staticNetwork,
			})

			errCh := make(chan error)
			err := netManager.SetupNetworking(boshsettings.Networks{"dhcp-network": dhcpNetwork, "static-network": staticNetwork}, errCh)
			Expect(err).ToNot(HaveOccurred())

			broadcastErr := <-errCh // wait for all arpings
			Expect(broadcastErr).ToNot(HaveOccurred())

			Expect(addressBroadcaster.BroadcastMACAddressesAddresses).To(Equal([]boship.InterfaceAddress{
				boship.NewSimpleInterfaceAddress("ethstatic", "1.2.3.4"),
				boship.NewResolvingInterfaceAddress("ethdhcp", ipResolver),
			}))

		})

		It("skips vip networks", func() {
			stubInterfaces(map[string]boshsettings.Network{
				"ethdhcp":   dhcpNetwork,
				"ethstatic": staticNetwork,
			})

			vipNetwork := boshsettings.Network{
				Type:    "vip",
				Default: []string{"dns"},
				DNS:     []string{"8.8.8.8", "9.9.9.9"},
				Mac:     "fake-vip-mac-address",
				IP:      "9.8.7.6",
			}

			err := netManager.SetupNetworking(boshsettings.Networks{
				"dhcp-network":   dhcpNetwork,
				"static-network": staticNetwork,
				"vip-network":    vipNetwork,
			}, nil)
			Expect(err).ToNot(HaveOccurred())

			networkConfig := fs.GetFileTestStat("/etc/network/interfaces")
			Expect(networkConfig).ToNot(BeNil())
			Expect(networkConfig.StringContents()).To(Equal(expectedNetworkConfigurationForStaticAndDhcp))
		})

		Context("when manual networks were not configured with proper IP addresses", func() {
			BeforeEach(func() {
				interfaceAddrsProvider.GetInterfaceAddresses = []boship.InterfaceAddress{
					boship.NewSimpleInterfaceAddress("ethstatic", "1.2.3.5"),
				}
			})

			It("fails", func() {
				stubInterfaces(map[string]boshsettings.Network{
					"ethstatic": staticNetwork,
				})

				errCh := make(chan error)
				err := netManager.SetupNetworking(boshsettings.Networks{"static-network": staticNetwork}, errCh)
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("Validating static network configuration"))
			})
		})

		Context("when dns is not properly configured", func() {
			BeforeEach(func() {
				fs.WriteFileString("/etc/resolv.conf", "")
			})

			It("fails", func() {
				staticNetwork = boshsettings.Network{
					Type:    "manual",
					IP:      "1.2.3.4",
					Default: []string{"dns"},
					DNS:     []string{"8.8.8.8"},
					Netmask: "255.255.255.0",
					Gateway: "3.4.5.6",
					Mac:     "fake-static-mac-address",
				}

				stubInterfaces(map[string]boshsettings.Network{
					"ethstatic": staticNetwork,
				})

				errCh := make(chan error)
				err := netManager.SetupNetworking(boshsettings.Networks{"static-network": staticNetwork}, errCh)
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("Validating dns configuration"))
			})
		})

		Context("when no MAC address is provided in the settings", func() {
			It("configures network for single device", func() {
				staticNetworkWithoutMAC := boshsettings.Network{
					Type:    "manual",
					IP:      "2.2.2.2",
					Default: []string{"gateway"},
					Netmask: "255.255.255.0",
					Gateway: "3.4.5.6",
				}

				stubInterfaces(
					map[string]boshsettings.Network{
						"ethstatic": staticNetwork,
					},
				)
				interfaceAddrsProvider.GetInterfaceAddresses = []boship.InterfaceAddress{
					boship.NewSimpleInterfaceAddress("ethstatic", "2.2.2.2"),
				}

				err := netManager.SetupNetworking(boshsettings.Networks{
					"static-network": staticNetworkWithoutMAC,
				}, nil)
				Expect(err).ToNot(HaveOccurred())

				networkConfig := fs.GetFileTestStat("/etc/network/interfaces")
				Expect(networkConfig).ToNot(BeNil())

				expectedNetworkConfiguration := `# Generated by bosh-agent
auto lo
iface lo inet loopback

auto ethstatic
iface ethstatic inet static
    address 2.2.2.2
    network 2.2.2.0
    netmask 255.255.255.0
    broadcast 2.2.2.255
    gateway 3.4.5.6
`

				Expect(networkConfig.StringContents()).To(Equal(expectedNetworkConfiguration))
			})

			It("configures network for a single physical device, when a virtual device is also present", func() {
				staticNetworkWithoutMAC := boshsettings.Network{
					Type:    "manual",
					IP:      "2.2.2.2",
					Default: []string{"gateway"},
					Netmask: "255.255.255.0",
					Gateway: "3.4.5.6",
				}

				stubInterfacesWithVirtual(
					map[string]boshsettings.Network{
						"ethstatic": staticNetwork,
					},
					[]string{"virtual"},
				)
				interfaceAddrsProvider.GetInterfaceAddresses = []boship.InterfaceAddress{
					boship.NewSimpleInterfaceAddress("ethstatic", "2.2.2.2"),
				}

				err := netManager.SetupNetworking(boshsettings.Networks{
					"static-network": staticNetworkWithoutMAC,
				}, nil)
				Expect(err).ToNot(HaveOccurred())

				networkConfig := fs.GetFileTestStat("/etc/network/interfaces")
				Expect(networkConfig).ToNot(BeNil())

				expectedNetworkConfiguration := `# Generated by bosh-agent
auto lo
iface lo inet loopback

auto ethstatic
iface ethstatic inet static
    address 2.2.2.2
    network 2.2.2.0
    netmask 255.255.255.0
    broadcast 2.2.2.255
    gateway 3.4.5.6
`

				Expect(networkConfig.StringContents()).To(Equal(expectedNetworkConfiguration))
			})
		})
	})

	Describe("GetConfiguredNetworkInterfaces", func() {
		Context("when there are network devices", func() {
			BeforeEach(func() {
				interfacePaths := []string{}
				interfacePaths = append(interfacePaths, writeNetworkDevice("fake-eth0", "aa:bb", true))
				interfacePaths = append(interfacePaths, writeNetworkDevice("fake-eth1", "cc:dd", true))
				interfacePaths = append(interfacePaths, writeNetworkDevice("fake-eth2", "ee:ff", true))
				fs.SetGlob("/sys/class/net/*", interfacePaths)
			})

			It("returns networks that are defined in /etc/network/interfaces", func() {
				cmdRunner.AddCmdResult("ifup --no-act fake-eth0", fakesys.FakeCmdResult{
					Stdout:     "",
					Stderr:     "ifup: interface fake-eth0 already configured",
					ExitStatus: 0,
				})

				cmdRunner.AddCmdResult("ifup --no-act fake-eth1", fakesys.FakeCmdResult{
					Stdout:     "",
					Stderr:     "Ignoring unknown interface fake-eth1=fake-eth1.",
					ExitStatus: 0,
				})

				cmdRunner.AddCmdResult("ifup --no-act fake-eth2", fakesys.FakeCmdResult{
					Stdout:     "",
					Stderr:     "ifup: interface fake-eth2 already configured",
					ExitStatus: 0,
				})

				interfaces, err := netManager.GetConfiguredNetworkInterfaces()
				Expect(err).ToNot(HaveOccurred())

				Expect(interfaces).To(ConsistOf("fake-eth0", "fake-eth2"))
			})
		})

		Context("when there are no network devices", func() {
			It("returns empty list", func() {
				interfaces, err := netManager.GetConfiguredNetworkInterfaces()
				Expect(err).ToNot(HaveOccurred())
				Expect(interfaces).To(Equal([]string{}))
			})
		})
	})
}
func describeDummyPlatform() {
	var (
		platform           Platform
		collector          boshstats.Collector
		fs                 *fakesys.FakeFileSystem
		cmdRunner          boshsys.CmdRunner
		dirProvider        boshdirs.Provider
		devicePathResolver boshdpresolv.DevicePathResolver
		logger             boshlog.Logger
	)

	BeforeEach(func() {
		collector = &fakestats.FakeCollector{}
		fs = fakesys.NewFakeFileSystem()
		cmdRunner = fakesys.NewFakeCmdRunner()
		dirProvider = boshdirs.NewProvider("/fake-dir")
		devicePathResolver = fakedpresolv.NewFakeDevicePathResolver()
		logger = boshlog.NewLogger(boshlog.LevelNone)
	})

	JustBeforeEach(func() {
		platform = NewDummyPlatform(
			collector,
			fs,
			cmdRunner,
			dirProvider,
			devicePathResolver,
			logger,
		)
	})

	Describe("GetDefaultNetwork", func() {
		It("returns the contents of dummy-defaults-network-settings.json since that's what the dummy cpi writes", func() {
			settingsFilePath := "/fake-dir/bosh/dummy-default-network-settings.json"
			fs.WriteFileString(settingsFilePath, `{"IP": "1.2.3.4"}`)

			network, err := platform.GetDefaultNetwork()
			Expect(err).NotTo(HaveOccurred())

			Expect(network.IP).To(Equal("1.2.3.4"))
		})
	})

	Describe("GetCertManager", func() {
		It("returns a dummy cert manager", func() {
			certManager := platform.GetCertManager()

			Expect(certManager.UpdateCertificates("")).Should(BeNil())
		})
	})

	Describe("MountPersistentDisk", func() {
		var diskSettings boshsettings.DiskSettings
		var mountsPath, managedSettingsPath, formattedDisksPath string

		BeforeEach(func() {
			diskSettings = boshsettings.DiskSettings{ID: "somediskid"}
			mountsPath = filepath.Join(dirProvider.BoshDir(), "mounts.json")
			managedSettingsPath = filepath.Join(dirProvider.BoshDir(), "managed_disk_settings.json")
			formattedDisksPath = filepath.Join(dirProvider.BoshDir(), "formatted_disks.json")
		})

		It("Mounts a persistent disk", func() {
			mountsContent, _ := fs.ReadFileString(mountsPath)
			Expect(mountsContent).To(Equal(""))

			err := platform.MountPersistentDisk(diskSettings, "/dev/potato")
			Expect(err).NotTo(HaveOccurred())

			mountsContent, _ = fs.ReadFileString(mountsPath)
			Expect(mountsContent).To(Equal(`[{"MountDir":"/dev/potato","DiskCid":"somediskid"}]`))
		})

		It("Updates the managed disk settings", func() {
			lastMountedCid, _ := fs.ReadFileString(managedSettingsPath)
			Expect(lastMountedCid).To(Equal(""))

			err := platform.MountPersistentDisk(diskSettings, "/dev/potato")
			Expect(err).NotTo(HaveOccurred())

			lastMountedCid, _ = fs.ReadFileString(managedSettingsPath)
			Expect(lastMountedCid).To(Equal("somediskid"))
		})

		It("Updates the formatted disks", func() {
			formattedDisks, _ := fs.ReadFileString(formattedDisksPath)
			Expect(formattedDisks).To(Equal(""))

			err := platform.MountPersistentDisk(diskSettings, "/dev/potato")
			Expect(err).NotTo(HaveOccurred())

			formattedDisks, _ = fs.ReadFileString(formattedDisksPath)
			Expect(formattedDisks).To(Equal(`[{"DiskCid":"somediskid"}]`))
		})

		Context("Device has already been mounted as expected", func() {
			BeforeEach(func() {
				fs.WriteFileString(managedSettingsPath, "somediskid")
				fs.WriteFileString(mountsPath, `[{"MountDir":"/dev/potato","DiskCid":"somediskid"}]`)
			})

			It("Does not mount in new location", func() {
				err := platform.MountPersistentDisk(diskSettings, "/dev/potato")
				Expect(err).NotTo(HaveOccurred())

				mountsContent, _ := fs.ReadFileString(mountsPath)
				Expect(mountsContent).To(Equal(`[{"MountDir":"/dev/potato","DiskCid":"somediskid"}]`))
			})
		})
	})

	Describe("UnmountPersistentDisk", func() {
		Context("when there are two mounted persistent disks in the mounts json", func() {
			BeforeEach(func() {
				var mounts []mount
				mounts = append(mounts, mount{MountDir: "dir1", DiskCid: "cid1"})
				mounts = append(mounts, mount{MountDir: "dir2", DiskCid: "cid2"})
				mountsJSON, err := json.Marshal(mounts)
				Expect(err).NotTo(HaveOccurred())

				mountsPath := filepath.Join(dirProvider.BoshDir(), "mounts.json")
				fs.WriteFile(mountsPath, mountsJSON)
			})

			It("removes one of the disks from the mounts json", func() {
				unmounted, err := platform.UnmountPersistentDisk(settings.DiskSettings{ID: "cid1"})
				Expect(err).NotTo(HaveOccurred())
				Expect(unmounted).To(Equal(true))

				_, isMountPoint, err := platform.IsMountPoint("dir1")
				Expect(err).NotTo(HaveOccurred())
				Expect(isMountPoint).To(Equal(false))

				_, isMountPoint, err = platform.IsMountPoint("dir2")
				Expect(err).NotTo(HaveOccurred())
				Expect(isMountPoint).To(Equal(true))
			})
		})
	})

	Describe("SetDiskAssociations", func() {
		It("writes the associations to the file", func() {
			diskName1 := "disk1"
			diskName2 := "disk2"

			err := platform.AssociateDisk(diskName1, boshsettings.DiskSettings{})
			Expect(err).NotTo(HaveOccurred())

			err = platform.AssociateDisk(diskName2, boshsettings.DiskSettings{})
			Expect(err).NotTo(HaveOccurred())
			diskAssociationsPath := filepath.Join(dirProvider.BoshDir(), "disk_associations.json")

			actualDiskNames := []string{}
			fileContent, err := fs.ReadFile(diskAssociationsPath)
			Expect(err).NotTo(HaveOccurred())

			err = json.Unmarshal(fileContent, &actualDiskNames)
			Expect(err).NotTo(HaveOccurred())

			Expect(actualDiskNames).To(ConsistOf([]string{
				diskName1,
				diskName2,
			}))
		})
	})

	Describe("IsPersistentDiskMountable", func() {
		BeforeEach(func() {
			formattedDisksPath := filepath.Join(dirProvider.BoshDir(), "formatted_disks.json")
			fs.WriteFileString(formattedDisksPath, `[{"DiskCid": "my-disk-id"}]`)
		})

		Context("when disk has been formatted", func() {
			It("returns true with no error", func() {
				diskSettings := boshsettings.DiskSettings{ID: "my-disk-id"}

				mountable, err := platform.IsPersistentDiskMountable(diskSettings)
				Expect(err).ToNot(HaveOccurred())
				Expect(mountable).To(Equal(true))
			})
		})

		Context("when disk has NOT been formatted", func() {
			It("returns false with no error", func() {
				diskSettings := boshsettings.DiskSettings{ID: "some-other-disk-id"}

				mountable, err := platform.IsPersistentDiskMountable(diskSettings)
				Expect(err).ToNot(HaveOccurred())
				Expect(mountable).To(Equal(false))
			})
		})
	})

	Describe("SetUserPassword", func() {
		It("writes the password to a file", func() {
			err := platform.SetUserPassword("user-name", "fake-password")
			Expect(err).NotTo(HaveOccurred())

			userPasswordsPath := filepath.Join(dirProvider.BoshDir(), "user-name", CredentialFileName)
			password, err := fs.ReadFileString(userPasswordsPath)
			Expect(err).NotTo(HaveOccurred())
			Expect(password).To(Equal("fake-password"))
		})

		It("writes the passwords to different files for each user", func() {
			err := platform.SetUserPassword("user-name1", "fake-password1")
			Expect(err).NotTo(HaveOccurred())
			err = platform.SetUserPassword("user-name2", "fake-password2")
			Expect(err).NotTo(HaveOccurred())

			userPasswordsPath := filepath.Join(dirProvider.BoshDir(), "user-name1", CredentialFileName)
			password, err := fs.ReadFileString(userPasswordsPath)
			Expect(err).NotTo(HaveOccurred())
			Expect(password).To(Equal("fake-password1"))

			userPasswordsPath = filepath.Join(dirProvider.BoshDir(), "user-name2", CredentialFileName)
			password, err = fs.ReadFileString(userPasswordsPath)
			Expect(err).NotTo(HaveOccurred())
			Expect(password).To(Equal("fake-password2"))
		})
	})

	Describe("SetupDataDir", func() {
		It("creates a link from BASEDIR/sys to BASEDIR/data/sys", func() {
			err := platform.SetupDataDir()
			Expect(err).NotTo(HaveOccurred())

			stat := fs.GetFileTestStat(filepath.Clean("/fake-dir/sys"))

			Expect(stat).ToNot(BeNil())
			Expect(stat.SymlinkTarget).To(Equal("/fake-dir/data/sys"))
		})
	})

	Describe("SetupBlobsDir", func() {
		It("creates a blobs folder under BASEDIR/DATADIR with correct permissions", func() {
			err := platform.SetupBlobsDir()
			Expect(err).NotTo(HaveOccurred())

			stat := fs.GetFileTestStat(filepath.Clean("/fake-dir/data/blobs"))

			Expect(stat.FileType).To(Equal(fakesys.FakeFileTypeDir))
			Expect(stat.FileMode).To(Equal(os.FileMode(0700)))
		})
	})
}
func init() {
	Describe("concreteManagerProvider", func() {
		Describe("NewManager", func() {
			It("returns manager with tasks.json as its tasks path", func() {
				logger := boshlog.NewLogger(boshlog.LevelNone)
				fs := fakesys.NewFakeFileSystem()

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

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

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

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

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

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

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

				err = manager.AddInfo(boshtask.Info{
					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.GetInfos()
				Expect(err).ToNot(HaveOccurred())
				Expect(taskInfos).To(ConsistOf([]boshtask.Info{
					boshtask.Info{
						TaskID:  "fake-task-id-1",
						Method:  "fake-method-1",
						Payload: []byte("fake-payload-1"),
					},
					boshtask.Info{
						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.GetInfos()
				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.AddInfo(boshtask.Info{
					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.GetInfos()
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-read-error"))
			})
		})

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

				err = manager.AddInfo(boshtask.Info{
					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.Info

				err = json.Unmarshal(content, &decodedMap)
				Expect(err).ToNot(HaveOccurred())
				Expect(decodedMap).To(Equal(map[string]boshtask.Info{
					"fake-task-id-1": boshtask.Info{
						TaskID:  "fake-task-id-1",
						Method:  "fake-method-1",
						Payload: []byte("fake-payload-1"),
					},
					"fake-task-id-2": boshtask.Info{
						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.WriteFileError = errors.New("fake-write-error")

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

		Describe("RemoveInfo", func() {
			BeforeEach(func() {
				err := manager.AddInfo(boshtask.Info{
					TaskID:  "fake-task-id-1",
					Method:  "fake-method-1",
					Payload: []byte("fake-payload-1"),
				})
				Expect(err).ToNot(HaveOccurred())

				err = manager.AddInfo(boshtask.Info{
					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.RemoveInfo("fake-task-id-1")
				Expect(err).ToNot(HaveOccurred())

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

				var decodedMap map[string]boshtask.Info

				err = json.Unmarshal(content, &decodedMap)
				Expect(err).ToNot(HaveOccurred())
				Expect(decodedMap).To(Equal(map[string]boshtask.Info{
					"fake-task-id-2": boshtask.Info{
						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.RemoveInfo("fake-unknown-task-id")
				Expect(err).ToNot(HaveOccurred())
			})

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

				err := manager.RemoveInfo("fake-task-id")
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-write-error"))
			})
		})
	})
}