311 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			311 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
| package test_test
 | |
| 
 | |
| import (
 | |
| 	"io"
 | |
| 	"os/exec"
 | |
| 
 | |
| 	. "k8s.io/kubectl/pkg/framework/test"
 | |
| 
 | |
| 	"fmt"
 | |
| 
 | |
| 	"time"
 | |
| 
 | |
| 	. "github.com/onsi/ginkgo"
 | |
| 	. "github.com/onsi/gomega"
 | |
| 	"github.com/onsi/gomega/gbytes"
 | |
| 	"github.com/onsi/gomega/gexec"
 | |
| 	"k8s.io/kubectl/pkg/framework/test/testfakes"
 | |
| )
 | |
| 
 | |
| var _ = Describe("Apiserver", func() {
 | |
| 	var (
 | |
| 		fakeSession        *testfakes.FakeSimpleSession
 | |
| 		apiServer          *APIServer
 | |
| 		fakeEtcdProcess    *testfakes.FakeControlPlaneProcess
 | |
| 		fakeAddressManager *testfakes.FakeAddressManager
 | |
| 		apiServerStopper   chan struct{}
 | |
| 		cleanupCallCount   int
 | |
| 	)
 | |
| 
 | |
| 	BeforeEach(func() {
 | |
| 		fakeSession = &testfakes.FakeSimpleSession{}
 | |
| 		fakeEtcdProcess = &testfakes.FakeControlPlaneProcess{}
 | |
| 		fakeAddressManager = &testfakes.FakeAddressManager{}
 | |
| 
 | |
| 		apiServerStopper = make(chan struct{}, 1)
 | |
| 		fakeSession.TerminateReturns(&gexec.Session{
 | |
| 			Exited: apiServerStopper,
 | |
| 		})
 | |
| 		close(apiServerStopper)
 | |
| 
 | |
| 		apiServer = &APIServer{
 | |
| 			AddressManager: fakeAddressManager,
 | |
| 			Path:           "/some/path/to/apiserver",
 | |
| 			CertDir: &Directory{
 | |
| 				Path: "/some/path/to/certdir",
 | |
| 				Cleanup: func() error {
 | |
| 					cleanupCallCount += 1
 | |
| 					return nil
 | |
| 				},
 | |
| 			},
 | |
| 			Etcd:        fakeEtcdProcess,
 | |
| 			StopTimeout: 500 * time.Millisecond,
 | |
| 		}
 | |
| 	})
 | |
| 
 | |
| 	Describe("starting and stopping the server", func() {
 | |
| 		Context("when given a path to a binary that runs for a long time", func() {
 | |
| 			It("can start and stop that binary", func() {
 | |
| 				//TODO Break this long test up
 | |
| 				sessionBuffer := gbytes.NewBuffer()
 | |
| 				fmt.Fprint(sessionBuffer, "Everything is fine")
 | |
| 				fakeSession.BufferReturns(sessionBuffer)
 | |
| 
 | |
| 				fakeSession.ExitCodeReturnsOnCall(0, -1)
 | |
| 				fakeSession.ExitCodeReturnsOnCall(1, 143)
 | |
| 
 | |
| 				fakeAddressManager.InitializeReturns(1234, "this.is.the.API.server", nil)
 | |
| 				fakeEtcdProcess.URLReturns("the etcd url", nil)
 | |
| 
 | |
| 				apiServer.ProcessStarter = func(command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
 | |
| 					Expect(command.Args).To(ContainElement("--insecure-port=1234"))
 | |
| 					Expect(command.Args).To(ContainElement("--insecure-bind-address=this.is.the.API.server"))
 | |
| 					Expect(command.Args).To(ContainElement("--etcd-servers=the etcd url"))
 | |
| 					Expect(command.Args).To(ContainElement("--cert-dir=/some/path/to/certdir"))
 | |
| 					Expect(command.Path).To(Equal("/some/path/to/apiserver"))
 | |
| 					fmt.Fprint(err, "Serving insecurely on this.is.the.API.server:1234")
 | |
| 					return fakeSession, nil
 | |
| 				}
 | |
| 
 | |
| 				By("Starting the API Server")
 | |
| 				err := apiServer.Start()
 | |
| 				Expect(err).NotTo(HaveOccurred())
 | |
| 
 | |
| 				By("...in turn starting Etcd")
 | |
| 				Expect(fakeEtcdProcess.StartCallCount()).To(Equal(1),
 | |
| 					"the Etcd process should be started exactly once")
 | |
| 
 | |
| 				By("...in turn calling the AddressManager")
 | |
| 				Expect(fakeAddressManager.InitializeCallCount()).To(Equal(1))
 | |
| 
 | |
| 				By("...getting the URL of Etcd")
 | |
| 				Expect(fakeEtcdProcess.URLCallCount()).To(Equal(1))
 | |
| 
 | |
| 				Eventually(apiServer).Should(gbytes.Say("Everything is fine"))
 | |
| 				Expect(fakeSession.ExitCodeCallCount()).To(Equal(0))
 | |
| 				Expect(apiServer).NotTo(gexec.Exit())
 | |
| 				Expect(fakeSession.ExitCodeCallCount()).To(Equal(1))
 | |
| 
 | |
| 				By("Stopping the API Server")
 | |
| 				Expect(apiServer.Stop()).To(Succeed())
 | |
| 
 | |
| 				Expect(cleanupCallCount).To(Equal(1))
 | |
| 				Expect(fakeEtcdProcess.StopCallCount()).To(Equal(1))
 | |
| 				Expect(apiServer).To(gexec.Exit(143))
 | |
| 				Expect(fakeSession.TerminateCallCount()).To(Equal(1))
 | |
| 				Expect(fakeSession.ExitCodeCallCount()).To(Equal(2))
 | |
| 			})
 | |
| 		})
 | |
| 
 | |
| 		Context("when the certificate directory cannot be destroyed", func() {
 | |
| 			It("propagates the error", func() {
 | |
| 				apiServer.CertDir.Cleanup = func() error { return fmt.Errorf("destroy failed") }
 | |
| 				fakeAddressManager.InitializeReturns(1234, "this.is.apiserver", nil)
 | |
| 				apiServer.ProcessStarter = func(Command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
 | |
| 					fmt.Fprint(err, "Serving insecurely on this.is.apiserver:1234")
 | |
| 					return fakeSession, nil
 | |
| 				}
 | |
| 
 | |
| 				Expect(apiServer.Start()).To(Succeed())
 | |
| 				err := apiServer.Stop()
 | |
| 				Expect(err).To(MatchError(ContainSubstring("destroy failed")))
 | |
| 			})
 | |
| 		})
 | |
| 
 | |
| 		Context("when there is on function to cleanup the certificate directory", func() {
 | |
| 			It("does not panic", func() {
 | |
| 				apiServer.CertDir.Cleanup = nil
 | |
| 				fakeAddressManager.InitializeReturns(1234, "this.is.apiserver", nil)
 | |
| 				apiServer.ProcessStarter = func(Command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
 | |
| 					fmt.Fprint(err, "Serving insecurely on this.is.apiserver:1234")
 | |
| 					return fakeSession, nil
 | |
| 				}
 | |
| 
 | |
| 				Expect(apiServer.Start()).To(Succeed())
 | |
| 
 | |
| 				var err error
 | |
| 				Expect(func() {
 | |
| 					err = apiServer.Stop()
 | |
| 				}).NotTo(Panic())
 | |
| 				Expect(err).NotTo(HaveOccurred())
 | |
| 			})
 | |
| 		})
 | |
| 
 | |
| 		Context("when etcd cannot be stopped", func() {
 | |
| 			It("propagates the error", func() {
 | |
| 				fakeEtcdProcess.StopReturns(fmt.Errorf("stopping etcd failed"))
 | |
| 				fakeAddressManager.InitializeReturns(1234, "this.is.apiserver", nil)
 | |
| 				apiServer.ProcessStarter = func(Command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
 | |
| 					fmt.Fprint(err, "Serving insecurely on this.is.apiserver:1234")
 | |
| 					return fakeSession, nil
 | |
| 				}
 | |
| 
 | |
| 				Expect(apiServer.Start()).To(Succeed())
 | |
| 				err := apiServer.Stop()
 | |
| 				Expect(err).To(MatchError(ContainSubstring("stopping etcd failed")))
 | |
| 			})
 | |
| 		})
 | |
| 
 | |
| 		Context("when starting etcd fails", func() {
 | |
| 			It("propagates the error, and does not start the process", func() {
 | |
| 				fakeEtcdProcess.StartReturnsOnCall(0, fmt.Errorf("starting etcd failed"))
 | |
| 				apiServer.ProcessStarter = func(Command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
 | |
| 					Expect(true).To(BeFalse(),
 | |
| 						"the api server process starter shouldn't be called if starting etcd fails")
 | |
| 					return nil, nil
 | |
| 				}
 | |
| 
 | |
| 				err := apiServer.Start()
 | |
| 				Expect(err).To(MatchError(ContainSubstring("starting etcd failed")))
 | |
| 			})
 | |
| 		})
 | |
| 
 | |
| 		Context("when getting the URL of Etcd fails", func() {
 | |
| 			It("propagates the error, stop Etcd and keep APIServer down", func() {
 | |
| 				fakeEtcdProcess.URLReturns("", fmt.Errorf("no etcd url"))
 | |
| 
 | |
| 				apiServer.ProcessStarter = func(Command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
 | |
| 					Expect(true).To(BeFalse(),
 | |
| 						"the api server process starter shouldn't be called if getting etcd's URL fails")
 | |
| 					return nil, nil
 | |
| 				}
 | |
| 
 | |
| 				err := apiServer.Start()
 | |
| 				Expect(err).To(MatchError(ContainSubstring("no etcd url")))
 | |
| 				Expect(fakeEtcdProcess.StopCallCount()).To(Equal(1))
 | |
| 			})
 | |
| 
 | |
| 			Context("and stopping of etcd fails too", func() {
 | |
| 				It("propagates the combined error", func() {
 | |
| 					fakeEtcdProcess.URLReturns("", fmt.Errorf("no etcd url"))
 | |
| 					fakeEtcdProcess.StopReturns(fmt.Errorf("stopping etcd failed"))
 | |
| 
 | |
| 					apiServer.ProcessStarter = func(Command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
 | |
| 						Expect(true).To(BeFalse(),
 | |
| 							"the api server process starter shouldn't be called if getting etcd's URL fails")
 | |
| 						return nil, nil
 | |
| 					}
 | |
| 
 | |
| 					err := apiServer.Start()
 | |
| 					Expect(err).To(MatchError(ContainSubstring("no etcd url")))
 | |
| 					Expect(err).To(MatchError(ContainSubstring("stopping etcd failed")))
 | |
| 					Expect(fakeEtcdProcess.StopCallCount()).To(Equal(1))
 | |
| 				})
 | |
| 			})
 | |
| 		})
 | |
| 
 | |
| 		Context("when the address manager fails to get a new address", func() {
 | |
| 			It("propagates the error and does not start any process", func() {
 | |
| 				fakeAddressManager.InitializeReturns(0, "", fmt.Errorf("some error finding a free port"))
 | |
| 
 | |
| 				apiServer.ProcessStarter = func(Command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
 | |
| 					Expect(true).To(BeFalse(),
 | |
| 						"the api server process starter shouldn't be called if getting a free port fails")
 | |
| 					return nil, nil
 | |
| 				}
 | |
| 
 | |
| 				Expect(apiServer.Start()).To(MatchError(ContainSubstring("some error finding a free port")))
 | |
| 				Expect(fakeEtcdProcess.StartCallCount()).To(Equal(0))
 | |
| 			})
 | |
| 		})
 | |
| 
 | |
| 		Context("when the starter returns an error", func() {
 | |
| 			It("propagates the error", func() {
 | |
| 				apiServer.ProcessStarter = func(command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
 | |
| 					return nil, fmt.Errorf("Some error in the apiserver starter.")
 | |
| 				}
 | |
| 
 | |
| 				err := apiServer.Start()
 | |
| 				Expect(err).To(MatchError(ContainSubstring("Some error in the apiserver starter.")))
 | |
| 			})
 | |
| 		})
 | |
| 
 | |
| 		Context("when the starter takes longer than our timeout", func() {
 | |
| 			It("gives us a timeout error", func() {
 | |
| 				apiServer.StartTimeout = 1 * time.Nanosecond
 | |
| 				apiServer.ProcessStarter = func(command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
 | |
| 					return &gexec.Session{}, nil
 | |
| 				}
 | |
| 
 | |
| 				err := apiServer.Start()
 | |
| 				Expect(err).To(MatchError(ContainSubstring("timeout waiting for apiserver to start serving")))
 | |
| 			})
 | |
| 		})
 | |
| 
 | |
| 		Context("when we try to stop a server that hasn't been started", func() {
 | |
| 			It("is a noop and does not call exit on the session", func() {
 | |
| 				apiServer.ProcessStarter = func(command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
 | |
| 					return fakeSession, nil
 | |
| 				}
 | |
| 				apiServer.Stop()
 | |
| 				Expect(fakeSession.ExitCodeCallCount()).To(Equal(0))
 | |
| 			})
 | |
| 		})
 | |
| 
 | |
| 		Context("when Stop() times out", func() {
 | |
| 			JustBeforeEach(func() {
 | |
| 				apiServerStopperWillNeverBeUsed := make(chan struct{}, 1)
 | |
| 				fakeSession.TerminateReturns(&gexec.Session{
 | |
| 					Exited: apiServerStopperWillNeverBeUsed,
 | |
| 				})
 | |
| 			})
 | |
| 			It("propagates the error", func() {
 | |
| 				fakeAddressManager.InitializeReturns(1234, "this.is.apiserver", nil)
 | |
| 				apiServer.ProcessStarter = func(Command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
 | |
| 					fmt.Fprint(err, "Serving insecurely on this.is.apiserver:1234")
 | |
| 					return fakeSession, nil
 | |
| 				}
 | |
| 
 | |
| 				Expect(apiServer.Start()).To(Succeed())
 | |
| 				err := apiServer.Stop()
 | |
| 				Expect(err).To(MatchError(ContainSubstring("timeout")))
 | |
| 			})
 | |
| 		})
 | |
| 	})
 | |
| 
 | |
| 	Describe("querying the server for its URL", func() {
 | |
| 		It("can be queried for the URL it listens on", func() {
 | |
| 			fakeAddressManager.HostReturns("the.host.for.api.server", nil)
 | |
| 			fakeAddressManager.PortReturns(5678, nil)
 | |
| 			apiServerURL, err := apiServer.URL()
 | |
| 			Expect(err).NotTo(HaveOccurred())
 | |
| 			Expect(apiServerURL).To(Equal("http://the.host.for.api.server:5678"))
 | |
| 		})
 | |
| 		Context("and so the addressmanager fails to give us a port", func() {
 | |
| 			It("propagates the failure", func() {
 | |
| 				fakeAddressManager.PortReturns(0, fmt.Errorf("boom"))
 | |
| 				_, err := apiServer.URL()
 | |
| 				Expect(err).To(MatchError(ContainSubstring("boom")))
 | |
| 			})
 | |
| 		})
 | |
| 		Context("and so the addressmanager fails to give us a host", func() {
 | |
| 			It("propagates the failure", func() {
 | |
| 				fakeAddressManager.HostReturns("", fmt.Errorf("biff!"))
 | |
| 				_, err := apiServer.URL()
 | |
| 				Expect(err).To(MatchError(ContainSubstring("biff!")))
 | |
| 			})
 | |
| 		})
 | |
| 		Context("before starting the server", func() {
 | |
| 			Context("and therefore the addressmanager has not been initialized", func() {
 | |
| 				BeforeEach(func() {
 | |
| 					apiServer = &APIServer{}
 | |
| 				})
 | |
| 				It("gives a sane error", func() {
 | |
| 					_, err := apiServer.URL()
 | |
| 					Expect(err).To(MatchError(ContainSubstring("not initialized")))
 | |
| 				})
 | |
| 			})
 | |
| 		})
 | |
| 	})
 | |
| })
 |