Refactor FixtureProcesses

- Remove the logic from the constructors
- Have start take a configuration map for the fixture processes
- Move the testing on open ports closer to the actual start
This commit is contained in:
Hannes Hörl 2017-12-05 16:05:34 +00:00 committed by Gareth Smith
parent 1513093427
commit de3af899fc
9 changed files with 122 additions and 100 deletions

View File

@ -16,10 +16,8 @@ import (
type APIServer struct { type APIServer struct {
// The path to the apiserver binary // The path to the apiserver binary
Path string Path string
EtcdURL string
ProcessStarter simpleSessionStarter ProcessStarter simpleSessionStarter
CertDirManager certDirManager CertDirManager certDirManager
APIServerURL string
session SimpleSession session SimpleSession
stdOut *gbytes.Buffer stdOut *gbytes.Buffer
stdErr *gbytes.Buffer stdErr *gbytes.Buffer
@ -33,24 +31,22 @@ type certDirManager interface {
//go:generate counterfeiter . certDirManager //go:generate counterfeiter . certDirManager
// NewAPIServer creates a new APIServer Fixture Process // NewAPIServer creates a new APIServer Fixture Process
func NewAPIServer(pathToAPIServer, apiServerURL, etcdURL string) *APIServer { func NewAPIServer(pathToAPIServer string) *APIServer {
starter := func(command *exec.Cmd, out, err io.Writer) (SimpleSession, error) { starter := func(command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
return gexec.Start(command, out, err) return gexec.Start(command, out, err)
} }
apiserver := &APIServer{ apiserver := &APIServer{
Path: pathToAPIServer, Path: pathToAPIServer,
EtcdURL: etcdURL,
ProcessStarter: starter, ProcessStarter: starter,
CertDirManager: NewTempDirManager(), CertDirManager: NewTempDirManager(),
APIServerURL: apiServerURL,
} }
return apiserver return apiserver
} }
// Start starts the apiserver, waits for it to come up, and returns an error, if occoured. // Start starts the apiserver, waits for it to come up, and returns an error, if occoured.
func (s *APIServer) Start() error { func (s *APIServer) Start(config map[string]string) error {
s.stdOut = gbytes.NewBuffer() s.stdOut = gbytes.NewBuffer()
s.stdErr = gbytes.NewBuffer() s.stdErr = gbytes.NewBuffer()
@ -59,7 +55,16 @@ func (s *APIServer) Start() error {
return err return err
} }
url, err := url.Parse(s.APIServerURL) etcdURL, ok := config["etcdURL"]
if !ok {
return fmt.Errorf("config setting 'etcdURL' not found")
}
apiServerURL, ok := config["apiServerURL"]
if !ok {
return fmt.Errorf("config setting 'apiServerURL' not found")
}
url, err := url.Parse(apiServerURL)
if err != nil { if err != nil {
return err return err
} }
@ -72,7 +77,7 @@ func (s *APIServer) Start() error {
"--admission-control-config-file=", "--admission-control-config-file=",
"--bind-address=0.0.0.0", "--bind-address=0.0.0.0",
"--storage-backend=etcd3", "--storage-backend=etcd3",
fmt.Sprintf("--etcd-servers=%s", s.EtcdURL), fmt.Sprintf("--etcd-servers=%s", etcdURL),
fmt.Sprintf("--cert-dir=%s", certDir), fmt.Sprintf("--cert-dir=%s", certDir),
fmt.Sprintf("--insecure-port=%s", url.Port()), fmt.Sprintf("--insecure-port=%s", url.Port()),
fmt.Sprintf("--insecure-bind-address=%s", url.Hostname()), fmt.Sprintf("--insecure-bind-address=%s", url.Hostname()),

View File

@ -20,6 +20,7 @@ var _ = Describe("Apiserver", func() {
fakeSession *testfakes.FakeSimpleSession fakeSession *testfakes.FakeSimpleSession
fakeCertDirManager *testfakes.FakeCertDirManager fakeCertDirManager *testfakes.FakeCertDirManager
apiServer *APIServer apiServer *APIServer
apiServerConfig map[string]string
) )
BeforeEach(func() { BeforeEach(func() {
@ -28,9 +29,13 @@ var _ = Describe("Apiserver", func() {
apiServer = &APIServer{ apiServer = &APIServer{
Path: "", Path: "",
EtcdURL: "the etcd url",
CertDirManager: fakeCertDirManager, CertDirManager: fakeCertDirManager,
} }
apiServerConfig = map[string]string{
"apiServerURL": "http://this.is.the.API.server:8080",
"etcdURL": "http://this.is.etcd:2345/",
}
}) })
Context("when given a path to a binary that runs for a long time", func() { Context("when given a path to a binary that runs for a long time", func() {
@ -43,12 +48,12 @@ var _ = Describe("Apiserver", func() {
fakeSession.ExitCodeReturnsOnCall(1, 143) fakeSession.ExitCodeReturnsOnCall(1, 143)
apiServer.ProcessStarter = func(command *exec.Cmd, out, err io.Writer) (SimpleSession, error) { apiServer.ProcessStarter = func(command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
fmt.Fprint(err, "Serving insecurely on 127.0.0.1:8080") fmt.Fprint(err, "Serving insecurely on this.is.the.API.server:8080")
return fakeSession, nil return fakeSession, nil
} }
By("Starting the API Server") By("Starting the API Server")
err := apiServer.Start() err := apiServer.Start(apiServerConfig)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Eventually(apiServer).Should(gbytes.Say("Everything is fine")) Eventually(apiServer).Should(gbytes.Say("Everything is fine"))
@ -77,7 +82,7 @@ var _ = Describe("Apiserver", func() {
return fakeSession, nil return fakeSession, nil
} }
err := apiServer.Start() err := apiServer.Start(apiServerConfig)
Expect(err).To(MatchError(ContainSubstring("Error on cert directory creation."))) Expect(err).To(MatchError(ContainSubstring("Error on cert directory creation.")))
Expect(processStarterCounter).To(Equal(0)) Expect(processStarterCounter).To(Equal(0))
}) })
@ -89,7 +94,7 @@ var _ = Describe("Apiserver", func() {
return nil, fmt.Errorf("Some error in the apiserver starter.") return nil, fmt.Errorf("Some error in the apiserver starter.")
} }
err := apiServer.Start() err := apiServer.Start(apiServerConfig)
Expect(err).To(MatchError(ContainSubstring("Some error in the apiserver starter."))) Expect(err).To(MatchError(ContainSubstring("Some error in the apiserver starter.")))
}) })
}) })

View File

@ -42,8 +42,7 @@ var _ = BeforeSuite(func() {
Expect(assetsDir).NotTo(BeEmpty(), Expect(assetsDir).NotTo(BeEmpty(),
"Could not determine assets directory (Hint: you can set $KUBE_ASSETS_DIR)") "Could not determine assets directory (Hint: you can set $KUBE_ASSETS_DIR)")
fixtures, err = test.NewFixtures(filepath.Join(assetsDir, "etcd"), filepath.Join(assetsDir, "kube-apiserver")) fixtures = test.NewFixtures(filepath.Join(assetsDir, "etcd"), filepath.Join(assetsDir, "kube-apiserver"))
Expect(err).NotTo(HaveOccurred())
err = fixtures.Start() err = fixtures.Start()
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
}) })

View File

@ -16,8 +16,6 @@ import (
// Etcd knows how to run an etcd server. Set it up with the path to a precompiled binary. // Etcd knows how to run an etcd server. Set it up with the path to a precompiled binary.
type Etcd struct { type Etcd struct {
Path string Path string
EtcdURL string
EtcdPeerURL string
ProcessStarter simpleSessionStarter ProcessStarter simpleSessionStarter
DataDirManager dataDirManager DataDirManager dataDirManager
session SimpleSession session SimpleSession
@ -45,15 +43,13 @@ type SimpleSession interface {
type simpleSessionStarter func(command *exec.Cmd, out, err io.Writer) (SimpleSession, error) type simpleSessionStarter func(command *exec.Cmd, out, err io.Writer) (SimpleSession, error)
// NewEtcd constructs an Etcd Fixture Process // NewEtcd constructs an Etcd Fixture Process
func NewEtcd(pathToEtcd, etcdURL, etcdPeerURL string) *Etcd { func NewEtcd(pathToEtcd string) *Etcd {
starter := func(command *exec.Cmd, out, err io.Writer) (SimpleSession, error) { starter := func(command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
return gexec.Start(command, out, err) return gexec.Start(command, out, err)
} }
etcd := &Etcd{ etcd := &Etcd{
Path: pathToEtcd, Path: pathToEtcd,
EtcdURL: etcdURL,
EtcdPeerURL: etcdPeerURL,
ProcessStarter: starter, ProcessStarter: starter,
DataDirManager: NewTempDirManager(), DataDirManager: NewTempDirManager(),
} }
@ -62,7 +58,7 @@ func NewEtcd(pathToEtcd, etcdURL, etcdPeerURL string) *Etcd {
} }
// Start starts the etcd, waits for it to come up, and returns an error, if occoured. // Start starts the etcd, waits for it to come up, and returns an error, if occoured.
func (e *Etcd) Start() error { func (e *Etcd) Start(config map[string]string) error {
e.stdOut = gbytes.NewBuffer() e.stdOut = gbytes.NewBuffer()
e.stdErr = gbytes.NewBuffer() e.stdErr = gbytes.NewBuffer()
@ -71,19 +67,28 @@ func (e *Etcd) Start() error {
return err return err
} }
clientURL, ok := config["clientURL"]
if !ok {
return fmt.Errorf("config setting 'clientURL' not found")
}
peerURL, ok := config["peerURL"]
if !ok {
return fmt.Errorf("config setting 'peerURL' not found")
}
args := []string{ args := []string{
"--debug", "--debug",
"--advertise-client-urls", "--advertise-client-urls",
e.EtcdURL, clientURL,
"--listen-client-urls", "--listen-client-urls",
e.EtcdURL, clientURL,
"--listen-peer-urls", "--listen-peer-urls",
e.EtcdPeerURL, peerURL,
"--data-dir", "--data-dir",
dataDir, dataDir,
} }
url, err := url.Parse(e.EtcdURL) url, err := url.Parse(clientURL)
if err != nil { if err != nil {
return err return err
} }

View File

@ -20,6 +20,7 @@ var _ = Describe("Etcd", func() {
fakeSession *testfakes.FakeSimpleSession fakeSession *testfakes.FakeSimpleSession
fakeDataDirManager *testfakes.FakeDataDirManager fakeDataDirManager *testfakes.FakeDataDirManager
etcd *Etcd etcd *Etcd
etcdConfig map[string]string
) )
BeforeEach(func() { BeforeEach(func() {
@ -28,9 +29,13 @@ var _ = Describe("Etcd", func() {
etcd = &Etcd{ etcd = &Etcd{
Path: "", Path: "",
EtcdURL: "our etcd url",
DataDirManager: fakeDataDirManager, DataDirManager: fakeDataDirManager,
} }
etcdConfig = map[string]string{
"clientURL": "http://this.is.etcd.listening.for.clients:1234",
"peerURL": "http://this.is.etcd.listening.for.peers:1235",
}
}) })
Context("when given a path to a binary that runs for a long time", func() { Context("when given a path to a binary that runs for a long time", func() {
@ -43,12 +48,12 @@ var _ = Describe("Etcd", func() {
fakeSession.ExitCodeReturnsOnCall(1, 143) fakeSession.ExitCodeReturnsOnCall(1, 143)
etcd.ProcessStarter = func(command *exec.Cmd, out, err io.Writer) (SimpleSession, error) { etcd.ProcessStarter = func(command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
fmt.Fprint(err, "serving insecure client requests on 127.0.0.1:2379") fmt.Fprint(err, "serving insecure client requests on this.is.etcd.listening.for.clients:1234")
return fakeSession, nil return fakeSession, nil
} }
By("Starting the Etcd Server") By("Starting the Etcd Server")
err := etcd.Start() err := etcd.Start(etcdConfig)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Eventually(etcd).Should(gbytes.Say("Everything is dandy")) Eventually(etcd).Should(gbytes.Say("Everything is dandy"))
@ -77,7 +82,7 @@ var _ = Describe("Etcd", func() {
return fakeSession, nil return fakeSession, nil
} }
err := etcd.Start() err := etcd.Start(etcdConfig)
Expect(err).To(MatchError(ContainSubstring("Error on directory creation."))) Expect(err).To(MatchError(ContainSubstring("Error on directory creation.")))
Expect(processStarterCounter).To(Equal(0)) Expect(processStarterCounter).To(Equal(0))
}) })
@ -89,7 +94,7 @@ var _ = Describe("Etcd", func() {
return nil, fmt.Errorf("Some error in the starter.") return nil, fmt.Errorf("Some error in the starter.")
} }
err := etcd.Start() err := etcd.Start(etcdConfig)
Expect(err).To(MatchError(ContainSubstring("Some error in the starter."))) Expect(err).To(MatchError(ContainSubstring("Some error in the starter.")))
}) })
}) })

View File

@ -24,62 +24,71 @@ type FixturesConfig struct {
// This interface is potentially going to be expanded to e.g. allow access to the processes StdOut/StdErr // This interface is potentially going to be expanded to e.g. allow access to the processes StdOut/StdErr
// and other internals. // and other internals.
type FixtureProcess interface { type FixtureProcess interface {
Start() error Start(config map[string]string) error
Stop() Stop()
} }
//go:generate counterfeiter . FixtureProcess //go:generate counterfeiter . FixtureProcess
// NewFixtures will give you a Fixtures struct that's properly wired together. // NewFixtures will give you a Fixtures struct that's properly wired together.
func NewFixtures(pathToEtcd, pathToAPIServer string) (*Fixtures, error) { func NewFixtures(pathToEtcd, pathToAPIServer string) *Fixtures {
urls := map[string]string{
"etcdClients": "",
"etcdPeers": "",
"apiServerClients": "",
}
host := "127.0.0.1"
for name := range urls {
port, err := getFreePort(host)
if err != nil {
return nil, err
}
urls[name] = fmt.Sprintf("http://%s:%d", host, port)
}
fixtures := &Fixtures{ fixtures := &Fixtures{
Etcd: NewEtcd(pathToEtcd, urls["etcdClients"], urls["etcdPeers"]), Etcd: NewEtcd(pathToEtcd),
APIServer: NewAPIServer(pathToAPIServer, urls["apiServerClients"], urls["etcdClients"]), APIServer: NewAPIServer(pathToAPIServer),
} }
fixtures.Config = FixturesConfig{ return fixtures
APIServerURL: urls["apiServerClients"],
}
return fixtures, nil
} }
// Start will start all your fixtures. To stop them, call Stop(). // Start will start all your fixtures. To stop them, call Stop().
func (f *Fixtures) Start() error { func (f *Fixtures) Start() error {
type configs map[string]string
etcdClientURL, err := getHTTPListenURL()
if err != nil {
return err
}
etcdPeerURL, err := getHTTPListenURL()
if err != nil {
return err
}
apiServerURL, err := getHTTPListenURL()
if err != nil {
return err
}
etcdConf := configs{
"peerURL": etcdPeerURL,
"clientURL": etcdClientURL,
}
apiServerConf := configs{
"etcdURL": etcdClientURL,
"apiServerURL": apiServerURL,
}
started := make(chan error) started := make(chan error)
starter := func(process FixtureProcess) { starter := func(process FixtureProcess, conf configs) {
started <- process.Start() started <- process.Start(conf)
} }
processes := []FixtureProcess{ processes := map[FixtureProcess]configs{
f.Etcd, f.Etcd: etcdConf,
f.APIServer, f.APIServer: apiServerConf,
} }
for _, process := range processes { for process, config := range processes {
go starter(process) go starter(process, config)
} }
for pendingProcesses := len(processes); pendingProcesses > 0; pendingProcesses-- { for range processes {
if err := <-started; err != nil { if err := <-started; err != nil {
return err return err
} }
} }
f.Config = FixturesConfig{
APIServerURL: apiServerURL,
}
return nil return nil
} }
@ -90,6 +99,15 @@ func (f *Fixtures) Stop() error {
return nil return nil
} }
func getHTTPListenURL() (url string, err error) {
host := "127.0.0.1"
port, err := getFreePort(host)
if err != nil {
return "", err
}
return fmt.Sprintf("http://%s:%d", host, port), nil
}
func getFreePort(host string) (int, error) { func getFreePort(host string) (int, error) {
addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:0", host)) addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:0", host))
if err != nil { if err != nil {

View File

@ -12,8 +12,7 @@ import (
var _ = Describe("Fixtures", func() { var _ = Describe("Fixtures", func() {
It("can construct a properly wired Fixtures struct", func() { It("can construct a properly wired Fixtures struct", func() {
f, err := NewFixtures("path to etcd", "path to apiserver") f := NewFixtures("path to etcd", "path to apiserver")
Expect(err).NotTo(HaveOccurred())
Expect(f.Etcd.(*Etcd).Path).To(Equal("path to etcd")) Expect(f.Etcd.(*Etcd).Path).To(Equal("path to etcd"))
Expect(f.APIServer.(*APIServer).Path).To(Equal("path to apiserver")) Expect(f.APIServer.(*APIServer).Path).To(Equal("path to apiserver"))
}) })

View File

@ -4,9 +4,8 @@ import (
"net" "net"
"time" "time"
"net/url"
"fmt" "fmt"
"net/url"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
@ -15,34 +14,17 @@ import (
var _ = Describe("The Testing Framework", func() { var _ = Describe("The Testing Framework", func() {
It("Successfully manages the fixtures lifecycle", func() { It("Successfully manages the fixtures lifecycle", func() {
fixtures, err := test.NewFixtures(defaultPathToEtcd, defaultPathToApiserver) fixtures := test.NewFixtures(defaultPathToEtcd, defaultPathToApiserver)
Expect(err).NotTo(HaveOccurred())
By("Starting all the fixture processes") By("Starting all the fixture processes")
err = fixtures.Start() err := fixtures.Start()
Expect(err).NotTo(HaveOccurred(), "Expected fixtures to start successfully") Expect(err).NotTo(HaveOccurred(), "Expected fixtures to start successfully")
var etcdURL, etcdPeerURL, apiServerURL *url.URL apiServerURL, err := url.Parse(fixtures.Config.APIServerURL)
etcd := fixtures.Etcd.(*test.Etcd)
apiServer := fixtures.APIServer.(*test.APIServer)
etcdURL, err = url.Parse(etcd.EtcdURL)
Expect(err).NotTo(HaveOccurred())
etcdPeerURL, err = url.Parse(etcd.EtcdPeerURL)
Expect(err).NotTo(HaveOccurred())
apiServerURL, err = url.Parse(apiServer.APIServerURL)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
isEtcdListening := isSomethingListeningOnPort(etcdURL.Host)
isEtcdPeerListening := isSomethingListeningOnPort(etcdPeerURL.Host)
isAPIServerListening := isSomethingListeningOnPort(apiServerURL.Host) isAPIServerListening := isSomethingListeningOnPort(apiServerURL.Host)
By("Ensuring Etcd is listening")
Expect(isEtcdListening()).To(BeTrue(),
fmt.Sprintf("Expected Etcd to listen on %s", etcdURL.Host))
Expect(isEtcdPeerListening()).To(BeTrue(),
fmt.Sprintf("Expected Etcd to listen for peers on %s", etcdPeerURL.Host))
By("Ensuring APIServer is listening") By("Ensuring APIServer is listening")
Expect(isAPIServerListening()).To(BeTrue(), Expect(isAPIServerListening()).To(BeTrue(),
fmt.Sprintf("Expected APIServer to listen on %s", apiServerURL.Host)) fmt.Sprintf("Expected APIServer to listen on %s", apiServerURL.Host))
@ -51,19 +33,13 @@ var _ = Describe("The Testing Framework", func() {
err = fixtures.Stop() err = fixtures.Stop()
Expect(err).NotTo(HaveOccurred(), "Expected fixtures to stop successfully") Expect(err).NotTo(HaveOccurred(), "Expected fixtures to stop successfully")
By("Ensuring Etcd is not listening anymore")
Expect(isEtcdListening()).To(BeFalse(), "Expected Etcd not to listen anymore")
Expect(isEtcdPeerListening()).To(BeFalse(), "Expected Etcd not to listen for peers anymore")
By("Ensuring APIServer is not listening anymore") By("Ensuring APIServer is not listening anymore")
Expect(isAPIServerListening()).To(BeFalse(), "Expected APIServer not to listen anymore") Expect(isAPIServerListening()).To(BeFalse(), "Expected APIServer not to listen anymore")
}) })
Measure("It should be fast to bring up and tear down the fixtures", func(b Benchmarker) { Measure("It should be fast to bring up and tear down the fixtures", func(b Benchmarker) {
b.Time("lifecycle", func() { b.Time("lifecycle", func() {
fixtures, err := test.NewFixtures(defaultPathToEtcd, defaultPathToApiserver) fixtures := test.NewFixtures(defaultPathToEtcd, defaultPathToApiserver)
Expect(err).NotTo(HaveOccurred())
fixtures.Start() fixtures.Start()
fixtures.Stop() fixtures.Stop()
}) })

View File

@ -8,9 +8,11 @@ import (
) )
type FakeFixtureProcess struct { type FakeFixtureProcess struct {
StartStub func() error StartStub func(config map[string]string) error
startMutex sync.RWMutex startMutex sync.RWMutex
startArgsForCall []struct{} startArgsForCall []struct {
config map[string]string
}
startReturns struct { startReturns struct {
result1 error result1 error
} }
@ -24,14 +26,16 @@ type FakeFixtureProcess struct {
invocationsMutex sync.RWMutex invocationsMutex sync.RWMutex
} }
func (fake *FakeFixtureProcess) Start() error { func (fake *FakeFixtureProcess) Start(config map[string]string) error {
fake.startMutex.Lock() fake.startMutex.Lock()
ret, specificReturn := fake.startReturnsOnCall[len(fake.startArgsForCall)] ret, specificReturn := fake.startReturnsOnCall[len(fake.startArgsForCall)]
fake.startArgsForCall = append(fake.startArgsForCall, struct{}{}) fake.startArgsForCall = append(fake.startArgsForCall, struct {
fake.recordInvocation("Start", []interface{}{}) config map[string]string
}{config})
fake.recordInvocation("Start", []interface{}{config})
fake.startMutex.Unlock() fake.startMutex.Unlock()
if fake.StartStub != nil { if fake.StartStub != nil {
return fake.StartStub() return fake.StartStub(config)
} }
if specificReturn { if specificReturn {
return ret.result1 return ret.result1
@ -45,6 +49,12 @@ func (fake *FakeFixtureProcess) StartCallCount() int {
return len(fake.startArgsForCall) return len(fake.startArgsForCall)
} }
func (fake *FakeFixtureProcess) StartArgsForCall(i int) map[string]string {
fake.startMutex.RLock()
defer fake.startMutex.RUnlock()
return fake.startArgsForCall[i].config
}
func (fake *FakeFixtureProcess) StartReturns(result1 error) { func (fake *FakeFixtureProcess) StartReturns(result1 error) {
fake.StartStub = nil fake.StartStub = nil
fake.startReturns = struct { fake.startReturns = struct {