Make integration test the manifest url feature. Make kubelet's docker pull command testable.

This commit is contained in:
Daniel Smith 2014-06-24 16:31:33 -07:00
parent fd66a8b59b
commit f7968ce00b
5 changed files with 114 additions and 16 deletions

View File

@ -22,6 +22,7 @@ import (
"encoding/json" "encoding/json"
"io/ioutil" "io/ioutil"
"log" "log"
"net/http"
"net/http/httptest" "net/http/httptest"
"time" "time"
@ -34,6 +35,7 @@ import (
) )
func main() { func main() {
manifestUrl := ServeCachedManifestFile()
// Setup // Setup
servers := []string{"http://localhost:4001"} servers := []string{"http://localhost:4001"}
log.Printf("Creating etcd client pointing to %v", servers) log.Printf("Creating etcd client pointing to %v", servers)
@ -48,21 +50,24 @@ func main() {
controllerManager.Run(10 * time.Second) controllerManager.Run(10 * time.Second)
// Kublet // Kublet
fakeDocker := &kubelet.FakeDockerClient{} fakeDocker1 := &kubelet.FakeDockerClient{}
myKubelet := kubelet.Kubelet{ myKubelet := kubelet.Kubelet{
Hostname: machineList[0], Hostname: machineList[0],
DockerClient: fakeDocker, DockerClient: fakeDocker1,
DockerPuller: &kubelet.FakeDockerPuller{},
FileCheckFrequency: 5 * time.Second, FileCheckFrequency: 5 * time.Second,
SyncFrequency: 5 * time.Second, SyncFrequency: 5 * time.Second,
HTTPCheckFrequency: 5 * time.Second, HTTPCheckFrequency: 5 * time.Second,
} }
go myKubelet.RunKubelet("", "https://raw.githubusercontent.com/GoogleCloudPlatform/container-vm-guestbook-redis-python/master/manifest.yaml", servers[0], "localhost", 0) go myKubelet.RunKubelet("", manifestUrl, servers[0], "localhost", 0)
// Create a second kublet so that the guestbook example's two redis slaves both // Create a second kublet so that the guestbook example's two redis slaves both
// have a place they can schedule. // have a place they can schedule.
fakeDocker2 := &kubelet.FakeDockerClient{}
otherKubelet := kubelet.Kubelet{ otherKubelet := kubelet.Kubelet{
Hostname: machineList[1], Hostname: machineList[1],
DockerClient: &kubelet.FakeDockerClient{}, DockerClient: fakeDocker2,
DockerPuller: &kubelet.FakeDockerPuller{},
FileCheckFrequency: 5 * time.Second, FileCheckFrequency: 5 * time.Second,
SyncFrequency: 5 * time.Second, SyncFrequency: 5 * time.Second,
HTTPCheckFrequency: 5 * time.Second, HTTPCheckFrequency: 5 * time.Second,
@ -100,15 +105,57 @@ func main() {
// Using a set to list unique creation attempts. Our fake is // Using a set to list unique creation attempts. Our fake is
// really stupid, so kubelet tries to create these multiple times. // really stupid, so kubelet tries to create these multiple times.
createdPods := map[string]struct{}{} createdPods := map[string]struct{}{}
for _, p := range fakeDocker.Created { for _, p := range fakeDocker1.Created {
// The last 8 characters are random, so slice them off. // The last 8 characters are random, so slice them off.
if n := len(p); n > 8 { if n := len(p); n > 8 {
createdPods[p[:n-8]] = struct{}{} createdPods[p[:n-8]] = struct{}{}
} }
} }
// We expect 3: 1 net container + 1 pod from the replication controller + 1 pod from the URL. for _, p := range fakeDocker2.Created {
if len(createdPods) != 3 { // The last 8 characters are random, so slice them off.
if n := len(p); n > 8 {
createdPods[p[:n-8]] = struct{}{}
}
}
// We expect 5: 2 net containers + 2 pods from the replication controller +
// 1 net container + 2 pods from the URL.
if len(createdPods) != 7 {
log.Fatalf("Unexpected list of created pods: %#v\n", createdPods) log.Fatalf("Unexpected list of created pods: %#v\n", createdPods)
} }
log.Printf("OK") log.Printf("OK")
} }
// Serve a file for kubelet to read.
func ServeCachedManifestFile() (servingAddress string) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/manifest" {
w.Write([]byte(testManifestFile))
return
}
log.Fatalf("Got request: %#v\n", r)
http.NotFound(w, r)
}))
return server.URL + "/manifest"
}
const (
// This is copied from, and should be kept in sync with:
// https://raw.githubusercontent.com/GoogleCloudPlatform/container-vm-guestbook-redis-python/master/manifest.yaml
testManifestFile = `version: v1beta1
containers:
- name: redis
image: dockerfile/redis
volumeMounts:
- name: redis-data
path: /data
- name: guestbook
image: google/guestbook-python-redis
ports:
- name: www
hostPort: 80
containerPort: 80
volumes:
- name: redis-data`
)

View File

@ -22,7 +22,7 @@ fi
# Stop right away if the build fails # Stop right away if the build fails
set -e set -e
$(dirname $0)/build-go.sh $(dirname $0)/build-go.sh integration
ETCD_DIR=$(mktemp -d -t kube-integration.XXXXXX) ETCD_DIR=$(mktemp -d -t kube-integration.XXXXXX)
trap "rm -rf ${ETCD_DIR}" EXIT trap "rm -rf ${ETCD_DIR}" EXIT

View File

@ -67,3 +67,23 @@ func (f *FakeDockerClient) StopContainer(id string, timeout uint) error {
f.stopped = append(f.stopped, id) f.stopped = append(f.stopped, id)
return nil return nil
} }
type FakeDockerPuller struct {
ImagesPulled []string
// Every pull will return the first error here, and then reslice
// to remove it. Will give nil errors if this slice is empty.
ErrorsToInject []error
}
// Records the image pull attempt, and optionally injects an error.
func (f *FakeDockerPuller) Pull(image string) error {
f.ImagesPulled = append(f.ImagesPulled, image)
if n := len(f.ErrorsToInject); n > 0 {
err := f.ErrorsToInject[0]
f.ErrorsToInject = f.ErrorsToInject[:n-1]
return err
}
return nil
}

View File

@ -60,6 +60,11 @@ type DockerInterface interface {
StopContainer(id string, timeout uint) error StopContainer(id string, timeout uint) error
} }
//Interface for testability
type DockerPuller interface {
Pull(image string) error
}
type CadvisorInterface interface { type CadvisorInterface interface {
ContainerInfo(name string) (*info.ContainerInfo, error) ContainerInfo(name string) (*info.ContainerInfo, error)
MachineInfo() (*info.MachineInfo, error) MachineInfo() (*info.MachineInfo, error)
@ -70,6 +75,7 @@ type Kubelet struct {
Hostname string Hostname string
EtcdClient util.EtcdClient EtcdClient util.EtcdClient
DockerClient DockerInterface DockerClient DockerInterface
DockerPuller DockerPuller
CadvisorClient CadvisorInterface CadvisorClient CadvisorInterface
FileCheckFrequency time.Duration FileCheckFrequency time.Duration
SyncFrequency time.Duration SyncFrequency time.Duration
@ -92,6 +98,9 @@ const (
// Starts background goroutines. If config_path, manifest_url, or address are empty, // Starts background goroutines. If config_path, manifest_url, or address are empty,
// they are not watched. Never returns. // they are not watched. Never returns.
func (kl *Kubelet) RunKubelet(config_path, manifest_url, etcd_servers, address string, port uint) { func (kl *Kubelet) RunKubelet(config_path, manifest_url, etcd_servers, address string, port uint) {
if kl.DockerPuller == nil {
kl.DockerPuller = MakeDockerPuller()
}
updateChannel := make(chan manifestUpdate) updateChannel := make(chan manifestUpdate)
if config_path != "" { if config_path != "" {
go util.Forever(func() { kl.WatchFiles(config_path, updateChannel) }, kl.FileCheckFrequency) go util.Forever(func() { kl.WatchFiles(config_path, updateChannel) }, kl.FileCheckFrequency)
@ -216,9 +225,13 @@ func (kl *Kubelet) ListContainers() ([]string, error) {
return result, err return result, err
} }
func (kl *Kubelet) pullImage(image string) error { type dockerPuller struct{}
kl.pullLock.Lock()
defer kl.pullLock.Unlock() func MakeDockerPuller() DockerPuller {
return dockerPuller{}
}
func (dockerPuller) Pull(image string) error {
cmd := exec.Command("docker", "pull", image) cmd := exec.Command("docker", "pull", image)
err := cmd.Start() err := cmd.Start()
if err != nil { if err != nil {
@ -676,7 +689,7 @@ func (kl *Kubelet) createNetworkContainer(manifest *api.ContainerManifest) (stri
Command: []string{"sh", "-c", "rm -f nap && mkfifo nap && exec cat nap"}, Command: []string{"sh", "-c", "rm -f nap && mkfifo nap && exec cat nap"},
Ports: ports, Ports: ports,
} }
kl.pullImage("busybox") kl.DockerPuller.Pull("busybox")
return kl.RunContainer(manifest, container, "") return kl.RunContainer(manifest, container, "")
} }
@ -710,7 +723,7 @@ func (kl *Kubelet) SyncManifests(config []api.ContainerManifest) error {
} }
if !exists { if !exists {
log.Printf("%#v doesn't exist, creating", element) log.Printf("%#v doesn't exist, creating", element)
err = kl.pullImage(element.Image) kl.DockerPuller.Pull(element.Image)
if err != nil { if err != nil {
log.Printf("Error pulling container: %#v", err) log.Printf("Error pulling container: %#v", err)
continue continue

View File

@ -75,9 +75,15 @@ func verifyError(t *testing.T, e error) {
} }
} }
func makeTestKubelet() *Kubelet {
return &Kubelet{
DockerPuller: &FakeDockerPuller{},
}
}
func TestExtractJSON(t *testing.T) { func TestExtractJSON(t *testing.T) {
obj := TestObject{} obj := TestObject{}
kubelet := Kubelet{} kubelet := makeTestKubelet()
data := `{ "name": "foo", "data": { "value": "bar", "number": 10 } }` data := `{ "name": "foo", "data": { "value": "bar", "number": 10 } }`
kubelet.ExtractYAMLData([]byte(data), &obj) kubelet.ExtractYAMLData([]byte(data), &obj)
@ -133,6 +139,7 @@ func TestContainerExists(t *testing.T) {
} }
kubelet := Kubelet{ kubelet := Kubelet{
DockerClient: &fakeDocker, DockerClient: &fakeDocker,
DockerPuller: &FakeDockerPuller{},
} }
manifest := api.ContainerManifest{ manifest := api.ContainerManifest{
Id: "qux", Id: "qux",
@ -176,6 +183,7 @@ func TestGetContainerID(t *testing.T) {
} }
kubelet := Kubelet{ kubelet := Kubelet{
DockerClient: &fakeDocker, DockerClient: &fakeDocker,
DockerPuller: &FakeDockerPuller{},
} }
fakeDocker.containerList = []docker.APIContainers{ fakeDocker.containerList = []docker.APIContainers{
{ {
@ -214,6 +222,7 @@ func TestGetContainerByName(t *testing.T) {
} }
kubelet := Kubelet{ kubelet := Kubelet{
DockerClient: &fakeDocker, DockerClient: &fakeDocker,
DockerPuller: &FakeDockerPuller{},
} }
fakeDocker.containerList = []docker.APIContainers{ fakeDocker.containerList = []docker.APIContainers{
{ {
@ -242,6 +251,7 @@ func TestListContainers(t *testing.T) {
} }
kubelet := Kubelet{ kubelet := Kubelet{
DockerClient: &fakeDocker, DockerClient: &fakeDocker,
DockerPuller: &FakeDockerPuller{},
} }
fakeDocker.containerList = []docker.APIContainers{ fakeDocker.containerList = []docker.APIContainers{
{ {
@ -272,6 +282,7 @@ func TestKillContainerWithError(t *testing.T) {
} }
kubelet := Kubelet{ kubelet := Kubelet{
DockerClient: &fakeDocker, DockerClient: &fakeDocker,
DockerPuller: &FakeDockerPuller{},
} }
err := kubelet.KillContainer("foo") err := kubelet.KillContainer("foo")
verifyError(t, err) verifyError(t, err)
@ -284,6 +295,7 @@ func TestKillContainer(t *testing.T) {
} }
kubelet := Kubelet{ kubelet := Kubelet{
DockerClient: &fakeDocker, DockerClient: &fakeDocker,
DockerPuller: &FakeDockerPuller{},
} }
fakeDocker.containerList = []docker.APIContainers{ fakeDocker.containerList = []docker.APIContainers{
{ {
@ -303,7 +315,7 @@ func TestKillContainer(t *testing.T) {
} }
func TestResponseToContainersNil(t *testing.T) { func TestResponseToContainersNil(t *testing.T) {
kubelet := Kubelet{} kubelet := makeTestKubelet()
list, err := kubelet.ResponseToManifests(&etcd.Response{Node: nil}) list, err := kubelet.ResponseToManifests(&etcd.Response{Node: nil})
if len(list) != 0 { if len(list) != 0 {
t.Errorf("Unexpected non-zero list: %#v", list) t.Errorf("Unexpected non-zero list: %#v", list)
@ -314,7 +326,7 @@ func TestResponseToContainersNil(t *testing.T) {
} }
func TestResponseToManifests(t *testing.T) { func TestResponseToManifests(t *testing.T) {
kubelet := Kubelet{} kubelet := makeTestKubelet()
list, err := kubelet.ResponseToManifests(&etcd.Response{ list, err := kubelet.ResponseToManifests(&etcd.Response{
Node: &etcd.Node{ Node: &etcd.Node{
Value: util.MakeJSONString([]api.ContainerManifest{ Value: util.MakeJSONString([]api.ContainerManifest{
@ -468,6 +480,7 @@ func TestSyncManifestsDoesNothing(t *testing.T) {
} }
kubelet := Kubelet{ kubelet := Kubelet{
DockerClient: &fakeDocker, DockerClient: &fakeDocker,
DockerPuller: &FakeDockerPuller{},
} }
err := kubelet.SyncManifests([]api.ContainerManifest{ err := kubelet.SyncManifests([]api.ContainerManifest{
{ {
@ -510,6 +523,7 @@ func TestSyncManifestsDeletes(t *testing.T) {
} }
kubelet := Kubelet{ kubelet := Kubelet{
DockerClient: &fakeDocker, DockerClient: &fakeDocker,
DockerPuller: &FakeDockerPuller{},
} }
err := kubelet.SyncManifests([]api.ContainerManifest{}) err := kubelet.SyncManifests([]api.ContainerManifest{})
expectNoError(t, err) expectNoError(t, err)
@ -962,6 +976,7 @@ func TestGetContainerStats(t *testing.T) {
kubelet := Kubelet{ kubelet := Kubelet{
DockerClient: &fakeDocker, DockerClient: &fakeDocker,
DockerPuller: &FakeDockerPuller{},
CadvisorClient: mockCadvisor, CadvisorClient: mockCadvisor,
} }
fakeDocker.containerList = []docker.APIContainers{ fakeDocker.containerList = []docker.APIContainers{
@ -990,6 +1005,7 @@ func TestGetContainerStatsWithoutCadvisor(t *testing.T) {
kubelet := Kubelet{ kubelet := Kubelet{
DockerClient: &fakeDocker, DockerClient: &fakeDocker,
DockerPuller: &FakeDockerPuller{},
} }
fakeDocker.containerList = []docker.APIContainers{ fakeDocker.containerList = []docker.APIContainers{
{ {
@ -1027,6 +1043,7 @@ func TestGetContainerStatsWhenCadvisorFailed(t *testing.T) {
kubelet := Kubelet{ kubelet := Kubelet{
DockerClient: &fakeDocker, DockerClient: &fakeDocker,
DockerPuller: &FakeDockerPuller{},
CadvisorClient: mockCadvisor, CadvisorClient: mockCadvisor,
} }
fakeDocker.containerList = []docker.APIContainers{ fakeDocker.containerList = []docker.APIContainers{
@ -1059,6 +1076,7 @@ func TestGetContainerStatsOnNonExistContainer(t *testing.T) {
kubelet := Kubelet{ kubelet := Kubelet{
DockerClient: &fakeDocker, DockerClient: &fakeDocker,
DockerPuller: &FakeDockerPuller{},
CadvisorClient: mockCadvisor, CadvisorClient: mockCadvisor,
} }
fakeDocker.containerList = []docker.APIContainers{} fakeDocker.containerList = []docker.APIContainers{}