})

	Describe("#LoadState", func() {
		Context("when there is some failure loading", func() {
			Context("when SyncDNSState file cannot be read", func() {
				It("should fail loading DNS state", func() {
					_, err = syncDNSState.LoadState()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("reading state file"))
				})
			})

			Context("when SyncDNSState file cannot be unmarshalled", func() {
				Context("when state file is invalid JSON", func() {
					It("should fail loading DNS state", func() {
						fakeFileSystem.WriteFile(path, []byte("fake-state-file"))

						_, err := syncDNSState.LoadState()
						Expect(err).To(HaveOccurred())
						Expect(err.Error()).To(ContainSubstring("unmarshalling state file"))
					})
				})
			})
		})

		Context("when there are no failures", func() {
			It("loads and unmarshalls the DNS state with Version", func() {
				fakeFileSystem.WriteFile(path, []byte("{\"version\": 1234}"))

				localDNSState, err := syncDNSState.LoadState()
				Expect(err).ToNot(HaveOccurred())
Beispiel #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))
					})
				})
			})
		})
	})
}
Beispiel #3
0
func init() {
	Describe("bootstrap", func() {
		Describe("Run", func() {
			var (
				platform    *fakeplatform.FakePlatform
				dirProvider boshdir.Provider

				settingsSource  *fakeinf.FakeSettingsSource
				settingsService *fakesettings.FakeSettingsService
			)

			BeforeEach(func() {
				platform = fakeplatform.NewFakePlatform()
				dirProvider = boshdir.NewProvider("/var/vcap")

				settingsSource = &fakeinf.FakeSettingsSource{}
				settingsService = &fakesettings.FakeSettingsService{}
			})

			bootstrap := func() error {
				logger := boshlog.NewLogger(boshlog.LevelNone)
				return NewBootstrap(platform, dirProvider, settingsService, logger).Run()
			}

			It("sets up runtime configuration", func() {
				err := bootstrap()
				Expect(err).NotTo(HaveOccurred())
				Expect(platform.SetupRuntimeConfigurationWasInvoked).To(BeTrue())
			})

			Describe("SSH tunnel setup for registry", func() {
				It("returns error without configuring ssh on the platform if getting public key fails", func() {
					settingsService.PublicKeyErr = errors.New("fake-get-public-key-err")

					err := bootstrap()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-get-public-key-err"))

					Expect(platform.SetupSSHCalled).To(BeFalse())
				})

				Context("when public key is not empty", func() {
					BeforeEach(func() {
						settingsService.PublicKey = "fake-public-key"
					})

					It("gets the public key and sets up ssh via the platform", func() {
						err := bootstrap()
						Expect(err).NotTo(HaveOccurred())

						Expect(platform.SetupSSHPublicKey).To(Equal("fake-public-key"))
						Expect(platform.SetupSSHUsername).To(Equal("vcap"))
					})

					It("returns error if configuring ssh on the platform fails", func() {
						platform.SetupSSHErr = errors.New("fake-setup-ssh-err")

						err := bootstrap()
						Expect(err).To(HaveOccurred())
						Expect(err.Error()).To(ContainSubstring("fake-setup-ssh-err"))
					})
				})

				Context("when public key key is empty", func() {
					BeforeEach(func() {
						settingsSource.PublicKey = ""
					})

					It("gets the public key and does not setup SSH", func() {
						err := bootstrap()
						Expect(err).NotTo(HaveOccurred())

						Expect(platform.SetupSSHCalled).To(BeFalse())
					})
				})
			})

			It("sets up hostname", func() {
				settingsService.Settings.AgentID = "foo-bar-baz-123"

				err := bootstrap()
				Expect(err).NotTo(HaveOccurred())
				Expect(platform.SetupHostnameHostname).To(Equal("foo-bar-baz-123"))
			})

			It("fetches initial settings", func() {
				err := bootstrap()
				Expect(err).NotTo(HaveOccurred())
				Expect(settingsService.SettingsWereLoaded).To(BeTrue())
			})

			It("returns error from loading initial settings", func() {
				settingsService.LoadSettingsError = errors.New("fake-load-error")

				err := bootstrap()
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-load-error"))
			})

			It("sets up networking", func() {
				networks := boshsettings.Networks{
					"bosh": boshsettings.Network{},
				}
				settingsService.Settings.Networks = networks

				err := bootstrap()
				Expect(err).NotTo(HaveOccurred())
				Expect(platform.SetupNetworkingNetworks).To(Equal(networks))
			})

			It("sets up ephemeral disk", func() {
				settingsService.Settings.Disks = boshsettings.Disks{
					Ephemeral: "fake-ephemeral-disk-setting",
				}

				platform.GetEphemeralDiskPathRealPath = "/dev/sda"

				err := bootstrap()
				Expect(err).NotTo(HaveOccurred())
				Expect(platform.SetupEphemeralDiskWithPathDevicePath).To(Equal("/dev/sda"))
				Expect(platform.GetEphemeralDiskPathSettings).To(Equal(boshsettings.DiskSettings{
					VolumeID: "fake-ephemeral-disk-setting",
					Path:     "fake-ephemeral-disk-setting",
				}))
			})

			It("returns error if setting ephemeral disk fails", func() {
				platform.SetupEphemeralDiskWithPathErr = errors.New("fake-setup-ephemeral-disk-err")
				err := bootstrap()
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-setup-ephemeral-disk-err"))
			})

			It("sets up raw ephemeral disks if paths exist", func() {
				settingsService.Settings.Disks = boshsettings.Disks{
					RawEphemeral: []boshsettings.DiskSettings{{Path: "/dev/xvdb"}, {Path: "/dev/xvdc"}},
				}

				err := bootstrap()
				Expect(err).NotTo(HaveOccurred())
				Expect(platform.SetupRawEphemeralDisksCallCount).To(Equal(1))
				Expect(len(platform.SetupRawEphemeralDisksDevices)).To(Equal(2))
				Expect(platform.SetupRawEphemeralDisksDevices[0].Path).To(Equal("/dev/xvdb"))
				Expect(platform.SetupRawEphemeralDisksDevices[1].Path).To(Equal("/dev/xvdc"))
			})

			It("returns error if setting raw ephemeral disks fails", func() {
				platform.SetupRawEphemeralDisksErr = errors.New("fake-setup-raw-ephemeral-disks-err")
				err := bootstrap()
				Expect(platform.SetupRawEphemeralDisksCallCount).To(Equal(1))
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-setup-raw-ephemeral-disks-err"))
			})

			It("sets up data dir", func() {
				err := bootstrap()
				Expect(err).NotTo(HaveOccurred())
				Expect(platform.SetupDataDirCalled).To(BeTrue())
			})

			It("returns error if set up of data dir fails", func() {
				platform.SetupDataDirErr = errors.New("fake-setup-data-dir-err")
				err := bootstrap()
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-setup-data-dir-err"))
			})

			It("sets up tmp dir", func() {
				err := bootstrap()
				Expect(err).NotTo(HaveOccurred())
				Expect(platform.SetupTmpDirCalled).To(BeTrue())
			})

			It("returns error if set up of tmp dir fails", func() {
				platform.SetupTmpDirErr = errors.New("fake-setup-tmp-dir-err")
				err := bootstrap()
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-setup-tmp-dir-err"))
			})

			It("mounts persistent disk", func() {
				settingsService.Settings.Disks = boshsettings.Disks{
					Persistent: map[string]interface{}{
						"vol-123": map[string]interface{}{
							"volume_id": "2",
							"path":      "/dev/sdb",
						},
					},
				}

				err := bootstrap()
				Expect(err).NotTo(HaveOccurred())
				Expect(platform.MountPersistentDiskSettings).To(Equal(boshsettings.DiskSettings{
					ID:       "vol-123",
					VolumeID: "2",
					Path:     "/dev/sdb",
				}))
				Expect(platform.MountPersistentDiskMountPoint).To(Equal(dirProvider.StoreDir()))
			})

			It("errors if there is more than one persistent disk", func() {
				settingsService.Settings.Disks = boshsettings.Disks{
					Persistent: map[string]interface{}{
						"vol-123": "/dev/sdb",
						"vol-456": "/dev/sdc",
					},
				}

				err := bootstrap()
				Expect(err).To(HaveOccurred())
			})

			It("does not try to mount when no persistent disk", func() {
				settingsService.Settings.Disks = boshsettings.Disks{
					Persistent: map[string]interface{}{},
				}

				err := bootstrap()
				Expect(err).NotTo(HaveOccurred())
				Expect(platform.MountPersistentDiskSettings).To(Equal(boshsettings.DiskSettings{}))
				Expect(platform.MountPersistentDiskMountPoint).To(Equal(""))
			})

			It("grows the root filesystem", func() {
				err := bootstrap()
				Expect(err).NotTo(HaveOccurred())
				Expect(platform.SetupRootDiskCalledTimes).To(Equal(1))
			})

			It("returns an error if growing the root filesystem fails", func() {
				platform.SetupRootDiskError = errors.New("growfs failed")

				err := bootstrap()
				Expect(err).To(HaveOccurred())
				Expect(platform.SetupRootDiskCalledTimes).To(Equal(1))
				Expect(err.Error()).To(ContainSubstring("growfs failed"))
			})

			It("sets root and vcap passwords", func() {
				settingsService.Settings.Env.Bosh.Password = "******"
				settingsService.Settings.Env.Bosh.KeepRootPassword = false

				err := bootstrap()
				Expect(err).NotTo(HaveOccurred())
				Expect(2).To(Equal(len(platform.UserPasswords)))
				Expect("some-encrypted-password").To(Equal(platform.UserPasswords["root"]))
				Expect("some-encrypted-password").To(Equal(platform.UserPasswords["vcap"]))
			})

			It("does not change root password if keep_root_password is set to true", func() {
				settingsService.Settings.Env.Bosh.Password = "******"
				settingsService.Settings.Env.Bosh.KeepRootPassword = true

				err := bootstrap()
				Expect(err).NotTo(HaveOccurred())
				Expect(1).To(Equal(len(platform.UserPasswords)))
				Expect("some-encrypted-password").ToNot(Equal(platform.UserPasswords["root"]))
				Expect("some-encrypted-password").To(Equal(platform.UserPasswords["vcap"]))
			})

			It("does not set password if not provided", func() {
				settingsService.Settings.Env.Bosh.KeepRootPassword = false

				err := bootstrap()
				Expect(err).NotTo(HaveOccurred())
				Expect(0).To(Equal(len(platform.UserPasswords)))
			})

			It("sets ntp", func() {
				settingsService.Settings.Ntp = []string{
					"0.north-america.pool.ntp.org",
					"1.north-america.pool.ntp.org",
				}

				err := bootstrap()
				Expect(err).NotTo(HaveOccurred())
				Expect(2).To(Equal(len(platform.SetTimeWithNtpServersServers)))
				Expect("0.north-america.pool.ntp.org").To(Equal(platform.SetTimeWithNtpServersServers[0]))
				Expect("1.north-america.pool.ntp.org").To(Equal(platform.SetTimeWithNtpServersServers[1]))
			})

			It("setups up monit user", func() {
				err := bootstrap()
				Expect(err).NotTo(HaveOccurred())
				Expect(platform.SetupMonitUserSetup).To(BeTrue())
			})

			It("starts monit", func() {
				err := bootstrap()
				Expect(err).NotTo(HaveOccurred())
				Expect(platform.StartMonitStarted).To(BeTrue())
			})
		})

		Describe("Network setup exercised by Run", func() {
			var (
				settingsJSON string

				fs                     *fakesys.FakeFileSystem
				platform               boshplatform.Platform
				boot                   Bootstrap
				defaultNetworkResolver boshsettings.DefaultNetworkResolver
				logger                 boshlog.Logger
				dirProvider            boshdirs.Provider

				interfaceAddrsProvider *fakeip.FakeInterfaceAddressesProvider
			)

			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
			}

			stubInterfaces := func(interfaces [][]string) {
				var interfacePaths []string

				for _, iface := range interfaces {
					interfaceName := iface[0]
					interfaceMAC := iface[1]
					interfaceType := iface[2]
					isPhysical := interfaceType == "physical"
					interfacePaths = append(interfacePaths, writeNetworkDevice(interfaceName, interfaceMAC, isPhysical))
				}

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

			BeforeEach(func() {
				fs = fakesys.NewFakeFileSystem()
				runner := fakesys.NewFakeCmdRunner()
				dirProvider = boshdirs.NewProvider("/var/vcap/bosh")

				linuxOptions := boshplatform.LinuxOptions{
					CreatePartitionIfNoEphemeralDisk: true,
				}

				logger = boshlog.NewLogger(boshlog.LevelNone)

				diskManager := fakedisk.NewFakeDiskManager()
				diskManager.FakeMountsSearcher.SearchMountsMounts = []boshdisk.Mount{
					{MountPoint: "/", PartitionPath: "rootfs"},
					{MountPoint: "/", PartitionPath: "/dev/vda1"},
				}

				// for the GrowRootFS call to findRootDevicePath
				runner.AddCmdResult(
					"readlink -f /dev/vda1",
					fakesys.FakeCmdResult{Stdout: "/dev/vda1"},
				)

				// for the createEphemeralPartitionsOnRootDevice call to findRootDevicePath
				runner.AddCmdResult(
					"readlink -f /dev/vda1",
					fakesys.FakeCmdResult{Stdout: "/dev/vda1"},
				)

				diskManager.FakeRootDevicePartitioner.GetDeviceSizeInBytesSizes["/dev/vda"] = 1024 * 1024 * 1024

				udev := boshudev.NewConcreteUdevDevice(runner, logger)
				linuxCdrom := boshcdrom.NewLinuxCdrom("/dev/sr0", udev, runner)
				linuxCdutil := boshcdrom.NewCdUtil(dirProvider.SettingsDir(), fs, linuxCdrom, logger)

				compressor := boshcmd.NewTarballCompressor(runner, fs)
				copier := boshcmd.NewCpCopier(runner, fs, logger)

				sigarCollector := boshsigar.NewSigarStatsCollector(&sigar.ConcreteSigar{})

				vitalsService := boshvitals.NewService(sigarCollector, dirProvider)

				ipResolver := boship.NewResolver(boship.NetworkInterfaceToAddrsFunc)

				arping := bosharp.NewArping(runner, fs, logger, boshplatform.ArpIterations, boshplatform.ArpIterationDelay, boshplatform.ArpInterfaceCheckDelay)
				interfaceConfigurationCreator := boshnet.NewInterfaceConfigurationCreator(logger)

				interfaceAddrsProvider = &fakeip.FakeInterfaceAddressesProvider{}
				interfaceAddressesValidator := boship.NewInterfaceAddressesValidator(interfaceAddrsProvider)
				dnsValidator := boshnet.NewDNSValidator(fs)
				fs.WriteFileString("/etc/resolv.conf", "8.8.8.8 4.4.4.4")
				ubuntuNetManager := boshnet.NewUbuntuNetManager(fs, runner, ipResolver, interfaceConfigurationCreator, interfaceAddressesValidator, dnsValidator, arping, logger)

				ubuntuCertManager := boshcert.NewUbuntuCertManager(fs, runner, 1, logger)

				monitRetryable := boshplatform.NewMonitRetryable(runner)
				monitRetryStrategy := boshretry.NewAttemptRetryStrategy(10, 1*time.Second, monitRetryable, logger)

				devicePathResolver := devicepathresolver.NewIdentityDevicePathResolver()

				routesSearcher := boshnet.NewCmdRoutesSearcher(runner)
				defaultNetworkResolver = boshnet.NewDefaultNetworkResolver(routesSearcher, ipResolver)
				state, err := boshplatform.NewBootstrapState(fs, "/tmp/agent_state.json")
				Expect(err).NotTo(HaveOccurred())

				platform = boshplatform.NewLinuxPlatform(
					fs,
					runner,
					sigarCollector,
					compressor,
					copier,
					dirProvider,
					vitalsService,
					linuxCdutil,
					diskManager,
					ubuntuNetManager,
					ubuntuCertManager,
					monitRetryStrategy,
					devicePathResolver,
					500*time.Millisecond,
					state,
					linuxOptions,
					logger,
					defaultNetworkResolver,
				)
			})

			JustBeforeEach(func() {
				settingsPath := filepath.Join("bosh", "settings.json")

				var settings boshsettings.Settings
				json.Unmarshal([]byte(settingsJSON), &settings)

				settingsSource := fakeinf.FakeSettingsSource{
					PublicKey:     "123",
					SettingsValue: settings,
				}

				settingsService := boshsettings.NewService(
					platform.GetFs(),
					settingsPath,
					settingsSource,
					platform,
					logger,
				)

				boot = NewBootstrap(
					platform,
					dirProvider,
					settingsService,
					logger,
				)
			})

			Context("when a single network configuration is provided, with a MAC address", func() {
				BeforeEach(func() {
					settingsJSON = `{
					"networks": {
						"netA": {
							"default": ["dns", "gateway"],
							"ip": "2.2.2.2",
							"dns": [
								"8.8.8.8",
								"4.4.4.4"
							],
							"netmask": "255.255.255.0",
							"gateway": "2.2.2.0",
							"mac": "aa:bb:cc"
						}
					}
				}`
				})

				Context("and no physical network interfaces exist", func() {
					Context("and a single virtual network interface exists", func() {
						BeforeEach(func() {
							stubInterfaces([][]string{[]string{"lo", "aa:bb:cc", "virtual"}})
						})

						It("raises an error", func() {
							err := boot.Run()
							Expect(err).To(HaveOccurred())
							Expect(err.Error()).To(ContainSubstring("Number of network settings '1' is greater than the number of network devices '0"))
						})
					})
				})

				Context("and a single physical network interface exists", func() {
					BeforeEach(func() {
						stubInterfaces([][]string{[]string{"eth0", "aa:bb:cc", "physical"}})
						interfaceAddrsProvider.GetInterfaceAddresses = []boship.InterfaceAddress{
							boship.NewSimpleInterfaceAddress("eth0", "2.2.2.2"),
						}
					})

					It("succeeds", func() {
						err := boot.Run()
						Expect(err).NotTo(HaveOccurred())
					})
				})

				Context("and extra physical network interfaces exist", func() {
					BeforeEach(func() {
						stubInterfaces([][]string{[]string{"eth0", "aa:bb:cc", "physical"}, []string{"eth1", "aa:bb:dd", "physical"}})
						interfaceAddrsProvider.GetInterfaceAddresses = []boship.InterfaceAddress{
							boship.NewSimpleInterfaceAddress("eth0", "2.2.2.2"),
						}
					})

					It("succeeds", func() {
						err := boot.Run()
						Expect(err).NotTo(HaveOccurred())
					})
				})

				Context("and extra virtual network interfaces exist", func() {
					BeforeEach(func() {
						stubInterfaces([][]string{[]string{"eth0", "aa:bb:cc", "physical"}, []string{"lo", "aa:bb:ee", "virtual"}})
						interfaceAddrsProvider.GetInterfaceAddresses = []boship.InterfaceAddress{
							boship.NewSimpleInterfaceAddress("eth0", "2.2.2.2"),
						}
					})

					It("succeeds", func() {
						err := boot.Run()
						Expect(err).ToNot(HaveOccurred())
					})
				})
			})

			Context("when a single network configuration is provided, without a MAC address", func() {
				BeforeEach(func() {
					settingsJSON = `{
					"networks": {
						"netA": {
							"default": ["dns", "gateway"],
							"ip": "2.2.2.2",
							"dns": [
								"8.8.8.8",
								"4.4.4.4"
							],
							"netmask": "255.255.255.0",
							"gateway": "2.2.2.0"
						}
					}
				}`
				})

				Context("and no physical network interfaces exist", func() {
					Context("and a single virtual network interface exists", func() {
						BeforeEach(func() {
							stubInterfaces([][]string{[]string{"lo", "aa:bb:cc", "virtual"}})
						})

						It("raises an error", func() {
							err := boot.Run()
							Expect(err).To(HaveOccurred())
							Expect(err.Error()).To(ContainSubstring("Number of network settings '1' is greater than the number of network devices '0"))
						})
					})
				})

				Context("and a single physical network interface exists", func() {
					BeforeEach(func() {
						stubInterfaces([][]string{[]string{"eth0", "aa:bb:cc", "physical"}})
						interfaceAddrsProvider.GetInterfaceAddresses = []boship.InterfaceAddress{
							boship.NewSimpleInterfaceAddress("eth0", "2.2.2.2"),
						}
					})

					It("succeeds", func() {
						err := boot.Run()
						Expect(err).NotTo(HaveOccurred())
					})
				})

				Context("and extra physical network interfaces exist", func() {
					BeforeEach(func() {
						stubInterfaces([][]string{[]string{"eth0", "aa:bb:cc", "physical"}, []string{"eth1", "aa:bb:dd", "physical"}})
						interfaceAddrsProvider.GetInterfaceAddresses = []boship.InterfaceAddress{
							boship.NewSimpleInterfaceAddress("eth0", "2.2.2.2"),
						}
					})

					It("succeeds", func() {
						err := boot.Run()
						Expect(err).NotTo(HaveOccurred())
					})
				})

				Context("and an extra virtual network interface exists", func() {
					BeforeEach(func() {
						stubInterfaces([][]string{[]string{"eth0", "aa:bb:cc", "physical"}, []string{"lo", "aa:bb:dd", "virtual"}})
						interfaceAddrsProvider.GetInterfaceAddresses = []boship.InterfaceAddress{
							boship.NewSimpleInterfaceAddress("eth0", "2.2.2.2"),
						}
					})

					It("succeeds", func() {
						err := boot.Run()
						Expect(err).NotTo(HaveOccurred())
					})
				})
			})

			Context("when two network configurations are provided", func() {
				BeforeEach(func() {
					settingsJSON = `{
					"networks": {
						"netA": {
							"default": ["dns", "gateway"],
							"ip": "2.2.2.2",
							"dns": [
								"8.8.8.8",
								"4.4.4.4"
							],
							"netmask": "255.255.255.0",
							"gateway": "2.2.2.0",
							"mac": "aa:bb:cc"
						},
						"netB": {
							"default": ["dns", "gateway"],
							"ip": "3.3.3.3",
							"dns": [
								"8.8.8.8",
								"4.4.4.4"
							],
							"netmask": "255.255.255.0",
							"gateway": "3.3.3.0",
							"mac": ""
						}
					}
				}`
				})

				Context("and a single physical network interface exists", func() {
					BeforeEach(func() {
						stubInterfaces([][]string{[]string{"eth0", "aa:bb:cc", "physical"}})
					})

					It("raises an error", func() {
						err := boot.Run()
						Expect(err).To(HaveOccurred())
						Expect(err.Error()).To(ContainSubstring("Number of network settings '2' is greater than the number of network devices '1"))
					})
				})

				Context("and two physical network interfaces with matching MAC addresses exist", func() {
					BeforeEach(func() {
						stubInterfaces([][]string{[]string{"eth0", "aa:bb:cc", "physical"}, []string{"eth1", "aa:bb:dd", "physical"}})
						interfaceAddrsProvider.GetInterfaceAddresses = []boship.InterfaceAddress{
							boship.NewSimpleInterfaceAddress("eth0", "2.2.2.2"),
							boship.NewSimpleInterfaceAddress("eth1", "3.3.3.3"),
						}
					})

					It("succeeds", func() {
						err := boot.Run()
						Expect(err).ToNot(HaveOccurred())
					})
				})
			})
		})
	})
}
		It("returns path", func() {
			Expect(script.Path()).To(Equal("/fake/script"))
		})
	})

	Describe("Params", func() {
		It("returns params", func() {
			Expect(script.Params()).To(Equal(params))
		})
	})

	Describe("Exists", func() {
		It("returns bool", func() {
			Expect(script.Exists()).To(BeFalse())

			fs.WriteFile("/fake/script", []byte{})
			Expect(script.Exists()).To(BeTrue())
		})
	})

	Describe("Run", func() {
		BeforeEach(func() {
			oldSpec := exampleSpec()
			newSpec := exampleSpec()

			s := newSpec.PackageSpecs["foo"]
			s.Sha1 = "foo_updated_sha1"
			newSpec.PackageSpecs["foo"] = s

			s = newSpec.PackageSpecs["bar"]
			s.Sha1 = "bar_updated_sha1"
Beispiel #5
0
func rootDesc() {
	var mockCtrl *gomock.Controller

	BeforeEach(func() {
		mockCtrl = gomock.NewController(GinkgoT())
	})

	AfterEach(func() {
		mockCtrl.Finish()
	})

	Describe("Run", func() {
		var (
			command        bicmd.Cmd
			fakeFs         *fakesys.FakeFileSystem
			stdOut         *gbytes.Buffer
			stdErr         *gbytes.Buffer
			userInterface  biui.UI
			sha1Calculator crypto.SHA1Calculator
			manifestSHA1   string

			mockDeployer              *mock_deployment.MockDeployer
			mockInstaller             *mock_install.MockInstaller
			mockInstallerFactory      *mock_install.MockInstallerFactory
			mockReleaseExtractor      *mock_release.MockExtractor
			releaseManager            birel.Manager
			mockRegistryServerManager *mock_registry.MockServerManager
			mockRegistryServer        *mock_registry.MockServer
			mockAgentClient           *mock_agentclient.MockAgentClient
			mockAgentClientFactory    *mock_httpagent.MockAgentClientFactory
			mockCloudFactory          *mock_cloud.MockFactory

			fakeCPIRelease *fakebirel.FakeRelease
			logger         boshlog.Logger

			mockBlobstoreFactory *mock_blobstore.MockFactory
			mockBlobstore        *mock_blobstore.MockBlobstore

			mockVMManagerFactory       *mock_vm.MockManagerFactory
			fakeVMManager              *fakebivm.FakeManager
			fakeStemcellExtractor      *fakebistemcell.FakeExtractor
			mockStemcellManager        *mock_stemcell.MockManager
			fakeStemcellManagerFactory *fakebistemcell.FakeManagerFactory

			fakeReleaseSetParser              *fakebirelsetmanifest.FakeParser
			fakeInstallationParser            *fakebiinstallmanifest.FakeParser
			fakeDeploymentParser              *fakebideplmanifest.FakeParser
			mockLegacyDeploymentStateMigrator *mock_config.MockLegacyDeploymentStateMigrator
			setupDeploymentStateService       biconfig.DeploymentStateService
			fakeDeploymentValidator           *fakebideplval.FakeValidator

			directorID          = "generated-director-uuid"
			fakeUUIDGenerator   *fakeuuid.FakeGenerator
			configUUIDGenerator *fakeuuid.FakeGenerator

			fakeStage *fakebiui.FakeStage

			deploymentManifestPath string
			deploymentStatePath    string
			cpiReleaseTarballPath  string
			stemcellTarballPath    string
			extractedStemcell      bistemcell.ExtractedStemcell

			expectDeploy *gomock.Call

			mbusURL = "http://*****:*****@fake-mbus-endpoint"

			releaseSetManifest     birelsetmanifest.Manifest
			boshDeploymentManifest bideplmanifest.Manifest
			installationManifest   biinstallmanifest.Manifest
			cloud                  bicloud.Cloud

			cloudStemcell bistemcell.CloudStemcell

			expectLegacyMigrate        *gomock.Call
			expectStemcellUpload       *gomock.Call
			expectStemcellDeleteUnused *gomock.Call
			expectCPIReleaseExtract    *gomock.Call
			expectInstall              *gomock.Call
			expectNewCloud             *gomock.Call
		)

		BeforeEach(func() {
			logger = boshlog.NewLogger(boshlog.LevelNone)
			stdOut = gbytes.NewBuffer()
			stdErr = gbytes.NewBuffer()
			userInterface = biui.NewWriterUI(stdOut, stdErr, logger)
			fakeFs = fakesys.NewFakeFileSystem()
			fakeFs.EnableStrictTempRootBehavior()
			deploymentManifestPath = "/path/to/manifest.yml"
			deploymentStatePath = "/path/to/manifest-state.json"
			fakeFs.RegisterOpenFile(deploymentManifestPath, &fakesys.FakeFile{
				Stats: &fakesys.FakeFileStats{FileType: fakesys.FakeFileTypeFile},
			})

			fakeFs.WriteFileString(deploymentManifestPath, "")

			mockDeployer = mock_deployment.NewMockDeployer(mockCtrl)
			mockInstaller = mock_install.NewMockInstaller(mockCtrl)
			mockInstallerFactory = mock_install.NewMockInstallerFactory(mockCtrl)

			mockReleaseExtractor = mock_release.NewMockExtractor(mockCtrl)
			releaseManager = birel.NewManager(logger)

			mockRegistryServerManager = mock_registry.NewMockServerManager(mockCtrl)
			mockRegistryServer = mock_registry.NewMockServer(mockCtrl)

			mockAgentClientFactory = mock_httpagent.NewMockAgentClientFactory(mockCtrl)
			mockAgentClient = mock_agentclient.NewMockAgentClient(mockCtrl)
			mockAgentClientFactory.EXPECT().NewAgentClient(gomock.Any(), gomock.Any()).Return(mockAgentClient).AnyTimes()

			mockCloudFactory = mock_cloud.NewMockFactory(mockCtrl)

			mockBlobstoreFactory = mock_blobstore.NewMockFactory(mockCtrl)
			mockBlobstore = mock_blobstore.NewMockBlobstore(mockCtrl)
			mockBlobstoreFactory.EXPECT().Create(mbusURL, gomock.Any()).Return(mockBlobstore, nil).AnyTimes()

			mockVMManagerFactory = mock_vm.NewMockManagerFactory(mockCtrl)
			fakeVMManager = fakebivm.NewFakeManager()
			mockVMManagerFactory.EXPECT().NewManager(gomock.Any(), mockAgentClient).Return(fakeVMManager).AnyTimes()

			fakeStemcellExtractor = fakebistemcell.NewFakeExtractor()
			mockStemcellManager = mock_stemcell.NewMockManager(mockCtrl)
			fakeStemcellManagerFactory = fakebistemcell.NewFakeManagerFactory()

			fakeReleaseSetParser = fakebirelsetmanifest.NewFakeParser()
			fakeInstallationParser = fakebiinstallmanifest.NewFakeParser()
			fakeDeploymentParser = fakebideplmanifest.NewFakeParser()

			mockLegacyDeploymentStateMigrator = mock_config.NewMockLegacyDeploymentStateMigrator(mockCtrl)

			configUUIDGenerator = &fakeuuid.FakeGenerator{}
			configUUIDGenerator.GeneratedUUID = directorID
			setupDeploymentStateService = biconfig.NewFileSystemDeploymentStateService(fakeFs, configUUIDGenerator, logger, biconfig.DeploymentStatePath(deploymentManifestPath))

			fakeDeploymentValidator = fakebideplval.NewFakeValidator()

			fakeStage = fakebiui.NewFakeStage()

			sha1Calculator = crypto.NewSha1Calculator(fakeFs)
			fakeUUIDGenerator = &fakeuuid.FakeGenerator{}

			var err error
			manifestSHA1, err = sha1Calculator.Calculate(deploymentManifestPath)
			Expect(err).ToNot(HaveOccurred())

			cpiReleaseTarballPath = "/release/tarball/path"

			stemcellTarballPath = "/stemcell/tarball/path"
			extractedStemcell = bistemcell.NewExtractedStemcell(
				bistemcell.Manifest{
					ImagePath:       "/stemcell/image/path",
					Name:            "fake-stemcell-name",
					Version:         "fake-stemcell-version",
					SHA1:            "fake-stemcell-sha1",
					CloudProperties: biproperty.Map{},
					OS:              "ubuntu-trusty",
				},
				"fake-extracted-path",
				fakeFs,
			)

			// create input files
			fakeFs.WriteFileString(cpiReleaseTarballPath, "")
			fakeFs.WriteFileString(stemcellTarballPath, "")

			// deployment exists
			fakeFs.WriteFileString(deploymentManifestPath, "")

			// deployment is valid
			fakeDeploymentValidator.SetValidateBehavior([]fakebideplval.ValidateOutput{
				{Err: nil},
			})
			fakeDeploymentValidator.SetValidateReleaseJobsBehavior([]fakebideplval.ValidateReleaseJobsOutput{
				{Err: nil},
			})

			// stemcell exists
			fakeFs.WriteFile(stemcellTarballPath, []byte{})

			releaseSetManifest = birelsetmanifest.Manifest{
				Releases: []birelmanifest.ReleaseRef{
					{
						Name: "fake-cpi-release-name",
						URL:  "file://" + cpiReleaseTarballPath,
					},
				},
			}

			// parsed CPI deployment manifest
			installationManifest = biinstallmanifest.Manifest{
				Template: biinstallmanifest.ReleaseJobRef{
					Name:    "fake-cpi-release-job-name",
					Release: "fake-cpi-release-name",
				},
				Mbus: mbusURL,
			}

			// parsed BOSH deployment manifest
			boshDeploymentManifest = bideplmanifest.Manifest{
				Name: "fake-deployment-name",
				Jobs: []bideplmanifest.Job{
					{
						Name: "fake-job-name",
					},
				},
				ResourcePools: []bideplmanifest.ResourcePool{
					{
						Stemcell: bideplmanifest.StemcellRef{
							URL: "file://" + stemcellTarballPath,
						},
					},
				},
			}
			fakeDeploymentParser.ParseManifest = boshDeploymentManifest

			// parsed/extracted CPI release
			fakeCPIRelease = fakebirel.NewFakeRelease()
			fakeCPIRelease.ReleaseName = "fake-cpi-release-name"
			fakeCPIRelease.ReleaseVersion = "1.0"
			fakeCPIRelease.ReleaseIsCompiled = false
			fakeCPIRelease.ReleaseJobs = []bireljob.Job{
				{
					Name: "fake-cpi-release-job-name",
					Templates: map[string]string{
						"templates/cpi.erb": "bin/cpi",
					},
				},
			}

			cloud = bicloud.NewCloud(fakebicloud.NewFakeCPICmdRunner(), "fake-director-id", logger)

			cloudStemcell = fakebistemcell.NewFakeCloudStemcell("fake-stemcell-cid", "fake-stemcell-name", "fake-stemcell-version")
		})

		JustBeforeEach(func() {

			doGet := func(deploymentManifestPath string) (bicmd.DeploymentPreparer, error) {
				deploymentStateService := biconfig.NewFileSystemDeploymentStateService(fakeFs, configUUIDGenerator, logger, biconfig.DeploymentStatePath(deploymentManifestPath))
				deploymentRepo := biconfig.NewDeploymentRepo(deploymentStateService)
				releaseRepo := biconfig.NewReleaseRepo(deploymentStateService, fakeUUIDGenerator)
				stemcellRepo := biconfig.NewStemcellRepo(deploymentStateService, fakeUUIDGenerator)
				deploymentRecord := deployment.NewRecord(deploymentRepo, releaseRepo, stemcellRepo, sha1Calculator)

				fakeHTTPClient := fakebihttpclient.NewFakeHTTPClient()
				tarballCache := bitarball.NewCache("fake-base-path", fakeFs, logger)
				tarballProvider := bitarball.NewProvider(tarballCache, fakeFs, fakeHTTPClient, sha1Calculator, 1, 0, logger)

				cpiInstaller := bicpirel.CpiInstaller{
					ReleaseManager:   releaseManager,
					InstallerFactory: mockInstallerFactory,
					Validator:        bicpirel.NewValidator(),
				}
				releaseFetcher := birel.NewFetcher(tarballProvider, mockReleaseExtractor, releaseManager)
				stemcellFetcher := bistemcell.Fetcher{
					TarballProvider:   tarballProvider,
					StemcellExtractor: fakeStemcellExtractor,
				}
				releaseSetAndInstallationManifestParser := bicmd.ReleaseSetAndInstallationManifestParser{
					ReleaseSetParser:   fakeReleaseSetParser,
					InstallationParser: fakeInstallationParser,
				}

				deploymentManifestParser := bicmd.DeploymentManifestParser{
					DeploymentParser:    fakeDeploymentParser,
					DeploymentValidator: fakeDeploymentValidator,
					ReleaseManager:      releaseManager,
				}

				fakeInstallationUUIDGenerator := &fakeuuid.FakeGenerator{}
				fakeInstallationUUIDGenerator.GeneratedUUID = "fake-installation-id"
				targetProvider := biinstall.NewTargetProvider(
					deploymentStateService,
					fakeInstallationUUIDGenerator,
					filepath.Join("fake-install-dir"),
				)
				tempRootConfigurator := bicmd.NewTempRootConfigurator(fakeFs)

				return bicmd.NewDeploymentPreparer(
					userInterface,
					logger,
					"deployCmd",
					deploymentStateService,
					mockLegacyDeploymentStateMigrator,
					releaseManager,
					deploymentRecord,
					mockCloudFactory,
					fakeStemcellManagerFactory,
					mockAgentClientFactory,
					mockVMManagerFactory,
					mockBlobstoreFactory,
					mockDeployer,
					deploymentManifestPath,
					cpiInstaller,
					releaseFetcher,
					stemcellFetcher,
					releaseSetAndInstallationManifestParser,
					deploymentManifestParser,
					tempRootConfigurator,
					targetProvider,
				), nil
			}

			command = bicmd.NewDeployCmd(userInterface, fakeFs, logger, doGet)

			expectLegacyMigrate = mockLegacyDeploymentStateMigrator.EXPECT().MigrateIfExists("/path/to/bosh-deployments.yml").AnyTimes()

			fakeStemcellExtractor.SetExtractBehavior(stemcellTarballPath, extractedStemcell, nil)

			fakeStemcellManagerFactory.SetNewManagerBehavior(cloud, mockStemcellManager)

			expectStemcellUpload = mockStemcellManager.EXPECT().Upload(extractedStemcell, fakeStage).Return(cloudStemcell, nil).AnyTimes()

			expectStemcellDeleteUnused = mockStemcellManager.EXPECT().DeleteUnused(fakeStage).AnyTimes()

			fakeReleaseSetParser.ParseManifest = releaseSetManifest
			fakeDeploymentParser.ParseManifest = boshDeploymentManifest
			fakeInstallationParser.ParseManifest = installationManifest

			installationPath := filepath.Join("fake-install-dir", "fake-installation-id")
			target := biinstall.NewTarget(installationPath)

			installedJob := biinstall.NewInstalledJob(
				biinstall.RenderedJobRef{
					Name: "fake-cpi-release-job-name",
				},
				filepath.Join(target.JobsPath(), "fake-cpi-release-job-name"),
			)

			mockInstallerFactory.EXPECT().NewInstaller(target).Return(mockInstaller).AnyTimes()

			installation := biinstall.NewInstallation(target, installedJob, installationManifest, mockRegistryServerManager)

			expectInstall = mockInstaller.EXPECT().Install(installationManifest, gomock.Any()).Do(func(_ interface{}, stage biui.Stage) {
				Expect(fakeStage.SubStages).To(ContainElement(stage))
			}).Return(installation, nil).AnyTimes()
			mockInstaller.EXPECT().Cleanup(installation).AnyTimes()

			mockDeployment := mock_deployment.NewMockDeployment(mockCtrl)

			expectDeploy = mockDeployer.EXPECT().Deploy(
				cloud,
				boshDeploymentManifest,
				cloudStemcell,
				installationManifest.Registry,
				fakeVMManager,
				mockBlobstore,
				gomock.Any(),
			).Do(func(_, _, _, _, _, _ interface{}, stage biui.Stage) {
				Expect(fakeStage.SubStages).To(ContainElement(stage))
			}).Return(mockDeployment, nil).AnyTimes()

			expectCPIReleaseExtract = mockReleaseExtractor.EXPECT().Extract(cpiReleaseTarballPath).Return(fakeCPIRelease, nil).AnyTimes()

			expectNewCloud = mockCloudFactory.EXPECT().NewCloud(installation, directorID).Return(cloud, nil).AnyTimes()
		})

		It("prints the deployment manifest and state file", func() {
			err := command.Run(fakeStage, []string{deploymentManifestPath})
			Expect(err).NotTo(HaveOccurred())

			Expect(stdOut).To(gbytes.Say("Deployment manifest: '/path/to/manifest.yml'"))
			Expect(stdOut).To(gbytes.Say("Deployment state: '/path/to/manifest-state.json'"))
		})

		It("does not migrate the legacy bosh-deployments.yml if manifest-state.json exists", func() {
			err := fakeFs.WriteFileString(deploymentStatePath, "{}")
			Expect(err).ToNot(HaveOccurred())

			expectLegacyMigrate.Times(0)

			err = command.Run(fakeStage, []string{deploymentManifestPath})
			Expect(err).NotTo(HaveOccurred())
			Expect(fakeInstallationParser.ParsePath).To(Equal(deploymentManifestPath))
		})

		It("migrates the legacy bosh-deployments.yml if manifest-state.json does not exist", func() {
			err := fakeFs.RemoveAll(deploymentStatePath)
			Expect(err).ToNot(HaveOccurred())

			expectLegacyMigrate.Return(true, nil).Times(1)

			err = command.Run(fakeStage, []string{deploymentManifestPath})
			Expect(err).NotTo(HaveOccurred())
			Expect(fakeInstallationParser.ParsePath).To(Equal(deploymentManifestPath))

			Expect(stdOut).To(gbytes.Say("Deployment manifest: '/path/to/manifest.yml'"))
			Expect(stdOut).To(gbytes.Say("Deployment state: '/path/to/manifest-state.json'"))
			Expect(stdOut).To(gbytes.Say("Migrated legacy deployments file: '/path/to/bosh-deployments.yml'"))
		})

		It("sets the temp root", func() {
			err := command.Run(fakeStage, []string{deploymentManifestPath})
			Expect(err).NotTo(HaveOccurred())
			Expect(fakeFs.TempRootPath).To(Equal("fake-install-dir/fake-installation-id/tmp"))
		})

		Context("when setting the temp root fails", func() {
			It("returns an error", func() {
				fakeFs.ChangeTempRootErr = errors.New("fake ChangeTempRootErr")
				err := command.Run(fakeStage, []string{deploymentManifestPath})
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(Equal("Setting temp root: fake ChangeTempRootErr"))
			})
		})

		It("parses the installation manifest", func() {
			err := command.Run(fakeStage, []string{deploymentManifestPath})
			Expect(err).NotTo(HaveOccurred())
			Expect(fakeInstallationParser.ParsePath).To(Equal(deploymentManifestPath))
		})

		It("parses the deployment manifest", func() {
			err := command.Run(fakeStage, []string{deploymentManifestPath})
			Expect(err).NotTo(HaveOccurred())
			Expect(fakeDeploymentParser.ParsePath).To(Equal(deploymentManifestPath))
		})

		It("validates bosh deployment manifest", func() {
			err := command.Run(fakeStage, []string{deploymentManifestPath})
			Expect(err).NotTo(HaveOccurred())
			Expect(fakeDeploymentValidator.ValidateInputs).To(Equal([]fakebideplval.ValidateInput{
				{Manifest: boshDeploymentManifest, ReleaseSetManifest: releaseSetManifest},
			}))
		})

		It("validates jobs in manifest refer to job in releases", func() {
			err := command.Run(fakeStage, []string{deploymentManifestPath})
			Expect(err).NotTo(HaveOccurred())
			Expect(fakeDeploymentValidator.ValidateReleaseJobsInputs).To(Equal([]fakebideplval.ValidateReleaseJobsInput{
				{Manifest: boshDeploymentManifest, ReleaseManager: releaseManager},
			}))
		})

		It("logs validating stages", func() {
			err := command.Run(fakeStage, []string{deploymentManifestPath})
			Expect(err).NotTo(HaveOccurred())

			Expect(fakeStage.PerformCalls[0]).To(Equal(&fakebiui.PerformCall{
				Name: "validating",
				Stage: &fakebiui.FakeStage{
					PerformCalls: []*fakebiui.PerformCall{
						{Name: "Validating release 'fake-cpi-release-name'"},
						{Name: "Validating cpi release"},
						{Name: "Validating deployment manifest"},
						{Name: "Validating stemcell"},
					},
				},
			}))
		})

		It("extracts CPI release tarball", func() {
			expectCPIReleaseExtract.Times(1)

			err := command.Run(fakeStage, []string{deploymentManifestPath})
			Expect(err).NotTo(HaveOccurred())
		})

		It("installs the CPI locally", func() {
			expectInstall.Times(1)
			expectNewCloud.Times(1)

			err := command.Run(fakeStage, []string{deploymentManifestPath})
			Expect(err).NotTo(HaveOccurred())
		})

		It("adds a new 'installing CPI' event logger stage", func() {
			err := command.Run(fakeStage, []string{deploymentManifestPath})
			Expect(err).NotTo(HaveOccurred())

			Expect(fakeStage.PerformCalls[1]).To(Equal(&fakebiui.PerformCall{
				Name:  "installing CPI",
				Stage: &fakebiui.FakeStage{}, // mock installer doesn't add sub-stages
			}))
		})

		It("adds a new 'Starting registry' event logger stage", func() {
			err := command.Run(fakeStage, []string{deploymentManifestPath})
			Expect(err).NotTo(HaveOccurred())

			Expect(fakeStage.PerformCalls[2]).To(Equal(&fakebiui.PerformCall{
				Name: "Starting registry",
			}))
		})

		Context("when the registry is configured", func() {
			BeforeEach(func() {
				installationManifest.Registry = biinstallmanifest.Registry{
					Username: "******",
					Password: "******",
					Host:     "fake-host",
					Port:     123,
				}
			})

			It("starts & stops the registry", func() {
				mockRegistryServerManager.EXPECT().Start("fake-username", "fake-password", "fake-host", 123).Return(mockRegistryServer, nil)
				mockRegistryServer.EXPECT().Stop()

				err := command.Run(fakeStage, []string{deploymentManifestPath})
				Expect(err).NotTo(HaveOccurred())
			})
		})

		It("deletes the extracted CPI release", func() {
			err := command.Run(fakeStage, []string{deploymentManifestPath})
			Expect(err).NotTo(HaveOccurred())
			Expect(fakeCPIRelease.DeleteCalled).To(BeTrue())
		})

		It("extracts the stemcell", func() {
			err := command.Run(fakeStage, []string{deploymentManifestPath})
			Expect(err).NotTo(HaveOccurred())
			Expect(fakeStemcellExtractor.ExtractInputs).To(Equal([]fakebistemcell.ExtractInput{
				{TarballPath: stemcellTarballPath},
			}))
		})

		It("uploads the stemcell", func() {
			expectStemcellUpload.Times(1)

			err := command.Run(fakeStage, []string{deploymentManifestPath})
			Expect(err).ToNot(HaveOccurred())
		})

		It("adds a new 'deploying' event logger stage", func() {
			err := command.Run(fakeStage, []string{deploymentManifestPath})
			Expect(err).NotTo(HaveOccurred())

			Expect(fakeStage.PerformCalls[3]).To(Equal(&fakebiui.PerformCall{
				Name:  "deploying",
				Stage: &fakebiui.FakeStage{}, // mock deployer doesn't add sub-stages
			}))
		})

		It("deploys", func() {
			expectDeploy.Times(1)

			err := command.Run(fakeStage, []string{deploymentManifestPath})
			Expect(err).NotTo(HaveOccurred())
		})

		It("updates the deployment record", func() {
			err := command.Run(fakeStage, []string{deploymentManifestPath})
			Expect(err).NotTo(HaveOccurred())

			deploymentState, err := setupDeploymentStateService.Load()
			Expect(err).ToNot(HaveOccurred())

			Expect(deploymentState.CurrentManifestSHA1).To(Equal(manifestSHA1))
			Expect(deploymentState.Releases).To(Equal([]biconfig.ReleaseRecord{
				{
					ID:      "fake-uuid-0",
					Name:    fakeCPIRelease.Name(),
					Version: fakeCPIRelease.Version(),
				},
			}))
		})

		It("deletes unused stemcells", func() {
			expectStemcellDeleteUnused.Times(1)

			err := command.Run(fakeStage, []string{deploymentManifestPath})
			Expect(err).NotTo(HaveOccurred())
		})

		Context("when deployment has not changed", func() {
			JustBeforeEach(func() {
				previousDeploymentState := biconfig.DeploymentState{
					DirectorID:        directorID,
					CurrentReleaseIDs: []string{"my-release-id-1"},
					Releases: []biconfig.ReleaseRecord{{
						ID:      "my-release-id-1",
						Name:    fakeCPIRelease.Name(),
						Version: fakeCPIRelease.Version(),
					}},
					CurrentStemcellID: "my-stemcellRecordID",
					Stemcells: []biconfig.StemcellRecord{{
						ID:      "my-stemcellRecordID",
						Name:    cloudStemcell.Name(),
						Version: cloudStemcell.Version(),
					}},
					CurrentManifestSHA1: manifestSHA1,
				}

				err := setupDeploymentStateService.Save(previousDeploymentState)
				Expect(err).ToNot(HaveOccurred())
			})

			It("skips deploy", func() {
				expectDeploy.Times(0)

				err := command.Run(fakeStage, []string{deploymentManifestPath})
				Expect(err).NotTo(HaveOccurred())
				Expect(stdOut).To(gbytes.Say("No deployment, stemcell or release changes. Skipping deploy."))
			})
		})

		Context("when parsing the cpi deployment manifest fails", func() {
			BeforeEach(func() {
				fakeDeploymentParser.ParseErr = bosherr.Error("fake-parse-error")
			})

			It("returns error", func() {
				err := command.Run(fakeStage, []string{deploymentManifestPath})
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("Parsing deployment manifest"))
				Expect(err.Error()).To(ContainSubstring("fake-parse-error"))
				Expect(fakeDeploymentParser.ParsePath).To(Equal(deploymentManifestPath))
			})
		})

		Context("when the cpi release does not contain a 'cpi' job", func() {
			BeforeEach(func() {
				fakeCPIRelease.ReleaseJobs = []bireljob.Job{
					{
						Name: "not-cpi",
					},
				}
			})

			It("returns error", func() {
				err := command.Run(fakeStage, []string{deploymentManifestPath})
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(Equal("Invalid CPI release 'fake-cpi-release-name': CPI release must contain specified job 'fake-cpi-release-job-name'"))
			})
		})

		Context("when multiple releases are given", func() {
			var (
				otherReleaseTarballPath   string
				fakeOtherRelease          *fakebirel.FakeRelease
				expectOtherReleaseExtract *gomock.Call
			)

			BeforeEach(func() {
				otherReleaseTarballPath = "/path/to/other-release.tgz"

				fakeFs.WriteFileString(otherReleaseTarballPath, "")

				fakeOtherRelease = fakebirel.New("other-release", "1234")
				fakeOtherRelease.ReleaseJobs = []bireljob.Job{{Name: "not-cpi"}}

				expectOtherReleaseExtract = mockReleaseExtractor.EXPECT().Extract(
					otherReleaseTarballPath,
				).Return(fakeOtherRelease, nil).AnyTimes()

				releaseSetManifest = birelsetmanifest.Manifest{
					Releases: []birelmanifest.ReleaseRef{
						{
							Name: "fake-cpi-release-name",
							URL:  "file://" + cpiReleaseTarballPath,
						},
						{
							Name: "other-release",
							URL:  "file://" + otherReleaseTarballPath,
						},
					},
				}
			})

			It("extracts all the release tarballs", func() {
				expectCPIReleaseExtract.Times(1)
				expectOtherReleaseExtract.Times(1)

				err := command.Run(fakeStage, []string{deploymentManifestPath})
				Expect(err).NotTo(HaveOccurred())
			})

			It("installs the CPI release locally", func() {
				expectInstall.Times(1)
				expectNewCloud.Times(1)

				err := command.Run(fakeStage, []string{deploymentManifestPath})
				Expect(err).NotTo(HaveOccurred())
			})

			It("updates the deployment record", func() {
				err := command.Run(fakeStage, []string{deploymentManifestPath})
				Expect(err).NotTo(HaveOccurred())

				deploymentState, err := setupDeploymentStateService.Load()
				Expect(err).ToNot(HaveOccurred())

				Expect(deploymentState.CurrentManifestSHA1).To(Equal(manifestSHA1))
				Expect(deploymentState.Releases).To(Equal([]biconfig.ReleaseRecord{
					{
						ID:      "fake-uuid-0",
						Name:    fakeCPIRelease.Name(),
						Version: fakeCPIRelease.Version(),
					},
					{
						ID:      "fake-uuid-1",
						Name:    fakeOtherRelease.Name(),
						Version: fakeOtherRelease.Version(),
					},
				}))
			})

			Context("when one of the releases in the deployment has changed", func() {
				JustBeforeEach(func() {
					olderReleaseVersion := "1233"
					Expect(fakeOtherRelease.Version()).ToNot(Equal(olderReleaseVersion))
					previousDeploymentState := biconfig.DeploymentState{
						DirectorID:        directorID,
						CurrentReleaseIDs: []string{"existing-release-id-1", "existing-release-id-2"},
						Releases: []biconfig.ReleaseRecord{
							{
								ID:      "existing-release-id-1",
								Name:    fakeCPIRelease.Name(),
								Version: fakeCPIRelease.Version(),
							},
							{
								ID:      "existing-release-id-2",
								Name:    fakeOtherRelease.Name(),
								Version: olderReleaseVersion,
							},
						},
						CurrentStemcellID: "my-stemcellRecordID",
						Stemcells: []biconfig.StemcellRecord{{
							ID:      "my-stemcellRecordID",
							Name:    cloudStemcell.Name(),
							Version: cloudStemcell.Version(),
						}},
						CurrentManifestSHA1: manifestSHA1,
					}

					err := setupDeploymentStateService.Save(previousDeploymentState)
					Expect(err).ToNot(HaveOccurred())
				})

				It("updates the deployment record, clearing out unused releases", func() {
					err := command.Run(fakeStage, []string{deploymentManifestPath})
					Expect(err).NotTo(HaveOccurred())

					deploymentState, err := setupDeploymentStateService.Load()
					Expect(err).ToNot(HaveOccurred())

					Expect(deploymentState.CurrentManifestSHA1).To(Equal(manifestSHA1))
					keys := []string{}
					ids := []string{}
					for _, releaseRecord := range deploymentState.Releases {
						keys = append(keys, fmt.Sprintf("%s-%s", releaseRecord.Name, releaseRecord.Version))
						ids = append(ids, releaseRecord.ID)
					}
					Expect(deploymentState.CurrentReleaseIDs).To(ConsistOf(ids))
					Expect(keys).To(ConsistOf([]string{
						fmt.Sprintf("%s-%s", fakeCPIRelease.Name(), fakeCPIRelease.Version()),
						fmt.Sprintf("%s-%s", fakeOtherRelease.Name(), fakeOtherRelease.Version()),
					}))
				})
			})

			Context("when the deployment has not changed", func() {
				JustBeforeEach(func() {
					previousDeploymentState := biconfig.DeploymentState{
						DirectorID:        directorID,
						CurrentReleaseIDs: []string{"my-release-id-1", "my-release-id-2"},
						Releases: []biconfig.ReleaseRecord{
							{
								ID:      "my-release-id-1",
								Name:    fakeCPIRelease.Name(),
								Version: fakeCPIRelease.Version(),
							},
							{
								ID:      "my-release-id-2",
								Name:    fakeOtherRelease.Name(),
								Version: fakeOtherRelease.Version(),
							},
						},
						CurrentStemcellID: "my-stemcellRecordID",
						Stemcells: []biconfig.StemcellRecord{{
							ID:      "my-stemcellRecordID",
							Name:    cloudStemcell.Name(),
							Version: cloudStemcell.Version(),
						}},
						CurrentManifestSHA1: manifestSHA1,
					}

					err := setupDeploymentStateService.Save(previousDeploymentState)
					Expect(err).ToNot(HaveOccurred())
				})

				It("skips deploy", func() {
					expectDeploy.Times(0)

					err := command.Run(fakeStage, []string{deploymentManifestPath})
					Expect(err).NotTo(HaveOccurred())
					Expect(stdOut).To(gbytes.Say("No deployment, stemcell or release changes. Skipping deploy."))
				})
			})
		})

		Context("when release name does not match the name in release tarball", func() {
			BeforeEach(func() {
				releaseSetManifest.Releases = []birelmanifest.ReleaseRef{
					{
						Name: "fake-other-cpi-release-name",
						URL:  "file://" + cpiReleaseTarballPath,
					},
				}
			})

			It("returns an error", func() {
				err := command.Run(fakeStage, []string{deploymentManifestPath})
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("Release name 'fake-other-cpi-release-name' does not match the name in release tarball 'fake-cpi-release-name'"))
			})
		})

		Context("When the stemcell tarball does not exist", func() {
			JustBeforeEach(func() {
				fakeStemcellExtractor.SetExtractBehavior(stemcellTarballPath, extractedStemcell, errors.New("no-stemcell-there"))
			})

			It("returns error", func() {
				err := command.Run(fakeStage, []string{deploymentManifestPath})
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("no-stemcell-there"))

				performCall := fakeStage.PerformCalls[0].Stage.PerformCalls[3]
				Expect(performCall.Name).To(Equal("Validating stemcell"))
				Expect(performCall.Error.Error()).To(ContainSubstring("no-stemcell-there"))
			})
		})

		Context("when release file does not exist", func() {
			BeforeEach(func() {
				mockReleaseExtractor.EXPECT().Extract(cpiReleaseTarballPath).Return(nil, errors.New("not there"))
			})

			It("returns error", func() {
				err := command.Run(fakeStage, []string{deploymentManifestPath})
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("not there"))

				performCall := fakeStage.PerformCalls[0].Stage.PerformCalls[0]
				Expect(performCall.Name).To(Equal("Validating release 'fake-cpi-release-name'"))
				Expect(performCall.Error.Error()).To(ContainSubstring("not there"))
			})
		})

		Context("when the deployment state file does not exist", func() {
			BeforeEach(func() {
				fakeFs.RemoveAll(deploymentStatePath)
			})

			It("creates a deployment state", func() {
				err := command.Run(fakeStage, []string{deploymentManifestPath})
				Expect(err).ToNot(HaveOccurred())

				deploymentState, err := setupDeploymentStateService.Load()
				Expect(err).ToNot(HaveOccurred())

				Expect(deploymentState.DirectorID).To(Equal(directorID))
			})
		})

		It("returns err when the deployment manifest does not exist", func() {
			fakeFs.RemoveAll(deploymentManifestPath)

			err := command.Run(fakeStage, []string{deploymentManifestPath})
			Expect(err).To(HaveOccurred())
			Expect(err.Error()).To(ContainSubstring("Deployment manifest does not exist at '/path/to/manifest.yml'"))
			Expect(stdErr).To(gbytes.Say("Deployment '/path/to/manifest.yml' does not exist"))
		})

		Context("when the deployment manifest is invalid", func() {
			BeforeEach(func() {
				fakeDeploymentValidator.SetValidateBehavior([]fakebideplval.ValidateOutput{
					{Err: bosherr.Error("fake-deployment-validation-error")},
				})
			})

			It("returns err", func() {
				err := command.Run(fakeStage, []string{deploymentManifestPath})
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-deployment-validation-error"))
			})

			It("logs the failed event log", func() {
				err := command.Run(fakeStage, []string{deploymentManifestPath})
				Expect(err).To(HaveOccurred())

				performCall := fakeStage.PerformCalls[0].Stage.PerformCalls[2]
				Expect(performCall.Name).To(Equal("Validating deployment manifest"))
				Expect(performCall.Error.Error()).To(Equal("Validating deployment manifest: fake-deployment-validation-error"))
			})
		})

		Context("when validating jobs fails", func() {
			BeforeEach(func() {
				fakeDeploymentValidator.SetValidateReleaseJobsBehavior([]fakebideplval.ValidateReleaseJobsOutput{
					{Err: bosherr.Error("fake-jobs-validation-error")},
				})
			})

			It("returns err", func() {
				err := command.Run(fakeStage, []string{deploymentManifestPath})
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-jobs-validation-error"))
			})

			It("logs the failed event log", func() {
				err := command.Run(fakeStage, []string{deploymentManifestPath})
				Expect(err).To(HaveOccurred())

				performCall := fakeStage.PerformCalls[0].Stage.PerformCalls[2]
				Expect(performCall.Name).To(Equal("Validating deployment manifest"))
				Expect(performCall.Error.Error()).To(Equal("Validating deployment jobs refer to jobs in release: fake-jobs-validation-error"))
			})
		})

		It("returns err when number of arguments is not equal 1", func() {
			err := command.Run(fakeStage, []string{})
			Expect(err).To(HaveOccurred())
			Expect(err.Error()).To(ContainSubstring("Invalid usage"))

			err = command.Run(fakeStage, []string{"1", "2"})
			Expect(err).To(HaveOccurred())
			Expect(err.Error()).To(ContainSubstring("Invalid usage"))
		})

		Context("when uploading stemcell fails", func() {
			JustBeforeEach(func() {
				expectStemcellUpload.Return(nil, bosherr.Error("fake-upload-error"))
			})

			It("returns an error", func() {
				err := command.Run(fakeStage, []string{deploymentManifestPath})
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-upload-error"))
			})
		})

		Context("when deploy fails", func() {
			BeforeEach(func() {
				mockDeployer.EXPECT().Deploy(
					cloud,
					boshDeploymentManifest,
					cloudStemcell,
					installationManifest.Registry,
					fakeVMManager,
					mockBlobstore,
					gomock.Any(),
				).Return(nil, errors.New("fake-deploy-error")).AnyTimes()

				previousDeploymentState := biconfig.DeploymentState{
					CurrentReleaseIDs: []string{"my-release-id-1"},
					Releases: []biconfig.ReleaseRecord{{
						ID:      "my-release-id-1",
						Name:    fakeCPIRelease.Name(),
						Version: fakeCPIRelease.Version(),
					}},
					CurrentManifestSHA1: "fake-manifest-sha",
				}

				setupDeploymentStateService.Save(previousDeploymentState)
			})

			It("clears the deployment record", func() {
				err := command.Run(fakeStage, []string{deploymentManifestPath})
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-deploy-error"))

				deploymentState, err := setupDeploymentStateService.Load()
				Expect(err).ToNot(HaveOccurred())

				Expect(deploymentState.CurrentManifestSHA1).To(Equal(""))
				Expect(deploymentState.Releases).To(Equal([]biconfig.ReleaseRecord{}))
				Expect(deploymentState.CurrentReleaseIDs).To(Equal([]string{}))
			})
		})

		Context("when compiled releases are being used", func() {

			var (
				otherReleaseTarballPath   string
				fakeOtherRelease          *fakebirel.FakeRelease
				expectOtherReleaseExtract *gomock.Call
			)

			BeforeEach(func() {
				otherReleaseTarballPath = "/path/to/other-release.tgz"

				fakeFs.WriteFileString(otherReleaseTarballPath, "")

				fakeOtherRelease = fakebirel.New("other-release", "1234")

				fakeOtherRelease.ReleaseIsCompiled = true
				fakeOtherRelease.ReleaseJobs = []bireljob.Job{{Name: "not-cpi"}}
				fakeOtherRelease.ReleasePackages = []*bipkg.Package{
					{
						Stemcell: "ubuntu-trusty/fake-stemcell-version",
					},
				}
				expectOtherReleaseExtract = mockReleaseExtractor.EXPECT().Extract(
					otherReleaseTarballPath,
				).Return(fakeOtherRelease, nil).AnyTimes()

				releaseSetManifest = birelsetmanifest.Manifest{
					Releases: []birelmanifest.ReleaseRef{
						{
							Name: "fake-cpi-release-name",
							URL:  "file://" + cpiReleaseTarballPath,
						},
						{
							Name: "other-release",
							URL:  "file://" + otherReleaseTarballPath,
						},
					},
				}

				boshDeploymentManifest = bideplmanifest.Manifest{
					Name: "fake-deployment-name",
					Jobs: []bideplmanifest.Job{
						{
							Name: "fake-job-name",
							Templates: []bideplmanifest.ReleaseJobRef{
								{
									Release: "other-release",
								},
							},
						},
					},
					ResourcePools: []bideplmanifest.ResourcePool{
						{
							Stemcell: bideplmanifest.StemcellRef{
								URL: "file://" + stemcellTarballPath,
							},
						},
					},
				}
			})

			It("extracts the compiled release tarball", func() {
				expectOtherReleaseExtract.Times(1)

				err := command.Run(fakeStage, []string{deploymentManifestPath})
				Expect(err).NotTo(HaveOccurred())
			})

			It("parse compiled releases correctly", func() {
				err := command.Run(fakeStage, []string{deploymentManifestPath})
				Expect(err).ToNot(HaveOccurred())
			})

			It("returns error if compiled package stemcell does not match the deployment stemcell", func() {
				fakeOtherRelease.ReleasePackages = []*bipkg.Package{
					{
						Stemcell: "ubuntu-trusty/wrong-version",
					},
				}

				err := command.Run(fakeStage, []string{deploymentManifestPath})
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("OS/Version mismatch between deployment stemcell and compiled package stemcell for release 'other-release'"))
			})

			It("returns error if CPI release is compiled", func() {
				fakeCPIRelease.ReleaseIsCompiled = true

				err := command.Run(fakeStage, []string{deploymentManifestPath})
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("CPI is not allowed to be a compiled release. The provided CPI release 'fake-cpi-release-name' is compiled"))
			})
		})
	})
}
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("returs a dummy cert manager", func() {
			certManager := platform.GetCertManager()

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

	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, _ := json.Marshal(mounts)

				mountsPath := path.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(isMountPoint).To(Equal(false))

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

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

			userPasswordsPath := path.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 := path.Join(dirProvider.BoshDir(), "user-name1", CredentialFileName)
			password, err := fs.ReadFileString(userPasswordsPath)
			Expect(err).NotTo(HaveOccurred())
			Expect(password).To(Equal("fake-password1"))

			userPasswordsPath = path.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("/fake-dir/sys")

			Expect(stat).ToNot(BeNil())
			Expect(stat.SymlinkTarget).To(Equal("/fake-dir/data/sys"))
		})
	})
}
func describeCentosNetManager() {
	var (
		fs                            *fakesys.FakeFileSystem
		cmdRunner                     *fakesys.FakeCmdRunner
		ipResolver                    *fakeip.FakeResolver
		interfaceAddrsProvider        *fakeip.FakeInterfaceAddressesProvider
		addressBroadcaster            *fakearp.FakeAddressBroadcaster
		netManager                    Manager
		interfaceConfigurationCreator InterfaceConfigurationCreator
	)

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

	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
	}

	Describe("SetupNetworking", func() {
		var (
			dhcpNetwork                           boshsettings.Network
			staticNetwork                         boshsettings.Network
			expectedNetworkConfigurationForStatic string
			expectedNetworkConfigurationForDHCP   string
			expectedDhclientConfiguration         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",
				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
`)

			expectedNetworkConfigurationForStatic = `DEVICE=ethstatic
BOOTPROTO=static
IPADDR=1.2.3.4
NETMASK=255.255.255.0
BROADCAST=1.2.3.255
GATEWAY=3.4.5.6
ONBOOT=yes
PEERDNS=no
DNS1=8.8.8.8
DNS2=9.9.9.9
`

			expectedNetworkConfigurationForDHCP = `DEVICE=ethdhcp
BOOTPROTO=dhcp
ONBOOT=yes
PEERDNS=yes
`

			expectedDhclientConfiguration = `# 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;
`
		})

		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)
		}

		It("writes a network script for static and dynamic interfaces", 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())

			staticConfig := fs.GetFileTestStat("/etc/sysconfig/network-scripts/ifcfg-ethstatic")
			Expect(staticConfig).ToNot(BeNil())
			Expect(staticConfig.StringContents()).To(Equal(expectedNetworkConfigurationForStatic))

			dhcpConfig := fs.GetFileTestStat("/etc/sysconfig/network-scripts/ifcfg-ethdhcp")
			Expect(dhcpConfig).ToNot(BeNil())
			Expect(dhcpConfig.StringContents()).To(Equal(expectedNetworkConfigurationForDHCP))
		})

		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 create network interface configurations", func() {
			stubInterfaces(map[string]boshsettings.Network{
				"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("wrtites 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(expectedDhclientConfiguration))

			dhcpConfigSymlink := fs.GetFileTestStat("/etc/dhcp/dhclient-ethdhcp.conf")
			Expect(dhcpConfigSymlink).ToNot(BeNil())
			Expect(dhcpConfigSymlink.SymlinkTarget).To(Equal("/etc/dhcp/dhclient.conf"))
		})

		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;

`))
			dhcpConfigSymlink := fs.GetFileTestStat("/etc/dhcp/dhclient-ethdhcp.conf")
			Expect(dhcpConfigSymlink).ToNot(BeNil())
			Expect(dhcpConfigSymlink.SymlinkTarget).To(Equal("/etc/dhcp/dhclient.conf"))
		})

		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("returns an error if it can't symlink a dhcp configuration", func() {
			stubInterfaces(map[string]boshsettings.Network{
				"ethdhcp":   dhcpNetwork,
				"ethstatic": staticNetwork,
			})

			fs.SymlinkError = errors.New("dhclient-ethdhcp.conf-symlink-error")

			err := netManager.SetupNetworking(boshsettings.Networks{"dhcp-network": dhcpNetwork, "static-network": staticNetwork}, nil)
			Expect(err).To(HaveOccurred())
			Expect(err.Error()).To(ContainSubstring("dhclient-ethdhcp.conf-symlink-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-ethdhcp.conf")
			Expect(dhcpConfig).To(BeNil())
		})

		It("restarts the networks if any ifconfig file changes", func() {
			changingStaticNetwork := boshsettings.Network{
				Type:    "manual",
				IP:      "1.2.3.5",
				Netmask: "255.255.255.0",
				Gateway: "3.4.5.6",
				Mac:     "ethstatict-that-changes",
			}

			stubInterfaces(map[string]boshsettings.Network{
				"ethdhcp":                dhcpNetwork,
				"ethstatic-that-changes": changingStaticNetwork,
				"ethstatic":              staticNetwork,
			})
			interfaceAddrsProvider.GetInterfaceAddresses = []boship.InterfaceAddress{
				boship.NewSimpleInterfaceAddress("ethstatic", "1.2.3.4"),
				boship.NewSimpleInterfaceAddress("ethstatic-that-changes", "1.2.3.5"),
			}

			fs.WriteFileString("/etc/sysconfig/network-scripts/ifcfg-ethstatic", expectedNetworkConfigurationForStatic)
			fs.WriteFileString("/etc/dhcp/dhclient.conf", expectedDhclientConfiguration)

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

			Expect(len(cmdRunner.RunCommands)).To(Equal(1))
			Expect(cmdRunner.RunCommands[0]).To(Equal([]string{"service", "network", "restart"}))
		})

		It("doesn't restart the networks if ifcfg and /etc/dhcp/dhclient.conf don't change", func() {
			stubInterfaces(map[string]boshsettings.Network{
				"ethdhcp":   dhcpNetwork,
				"ethstatic": staticNetwork,
			})

			fs.WriteFileString("/etc/sysconfig/network-scripts/ifcfg-ethstatic", expectedNetworkConfigurationForStatic)
			fs.WriteFileString("/etc/sysconfig/network-scripts/ifcfg-ethdhcp", expectedNetworkConfigurationForDHCP)
			fs.WriteFileString("/etc/dhcp/dhclient.conf", expectedDhclientConfiguration)

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

			networkConfig := fs.GetFileTestStat("/etc/sysconfig/network-scripts/ifcfg-ethstatic")
			Expect(networkConfig).ToNot(BeNil())
			Expect(networkConfig.StringContents()).To(Equal(expectedNetworkConfigurationForStatic))

			dhcpConfig := fs.GetFileTestStat("/etc/dhcp/dhclient.conf")
			Expect(dhcpConfig.StringContents()).To(Equal(expectedDhclientConfiguration))

			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/sysconfig/network-scripts/ifcfg-ethstatic", expectedNetworkConfigurationForStatic)

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

			networkConfig := fs.GetFileTestStat("/etc/sysconfig/network-scripts/ifcfg-ethstatic")
			Expect(networkConfig).ToNot(BeNil())
			Expect(networkConfig.StringContents()).To(Equal(expectedNetworkConfigurationForStatic))

			Expect(len(cmdRunner.RunCommands)).To(Equal(1))
			Expect(cmdRunner.RunCommands[0]).To(Equal([]string{"service", "network", "restart"}))
		})

		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"))
			})
		})

		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{"4.4.4.4", "5.5.5.5"},
				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/sysconfig/network-scripts/ifcfg-ethstatic")
			Expect(networkConfig).ToNot(BeNil())
			Expect(networkConfig.StringContents()).To(Equal(expectedNetworkConfigurationForStatic))
		})

		It("doesn't use vip networks dns", func() {
			stubInterfaces(map[string]boshsettings.Network{
				"ethstatic": staticNetwork,
			})

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

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

			networkConfig := fs.GetFileTestStat("/etc/sysconfig/network-scripts/ifcfg-ethstatic")
			Expect(networkConfig).ToNot(BeNil())
			Expect(networkConfig.StringContents()).ToNot(ContainSubstring("4.4.4.4"))
			Expect(networkConfig.StringContents()).ToNot(ContainSubstring("5.5.5.5"))
		})

		Context("when no MAC address is provided in the settings", func() {
			var staticNetworkWithoutMAC boshsettings.Network

			BeforeEach(func() {
				staticNetworkWithoutMAC = boshsettings.Network{
					Type:    "manual",
					IP:      "1.2.3.4",
					Netmask: "255.255.255.0",
					Gateway: "3.4.5.6",
					DNS:     []string{"8.8.8.8", "9.9.9.9"},
					Default: []string{"dns"},
				}
			})

			It("configures network for single device", func() {
				stubInterfaces(
					map[string]boshsettings.Network{
						"ethstatic": staticNetwork,
					},
				)

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

				networkConfig := fs.GetFileTestStat("/etc/sysconfig/network-scripts/ifcfg-ethstatic")
				Expect(networkConfig).ToNot(BeNil())
				Expect(networkConfig.StringContents()).To(Equal(expectedNetworkConfigurationForStatic))
			})

			It("configures network for single device, when a virtual device is also present", func() {
				stubInterfacesWithVirtual(
					map[string]boshsettings.Network{
						"ethstatic": staticNetwork,
					},
					[]string{"virtual"},
				)

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

				physicalNetworkConfig := fs.GetFileTestStat("/etc/sysconfig/network-scripts/ifcfg-ethstatic")
				Expect(physicalNetworkConfig).ToNot(BeNil())
				Expect(physicalNetworkConfig.StringContents()).To(Equal(expectedNetworkConfigurationForStatic))

				virtualNetworkConfig := fs.GetFileTestStat("/etc/sysconfig/network-scripts/ifcfg-virtual")
				Expect(virtualNetworkConfig).To(BeNil())
			})
		})
	})

	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)
			})

			writeIfcgfFile := func(iface string) {
				fs.WriteFileString(fmt.Sprintf("/etc/sysconfig/network-scripts/ifcfg-%s", iface), "fake-config")
			}

			It("returns networks that have ifcfg config present", func() {
				writeIfcgfFile("fake-eth0")
				writeIfcgfFile("fake-eth2")

				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{}))
			})
		})
	})
}
		It("returns path", func() {
			Expect(genericScript.Tag()).To(Equal("my-tag"))
		})
	})

	Describe("Path", func() {
		It("returns path", func() {
			Expect(genericScript.Path()).To(Equal("/path-to-script"))
		})
	})

	Describe("Exists", func() {
		It("returns bool", func() {
			Expect(genericScript.Exists()).To(BeFalse())

			fs.WriteFile("/path-to-script", []byte{})
			Expect(genericScript.Exists()).To(BeTrue())
		})
	})

	Describe("Run", func() {
		It("executes given command", func() {
			err := genericScript.Run()
			Expect(err).ToNot(HaveOccurred())
		})

		It("returns an error if it fails to create logs directory", func() {
			fs.MkdirAllError = errors.New("fake-mkdir-all-error")

			err := genericScript.Run()
			Expect(err).To(HaveOccurred())
		runner = NewFileLoggingCmdRunner(fs, cmdRunner, "/fake-base-dir", 15)

		cmd = boshsys.Command{
			Name:       "fake-cmd",
			Args:       []string{"fake-args"},
			Env:        map[string]string{"fake-env-key": "fake-env-var"},
			WorkingDir: "/fake-working-dir",
		}
	})

	Describe("RunCommand", func() {
		It("cleans logs directory", func() {
			err := fs.MkdirAll("/fake-base-dir/fake-log-dir-name/", os.FileMode(0750))
			Expect(err).ToNot(HaveOccurred())

			err = fs.WriteFile("/fake-base-dir/fake-log-dir-name/old-file", []byte("test-data"))
			Expect(err).ToNot(HaveOccurred())

			_, err = runner.RunCommand("fake-log-dir-name", "fake-log-file-name", cmd)
			Expect(err).ToNot(HaveOccurred())

			Expect(fs.FileExists("/fake-base-dir/fake-log-dir-name/old-file")).To(BeFalse())
		})

		It("returns an error if it fails to remove previous logs directory", func() {
			fs.RemoveAllError = errors.New("fake-remove-all-error")

			_, err := runner.RunCommand("fake-log-dir-name", "fake-log-file-name", cmd)
			Expect(err).To(HaveOccurred())
			Expect(err.Error()).To(ContainSubstring("fake-remove-all-error"))
		})
Beispiel #10
0
	var (
		fs        *fakesys.FakeFileSystem
		cmdRunner *fakesys.FakeCmdRunner
		arping    AddressBroadcaster
	)

	BeforeEach(func() {
		fs = fakesys.NewFakeFileSystem()
		cmdRunner = fakesys.NewFakeCmdRunner()
		logger := boshlog.NewLogger(boshlog.LevelNone)
		arping = NewArping(cmdRunner, fs, logger, arpingIterations, 0, 0)
	})

	Describe("BroadcastMACAddresses", func() {
		BeforeEach(func() {
			fs.WriteFile("/sys/class/net/eth0", []byte{})
			fs.WriteFile("/sys/class/net/eth1", []byte{})
		})

		It("runs arping commands for each interface", func() {
			addresses := []boship.InterfaceAddress{
				boship.NewSimpleInterfaceAddress("eth0", "192.168.195.6"),
				boship.NewSimpleInterfaceAddress("eth1", "127.0.0.1"),
			}

			arping.BroadcastMACAddresses(addresses)

			countA := 0
			countB := 0

			a := []string{"arping", "-c", "1", "-U", "-I", "eth0", "192.168.195.6"}
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
		)

		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`
		})

		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{}))
			})
		})
	})
}
	})

	Describe("GetSettings", func() {
		Context("when the registry file exists", func() {
			var (
				expectedSettings boshsettings.Settings
			)

			BeforeEach(func() {
				expectedSettings = boshsettings.Settings{
					AgentID: "fake-agent-id",
				}
				settingsJSON, err := json.Marshal(expectedSettings)
				Expect(err).ToNot(HaveOccurred())

				fs.WriteFile("/fake-registry-file-path", settingsJSON)
			})

			It("returns the settings", func() {
				settings, err := fileRegistry.GetSettings()
				Expect(err).ToNot(HaveOccurred())
				Expect(settings).To(Equal(expectedSettings))
			})
		})

		Context("when the registry file does not exist", func() {
			It("returns an error", func() {
				_, err := fileRegistry.GetSettings()
				Expect(err).To(HaveOccurred())
			})
		})
func init() {
	Describe("renderedJobApplier", func() {
		var (
			jobsBc                 *fakebc.FakeBundleCollection
			jobSupervisor          *fakejobsuper.FakeJobSupervisor
			packageApplierProvider *fakepackages.FakeApplierProvider
			blobstore              *fakeblob.FakeBlobstore
			compressor             *fakecmd.FakeCompressor
			fs                     *fakesys.FakeFileSystem
			applier                Applier
		)

		BeforeEach(func() {
			jobsBc = fakebc.NewFakeBundleCollection()
			jobSupervisor = fakejobsuper.NewFakeJobSupervisor()
			packageApplierProvider = fakepackages.NewFakeApplierProvider()
			blobstore = fakeblob.NewFakeBlobstore()
			fs = fakesys.NewFakeFileSystem()
			compressor = fakecmd.NewFakeCompressor()
			logger := boshlog.NewLogger(boshlog.LevelNone)
			applier = NewRenderedJobApplier(
				jobsBc,
				jobSupervisor,
				packageApplierProvider,
				blobstore,
				compressor,
				fs,
				logger,
			)
		})

		Describe("Prepare & Apply", func() {
			var (
				job    models.Job
				bundle *fakebc.FakeBundle
			)

			BeforeEach(func() {
				job, bundle = buildJob(jobsBc)

			})

			ItInstallsJob := func(act func() error) {
				BeforeEach(func() {
					fs.TempDirDir = "/fake-tmp-dir"
				})

				It("returns error when installing job fails", func() {
					bundle.InstallError = errors.New("fake-install-error")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-install-error"))
				})

				It("downloads and later cleans up downloaded job template blob", func() {
					blobstore.GetFileName = "/fake-blobstore-file-name"

					err := act()
					Expect(err).ToNot(HaveOccurred())
					Expect(blobstore.GetBlobIDs[0]).To(Equal("fake-blobstore-id"))
					Expect(blobstore.GetFingerprints[0]).To(Equal(boshcrypto.NewDigest("sha1", "fake-blob-sha1")))

					// downloaded file is cleaned up
					Expect(blobstore.CleanUpFileName).To(Equal("/fake-blobstore-file-name"))
				})

				It("returns error when downloading job template blob fails", func() {
					blobstore.GetError = errors.New("fake-get-error")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-get-error"))
				})

				It("decompresses job template blob to tmp path and later cleans it up", func() {
					blobstore.GetFileName = "/fake-blobstore-file-name"

					var tmpDirExistsBeforeInstall bool

					bundle.InstallCallBack = func() {
						tmpDirExistsBeforeInstall = true
					}

					err := act()
					Expect(err).ToNot(HaveOccurred())

					Expect(compressor.DecompressFileToDirTarballPaths[0]).To(Equal("/fake-blobstore-file-name"))
					Expect(compressor.DecompressFileToDirDirs[0]).To(Equal("/fake-tmp-dir"))

					// tmp dir exists before bundle install
					Expect(tmpDirExistsBeforeInstall).To(BeTrue())

					// tmp dir is cleaned up after install
					Expect(fs.FileExists(fs.TempDirDir)).To(BeFalse())
				})

				It("returns error when temporary directory creation fails", func() {
					fs.TempDirError = errors.New("fake-filesystem-tempdir-error")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-filesystem-tempdir-error"))
				})

				It("can process sha1 checksums in the new format", func() {
					blobstore.GetFileName = "/fake-blobstore-file-name"
					job.Source.Sha1 = "sha1:fake-blob-sha1"

					err := act()
					Expect(err).ToNot(HaveOccurred())
					Expect(blobstore.GetBlobIDs[0]).To(Equal("fake-blobstore-id"))
					Expect(blobstore.GetFingerprints[0]).To(Equal(boshcrypto.NewDigest("sha1", "fake-blob-sha1")))
				})

				It("can process sha2 checksums", func() {
					blobstore.GetFileName = "/fake-blobstore-file-name"
					job.Source.Sha1 = "sha256:fake-blob-sha256"

					err := act()
					Expect(err).ToNot(HaveOccurred())
					Expect(blobstore.GetBlobIDs[0]).To(Equal("fake-blobstore-id"))
					Expect(blobstore.GetFingerprints[0]).To(Equal(boshcrypto.NewDigest("sha256", "fake-blob-sha256")))
				})

				It("returns error when given and unsupported fingerprint", func() {
					blobstore.GetFileName = "/fake-blobstore-file-name"
					job.Source.Sha1 = "unsupported:checksum"

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("Parsing job blob digest"))
				})

				It("returns error when decompressing job template fails", func() {
					compressor.DecompressFileToDirErr = errors.New("fake-decompress-error")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-decompress-error"))
				})

				It("returns error when walking the tree of files fails", func() {
					fs.WalkErr = errors.New("fake-walk-error")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-walk-error"))
				})

				It("installs bundle from decompressed tmp path of a job template", func() {
					var installedBeforeDecompression bool

					compressor.DecompressFileToDirCallBack = func() {
						installedBeforeDecompression = bundle.Installed
					}

					err := act()
					Expect(err).ToNot(HaveOccurred())

					// bundle installation did not happen before decompression
					Expect(installedBeforeDecompression).To(BeFalse())

					// make sure that bundle install happened after decompression
					Expect(bundle.InstallSourcePath).To(Equal("/fake-tmp-dir/fake-path-in-archive"))
				})

				It("sets executable bit for the bin and config directories", func() {
					var binDirStats, configDirStats *fakesys.FakeFileStats
					compressor.DecompressFileToDirCallBack = func() {
						fs.WriteFile("/fake-tmp-dir/fake-path-in-archive/bin/blarg", []byte{})
						fs.WriteFile("/fake-tmp-dir/fake-path-in-archive/config/blarg.yml", []byte{})
					}

					bundle.InstallCallBack = func() {
						binDirStats = fs.GetFileTestStat("/fake-tmp-dir/fake-path-in-archive/bin")
						configDirStats = fs.GetFileTestStat("/fake-tmp-dir/fake-path-in-archive/config")
					}

					err := act()
					Expect(err).ToNot(HaveOccurred())

					Expect(int(binDirStats.FileMode)).To(Equal(0755))
					Expect(int(configDirStats.FileMode)).To(Equal(0755))
				})

				It("sets executable bit for files in bin", func() {
					compressor.DecompressFileToDirCallBack = func() {
						fs.WriteFile("/fake-tmp-dir/fake-path-in-archive/bin/test1", []byte{})
						fs.WriteFile("/fake-tmp-dir/fake-path-in-archive/bin/test2", []byte{})
						fs.WriteFile("/fake-tmp-dir/fake-path-in-archive/config/test", []byte{})
					}

					fs.SetGlob("/fake-tmp-dir/fake-path-in-archive/bin/*", []string{
						"/fake-tmp-dir/fake-path-in-archive/bin/test1",
						"/fake-tmp-dir/fake-path-in-archive/bin/test2",
					})

					var binTest1Stats, binTest2Stats, configTestStats *fakesys.FakeFileStats

					bundle.InstallCallBack = func() {
						binTest1Stats = fs.GetFileTestStat("/fake-tmp-dir/fake-path-in-archive/bin/test1")
						binTest2Stats = fs.GetFileTestStat("/fake-tmp-dir/fake-path-in-archive/bin/test2")
						configTestStats = fs.GetFileTestStat("/fake-tmp-dir/fake-path-in-archive/config/test")
					}

					err := act()
					Expect(err).ToNot(HaveOccurred())

					// bin files are executable
					Expect(int(binTest1Stats.FileMode)).To(Equal(0755))
					Expect(int(binTest2Stats.FileMode)).To(Equal(0755))

					// non-bin files are not made executable
					Expect(int(configTestStats.FileMode)).ToNot(Equal(0755))
				})

				It("sets 644 permissions for files in config", func() {
					compressor.DecompressFileToDirCallBack = func() {
						fs.WriteFile("/fake-tmp-dir/fake-path-in-archive/config/config1", []byte{})
						fs.WriteFile("/fake-tmp-dir/fake-path-in-archive/config/config2", []byte{})
					}

					fs.SetGlob("/fake-tmp-dir/fake-path-in-archive/config/*", []string{
						"/fake-tmp-dir/fake-path-in-archive/config/config1",
						"/fake-tmp-dir/fake-path-in-archive/config/config2",
					})

					var config1Stats, config2Stats *fakesys.FakeFileStats

					bundle.InstallCallBack = func() {
						config1Stats = fs.GetFileTestStat("/fake-tmp-dir/fake-path-in-archive/config/config1")
						config2Stats = fs.GetFileTestStat("/fake-tmp-dir/fake-path-in-archive/config/config2")
					}

					err := act()
					Expect(err).ToNot(HaveOccurred())

					// permission for config files should be readable by all
					Expect(int(config1Stats.FileMode)).To(Equal(0644))
					Expect(int(config2Stats.FileMode)).To(Equal(0644))
				})
			}

			ItUpdatesPackages := func(act func() error) {
				var packageApplier *fakepackages.FakeApplier

				BeforeEach(func() {
					packageApplier = fakepackages.NewFakeApplier()
					packageApplierProvider.JobSpecificAppliers[job.Name] = packageApplier
				})

				It("applies each package that job depends on and then cleans up packages", func() {
					err := act()
					Expect(err).ToNot(HaveOccurred())
					Expect(packageApplier.ActionsCalled).To(Equal([]string{"Apply", "Apply", "KeepOnly"}))
					Expect(len(packageApplier.AppliedPackages)).To(Equal(2)) // present
					Expect(packageApplier.AppliedPackages).To(Equal(job.Packages))
				})

				It("returns error when applying package that job depends on fails", func() {
					packageApplier.ApplyError = errors.New("fake-apply-err")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-apply-err"))
				})

				It("keeps only currently required packages but does not completely uninstall them", func() {
					err := act()
					Expect(err).ToNot(HaveOccurred())
					Expect(len(packageApplier.KeptOnlyPackages)).To(Equal(2)) // present
					Expect(packageApplier.KeptOnlyPackages).To(Equal(job.Packages))
				})

				It("returns error when keeping only currently required packages fails", func() {
					packageApplier.KeepOnlyErr = errors.New("fake-keep-only-err")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-keep-only-err"))
				})
			}

			Describe("Prepare", func() {
				act := func() error { return applier.Prepare(job) }

				It("return an error if getting file bundle fails", func() {
					jobsBc.GetErr = errors.New("fake-get-bundle-error")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-get-bundle-error"))
				})

				It("returns an error if checking for installed path fails", func() {
					bundle.IsInstalledErr = errors.New("fake-is-installed-error")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-is-installed-error"))
				})

				Context("when job is already installed", func() {
					BeforeEach(func() {
						bundle.Installed = true
					})

					It("does not install", func() {
						err := act()
						Expect(err).ToNot(HaveOccurred())
						Expect(bundle.ActionsCalled).To(Equal([]string{})) // no Install
					})

					It("does not download the job template", func() {
						err := act()
						Expect(err).ToNot(HaveOccurred())
						Expect(blobstore.GetBlobIDs).To(BeNil())
					})
				})

				Context("when job is not installed", func() {
					BeforeEach(func() {
						bundle.Installed = false
					})

					It("installs job (but does not enable)", func() {
						err := act()
						Expect(err).ToNot(HaveOccurred())
						Expect(bundle.ActionsCalled).To(Equal([]string{"Install"}))
					})

					ItInstallsJob(act)
				})
			})

			Describe("Apply", func() {
				act := func() error { return applier.Apply(job) }

				It("return an error if getting file bundle fails", func() {
					jobsBc.GetErr = errors.New("fake-get-bundle-error")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-get-bundle-error"))
				})

				It("returns an error if checking for installed path fails", func() {
					bundle.IsInstalledErr = errors.New("fake-is-installed-error")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-is-installed-error"))
				})

				Context("when job is already installed", func() {
					BeforeEach(func() {
						bundle.Installed = true
					})

					It("does not install but only enables job", func() {
						err := act()
						Expect(err).ToNot(HaveOccurred())
						Expect(bundle.ActionsCalled).To(Equal([]string{"Enable"})) // no Install
					})

					It("returns error when job enable fails", func() {
						bundle.EnableError = errors.New("fake-enable-error")

						err := act()
						Expect(err).To(HaveOccurred())
						Expect(err.Error()).To(ContainSubstring("fake-enable-error"))
					})

					It("does not download the job template", func() {
						err := act()
						Expect(err).ToNot(HaveOccurred())
						Expect(blobstore.GetBlobIDs).To(BeNil())
					})

					ItUpdatesPackages(act)
				})

				Context("when job is not installed", func() {
					BeforeEach(func() {
						bundle.Installed = false
					})

					It("installs and enables job", func() {
						err := act()
						Expect(err).ToNot(HaveOccurred())
						Expect(bundle.ActionsCalled).To(Equal([]string{"Install", "Enable"}))
					})

					It("returns error when job enable fails", func() {
						bundle.EnableError = errors.New("fake-enable-error")

						err := act()
						Expect(err).To(HaveOccurred())
						Expect(err.Error()).To(ContainSubstring("fake-enable-error"))
					})

					ItInstallsJob(act)

					ItUpdatesPackages(act)
				})
			})
		})

		Describe("Configure", func() {
			It("adds job to the job supervisor", func() {
				job, bundle := buildJob(jobsBc)

				fs := fakesys.NewFakeFileSystem()
				fs.WriteFileString("/path/to/job/monit", "some conf")
				fs.SetGlob("/path/to/job/*.monit", []string{"/path/to/job/subjob.monit"})

				bundle.GetDirPath = "/path/to/job"
				bundle.GetDirFs = fs

				err := applier.Configure(job, 0)
				Expect(err).ToNot(HaveOccurred())

				Expect(len(jobSupervisor.AddJobArgs)).To(Equal(2))

				Expect(jobSupervisor.AddJobArgs[0]).To(Equal(fakejobsuper.AddJobArgs{
					Name:       job.Name,
					Index:      0,
					ConfigPath: "/path/to/job/monit",
				}))

				Expect(jobSupervisor.AddJobArgs[1]).To(Equal(fakejobsuper.AddJobArgs{
					Name:       job.Name + "_subjob",
					Index:      0,
					ConfigPath: "/path/to/job/subjob.monit",
				}))
			})

			It("does not require monit script", func() {
				job, bundle := buildJob(jobsBc)

				fs := fakesys.NewFakeFileSystem()
				bundle.GetDirFs = fs

				err := applier.Configure(job, 0)
				Expect(err).ToNot(HaveOccurred())
				Expect(len(jobSupervisor.AddJobArgs)).To(Equal(0))
			})
		})

		Describe("KeepOnly", func() {
			It("first disables and then uninstalls jobs that are not in keeponly list", func() {
				_, bundle1 := buildJob(jobsBc)
				job2, bundle2 := buildJob(jobsBc)
				_, bundle3 := buildJob(jobsBc)
				job4, bundle4 := buildJob(jobsBc)

				jobsBc.ListBundles = []boshbc.Bundle{bundle1, bundle2, bundle3, bundle4}

				err := applier.KeepOnly([]models.Job{job4, job2})
				Expect(err).ToNot(HaveOccurred())

				Expect(bundle1.ActionsCalled).To(Equal([]string{"Disable", "Uninstall"}))
				Expect(bundle2.ActionsCalled).To(Equal([]string{}))
				Expect(bundle3.ActionsCalled).To(Equal([]string{"Disable", "Uninstall"}))
				Expect(bundle4.ActionsCalled).To(Equal([]string{}))
			})

			It("returns error when bundle collection fails to return list of installed bundles", func() {
				jobsBc.ListErr = errors.New("fake-bc-list-error")

				err := applier.KeepOnly([]models.Job{})
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-bc-list-error"))
			})

			It("returns error when bundle collection cannot retrieve bundle for keep-only job", func() {
				job1, bundle1 := buildJob(jobsBc)

				jobsBc.ListBundles = []boshbc.Bundle{bundle1}
				jobsBc.GetErr = errors.New("fake-bc-get-error")

				err := applier.KeepOnly([]models.Job{job1})
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-bc-get-error"))
			})

			It("returns error when at least one bundle cannot be disabled", func() {
				_, bundle1 := buildJob(jobsBc)

				jobsBc.ListBundles = []boshbc.Bundle{bundle1}
				bundle1.DisableErr = errors.New("fake-bc-disable-error")

				err := applier.KeepOnly([]models.Job{})
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-bc-disable-error"))
			})

			It("returns error when at least one bundle cannot be uninstalled", func() {
				_, bundle1 := buildJob(jobsBc)

				jobsBc.ListBundles = []boshbc.Bundle{bundle1}
				bundle1.UninstallErr = errors.New("fake-bc-uninstall-error")

				err := applier.KeepOnly([]models.Job{})
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-bc-uninstall-error"))
			})
		})
	})
}
func init() {
	Describe("prepareNetworkChange", func() {
		var (
			action PrepareNetworkChangeAction
			fs     *fakesys.FakeFileSystem

			settingsService *fakesettings.FakeSettingsService
		)

		BeforeEach(func() {
			fs = fakesys.NewFakeFileSystem()
			settingsService = &fakesettings.FakeSettingsService{}
			action = NewPrepareNetworkChange(fs, settingsService, fakeactions.NewFakeAgentKiller())
		})

		It("is synchronous", func() {
			Expect(action.IsAsynchronous()).To(BeFalse())
		})

		It("is not persistent", func() {
			Expect(action.IsPersistent()).To(BeFalse())
		})

		It("invalidates settings so that load settings cannot fall back on old settings", func() {
			resp, err := action.Run()
			Expect(err).NotTo(HaveOccurred())
			Expect(resp).To(Equal("ok"))

			Expect(settingsService.SettingsWereInvalidated).To(BeTrue())
		})

		Context("when settings invalidation succeeds", func() {
			Context("when the network rules file can be removed", func() {
				It("removes the network rules file", func() {
					fs.WriteFile("/etc/udev/rules.d/70-persistent-net.rules", []byte{})

					resp, err := action.Run()
					Expect(err).NotTo(HaveOccurred())
					Expect(resp).To(Equal("ok"))

					Expect(fs.FileExists("/etc/udev/rules.d/70-persistent-net.rules")).To(BeFalse())
				})
			})

			Context("when the network rules file cannot be removed", func() {
				BeforeEach(func() {
					fs.RemoveAllStub = func(_ string) error {
						return errors.New("fake-remove-all-error")
					}
				})

				It("returns error from removing the network rules file", func() {
					resp, err := action.Run()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-remove-all-error"))

					Expect(resp).To(BeNil())
				})
			})
		})

		Context("when settings invalidation fails", func() {
			BeforeEach(func() {
				settingsService.InvalidateSettingsError = errors.New("fake-invalidate-error")
			})

			It("returns error early if settings err invalidating", func() {
				resp, err := action.Run()
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-invalidate-error"))

				Expect(resp).To(BeNil())
			})

			It("does not remove the network rules file", func() {
				fs.WriteFile("/etc/udev/rules.d/70-persistent-net.rules", []byte{})

				action.Run()
				Expect(fs.FileExists("/etc/udev/rules.d/70-persistent-net.rules")).To(BeTrue())
			})
		})
	})
}
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)))
		})
	})
}
			formatter.Format("/dev/xvda2", FileSystemExt4)

			Expect(2).To(Equal(len(fakeRunner.RunCommands)))
			Expect(fakeRunner.RunCommands[1]).To(Equal([]string{"mke2fs", "-t", "ext4", "-j", "-E", "lazy_itable_init=1", "/dev/xvda2"}))

		})

		Context("when mke2fs errors", func() {
			var fakeRunner *fakesys.FakeCmdRunner
			var fakeFs *fakesys.FakeFileSystem
			var mkeCmd string

			BeforeEach(func() {
				fakeRunner = fakesys.NewFakeCmdRunner()
				fakeFs = fakesys.NewFakeFileSystem()
				fakeFs.WriteFile("/sys/fs/ext4/features/lazy_itable_init", []byte{})
				fakeRunner.AddCmdResult("blkid -p /dev/xvda2", fakesys.FakeCmdResult{Stdout: `xxxxx TYPE="ext2" yyyy zzzz`})

				mkeCmd = fmt.Sprintf("mke2fs -t %s -j -E lazy_itable_init=1 %s", FileSystemExt4, "/dev/xvda2")
			})

			It("retries mke2fs if the erros is 'device is already in use'", func() {
				fakeRunner.AddCmdResult(mkeCmd, fakesys.FakeCmdResult{
					Error: errors.New(`mke2fs 1.42.9 (4-Feb-2014)
/dev/xvdf1 is apparently in use by the system; will not make a filesystem here`),
				})
				fakeRunner.AddCmdResult(mkeCmd, fakesys.FakeCmdResult{
					ExitStatus: 0,
				})
				formatter := NewLinuxFormatter(fakeRunner, fakeFs)
				formatter.Format("/dev/xvda2", FileSystemExt4)