Example #1
0
func newSignalableFileLogger(logPath string, level boshlog.LogLevel) boshlog.Logger {
	// Log file logger errors to the STDERR logger
	logger := boshlog.NewLogger(boshlog.LevelError)
	fileSystem := boshsys.NewOsFileSystem(logger)

	// log file will be closed by process exit
	// log file readable by all
	logfileLogger, _, err := boshlogfile.New(level, logPath, boshlogfile.DefaultLogFileMode, fileSystem)
	if err != nil {
		logger := boshlog.NewLogger(boshlog.LevelError)
		ui := biui.NewConsoleUI(logger)
		fail(err, ui, logger, nil)
	}
	return newSignalableLogger(logfileLogger)
}
Example #2
0
func newLogger() boshlog.Logger {
	logLevelString := os.Getenv("BOSH_INIT_LOG_LEVEL")
	level := boshlog.LevelNone
	if logLevelString != "" {
		var err error
		level, err = boshlog.Levelify(logLevelString)
		if err != nil {
			err = bosherr.WrapError(err, "Invalid BOSH_INIT_LOG_LEVEL value")
			logger := boshlog.NewLogger(boshlog.LevelError)
			ui := biui.NewConsoleUI(logger)
			fail(err, ui, logger, nil)
		}
	}

	logPath := os.Getenv("BOSH_INIT_LOG_PATH")
	if logPath != "" {
		return newSignalableFileLogger(logPath, level)
	}

	return newSignalableLogger(boshlog.NewLogger(level))
}
Example #3
0
func describeDummyPlatform() {
	var (
		platform           Platform
		collector          boshstats.Collector
		fs                 boshsys.FileSystem
		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())
		})
	})
}
func describeHTTPMetadataService() {
	var (
		dnsResolver     *fakeinf.FakeDNSResolver
		platform        *fakeplat.FakePlatform
		logger          boshlog.Logger
		metadataService MetadataService
	)

	BeforeEach(func() {
		dnsResolver = &fakeinf.FakeDNSResolver{}
		platform = fakeplat.NewFakePlatform()
		logger = boshlog.NewLogger(boshlog.LevelNone)
		metadataService = NewHTTPMetadataService("fake-metadata-host", dnsResolver, platform, logger)
	})

	ItEnsuresMinimalNetworkSetup := func(subject func() (string, error)) {
		Context("when no networks are configured", func() {
			BeforeEach(func() {
				platform.GetConfiguredNetworkInterfacesInterfaces = []string{}
			})

			It("sets up DHCP network", func() {
				_, err := subject()
				Expect(err).ToNot(HaveOccurred())

				Expect(platform.SetupNetworkingCalled).To(BeTrue())
				Expect(platform.SetupNetworkingNetworks).To(Equal(boshsettings.Networks{
					"eth0": boshsettings.Network{
						Type: "dynamic",
					},
				}))
			})

			Context("when setting up DHCP fails", func() {
				BeforeEach(func() {
					platform.SetupNetworkingErr = errors.New("fake-network-error")
				})

				It("returns an error", func() {
					_, err := subject()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-network-error"))
				})
			})
		})
	}

	Describe("IsAvailable", func() {
		It("returns true", func() {
			Expect(metadataService.IsAvailable()).To(BeTrue())
		})
	})

	Describe("GetPublicKey", func() {
		var (
			ts *httptest.Server
		)

		BeforeEach(func() {
			handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				GinkgoRecover()

				Expect(r.Method).To(Equal("GET"))
				Expect(r.URL.Path).To(Equal("/latest/meta-data/public-keys/0/openssh-key"))

				w.Write([]byte("fake-public-key"))
			})

			ts = httptest.NewServer(handler)

			metadataService = NewHTTPMetadataService(ts.URL, dnsResolver, platform, logger)
		})

		AfterEach(func() {
			ts.Close()
		})

		ItEnsuresMinimalNetworkSetup(func() (string, error) {
			return metadataService.GetPublicKey()
		})

		It("returns fetched public key", func() {
			publicKey, err := metadataService.GetPublicKey()
			Expect(err).NotTo(HaveOccurred())
			Expect(publicKey).To(Equal("fake-public-key"))
		})
	})

	Describe("GetInstanceID", func() {
		var (
			ts *httptest.Server
		)

		BeforeEach(func() {
			handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				GinkgoRecover()

				Expect(r.Method).To(Equal("GET"))
				Expect(r.URL.Path).To(Equal("/latest/meta-data/instance-id"))

				w.Write([]byte("fake-instance-id"))
			})

			ts = httptest.NewServer(handler)

			metadataService = NewHTTPMetadataService(ts.URL, dnsResolver, platform, logger)
		})

		AfterEach(func() {
			ts.Close()
		})

		ItEnsuresMinimalNetworkSetup(func() (string, error) {
			return metadataService.GetInstanceID()
		})

		It("returns fetched instance id", func() {
			publicKey, err := metadataService.GetInstanceID()
			Expect(err).NotTo(HaveOccurred())
			Expect(publicKey).To(Equal("fake-instance-id"))
		})
	})

	Describe("GetServerName", func() {
		var (
			ts         *httptest.Server
			serverName *string
		)

		handlerFunc := func(w http.ResponseWriter, r *http.Request) {
			GinkgoRecover()

			Expect(r.Method).To(Equal("GET"))
			Expect(r.URL.Path).To(Equal("/latest/user-data"))

			var jsonStr string

			if serverName == nil {
				jsonStr = `{}`
			} else {
				jsonStr = fmt.Sprintf(`{"server":{"name":"%s"}}`, *serverName)
			}

			w.Write([]byte(jsonStr))
		}

		BeforeEach(func() {
			serverName = nil

			handler := http.HandlerFunc(handlerFunc)
			ts = httptest.NewServer(handler)
			metadataService = NewHTTPMetadataService(ts.URL, dnsResolver, platform, logger)
		})

		AfterEach(func() {
			ts.Close()
		})

		Context("when the server name is present in the JSON", func() {
			BeforeEach(func() {
				name := "fake-server-name"
				serverName = &name
			})

			It("returns the server name", func() {
				name, err := metadataService.GetServerName()
				Expect(err).ToNot(HaveOccurred())
				Expect(name).To(Equal("fake-server-name"))
			})

			ItEnsuresMinimalNetworkSetup(func() (string, error) {
				return metadataService.GetServerName()
			})
		})

		Context("when the server name is not present in the JSON", func() {
			BeforeEach(func() {
				serverName = nil
			})

			It("returns an error", func() {
				name, err := metadataService.GetServerName()
				Expect(err).To(HaveOccurred())
				Expect(name).To(BeEmpty())
			})
		})
	})

	Describe("GetRegistryEndpoint", func() {
		var (
			ts          *httptest.Server
			registryURL *string
			dnsServer   *string
		)

		handlerFunc := func(w http.ResponseWriter, r *http.Request) {
			GinkgoRecover()

			Expect(r.Method).To(Equal("GET"))
			Expect(r.URL.Path).To(Equal("/latest/user-data"))

			var jsonStr string

			if dnsServer == nil {
				jsonStr = fmt.Sprintf(`{"registry":{"endpoint":"%s"}}`, *registryURL)
			} else {
				jsonStr = fmt.Sprintf(`{
					"registry":{"endpoint":"%s"},
					"dns":{"nameserver":["%s"]}
				}`, *registryURL, *dnsServer)
			}

			w.Write([]byte(jsonStr))
		}

		BeforeEach(func() {
			url := "http://fake-registry.com"
			registryURL = &url
			dnsServer = nil

			handler := http.HandlerFunc(handlerFunc)
			ts = httptest.NewServer(handler)
			metadataService = NewHTTPMetadataService(ts.URL, dnsResolver, platform, logger)
		})

		AfterEach(func() {
			ts.Close()
		})

		ItEnsuresMinimalNetworkSetup(func() (string, error) {
			return metadataService.GetRegistryEndpoint()
		})

		Context("when metadata contains a dns server", func() {
			BeforeEach(func() {
				server := "fake-dns-server-ip"
				dnsServer = &server
			})

			Context("when registry endpoint is successfully resolved", func() {
				BeforeEach(func() {
					dnsResolver.RegisterRecord(fakeinf.FakeDNSRecord{
						DNSServers: []string{"fake-dns-server-ip"},
						Host:       "http://fake-registry.com",
						IP:         "http://fake-registry-ip",
					})
				})

				It("returns the successfully resolved registry endpoint", func() {
					endpoint, err := metadataService.GetRegistryEndpoint()
					Expect(err).ToNot(HaveOccurred())
					Expect(endpoint).To(Equal("http://fake-registry-ip"))
				})
			})

			Context("when registry endpoint is not successfully resolved", func() {
				BeforeEach(func() {
					dnsResolver.LookupHostErr = errors.New("fake-lookup-host-err")
				})

				It("returns error because it failed to resolve registry endpoint", func() {
					endpoint, err := metadataService.GetRegistryEndpoint()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-lookup-host-err"))
					Expect(endpoint).To(BeEmpty())
				})
			})
		})

		Context("when metadata does not contain dns servers", func() {
			It("returns fetched registry endpoint", func() {
				endpoint, err := metadataService.GetRegistryEndpoint()
				Expect(err).NotTo(HaveOccurred())
				Expect(endpoint).To(Equal("http://fake-registry.com"))
			})
		})
	})

	Describe("GetNetworks", func() {
		It("returns nil networks, since you don't need them for bootstrapping since your network must be set up before you can get the metadata", func() {
			Expect(metadataService.GetNetworks()).To(BeNil())
		})
	})
}
func describeUbuntuNetManager() {
	var (
		fs                            *fakesys.FakeFileSystem
		cmdRunner                     *fakesys.FakeCmdRunner
		ipResolver                    *fakeip.FakeResolver
		addressBroadcaster            *fakearp.FakeAddressBroadcaster
		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{}
		netManager = NewUbuntuNetManager(
			fs,
			cmdRunner,
			ipResolver,
			interfaceConfigurationCreator,
			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",
						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",
				Netmask: "255.255.255.0",
				Gateway: "3.4.5.6",
				Mac:     "fake-static-mac-address",
			}
			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("writes /etc/network/interfaces without dns-namservers if there are no dns servers", func() {
			staticNetworkWithoutDNS := boshsettings.Network{
				Type:    "manual",
				IP:      "1.2.3.4",
				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", "-a", "--no-loopback"}))
			Expect(cmdRunner.RunCommands[4]).To(Equal([]string{"ifup", "-a", "--no-loopback"}))
		})

		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", "-a", "--no-loopback"}))
			Expect(cmdRunner.RunCommands[4]).To(Equal([]string{"ifup", "-a", "--no-loopback"}))
		})

		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 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",
					Netmask: "255.255.255.0",
					Gateway: "3.4.5.6",
				}

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

				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",
					Netmask: "255.255.255.0",
					Gateway: "3.4.5.6",
				}

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

				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{}))
			})
		})
	})
}
Example #6
0
func init() {
	Describe("settingsService", func() {
		var (
			fs                         *fakesys.FakeFileSystem
			fakeDefaultNetworkResolver *fakenet.FakeDefaultNetworkResolver
			fakeSettingsSource         *fakes.FakeSettingsSource
		)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

						It("returns settings from the settings file with resolved network", func() {
							err := service.LoadSettings()
							Expect(err).ToNot(HaveOccurred())
							Expect(service.GetSettings()).To(Equal(Settings{
								AgentID: "some-agent-id",
								Networks: Networks{
									"fake-net-1": Network{
										Type:     NetworkTypeDynamic,
										IP:       "fake-resolved-ip",
										Netmask:  "fake-resolved-netmask",
										Gateway:  "fake-resolved-gateway",
										Resolved: true,
									},
								},
							}))
						})
					})
				})

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

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

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

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

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

		Describe("InvalidateSettings", func() {
			It("removes the settings file", func() {
				fakeSettingsSource.SettingsValue = Settings{}
				fakeSettingsSource.SettingsErr = nil
				service, fs := buildService()

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

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

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

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

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

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

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

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

			JustBeforeEach(func() {
				fakeSettingsSource.SettingsValue = loadedSettings
				fakeSettingsSource.SettingsErr = nil
				service, _ = buildService()
				err := service.LoadSettings()
				Expect(err).NotTo(HaveOccurred())
			})

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

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

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

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

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

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

					It("returns error", func() {
						settings := service.GetSettings()
						Expect(settings).To(Equal(loadedSettings))
					})
				})
			})
		})
	})
}
Example #7
0
func init() {
	Describe("execCmdRunner", func() {
		var (
			runner CmdRunner
		)

		BeforeEach(func() {
			runner = NewExecCmdRunner(boshlog.NewLogger(boshlog.LevelNone))
		})

		Describe("RunComplexCommand", func() {
			It("run complex command with working directory", func() {
				cmd := Command{
					Name:       "ls",
					Args:       []string{"-l"},
					WorkingDir: ".",
				}
				stdout, stderr, status, err := runner.RunComplexCommand(cmd)
				Expect(err).ToNot(HaveOccurred())
				Expect(stdout).To(ContainSubstring("exec_cmd_runner_fixtures"))
				Expect(stdout).To(ContainSubstring("total"))
				Expect(stderr).To(BeEmpty())
				Expect(status).To(Equal(0))
			})

			It("run complex command with env", func() {
				cmd := Command{
					Name: "env",
					Env: map[string]string{
						"FOO": "BAR",
					},
				}
				stdout, stderr, status, err := runner.RunComplexCommand(cmd)
				Expect(err).ToNot(HaveOccurred())
				Expect(stdout).To(ContainSubstring("FOO=BAR"))
				Expect(stdout).To(ContainSubstring("PATH="))
				Expect(stderr).To(BeEmpty())
				Expect(status).To(Equal(0))
			})

			It("runs complex command with specific env", func() {
				cmd := Command{
					Name: "env",
					Env: map[string]string{
						"FOO": "BAR",
					},
					UseIsolatedEnv: true,
				}
				stdout, stderr, status, err := runner.RunComplexCommand(cmd)
				Expect(err).ToNot(HaveOccurred())
				Expect(stdout).To(ContainSubstring("FOO=BAR"))
				Expect(stdout).ToNot(ContainSubstring("PATH="))
				Expect(stderr).To(BeEmpty())
				Expect(status).To(Equal(0))
			})

			It("run complex command with stdin", func() {
				input := "This is STDIN\nWith another line."
				cmd := Command{
					Name:  "cat",
					Args:  []string{"/dev/stdin"},
					Stdin: strings.NewReader(input),
				}
				stdout, stderr, status, err := runner.RunComplexCommand(cmd)
				Expect(err).ToNot(HaveOccurred())
				Expect(stdout).To(Equal(input))
				Expect(stderr).To(BeEmpty())
				Expect(status).To(Equal(0))
			})

			It("prints stdout/stderr to provided I/O object", func() {
				fs := fakesys.NewFakeFileSystem()
				stdoutFile, err := fs.OpenFile("/fake-stdout-path", os.O_RDWR, os.FileMode(0644))
				Expect(err).ToNot(HaveOccurred())

				stderrFile, err := fs.OpenFile("/fake-stderr-path", os.O_RDWR, os.FileMode(0644))
				Expect(err).ToNot(HaveOccurred())

				cmd := Command{
					Name:   "bash",
					Args:   []string{"-c", "echo fake-out >&1; echo fake-err >&2"},
					Stdout: stdoutFile,
					Stderr: stderrFile,
				}

				stdout, stderr, status, err := runner.RunComplexCommand(cmd)
				Expect(err).ToNot(HaveOccurred())

				Expect(stdout).To(BeEmpty())
				Expect(stderr).To(BeEmpty())
				Expect(status).To(Equal(0))

				stdoutContents := make([]byte, 1024)
				_, err = stdoutFile.Read(stdoutContents)
				Expect(err).ToNot(HaveOccurred())
				Expect(string(stdoutContents)).To(ContainSubstring("fake-out"))

				stderrContents := make([]byte, 1024)
				_, err = stderrFile.Read(stderrContents)
				Expect(err).ToNot(HaveOccurred())
				Expect(string(stderrContents)).To(ContainSubstring("fake-err"))
			})
		})

		Describe("RunComplexCommandAsync", func() {
			It("populates stdout and stderr", func() {
				cmd := Command{Name: "ls"}
				process, err := runner.RunComplexCommandAsync(cmd)
				Expect(err).ToNot(HaveOccurred())

				result := <-process.Wait()
				Expect(result.Error).ToNot(HaveOccurred())
				Expect(result.ExitStatus).To(Equal(0))
			})

			It("populates stdout and stderr", func() {
				cmd := Command{Name: "bash", Args: []string{"-c", "echo stdout >&1; echo stderr >&2"}}
				process, err := runner.RunComplexCommandAsync(cmd)
				Expect(err).ToNot(HaveOccurred())

				result := <-process.Wait()
				Expect(result.Error).ToNot(HaveOccurred())
				Expect(result.Stdout).To(Equal("stdout\n"))
				Expect(result.Stderr).To(Equal("stderr\n"))
			})

			It("returns error and sets status to exit status of comamnd if it command exits with non-0 status", func() {
				cmd := Command{Name: "bash", Args: []string{"-c", "exit 10"}}
				process, err := runner.RunComplexCommandAsync(cmd)
				Expect(err).ToNot(HaveOccurred())

				result := <-process.Wait()
				Expect(result.Error).To(HaveOccurred())
				Expect(result.ExitStatus).To(Equal(10))
			})

			It("allows setting custom env variable in addition to inheriting process env variables", func() {
				cmd := Command{
					Name: "env",
					Env: map[string]string{
						"FOO": "BAR",
					},
				}
				process, err := runner.RunComplexCommandAsync(cmd)
				Expect(err).ToNot(HaveOccurred())

				result := <-process.Wait()
				Expect(result.Error).ToNot(HaveOccurred())
				Expect(result.Stdout).To(ContainSubstring("FOO=BAR"))
				Expect(result.Stdout).To(ContainSubstring("PATH="))
			})

			It("changes working dir", func() {
				cmd := Command{Name: "bash", Args: []string{"-c", "echo $PWD"}, WorkingDir: "/tmp"}
				process, err := runner.RunComplexCommandAsync(cmd)
				Expect(err).ToNot(HaveOccurred())

				result := <-process.Wait()
				Expect(result.Error).ToNot(HaveOccurred())
				Expect(result.Stdout).To(ContainSubstring("/tmp"))
			})
		})

		Describe("RunCommand", func() {
			It("run command", func() {
				stdout, stderr, status, err := runner.RunCommand("echo", "Hello World!")
				Expect(err).ToNot(HaveOccurred())
				Expect(stdout).To(Equal("Hello World!\n"))
				Expect(stderr).To(BeEmpty())
				Expect(status).To(Equal(0))
			})

			It("run command with error output", func() {
				stdout, stderr, status, err := runner.RunCommand("bash", "-c", "echo error-output >&2")
				Expect(err).ToNot(HaveOccurred())
				Expect(stdout).To(BeEmpty())
				Expect(stderr).To(ContainSubstring("error-output"))
				Expect(status).To(Equal(0))
			})

			It("run command with non-0 exit status", func() {
				stdout, stderr, status, err := runner.RunCommand("bash", "-c", "exit 14")
				Expect(err).To(HaveOccurred())
				Expect(stdout).To(BeEmpty())
				Expect(stderr).To(BeEmpty())
				Expect(status).To(Equal(14))
			})

			It("run command with error", func() {
				stdout, stderr, status, err := runner.RunCommand("false")
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(Equal("Running command: 'false', stdout: '', stderr: '': exit status 1"))
				Expect(stderr).To(BeEmpty())
				Expect(stdout).To(BeEmpty())
				Expect(status).To(Equal(1))
			})

			It("run command with error with args", func() {
				stdout, stderr, status, err := runner.RunCommand("false", "second arg")
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(Equal("Running command: 'false second arg', stdout: '', stderr: '': exit status 1"))
				Expect(stderr).To(BeEmpty())
				Expect(stdout).To(BeEmpty())
				Expect(status).To(Equal(1))
			})

			It("run command with cmd not found", func() {
				stdout, stderr, status, err := runner.RunCommand("something that does not exist")
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("not found"))
				Expect(stderr).To(BeEmpty())
				Expect(stdout).To(BeEmpty())
				Expect(status).To(Equal(-1))
			})
		})

		Describe("CommandExists", func() {
			It("run command with input", func() {
				stdout, stderr, status, err := runner.RunCommandWithInput("foo\nbar\nbaz", "grep", "ba")
				Expect(err).ToNot(HaveOccurred())
				Expect(stdout).To(Equal("bar\nbaz\n"))
				Expect(stderr).To(BeEmpty())
				Expect(status).To(Equal(0))
			})
		})

		Describe("CommandExists", func() {
			It("command exists", func() {
				Expect(runner.CommandExists("env")).To(BeTrue())
				Expect(runner.CommandExists("absolutely-does-not-exist-ever-please-unicorns")).To(BeFalse())
			})
		})
	})

	Describe("execProcess", func() {
		var (
			runner CmdRunner
		)

		BeforeEach(func() {
			runner = NewExecCmdRunner(boshlog.NewLogger(boshlog.LevelNone))
		})

		Describe("TerminateNicely", func() {
			var (
				buildDir string
			)

			hasProcessesFromBuildDir := func() (bool, string) {
				// Make sure to show all processes on the system
				output, err := exec.Command("ps", "-A", "-o", "pid,args").Output()
				Expect(err).ToNot(HaveOccurred())

				// Cannot check for PID existence directly because
				// PID could have been recycled by the OS; make sure it's not the same process
				for _, line := range strings.Split(strings.TrimSpace(string(output)), "\n") {
					if strings.Contains(line, buildDir) {
						return true, line
					}
				}

				return false, ""
			}

			expectProcessesToNotExist := func() {
				exists, ps := hasProcessesFromBuildDir()
				Expect(exists).To(BeFalse(), "Expected following process to not exist %s", ps)
			}

			BeforeEach(func() {
				var (
					err error
				)

				buildDir, err = ioutil.TempDir("", "TerminateNicely")
				Expect(err).ToNot(HaveOccurred())

				exesToCompile := []string{
					"exe_exits",
					"child_ignore_term",
					"child_term",
					"parent_ignore_term",
					"parent_term",
				}

				for _, exe := range exesToCompile {
					dst := filepath.Join(buildDir, exe)
					src := filepath.Join("exec_cmd_runner_fixtures", exe+".go")
					err := exec.Command("go", "build", "-o", dst, src).Run()
					Expect(err).ToNot(HaveOccurred())
				}
			})

			AfterEach(func() {
				os.RemoveAll(buildDir)
			})

			Context("when parent and child terminate after receiving SIGTERM", func() {
				It("sends term signal to the whole group and returns with exit status that parent exited", func() {
					cmd := Command{Name: filepath.Join(buildDir, "parent_term")}
					process, err := runner.RunComplexCommandAsync(cmd)
					Expect(err).ToNot(HaveOccurred())

					// Wait for script to start and output pids
					time.Sleep(2 * time.Second)

					waitCh := process.Wait()

					err = process.TerminateNicely(1 * time.Minute)
					Expect(err).ToNot(HaveOccurred())

					result := <-waitCh
					Expect(result.Error).To(HaveOccurred())

					// Parent exit code is returned
					// bash adds 128 to signal status as exit code
					Expect(result.ExitStatus).To(Equal(13))

					// Term signal was sent to all processes in the group
					Expect(result.Stdout).To(ContainSubstring("Parent received SIGTERM"))
					Expect(result.Stdout).To(ContainSubstring("Child received SIGTERM"))

					// All processes are gone
					expectProcessesToNotExist()
				})
			})

			Context("when parent and child do not exit after receiving SIGTERM in small amount of time", func() {
				It("sends kill signal to the whole group and returns with ? exit status", func() {
					cmd := Command{Name: filepath.Join(buildDir, "parent_ignore_term")}
					process, err := runner.RunComplexCommandAsync(cmd)
					Expect(err).ToNot(HaveOccurred())

					// Wait for script to start and output pids
					time.Sleep(2 * time.Second)

					waitCh := process.Wait()

					err = process.TerminateNicely(2 * time.Second)
					Expect(err).ToNot(HaveOccurred())

					result := <-waitCh
					Expect(result.Error).To(HaveOccurred())

					// Parent exit code is returned
					Expect(result.ExitStatus).To(Equal(128 + 9))

					// Term signal was sent to all processes in the group before kill
					Expect(result.Stdout).To(ContainSubstring("Parent received SIGTERM"))
					Expect(result.Stdout).To(ContainSubstring("Child received SIGTERM"))

					// Parent and child are killed
					expectProcessesToNotExist()
				})
			})

			Context("when parent and child already exited before calling TerminateNicely", func() {
				It("returns without an error since all processes are gone", func() {
					cmd := Command{Name: filepath.Join(buildDir, "exe_exits")}
					process, err := runner.RunComplexCommandAsync(cmd)
					Expect(err).ToNot(HaveOccurred())

					// Wait for script to exit
					for i := 0; i < 20; i++ {
						if exists, _ := hasProcessesFromBuildDir(); !exists {
							break
						}
						if i == 19 {
							Fail("Expected process did not exit fast enough")
						}
						time.Sleep(500 * time.Millisecond)
					}

					waitCh := process.Wait()

					err = process.TerminateNicely(2 * time.Second)
					Expect(err).ToNot(HaveOccurred())

					result := <-waitCh
					Expect(result.Error).ToNot(HaveOccurred())
					Expect(result.Stdout).To(Equal(""))
					Expect(result.Stderr).To(Equal(""))
					Expect(result.ExitStatus).To(Equal(0))
				})
			})
		})
	})

	Describe("ExecError", func() {
		Describe("Error", func() {
			It("returns error message with full stdout and full stderr to aid debugging", func() {
				execErr := NewExecError("fake-cmd", "fake-stdout", "fake-stderr")
				expectedMsg := "Running command: 'fake-cmd', stdout: 'fake-stdout', stderr: 'fake-stderr'"
				Expect(execErr.Error()).To(Equal(expectedMsg))
			})
		})

		Describe("ShortError", func() {
			buildLines := func(start, stop int, suffix string) string {
				var result []string
				for i := start; i <= stop; i++ {
					result = append(result, fmt.Sprintf("%d %s", i, suffix))
				}
				return strings.Join(result, "\n")
			}

			Context("when stdout and stderr contains more than 100 lines", func() {
				It("returns error message with truncated stdout and stderr to 100 lines", func() {
					fullStdout101 := buildLines(1, 101, "stdout")
					truncatedStdout100 := buildLines(2, 101, "stdout")

					fullStderr101 := buildLines(1, 101, "stderr")
					truncatedStderr100 := buildLines(2, 101, "stderr")

					execErr := NewExecError("fake-cmd", fullStdout101, fullStderr101)

					expectedMsg := fmt.Sprintf(
						"Running command: 'fake-cmd', stdout: '%s', stderr: '%s'",
						truncatedStdout100, truncatedStderr100,
					)

					Expect(execErr.ShortError()).To(Equal(expectedMsg))
				})
			})

			Context("when stdout and stderr contains exactly 100 lines", func() {
				It("returns error message with full lines", func() {
					stdout100 := buildLines(1, 100, "stdout")
					stderr100 := buildLines(1, 100, "stderr")
					execErr := NewExecError("fake-cmd", stdout100, stderr100)
					expectedMsg := fmt.Sprintf("Running command: 'fake-cmd', stdout: '%s', stderr: '%s'", stdout100, stderr100)
					Expect(execErr.ShortError()).To(Equal(expectedMsg))
				})
			})

			Context("when stdout and stderr contains less than 100 lines", func() {
				It("returns error message with full lines", func() {
					stdout99 := buildLines(1, 99, "stdout")
					stderr99 := buildLines(1, 99, "stderr")
					execErr := NewExecError("fake-cmd", stdout99, stderr99)
					expectedMsg := fmt.Sprintf("Running command: 'fake-cmd', stdout: '%s', stderr: '%s'", stdout99, stderr99)
					Expect(execErr.ShortError()).To(Equal(expectedMsg))
				})
			})
		})
	})
}
	return
}

var _ = Describe("SignalableLogger", func() {
	var (
		signalChannel chan os.Signal
	)
	BeforeEach(func() {
		signalChannel = make(chan os.Signal, 1)
	})

	Describe("Toggling forced debug", func() {
		Describe("when the log level is error", func() {
			It("outputs at debug level", func() {
				stdout, stderr := captureOutputs(func() {
					logger, doneChannel := bilog.NewSignalableLogger(boshlog.NewLogger(boshlog.LevelError), signalChannel)

					signalChannel <- syscall.SIGHUP
					<-doneChannel

					logger.Debug("TOGGLED_DEBUG", "some debug log")
					logger.Info("TOGGLED_INFO", "some info log")
					logger.Warn("TOGGLED_WARN", "some warn log")
					logger.Error("TOGGLED_ERROR", "some error log")
				})

				Expect(stdout).To(ContainSubstring("TOGGLED_DEBUG"))
				Expect(stdout).To(ContainSubstring("TOGGLED_INFO"))
				Expect(stderr).To(ContainSubstring("TOGGLED_WARN"))
				Expect(stderr).To(ContainSubstring("TOGGLED_ERROR"))
			})
Example #9
0
func init() {
	Describe("Testing with Ginkgo", func() {
		It("home dir", func() {
			osFs, _ := createOsFs()

			homeDir, err := osFs.HomeDir("root")
			Expect(err).ToNot(HaveOccurred())
			Expect(homeDir).To(ContainSubstring("/root"))
		})

		It("expand path", func() {
			osFs, _ := createOsFs()

			expandedPath, err := osFs.ExpandPath("~/fake-dir/fake-file.txt")
			Expect(err).ToNot(HaveOccurred())

			currentUser, err := osuser.Current()
			Expect(err).ToNot(HaveOccurred())
			Expect(expandedPath).To(Equal(currentUser.HomeDir + "/fake-dir/fake-file.txt"))

			expandedPath, err = osFs.ExpandPath("/fake-dir//fake-file.txt")
			Expect(err).ToNot(HaveOccurred())
			Expect(expandedPath).To(Equal("/fake-dir/fake-file.txt"))

			expandedPath, err = osFs.ExpandPath("./fake-file.txt")
			Expect(err).ToNot(HaveOccurred())
			currentDir, err := os.Getwd()
			Expect(err).ToNot(HaveOccurred())
			Expect(expandedPath).To(Equal(currentDir + "/fake-file.txt"))
		})

		It("mkdir all", func() {
			osFs, _ := createOsFs()
			tmpPath := os.TempDir()
			testPath := filepath.Join(tmpPath, "MkdirAllTestDir", "bar", "baz")
			defer os.RemoveAll(filepath.Join(tmpPath, "MkdirAllTestDir"))

			_, err := os.Stat(testPath)
			Expect(err).To(HaveOccurred())
			Expect(os.IsNotExist(err)).To(BeTrue())

			fileMode := os.FileMode(0700)

			err = osFs.MkdirAll(testPath, fileMode)
			Expect(err).ToNot(HaveOccurred())

			stat, err := os.Stat(testPath)
			Expect(err).ToNot(HaveOccurred())
			Expect(stat.IsDir()).To(BeTrue())
			Expect(stat.Mode().Perm()).To(Equal(fileMode))

			err = osFs.MkdirAll(testPath, fileMode)
			Expect(err).ToNot(HaveOccurred())
		})

		It("chown", func() {
			osFs, _ := createOsFs()
			testPath := filepath.Join(os.TempDir(), "ChownTestDir")

			err := os.Mkdir(testPath, os.FileMode(0700))
			Expect(err).ToNot(HaveOccurred())
			defer os.RemoveAll(testPath)

			err = osFs.Chown(testPath, "root")
			Expect(err).To(HaveOccurred())
			Expect(err.Error()).To(ContainSubstring("not permitted"))
		})

		It("chmod", func() {
			osFs, _ := createOsFs()
			testPath := filepath.Join(os.TempDir(), "ChmodTestDir")

			_, err := os.Create(testPath)
			Expect(err).ToNot(HaveOccurred())
			defer os.Remove(testPath)

			os.Chmod(testPath, os.FileMode(0666))

			err = osFs.Chmod(testPath, os.FileMode(0644))
			Expect(err).ToNot(HaveOccurred())

			fileStat, err := os.Stat(testPath)
			Expect(err).ToNot(HaveOccurred())
			Expect(fileStat.Mode()).To(Equal(os.FileMode(0644)))
		})

		It("opens file", func() {
			osFs, _ := createOsFs()
			testPath := filepath.Join(os.TempDir(), "OpenFileTestFile")

			file, err := osFs.OpenFile(testPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.FileMode(0644))
			defer os.Remove(testPath)

			Expect(err).ToNot(HaveOccurred())

			file.Write([]byte("testing new file"))
			file.Close()

			createdFile, err := os.Open(testPath)
			Expect(err).ToNot(HaveOccurred())
			defer createdFile.Close()
			Expect(readFile(createdFile)).To(Equal("testing new file"))
		})

		Context("the file already exists and is not write only", func() {
			It("writes to file", func() {
				osFs, _ := createOsFs()
				testPath := filepath.Join(os.TempDir(), "subDir", "ConvergeFileContentsTestFile")

				_, err := os.Stat(testPath)
				Expect(err).To(HaveOccurred())

				written, err := osFs.ConvergeFileContents(testPath, []byte("initial write"))
				Expect(err).ToNot(HaveOccurred())
				Expect(written).To(BeTrue())
				defer os.Remove(testPath)

				file, err := os.Open(testPath)
				Expect(err).ToNot(HaveOccurred())
				defer file.Close()

				Expect(readFile(file)).To(Equal("initial write"))

				written, err = osFs.ConvergeFileContents(testPath, []byte("second write"))
				Expect(err).ToNot(HaveOccurred())
				Expect(written).To(BeTrue())

				file.Close()
				file, err = os.Open(testPath)
				Expect(err).ToNot(HaveOccurred())

				Expect(readFile(file)).To(Equal("second write"))

				file.Close()
				file, err = os.Open(testPath)

				written, err = osFs.ConvergeFileContents(testPath, []byte("second write"))
				Expect(err).ToNot(HaveOccurred())
				Expect(written).To(BeFalse())
				Expect(readFile(file)).To(Equal("second write"))
			})
		})

		Context("the file already exists and is write only", func() {
			It("writes to file", func() {
				osFs, _ := createOsFs()
				testPath := filepath.Join(os.TempDir(), "subDir", "ConvergeFileContentsTestFile")

				_, err := os.OpenFile(testPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(0200))
				Expect(err).ToNot(HaveOccurred())
				defer os.Remove(testPath)

				err = osFs.WriteFile(testPath, []byte("test"))
				Expect(err).ToNot(HaveOccurred())
			})
		})

		Context("the file parent fir does not exist", func() {
			BeforeEach(func() {
				err := os.RemoveAll(filepath.Join(os.TempDir(), "subDirNew"))
				Expect(err).ToNot(HaveOccurred())
			})

			AfterEach(func() {
				err := os.RemoveAll(filepath.Join(os.TempDir(), "subDirNew"))
				Expect(err).ToNot(HaveOccurred())
			})

			It("writes to file", func() {
				osFs, _ := createOsFs()

				testPath := filepath.Join(os.TempDir(), "subDirNew", "ConvergeFileContentsTestFile")

				err := osFs.WriteFile(testPath, []byte("test"))
				Expect(err).ToNot(HaveOccurred())
			})
		})

		It("read file", func() {
			osFs, _ := createOsFs()
			testPath := filepath.Join(os.TempDir(), "ReadFileTestFile")

			osFs.WriteFileString(testPath, "some contents")
			defer os.Remove(testPath)

			content, err := osFs.ReadFile(testPath)
			Expect(err).ToNot(HaveOccurred())
			Expect("some contents").To(Equal(string(content)))
		})

		It("file exists", func() {
			osFs, _ := createOsFs()
			testPath := filepath.Join(os.TempDir(), "FileExistsTestFile")

			Expect(osFs.FileExists(testPath)).To(BeFalse())

			osFs.WriteFileString(testPath, "initial write")
			defer os.Remove(testPath)

			Expect(osFs.FileExists(testPath)).To(BeTrue())
		})

		It("rename", func() {
			osFs, _ := createOsFs()
			tempDir := os.TempDir()
			oldPath := filepath.Join(tempDir, "old")
			oldFilePath := filepath.Join(oldPath, "test.txt")
			newPath := filepath.Join(tempDir, "new")

			os.Mkdir(oldPath, os.ModePerm)
			_, err := os.Create(oldFilePath)
			Expect(err).ToNot(HaveOccurred())

			err = osFs.Rename(oldPath, newPath)
			Expect(err).ToNot(HaveOccurred())

			Expect(osFs.FileExists(newPath)).To(BeTrue())

			newFilePath := filepath.Join(newPath, "test.txt")
			Expect(osFs.FileExists(newFilePath)).To(BeTrue())
		})

		It("symlink", func() {
			osFs, _ := createOsFs()
			filePath := filepath.Join(os.TempDir(), "SymlinkTestFile")
			containingDir := filepath.Join(os.TempDir(), "SubDir")
			os.Remove(containingDir)
			symlinkPath := filepath.Join(containingDir, "SymlinkTestSymlink")

			osFs.WriteFileString(filePath, "some content")
			defer os.Remove(filePath)

			osFs.Symlink(filePath, symlinkPath)
			defer os.Remove(containingDir)

			symlinkStats, err := os.Lstat(symlinkPath)
			Expect(err).ToNot(HaveOccurred())
			Expect(os.ModeSymlink).To(Equal(os.ModeSymlink & symlinkStats.Mode()))

			symlinkFile, err := os.Open(symlinkPath)
			Expect(err).ToNot(HaveOccurred())
			Expect("some content").To(Equal(readFile(symlinkFile)))
		})

		It("symlink when link already exists and links to the intended path", func() {
			osFs, _ := createOsFs()
			filePath := filepath.Join(os.TempDir(), "SymlinkTestIdempotent1File")
			symlinkPath := filepath.Join(os.TempDir(), "SymlinkTestIdempotent1Symlink")

			osFs.WriteFileString(filePath, "some content")
			defer os.Remove(filePath)

			osFs.Symlink(filePath, symlinkPath)
			defer os.Remove(symlinkPath)

			firstSymlinkStats, err := os.Lstat(symlinkPath)
			Expect(err).ToNot(HaveOccurred())

			err = osFs.Symlink(filePath, symlinkPath)
			Expect(err).ToNot(HaveOccurred())

			secondSymlinkStats, err := os.Lstat(symlinkPath)
			Expect(err).ToNot(HaveOccurred())
			Expect(firstSymlinkStats.ModTime()).To(Equal(secondSymlinkStats.ModTime()))
		})

		It("symlink when link already exists and does not link to the intended path", func() {
			osFs, _ := createOsFs()
			filePath := filepath.Join(os.TempDir(), "SymlinkTestIdempotent1File")
			otherFilePath := filepath.Join(os.TempDir(), "SymlinkTestIdempotent1OtherFile")
			symlinkPath := filepath.Join(os.TempDir(), "SymlinkTestIdempotent1Symlink")

			osFs.WriteFileString(filePath, "some content")
			defer os.Remove(filePath)

			osFs.WriteFileString(otherFilePath, "other content")
			defer os.Remove(otherFilePath)

			err := osFs.Symlink(otherFilePath, symlinkPath)
			Expect(err).ToNot(HaveOccurred())

			err = osFs.Symlink(filePath, symlinkPath)
			Expect(err).ToNot(HaveOccurred())

			defer os.Remove(symlinkPath)

			symlinkStats, err := os.Lstat(symlinkPath)
			Expect(err).ToNot(HaveOccurred())
			Expect(os.ModeSymlink).To(Equal(os.ModeSymlink & symlinkStats.Mode()))

			symlinkFile, err := os.Open(symlinkPath)
			Expect(err).ToNot(HaveOccurred())
			Expect("some content").To(Equal(readFile(symlinkFile)))
		})

		It("symlink when a file exists at intended path", func() {
			osFs, _ := createOsFs()
			filePath := filepath.Join(os.TempDir(), "SymlinkTestIdempotent1File")
			symlinkPath := filepath.Join(os.TempDir(), "SymlinkTestIdempotent1Symlink")

			osFs.WriteFileString(filePath, "some content")
			defer os.Remove(filePath)

			osFs.WriteFileString(symlinkPath, "some other content")
			defer os.Remove(symlinkPath)

			err := osFs.Symlink(filePath, symlinkPath)
			Expect(err).ToNot(HaveOccurred())

			defer os.Remove(symlinkPath)

			symlinkStats, err := os.Lstat(symlinkPath)
			Expect(err).ToNot(HaveOccurred())
			Expect(os.ModeSymlink).To(Equal(os.ModeSymlink & symlinkStats.Mode()))

			symlinkFile, err := os.Open(symlinkPath)
			Expect(err).ToNot(HaveOccurred())
			Expect("some content").To(Equal(readFile(symlinkFile)))
		})

		It("read link", func() {
			osFs, _ := createOsFs()
			filePath := filepath.Join(os.TempDir(), "SymlinkTestFile")
			containingDir := filepath.Join(os.TempDir(), "SubDir")
			os.Remove(containingDir)
			symlinkPath := filepath.Join(containingDir, "SymlinkTestSymlink")

			osFs.WriteFileString(filePath, "some content")
			defer os.Remove(filePath)

			err := osFs.Symlink(filePath, symlinkPath)
			Expect(err).ToNot(HaveOccurred())
			defer os.Remove(containingDir)

			actualFilePath, err := osFs.ReadLink(symlinkPath)
			Expect(err).ToNot(HaveOccurred())

			// on Mac OS /var -> private/var
			absPath, err := filepath.EvalSymlinks(filePath)
			Expect(err).ToNot(HaveOccurred())
			Expect(actualFilePath).To(Equal(absPath))
		})

		It("temp file", func() {
			osFs, _ := createOsFs()

			file1, err := osFs.TempFile("fake-prefix")
			Expect(err).ToNot(HaveOccurred())
			assert.NotEmpty(GinkgoT(), file1)

			defer os.Remove(file1.Name())

			file2, err := osFs.TempFile("fake-prefix")
			Expect(err).ToNot(HaveOccurred())
			assert.NotEmpty(GinkgoT(), file2)

			defer os.Remove(file2.Name())

			assert.NotEqual(GinkgoT(), file1.Name(), file2.Name())
		})

		It("temp dir", func() {
			osFs, _ := createOsFs()

			path1, err := osFs.TempDir("fake-prefix")
			Expect(err).ToNot(HaveOccurred())
			assert.NotEmpty(GinkgoT(), path1)

			defer os.Remove(path1)

			path2, err := osFs.TempDir("fake-prefix")
			Expect(err).ToNot(HaveOccurred())
			assert.NotEmpty(GinkgoT(), path2)

			defer os.Remove(path2)

			assert.NotEqual(GinkgoT(), path1, path2)
		})

		Describe("Temporary directories and files", func() {
			var (
				osFs        FileSystem
				testTempDir string
			)
			BeforeEach(func() {
				osFs, _ = createOsFs()
				var err error
				testTempDir, err = ioutil.TempDir("", "os_filesystem_test")
				Expect(err).ToNot(HaveOccurred())
			})

			AfterEach(func() {
				os.Remove(testTempDir)
			})

			Context("a temp root is set", func() {
				BeforeEach(func() {
					osFs.ChangeTempRoot(testTempDir)
				})

				It("creates temp files under that root", func() {
					file, err := osFs.TempFile("some-file-prefix")
					Expect(err).ToNot(HaveOccurred())
					Expect(file.Name()).To(HavePrefix(filepath.Join(testTempDir, "some-file-prefix")))
				})

				It("creates temp directories under that root", func() {
					dirName, err := osFs.TempDir("some-dir-prefix")
					Expect(err).ToNot(HaveOccurred())
					Expect(dirName).To(HavePrefix(filepath.Join(testTempDir, "some-dir-prefix")))
				})
			})

			Context("no temp root is set and was initialized as a strict temp root", func() {
				BeforeEach(func() {
					osFs = NewOsFileSystemWithStrictTempRoot(boshlog.NewLogger(boshlog.LevelNone))
				})

				It("should eror", func() {
					_, err := osFs.TempFile("some-prefix")
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("ChangeTempRoot"))

					_, err = osFs.TempDir("some-prefix")
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("ChangeTempRoot"))
				})
			})
		})

		Describe("CopyFile", func() {
			It("copies file", func() {
				osFs, _ := createOsFs()
				srcPath := "test_assets/test_copy_dir_entries/foo.txt"
				dstFile, err := osFs.TempFile("CopyFileTestFile")
				Expect(err).ToNot(HaveOccurred())
				defer os.Remove(dstFile.Name())

				err = osFs.CopyFile(srcPath, dstFile.Name())

				fooContent, err := osFs.ReadFileString(dstFile.Name())
				Expect(err).ToNot(HaveOccurred())
				Expect(fooContent).To(Equal("foo\n"))
			})

			It("does not leak file descriptors", func() {
				osFs, _ := createOsFs()

				srcFile, err := osFs.TempFile("srcPath")
				Expect(err).ToNot(HaveOccurred())

				err = srcFile.Close()
				Expect(err).ToNot(HaveOccurred())

				dstFile, err := osFs.TempFile("dstPath")
				Expect(err).ToNot(HaveOccurred())

				err = dstFile.Close()
				Expect(err).ToNot(HaveOccurred())

				err = osFs.CopyFile(srcFile.Name(), dstFile.Name())
				Expect(err).ToNot(HaveOccurred())

				runner := NewExecCmdRunner(boshlog.NewLogger(boshlog.LevelNone))
				stdout, _, _, err := runner.RunCommand("lsof")
				Expect(err).ToNot(HaveOccurred())

				for _, line := range strings.Split(stdout, "\n") {
					if strings.Contains(line, srcFile.Name()) {
						Fail(fmt.Sprintf("CopyFile did not close: srcFile: %s", srcFile.Name()))
					}
					if strings.Contains(line, dstFile.Name()) {
						Fail(fmt.Sprintf("CopyFile did not close: dstFile: %s", dstFile.Name()))
					}
				}

				os.Remove(srcFile.Name())
				os.Remove(dstFile.Name())
			})
		})

		Describe("CopyDir", func() {
			var fixtureFiles = []string{
				"foo.txt",
				"bar/bar.txt",
				"bar/baz/.gitkeep",
			}

			It("recursively copies directory contents", func() {
				osFs, _ := createOsFs()
				srcPath := "test_assets/test_copy_dir_entries"
				dstPath, err := osFs.TempDir("CopyDirTestDir")
				Expect(err).ToNot(HaveOccurred())
				defer osFs.RemoveAll(dstPath)

				err = osFs.CopyDir(srcPath, dstPath)
				Expect(err).ToNot(HaveOccurred())

				for _, fixtureFile := range fixtureFiles {
					srcContents, err := osFs.ReadFile(filepath.Join(srcPath, fixtureFile))
					Expect(err).ToNot(HaveOccurred())

					dstContents, err := osFs.ReadFile(filepath.Join(dstPath, fixtureFile))
					Expect(err).ToNot(HaveOccurred())

					Expect(srcContents).To(Equal(dstContents), "Copied file does not match source file: '%s", fixtureFile)
				}
			})

			It("does not leak file descriptors", func() {
				osFs, _ := createOsFs()
				srcPath := "test_assets/test_copy_dir_entries"
				dstPath, err := osFs.TempDir("CopyDirTestDir")
				Expect(err).ToNot(HaveOccurred())
				defer osFs.RemoveAll(dstPath)

				err = osFs.CopyDir(srcPath, dstPath)
				Expect(err).ToNot(HaveOccurred())

				runner := NewExecCmdRunner(boshlog.NewLogger(boshlog.LevelNone))
				stdout, _, _, err := runner.RunCommand("lsof")
				Expect(err).ToNot(HaveOccurred())

				// lsof uses absolute paths
				srcPath, err = filepath.Abs(srcPath)
				Expect(err).ToNot(HaveOccurred())

				for _, line := range strings.Split(stdout, "\n") {
					for _, fixtureFile := range fixtureFiles {
						srcFilePath := filepath.Join(srcPath, fixtureFile)
						if strings.Contains(line, srcFilePath) {
							Fail(fmt.Sprintf("CopyDir did not close source file: %s", srcFilePath))
						}

						srcFileDirPath := filepath.Dir(srcFilePath)
						if strings.Contains(line, srcFileDirPath) {
							Fail(fmt.Sprintf("CopyDir did not close source dir: %s", srcFileDirPath))
						}

						dstFilePath := filepath.Join(dstPath, fixtureFile)
						if strings.Contains(line, dstFilePath) {
							Fail(fmt.Sprintf("CopyDir did not close destination file: %s", dstFilePath))
						}

						dstFileDirPath := filepath.Dir(dstFilePath)
						if strings.Contains(line, dstFileDirPath) {
							Fail(fmt.Sprintf("CopyDir did not close destination dir: %s", dstFileDirPath))
						}
					}
				}
			})
		})

		It("remove all", func() {
			osFs, _ := createOsFs()
			dstFile, err := osFs.TempFile("CopyFileTestFile")
			Expect(err).ToNot(HaveOccurred())
			defer os.Remove(dstFile.Name())

			err = osFs.RemoveAll(dstFile.Name())
			Expect(err).ToNot(HaveOccurred())

			_, err = os.Stat(dstFile.Name())
			Expect(os.IsNotExist(err)).To(BeTrue())
		})
	})
}
Example #10
0
	"github.com/cloudfoundry/bosh-init/internal/github.com/cloudfoundry/bosh-utils/logger"
	"github.com/cloudfoundry/bosh-init/internal/github.com/cloudfoundry/bosh-utils/system"
	fakesys "github.com/cloudfoundry/bosh-init/internal/github.com/cloudfoundry/bosh-utils/system/fakes"
)

const cert1 string = `-----BEGIN CERTIFICATE-----
MIIEJDCCAwygAwIBAgIJAO+CqgiJnCgpMA0GCSqGSIb3DQEBBQUAMGkxCzAJBgNV
BAYTAkNBMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
qokoSBXzJCJTt2P681gyqBDr/hUYzqpoXUsOTRisScbEbaSv8hTiTeFJUMyNQAqn
DtmvI8bXKxU=
-----END CERTIFICATE-----`

var _ = Describe("Certificate Management", func() {
	var log logger.Logger
	BeforeEach(func() {
		log = logger.NewLogger(logger.LevelNone)
	})

	Describe("CertificateSplitting", func() {
		It("splits 2 back-to-back certificates", func() {
			certs := fmt.Sprintf("%s\n%s\n", cert1, cert1)

			result := cert.SplitCerts(certs)
			Expect(result[0]).To(Equal(cert1))
			Expect(result[1]).To(Equal(cert1))
			Expect(len(result)).To(Equal(2))
		})

		It("splits 2 back-to-back certificates without trailing newline", func() {
			certs := fmt.Sprintf("%s\n%s", cert1, cert1)
Example #11
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).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 describeConfigDriveMetadataService() {
	var (
		metadataService MetadataService
		resolver        *fakeinf.FakeDNSResolver
		platform        *fakeplatform.FakePlatform
		logger          boshlog.Logger
	)

	updateMetadata := func(metadataContents MetadataContentsType) {
		metadataJSON, err := json.Marshal(metadataContents)
		Expect(err).ToNot(HaveOccurred())
		platform.SetGetFilesContentsFromDisk("/fake-disk-path-1/fake-metadata-path", metadataJSON, nil)

		Expect(metadataService.IsAvailable()).To(BeTrue())
	}

	updateUserdata := func(userdataContents string) {
		platform.SetGetFilesContentsFromDisk("/fake-disk-path-1/fake-userdata-path", []byte(userdataContents), nil)

		Expect(metadataService.IsAvailable()).To(BeTrue())
	}

	BeforeEach(func() {
		resolver = &fakeinf.FakeDNSResolver{}
		platform = fakeplatform.NewFakePlatform()
		logger = boshlog.NewLogger(boshlog.LevelNone)
		diskPaths := []string{
			"/fake-disk-path-1",
			"/fake-disk-path-2",
		}
		metadataService = NewConfigDriveMetadataService(
			resolver,
			platform,
			diskPaths,
			"fake-metadata-path",
			"fake-userdata-path",
			logger,
		)

		userdataContents := fmt.Sprintf(`{"server":{"name":"fake-server-name"},"registry":{"endpoint":"fake-registry-endpoint"}}`)
		platform.SetGetFilesContentsFromDisk("/fake-disk-path-1/fake-userdata-path", []byte(userdataContents), nil)

		metadata := MetadataContentsType{
			PublicKeys: map[string]PublicKeyType{
				"0": PublicKeyType{
					"openssh-key": "fake-openssh-key",
				},
			},
			InstanceID: "fake-instance-id",
		}
		updateMetadata(metadata)
	})

	Describe("GetNetworks", func() {
		It("returns the network settings", func() {
			userdataContents := `
				{
					"networks": {
						"network_1": {"type": "manual", "ip": "1.2.3.4", "netmask": "2.3.4.5", "gateway": "3.4.5.6", "default": ["dns"], "dns": ["8.8.8.8"], "mac": "fake-mac-address-1"},
						"network_2": {"type": "dynamic", "default": ["dns"], "dns": ["8.8.8.8"], "mac": "fake-mac-address-2"}
					}
				}`
			updateUserdata(userdataContents)

			networks, err := metadataService.GetNetworks()
			Expect(err).ToNot(HaveOccurred())
			Expect(networks).To(Equal(boshsettings.Networks{
				"network_1": boshsettings.Network{
					Type:    "manual",
					IP:      "1.2.3.4",
					Netmask: "2.3.4.5",
					Gateway: "3.4.5.6",
					Default: []string{"dns"},
					DNS:     []string{"8.8.8.8"},
					Mac:     "fake-mac-address-1",
				},
				"network_2": boshsettings.Network{
					Type:    "dynamic",
					Default: []string{"dns"},
					DNS:     []string{"8.8.8.8"},
					Mac:     "fake-mac-address-2",
				},
			}))
		})

		It("returns a nil Networks if the settings are missing (from an old CPI version)", func() {
			userdataContents := `{}`
			updateUserdata(userdataContents)

			networks, err := metadataService.GetNetworks()
			Expect(err).ToNot(HaveOccurred())
			Expect(networks).To(BeNil())
		})
	})

	Describe("IsAvailable", func() {
		It("return true when it can load successfully", func() {
			Expect(metadataService.IsAvailable()).To(BeTrue())
		})

		It("returns an error if it fails to read meta-data.json from disk", func() {
			platform.SetGetFilesContentsFromDisk("/fake-disk-path-1/fake-metadata-path", []byte{}, errors.New("fake-read-disk-error"))
			Expect(metadataService.IsAvailable()).To(BeFalse())
		})

		It("tries to load meta-data.json from potential disk locations", func() {
			platform.SetGetFilesContentsFromDisk("/fake-disk-path-1/fake-metadata-path", []byte{}, errors.New("fake-read-disk-error"))
			Expect(metadataService.IsAvailable()).To(BeFalse())

			Expect(platform.GetFileContentsFromDiskDiskPaths).To(ContainElement("/fake-disk-path-1"))
			Expect(platform.GetFileContentsFromDiskDiskPaths).To(ContainElement("/fake-disk-path-2"))
		})

		It("returns an error if it fails to parse meta-data.json contents", func() {
			platform.SetGetFilesContentsFromDisk("/fake-disk-path-1/fake-metadata-path", []byte("broken"), nil)
			Expect(metadataService.IsAvailable()).To(BeFalse())
		})

		It("returns an error if it fails to read user_data from disk", func() {
			platform.SetGetFilesContentsFromDisk("/fake-disk-path-1/fake-userdata-path", []byte{}, errors.New("fake-read-disk-error"))
			Expect(metadataService.IsAvailable()).To(BeFalse())
		})

		It("returns an error if it fails to parse user_data contents", func() {
			platform.SetGetFilesContentsFromDisk("/fake-disk-path-1/fake-userdata-path", []byte("broken"), nil)
			Expect(metadataService.IsAvailable()).To(BeFalse())
		})

		Context("when disk paths are not given", func() {
			It("returns false", func() {
				metadataService = NewConfigDriveMetadataService(
					resolver,
					platform,
					[]string{},
					"fake-metadata-path",
					"fake-userdata-path",
					logger,
				)
				Expect(metadataService.IsAvailable()).To(BeFalse())
			})
		})
	})

	Describe("GetPublicKey", func() {
		It("returns public key", func() {
			value, err := metadataService.GetPublicKey()
			Expect(err).ToNot(HaveOccurred())
			Expect(value).To(Equal("fake-openssh-key"))
		})

		It("returns an error if it fails to get ssh key", func() {
			updateMetadata(MetadataContentsType{})

			value, err := metadataService.GetPublicKey()
			Expect(err).To(HaveOccurred())
			Expect(err.Error()).To(ContainSubstring("Failed to load openssh-key from config drive metadata service"))

			Expect(value).To(Equal(""))
		})
	})

	Describe("GetInstanceID", func() {
		It("returns instance id", func() {
			value, err := metadataService.GetInstanceID()
			Expect(err).ToNot(HaveOccurred())
			Expect(value).To(Equal("fake-instance-id"))
		})

		It("returns an error if it fails to get instance id", func() {
			updateMetadata(MetadataContentsType{})

			value, err := metadataService.GetInstanceID()
			Expect(err).To(HaveOccurred())
			Expect(err.Error()).To(ContainSubstring("Failed to load instance-id from config drive metadata service"))

			Expect(value).To(Equal(""))
		})
	})

	Describe("GetServerName", func() {
		It("returns server name", func() {
			value, err := metadataService.GetServerName()
			Expect(err).ToNot(HaveOccurred())
			Expect(value).To(Equal("fake-server-name"))
		})

		It("returns an error if it fails to get server name", func() {
			updateUserdata("{}")

			value, err := metadataService.GetServerName()
			Expect(err).To(HaveOccurred())
			Expect(err.Error()).To(ContainSubstring("Failed to load server name from config drive metadata service"))

			Expect(value).To(Equal(""))
		})
	})

	Describe("GetRegistryEndpoint", func() {
		It("returns an error if it fails to get registry endpoint", func() {
			updateUserdata("{}")

			value, err := metadataService.GetRegistryEndpoint()
			Expect(err).To(HaveOccurred())
			Expect(err.Error()).To(ContainSubstring("Failed to load registry endpoint from config drive metadata service"))

			Expect(value).To(Equal(""))
		})

		Context("when user_data does not contain a dns server", func() {
			It("returns registry endpoint", func() {
				value, err := metadataService.GetRegistryEndpoint()
				Expect(err).ToNot(HaveOccurred())
				Expect(value).To(Equal("fake-registry-endpoint"))
			})
		})

		Context("when user_data contains a dns server", func() {
			BeforeEach(func() {
				userdataContents := fmt.Sprintf(
					`{"server":{"name":"%s"},"registry":{"endpoint":"%s"},"dns":{"nameserver":["%s"]}}`,
					"fake-server-name",
					"http://fake-registry.com",
					"fake-dns-server-ip",
				)
				updateUserdata(userdataContents)
			})

			Context("when registry endpoint is successfully resolved", func() {
				BeforeEach(func() {
					resolver.RegisterRecord(fakeinf.FakeDNSRecord{
						DNSServers: []string{"fake-dns-server-ip"},
						Host:       "http://fake-registry.com",
						IP:         "http://fake-registry-ip",
					})
				})

				It("returns the successfully resolved registry endpoint", func() {
					endpoint, err := metadataService.GetRegistryEndpoint()
					Expect(err).ToNot(HaveOccurred())
					Expect(endpoint).To(Equal("http://fake-registry-ip"))
				})
			})

			Context("when registry endpoint is not successfully resolved", func() {
				BeforeEach(func() {
					resolver.LookupHostErr = errors.New("fake-lookup-host-err")
				})

				It("returns error because it failed to resolve registry endpoint", func() {
					endpoint, err := metadataService.GetRegistryEndpoint()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-lookup-host-err"))
					Expect(endpoint).To(BeEmpty())
				})
			})
		})
	})
}
func describeCentosNetManager() {
	var (
		fs                            *fakesys.FakeFileSystem
		cmdRunner                     *fakesys.FakeCmdRunner
		ipResolver                    *fakeip.FakeResolver
		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)
		addressBroadcaster = &fakearp.FakeAddressBroadcaster{}
		netManager = NewCentosNetManager(
			fs,
			cmdRunner,
			ipResolver,
			interfaceConfigurationCreator,
			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",
			}

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

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

		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{}))
			})
		})
	})
}
Example #14
0
func rootDesc() {
	var (
		repo                   ReleaseRepo
		deploymentStateService DeploymentStateService
		fs                     *fakesys.FakeFileSystem
		fakeUUIDGenerator      *fakeuuid.FakeGenerator
	)

	BeforeEach(func() {
		logger := boshlog.NewLogger(boshlog.LevelNone)
		fs = fakesys.NewFakeFileSystem()
		fakeUUIDGenerator = &fakeuuid.FakeGenerator{}
		fakeUUIDGenerator.GeneratedUUID = "fake-uuid"
		deploymentStateService = NewFileSystemDeploymentStateService(fs, fakeUUIDGenerator, logger, "/fake/path")
		deploymentStateService.Load()
		repo = NewReleaseRepo(deploymentStateService, fakeUUIDGenerator)
	})

	Describe("List", func() {
		Context("when a current release exists", func() {
			BeforeEach(func() {
				conf, err := deploymentStateService.Load()
				Expect(err).ToNot(HaveOccurred())
				conf.Releases = []ReleaseRecord{
					ReleaseRecord{ID: "fake-guid-a", Name: "fake-name-a", Version: "fake-version-a"},
					ReleaseRecord{ID: "fake-guid-b", Name: "fake-name-b", Version: "fake-version-b"},
				}
				err = deploymentStateService.Save(conf)
				Expect(err).ToNot(HaveOccurred())
			})

			It("returns existing release", func() {
				records, err := repo.List()
				Expect(err).ToNot(HaveOccurred())
				Expect(records).To(Equal([]ReleaseRecord{
					{
						ID:      "fake-guid-a",
						Name:    "fake-name-a",
						Version: "fake-version-a",
					},
					{
						ID:      "fake-guid-b",
						Name:    "fake-name-b",
						Version: "fake-version-b",
					},
				}))
			})
		})

		Context("when there are no releases recorded", func() {
			It("returns not found", func() {
				records, err := repo.List()
				Expect(err).ToNot(HaveOccurred())
				Expect(records).To(HaveLen(0))
			})
		})

		Context("when the config service fails to load", func() {
			BeforeEach(func() {
				fs.ReadFileError = errors.New("kaboom")
			})

			It("returns an error", func() {
				_, err := repo.List()
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("Loading existing config"))
			})
		})
	})

	Describe("Update", func() {
		Context("when there are no existing releases", func() {
			It("saves the provided releases to the config file", func() {
				err := repo.Update([]release.Release{
					fakerelease.New("name1", "1"),
					fakerelease.New("name2", "2"),
				})
				Expect(err).ToNot(HaveOccurred())
				conf, err := deploymentStateService.Load()
				Expect(err).ToNot(HaveOccurred())
				Expect(conf.Releases).To(ConsistOf(
					ReleaseRecord{ID: "fake-uuid", Name: "name1", Version: "1"},
					ReleaseRecord{ID: "fake-uuid", Name: "name2", Version: "2"},
				))
			})
		})

		Context("when the existing releases exactly match the provided releases", func() {
			BeforeEach(func() {
				conf, err := deploymentStateService.Load()
				Expect(err).ToNot(HaveOccurred())
				conf.Releases = []ReleaseRecord{
					ReleaseRecord{ID: "old-uuid", Name: "name1", Version: "1"},
					ReleaseRecord{ID: "old-uuid", Name: "name2", Version: "2"},
				}
				err = deploymentStateService.Save(conf)
				Expect(err).ToNot(HaveOccurred())
			})
		})

		Context("when existing versions differ from the provided release versions", func() {
			BeforeEach(func() {
				conf, err := deploymentStateService.Load()
				Expect(err).ToNot(HaveOccurred())
				conf.Releases = []ReleaseRecord{
					ReleaseRecord{ID: "old-uuid", Name: "name1", Version: "1"},
					ReleaseRecord{ID: "old-uuid", Name: "name2", Version: "3"},
				}
				err = deploymentStateService.Save(conf)
				Expect(err).ToNot(HaveOccurred())
			})

			It("saves the provided releases to the config file", func() {
				err := repo.Update([]release.Release{
					fakerelease.New("name1", "1"),
					fakerelease.New("name2", "2"),
				})
				Expect(err).ToNot(HaveOccurred())
				conf, err := deploymentStateService.Load()
				Expect(err).ToNot(HaveOccurred())
				Expect(conf.Releases).To(ConsistOf(
					ReleaseRecord{ID: "fake-uuid", Name: "name1", Version: "1"},
					ReleaseRecord{ID: "fake-uuid", Name: "name2", Version: "2"},
				))
			})
		})

		Context("when existing names differ from the provided release names", func() {
			BeforeEach(func() {
				conf, err := deploymentStateService.Load()
				Expect(err).ToNot(HaveOccurred())
				conf.Releases = []ReleaseRecord{
					ReleaseRecord{ID: "old-uuid", Name: "name1", Version: "1"},
					ReleaseRecord{ID: "old-uuid", Name: "other-name", Version: "2"},
				}
				err = deploymentStateService.Save(conf)
				Expect(err).ToNot(HaveOccurred())
			})

			It("saves the provided releases to the config file", func() {
				err := repo.Update([]release.Release{
					fakerelease.New("name1", "1"),
					fakerelease.New("name2", "2"),
				})
				Expect(err).ToNot(HaveOccurred())
				conf, err := deploymentStateService.Load()
				Expect(err).ToNot(HaveOccurred())
				Expect(conf.Releases).To(ConsistOf(
					ReleaseRecord{ID: "fake-uuid", Name: "name1", Version: "1"},
					ReleaseRecord{ID: "fake-uuid", Name: "name2", Version: "2"},
				))
			})
		})

		Context("when a release is removed", func() {
			BeforeEach(func() {
				conf, err := deploymentStateService.Load()
				Expect(err).ToNot(HaveOccurred())
				conf.Releases = []ReleaseRecord{
					ReleaseRecord{ID: "old-uuid", Name: "name1", Version: "1"},
					ReleaseRecord{ID: "old-uuid", Name: "name2", Version: "2"},
					ReleaseRecord{ID: "old-uuid", Name: "name3", Version: "3"},
				}
				err = deploymentStateService.Save(conf)
				Expect(err).ToNot(HaveOccurred())
			})

			It("saves the provided releases to the config file", func() {
				err := repo.Update([]release.Release{
					fakerelease.New("name1", "1"),
					fakerelease.New("name2", "2"),
				})
				Expect(err).ToNot(HaveOccurred())
				conf, err := deploymentStateService.Load()
				Expect(err).ToNot(HaveOccurred())
				Expect(conf.Releases).To(ConsistOf(
					ReleaseRecord{ID: "fake-uuid", Name: "name1", Version: "1"},
					ReleaseRecord{ID: "fake-uuid", Name: "name2", Version: "2"},
				))
			})
		})

		Context("when a release is added", func() {
			BeforeEach(func() {
				conf, err := deploymentStateService.Load()
				Expect(err).ToNot(HaveOccurred())
				conf.Releases = []ReleaseRecord{
					ReleaseRecord{ID: "old-uuid", Name: "name1", Version: "1"},
				}
				err = deploymentStateService.Save(conf)
				Expect(err).ToNot(HaveOccurred())
			})

			It("saves the provided releases to the config file", func() {
				err := repo.Update([]release.Release{
					fakerelease.New("name1", "1"),
					fakerelease.New("name2", "2"),
				})
				Expect(err).ToNot(HaveOccurred())
				conf, err := deploymentStateService.Load()
				Expect(err).ToNot(HaveOccurred())
				Expect(conf.Releases).To(ConsistOf(
					ReleaseRecord{ID: "fake-uuid", Name: "name1", Version: "1"},
					ReleaseRecord{ID: "fake-uuid", Name: "name2", Version: "2"},
				))
			})
		})

		Context("when the config service fails to save", func() {
			BeforeEach(func() {
				fs.WriteFileError = errors.New("kaboom")
			})

			It("returns an error", func() {
				err := repo.Update([]release.Release{
					fakerelease.New("name1", "1"),
					fakerelease.New("name2", "2"),
				})
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("kaboom"))
			})
		})
	})
}
Example #15
0
func describeBuilder() {
	var mockCtrl *gomock.Controller

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

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

	var (
		logger boshlog.Logger

		mockReleaseJobResolver *mock_deployment_release.MockJobResolver
		mockDependencyCompiler *mock_state_job.MockDependencyCompiler
		mockJobListRenderer    *mock_template.MockJobListRenderer
		mockCompressor         *mock_template.MockRenderedJobListCompressor
		mockBlobstore          *mock_blobstore.MockBlobstore

		stateBuilder Builder
	)

	BeforeEach(func() {
		logger = boshlog.NewLogger(boshlog.LevelNone)

		mockReleaseJobResolver = mock_deployment_release.NewMockJobResolver(mockCtrl)
		mockDependencyCompiler = mock_state_job.NewMockDependencyCompiler(mockCtrl)
		mockJobListRenderer = mock_template.NewMockJobListRenderer(mockCtrl)
		mockCompressor = mock_template.NewMockRenderedJobListCompressor(mockCtrl)
		mockBlobstore = mock_blobstore.NewMockBlobstore(mockCtrl)
	})

	Describe("Build", func() {
		var (
			mockRenderedJobList        *mock_template.MockRenderedJobList
			mockRenderedJobListArchive *mock_template.MockRenderedJobListArchive

			jobName            string
			instanceID         int
			deploymentManifest bideplmanifest.Manifest
			fakeStage          *fakebiui.FakeStage

			releasePackageLibyaml *birelpkg.Package
			releasePackageRuby    *birelpkg.Package
			releasePackageCPI     *birelpkg.Package

			expectCompile *gomock.Call
		)

		BeforeEach(func() {
			mockRenderedJobList = mock_template.NewMockRenderedJobList(mockCtrl)
			mockRenderedJobListArchive = mock_template.NewMockRenderedJobListArchive(mockCtrl)

			jobName = "fake-deployment-job-name"
			instanceID = 0

			deploymentManifest = bideplmanifest.Manifest{
				Name: "fake-deployment-name",
				Jobs: []bideplmanifest.Job{
					{
						Name: "fake-deployment-job-name",
						Networks: []bideplmanifest.JobNetwork{
							{
								Name: "fake-network-name",
							},
						},
						Templates: []bideplmanifest.ReleaseJobRef{
							{
								Name:    "fake-release-job-name",
								Release: "fake-release-name",
							},
						},
						Properties: biproperty.Map{
							"fake-job-property": "fake-job-property-value",
						},
					},
				},
				Networks: []bideplmanifest.Network{
					{
						Name: "fake-network-name",
						Type: "fake-network-type",
						CloudProperties: biproperty.Map{
							"fake-network-cloud-property": "fake-network-cloud-property-value",
						},
					},
				},
				Properties: biproperty.Map{
					"fake-job-property": "fake-global-property-value", //overridden by job property value
				},
			}

			fakeStage = fakebiui.NewFakeStage()

			stateBuilder = NewBuilder(
				mockReleaseJobResolver,
				mockDependencyCompiler,
				mockJobListRenderer,
				mockCompressor,
				mockBlobstore,
				logger,
			)

			releasePackageLibyaml = &birelpkg.Package{
				Name:         "libyaml",
				Fingerprint:  "fake-package-source-fingerprint-libyaml",
				SHA1:         "fake-package-source-sha1-libyaml",
				Dependencies: []*birelpkg.Package{},
				ArchivePath:  "fake-package-archive-path-libyaml", // only required by compiler...
			}
			releasePackageRuby = &birelpkg.Package{
				Name:         "ruby",
				Fingerprint:  "fake-package-source-fingerprint-ruby",
				SHA1:         "fake-package-source-sha1-ruby",
				Dependencies: []*birelpkg.Package{releasePackageLibyaml},
				ArchivePath:  "fake-package-archive-path-ruby", // only required by compiler...
			}
			releasePackageCPI = &birelpkg.Package{
				Name:         "cpi",
				Fingerprint:  "fake-package-source-fingerprint-cpi",
				SHA1:         "fake-package-source-sha1-cpi",
				Dependencies: []*birelpkg.Package{releasePackageRuby},
				ArchivePath:  "fake-package-archive-path-cpi", // only required by compiler...
			}
		})

		JustBeforeEach(func() {
			releaseJob := bireljob.Job{
				Name:        "fake-release-job-name",
				Fingerprint: "fake-release-job-source-fingerprint",
				Packages:    []*birelpkg.Package{releasePackageCPI, releasePackageRuby},
			}
			mockReleaseJobResolver.EXPECT().Resolve("fake-release-job-name", "fake-release-name").Return(releaseJob, nil)

			releaseJobs := []bireljob.Job{releaseJob}
			compiledPackageRefs := []bistatejob.CompiledPackageRef{
				{
					Name:        "libyaml",
					Version:     "fake-package-source-fingerprint-libyaml",
					BlobstoreID: "fake-package-compiled-archive-blob-id-libyaml",
					SHA1:        "fake-package-compiled-archive-sha1-libyaml",
				},
				{
					Name:        "ruby",
					Version:     "fake-package-source-fingerprint-ruby",
					BlobstoreID: "fake-package-compiled-archive-blob-id-ruby",
					SHA1:        "fake-package-compiled-archive-sha1-ruby",
				},
				{
					Name:        "cpi",
					Version:     "fake-package-source-fingerprint-cpi",
					BlobstoreID: "fake-package-compiled-archive-blob-id-cpi",
					SHA1:        "fake-package-compiled-archive-sha1-cpi",
				},
			}
			expectCompile = mockDependencyCompiler.EXPECT().Compile(releaseJobs, fakeStage).Return(compiledPackageRefs, nil).AnyTimes()

			jobProperties := biproperty.Map{
				"fake-job-property": "fake-job-property-value",
			}
			globalProperties := biproperty.Map{
				"fake-job-property": "fake-global-property-value",
			}
			mockJobListRenderer.EXPECT().Render(releaseJobs, jobProperties, globalProperties, "fake-deployment-name").Return(mockRenderedJobList, nil)

			mockRenderedJobList.EXPECT().DeleteSilently()

			mockCompressor.EXPECT().Compress(mockRenderedJobList).Return(mockRenderedJobListArchive, nil)

			mockRenderedJobListArchive.EXPECT().DeleteSilently()

			mockRenderedJobListArchive.EXPECT().Path().Return("fake-rendered-job-list-archive-path")
			mockRenderedJobListArchive.EXPECT().SHA1().Return("fake-rendered-job-list-archive-sha1")
			mockRenderedJobListArchive.EXPECT().Fingerprint().Return("fake-rendered-job-list-fingerprint")

			mockBlobstore.EXPECT().Add("fake-rendered-job-list-archive-path").Return("fake-rendered-job-list-archive-blob-id", nil)
		})

		It("compiles the dependencies of the jobs", func() {
			expectCompile.Times(1)

			_, err := stateBuilder.Build(jobName, instanceID, deploymentManifest, fakeStage)
			Expect(err).ToNot(HaveOccurred())
		})

		It("builds a new instance state with zero-to-many networks", func() {
			state, err := stateBuilder.Build(jobName, instanceID, deploymentManifest, fakeStage)
			Expect(err).ToNot(HaveOccurred())

			Expect(state.NetworkInterfaces()).To(ContainElement(NetworkRef{
				Name: "fake-network-name",
				Interface: biproperty.Map{
					"type":    "fake-network-type",
					"default": []bideplmanifest.NetworkDefault{"dns", "gateway"},
					"cloud_properties": biproperty.Map{
						"fake-network-cloud-property": "fake-network-cloud-property-value",
					},
				},
			}))
			Expect(state.NetworkInterfaces()).To(HaveLen(1))
		})

		It("builds a new instance state with zero-to-many rendered jobs from one or more releases", func() {
			state, err := stateBuilder.Build(jobName, instanceID, deploymentManifest, fakeStage)
			Expect(err).ToNot(HaveOccurred())

			Expect(state.RenderedJobs()).To(ContainElement(JobRef{
				Name:    "fake-release-job-name",
				Version: "fake-release-job-source-fingerprint",
			}))

			// multiple jobs are rendered in a single archive
			Expect(state.RenderedJobListArchive()).To(Equal(BlobRef{
				BlobstoreID: "fake-rendered-job-list-archive-blob-id",
				SHA1:        "fake-rendered-job-list-archive-sha1",
			}))
			Expect(state.RenderedJobs()).To(HaveLen(1))
		})

		It("prints ui stages for compiling packages and rendering job templates", func() {
			_, err := stateBuilder.Build(jobName, instanceID, deploymentManifest, fakeStage)
			Expect(err).ToNot(HaveOccurred())

			Expect(fakeStage.PerformCalls).To(Equal([]*fakebiui.PerformCall{
				// compile stages not produced by mockDependencyCompiler
				{Name: "Rendering job templates"},
			}))
		})

		It("builds a new instance state with the compiled packages required by the release jobs", func() {
			state, err := stateBuilder.Build(jobName, instanceID, deploymentManifest, fakeStage)
			Expect(err).ToNot(HaveOccurred())

			Expect(state.CompiledPackages()).To(ContainElement(PackageRef{
				Name:    "cpi",
				Version: "fake-package-source-fingerprint-cpi",
				Archive: BlobRef{
					SHA1:        "fake-package-compiled-archive-sha1-cpi",
					BlobstoreID: "fake-package-compiled-archive-blob-id-cpi",
				},
			}))
			Expect(state.CompiledPackages()).To(ContainElement(PackageRef{
				Name:    "ruby",
				Version: "fake-package-source-fingerprint-ruby",
				Archive: BlobRef{
					SHA1:        "fake-package-compiled-archive-sha1-ruby",
					BlobstoreID: "fake-package-compiled-archive-blob-id-ruby",
				},
			}))
		})

		It("builds a new instance state that includes transitively dependent compiled packages", func() {
			state, err := stateBuilder.Build(jobName, instanceID, deploymentManifest, fakeStage)
			Expect(err).ToNot(HaveOccurred())

			Expect(state.CompiledPackages()).To(ContainElement(PackageRef{
				Name:    "cpi",
				Version: "fake-package-source-fingerprint-cpi",
				Archive: BlobRef{
					SHA1:        "fake-package-compiled-archive-sha1-cpi",
					BlobstoreID: "fake-package-compiled-archive-blob-id-cpi",
				},
			}))
			Expect(state.CompiledPackages()).To(ContainElement(PackageRef{
				Name:    "ruby",
				Version: "fake-package-source-fingerprint-ruby",
				Archive: BlobRef{
					SHA1:        "fake-package-compiled-archive-sha1-ruby",
					BlobstoreID: "fake-package-compiled-archive-blob-id-ruby",
				},
			}))
			Expect(state.CompiledPackages()).To(ContainElement(PackageRef{
				Name:    "libyaml",
				Version: "fake-package-source-fingerprint-libyaml",
				Archive: BlobRef{
					SHA1:        "fake-package-compiled-archive-sha1-libyaml",
					BlobstoreID: "fake-package-compiled-archive-blob-id-libyaml",
				},
			}))
			Expect(state.CompiledPackages()).To(HaveLen(3))
		})

		Context("when multiple packages have the same dependency", func() {
			BeforeEach(func() {
				releasePackageRuby.Dependencies = append(releasePackageRuby.Dependencies, releasePackageLibyaml)
			})

			It("does not recompile dependant packages", func() {
				state, err := stateBuilder.Build(jobName, instanceID, deploymentManifest, fakeStage)
				Expect(err).ToNot(HaveOccurred())

				Expect(state.CompiledPackages()).To(ContainElement(PackageRef{
					Name:    "cpi",
					Version: "fake-package-source-fingerprint-cpi",
					Archive: BlobRef{
						SHA1:        "fake-package-compiled-archive-sha1-cpi",
						BlobstoreID: "fake-package-compiled-archive-blob-id-cpi",
					},
				}))
				Expect(state.CompiledPackages()).To(ContainElement(PackageRef{
					Name:    "ruby",
					Version: "fake-package-source-fingerprint-ruby",
					Archive: BlobRef{
						SHA1:        "fake-package-compiled-archive-sha1-ruby",
						BlobstoreID: "fake-package-compiled-archive-blob-id-ruby",
					},
				}))
				Expect(state.CompiledPackages()).To(ContainElement(PackageRef{
					Name:    "libyaml",
					Version: "fake-package-source-fingerprint-libyaml",
					Archive: BlobRef{
						SHA1:        "fake-package-compiled-archive-sha1-libyaml",
						BlobstoreID: "fake-package-compiled-archive-blob-id-libyaml",
					},
				}))
				Expect(state.CompiledPackages()).To(HaveLen(3))
			})
		})

		It("builds an instance state that can be converted to an ApplySpec", func() {
			state, err := stateBuilder.Build(jobName, instanceID, deploymentManifest, fakeStage)
			Expect(err).ToNot(HaveOccurred())

			Expect(state.ToApplySpec()).To(Equal(bias.ApplySpec{
				Deployment: "fake-deployment-name",
				Index:      0,
				Networks: map[string]biproperty.Map{
					"fake-network-name": biproperty.Map{
						"type":    "fake-network-type",
						"default": []bideplmanifest.NetworkDefault{"dns", "gateway"},
						"cloud_properties": biproperty.Map{
							"fake-network-cloud-property": "fake-network-cloud-property-value",
						},
					},
				},
				Job: bias.Job{
					Name: "fake-deployment-job-name",
					Templates: []bias.Blob{
						{
							Name:    "fake-release-job-name",
							Version: "fake-release-job-source-fingerprint",
						},
					},
				},
				Packages: map[string]bias.Blob{
					"cpi": bias.Blob{
						Name:        "cpi",
						Version:     "fake-package-source-fingerprint-cpi",
						SHA1:        "fake-package-compiled-archive-sha1-cpi",
						BlobstoreID: "fake-package-compiled-archive-blob-id-cpi",
					},
					"ruby": bias.Blob{
						Name:        "ruby",
						Version:     "fake-package-source-fingerprint-ruby",
						SHA1:        "fake-package-compiled-archive-sha1-ruby",
						BlobstoreID: "fake-package-compiled-archive-blob-id-ruby",
					},
					"libyaml": bias.Blob{
						Name:        "libyaml",
						Version:     "fake-package-source-fingerprint-libyaml",
						SHA1:        "fake-package-compiled-archive-sha1-libyaml",
						BlobstoreID: "fake-package-compiled-archive-blob-id-libyaml",
					},
				},
				RenderedTemplatesArchive: bias.RenderedTemplatesArchiveSpec{
					BlobstoreID: "fake-rendered-job-list-archive-blob-id",
					SHA1:        "fake-rendered-job-list-archive-sha1",
				},
				ConfigurationHash: "fake-rendered-job-list-fingerprint",
			}))
		})
	})
}
Example #16
0
	return "", errors.New("fake-get-ip-err")
}

var _ = Describe("arping", func() {
	const arpingIterations = 6

	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"),
			}
Example #17
0
func createOsFs() (fs FileSystem, runner CmdRunner) {
	logger := boshlog.NewLogger(boshlog.LevelNone)
	fs = NewOsFileSystem(logger)
	return
}