func createBlobManager() (blobManager BlobManager, fs *fakesys.FakeFileSystem) { fs = fakesys.NewFakeFileSystem() dirProvider := boshdir.NewDirectoriesProvider("/var/vcap") blobManager = NewBlobManager(fs, dirProvider) return }
func NewFakePlatform() (platform *FakePlatform) { platform = new(FakePlatform) platform.Fs = fakesys.NewFakeFileSystem() platform.Runner = fakesys.NewFakeCmdRunner() platform.FakeCompressor = fakecmd.NewFakeCompressor() platform.FakeCopier = fakecmd.NewFakeCopier() platform.FakeVitalsService = fakevitals.NewFakeService() platform.DevicePathResolver = fakedpresolv.NewFakeDevicePathResolver() platform.AddUserToGroupsGroups = make(map[string][]string) platform.SetupSSHPublicKeys = make(map[string]string) platform.UserPasswords = make(map[string]string) platform.ScsiDiskMap = make(map[string]string) return }
func init() { Describe("Testing with Ginkgo", func() { It("new drain script", func() { runner := fakesys.NewFakeCmdRunner() fs := fakesys.NewFakeFileSystem() dirProvider := boshdir.NewDirectoriesProvider("/var/vcap") scriptProvider := NewConcreteDrainScriptProvider(runner, fs, dirProvider) drainScript := scriptProvider.NewDrainScript("foo") Expect(drainScript.Path()).To(Equal("/var/vcap/jobs/foo/bin/drain")) }) }) }
import ( fakesys "github.com/cloudfoundry/bosh-agent/system/fakes" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/sclevine/bosh-provisioner/main" bpvm "github.com/sclevine/bosh-provisioner/vm" ) var _ = Describe("NewConfigFromPath", func() { var ( fs *fakesys.FakeFileSystem ) BeforeEach(func() { fs = fakesys.NewFakeFileSystem() }) It("defautls null values to agent provisioner config defaults", func() { configJSON := `{ "manifest_path": "fake-manifest-path", "assets_dir": "fake-assets-dir", "repos_dir": "fake-repos-dir", "blobstore": { "provider": "local", "options": { "blobstore_path": "fake-blobstore-path" } }, "vm_provisioner": { "agent_provisioner": {
func init() { Describe("concreteV1Service", func() { var ( fs *fakesys.FakeFileSystem specPath = "/spec.json" service V1Service ) BeforeEach(func() { fs = fakesys.NewFakeFileSystem() service = NewConcreteV1Service(fs, specPath) }) Describe("Get", func() { Context("when filesystem has a spec file", func() { BeforeEach(func() { fs.WriteFileString(specPath, `{"deployment":"fake-deployment-name"}`) }) It("reads spec from filesystem", func() { spec, err := service.Get() Expect(err).ToNot(HaveOccurred()) Expect(spec).To(Equal(V1ApplySpec{Deployment: "fake-deployment-name"})) }) It("returns error if reading spec from filesystem errs", func() { fs.ReadFileError = errors.New("fake-read-error") spec, err := service.Get() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-read-error")) Expect(spec).To(Equal(V1ApplySpec{})) }) }) Context("when filesystem does not have a spec file", func() { It("reads spec from filesystem", func() { spec, err := service.Get() Expect(err).ToNot(HaveOccurred()) Expect(spec).To(Equal(V1ApplySpec{})) }) }) }) Describe("Set", func() { newSpec := V1ApplySpec{Deployment: "fake-deployment-name"} It("writes spec to filesystem", func() { err := service.Set(newSpec) Expect(err).ToNot(HaveOccurred()) specPathStats := fs.GetFileTestStat(specPath) Expect(specPathStats).ToNot(BeNil()) boshassert.MatchesJSONBytes(GinkgoT(), newSpec, specPathStats.Content) }) It("returns error if writing spec to filesystem errs", func() { fs.WriteToFileError = errors.New("fake-write-error") err := service.Set(newSpec) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-write-error")) }) }) Describe("PopulateDynamicNetworks", func() { Context("when there are no dynamic networks", func() { unresolvedSpec := V1ApplySpec{ Deployment: "fake-deployment", NetworkSpecs: map[string]NetworkSpec{ "fake-net": NetworkSpec{ Fields: map[string]interface{}{"ip": "fake-net-ip"}, }, }, } It("returns spec without modifying any networks", func() { spec, err := service.PopulateDynamicNetworks(unresolvedSpec, boshsettings.Settings{}) Expect(err).ToNot(HaveOccurred()) Expect(spec).To(Equal(V1ApplySpec{ Deployment: "fake-deployment", NetworkSpecs: map[string]NetworkSpec{ "fake-net": NetworkSpec{ Fields: map[string]interface{}{"ip": "fake-net-ip"}, }, }, })) }) }) Context("when there are dynamic networks", func() { unresolvedSpec := V1ApplySpec{ Deployment: "fake-deployment", NetworkSpecs: map[string]NetworkSpec{ "fake-net1": NetworkSpec{ Fields: map[string]interface{}{ "ip": "fake-net1-ip", "netmask": "fake-net1-netmask", "gateway": "fake-net1-gateway", }, }, "fake-net2": NetworkSpec{ Fields: map[string]interface{}{ "type": NetworkSpecTypeDynamic, "ip": "fake-net2-ip", "netmask": "fake-net2-netmask", "gateway": "fake-net2-gateway", }, }, "fake-net3": NetworkSpec{ Fields: map[string]interface{}{ "type": NetworkSpecTypeDynamic, "ip": "fake-net3-ip", "netmask": "fake-net3-netmask", "gateway": "fake-net3-gateway", }, }, }, } Context("when associated network is in settings", func() { settings := boshsettings.Settings{ Networks: boshsettings.Networks{ "fake-net1": boshsettings.Network{ IP: "fake-resolved1-ip", Netmask: "fake-resolved1-netmask", Gateway: "fake-resolved1-gateway", }, "fake-net2": boshsettings.Network{ IP: "fake-resolved2-ip", Netmask: "fake-resolved2-netmask", Gateway: "fake-resolved2-gateway", }, "fake-net3": boshsettings.Network{ IP: "fake-resolved3-ip", Netmask: "fake-resolved3-netmask", Gateway: "fake-resolved3-gateway", }, }, } It("returns spec with modified dynamic networks and keeping everything else the same", func() { spec, err := service.PopulateDynamicNetworks(unresolvedSpec, settings) Expect(err).ToNot(HaveOccurred()) Expect(spec).To(Equal(V1ApplySpec{ Deployment: "fake-deployment", NetworkSpecs: map[string]NetworkSpec{ "fake-net1": NetworkSpec{ Fields: map[string]interface{}{ // ip info not replaced "ip": "fake-net1-ip", "netmask": "fake-net1-netmask", "gateway": "fake-net1-gateway", }, }, "fake-net2": NetworkSpec{ Fields: map[string]interface{}{ "type": NetworkSpecTypeDynamic, "ip": "fake-resolved2-ip", "netmask": "fake-resolved2-netmask", "gateway": "fake-resolved2-gateway", }, }, "fake-net3": NetworkSpec{ Fields: map[string]interface{}{ "type": NetworkSpecTypeDynamic, "ip": "fake-resolved3-ip", "netmask": "fake-resolved3-netmask", "gateway": "fake-resolved3-gateway", }, }, }, })) }) }) Context("when associated network cannot be found in settings", func() { It("returns error", func() { spec, err := service.PopulateDynamicNetworks(unresolvedSpec, boshsettings.Settings{}) Expect(err).To(Equal(errors.New("Network fake-net2 is not found in settings"))) Expect(spec).To(Equal(V1ApplySpec{})) }) }) }) }) }) }
func init() { Describe("renderedJobApplier", func() { var ( jobsBc *fakebc.FakeBundleCollection jobSupervisor *fakejobsuper.FakeJobSupervisor packageApplierProvider *fakepa.FakePackageApplierProvider blobstore *fakeblob.FakeBlobstore compressor *fakecmd.FakeCompressor fs *fakesys.FakeFileSystem applier JobApplier ) BeforeEach(func() { jobsBc = fakebc.NewFakeBundleCollection() jobSupervisor = fakejobsuper.NewFakeJobSupervisor() packageApplierProvider = fakepa.NewFakePackageApplierProvider() blobstore = fakeblob.NewFakeBlobstore() fs = fakesys.NewFakeFileSystem() compressor = fakecmd.NewFakeCompressor() logger := boshlog.NewLogger(boshlog.LevelNone) applier = NewRenderedJobApplier( jobsBc, jobSupervisor, packageApplierProvider, blobstore, compressor, fs, logger, ) }) Describe("Prepare & Apply", func() { var ( job models.Job bundle *fakebc.FakeBundle ) BeforeEach(func() { job, bundle = buildJob(jobsBc) }) ItInstallsJob := func(act func() error) { It("returns error when installing job fails", func() { bundle.InstallError = errors.New("fake-install-error") err := act() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-install-error")) }) It("downloads and later cleans up downloaded job template blob", func() { blobstore.GetFileName = "/fake-blobstore-file-name" err := act() Expect(err).ToNot(HaveOccurred()) Expect(blobstore.GetBlobIDs[0]).To(Equal("fake-blobstore-id")) Expect(blobstore.GetFingerprints[0]).To(Equal("fake-blob-sha1")) // downloaded file is cleaned up Expect(blobstore.CleanUpFileName).To(Equal("/fake-blobstore-file-name")) }) It("returns error when downloading job template blob fails", func() { blobstore.GetError = errors.New("fake-get-error") err := act() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-get-error")) }) It("decompresses job template blob to tmp path and later cleans it up", func() { fs.TempDirDir = "/fake-tmp-dir" blobstore.GetFileName = "/fake-blobstore-file-name" var tmpDirExistsBeforeInstall bool bundle.InstallCallBack = func() { tmpDirExistsBeforeInstall = true } err := act() Expect(err).ToNot(HaveOccurred()) Expect(compressor.DecompressFileToDirTarballPaths[0]).To(Equal("/fake-blobstore-file-name")) Expect(compressor.DecompressFileToDirDirs[0]).To(Equal("/fake-tmp-dir")) // tmp dir exists before bundle install Expect(tmpDirExistsBeforeInstall).To(BeTrue()) // tmp dir is cleaned up after install Expect(fs.FileExists(fs.TempDirDir)).To(BeFalse()) }) It("returns error when temporary directory creation fails", func() { fs.TempDirError = errors.New("fake-filesystem-tempdir-error") err := act() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-filesystem-tempdir-error")) }) It("returns error when decompressing job template fails", func() { compressor.DecompressFileToDirErr = errors.New("fake-decompress-error") err := act() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-decompress-error")) }) It("returns error when getting the list of bin files fails", func() { fs.GlobErr = errors.New("fake-glob-error") err := act() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-glob-error")) }) It("returns error when changing permissions on bin files fails", func() { fs.TempDirDir = "/fake-tmp-dir" fs.SetGlob("/fake-tmp-dir/fake-path-in-archive/bin/*", []string{ "/fake-tmp-dir/fake-path-in-archive/bin/test", }) fs.ChmodErr = errors.New("fake-chmod-error") err := act() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-chmod-error")) }) It("installs bundle from decompressed tmp path of a job template", func() { fs.TempDirDir = "/fake-tmp-dir" var installedBeforeDecompression bool compressor.DecompressFileToDirCallBack = func() { installedBeforeDecompression = bundle.Installed } err := act() Expect(err).ToNot(HaveOccurred()) // bundle installation did not happen before decompression Expect(installedBeforeDecompression).To(BeFalse()) // make sure that bundle install happened after decompression Expect(bundle.InstallSourcePath).To(Equal("/fake-tmp-dir/fake-path-in-archive")) }) It("sets executable bit for files in bin", func() { fs.TempDirDir = "/fake-tmp-dir" compressor.DecompressFileToDirCallBack = func() { fs.WriteFile("/fake-tmp-dir/fake-path-in-archive/bin/test1", []byte{}) fs.WriteFile("/fake-tmp-dir/fake-path-in-archive/bin/test2", []byte{}) fs.WriteFile("/fake-tmp-dir/fake-path-in-archive/config/test", []byte{}) } fs.SetGlob("/fake-tmp-dir/fake-path-in-archive/bin/*", []string{ "/fake-tmp-dir/fake-path-in-archive/bin/test1", "/fake-tmp-dir/fake-path-in-archive/bin/test2", }) var binTest1Stats, binTest2Stats, configTestStats *fakesys.FakeFileStats bundle.InstallCallBack = func() { binTest1Stats = fs.GetFileTestStat("/fake-tmp-dir/fake-path-in-archive/bin/test1") binTest2Stats = fs.GetFileTestStat("/fake-tmp-dir/fake-path-in-archive/bin/test2") configTestStats = fs.GetFileTestStat("/fake-tmp-dir/fake-path-in-archive/config/test") } err := act() Expect(err).ToNot(HaveOccurred()) // bin files are executable Expect(int(binTest1Stats.FileMode)).To(Equal(0755)) Expect(int(binTest2Stats.FileMode)).To(Equal(0755)) // non-bin files are not made executable Expect(int(configTestStats.FileMode)).ToNot(Equal(0755)) }) } ItUpdatesPackages := func(act func() error) { var packageApplier *fakepa.FakePackageApplier BeforeEach(func() { packageApplier = fakepa.NewFakePackageApplier() packageApplierProvider.JobSpecificPackageAppliers[job.Name] = packageApplier }) It("applies each package that job depends on and then cleans up packages", func() { err := act() Expect(err).ToNot(HaveOccurred()) Expect(packageApplier.ActionsCalled).To(Equal([]string{"Apply", "Apply", "KeepOnly"})) Expect(len(packageApplier.AppliedPackages)).To(Equal(2)) // present Expect(packageApplier.AppliedPackages).To(Equal(job.Packages)) }) It("returns error when applying package that job depends on fails", func() { packageApplier.ApplyError = errors.New("fake-apply-err") err := act() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-apply-err")) }) It("keeps only currently required packages but does not completely uninstall them", func() { err := act() Expect(err).ToNot(HaveOccurred()) Expect(len(packageApplier.KeptOnlyPackages)).To(Equal(2)) // present Expect(packageApplier.KeptOnlyPackages).To(Equal(job.Packages)) }) It("returns error when keeping only currently required packages fails", func() { packageApplier.KeepOnlyErr = errors.New("fake-keep-only-err") err := act() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-keep-only-err")) }) } Describe("Prepare", func() { act := func() error { return applier.Prepare(job) } It("return an error if getting file bundle fails", func() { jobsBc.GetErr = errors.New("fake-get-bundle-error") err := act() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-get-bundle-error")) }) It("returns an error if checking for installed path fails", func() { bundle.IsInstalledErr = errors.New("fake-is-installed-error") err := act() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-is-installed-error")) }) Context("when job is already installed", func() { BeforeEach(func() { bundle.Installed = true }) It("does not install", func() { err := act() Expect(err).ToNot(HaveOccurred()) Expect(bundle.ActionsCalled).To(Equal([]string{})) // no Install }) It("does not download the job template", func() { err := act() Expect(err).ToNot(HaveOccurred()) Expect(blobstore.GetBlobIDs).To(BeNil()) }) }) Context("when job is not installed", func() { BeforeEach(func() { bundle.Installed = false }) It("installs job (but does not enable)", func() { err := act() Expect(err).ToNot(HaveOccurred()) Expect(bundle.ActionsCalled).To(Equal([]string{"Install"})) }) ItInstallsJob(act) }) }) Describe("Apply", func() { act := func() error { return applier.Apply(job) } It("return an error if getting file bundle fails", func() { jobsBc.GetErr = errors.New("fake-get-bundle-error") err := act() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-get-bundle-error")) }) It("returns an error if checking for installed path fails", func() { bundle.IsInstalledErr = errors.New("fake-is-installed-error") err := act() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-is-installed-error")) }) Context("when job is already installed", func() { BeforeEach(func() { bundle.Installed = true }) It("does not install but only enables job", func() { err := act() Expect(err).ToNot(HaveOccurred()) Expect(bundle.ActionsCalled).To(Equal([]string{"Enable"})) // no Install }) It("returns error when job enable fails", func() { bundle.EnableError = errors.New("fake-enable-error") err := act() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-enable-error")) }) It("does not download the job template", func() { err := act() Expect(err).ToNot(HaveOccurred()) Expect(blobstore.GetBlobIDs).To(BeNil()) }) ItUpdatesPackages(act) }) Context("when job is not installed", func() { BeforeEach(func() { bundle.Installed = false }) It("installs and enables job", func() { err := act() Expect(err).ToNot(HaveOccurred()) Expect(bundle.ActionsCalled).To(Equal([]string{"Install", "Enable"})) }) It("returns error when job enable fails", func() { bundle.EnableError = errors.New("fake-enable-error") err := act() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-enable-error")) }) ItInstallsJob(act) ItUpdatesPackages(act) }) }) }) Describe("Configure", func() { It("adds job to the job supervisor", func() { job, bundle := buildJob(jobsBc) fs := fakesys.NewFakeFileSystem() fs.WriteFileString("/path/to/job/monit", "some conf") fs.SetGlob("/path/to/job/*.monit", []string{"/path/to/job/subjob.monit"}) bundle.GetDirPath = "/path/to/job" bundle.GetDirFs = fs err := applier.Configure(job, 0) Expect(err).ToNot(HaveOccurred()) Expect(len(jobSupervisor.AddJobArgs)).To(Equal(2)) Expect(jobSupervisor.AddJobArgs[0]).To(Equal(fakejobsuper.AddJobArgs{ Name: job.Name, Index: 0, ConfigPath: "/path/to/job/monit", })) Expect(jobSupervisor.AddJobArgs[1]).To(Equal(fakejobsuper.AddJobArgs{ Name: job.Name + "_subjob", Index: 0, ConfigPath: "/path/to/job/subjob.monit", })) }) It("does not require monit script", func() { job, bundle := buildJob(jobsBc) fs := fakesys.NewFakeFileSystem() bundle.GetDirFs = fs err := applier.Configure(job, 0) Expect(err).ToNot(HaveOccurred()) Expect(len(jobSupervisor.AddJobArgs)).To(Equal(0)) }) }) Describe("KeepOnly", func() { It("first disables and then uninstalls jobs that are not in keeponly list", func() { _, bundle1 := buildJob(jobsBc) job2, bundle2 := buildJob(jobsBc) _, bundle3 := buildJob(jobsBc) job4, bundle4 := buildJob(jobsBc) jobsBc.ListBundles = []boshbc.Bundle{bundle1, bundle2, bundle3, bundle4} err := applier.KeepOnly([]models.Job{job4, job2}) Expect(err).ToNot(HaveOccurred()) Expect(bundle1.ActionsCalled).To(Equal([]string{"Disable", "Uninstall"})) Expect(bundle2.ActionsCalled).To(Equal([]string{})) Expect(bundle3.ActionsCalled).To(Equal([]string{"Disable", "Uninstall"})) Expect(bundle4.ActionsCalled).To(Equal([]string{})) }) It("returns error when bundle collection fails to return list of installed bundles", func() { jobsBc.ListErr = errors.New("fake-bc-list-error") err := applier.KeepOnly([]models.Job{}) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-bc-list-error")) }) It("returns error when bundle collection cannot retrieve bundle for keep-only job", func() { job1, bundle1 := buildJob(jobsBc) jobsBc.ListBundles = []boshbc.Bundle{bundle1} jobsBc.GetErr = errors.New("fake-bc-get-error") err := applier.KeepOnly([]models.Job{job1}) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-bc-get-error")) }) It("returns error when at least one bundle cannot be disabled", func() { _, bundle1 := buildJob(jobsBc) jobsBc.ListBundles = []boshbc.Bundle{bundle1} bundle1.DisableErr = errors.New("fake-bc-disable-error") err := applier.KeepOnly([]models.Job{}) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-bc-disable-error")) }) It("returns error when at least one bundle cannot be uninstalled", func() { _, bundle1 := buildJob(jobsBc) jobsBc.ListBundles = []boshbc.Bundle{bundle1} bundle1.UninstallErr = errors.New("fake-bc-uninstall-error") err := applier.KeepOnly([]models.Job{}) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-bc-uninstall-error")) }) }) }) }
func init() { Describe("concretePackageApplier", func() { var ( packagesBc *fakebc.FakeBundleCollection blobstore *fakeblob.FakeBlobstore compressor *fakecmd.FakeCompressor fs *fakesys.FakeFileSystem logger boshlog.Logger applier PackageApplier ) BeforeEach(func() { packagesBc = fakebc.NewFakeBundleCollection() blobstore = fakeblob.NewFakeBlobstore() compressor = fakecmd.NewFakeCompressor() fs = fakesys.NewFakeFileSystem() logger = boshlog.NewLogger(boshlog.LevelNone) applier = NewConcretePackageApplier(packagesBc, true, blobstore, compressor, fs, logger) }) Describe("Prepare & Apply", func() { var ( pkg models.Package bundle *fakebc.FakeBundle ) BeforeEach(func() { pkg, bundle = buildPkg(packagesBc) }) ItInstallsPkg := func(act func() error) { It("returns error when installing package fails", func() { bundle.InstallError = errors.New("fake-install-error") err := act() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-install-error")) }) It("downloads and later cleans up downloaded package blob", func() { blobstore.GetFileName = "/fake-blobstore-file-name" err := act() Expect(err).ToNot(HaveOccurred()) Expect(blobstore.GetBlobIDs[0]).To(Equal("fake-blobstore-id")) Expect(blobstore.GetFingerprints[0]).To(Equal("fake-blob-sha1")) // downloaded file is cleaned up Expect(blobstore.CleanUpFileName).To(Equal("/fake-blobstore-file-name")) }) It("returns error when downloading package blob fails", func() { blobstore.GetError = errors.New("fake-get-error") err := act() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-get-error")) }) It("decompresses package blob to tmp path and later cleans it up", func() { fs.TempDirDir = "/fake-tmp-dir" blobstore.GetFileName = "/fake-blobstore-file-name" var tmpDirExistsBeforeInstall bool bundle.InstallCallBack = func() { tmpDirExistsBeforeInstall = true } err := act() Expect(err).ToNot(HaveOccurred()) Expect(compressor.DecompressFileToDirTarballPaths[0]).To(Equal("/fake-blobstore-file-name")) Expect(compressor.DecompressFileToDirDirs[0]).To(Equal("/fake-tmp-dir")) // tmp dir exists before bundle install Expect(tmpDirExistsBeforeInstall).To(BeTrue()) // tmp dir is cleaned up after install Expect(fs.FileExists(fs.TempDirDir)).To(BeFalse()) }) It("returns error when temporary directory creation fails", func() { fs.TempDirError = errors.New("fake-filesystem-tempdir-error") err := act() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-filesystem-tempdir-error")) }) It("returns error when decompressing package blob fails", func() { compressor.DecompressFileToDirErr = errors.New("fake-decompress-error") err := act() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-decompress-error")) }) It("installs bundle from decompressed tmp path of a package blob", func() { fs.TempDirDir = "/fake-tmp-dir" var installedBeforeDecompression bool compressor.DecompressFileToDirCallBack = func() { installedBeforeDecompression = bundle.Installed } err := act() Expect(err).ToNot(HaveOccurred()) // bundle installation did not happen before decompression Expect(installedBeforeDecompression).To(BeFalse()) // make sure that bundle install happened after decompression Expect(bundle.InstallSourcePath).To(Equal("/fake-tmp-dir")) }) } Describe("Prepare", func() { act := func() error { return applier.Prepare(pkg) } It("return an error if getting file bundle fails", func() { packagesBc.GetErr = errors.New("fake-get-bundle-error") err := act() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-get-bundle-error")) }) It("returns an error if checking for package installation fails", func() { bundle.IsInstalledErr = errors.New("fake-is-installed-error") err := act() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-is-installed-error")) }) Context("when package is already installed", func() { BeforeEach(func() { bundle.Installed = true }) It("does not install", func() { err := act() Expect(err).ToNot(HaveOccurred()) Expect(bundle.ActionsCalled).To(Equal([]string{})) // no Install }) It("does not download the package", func() { err := act() Expect(err).ToNot(HaveOccurred()) Expect(blobstore.GetBlobIDs).To(BeNil()) }) }) Context("when package is not installed", func() { BeforeEach(func() { bundle.Installed = false }) It("installs package (but does not enable it)", func() { err := act() Expect(err).ToNot(HaveOccurred()) Expect(bundle.ActionsCalled).To(Equal([]string{"Install"})) }) ItInstallsPkg(act) }) }) Describe("Apply", func() { act := func() error { return applier.Apply(pkg) } It("return an error if getting file bundle fails", func() { packagesBc.GetErr = errors.New("fake-get-bundle-error") err := act() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-get-bundle-error")) }) It("returns an error if checking for package installation fails", func() { bundle.IsInstalledErr = errors.New("fake-is-installed-error") err := act() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-is-installed-error")) }) Context("when package is already installed", func() { BeforeEach(func() { bundle.Installed = true }) It("does not install but only enables package", func() { err := act() Expect(err).ToNot(HaveOccurred()) Expect(bundle.ActionsCalled).To(Equal([]string{"Enable"})) // no Install }) It("returns error when package enable fails", func() { bundle.EnableError = errors.New("fake-enable-error") err := act() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-enable-error")) }) It("does not download the package", func() { err := act() Expect(err).ToNot(HaveOccurred()) Expect(blobstore.GetBlobIDs).To(BeNil()) }) }) Context("when package is not installed", func() { BeforeEach(func() { bundle.Installed = false }) It("installs and enables package", func() { err := act() Expect(err).ToNot(HaveOccurred()) Expect(bundle.ActionsCalled).To(Equal([]string{"Install", "Enable"})) }) It("returns error when package enable fails", func() { bundle.EnableError = errors.New("fake-enable-error") err := act() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-enable-error")) }) ItInstallsPkg(act) }) }) }) Describe("KeepOnly", func() { ItReturnsErrors := func() { It("returns error when bundle collection fails to return list of installed bundles", func() { packagesBc.ListErr = errors.New("fake-bc-list-error") err := applier.KeepOnly([]models.Package{}) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-bc-list-error")) }) It("returns error when bundle collection cannot retrieve bundle for keep-only package", func() { pkg1, bundle1 := buildPkg(packagesBc) packagesBc.ListBundles = []boshbc.Bundle{bundle1} packagesBc.GetErr = errors.New("fake-bc-get-error") err := applier.KeepOnly([]models.Package{pkg1}) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-bc-get-error")) }) It("returns error when at least one bundle cannot be disabled", func() { _, bundle1 := buildPkg(packagesBc) packagesBc.ListBundles = []boshbc.Bundle{bundle1} bundle1.DisableErr = errors.New("fake-bc-disable-error") err := applier.KeepOnly([]models.Package{}) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-bc-disable-error")) }) } Context("when operating on packages as a package owner", func() { BeforeEach(func() { applier = NewConcretePackageApplier(packagesBc, true, blobstore, compressor, fs, logger) }) It("first disables and then uninstalls packages that are not in keeponly list", func() { _, bundle1 := buildPkg(packagesBc) pkg2, bundle2 := buildPkg(packagesBc) _, bundle3 := buildPkg(packagesBc) pkg4, bundle4 := buildPkg(packagesBc) packagesBc.ListBundles = []boshbc.Bundle{bundle1, bundle2, bundle3, bundle4} err := applier.KeepOnly([]models.Package{pkg4, pkg2}) Expect(err).ToNot(HaveOccurred()) Expect(bundle1.ActionsCalled).To(Equal([]string{"Disable", "Uninstall"})) Expect(bundle2.ActionsCalled).To(Equal([]string{})) Expect(bundle3.ActionsCalled).To(Equal([]string{"Disable", "Uninstall"})) Expect(bundle4.ActionsCalled).To(Equal([]string{})) }) ItReturnsErrors() It("returns error when at least one bundle cannot be uninstalled", func() { _, bundle1 := buildPkg(packagesBc) packagesBc.ListBundles = []boshbc.Bundle{bundle1} bundle1.UninstallErr = errors.New("fake-bc-uninstall-error") err := applier.KeepOnly([]models.Package{}) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-bc-uninstall-error")) }) }) Context("when operating on packages not as a package owner", func() { BeforeEach(func() { applier = NewConcretePackageApplier(packagesBc, false, blobstore, compressor, fs, logger) }) It("disables and but does not uninstall packages that are not in keeponly list", func() { _, bundle1 := buildPkg(packagesBc) pkg2, bundle2 := buildPkg(packagesBc) _, bundle3 := buildPkg(packagesBc) pkg4, bundle4 := buildPkg(packagesBc) packagesBc.ListBundles = []boshbc.Bundle{bundle1, bundle2, bundle3, bundle4} err := applier.KeepOnly([]models.Package{pkg4, pkg2}) Expect(err).ToNot(HaveOccurred()) Expect(bundle1.ActionsCalled).To(Equal([]string{"Disable"})) // no Uninstall Expect(bundle2.ActionsCalled).To(Equal([]string{})) Expect(bundle3.ActionsCalled).To(Equal([]string{"Disable"})) // no Uninstall Expect(bundle4.ActionsCalled).To(Equal([]string{})) }) ItReturnsErrors() }) }) }) }
func init() { Describe("concreteManagerProvider", func() { Describe("NewManager", func() { It("returns manager with tasks.json as its tasks path", func() { logger := boshlog.NewLogger(boshlog.LevelNone) fs := fakesys.NewFakeFileSystem() taskInfo := boshtask.TaskInfo{ TaskID: "fake-task-id", Method: "fake-method", Payload: []byte("fake-payload"), } manager := boshtask.NewManagerProvider().NewManager(logger, fs, "/dir/path") err := manager.AddTaskInfo(taskInfo) Expect(err).ToNot(HaveOccurred()) // Check expected file location with another manager otherManager := boshtask.NewManager(logger, fs, "/dir/path/tasks.json") taskInfos, err := otherManager.GetTaskInfos() Expect(err).ToNot(HaveOccurred()) Expect(taskInfos).To(Equal([]boshtask.TaskInfo{taskInfo})) }) }) }) Describe("concreteManager", func() { var ( logger boshlog.Logger fs *fakesys.FakeFileSystem manager boshtask.Manager ) BeforeEach(func() { logger = boshlog.NewLogger(boshlog.LevelNone) fs = fakesys.NewFakeFileSystem() manager = boshtask.NewManager(logger, fs, "/dir/path") }) Describe("GetTaskInfos", func() { It("can load multiple tasks", func() { err := manager.AddTaskInfo(boshtask.TaskInfo{ TaskID: "fake-task-id-1", Method: "fake-method-1", Payload: []byte("fake-payload-1"), }) Expect(err).ToNot(HaveOccurred()) err = manager.AddTaskInfo(boshtask.TaskInfo{ TaskID: "fake-task-id-2", Method: "fake-method-2", Payload: []byte("fake-payload-2"), }) Expect(err).ToNot(HaveOccurred()) // Make sure we are not getting cached copy of taskInfos reloadedManager := boshtask.NewManager(logger, fs, "/dir/path") taskInfos, err := reloadedManager.GetTaskInfos() Expect(err).ToNot(HaveOccurred()) Expect(taskInfos).To(Equal([]boshtask.TaskInfo{ boshtask.TaskInfo{ TaskID: "fake-task-id-1", Method: "fake-method-1", Payload: []byte("fake-payload-1"), }, boshtask.TaskInfo{ TaskID: "fake-task-id-2", Method: "fake-method-2", Payload: []byte("fake-payload-2"), }, })) }) It("succeeds when there is no tasks (file is not present)", func() { taskInfos, err := manager.GetTaskInfos() Expect(err).ToNot(HaveOccurred()) Expect(len(taskInfos)).To(Equal(0)) }) It("returns an error when failing to load tasks from the file that exists", func() { err := manager.AddTaskInfo(boshtask.TaskInfo{ TaskID: "fake-task-id-2", Method: "fake-method-2", Payload: []byte("fake-payload-2"), }) Expect(err).ToNot(HaveOccurred()) fs.ReadFileError = errors.New("fake-read-error") _, err = manager.GetTaskInfos() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-read-error")) }) }) Describe("AddTaskInfo", func() { It("can add multiple tasks", func() { err := manager.AddTaskInfo(boshtask.TaskInfo{ TaskID: "fake-task-id-1", Method: "fake-method-1", Payload: []byte("fake-payload-1"), }) Expect(err).ToNot(HaveOccurred()) err = manager.AddTaskInfo(boshtask.TaskInfo{ TaskID: "fake-task-id-2", Method: "fake-method-2", Payload: []byte("fake-payload-2"), }) Expect(err).ToNot(HaveOccurred()) content, err := fs.ReadFile("/dir/path") Expect(err).ToNot(HaveOccurred()) var decodedMap map[string]boshtask.TaskInfo err = json.Unmarshal(content, &decodedMap) Expect(err).ToNot(HaveOccurred()) Expect(decodedMap).To(Equal(map[string]boshtask.TaskInfo{ "fake-task-id-1": boshtask.TaskInfo{ TaskID: "fake-task-id-1", Method: "fake-method-1", Payload: []byte("fake-payload-1"), }, "fake-task-id-2": boshtask.TaskInfo{ TaskID: "fake-task-id-2", Method: "fake-method-2", Payload: []byte("fake-payload-2"), }, })) }) It("returns an error when failing to save task", func() { fs.WriteToFileError = errors.New("fake-write-error") err := manager.AddTaskInfo(boshtask.TaskInfo{ TaskID: "fake-task-id", Method: "fake-method", Payload: []byte("fake-payload"), }) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-write-error")) }) }) Describe("RemoveTaskInfo", func() { BeforeEach(func() { err := manager.AddTaskInfo(boshtask.TaskInfo{ TaskID: "fake-task-id-1", Method: "fake-method-1", Payload: []byte("fake-payload-1"), }) Expect(err).ToNot(HaveOccurred()) err = manager.AddTaskInfo(boshtask.TaskInfo{ TaskID: "fake-task-id-2", Method: "fake-method-2", Payload: []byte("fake-payload-2"), }) Expect(err).ToNot(HaveOccurred()) }) It("removes the task", func() { err := manager.RemoveTaskInfo("fake-task-id-1") Expect(err).ToNot(HaveOccurred()) content, err := fs.ReadFile("/dir/path") Expect(err).ToNot(HaveOccurred()) var decodedMap map[string]boshtask.TaskInfo err = json.Unmarshal(content, &decodedMap) Expect(err).ToNot(HaveOccurred()) Expect(decodedMap).To(Equal(map[string]boshtask.TaskInfo{ "fake-task-id-2": boshtask.TaskInfo{ TaskID: "fake-task-id-2", Method: "fake-method-2", Payload: []byte("fake-payload-2"), }, })) }) It("does not return error when removing task that does not exist", func() { err := manager.RemoveTaskInfo("fake-unknown-task-id") Expect(err).ToNot(HaveOccurred()) }) It("returns an error when failing to remove task", func() { fs.WriteToFileError = errors.New("fake-write-error") err := manager.RemoveTaskInfo("fake-task-id") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-write-error")) }) }) }) }
func init() { Describe("prepareNetworkChange", func() { var ( action PrepareNetworkChangeAction fs *fakesys.FakeFileSystem settingsService *fakesettings.FakeSettingsService ) BeforeEach(func() { fs = fakesys.NewFakeFileSystem() settingsService = &fakesettings.FakeSettingsService{} action = NewPrepareNetworkChange(fs, settingsService) }) It("is synchronous", func() { Expect(action.IsAsynchronous()).To(BeFalse()) }) It("is not persistent", func() { Expect(action.IsPersistent()).To(BeFalse()) }) It("invalidates settings so that load settings cannot fall back on old settings", func() { resp, err := action.Run() Expect(err).NotTo(HaveOccurred()) Expect(resp).To(Equal("ok")) Expect(settingsService.SettingsWereInvalidated).To(BeTrue()) }) Context("when settings invalidation succeeds", func() { Context("when the network rules file can be removed", func() { It("removes the network rules file", func() { fs.WriteFile("/etc/udev/rules.d/70-persistent-net.rules", []byte{}) resp, err := action.Run() Expect(err).NotTo(HaveOccurred()) Expect(resp).To(Equal("ok")) Expect(fs.FileExists("/etc/udev/rules.d/70-persistent-net.rules")).To(BeFalse()) }) }) Context("when the network rules file cannot be removed", func() { BeforeEach(func() { fs.RemoveAllError = errors.New("fake-remove-all-error") }) It("returns error from removing the network rules file", func() { resp, err := action.Run() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-remove-all-error")) Expect(resp).To(BeNil()) }) }) }) Context("when settings invalidation fails", func() { BeforeEach(func() { settingsService.InvalidateSettingsError = errors.New("fake-invalidate-error") }) It("returns error early if settings err invalidating", func() { resp, err := action.Run() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-invalidate-error")) Expect(resp).To(BeNil()) }) It("does not remove the network rules file", func() { fs.WriteFile("/etc/udev/rules.d/70-persistent-net.rules", []byte{}) action.Run() Expect(fs.FileExists("/etc/udev/rules.d/70-persistent-net.rules")).To(BeTrue()) }) }) }) }
func init() { Describe("Testing with Ginkgo", func() { It("linux format when using swap fs", func() { fakeRunner := fakesys.NewFakeCmdRunner() fakeFs := fakesys.NewFakeFileSystem() fakeRunner.AddCmdResult("blkid -p /dev/xvda1", fakesys.FakeCmdResult{Stdout: `xxxxx TYPE="ext4" yyyy zzzz`}) formatter := NewLinuxFormatter(fakeRunner, fakeFs) formatter.Format("/dev/xvda1", FileSystemSwap) Expect(2).To(Equal(len(fakeRunner.RunCommands))) Expect(fakeRunner.RunCommands[1]).To(Equal([]string{"mkswap", "/dev/xvda1"})) }) It("linux format when using swap fs and partition is swap", func() { fakeRunner := fakesys.NewFakeCmdRunner() fakeFs := fakesys.NewFakeFileSystem() fakeRunner.AddCmdResult("blkid -p /dev/xvda1", fakesys.FakeCmdResult{Stdout: `xxxxx TYPE="swap" yyyy zzzz`}) formatter := NewLinuxFormatter(fakeRunner, fakeFs) formatter.Format("/dev/xvda1", FileSystemSwap) Expect(1).To(Equal(len(fakeRunner.RunCommands))) Expect(fakeRunner.RunCommands[0]).To(Equal([]string{"blkid", "-p", "/dev/xvda1"})) }) It("linux format when using ext4 fs with lazy itable support", func() { fakeRunner := fakesys.NewFakeCmdRunner() fakeFs := fakesys.NewFakeFileSystem() fakeFs.WriteFile("/sys/fs/ext4/features/lazy_itable_init", []byte{}) fakeRunner.AddCmdResult("blkid -p /dev/xvda1", fakesys.FakeCmdResult{Stdout: `xxxxx TYPE="ext2" yyyy zzzz`}) formatter := NewLinuxFormatter(fakeRunner, fakeFs) formatter.Format("/dev/xvda2", FileSystemExt4) Expect(2).To(Equal(len(fakeRunner.RunCommands))) Expect(fakeRunner.RunCommands[1]).To(Equal([]string{"mke2fs", "-t", "ext4", "-j", "-E", "lazy_itable_init=1", "/dev/xvda2"})) }) It("linux format when using ext4 fs without lazy itable support", func() { fakeRunner := fakesys.NewFakeCmdRunner() fakeFs := fakesys.NewFakeFileSystem() fakeRunner.AddCmdResult("blkid -p /dev/xvda1", fakesys.FakeCmdResult{Stdout: `xxxxx TYPE="ext2" yyyy zzzz`}) formatter := NewLinuxFormatter(fakeRunner, fakeFs) formatter.Format("/dev/xvda2", FileSystemExt4) Expect(2).To(Equal(len(fakeRunner.RunCommands))) Expect(fakeRunner.RunCommands[1]).To(Equal([]string{"mke2fs", "-t", "ext4", "-j", "/dev/xvda2"})) }) It("linux format when using ext4 fs and partition is ext4", func() { fakeRunner := fakesys.NewFakeCmdRunner() fakeFs := fakesys.NewFakeFileSystem() fakeRunner.AddCmdResult("blkid -p /dev/xvda1", fakesys.FakeCmdResult{Stdout: `xxxxx TYPE="ext4" yyyy zzzz`}) formatter := NewLinuxFormatter(fakeRunner, fakeFs) formatter.Format("/dev/xvda1", FileSystemExt4) Expect(1).To(Equal(len(fakeRunner.RunCommands))) Expect(fakeRunner.RunCommands[0]).To(Equal([]string{"blkid", "-p", "/dev/xvda1"})) }) }) }
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("README.md")) 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("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)) }) }) }) }) }