diff --git a/cmd/integration/integration.go b/cmd/integration/integration.go index 845b6363b3..fc4114f959 100644 --- a/cmd/integration/integration.go +++ b/cmd/integration/integration.go @@ -26,39 +26,55 @@ import ( "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/controller" - "github.com/GoogleCloudPlatform/kubernetes/pkg/registry" + "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet" + "github.com/GoogleCloudPlatform/kubernetes/pkg/master" "github.com/coreos/go-etcd/etcd" ) func main() { - // Setup servers := []string{"http://localhost:4001"} log.Printf("Creating etcd client pointing to %v", servers) - etcdClient := etcd.NewClient(servers) - machineList := registry.MakeMinionRegistry([]string{"machine"}) + machineList := []string{"localhost", "machine"} - reg := registry.MakeEtcdRegistry(etcdClient, machineList) + // Master + m := master.New(servers, machineList, nil) + apiserver := httptest.NewServer(m.ConstructHandler("/api/v1beta1")) - apiserver := apiserver.New(map[string]apiserver.RESTStorage{ - "pods": registry.MakePodRegistryStorage(reg, &client.FakeContainerInfo{}, registry.MakeRoundRobinScheduler(machineList), nil, nil), - "replicationControllers": registry.MakeControllerRegistryStorage(reg), - }, "/api/v1beta1") - server := httptest.NewServer(apiserver) - - controllerManager := controller.MakeReplicationManager(etcd.NewClient(servers), client.New(server.URL, nil)) + controllerManager := controller.MakeReplicationManager(etcd.NewClient(servers), client.New(apiserver.URL, nil)) controllerManager.Run(10 * time.Second) + // Kublet + fakeDocker := &kubelet.FakeDockerClient{} + my_kubelet := kubelet.Kubelet{ + Hostname: machineList[0], + DockerClient: fakeDocker, + FileCheckFrequency: 5 * time.Second, + SyncFrequency: 5 * time.Second, + HTTPCheckFrequency: 5 * time.Second, + } + go my_kubelet.RunKubelet("", "https://raw.githubusercontent.com/GoogleCloudPlatform/container-vm-guestbook-redis-python/master/manifest.yaml", servers[0], "localhost", 0) + + // Create a second kublet so that the guestbook example's two redis slaves both + // have a place they can schedule. + other_kubelet := kubelet.Kubelet{ + Hostname: machineList[1], + DockerClient: &kubelet.FakeDockerClient{}, + FileCheckFrequency: 5 * time.Second, + SyncFrequency: 5 * time.Second, + HTTPCheckFrequency: 5 * time.Second, + } + go other_kubelet.RunKubelet("", "", servers[0], "localhost", 0) + // Ok. we're good to go. - log.Printf("API Server started on %s", server.URL) + log.Printf("API Server started on %s", apiserver.URL) // Wait for the synchronization threads to come up. time.Sleep(time.Second * 10) - kubeClient := client.New(server.URL, nil) + kubeClient := client.New(apiserver.URL, nil) data, err := ioutil.ReadFile("api/examples/controller.json") if err != nil { log.Fatalf("Unexpected error: %#v", err) @@ -79,5 +95,21 @@ func main() { if err != nil || len(pods.Items) != 2 { log.Fatal("FAILED") } + + // Check that kubelet tried to make the pods. + // Using a set to list unique creation attempts. Our fake is + // really stupid, so kubelet tries to create these multiple times. + createdPods := map[string]struct{}{} + for _, p := range fakeDocker.Created { + // The last 8 characters are random, so slice them off. + n := len(p) + if n > 8 { + createdPods[p[:n-8]] = struct{}{} + } + } + // We expect 3: 1 net container + 1 pod from the replication controller + 1 pod from the URL. + if len(createdPods) != 3 { + log.Fatalf("Unexpected list of created pods: %#v\n", createdPods) + } log.Printf("OK") } diff --git a/pkg/kubelet/fake_docker_client.go b/pkg/kubelet/fake_docker_client.go new file mode 100644 index 0000000000..f736c412e7 --- /dev/null +++ b/pkg/kubelet/fake_docker_client.go @@ -0,0 +1,69 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kubelet + +import ( + "github.com/fsouza/go-dockerclient" +) + +// A simple fake docker client, so that kublet can be run for testing without requiring a real docker setup. +type FakeDockerClient struct { + containerList []docker.APIContainers + container *docker.Container + err error + called []string + stopped []string + Created []string +} + +func (f *FakeDockerClient) clearCalls() { + f.called = []string{} +} + +func (f *FakeDockerClient) appendCall(call string) { + f.called = append(f.called, call) +} + +func (f *FakeDockerClient) ListContainers(options docker.ListContainersOptions) ([]docker.APIContainers, error) { + f.appendCall("list") + return f.containerList, f.err +} + +func (f *FakeDockerClient) InspectContainer(id string) (*docker.Container, error) { + f.appendCall("inspect") + return f.container, f.err +} + +func (f *FakeDockerClient) CreateContainer(c docker.CreateContainerOptions) (*docker.Container, error) { + f.appendCall("create") + f.Created = append(f.Created, c.Name) + // This is not a very good fake. We'll just add this container's name to the list. + // Docker likes to add a '/', so copy that behavior. + f.containerList = append(f.containerList, docker.APIContainers{ID: c.Name, Names: []string{"/" + c.Name}}) + return &docker.Container{ID: "/" + c.Name}, nil +} + +func (f *FakeDockerClient) StartContainer(id string, hostConfig *docker.HostConfig) error { + f.appendCall("start") + return nil +} + +func (f *FakeDockerClient) StopContainer(id string, timeout uint) error { + f.appendCall("stop") + f.stopped = append(f.stopped, id) + return nil +} diff --git a/pkg/kubelet/kubelet_test.go b/pkg/kubelet/kubelet_test.go index 46edd1ee40..39e6044865 100644 --- a/pkg/kubelet/kubelet_test.go +++ b/pkg/kubelet/kubelet_test.go @@ -86,48 +86,6 @@ func TestExtractJSON(t *testing.T) { verifyIntEquals(t, obj.Data.Number, 10) } -type FakeDockerClient struct { - containerList []docker.APIContainers - container *docker.Container - err error - called []string - stopped []string -} - -func (f *FakeDockerClient) clearCalls() { - f.called = []string{} -} - -func (f *FakeDockerClient) appendCall(call string) { - f.called = append(f.called, call) -} - -func (f *FakeDockerClient) ListContainers(options docker.ListContainersOptions) ([]docker.APIContainers, error) { - f.appendCall("list") - return f.containerList, f.err -} - -func (f *FakeDockerClient) InspectContainer(id string) (*docker.Container, error) { - f.appendCall("inspect") - return f.container, f.err -} - -func (f *FakeDockerClient) CreateContainer(docker.CreateContainerOptions) (*docker.Container, error) { - f.appendCall("create") - return nil, nil -} - -func (f *FakeDockerClient) StartContainer(id string, hostConfig *docker.HostConfig) error { - f.appendCall("start") - return nil -} - -func (f *FakeDockerClient) StopContainer(id string, timeout uint) error { - f.appendCall("stop") - f.stopped = append(f.stopped, id) - return nil -} - func verifyCalls(t *testing.T, fakeDocker FakeDockerClient, calls []string) { verifyStringArrayEquals(t, fakeDocker.called, calls) } diff --git a/pkg/master/master.go b/pkg/master/master.go index d27cbbddfd..4cfdbf5254 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -98,3 +98,12 @@ func (m *Master) Run(myAddress, apiPrefix string) error { } return s.ListenAndServe() } + +// Instead of calling Run, call ConstructHandler to get a handler for your own +// server. Intended for testing. Only call once. +func (m *Master) ConstructHandler(apiPrefix string) http.Handler { + endpoints := registry.MakeEndpointController(m.serviceRegistry, m.podRegistry) + go util.Forever(func() { endpoints.SyncServiceEndpoints() }, time.Second*10) + + return apiserver.New(m.storage, apiPrefix) +}