Merge pull request #155 from hoegaarden/proper_fake_for_apiserver

Proper fake for apiserver
This commit is contained in:
k8s-ci-robot 2017-12-05 13:28:30 -08:00 committed by GitHub
commit 88e83f4286
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 237 additions and 81 deletions

View File

@ -5,6 +5,8 @@ import (
"os/exec"
"time"
"io"
"github.com/onsi/gomega"
"github.com/onsi/gomega/gbytes"
"github.com/onsi/gomega/gexec"
@ -15,10 +17,11 @@ type APIServer struct {
// The path to the apiserver binary
Path string
EtcdURL string
session *gexec.Session
ProcessStarter simpleSessionStarter
CertDirManager certDirManager
session SimpleSession
stdOut *gbytes.Buffer
stdErr *gbytes.Buffer
certDirManager certDirManager
}
type certDirManager interface {
@ -26,13 +29,29 @@ type certDirManager interface {
Destroy() error
}
//go:generate counterfeiter . certDirManager
func NewAPIServer(pathToAPIServer, etcdURL string) *APIServer {
starter := func(command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
return gexec.Start(command, out, err)
}
apiserver := &APIServer{
Path: pathToAPIServer,
EtcdURL: etcdURL,
ProcessStarter: starter,
CertDirManager: NewTempDirManager(),
}
return apiserver
}
// Start starts the apiserver, waits for it to come up, and returns an error, if occoured.
func (s *APIServer) Start() error {
s.certDirManager = NewTempDirManager()
s.stdOut = gbytes.NewBuffer()
s.stdErr = gbytes.NewBuffer()
certDir, err := s.certDirManager.Create()
certDir, err := s.CertDirManager.Create()
if err != nil {
return err
}
@ -55,7 +74,7 @@ func (s *APIServer) Start() error {
timedOut := time.After(20 * time.Second)
command := exec.Command(s.Path, args...)
s.session, err = gexec.Start(command, s.stdOut, s.stdErr)
s.session, err = s.ProcessStarter(command, s.stdOut, s.stdErr)
if err != nil {
return err
}
@ -71,8 +90,9 @@ func (s *APIServer) Start() error {
// Stop stops this process gracefully, waits for its termination, and cleans up the cert directory.
func (s *APIServer) Stop() {
if s.session != nil {
s.session.Terminate().Wait(20 * time.Second)
err := s.certDirManager.Destroy()
s.session.Terminate()
s.session.Wait(20 * time.Second)
err := s.CertDirManager.Destroy()
gomega.Expect(err).NotTo(gomega.HaveOccurred())
}
}

View File

@ -1,62 +1,106 @@
package test_test
import (
"io"
"os/exec"
. "k8s.io/kubectl/pkg/framework/test"
"fmt"
. "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
fakeCertDirManager *testfakes.FakeCertDirManager
apiServer *APIServer
)
BeforeEach(func() {
fakeSession = &testfakes.FakeSimpleSession{}
fakeCertDirManager = &testfakes.FakeCertDirManager{}
apiServer = &APIServer{
Path: "",
EtcdURL: "the etcd url",
CertDirManager: fakeCertDirManager,
}
})
Context("when given a path to a binary that runs for a long time", func() {
It("can start and stop that binary", func() {
pathToFakeAPIServer, err := gexec.Build("k8s.io/kubectl/pkg/framework/test/assets/fakeapiserver")
Expect(err).NotTo(HaveOccurred())
apiServer := &APIServer{
Path: pathToFakeAPIServer,
EtcdURL: "the etcd url",
sessionBuffer := gbytes.NewBuffer()
fmt.Fprint(sessionBuffer, "Everything is fine")
fakeSession.BufferReturns(sessionBuffer)
fakeSession.ExitCodeReturnsOnCall(0, -1)
fakeSession.ExitCodeReturnsOnCall(1, 143)
apiServer.ProcessStarter = func(command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
fmt.Fprint(err, "Serving insecurely on 127.0.0.1:8080")
return fakeSession, nil
}
By("Starting the API Server")
err = apiServer.Start()
err := apiServer.Start()
Expect(err).NotTo(HaveOccurred())
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))
Expect(fakeCertDirManager.CreateCallCount()).To(Equal(1))
By("Stopping the API Server")
apiServer.Stop()
Expect(apiServer).To(gexec.Exit(143))
})
})
Context("when no path is given", func() {
It("fails with a helpful error", func() {
apiServer := &APIServer{}
err := apiServer.Start()
Expect(err).To(MatchError(ContainSubstring("no such file or directory")))
Expect(fakeSession.TerminateCallCount()).To(Equal(1))
Expect(fakeSession.WaitCallCount()).To(Equal(1))
Expect(fakeSession.ExitCodeCallCount()).To(Equal(2))
Expect(fakeCertDirManager.DestroyCallCount()).To(Equal(1))
})
})
Context("when given a path to a non-executable", func() {
It("fails with a helpful error", func() {
apiServer := &APIServer{
Path: "./apiserver.go",
EtcdURL: "the etcd url",
Context("when the certificate directory cannot be created", func() {
It("propagates the error, and does not start the process", func() {
fakeCertDirManager.CreateReturnsOnCall(0, "", fmt.Errorf("Error on cert directory creation."))
processStarterCounter := 0
apiServer.ProcessStarter = func(Command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
processStarterCounter += 1
return fakeSession, nil
}
err := apiServer.Start()
Expect(err).To(MatchError(ContainSubstring("./apiserver.go: permission denied")))
Expect(err).To(MatchError(ContainSubstring("Error on cert directory creation.")))
Expect(processStarterCounter).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 we try to stop a server that hasn't been started", func() {
It("does not panic", func() {
server := &APIServer{}
server.Stop()
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))
})
})
})

View File

@ -1,44 +0,0 @@
package main
import (
"fmt"
"os"
"regexp"
"time"
)
func main() {
expectedArgs := []*regexp.Regexp{
regexp.MustCompile("^--authorization-mode=Node,RBAC$"),
regexp.MustCompile("^--runtime-config=admissionregistration.k8s.io/v1alpha1$"),
regexp.MustCompile("^--v=3$"),
regexp.MustCompile("^--vmodule=$"),
regexp.MustCompile("^--admission-control=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,SecurityContextDeny,DefaultStorageClass,DefaultTolerationSeconds,GenericAdmissionWebhook,ResourceQuota$"),
regexp.MustCompile("^--admission-control-config-file=$"),
regexp.MustCompile("^--bind-address=0.0.0.0$"),
regexp.MustCompile("^--insecure-bind-address=127.0.0.1$"),
regexp.MustCompile("^--insecure-port=8080$"),
regexp.MustCompile("^--storage-backend=etcd3$"),
regexp.MustCompile("^--etcd-servers=the etcd url$"),
regexp.MustCompile("^--cert-dir=.*"),
}
numExpectedArgs := len(expectedArgs)
numGivenArgs := len(os.Args) - 1
if numGivenArgs < numExpectedArgs {
fmt.Printf("Expected at least %d args, only got %d\n", numExpectedArgs, numGivenArgs)
os.Exit(2)
}
for i, argRegexp := range expectedArgs {
givenArg := os.Args[i+1]
if !argRegexp.MatchString(givenArg) {
fmt.Printf("Expected arg '%s' to match '%s'\n", givenArg, argRegexp.String())
os.Exit(1)
}
}
fmt.Println("Everything is fine")
fmt.Fprintln(os.Stderr, "Serving insecurely on 127.0.0.1:8080")
time.Sleep(10 * time.Minute)
}

View File

@ -22,13 +22,9 @@ type FixtureProcess interface {
func NewFixtures(pathToEtcd, pathToAPIServer string) *Fixtures {
etcdURL := "http://127.0.0.1:2379"
etcd := NewEtcd(pathToEtcd, etcdURL)
return &Fixtures{
Etcd: etcd,
APIServer: &APIServer{
Path: pathToAPIServer,
EtcdURL: etcdURL,
},
Etcd: NewEtcd(pathToEtcd, etcdURL),
APIServer: NewAPIServer(pathToAPIServer, etcdURL),
}
}

View File

@ -0,0 +1,140 @@
// Code generated by counterfeiter. DO NOT EDIT.
package testfakes
import (
"sync"
)
type FakeCertDirManager struct {
CreateStub func() (string, error)
createMutex sync.RWMutex
createArgsForCall []struct{}
createReturns struct {
result1 string
result2 error
}
createReturnsOnCall map[int]struct {
result1 string
result2 error
}
DestroyStub func() error
destroyMutex sync.RWMutex
destroyArgsForCall []struct{}
destroyReturns struct {
result1 error
}
destroyReturnsOnCall map[int]struct {
result1 error
}
invocations map[string][][]interface{}
invocationsMutex sync.RWMutex
}
func (fake *FakeCertDirManager) Create() (string, error) {
fake.createMutex.Lock()
ret, specificReturn := fake.createReturnsOnCall[len(fake.createArgsForCall)]
fake.createArgsForCall = append(fake.createArgsForCall, struct{}{})
fake.recordInvocation("Create", []interface{}{})
fake.createMutex.Unlock()
if fake.CreateStub != nil {
return fake.CreateStub()
}
if specificReturn {
return ret.result1, ret.result2
}
return fake.createReturns.result1, fake.createReturns.result2
}
func (fake *FakeCertDirManager) CreateCallCount() int {
fake.createMutex.RLock()
defer fake.createMutex.RUnlock()
return len(fake.createArgsForCall)
}
func (fake *FakeCertDirManager) CreateReturns(result1 string, result2 error) {
fake.CreateStub = nil
fake.createReturns = struct {
result1 string
result2 error
}{result1, result2}
}
func (fake *FakeCertDirManager) CreateReturnsOnCall(i int, result1 string, result2 error) {
fake.CreateStub = nil
if fake.createReturnsOnCall == nil {
fake.createReturnsOnCall = make(map[int]struct {
result1 string
result2 error
})
}
fake.createReturnsOnCall[i] = struct {
result1 string
result2 error
}{result1, result2}
}
func (fake *FakeCertDirManager) Destroy() error {
fake.destroyMutex.Lock()
ret, specificReturn := fake.destroyReturnsOnCall[len(fake.destroyArgsForCall)]
fake.destroyArgsForCall = append(fake.destroyArgsForCall, struct{}{})
fake.recordInvocation("Destroy", []interface{}{})
fake.destroyMutex.Unlock()
if fake.DestroyStub != nil {
return fake.DestroyStub()
}
if specificReturn {
return ret.result1
}
return fake.destroyReturns.result1
}
func (fake *FakeCertDirManager) DestroyCallCount() int {
fake.destroyMutex.RLock()
defer fake.destroyMutex.RUnlock()
return len(fake.destroyArgsForCall)
}
func (fake *FakeCertDirManager) DestroyReturns(result1 error) {
fake.DestroyStub = nil
fake.destroyReturns = struct {
result1 error
}{result1}
}
func (fake *FakeCertDirManager) DestroyReturnsOnCall(i int, result1 error) {
fake.DestroyStub = nil
if fake.destroyReturnsOnCall == nil {
fake.destroyReturnsOnCall = make(map[int]struct {
result1 error
})
}
fake.destroyReturnsOnCall[i] = struct {
result1 error
}{result1}
}
func (fake *FakeCertDirManager) Invocations() map[string][][]interface{} {
fake.invocationsMutex.RLock()
defer fake.invocationsMutex.RUnlock()
fake.createMutex.RLock()
defer fake.createMutex.RUnlock()
fake.destroyMutex.RLock()
defer fake.destroyMutex.RUnlock()
copiedInvocations := map[string][][]interface{}{}
for key, value := range fake.invocations {
copiedInvocations[key] = value
}
return copiedInvocations
}
func (fake *FakeCertDirManager) recordInvocation(key string, args []interface{}) {
fake.invocationsMutex.Lock()
defer fake.invocationsMutex.Unlock()
if fake.invocations == nil {
fake.invocations = map[string][][]interface{}{}
}
if fake.invocations[key] == nil {
fake.invocations[key] = [][]interface{}{}
}
fake.invocations[key] = append(fake.invocations[key], args)
}