func TestPluginLegacy(t *testing.T) { plugMgr := volume.VolumePluginMgr{} plugMgr.InitPlugins(ProbeVolumePlugins(), volume.NewFakeVolumeHost("/tmp/fake", nil, nil)) plug, err := plugMgr.FindPluginByName("gce-pd") if err != nil { t.Errorf("Can't find the plugin by name") } if plug.Name() != "gce-pd" { t.Errorf("Wrong name: %s", plug.Name()) } if plug.CanSupport(&volume.Spec{Name: "foo", VolumeSource: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{}}}) { t.Errorf("Expected false") } spec := &api.Volume{VolumeSource: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{}}} pod := &api.Pod{ObjectMeta: api.ObjectMeta{UID: types.UID("poduid")}} if _, err := plug.NewBuilder(volume.NewSpecFromVolume(spec), pod, volume.VolumeOptions{""}, nil); err == nil { t.Errorf("Expected failiure") } cleaner, err := plug.NewCleaner("vol1", types.UID("poduid"), nil) if err != nil { t.Errorf("Failed to make a new Cleaner: %v", err) } if cleaner == nil { t.Errorf("Got a nil Cleaner") } }
func TestForgetNonExistingPodWorkers(t *testing.T) { podWorkers, _ := createPodWorkers() numPods := 20 for i := 0; i < numPods; i++ { podWorkers.UpdatePod(newPod(string(i), "name"), nil, func() {}) } drainWorkers(podWorkers, numPods) if len(podWorkers.podUpdates) != numPods { t.Errorf("Incorrect number of open channels %v", len(podWorkers.podUpdates)) } desiredPods := map[types.UID]empty{} desiredPods[types.UID(2)] = empty{} desiredPods[types.UID(14)] = empty{} podWorkers.ForgetNonExistingPodWorkers(desiredPods) if len(podWorkers.podUpdates) != 2 { t.Errorf("Incorrect number of open channels %v", len(podWorkers.podUpdates)) } if _, exists := podWorkers.podUpdates[types.UID(2)]; !exists { t.Errorf("No updates channel for pod 2") } if _, exists := podWorkers.podUpdates[types.UID(14)]; !exists { t.Errorf("No updates channel for pod 14") } podWorkers.ForgetNonExistingPodWorkers(map[types.UID]empty{}) if len(podWorkers.podUpdates) != 0 { t.Errorf("Incorrect number of open channels %v", len(podWorkers.podUpdates)) } }
func TestPlugin(t *testing.T) { plug := makePluginUnderTest(t, "qingyuan/empty-dir") spec := &api.Volume{ Name: "vol1", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{Medium: api.StorageMediumDefault}}, } mounter := mount.FakeMounter{} mountDetector := fakeMountDetector{} pod := &api.Pod{ObjectMeta: api.ObjectMeta{UID: types.UID("poduid")}} builder, err := plug.(*emptyDirPlugin).newBuilderInternal(volume.NewSpecFromVolume(spec), pod, &mounter, &mountDetector, volume.VolumeOptions{""}) if err != nil { t.Errorf("Failed to make a new Builder: %v", err) } if builder == nil { t.Errorf("Got a nil Builder") } volPath := builder.GetPath() if volPath != path.Join(basePath, "pods/poduid/volumes/qingyuan~empty-dir/vol1") { t.Errorf("Got unexpected path: %s", volPath) } if err := builder.SetUp(); err != nil { t.Errorf("Expected success, got: %v", err) } if _, err := os.Stat(volPath); err != nil { if os.IsNotExist(err) { t.Errorf("SetUp() failed, volume path not created: %s", volPath) } else { t.Errorf("SetUp() failed: %v", err) } } if len(mounter.Log) != 0 { t.Errorf("Expected 0 mounter calls, got %#v", mounter.Log) } mounter.ResetLog() cleaner, err := plug.(*emptyDirPlugin).newCleanerInternal("vol1", types.UID("poduid"), &mounter, &fakeMountDetector{}) if err != nil { t.Errorf("Failed to make a new Cleaner: %v", err) } if cleaner == nil { t.Errorf("Got a nil Cleaner") } if err := cleaner.TearDown(); err != nil { t.Errorf("Expected success, got: %v", err) } if _, err := os.Stat(volPath); err == nil { t.Errorf("TearDown() failed, volume path still exists: %s", volPath) } else if !os.IsNotExist(err) { t.Errorf("SetUp() failed: %v", err) } if len(mounter.Log) != 0 { t.Errorf("Expected 0 mounter calls, got %#v", mounter.Log) } mounter.ResetLog() }
func TestPlugin(t *testing.T) { plugMgr := volume.VolumePluginMgr{} plugMgr.InitPlugins(ProbeVolumePlugins(), newTestHost(t)) plug, err := plugMgr.FindPluginByName("qingyuan/git-repo") if err != nil { t.Errorf("Can't find the plugin by name") } spec := &api.Volume{ Name: "vol1", VolumeSource: api.VolumeSource{ GitRepo: &api.GitRepoVolumeSource{ Repository: "https://github.com/qingyuancloud/QingYuan.git", Revision: "2a30ce65c5ab586b98916d83385c5983edd353a1", }, }, } pod := &api.Pod{ObjectMeta: api.ObjectMeta{UID: types.UID("poduid")}} builder, err := plug.NewBuilder(volume.NewSpecFromVolume(spec), pod, volume.VolumeOptions{""}, mount.New()) if err != nil { t.Errorf("Failed to make a new Builder: %v", err) } if builder == nil { t.Errorf("Got a nil Builder") } path := builder.GetPath() if !strings.HasSuffix(path, "pods/poduid/volumes/qingyuan~git-repo/vol1") { t.Errorf("Got unexpected path: %s", path) } testSetUp(plug, builder, t) if _, err := os.Stat(path); err != nil { if os.IsNotExist(err) { t.Errorf("SetUp() failed, volume path not created: %s", path) } else { t.Errorf("SetUp() failed: %v", err) } } cleaner, err := plug.NewCleaner("vol1", types.UID("poduid"), mount.New()) if err != nil { t.Errorf("Failed to make a new Cleaner: %v", err) } if cleaner == nil { t.Errorf("Got a nil Cleaner") } if err := cleaner.TearDown(); err != nil { t.Errorf("Expected success, got: %v", err) } if _, err := os.Stat(path); err == nil { t.Errorf("TearDown() failed, volume path still exists: %s", path) } else if !os.IsNotExist(err) { t.Errorf("SetUp() failed: %v", err) } }
func doTestPlugin(t *testing.T, spec *volume.Spec) { plugMgr := volume.VolumePluginMgr{} plugMgr.InitPlugins(ProbeVolumePlugins(), volume.NewFakeVolumeHost("/tmp/fake", nil, nil)) plug, err := plugMgr.FindPluginByName("qingyuan/rbd") if err != nil { t.Errorf("Can't find the plugin by name") } builder, err := plug.(*RBDPlugin).newBuilderInternal(spec, types.UID("poduid"), &fakeDiskManager{}, &mount.FakeMounter{}, "secrets") if err != nil { t.Errorf("Failed to make a new Builder: %v", err) } if builder == nil { t.Errorf("Got a nil Builder: %v") } path := builder.GetPath() if path != "/tmp/fake/pods/poduid/volumes/qingyuan~rbd/vol1" { t.Errorf("Got unexpected path: %s", path) } if err := builder.SetUp(); err != nil { t.Errorf("Expected success, got: %v", err) } if _, err := os.Stat(path); err != nil { if os.IsNotExist(err) { t.Errorf("SetUp() failed, volume path not created: %s", path) } else { t.Errorf("SetUp() failed: %v", err) } } if _, err := os.Stat(path); err != nil { if os.IsNotExist(err) { t.Errorf("SetUp() failed, volume path not created: %s", path) } else { t.Errorf("SetUp() failed: %v", err) } } cleaner, err := plug.(*RBDPlugin).newCleanerInternal("vol1", types.UID("poduid"), &fakeDiskManager{}, &mount.FakeMounter{}) if err != nil { t.Errorf("Failed to make a new Cleaner: %v", err) } if cleaner == nil { t.Errorf("Got a nil Cleaner: %v") } if err := cleaner.TearDown(); err != nil { t.Errorf("Expected success, got: %v", err) } if _, err := os.Stat(path); err == nil { t.Errorf("TearDown() failed, volume path still exists: %s", path) } else if !os.IsNotExist(err) { t.Errorf("SetUp() failed: %v", err) } }
func applyDefaults(pod *api.Pod, source string, isFile bool, nodeName string) error { if len(pod.UID) == 0 { hasher := md5.New() if isFile { fmt.Fprintf(hasher, "host:%s", nodeName) fmt.Fprintf(hasher, "file:%s", source) } else { fmt.Fprintf(hasher, "url:%s", source) } util.DeepHashObject(hasher, pod) pod.UID = types.UID(hex.EncodeToString(hasher.Sum(nil)[0:])) glog.V(5).Infof("Generated UID %q pod %q from %s", pod.UID, pod.Name, source) } pod.Name = generatePodName(pod.Name, nodeName) glog.V(5).Infof("Generated Name %q for UID %q from URL %s", pod.Name, pod.UID, source) if pod.Namespace == "" { pod.Namespace = qinglet.NamespaceDefault } glog.V(5).Infof("Using namespace %q for pod %q from %s", pod.Namespace, pod.Name, source) // Set the Host field to indicate this pod is scheduled on the current node. pod.Spec.NodeName = nodeName pod.ObjectMeta.SelfLink = getSelfLink(pod.Name, pod.Namespace) return nil }
func TestUpdatePod(t *testing.T) { podWorkers, processed := createPodWorkers() // Check whether all pod updates will be processed. numPods := 20 for i := 0; i < numPods; i++ { for j := i; j < numPods; j++ { podWorkers.UpdatePod(newPod(string(j), string(i)), nil, func() {}) } } drainWorkers(podWorkers, numPods) if len(processed) != 20 { t.Errorf("Not all pods processed: %v", len(processed)) return } for i := 0; i < numPods; i++ { uid := types.UID(i) if len(processed[uid]) < 1 || len(processed[uid]) > i+1 { t.Errorf("Pod %v processed %v times", i, len(processed[uid])) continue } first := 0 last := len(processed[uid]) - 1 if processed[uid][first] != string(0) { t.Errorf("Pod %v: incorrect order %v, %v", i, first, processed[uid][first]) } if processed[uid][last] != string(i) { t.Errorf("Pod %v: incorrect order %v, %v", i, last, processed[uid][last]) } } }
// Unpacks a container name, returning the pod full name and container name we would have used to // construct the docker name. If we are unable to parse the name, an error is returned. func ParseDockerName(name string) (dockerName *QingletContainerName, hash uint64, err error) { // For some reason docker appears to be appending '/' to names. // If it's there, strip it. name = strings.TrimPrefix(name, "/") parts := strings.Split(name, "_") if len(parts) == 0 || parts[0] != containerNamePrefix { err = fmt.Errorf("failed to parse Docker container name %q into parts", name) return nil, 0, err } if len(parts) < 6 { // We have at least 5 fields. We may have more in the future. // Anything with less fields than this is not something we can // manage. glog.Warningf("found a container with the %q prefix, but too few fields (%d): %q", containerNamePrefix, len(parts), name) err = fmt.Errorf("Docker container name %q has less parts than expected %v", name, parts) return nil, 0, err } nameParts := strings.Split(parts[1], ".") containerName := nameParts[0] if len(nameParts) > 1 { hash, err = strconv.ParseUint(nameParts[1], 16, 32) if err != nil { glog.Warningf("invalid container hash %q in container %q", nameParts[1], name) } } podFullName := parts[2] + "_" + parts[3] podUID := types.UID(parts[4]) return &QingletContainerName{podFullName, podUID, containerName}, hash, nil }
func (d *NodeDescriber) Describe(namespace, name string) (string, error) { mc := d.Nodes() node, err := mc.Get(name) if err != nil { return "", err } var pods []*api.Pod allPods, err := d.Pods(namespace).List(labels.Everything(), fields.Everything()) if err != nil { return "", err } for i := range allPods.Items { pod := &allPods.Items[i] if pod.Spec.NodeName != name { continue } pods = append(pods, pod) } var events *api.EventList if ref, err := api.GetReference(node); err != nil { glog.Errorf("Unable to construct reference to '%#v': %v", node, err) } else { // TODO: We haven't decided the namespace for Node object yet. ref.UID = types.UID(ref.Name) events, _ = d.Events("").Search(ref) } return describeNode(node, pods, events) }
func newPod(uid, name string) *api.Pod { return &api.Pod{ ObjectMeta: api.ObjectMeta{ UID: types.UID(uid), Name: name, }, } }
// Test the case where the 'ready' file has been created and the pod volume dir // is a mountpoint. Mount should not be called. func TestPluginIdempotent(t *testing.T) { var ( testPodUID = types.UID("test_pod_uid2") testVolumeName = "test_volume_name" testNamespace = "test_secret_namespace" testName = "test_secret_name" volumeSpec = volumeSpec(testVolumeName, testName) secret = secret(testNamespace, testName) client = testclient.NewSimpleFake(&secret) pluginMgr = volume.VolumePluginMgr{} rootDir, host = newTestHost(t, client) ) pluginMgr.InitPlugins(ProbeVolumePlugins(), host) plugin, err := pluginMgr.FindPluginByName(secretPluginName) if err != nil { t.Errorf("Can't find the plugin by name") } podVolumeDir := fmt.Sprintf("%v/pods/test_pod_uid2/volumes/qingyuan~secret/test_volume_name", rootDir) podMetadataDir := fmt.Sprintf("%v/pods/test_pod_uid2/plugins/qingyuan~secret/test_volume_name", rootDir) pod := &api.Pod{ObjectMeta: api.ObjectMeta{UID: testPodUID}} mounter := &mount.FakeMounter{} mounter.MountPoints = []mount.MountPoint{ { Path: podVolumeDir, }, } util.SetReady(podMetadataDir) builder, err := plugin.NewBuilder(volume.NewSpecFromVolume(volumeSpec), pod, volume.VolumeOptions{}, mounter) if err != nil { t.Errorf("Failed to make a new Builder: %v", err) } if builder == nil { t.Errorf("Got a nil Builder") } volumePath := builder.GetPath() err = builder.SetUp() if err != nil { t.Errorf("Failed to setup volume: %v", err) } if len(mounter.Log) != 0 { t.Errorf("Unexpected calls made to mounter: %v", mounter.Log) } if _, err := os.Stat(volumePath); err != nil { if !os.IsNotExist(err) { t.Errorf("SetUp() failed unexpectedly: %v", err) } } else { t.Errorf("volume path should not exist: %v", volumePath) } }
/** * The UUID package is naive and can generate identical UUIDs if the time interval is quick enough. * Block subsequent UUIDs for 200 Nanoseconds, the UUID uses 100 ns increments, we block for 200 to be safe * Blocks in a go routine, so that the caller doesn't have to wait. * TODO: save old unused UUIDs so that no one has to block. */ func NewUUID() types.UID { uuidLock.Lock() result := uuid.NewUUID() go func() { time.Sleep(200 * time.Nanosecond) uuidLock.Unlock() }() return types.UID(result.String()) }
func TestPlugin(t *testing.T) { plugMgr := volume.VolumePluginMgr{} plugMgr.InitPlugins(ProbeVolumePlugins(), volume.NewFakeVolumeHost("fake", nil, nil)) plug, err := plugMgr.FindPluginByName("qingyuan/host-path") if err != nil { t.Errorf("Can't find the plugin by name") } spec := &api.Volume{ Name: "vol1", VolumeSource: api.VolumeSource{HostPath: &api.HostPathVolumeSource{"/vol1"}}, } pod := &api.Pod{ObjectMeta: api.ObjectMeta{UID: types.UID("poduid")}} builder, err := plug.NewBuilder(volume.NewSpecFromVolume(spec), pod, volume.VolumeOptions{}, nil) if err != nil { t.Errorf("Failed to make a new Builder: %v", err) } if builder == nil { t.Errorf("Got a nil Builder") } path := builder.GetPath() if path != "/vol1" { t.Errorf("Got unexpected path: %s", path) } if err := builder.SetUp(); err != nil { t.Errorf("Expected success, got: %v", err) } cleaner, err := plug.NewCleaner("vol1", types.UID("poduid"), nil) if err != nil { t.Errorf("Failed to make a new Cleaner: %v", err) } if cleaner == nil { t.Errorf("Got a nil Cleaner") } if err := cleaner.TearDown(); err != nil { t.Errorf("Expected success, got: %v", err) } }
// Test the case where the plugin's ready file exists, but the volume dir is not a // mountpoint, which is the state the system will be in after reboot. The dir // should be mounter and the secret data written to it. func TestPluginReboot(t *testing.T) { var ( testPodUID = types.UID("test_pod_uid3") testVolumeName = "test_volume_name" testNamespace = "test_secret_namespace" testName = "test_secret_name" volumeSpec = volumeSpec(testVolumeName, testName) secret = secret(testNamespace, testName) client = testclient.NewSimpleFake(&secret) pluginMgr = volume.VolumePluginMgr{} rootDir, host = newTestHost(t, client) ) pluginMgr.InitPlugins(ProbeVolumePlugins(), host) plugin, err := pluginMgr.FindPluginByName(secretPluginName) if err != nil { t.Errorf("Can't find the plugin by name") } pod := &api.Pod{ObjectMeta: api.ObjectMeta{UID: testPodUID}} builder, err := plugin.NewBuilder(volume.NewSpecFromVolume(volumeSpec), pod, volume.VolumeOptions{}, &mount.FakeMounter{}) if err != nil { t.Errorf("Failed to make a new Builder: %v", err) } if builder == nil { t.Errorf("Got a nil Builder") } podMetadataDir := fmt.Sprintf("%v/pods/test_pod_uid3/plugins/qingyuan~secret/test_volume_name", rootDir) util.SetReady(podMetadataDir) volumePath := builder.GetPath() if !strings.HasSuffix(volumePath, fmt.Sprintf("pods/test_pod_uid3/volumes/qingyuan~secret/test_volume_name")) { t.Errorf("Got unexpected path: %s", volumePath) } err = builder.SetUp() if err != nil { t.Errorf("Failed to setup volume: %v", err) } if _, err := os.Stat(volumePath); err != nil { if os.IsNotExist(err) { t.Errorf("SetUp() failed, volume path not created: %s", volumePath) } else { t.Errorf("SetUp() failed: %v", err) } } doTestSecretDataInVolume(volumePath, secret, t) doTestCleanAndTeardown(plugin, testPodUID, testVolumeName, volumePath, t) }
func verifyPackUnpack(t *testing.T, podNamespace, podUID, podName, containerName string) { container := &api.Container{Name: containerName} hasher := adler32.New() util.DeepHashObject(hasher, *container) computedHash := uint64(hasher.Sum32()) podFullName := fmt.Sprintf("%s_%s", podName, podNamespace) name := BuildDockerName(QingletContainerName{podFullName, types.UID(podUID), container.Name}, container) returned, hash, err := ParseDockerName(name) if err != nil { t.Errorf("Failed to parse Docker container name %q: %v", name, err) } if podFullName != returned.PodFullName || podUID != string(returned.PodUID) || containerName != returned.ContainerName || computedHash != hash { t.Errorf("For (%s, %s, %s, %d), unpacked (%s, %s, %s, %d)", podFullName, podUID, containerName, computedHash, returned.PodFullName, returned.PodUID, returned.ContainerName, hash) } }
func TestPluginLegacy(t *testing.T) { plug := makePluginUnderTest(t, "empty") if plug.Name() != "empty" { t.Errorf("Wrong name: %s", plug.Name()) } if plug.CanSupport(&volume.Spec{Name: "foo", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}) { t.Errorf("Expected false") } spec := api.Volume{VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}} pod := &api.Pod{ObjectMeta: api.ObjectMeta{UID: types.UID("poduid")}} if _, err := plug.(*emptyDirPlugin).newBuilderInternal(volume.NewSpecFromVolume(&spec), pod, &mount.FakeMounter{}, &fakeMountDetector{}, volume.VolumeOptions{""}); err == nil { t.Errorf("Expected failiure") } cleaner, err := plug.(*emptyDirPlugin).newCleanerInternal("vol1", types.UID("poduid"), &mount.FakeMounter{}, &fakeMountDetector{}) if err != nil { t.Errorf("Failed to make a new Cleaner: %v", err) } if cleaner == nil { t.Errorf("Got a nil Cleaner") } }
func drainWorkers(podWorkers *podWorkers, numPods int) { for { stillWorking := false podWorkers.podLock.Lock() for i := 0; i < numPods; i++ { if podWorkers.isWorking[types.UID(string(i))] { stillWorking = true } } podWorkers.podLock.Unlock() if !stillWorking { break } time.Sleep(50 * time.Millisecond) } }
// Converts docker.APIContainers to qingcontainer.Container. func toRuntimeContainer(c *docker.APIContainers) (*qingcontainer.Container, error) { if c == nil { return nil, fmt.Errorf("unable to convert a nil pointer to a runtime container") } dockerName, hash, err := getDockerContainerNameInfo(c) if err != nil { return nil, err } return &qingcontainer.Container{ ID: types.UID(c.ID), Name: dockerName.ContainerName, Image: c.Image, Hash: hash, Created: c.Created, }, nil }
func TestNewBuilderClaimNotBound(t *testing.T) { pv := &api.PersistentVolume{ ObjectMeta: api.ObjectMeta{ Name: "pvC", }, Spec: api.PersistentVolumeSpec{ PersistentVolumeSource: api.PersistentVolumeSource{ GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{}, }, }, } claim := &api.PersistentVolumeClaim{ ObjectMeta: api.ObjectMeta{ Name: "claimC", Namespace: "nsA", }, } podVolume := api.VolumeSource{ PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{ ReadOnly: false, ClaimName: "claimC", }, } o := testclient.NewObjects(api.Scheme, api.Scheme) o.Add(pv) o.Add(claim) client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, latest.RESTMapper)} plugMgr := volume.VolumePluginMgr{} plugMgr.InitPlugins(testProbeVolumePlugins(), newTestHost(t, client)) plug, err := plugMgr.FindPluginByName("qingyuan/persistent-claim") if err != nil { t.Errorf("Can't find the plugin by name") } spec := &volume.Spec{ Name: "vol1", VolumeSource: podVolume, } pod := &api.Pod{ObjectMeta: api.ObjectMeta{UID: types.UID("poduid")}} builder, err := plug.NewBuilder(spec, pod, volume.VolumeOptions{}, nil) if builder != nil { t.Errorf("Expected a nil builder if the claim wasn't bound") } }
func parsePodCoordinates(path string) (namespace, pod string, uid types.UID, err error) { parts := strings.Split(path, "/") if len(parts) == 4 { namespace = parts[2] pod = parts[3] return } if len(parts) == 5 { namespace = parts[2] pod = parts[3] uid = types.UID(parts[4]) return } err = fmt.Errorf("Unexpected path %s. Expected /.../.../<namespace>/<pod> or /.../.../<namespace>/<pod>/<uid>", path) return }
func TestPluginBackCompat(t *testing.T) { plug := makePluginUnderTest(t, "qingyuan/empty-dir") spec := &api.Volume{ Name: "vol1", } pod := &api.Pod{ObjectMeta: api.ObjectMeta{UID: types.UID("poduid")}} builder, err := plug.NewBuilder(volume.NewSpecFromVolume(spec), pod, volume.VolumeOptions{""}, nil) if err != nil { t.Errorf("Failed to make a new Builder: %v", err) } if builder == nil { t.Errorf("Got a nil Builder") } volPath := builder.GetPath() if volPath != path.Join(basePath, "pods/poduid/volumes/qingyuan~empty-dir/vol1") { t.Errorf("Got unexpected path: %s", volPath) } }
func parseContainerCoordinates(path string) (namespace, pod string, uid types.UID, container string, err error) { parts := strings.Split(path, "/") if len(parts) == 5 { namespace = parts[2] pod = parts[3] container = parts[4] return } if len(parts) == 6 { namespace = parts[2] pod = parts[3] uid = types.UID(parts[4]) container = parts[5] return } err = fmt.Errorf("Unexpected path %s. Expected /.../.../<namespace>/<pod>/<container> or /.../.../<namespace>/<pod>/<uid>/<container>", path) return }
// TODO(yifan): Remove the receiver once we can solve the appName->imageID problem. func (r *runtime) apiPodToruntimePod(uuid string, pod *api.Pod) *qingcontainer.Pod { p := &qingcontainer.Pod{ ID: pod.UID, Name: pod.Name, Namespace: pod.Namespace, } for i := range pod.Spec.Containers { c := &pod.Spec.Containers[i] img, err := r.getImageByName(c.Image) if err != nil { glog.Warningf("rkt: Cannot get image for %q: %v", c.Image, err) } p.Containers = append(p.Containers, &qingcontainer.Container{ ID: types.UID(buildContainerID(&containerID{uuid, c.Name, img.id})), Name: c.Name, Image: c.Image, Hash: qingcontainer.HashContainer(c), Created: time.Now().Unix(), }) } return p }
func CreateValidPod(name, namespace, source string) *api.Pod { return &api.Pod{ ObjectMeta: api.ObjectMeta{ UID: types.UID(name), // for the purpose of testing, this is unique enough Name: name, Namespace: namespace, Annotations: map[string]string{qinglet.ConfigSourceAnnotationKey: source}, }, Spec: api.PodSpec{ RestartPolicy: api.RestartPolicyAlways, DNSPolicy: api.DNSClusterFirst, Containers: []api.Container{ { Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(), }, }, }, } }
func TestToRuntimeContainer(t *testing.T) { original := &docker.APIContainers{ ID: "ab2cdf", Image: "bar_image", Created: 12345, Names: []string{"/k8s_bar.5678_foo_ns_1234_42"}, } expected := &qingcontainer.Container{ ID: types.UID("ab2cdf"), Name: "bar", Image: "bar_image", Hash: 0x5678, Created: 12345, } actual, err := toRuntimeContainer(original) if err != nil { t.Fatalf("unexpected error %v", err) } if !reflect.DeepEqual(expected, actual) { t.Errorf("expected %#v, got %#v", expected, actual) } }
func TestAutomountsAPIToken(t *testing.T) { ns := "myns" tokenName := "token-name" serviceAccountName := DefaultServiceAccountName serviceAccountUID := "12345" expectedVolume := api.Volume{ Name: tokenName, VolumeSource: api.VolumeSource{ Secret: &api.SecretVolumeSource{SecretName: tokenName}, }, } expectedVolumeMount := api.VolumeMount{ Name: tokenName, ReadOnly: true, MountPath: DefaultAPITokenMountPath, } admit := NewServiceAccount(nil) admit.MountServiceAccountToken = true // Add the default service account for the ns with a token into the cache admit.serviceAccounts.Add(&api.ServiceAccount{ ObjectMeta: api.ObjectMeta{ Name: serviceAccountName, Namespace: ns, UID: types.UID(serviceAccountUID), }, Secrets: []api.ObjectReference{ {Name: tokenName}, }, }) // Add a token for the service account into the cache admit.secrets.Add(&api.Secret{ ObjectMeta: api.ObjectMeta{ Name: tokenName, Namespace: ns, Annotations: map[string]string{ api.ServiceAccountNameKey: serviceAccountName, api.ServiceAccountUIDKey: serviceAccountUID, }, }, Type: api.SecretTypeServiceAccountToken, Data: map[string][]byte{ api.ServiceAccountTokenKey: []byte("token-data"), }, }) pod := &api.Pod{ Spec: api.PodSpec{ Containers: []api.Container{ {}, }, }, } attrs := admission.NewAttributesRecord(pod, "Pod", ns, "myname", string(api.ResourcePods), "", admission.Create, nil) err := admit.Admit(attrs) if err != nil { t.Errorf("Unexpected error: %v", err) } if pod.Spec.ServiceAccountName != DefaultServiceAccountName { t.Errorf("Expected service account %s assigned, got %s", DefaultServiceAccountName, pod.Spec.ServiceAccountName) } if len(pod.Spec.Volumes) != 1 { t.Fatalf("Expected 1 volume, got %d", len(pod.Spec.Volumes)) } if !reflect.DeepEqual(expectedVolume, pod.Spec.Volumes[0]) { t.Fatalf("Expected\n\t%#v\ngot\n\t%#v", expectedVolume, pod.Spec.Volumes[0]) } if len(pod.Spec.Containers[0].VolumeMounts) != 1 { t.Fatalf("Expected 1 volume mount, got %d", len(pod.Spec.Containers[0].VolumeMounts)) } if !reflect.DeepEqual(expectedVolumeMount, pod.Spec.Containers[0].VolumeMounts[0]) { t.Fatalf("Expected\n\t%#v\ngot\n\t%#v", expectedVolumeMount, pod.Spec.Containers[0].VolumeMounts[0]) } }
// serveStats implements stats logic. func (s *Server) serveStats(w http.ResponseWriter, req *http.Request) { // Stats requests are in the following forms: // // /stats/ : Root container stats // /stats/container/ : Non-QingYuan container stats (returns a map) // /stats/<pod name>/<container name> : Stats for QingYuan pod/container // /stats/<namespace>/<pod name>/<uid>/<container name> : Stats for QingYuan namespace/pod/uid/container components := strings.Split(strings.TrimPrefix(path.Clean(req.URL.Path), "/"), "/") var stats interface{} var err error var query StatsRequest query.NumStats = 60 err = json.NewDecoder(req.Body).Decode(&query) if err != nil && err != io.EOF { s.error(w, err) return } cadvisorRequest := cadvisorApi.ContainerInfoRequest{ NumStats: query.NumStats, Start: query.Start, End: query.End, } switch len(components) { case 1: // Root container stats. var statsMap map[string]*cadvisorApi.ContainerInfo statsMap, err = s.host.GetRawContainerInfo("/", &cadvisorRequest, false) stats = statsMap["/"] case 2: // Non-QingYuan container stats. if components[1] != "container" { http.Error(w, fmt.Sprintf("unknown stats request type %q", components[1]), http.StatusNotFound) return } containerName := path.Join("/", query.ContainerName) stats, err = s.host.GetRawContainerInfo(containerName, &cadvisorRequest, query.Subcontainers) case 3: // Backward compatibility without uid information, does not support namespace pod, ok := s.host.GetPodByName(api.NamespaceDefault, components[1]) if !ok { http.Error(w, "Pod does not exist", http.StatusNotFound) return } stats, err = s.host.GetContainerInfo(qingcontainer.GetPodFullName(pod), "", components[2], &cadvisorRequest) case 5: pod, ok := s.host.GetPodByName(components[1], components[2]) if !ok { http.Error(w, "Pod does not exist", http.StatusNotFound) return } stats, err = s.host.GetContainerInfo(qingcontainer.GetPodFullName(pod), types.UID(components[3]), components[4], &cadvisorRequest) default: http.Error(w, fmt.Sprintf("Unknown resource: %v", components), http.StatusNotFound) return } switch err { case nil: break case ErrContainerNotFound: http.Error(w, err.Error(), http.StatusNotFound) return default: s.error(w, err) return } if stats == nil { fmt.Fprint(w, "{}") return } data, err := json.Marshal(stats) if err != nil { s.error(w, err) return } w.Header().Add("Content-type", "application/json") w.Write(data) return }
func TestRespectsExistingMount(t *testing.T) { ns := "myns" tokenName := "token-name" serviceAccountName := DefaultServiceAccountName serviceAccountUID := "12345" expectedVolumeMount := api.VolumeMount{ Name: "my-custom-mount", ReadOnly: false, MountPath: DefaultAPITokenMountPath, } admit := NewServiceAccount(nil) admit.MountServiceAccountToken = true // Add the default service account for the ns with a token into the cache admit.serviceAccounts.Add(&api.ServiceAccount{ ObjectMeta: api.ObjectMeta{ Name: serviceAccountName, Namespace: ns, UID: types.UID(serviceAccountUID), }, Secrets: []api.ObjectReference{ {Name: tokenName}, }, }) // Add a token for the service account into the cache admit.secrets.Add(&api.Secret{ ObjectMeta: api.ObjectMeta{ Name: tokenName, Namespace: ns, Annotations: map[string]string{ api.ServiceAccountNameKey: serviceAccountName, api.ServiceAccountUIDKey: serviceAccountUID, }, }, Type: api.SecretTypeServiceAccountToken, Data: map[string][]byte{ api.ServiceAccountTokenKey: []byte("token-data"), }, }) // Define a pod with a container that already mounts a volume at the API token path // Admission should respect that // Additionally, no volume should be created if no container is going to use it pod := &api.Pod{ Spec: api.PodSpec{ Containers: []api.Container{ { VolumeMounts: []api.VolumeMount{ expectedVolumeMount, }, }, }, }, } attrs := admission.NewAttributesRecord(pod, "Pod", ns, "myname", string(api.ResourcePods), "", admission.Create, nil) err := admit.Admit(attrs) if err != nil { t.Errorf("Unexpected error: %v", err) } if pod.Spec.ServiceAccountName != DefaultServiceAccountName { t.Errorf("Expected service account %s assigned, got %s", DefaultServiceAccountName, pod.Spec.ServiceAccountName) } if len(pod.Spec.Volumes) != 0 { t.Fatalf("Expected 0 volumes (shouldn't create a volume for a secret we don't need), got %d", len(pod.Spec.Volumes)) } if len(pod.Spec.Containers[0].VolumeMounts) != 1 { t.Fatalf("Expected 1 volume mount, got %d", len(pod.Spec.Containers[0].VolumeMounts)) } if !reflect.DeepEqual(expectedVolumeMount, pod.Spec.Containers[0].VolumeMounts[0]) { t.Fatalf("Expected\n\t%#v\ngot\n\t%#v", expectedVolumeMount, pod.Spec.Containers[0].VolumeMounts[0]) } }
func TestPlugin(t *testing.T) { plugMgr := volume.VolumePluginMgr{} plugMgr.InitPlugins(ProbeVolumePlugins(), volume.NewFakeVolumeHost("/tmp/fake", nil, nil)) plug, err := plugMgr.FindPluginByName("qingyuan/aws-ebs") if err != nil { t.Errorf("Can't find the plugin by name") } spec := &api.Volume{ Name: "vol1", VolumeSource: api.VolumeSource{ AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{ VolumeID: "pd", FSType: "ext4", }, }, } builder, err := plug.(*awsElasticBlockStorePlugin).newBuilderInternal(volume.NewSpecFromVolume(spec), types.UID("poduid"), &fakePDManager{}, &mount.FakeMounter{}) if err != nil { t.Errorf("Failed to make a new Builder: %v", err) } if builder == nil { t.Errorf("Got a nil Builder") } path := builder.GetPath() if path != "/tmp/fake/pods/poduid/volumes/qingyuan~aws-ebs/vol1" { t.Errorf("Got unexpected path: %s", path) } if err := builder.SetUp(); err != nil { t.Errorf("Expected success, got: %v", err) } if _, err := os.Stat(path); err != nil { if os.IsNotExist(err) { t.Errorf("SetUp() failed, volume path not created: %s", path) } else { t.Errorf("SetUp() failed: %v", err) } } if _, err := os.Stat(path); err != nil { if os.IsNotExist(err) { t.Errorf("SetUp() failed, volume path not created: %s", path) } else { t.Errorf("SetUp() failed: %v", err) } } cleaner, err := plug.(*awsElasticBlockStorePlugin).newCleanerInternal("vol1", types.UID("poduid"), &fakePDManager{}, &mount.FakeMounter{}) if err != nil { t.Errorf("Failed to make a new Cleaner: %v", err) } if cleaner == nil { t.Errorf("Got a nil Cleaner") } if err := cleaner.TearDown(); err != nil { t.Errorf("Expected success, got: %v", err) } if _, err := os.Stat(path); err == nil { t.Errorf("TearDown() failed, volume path still exists: %s", path) } else if !os.IsNotExist(err) { t.Errorf("SetUp() failed: %v", err) } }
// FuzzerFor can randomly populate api objects that are destined for version. func FuzzerFor(t *testing.T, version string, src rand.Source) *fuzz.Fuzzer { f := fuzz.New().NilChance(.5).NumElements(1, 1) if src != nil { f.RandSource(src) } f.Funcs( func(j *runtime.PluginBase, c fuzz.Continue) { // Do nothing; this struct has only a Kind field and it must stay blank in memory. }, func(j *runtime.TypeMeta, c fuzz.Continue) { // We have to customize the randomization of TypeMetas because their // APIVersion and Kind must remain blank in memory. j.APIVersion = "" j.Kind = "" }, func(j *api.TypeMeta, c fuzz.Continue) { // We have to customize the randomization of TypeMetas because their // APIVersion and Kind must remain blank in memory. j.APIVersion = "" j.Kind = "" }, func(j *api.ObjectMeta, c fuzz.Continue) { j.Name = c.RandString() j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10) j.SelfLink = c.RandString() j.UID = types.UID(c.RandString()) j.GenerateName = c.RandString() var sec, nsec int64 c.Fuzz(&sec) c.Fuzz(&nsec) j.CreationTimestamp = util.Unix(sec, nsec).Rfc3339Copy() }, func(j *api.ObjectReference, c fuzz.Continue) { // We have to customize the randomization of TypeMetas because their // APIVersion and Kind must remain blank in memory. j.APIVersion = c.RandString() j.Kind = c.RandString() j.Namespace = c.RandString() j.Name = c.RandString() j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10) j.FieldPath = c.RandString() }, func(j *api.ListMeta, c fuzz.Continue) { j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10) j.SelfLink = c.RandString() }, func(j *api.ListOptions, c fuzz.Continue) { // TODO: add some parsing j.LabelSelector, _ = labels.Parse("a=b") j.FieldSelector, _ = fields.ParseSelector("a=b") }, func(j *api.PodPhase, c fuzz.Continue) { statuses := []api.PodPhase{api.PodPending, api.PodRunning, api.PodFailed, api.PodUnknown} *j = statuses[c.Rand.Intn(len(statuses))] }, func(j *api.PodTemplateSpec, c fuzz.Continue) { // TODO: v1beta1/2 can't round trip a nil template correctly, fix by having v1beta1/2 // conversion compare converted object to nil via DeepEqual j.ObjectMeta = api.ObjectMeta{} c.Fuzz(&j.ObjectMeta) j.ObjectMeta = api.ObjectMeta{Labels: j.ObjectMeta.Labels} j.Spec = api.PodSpec{} c.Fuzz(&j.Spec) }, func(j *api.Binding, c fuzz.Continue) { c.Fuzz(&j.ObjectMeta) j.Target.Name = c.RandString() }, func(j *api.ReplicationControllerSpec, c fuzz.Continue) { c.FuzzNoCustom(j) // fuzz self without calling this function again //j.TemplateRef = nil // this is required for round trip }, func(j *api.ReplicationControllerStatus, c fuzz.Continue) { // only replicas round trips j.Replicas = int(c.RandUint64()) }, func(j *api.List, c fuzz.Continue) { c.FuzzNoCustom(j) // fuzz self without calling this function again // TODO: uncomment when round trip starts from a versioned object if false { //j.Items == nil { j.Items = []runtime.Object{} } }, func(j *runtime.Object, c fuzz.Continue) { // TODO: uncomment when round trip starts from a versioned object if true { //c.RandBool() { *j = &runtime.Unknown{ TypeMeta: runtime.TypeMeta{Kind: "Something", APIVersion: "unknown"}, RawJSON: []byte(`{"apiVersion":"unknown","kind":"Something","someKey":"someValue"}`), } } else { types := []runtime.Object{&api.Pod{}, &api.ReplicationController{}} t := types[c.Rand.Intn(len(types))] c.Fuzz(t) *j = t } }, func(pb map[docker.Port][]docker.PortBinding, c fuzz.Continue) { // This is necessary because keys with nil values get omitted. // TODO: Is this a bug? pb[docker.Port(c.RandString())] = []docker.PortBinding{ {c.RandString(), c.RandString()}, {c.RandString(), c.RandString()}, } }, func(pm map[string]docker.PortMapping, c fuzz.Continue) { // This is necessary because keys with nil values get omitted. // TODO: Is this a bug? pm[c.RandString()] = docker.PortMapping{ c.RandString(): c.RandString(), } }, func(q *resource.Quantity, c fuzz.Continue) { // Real Quantity fuzz testing is done elsewhere; // this limited subset of functionality survives // round-tripping to v1beta1/2. q.Amount = &inf.Dec{} q.Format = resource.DecimalExponent //q.Amount.SetScale(inf.Scale(-c.Intn(12))) q.Amount.SetUnscaled(c.Int63n(1000)) }, func(p *api.PullPolicy, c fuzz.Continue) { policies := []api.PullPolicy{api.PullAlways, api.PullNever, api.PullIfNotPresent} *p = policies[c.Rand.Intn(len(policies))] }, func(rp *api.RestartPolicy, c fuzz.Continue) { policies := []api.RestartPolicy{api.RestartPolicyAlways, api.RestartPolicyNever, api.RestartPolicyOnFailure} *rp = policies[c.Rand.Intn(len(policies))] }, func(vs *api.VolumeSource, c fuzz.Continue) { // Exactly one of the fields must be set. v := reflect.ValueOf(vs).Elem() i := int(c.RandUint64() % uint64(v.NumField())) v = v.Field(i).Addr() // Use a new fuzzer which cannot populate nil to ensure one field will be set. fuzz.New().NilChance(0).NumElements(1, 1).Fuzz(v.Interface()) }, func(d *api.DNSPolicy, c fuzz.Continue) { policies := []api.DNSPolicy{api.DNSClusterFirst, api.DNSDefault} *d = policies[c.Rand.Intn(len(policies))] }, func(p *api.Protocol, c fuzz.Continue) { protocols := []api.Protocol{api.ProtocolTCP, api.ProtocolUDP} *p = protocols[c.Rand.Intn(len(protocols))] }, func(p *api.ServiceAffinity, c fuzz.Continue) { types := []api.ServiceAffinity{api.ServiceAffinityClientIP, api.ServiceAffinityNone} *p = types[c.Rand.Intn(len(types))] }, func(p *api.ServiceType, c fuzz.Continue) { types := []api.ServiceType{api.ServiceTypeClusterIP, api.ServiceTypeNodePort, api.ServiceTypeLoadBalancer} *p = types[c.Rand.Intn(len(types))] }, func(ct *api.Container, c fuzz.Continue) { c.FuzzNoCustom(ct) // fuzz self without calling this function again ct.TerminationMessagePath = "/" + ct.TerminationMessagePath // Must be non-empty }, func(ev *api.EnvVar, c fuzz.Continue) { ev.Name = c.RandString() if c.RandBool() { ev.Value = c.RandString() } else { ev.ValueFrom = &api.EnvVarSource{} ev.ValueFrom.FieldRef = &api.ObjectFieldSelector{} versions := []string{"v1beta1", "v1beta2", "v1beta3"} ev.ValueFrom.FieldRef.APIVersion = versions[c.Rand.Intn(len(versions))] ev.ValueFrom.FieldRef.FieldPath = c.RandString() } }, func(sc *api.SecurityContext, c fuzz.Continue) { c.FuzzNoCustom(sc) // fuzz self without calling this function again priv := c.RandBool() sc.Privileged = &priv sc.Capabilities = &api.Capabilities{ Add: make([]api.Capability, 0), Drop: make([]api.Capability, 0), } c.Fuzz(&sc.Capabilities.Add) c.Fuzz(&sc.Capabilities.Drop) }, func(e *api.Event, c fuzz.Continue) { c.FuzzNoCustom(e) // fuzz self without calling this function again // Fix event count to 1, otherwise, if a v1beta1 or v1beta2 event has a count set arbitrarily, it's count is ignored if e.FirstTimestamp.IsZero() { e.Count = 1 } else { c.Fuzz(&e.Count) } }, func(s *api.Secret, c fuzz.Continue) { c.FuzzNoCustom(s) // fuzz self without calling this function again s.Type = api.SecretTypeOpaque }, func(pv *api.PersistentVolume, c fuzz.Continue) { c.FuzzNoCustom(pv) // fuzz self without calling this function again types := []api.PersistentVolumePhase{api.VolumeAvailable, api.VolumePending, api.VolumeBound, api.VolumeReleased, api.VolumeFailed} pv.Status.Phase = types[c.Rand.Intn(len(types))] pv.Status.Message = c.RandString() reclamationPolicies := []api.PersistentVolumeReclaimPolicy{api.PersistentVolumeReclaimRecycle, api.PersistentVolumeReclaimRetain} pv.Spec.PersistentVolumeReclaimPolicy = reclamationPolicies[c.Rand.Intn(len(reclamationPolicies))] }, func(pvc *api.PersistentVolumeClaim, c fuzz.Continue) { c.FuzzNoCustom(pvc) // fuzz self without calling this function again types := []api.PersistentVolumeClaimPhase{api.ClaimBound, api.ClaimPending} pvc.Status.Phase = types[c.Rand.Intn(len(types))] }, func(s *api.NamespaceSpec, c fuzz.Continue) { s.Finalizers = []api.FinalizerName{api.FinalizerQingYuan} }, func(s *api.NamespaceStatus, c fuzz.Continue) { s.Phase = api.NamespaceActive }, func(http *api.HTTPGetAction, c fuzz.Continue) { c.FuzzNoCustom(http) // fuzz self without calling this function again http.Path = "/" + http.Path // can't be blank }, func(ss *api.ServiceSpec, c fuzz.Continue) { c.FuzzNoCustom(ss) // fuzz self without calling this function again if len(ss.Ports) == 0 { // There must be at least 1 port. ss.Ports = append(ss.Ports, api.ServicePort{}) c.Fuzz(&ss.Ports[0]) } for i := range ss.Ports { switch ss.Ports[i].TargetPort.Kind { case util.IntstrInt: ss.Ports[i].TargetPort.IntVal = 1 + ss.Ports[i].TargetPort.IntVal%65535 // non-zero case util.IntstrString: ss.Ports[i].TargetPort.StrVal = "x" + ss.Ports[i].TargetPort.StrVal // non-empty } } }, func(n *api.Node, c fuzz.Continue) { c.FuzzNoCustom(n) n.Spec.ExternalID = "external" }, ) return f }