Make APIServer manage its own port allocations
This means we will no longer need to pass a free port into the APIServer constructor.
This commit is contained in:
parent
d62ff04228
commit
657963b319
|
|
@ -3,7 +3,6 @@ package test
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -14,6 +13,7 @@ import (
|
||||||
|
|
||||||
// APIServer knows how to run a kubernetes apiserver. Set it up with the path to a precompiled binary.
|
// APIServer knows how to run a kubernetes apiserver. Set it up with the path to a precompiled binary.
|
||||||
type APIServer struct {
|
type APIServer struct {
|
||||||
|
AddressManager AddressManager
|
||||||
PathFinder BinPathFinder
|
PathFinder BinPathFinder
|
||||||
ProcessStarter simpleSessionStarter
|
ProcessStarter simpleSessionStarter
|
||||||
CertDirManager certDirManager
|
CertDirManager certDirManager
|
||||||
|
|
@ -52,7 +52,10 @@ func NewAPIServer(config *APIServerConfig) (*APIServer, error) {
|
||||||
|
|
||||||
// URL returns the URL APIServer is listening on. Clients can use this to connect to APIServer.
|
// URL returns the URL APIServer is listening on. Clients can use this to connect to APIServer.
|
||||||
func (s *APIServer) URL() string {
|
func (s *APIServer) URL() string {
|
||||||
return s.Config.APIServerURL
|
// TODO handle errors
|
||||||
|
port, _ := s.AddressManager.Port()
|
||||||
|
host, _ := s.AddressManager.Host()
|
||||||
|
return fmt.Sprintf("http://%s:%d", host, port)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
|
|
@ -60,28 +63,31 @@ func (s *APIServer) Start() error {
|
||||||
if s.PathFinder == nil {
|
if s.PathFinder == nil {
|
||||||
s.PathFinder = DefaultBinPathFinder
|
s.PathFinder = DefaultBinPathFinder
|
||||||
}
|
}
|
||||||
|
if s.AddressManager == nil {
|
||||||
|
s.AddressManager = &DefaultAddressManager{}
|
||||||
|
}
|
||||||
if err := s.Config.Validate(); err != nil {
|
if err := s.Config.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.Etcd.Start()
|
port, addr, err := s.AddressManager.Initialize("localhost")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.stdOut = gbytes.NewBuffer()
|
|
||||||
s.stdErr = gbytes.NewBuffer()
|
|
||||||
|
|
||||||
certDir, err := s.CertDirManager.Create()
|
certDir, err := s.CertDirManager.Create()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
clientURL, err := url.Parse(s.Config.APIServerURL)
|
err = s.Etcd.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.stdOut = gbytes.NewBuffer()
|
||||||
|
s.stdErr = gbytes.NewBuffer()
|
||||||
|
|
||||||
args := []string{
|
args := []string{
|
||||||
"--authorization-mode=Node,RBAC",
|
"--authorization-mode=Node,RBAC",
|
||||||
"--runtime-config=admissionregistration.k8s.io/v1alpha1",
|
"--runtime-config=admissionregistration.k8s.io/v1alpha1",
|
||||||
|
|
@ -92,11 +98,11 @@ func (s *APIServer) Start() error {
|
||||||
"--storage-backend=etcd3",
|
"--storage-backend=etcd3",
|
||||||
fmt.Sprintf("--etcd-servers=%s", s.Etcd.URL()),
|
fmt.Sprintf("--etcd-servers=%s", s.Etcd.URL()),
|
||||||
fmt.Sprintf("--cert-dir=%s", certDir),
|
fmt.Sprintf("--cert-dir=%s", certDir),
|
||||||
fmt.Sprintf("--insecure-port=%s", clientURL.Port()),
|
fmt.Sprintf("--insecure-port=%d", port),
|
||||||
fmt.Sprintf("--insecure-bind-address=%s", clientURL.Hostname()),
|
fmt.Sprintf("--insecure-bind-address=%s", addr),
|
||||||
}
|
}
|
||||||
|
|
||||||
detectedStart := s.stdErr.Detect(fmt.Sprintf("Serving insecurely on %s", clientURL.Host))
|
detectedStart := s.stdErr.Detect(fmt.Sprintf("Serving insecurely on %s:%d", addr, port))
|
||||||
timedOut := time.After(20 * time.Second)
|
timedOut := time.After(20 * time.Second)
|
||||||
|
|
||||||
command := exec.Command(s.PathFinder("kube-apiserver"), args...)
|
command := exec.Command(s.PathFinder("kube-apiserver"), args...)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import "github.com/asaskevich/govalidator"
|
||||||
|
|
||||||
// APIServerConfig is a struct holding data to configure the API Server process
|
// APIServerConfig is a struct holding data to configure the API Server process
|
||||||
type APIServerConfig struct {
|
type APIServerConfig struct {
|
||||||
APIServerURL string `valid:"required,url"`
|
APIServerURL string `valid:"url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate checks that the config contains only valid URLs
|
// Validate checks that the config contains only valid URLs
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,6 @@ var _ = Describe("APIServerConfig", func() {
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
})
|
})
|
||||||
|
|
||||||
It("errors on empty config", func() {
|
|
||||||
conf := &APIServerConfig{}
|
|
||||||
err := conf.Validate()
|
|
||||||
Expect(err).To(MatchError(ContainSubstring("APIServerURL: non zero value required")))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("errors on malformed URLs", func() {
|
It("errors on malformed URLs", func() {
|
||||||
conf := &APIServerConfig{
|
conf := &APIServerConfig{
|
||||||
APIServerURL: "something not URLish",
|
APIServerURL: "something not URLish",
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ var _ = Describe("Apiserver", func() {
|
||||||
apiServerConfig *APIServerConfig
|
apiServerConfig *APIServerConfig
|
||||||
fakeEtcdProcess *testfakes.FakeFixtureProcess
|
fakeEtcdProcess *testfakes.FakeFixtureProcess
|
||||||
fakePathFinder *testfakes.FakeBinPathFinder
|
fakePathFinder *testfakes.FakeBinPathFinder
|
||||||
|
fakeAddressManager *testfakes.FakeAddressManager
|
||||||
)
|
)
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
|
|
@ -30,11 +31,13 @@ var _ = Describe("Apiserver", func() {
|
||||||
fakeCertDirManager = &testfakes.FakeCertDirManager{}
|
fakeCertDirManager = &testfakes.FakeCertDirManager{}
|
||||||
fakeEtcdProcess = &testfakes.FakeFixtureProcess{}
|
fakeEtcdProcess = &testfakes.FakeFixtureProcess{}
|
||||||
fakePathFinder = &testfakes.FakeBinPathFinder{}
|
fakePathFinder = &testfakes.FakeBinPathFinder{}
|
||||||
|
fakeAddressManager = &testfakes.FakeAddressManager{}
|
||||||
|
|
||||||
apiServerConfig = &APIServerConfig{
|
apiServerConfig = &APIServerConfig{
|
||||||
APIServerURL: "http://this.is.the.API.server:8080",
|
APIServerURL: "",
|
||||||
}
|
}
|
||||||
apiServer = &APIServer{
|
apiServer = &APIServer{
|
||||||
|
AddressManager: fakeAddressManager,
|
||||||
PathFinder: fakePathFinder.Spy,
|
PathFinder: fakePathFinder.Spy,
|
||||||
CertDirManager: fakeCertDirManager,
|
CertDirManager: fakeCertDirManager,
|
||||||
Config: apiServerConfig,
|
Config: apiServerConfig,
|
||||||
|
|
@ -42,104 +45,150 @@ var _ = Describe("Apiserver", func() {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
It("can be queried for the URL it listens on", func() {
|
Describe("starting and stopping the server", func() {
|
||||||
Expect(apiServer.URL()).To(Equal("http://this.is.the.API.server:8080"))
|
Context("when given a path to a binary that runs for a long time", func() {
|
||||||
})
|
It("can start and stop that binary", func() {
|
||||||
|
sessionBuffer := gbytes.NewBuffer()
|
||||||
|
fmt.Fprint(sessionBuffer, "Everything is fine")
|
||||||
|
fakeSession.BufferReturns(sessionBuffer)
|
||||||
|
|
||||||
Context("when given a path to a binary that runs for a long time", func() {
|
fakeSession.ExitCodeReturnsOnCall(0, -1)
|
||||||
It("can start and stop that binary", func() {
|
fakeSession.ExitCodeReturnsOnCall(1, 143)
|
||||||
sessionBuffer := gbytes.NewBuffer()
|
|
||||||
fmt.Fprint(sessionBuffer, "Everything is fine")
|
|
||||||
fakeSession.BufferReturns(sessionBuffer)
|
|
||||||
|
|
||||||
fakeSession.ExitCodeReturnsOnCall(0, -1)
|
fakePathFinder.Returns("/some/path/to/apiserver")
|
||||||
fakeSession.ExitCodeReturnsOnCall(1, 143)
|
fakeAddressManager.InitializeReturns(1234, "this.is.the.API.server", nil)
|
||||||
|
|
||||||
fakePathFinder.Returns("/some/path/to/apiserver")
|
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.Path).To(Equal("/some/path/to/apiserver"))
|
||||||
|
fmt.Fprint(err, "Serving insecurely on this.is.the.API.server:1234")
|
||||||
|
return fakeSession, nil
|
||||||
|
}
|
||||||
|
|
||||||
apiServer.ProcessStarter = func(command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
|
By("Starting the API Server")
|
||||||
Expect(command.Path).To(Equal("/some/path/to/apiserver"))
|
err := apiServer.Start()
|
||||||
fmt.Fprint(err, "Serving insecurely on this.is.the.API.server:8080")
|
Expect(err).NotTo(HaveOccurred())
|
||||||
return fakeSession, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
By("Starting the API Server")
|
By("...in turn starting Etcd")
|
||||||
err := apiServer.Start()
|
Expect(fakeEtcdProcess.StartCallCount()).To(Equal(1),
|
||||||
Expect(err).NotTo(HaveOccurred())
|
"the Etcd process should be started exactly once")
|
||||||
|
|
||||||
By("starting Etcd")
|
By("...in turn calling the PathFinder")
|
||||||
Expect(fakeEtcdProcess.StartCallCount()).To(Equal(1),
|
Expect(fakePathFinder.CallCount()).To(Equal(1))
|
||||||
"the Etcd process should be started exactly once")
|
Expect(fakePathFinder.ArgsForCall(0)).To(Equal("kube-apiserver"))
|
||||||
|
|
||||||
By("...in turn calls the PathFinder")
|
By("...in turn calling the PortFinder")
|
||||||
Expect(fakePathFinder.CallCount()).To(Equal(1))
|
Expect(fakeAddressManager.InitializeCallCount()).To(Equal(1))
|
||||||
Expect(fakePathFinder.ArgsForCall(0)).To(Equal("kube-apiserver"))
|
Expect(fakeAddressManager.InitializeArgsForCall(0)).To(Equal("localhost"))
|
||||||
|
|
||||||
Eventually(apiServer).Should(gbytes.Say("Everything is fine"))
|
Eventually(apiServer).Should(gbytes.Say("Everything is fine"))
|
||||||
Expect(fakeSession.ExitCodeCallCount()).To(Equal(0))
|
Expect(fakeSession.ExitCodeCallCount()).To(Equal(0))
|
||||||
Expect(apiServer).NotTo(gexec.Exit())
|
Expect(apiServer).NotTo(gexec.Exit())
|
||||||
Expect(fakeSession.ExitCodeCallCount()).To(Equal(1))
|
Expect(fakeSession.ExitCodeCallCount()).To(Equal(1))
|
||||||
Expect(fakeCertDirManager.CreateCallCount()).To(Equal(1))
|
Expect(fakeCertDirManager.CreateCallCount()).To(Equal(1))
|
||||||
|
|
||||||
By("Stopping the API Server")
|
By("Stopping the API Server")
|
||||||
apiServer.Stop()
|
apiServer.Stop()
|
||||||
|
|
||||||
Expect(fakeEtcdProcess.StopCallCount()).To(Equal(1))
|
Expect(fakeEtcdProcess.StopCallCount()).To(Equal(1))
|
||||||
Expect(apiServer).To(gexec.Exit(143))
|
Expect(apiServer).To(gexec.Exit(143))
|
||||||
Expect(fakeSession.TerminateCallCount()).To(Equal(1))
|
Expect(fakeSession.TerminateCallCount()).To(Equal(1))
|
||||||
Expect(fakeSession.WaitCallCount()).To(Equal(1))
|
Expect(fakeSession.WaitCallCount()).To(Equal(1))
|
||||||
Expect(fakeSession.ExitCodeCallCount()).To(Equal(2))
|
Expect(fakeSession.ExitCodeCallCount()).To(Equal(2))
|
||||||
Expect(fakeCertDirManager.DestroyCallCount()).To(Equal(1))
|
Expect(fakeCertDirManager.DestroyCallCount()).To(Equal(1))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
Context("when starting etcd fails", func() {
|
Context("when starting etcd fails", func() {
|
||||||
It("propagates the error, and does not start the process", func() {
|
It("propagates the error, and does not start the process", func() {
|
||||||
fakeEtcdProcess.StartReturnsOnCall(0, fmt.Errorf("starting etcd failed"))
|
fakeEtcdProcess.StartReturnsOnCall(0, fmt.Errorf("starting etcd failed"))
|
||||||
apiServer.ProcessStarter = func(Command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
|
apiServer.ProcessStarter = func(Command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
|
||||||
Expect(true).To(BeFalse(),
|
Expect(true).To(BeFalse(),
|
||||||
"the api server process starter shouldn't be called if starting etcd fails")
|
"the api server process starter shouldn't be called if starting etcd fails")
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err := apiServer.Start()
|
err := apiServer.Start()
|
||||||
Expect(err).To(MatchError(ContainSubstring("starting etcd failed")))
|
Expect(err).To(MatchError(ContainSubstring("starting etcd failed")))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
Context("when the certificate directory cannot be created", func() {
|
Context("when the certificate directory cannot be created", func() {
|
||||||
It("propagates the error, and does not start the process", func() {
|
It("propagates the error, and does not start any process", func() {
|
||||||
fakeCertDirManager.CreateReturnsOnCall(0, "", fmt.Errorf("Error on cert directory creation."))
|
fakeCertDirManager.CreateReturnsOnCall(0, "", fmt.Errorf("Error on cert directory creation."))
|
||||||
|
|
||||||
apiServer.ProcessStarter = func(Command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
|
apiServer.ProcessStarter = func(Command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
|
||||||
Expect(true).To(BeFalse(),
|
Expect(true).To(BeFalse(),
|
||||||
"the api server process starter shouldn't be called if creating the cert dir fails")
|
"the api server process starter shouldn't be called if creating the cert dir fails")
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err := apiServer.Start()
|
err := apiServer.Start()
|
||||||
Expect(err).To(MatchError(ContainSubstring("Error on cert directory creation.")))
|
Expect(err).To(MatchError(ContainSubstring("Error on cert directory creation.")))
|
||||||
|
Expect(fakeEtcdProcess.StartCallCount()).To(Equal(0))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
Context("when the starter returns an error", func() {
|
Context("when the address manager fails to get a new address", func() {
|
||||||
It("propagates the error", func() {
|
It("propagates the error and does not start any process", func() {
|
||||||
apiServer.ProcessStarter = func(command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
|
fakeAddressManager.InitializeReturns(0, "", fmt.Errorf("some error finding a free port"))
|
||||||
return nil, fmt.Errorf("Some error in the apiserver starter.")
|
|
||||||
}
|
|
||||||
|
|
||||||
err := apiServer.Start()
|
apiServer.ProcessStarter = func(Command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
|
||||||
Expect(err).To(MatchError(ContainSubstring("Some error in the apiserver starter.")))
|
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 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 we try to stop a server that hasn't been started", func() {
|
Describe("querying the server for its URL", func() {
|
||||||
It("is a noop and does not call exit on the session", func() {
|
It("can be queried for the URL it listens on", func() {
|
||||||
apiServer.ProcessStarter = func(command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
|
fakeAddressManager.HostReturns("the.host.for.api.server", nil)
|
||||||
return fakeSession, nil
|
fakeAddressManager.PortReturns(5678, nil)
|
||||||
}
|
Expect(apiServer.URL()).To(Equal("http://the.host.for.api.server:5678"))
|
||||||
apiServer.Stop()
|
})
|
||||||
Expect(fakeSession.ExitCodeCallCount()).To(Equal(0))
|
|
||||||
|
Context("when we query for the URL before starting the server", func() {
|
||||||
|
|
||||||
|
Context("and so the addressmanager fails to give us a port", func() {
|
||||||
|
It("propagates the failure", func() {
|
||||||
|
//TODO
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("and so the addressmanager fails to give us a host", func() {
|
||||||
|
It("propagates the failure", func() {
|
||||||
|
//TODO
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,70 @@ import (
|
||||||
"net"
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// AddressManager knows how to generate and remember a single address on some
|
||||||
|
// local interface for a service to listen on.
|
||||||
|
type AddressManager interface {
|
||||||
|
Initialize(host string) (port int, resolvedAddress string, err error)
|
||||||
|
Host() (string, error)
|
||||||
|
Port() (int, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate counterfeiter . AddressManager
|
||||||
|
|
||||||
|
// DefaultAddressManager implements an AddressManager. It allocates a new address
|
||||||
|
// (interface & port) a process can bind and keeps track of that.
|
||||||
|
type DefaultAddressManager struct {
|
||||||
|
port int
|
||||||
|
host string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize returns a address a process can listen on. Given a hostname it returns an address,
|
||||||
|
// a tuple consisting of a free port and the hostname resolved to its IP.
|
||||||
|
func (d *DefaultAddressManager) Initialize(host string) (port int, resolvedHost string, err error) {
|
||||||
|
if d.port != 0 {
|
||||||
|
return 0, "", fmt.Errorf("this DefaultAddressManager is already initialized")
|
||||||
|
}
|
||||||
|
addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:0", host))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l, err := net.ListenTCP("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d.port = l.Addr().(*net.TCPAddr).Port
|
||||||
|
defer func() {
|
||||||
|
err = l.Close()
|
||||||
|
}()
|
||||||
|
d.host = addr.IP.String()
|
||||||
|
return d.port, d.host, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Port returns the port that this DefaultAddressManager is managing. Port returns an
|
||||||
|
// error if this DefaultAddressManager has not yet been initialized.
|
||||||
|
func (d *DefaultAddressManager) Port() (int, error) {
|
||||||
|
if d.port == 0 {
|
||||||
|
return 0, fmt.Errorf("this DefaultAdressManager has is not initialized yet")
|
||||||
|
}
|
||||||
|
return d.port, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Host returns the host that this DefaultAddressManager is managing. Host returns an
|
||||||
|
// error if this DefaultAddressManager has not yet been initialized.
|
||||||
|
func (d *DefaultAddressManager) Host() (string, error) {
|
||||||
|
if d.host == "" {
|
||||||
|
return "", fmt.Errorf("this DefaultAdressManager has is not initialized yet")
|
||||||
|
}
|
||||||
|
return d.host, nil
|
||||||
|
}
|
||||||
|
|
||||||
// PortFinder is the signature of a function returning a free port and a resolved
|
// PortFinder is the signature of a function returning a free port and a resolved
|
||||||
// address to listen/bind on, and an erro in case there were some problems finding
|
// address to listen/bind on, and an erro in case there were some problems finding
|
||||||
// a free pair of address & port
|
// a free pair of address & port
|
||||||
type PortFinder func(host string) (port int, resolvedAddress string, err error)
|
type PortFinder func(host string) (port int, resolvedAddress string, err error)
|
||||||
|
|
||||||
|
//go:generate counterfeiter . PortFinder
|
||||||
|
|
||||||
// DefaultPortFinder is the default implementation of PortFinder: It resolves that
|
// DefaultPortFinder is the default implementation of PortFinder: It resolves that
|
||||||
// hostname or IP handed in and asks the kernel for a random port. To make that a bit
|
// hostname or IP handed in and asks the kernel for a random port. To make that a bit
|
||||||
// safer, it also tries to bind to that port to make sure it is not in use. If all goes
|
// safer, it also tries to bind to that port to make sure it is not in use. If all goes
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,90 @@ package test_test
|
||||||
import (
|
import (
|
||||||
. "k8s.io/kubectl/pkg/framework/test"
|
. "k8s.io/kubectl/pkg/framework/test"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var _ = Describe("DefaultAddressManager", func() {
|
||||||
|
var defaultAddressManager *DefaultAddressManager
|
||||||
|
BeforeEach(func() {
|
||||||
|
defaultAddressManager = &DefaultAddressManager{}
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("Initialize", func() {
|
||||||
|
It("returns a free port and an address to bind to", func() {
|
||||||
|
port, host, err := defaultAddressManager.Initialize("localhost")
|
||||||
|
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(host).To(Equal("127.0.0.1"))
|
||||||
|
Expect(port).NotTo(Equal(0))
|
||||||
|
|
||||||
|
addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", host, port))
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
l, err := net.ListenTCP("tcp", addr)
|
||||||
|
defer func() {
|
||||||
|
Expect(l.Close()).To(Succeed())
|
||||||
|
}()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when given an invalid hostname", func() {
|
||||||
|
It("propagates the error", func() {
|
||||||
|
_, _, err := defaultAddressManager.Initialize("this is not a hostname")
|
||||||
|
|
||||||
|
Expect(err).To(MatchError(ContainSubstring("no such host")))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when given a hostname that we don't have permission to listen on", func() {
|
||||||
|
It("propagates the error", func() {
|
||||||
|
_, _, err := defaultAddressManager.Initialize("example.com")
|
||||||
|
|
||||||
|
Expect(err).To(MatchError(ContainSubstring("bind: can't assign requested address")))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("initialized multiple times", func() {
|
||||||
|
It("fails", func() {
|
||||||
|
_, _, err := defaultAddressManager.Initialize("localhost")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
_, _, err = defaultAddressManager.Initialize("localhost")
|
||||||
|
Expect(err).To(MatchError(ContainSubstring("already initialized")))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Describe("Port", func() {
|
||||||
|
It("returns an error if Initialize has not been called yet", func() {
|
||||||
|
_, err := defaultAddressManager.Port()
|
||||||
|
Expect(err).To(MatchError(ContainSubstring("not initialized yet")))
|
||||||
|
})
|
||||||
|
It("returns the same port as previously allocated by Initialize", func() {
|
||||||
|
expectedPort, _, err := defaultAddressManager.Initialize("localhost")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
actualPort, err := defaultAddressManager.Port()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(actualPort).To(Equal(expectedPort))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Describe("Host", func() {
|
||||||
|
It("returns an error if Initialize has not been called yet", func() {
|
||||||
|
_, err := defaultAddressManager.Host()
|
||||||
|
Expect(err).To(MatchError(ContainSubstring("not initialized yet")))
|
||||||
|
})
|
||||||
|
It("returns the same port as previously allocated by Initialize", func() {
|
||||||
|
_, expectedHost, err := defaultAddressManager.Initialize("localhost")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
actualHost, err := defaultAddressManager.Host()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(actualHost).To(Equal(expectedHost))
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
var _ = Describe("DefaultPortFinder", func() {
|
var _ = Describe("DefaultPortFinder", func() {
|
||||||
It("returns a free port and an address to bind to", func() {
|
It("returns a free port and an address to bind to", func() {
|
||||||
port, addr, err := DefaultPortFinder("127.0.0.1")
|
port, addr, err := DefaultPortFinder("127.0.0.1")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,220 @@
|
||||||
|
// Code generated by counterfeiter. DO NOT EDIT.
|
||||||
|
package testfakes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"k8s.io/kubectl/pkg/framework/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FakeAddressManager struct {
|
||||||
|
InitializeStub func(host string) (port int, resolvedAddress string, err error)
|
||||||
|
initializeMutex sync.RWMutex
|
||||||
|
initializeArgsForCall []struct {
|
||||||
|
host string
|
||||||
|
}
|
||||||
|
initializeReturns struct {
|
||||||
|
result1 int
|
||||||
|
result2 string
|
||||||
|
result3 error
|
||||||
|
}
|
||||||
|
initializeReturnsOnCall map[int]struct {
|
||||||
|
result1 int
|
||||||
|
result2 string
|
||||||
|
result3 error
|
||||||
|
}
|
||||||
|
HostStub func() (string, error)
|
||||||
|
hostMutex sync.RWMutex
|
||||||
|
hostArgsForCall []struct{}
|
||||||
|
hostReturns struct {
|
||||||
|
result1 string
|
||||||
|
result2 error
|
||||||
|
}
|
||||||
|
hostReturnsOnCall map[int]struct {
|
||||||
|
result1 string
|
||||||
|
result2 error
|
||||||
|
}
|
||||||
|
PortStub func() (int, error)
|
||||||
|
portMutex sync.RWMutex
|
||||||
|
portArgsForCall []struct{}
|
||||||
|
portReturns struct {
|
||||||
|
result1 int
|
||||||
|
result2 error
|
||||||
|
}
|
||||||
|
portReturnsOnCall map[int]struct {
|
||||||
|
result1 int
|
||||||
|
result2 error
|
||||||
|
}
|
||||||
|
invocations map[string][][]interface{}
|
||||||
|
invocationsMutex sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fake *FakeAddressManager) Initialize(host string) (port int, resolvedAddress string, err error) {
|
||||||
|
fake.initializeMutex.Lock()
|
||||||
|
ret, specificReturn := fake.initializeReturnsOnCall[len(fake.initializeArgsForCall)]
|
||||||
|
fake.initializeArgsForCall = append(fake.initializeArgsForCall, struct {
|
||||||
|
host string
|
||||||
|
}{host})
|
||||||
|
fake.recordInvocation("Initialize", []interface{}{host})
|
||||||
|
fake.initializeMutex.Unlock()
|
||||||
|
if fake.InitializeStub != nil {
|
||||||
|
return fake.InitializeStub(host)
|
||||||
|
}
|
||||||
|
if specificReturn {
|
||||||
|
return ret.result1, ret.result2, ret.result3
|
||||||
|
}
|
||||||
|
return fake.initializeReturns.result1, fake.initializeReturns.result2, fake.initializeReturns.result3
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fake *FakeAddressManager) InitializeCallCount() int {
|
||||||
|
fake.initializeMutex.RLock()
|
||||||
|
defer fake.initializeMutex.RUnlock()
|
||||||
|
return len(fake.initializeArgsForCall)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fake *FakeAddressManager) InitializeArgsForCall(i int) string {
|
||||||
|
fake.initializeMutex.RLock()
|
||||||
|
defer fake.initializeMutex.RUnlock()
|
||||||
|
return fake.initializeArgsForCall[i].host
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fake *FakeAddressManager) InitializeReturns(result1 int, result2 string, result3 error) {
|
||||||
|
fake.InitializeStub = nil
|
||||||
|
fake.initializeReturns = struct {
|
||||||
|
result1 int
|
||||||
|
result2 string
|
||||||
|
result3 error
|
||||||
|
}{result1, result2, result3}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fake *FakeAddressManager) InitializeReturnsOnCall(i int, result1 int, result2 string, result3 error) {
|
||||||
|
fake.InitializeStub = nil
|
||||||
|
if fake.initializeReturnsOnCall == nil {
|
||||||
|
fake.initializeReturnsOnCall = make(map[int]struct {
|
||||||
|
result1 int
|
||||||
|
result2 string
|
||||||
|
result3 error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fake.initializeReturnsOnCall[i] = struct {
|
||||||
|
result1 int
|
||||||
|
result2 string
|
||||||
|
result3 error
|
||||||
|
}{result1, result2, result3}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fake *FakeAddressManager) Host() (string, error) {
|
||||||
|
fake.hostMutex.Lock()
|
||||||
|
ret, specificReturn := fake.hostReturnsOnCall[len(fake.hostArgsForCall)]
|
||||||
|
fake.hostArgsForCall = append(fake.hostArgsForCall, struct{}{})
|
||||||
|
fake.recordInvocation("Host", []interface{}{})
|
||||||
|
fake.hostMutex.Unlock()
|
||||||
|
if fake.HostStub != nil {
|
||||||
|
return fake.HostStub()
|
||||||
|
}
|
||||||
|
if specificReturn {
|
||||||
|
return ret.result1, ret.result2
|
||||||
|
}
|
||||||
|
return fake.hostReturns.result1, fake.hostReturns.result2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fake *FakeAddressManager) HostCallCount() int {
|
||||||
|
fake.hostMutex.RLock()
|
||||||
|
defer fake.hostMutex.RUnlock()
|
||||||
|
return len(fake.hostArgsForCall)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fake *FakeAddressManager) HostReturns(result1 string, result2 error) {
|
||||||
|
fake.HostStub = nil
|
||||||
|
fake.hostReturns = struct {
|
||||||
|
result1 string
|
||||||
|
result2 error
|
||||||
|
}{result1, result2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fake *FakeAddressManager) HostReturnsOnCall(i int, result1 string, result2 error) {
|
||||||
|
fake.HostStub = nil
|
||||||
|
if fake.hostReturnsOnCall == nil {
|
||||||
|
fake.hostReturnsOnCall = make(map[int]struct {
|
||||||
|
result1 string
|
||||||
|
result2 error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fake.hostReturnsOnCall[i] = struct {
|
||||||
|
result1 string
|
||||||
|
result2 error
|
||||||
|
}{result1, result2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fake *FakeAddressManager) Port() (int, error) {
|
||||||
|
fake.portMutex.Lock()
|
||||||
|
ret, specificReturn := fake.portReturnsOnCall[len(fake.portArgsForCall)]
|
||||||
|
fake.portArgsForCall = append(fake.portArgsForCall, struct{}{})
|
||||||
|
fake.recordInvocation("Port", []interface{}{})
|
||||||
|
fake.portMutex.Unlock()
|
||||||
|
if fake.PortStub != nil {
|
||||||
|
return fake.PortStub()
|
||||||
|
}
|
||||||
|
if specificReturn {
|
||||||
|
return ret.result1, ret.result2
|
||||||
|
}
|
||||||
|
return fake.portReturns.result1, fake.portReturns.result2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fake *FakeAddressManager) PortCallCount() int {
|
||||||
|
fake.portMutex.RLock()
|
||||||
|
defer fake.portMutex.RUnlock()
|
||||||
|
return len(fake.portArgsForCall)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fake *FakeAddressManager) PortReturns(result1 int, result2 error) {
|
||||||
|
fake.PortStub = nil
|
||||||
|
fake.portReturns = struct {
|
||||||
|
result1 int
|
||||||
|
result2 error
|
||||||
|
}{result1, result2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fake *FakeAddressManager) PortReturnsOnCall(i int, result1 int, result2 error) {
|
||||||
|
fake.PortStub = nil
|
||||||
|
if fake.portReturnsOnCall == nil {
|
||||||
|
fake.portReturnsOnCall = make(map[int]struct {
|
||||||
|
result1 int
|
||||||
|
result2 error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fake.portReturnsOnCall[i] = struct {
|
||||||
|
result1 int
|
||||||
|
result2 error
|
||||||
|
}{result1, result2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fake *FakeAddressManager) Invocations() map[string][][]interface{} {
|
||||||
|
fake.invocationsMutex.RLock()
|
||||||
|
defer fake.invocationsMutex.RUnlock()
|
||||||
|
fake.initializeMutex.RLock()
|
||||||
|
defer fake.initializeMutex.RUnlock()
|
||||||
|
fake.hostMutex.RLock()
|
||||||
|
defer fake.hostMutex.RUnlock()
|
||||||
|
fake.portMutex.RLock()
|
||||||
|
defer fake.portMutex.RUnlock()
|
||||||
|
copiedInvocations := map[string][][]interface{}{}
|
||||||
|
for key, value := range fake.invocations {
|
||||||
|
copiedInvocations[key] = value
|
||||||
|
}
|
||||||
|
return copiedInvocations
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fake *FakeAddressManager) 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ test.AddressManager = new(FakeAddressManager)
|
||||||
Loading…
Reference in New Issue