Ensure Namespace and UID are set in kubelet

Make all kubelet config sources ensure that UID and Namespace are defaulted, if
need be.

We can *almost* disable the "if blank" logic for UID, except for tests that
call APIs that do not run through SyncPods.  We really ought to be enforcing
invariants better.
pull/6/head
Tim Hockin 2015-01-04 17:30:30 -08:00
parent af0e2fd551
commit 905514a12b
14 changed files with 206 additions and 191 deletions

View File

@ -622,7 +622,7 @@ func main() {
createdPods.Insert(p[:n-8])
}
}
// We expect 5: 2 net containers + 2 pods from the replication controller +
// We expect 9: 2 net containers + 2 pods from the replication controller +
// 1 net container + 2 pods from the URL +
// 1 net container + 1 pod from the service test.
if len(createdPods) != 9 {

View File

@ -24,9 +24,11 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
apierrs "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/record"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/config"
utilerrors "github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
"github.com/golang/glog"
)
@ -293,21 +295,28 @@ func (s *podStorage) seenSources(sources ...string) bool {
func filterInvalidPods(pods []api.BoundPod, source string) (filtered []*api.BoundPod) {
names := util.StringSet{}
for i := range pods {
var errors []error
name := podUniqueName(&pods[i])
if names.Has(name) {
errors = append(errors, apierrs.NewFieldDuplicate("name", pods[i].Name))
pod := &pods[i]
var errlist []error
if errs := validation.ValidateBoundPod(pod); len(errs) != 0 {
errlist = append(errlist, errs...)
// If validation fails, don't trust it any further -
// even Name could be bad.
} else {
names.Insert(name)
name := podUniqueName(pod)
if names.Has(name) {
errlist = append(errlist, apierrs.NewFieldDuplicate("name", pod.Name))
} else {
names.Insert(name)
}
}
if errs := validation.ValidateBoundPod(&pods[i]); len(errs) != 0 {
errors = append(errors, errs...)
}
if len(errors) > 0 {
glog.Warningf("Pod %d (%s) from %s failed validation, ignoring: %v", i+1, pods[i].Name, source, errors)
if len(errlist) > 0 {
name := bestPodIdentString(pod)
err := utilerrors.NewAggregate(errlist)
glog.Warningf("Pod[%d] (%s) from %s failed validation, ignoring: %v", i+1, name, source, err)
record.Eventf(pod, "", "failedValidation", "Error validating pod %s from %s, ignoring: %v", name, source, err)
continue
}
filtered = append(filtered, &pods[i])
filtered = append(filtered, pod)
}
return
}
@ -337,11 +346,19 @@ func (s *podStorage) MergedState() interface{} {
}
// podUniqueName returns a value for a given pod that is unique across a source,
// which is the combination of namespace and ID.
// which is the combination of namespace and name.
func podUniqueName(pod *api.BoundPod) string {
namespace := pod.Namespace
if len(namespace) == 0 {
namespace = api.NamespaceDefault
}
return fmt.Sprintf("%s.%s", pod.Name, namespace)
return fmt.Sprintf("%s.%s", pod.Name, pod.Namespace)
}
func bestPodIdentString(pod *api.BoundPod) string {
namespace := pod.Namespace
if namespace == "" {
namespace = "<empty-namespace>"
}
name := pod.Name
if name == "" {
name = "<empty-name>"
}
return fmt.Sprintf("%s.%s", name, namespace)
}

View File

@ -52,6 +52,7 @@ func (s sortedPods) Less(i, j int) bool {
func CreateValidPod(name, namespace, source string) api.BoundPod {
return api.BoundPod{
ObjectMeta: api.ObjectMeta{
UID: name, // for the purpose of testing, this is unique enough
Name: name,
Namespace: namespace,
Annotations: map[string]string{kubelet.ConfigSourceAnnotationKey: source},

View File

@ -20,7 +20,6 @@ package config
import (
"errors"
"path"
"strconv"
"time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
@ -105,11 +104,8 @@ func eventToPods(ev watch.Event) ([]api.BoundPod, error) {
}
for _, pod := range boundPods.Items {
// TODO: generate random UID if not present
if pod.UID == "" && !pod.CreationTimestamp.IsZero() {
pod.UID = strconv.FormatInt(pod.CreationTimestamp.Unix(), 10)
}
// Backwards compatibility with old api servers
// TODO: Remove this after 1.0 release.
if len(pod.Namespace) == 0 {
pod.Namespace = api.NamespaceDefault
}

View File

@ -44,14 +44,14 @@ func TestEventToPods(t *testing.T) {
input: watch.Event{
Object: &api.BoundPods{
Items: []api.BoundPod{
{ObjectMeta: api.ObjectMeta{Name: "foo"}},
{ObjectMeta: api.ObjectMeta{Name: "bar"}},
{ObjectMeta: api.ObjectMeta{UID: "111", Name: "foo", Namespace: "foo"}},
{ObjectMeta: api.ObjectMeta{UID: "222", Name: "bar", Namespace: "bar"}},
},
},
},
pods: []api.BoundPod{
{ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "default"}, Spec: api.PodSpec{}},
{ObjectMeta: api.ObjectMeta{Name: "bar", Namespace: "default"}, Spec: api.PodSpec{}},
{ObjectMeta: api.ObjectMeta{UID: "111", Name: "foo", Namespace: "foo"}, Spec: api.PodSpec{}},
{ObjectMeta: api.ObjectMeta{UID: "222", Name: "bar", Namespace: "bar"}, Spec: api.PodSpec{}},
},
fail: false,
},
@ -59,14 +59,12 @@ func TestEventToPods(t *testing.T) {
input: watch.Event{
Object: &api.BoundPods{
Items: []api.BoundPod{
{ObjectMeta: api.ObjectMeta{Name: "1"}},
{ObjectMeta: api.ObjectMeta{Name: "2", Namespace: "foo"}},
{ObjectMeta: api.ObjectMeta{UID: "111", Name: "foo"}},
},
},
},
pods: []api.BoundPod{
{ObjectMeta: api.ObjectMeta{Name: "1", Namespace: "default"}, Spec: api.PodSpec{}},
{ObjectMeta: api.ObjectMeta{Name: "2", Namespace: "foo"}, Spec: api.PodSpec{}},
{ObjectMeta: api.ObjectMeta{UID: "111", Name: "foo", Namespace: "default"}, Spec: api.PodSpec{}},
},
fail: false,
},

View File

@ -18,15 +18,14 @@ limitations under the License.
package config
import (
"crypto/sha1"
"encoding/base32"
"crypto/md5"
"encoding/hex"
"fmt"
"hash/adler32"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
@ -155,11 +154,27 @@ func extractFromFile(filename string) (api.BoundPod, error) {
return pod, fmt.Errorf("can't convert pod from file %q: %v", filename, err)
}
hostname, err := os.Hostname() //TODO: kubelet name would be better
if err != nil {
return pod, err
}
if len(pod.UID) == 0 {
pod.UID = simpleSubdomainSafeHash(filename)
hasher := md5.New()
fmt.Fprintf(hasher, "host:%s", hostname)
fmt.Fprintf(hasher, "file:%s", filename)
util.DeepHashObject(hasher, pod)
pod.UID = hex.EncodeToString(hasher.Sum(nil)[0:])
glog.V(5).Infof("Generated UID %q for pod %q from file %s", pod.UID, pod.Name, filename)
}
if len(pod.Namespace) == 0 {
pod.Namespace = api.NamespaceDefault
hasher := adler32.New()
fmt.Fprint(hasher, filename)
// TODO: file-<sum>.hostname would be better, if DNS subdomains
// are allowed for namespace (some places only allow DNS
// labels).
pod.Namespace = fmt.Sprintf("file-%08x-%s", hasher.Sum32(), hostname)
glog.V(5).Infof("Generated namespace %q for pod %q from file %s", pod.Namespace, pod.Name, filename)
}
// TODO(dchen1107): BoundPod is not type of runtime.Object. Once we allow kubelet talks
// about Pod directly, we can use SelfLinker defined in package: latest
@ -174,17 +189,3 @@ func extractFromFile(filename string) (api.BoundPod, error) {
}
return pod, nil
}
var simpleSubdomainSafeEncoding = base32.NewEncoding("0123456789abcdefghijklmnopqrstuv")
var unsafeDNSLabelReplacement = regexp.MustCompile("[^a-z0-9]+")
// simpleSubdomainSafeHash generates a pod name for the given path that is
// suitable as a subdomain label.
func simpleSubdomainSafeHash(path string) string {
name := strings.ToLower(filepath.Base(path))
name = unsafeDNSLabelReplacement.ReplaceAllString(name, "")
hasher := sha1.New()
hasher.Write([]byte(path))
sha := simpleSubdomainSafeEncoding.EncodeToString(hasher.Sum(nil))
return fmt.Sprintf("%.15s%.30s", name, sha)
}

View File

@ -21,20 +21,19 @@ import (
"io/ioutil"
"os"
"sort"
"strings"
"testing"
"time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet"
"github.com/ghodss/yaml"
)
func ExampleManifestAndPod(id string) (api.ContainerManifest, api.BoundPod) {
manifest := api.ContainerManifest{
ID: id,
UUID: "uid",
UUID: id,
Containers: []api.Container{
{
Name: "c" + id,
@ -53,9 +52,8 @@ func ExampleManifestAndPod(id string) (api.ContainerManifest, api.BoundPod) {
}
expectedPod := api.BoundPod{
ObjectMeta: api.ObjectMeta{
Name: id,
UID: "uid",
Namespace: "default",
Name: id,
UID: id,
},
Spec: api.PodSpec{
Containers: []api.Container{
@ -116,7 +114,13 @@ func writeTestFile(t *testing.T, dir, name string, contents string) *os.File {
}
func TestReadFromFile(t *testing.T) {
file := writeTestFile(t, os.TempDir(), "test_pod_config", "version: v1beta1\nid: test\ncontainers:\n- image: test/image")
file := writeTestFile(t, os.TempDir(), "test_pod_config",
`{
"version": "v1beta1",
"uuid": "12345",
"id": "test",
"containers": [{ "image": "test/image" }]
}`)
defer os.Remove(file.Name())
ch := make(chan interface{})
@ -127,14 +131,28 @@ func TestReadFromFile(t *testing.T) {
expected := CreatePodUpdate(kubelet.SET, kubelet.FileSource, api.BoundPod{
ObjectMeta: api.ObjectMeta{
Name: "test",
UID: simpleSubdomainSafeHash(file.Name()),
Namespace: "default",
SelfLink: "/api/v1beta2/pods/test?namespace=default",
UID: "12345",
Namespace: "",
SelfLink: "",
},
Spec: api.PodSpec{
Containers: []api.Container{{Image: "test/image", TerminationMessagePath: "/dev/termination-log"}},
},
})
// There's no way to provide namespace in ContainerManifest, so
// it will be defaulted.
if !strings.HasPrefix(update.Pods[0].ObjectMeta.Namespace, "file-") {
t.Errorf("Unexpected namespace: %s", update.Pods[0].ObjectMeta.Namespace)
}
update.Pods[0].ObjectMeta.Namespace = ""
// SelfLink depends on namespace.
if !strings.HasPrefix(update.Pods[0].ObjectMeta.SelfLink, "/api/") {
t.Errorf("Unexpected selflink: %s", update.Pods[0].ObjectMeta.SelfLink)
}
update.Pods[0].ObjectMeta.SelfLink = ""
if !api.Semantic.DeepEqual(expected, update) {
t.Fatalf("Expected %#v, Got %#v", expected, update)
}
@ -144,6 +162,29 @@ func TestReadFromFile(t *testing.T) {
}
}
func TestReadFromFileWithDefaults(t *testing.T) {
file := writeTestFile(t, os.TempDir(), "test_pod_config",
`{
"version": "v1beta1",
"id": "test",
"containers": [{ "image": "test/image" }]
}`)
defer os.Remove(file.Name())
ch := make(chan interface{})
NewSourceFile(file.Name(), time.Millisecond, ch)
select {
case got := <-ch:
update := got.(kubelet.PodUpdate)
if update.Pods[0].ObjectMeta.UID == "" {
t.Errorf("Unexpected UID: %s", update.Pods[0].ObjectMeta.UID)
}
case <-time.After(2 * time.Millisecond):
t.Errorf("Expected update, timeout instead")
}
}
func TestExtractFromBadDataFile(t *testing.T) {
file := writeTestFile(t, os.TempDir(), "test_pod_config", string([]byte{1, 2, 3}))
defer os.Remove(file.Name())
@ -157,30 +198,6 @@ func TestExtractFromBadDataFile(t *testing.T) {
expectEmptyChannel(t, ch)
}
func TestExtractFromValidDataFile(t *testing.T) {
manifest, expectedPod := ExampleManifestAndPod("id")
text, err := json.Marshal(manifest)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
file := writeTestFile(t, os.TempDir(), "test_pod_config", string(text))
defer os.Remove(file.Name())
expectedPod.ObjectMeta.SelfLink = "/api/v1beta2/pods/" + expectedPod.Name + "?namespace=default"
ch := make(chan interface{}, 1)
c := sourceFile{file.Name(), ch}
err = c.extractFromPath()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
update := (<-ch).(kubelet.PodUpdate)
expected := CreatePodUpdate(kubelet.SET, kubelet.FileSource, expectedPod)
if !api.Semantic.DeepEqual(expected, update) {
t.Errorf("Expected %#v, Got %#v", expected, update)
}
}
func TestExtractFromEmptyDir(t *testing.T) {
dirName, err := ioutil.TempDir("", "foo")
if err != nil {
@ -233,7 +250,6 @@ func TestExtractFromDir(t *testing.T) {
}
ioutil.WriteFile(name, data, 0755)
files[i] = file
pods[i].ObjectMeta.SelfLink = "/api/v1beta2/pods/" + pods[i].Name + "?namespace=default"
}
ch := make(chan interface{}, 1)
@ -244,7 +260,14 @@ func TestExtractFromDir(t *testing.T) {
}
update := (<-ch).(kubelet.PodUpdate)
for i := range update.Pods {
update.Pods[i].Namespace = "foobar"
update.Pods[i].SelfLink = ""
}
expected := CreatePodUpdate(kubelet.SET, kubelet.FileSource, pods...)
for i := range expected.Pods {
expected.Pods[i].Namespace = "foobar"
}
sort.Sort(sortedPods(update.Pods))
sort.Sort(sortedPods(expected.Pods))
if !api.Semantic.DeepEqual(expected, update) {
@ -256,60 +279,3 @@ func TestExtractFromDir(t *testing.T) {
}
}
}
func TestSubdomainSafeName(t *testing.T) {
type Case struct {
Input string
Expected string
}
testCases := []Case{
{"/some/path/invalidUPPERCASE", "invaliduppercasa6hlenc0vpqbbdtt26ghneqsq3pvud"},
{"/some/path/_-!%$#&@^&*(){}", "nvhc03p016m60huaiv3avts372rl2p"},
}
for _, testCase := range testCases {
value := simpleSubdomainSafeHash(testCase.Input)
if value != testCase.Expected {
t.Errorf("Expected %s, Got %s", testCase.Expected, value)
}
value2 := simpleSubdomainSafeHash(testCase.Input)
if value != value2 {
t.Errorf("Value for %s was not stable across runs: %s %s", testCase.Input, value, value2)
}
}
}
// These are used for testing extract json (below)
type TestData struct {
Value string
Number int
}
type TestObject struct {
Name string
Data TestData
}
func verifyStringEquals(t *testing.T, actual, expected string) {
if actual != expected {
t.Errorf("Verification failed. Expected: %s, Found %s", expected, actual)
}
}
func verifyIntEquals(t *testing.T, actual, expected int) {
if actual != expected {
t.Errorf("Verification failed. Expected: %d, Found %d", expected, actual)
}
}
func TestExtractJSON(t *testing.T) {
obj := TestObject{}
data := `{ "name": "foo", "data": { "value": "bar", "number": 10 } }`
if err := yaml.Unmarshal([]byte(data), &obj); err != nil {
t.Fatalf("Could not unmarshal JSON: %v", err)
}
verifyStringEquals(t, obj.Name, "foo")
verifyStringEquals(t, obj.Data.Value, "bar")
verifyIntEquals(t, obj.Data.Number, 10)
}

View File

@ -19,7 +19,10 @@ package config
import (
"bytes"
"crypto/md5"
"encoding/hex"
"fmt"
"hash/adler32"
"io/ioutil"
"net/http"
"time"
@ -93,9 +96,7 @@ func (s *sourceURL) extractFromURL() error {
if err := api.Scheme.Convert(&manifest, &pod); err != nil {
return err
}
if len(pod.Namespace) == 0 {
pod.Namespace = api.NamespaceDefault
}
applyDefaults(&pod, s.url)
s.updates <- kubelet.PodUpdate{[]api.BoundPod{pod}, kubelet.SET, kubelet.HTTPSource}
return nil
}
@ -130,12 +131,7 @@ func (s *sourceURL) extractFromURL() error {
}
for i := range boundPods.Items {
pod := &boundPods.Items[i]
if len(pod.Name) == 0 {
pod.Name = fmt.Sprintf("%d", i+1)
}
if len(pod.Namespace) == 0 {
pod.Namespace = api.NamespaceDefault
}
applyDefaults(pod, s.url)
}
s.updates <- kubelet.PodUpdate{boundPods.Items, kubelet.SET, kubelet.HTTPSource}
return nil
@ -145,3 +141,19 @@ func (s *sourceURL) extractFromURL() error {
"single manifest (%v: %+v) or as multiple manifests (%v: %+v).\n",
s.url, string(data), singleErr, manifest, multiErr, manifests)
}
func applyDefaults(pod *api.BoundPod, url string) {
if len(pod.UID) == 0 {
hasher := md5.New()
fmt.Fprintf(hasher, "url:%s", url)
util.DeepHashObject(hasher, pod)
pod.UID = hex.EncodeToString(hasher.Sum(nil)[0:])
glog.V(5).Infof("Generated UID %q for pod %q from URL %s", pod.UID, pod.Name, url)
}
if len(pod.Namespace) == 0 {
hasher := adler32.New()
fmt.Fprint(hasher, url)
pod.Namespace = fmt.Sprintf("url-%08x", hasher.Sum32())
glog.V(5).Infof("Generated namespace %q for pod %q from URL %s", pod.Namespace, pod.Name, url)
}
}

View File

@ -19,6 +19,7 @@ package config
import (
"encoding/json"
"net/http/httptest"
"strings"
"testing"
"time"
@ -26,6 +27,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
)
func TestURLErrorNotExistNoUpdate(t *testing.T) {
@ -121,13 +123,14 @@ func TestExtractFromHTTP(t *testing.T) {
}{
{
desc: "Single manifest",
manifests: api.ContainerManifest{Version: "v1beta1", ID: "foo"},
manifests: api.ContainerManifest{Version: "v1beta1", ID: "foo", UUID: "111"},
expected: CreatePodUpdate(kubelet.SET,
kubelet.HTTPSource,
api.BoundPod{
ObjectMeta: api.ObjectMeta{
UID: "111",
Name: "foo",
Namespace: "default",
Namespace: "foobar",
},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
@ -138,15 +141,16 @@ func TestExtractFromHTTP(t *testing.T) {
{
desc: "Multiple manifests",
manifests: []api.ContainerManifest{
{Version: "v1beta1", ID: "", Containers: []api.Container{{Name: "1", Image: "foo"}}},
{Version: "v1beta1", ID: "bar", Containers: []api.Container{{Name: "1", Image: "foo"}}},
{Version: "v1beta1", ID: "foo", UUID: "111", Containers: []api.Container{{Name: "1", Image: "foo"}}},
{Version: "v1beta1", ID: "bar", UUID: "222", Containers: []api.Container{{Name: "1", Image: "foo"}}},
},
expected: CreatePodUpdate(kubelet.SET,
kubelet.HTTPSource,
api.BoundPod{
ObjectMeta: api.ObjectMeta{
Name: "1",
Namespace: "default",
UID: "111",
Name: "foo",
Namespace: "foobar",
},
Spec: api.PodSpec{
Containers: []api.Container{{
@ -157,8 +161,9 @@ func TestExtractFromHTTP(t *testing.T) {
},
api.BoundPod{
ObjectMeta: api.ObjectMeta{
UID: "222",
Name: "bar",
Namespace: "default",
Namespace: "foobar",
},
Spec: api.PodSpec{
Containers: []api.Container{{
@ -192,12 +197,21 @@ func TestExtractFromHTTP(t *testing.T) {
continue
}
update := (<-ch).(kubelet.PodUpdate)
for i := range update.Pods {
// There's no way to provide namespace in ContainerManifest, so
// it will be defaulted.
if !strings.HasPrefix(update.Pods[i].ObjectMeta.Namespace, "url-") {
t.Errorf("Unexpected namespace: %s", update.Pods[0].ObjectMeta.Namespace)
}
update.Pods[i].ObjectMeta.Namespace = "foobar"
}
if !api.Semantic.DeepEqual(testCase.expected, update) {
t.Errorf("%s: Expected: %#v, Got: %#v", testCase.desc, testCase.expected, update)
}
for i := range update.Pods {
if errs := validation.ValidateBoundPod(&update.Pods[i]); len(errs) != 0 {
t.Errorf("%s: Expected no validation errors on %#v, Got %#v", testCase.desc, update.Pods[i], errs)
t.Errorf("%s: Expected no validation errors on %#v, Got %v", testCase.desc, update.Pods[i], errors.NewAggregate(errs))
}
}
}

View File

@ -545,10 +545,10 @@ func HashContainer(container *api.Container) uint64 {
}
// Creates a name which can be reversed to identify both full pod name and container name.
func BuildDockerName(manifestUUID, podFullName string, container *api.Container) string {
func BuildDockerName(podUID, podFullName string, container *api.Container) string {
containerName := container.Name + "." + strconv.FormatUint(HashContainer(container), 16)
// Note, manifest.ID could be blank.
if len(manifestUUID) == 0 {
if len(podUID) == 0 {
return fmt.Sprintf("%s_%s_%s_%08x",
containerNamePrefix,
containerName,
@ -559,7 +559,7 @@ func BuildDockerName(manifestUUID, podFullName string, container *api.Container)
containerNamePrefix,
containerName,
podFullName,
manifestUUID,
podUID,
rand.Uint32())
}
}
@ -590,6 +590,12 @@ func ParseDockerName(name string) (podFullName, uuid, containerName string, hash
if len(parts) > 2 {
podFullName = parts[2]
}
// This is not an off-by-one. We check for > 4 here because (sadly) the
// format generated by BuildDockerName() has an optional field in the
// middle. If len(parts) > 3, parts[3] might be the optional field or
// the (poorly documented) random suffix. If len(parts) > 4, then we
// know [3] is the UUID and [4] is the suffix. Sort of pukey, should
// be fixed by making UID non-optional.
if len(parts) > 4 {
uuid = parts[3]
}

View File

@ -85,31 +85,31 @@ func TestGetContainerID(t *testing.T) {
}
}
func verifyPackUnpack(t *testing.T, podNamespace, manifestUUID, podName, containerName string) {
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(manifestUUID, podFullName, container)
returnedPodFullName, returnedUUID, returnedContainerName, hash := ParseDockerName(name)
if podFullName != returnedPodFullName || manifestUUID != returnedUUID || containerName != returnedContainerName || computedHash != hash {
t.Errorf("For (%s, %s, %s, %d), unpacked (%s, %s, %s, %d)", podFullName, manifestUUID, containerName, computedHash, returnedPodFullName, returnedUUID, returnedContainerName, hash)
name := BuildDockerName(podUID, podFullName, container)
returnedPodFullName, returnedUID, returnedContainerName, hash := ParseDockerName(name)
if podFullName != returnedPodFullName || podUID != returnedUID || containerName != returnedContainerName || computedHash != hash {
t.Errorf("For (%s, %s, %s, %d), unpacked (%s, %s, %s, %d)", podFullName, podUID, containerName, computedHash, returnedPodFullName, returnedUID, returnedContainerName, hash)
}
}
func TestContainerManifestNaming(t *testing.T) {
manifestUUID := "d1b925c9-444a-11e4-a576-42010af0a203"
verifyPackUnpack(t, "file", manifestUUID, "manifest1234", "container5678")
verifyPackUnpack(t, "file", manifestUUID, "mani-fest-1234", "container5678")
// UUID is same as pod name
verifyPackUnpack(t, "file", manifestUUID, manifestUUID, "container123")
podUID := "d1b925c9-444a-11e4-a576-42010af0a203"
verifyPackUnpack(t, "file", podUID, "manifest1234", "container5678")
verifyPackUnpack(t, "file", podUID, "mani-fest-1234", "container5678")
// UID is same as pod name
verifyPackUnpack(t, "file", podUID, podUID, "container123")
// empty namespace
verifyPackUnpack(t, "", manifestUUID, manifestUUID, "container123")
// No UUID
verifyPackUnpack(t, "other", "", manifestUUID, "container456")
verifyPackUnpack(t, "", podUID, podUID, "container123")
// No UID
verifyPackUnpack(t, "other", "", podUID, "container456")
// No Container name
verifyPackUnpack(t, "other", "", manifestUUID, "")
verifyPackUnpack(t, "other", "", podUID, "")
container := &api.Container{Name: "container"}
podName := "foo"

View File

@ -1056,8 +1056,7 @@ func (kl *Kubelet) SyncPods(pods []api.BoundPod) error {
// Run the sync in an async manifest worker.
kl.podWorkers.Run(podFullName, func() {
err := kl.syncPod(pod, dockerContainers)
if err != nil {
if err := kl.syncPod(pod, dockerContainers); err != nil {
glog.Errorf("Error syncing pod, skipping: %v", err)
record.Eventf(pod, "", "failedSync", "Error syncing pod, skipping: %v", err)
}

View File

@ -291,19 +291,20 @@ func TestSyncPodsDoesNothing(t *testing.T) {
container := api.Container{Name: "bar"}
fakeDocker.ContainerList = []docker.APIContainers{
{
// format is k8s_<container-id>_<pod-fullname>
Names: []string{"/k8s_bar." + strconv.FormatUint(dockertools.HashContainer(&container), 16) + "_foo.new.test"},
// format is // k8s_<container-id>_<pod-fullname>_<pod-uid>_<random>
Names: []string{"/k8s_bar." + strconv.FormatUint(dockertools.HashContainer(&container), 16) + "_foo.new.test_12345678_0"},
ID: "1234",
},
{
// network container
Names: []string{"/k8s_net_foo.new.test_"},
Names: []string{"/k8s_net_foo.new.test_12345678_0"},
ID: "9876",
},
}
err := kubelet.SyncPods([]api.BoundPod{
{
ObjectMeta: api.ObjectMeta{
UID: "12345678",
Name: "foo",
Namespace: "new",
Annotations: map[string]string{ConfigSourceAnnotationKey: "test"},
@ -476,13 +477,14 @@ func TestSyncPodsWithNetCreatesContainer(t *testing.T) {
fakeDocker.ContainerList = []docker.APIContainers{
{
// network container
Names: []string{"/k8s_net_foo.new.test_"},
Names: []string{"/k8s_net_foo.new.test_12345678_0"},
ID: "9876",
},
}
err := kubelet.SyncPods([]api.BoundPod{
{
ObjectMeta: api.ObjectMeta{
UID: "12345678",
Name: "foo",
Namespace: "new",
Annotations: map[string]string{ConfigSourceAnnotationKey: "test"},
@ -517,13 +519,14 @@ func TestSyncPodsWithNetCreatesContainerCallsHandler(t *testing.T) {
fakeDocker.ContainerList = []docker.APIContainers{
{
// network container
Names: []string{"/k8s_net_foo.new.test_"},
Names: []string{"/k8s_net_foo.new.test_12345678_0"},
ID: "9876",
},
}
err := kubelet.SyncPods([]api.BoundPod{
{
ObjectMeta: api.ObjectMeta{
UID: "12345678",
Name: "foo",
Namespace: "new",
Annotations: map[string]string{ConfigSourceAnnotationKey: "test"},
@ -569,14 +572,15 @@ func TestSyncPodsDeletesWithNoNetContainer(t *testing.T) {
kubelet, _, fakeDocker := newTestKubelet(t)
fakeDocker.ContainerList = []docker.APIContainers{
{
// format is k8s_<container-id>_<pod-fullname>
Names: []string{"/k8s_bar_foo.new.test"},
// format is // k8s_<container-id>_<pod-fullname>_<pod-uid>
Names: []string{"/k8s_bar_foo.new.test_12345678_0"},
ID: "1234",
},
}
err := kubelet.SyncPods([]api.BoundPod{
{
ObjectMeta: api.ObjectMeta{
UID: "12345678",
Name: "foo",
Namespace: "new",
Annotations: map[string]string{ConfigSourceAnnotationKey: "test"},
@ -694,17 +698,17 @@ func TestSyncPodDeletesDuplicate(t *testing.T) {
dockerContainers := dockertools.DockerContainers{
"1234": &docker.APIContainers{
// the k8s prefix is required for the kubelet to manage the container
Names: []string{"/k8s_foo_bar.new.test_1"},
Names: []string{"/k8s_foo_bar.new.test_12345678_1111"},
ID: "1234",
},
"9876": &docker.APIContainers{
// network container
Names: []string{"/k8s_net_bar.new.test_"},
Names: []string{"/k8s_net_bar.new.test_12345678_2222"},
ID: "9876",
},
"4567": &docker.APIContainers{
// Duplicate for the same container.
Names: []string{"/k8s_foo_bar.new.test_2"},
Names: []string{"/k8s_foo_bar.new.test_12345678_3333"},
ID: "4567",
},
"2304": &docker.APIContainers{
@ -715,6 +719,7 @@ func TestSyncPodDeletesDuplicate(t *testing.T) {
}
err := kubelet.syncPod(&api.BoundPod{
ObjectMeta: api.ObjectMeta{
UID: "12345678",
Name: "bar",
Namespace: "new",
Annotations: map[string]string{ConfigSourceAnnotationKey: "test"},
@ -732,7 +737,7 @@ func TestSyncPodDeletesDuplicate(t *testing.T) {
verifyCalls(t, fakeDocker, []string{"list", "stop"})
// Expect one of the duplicates to be killed.
if len(fakeDocker.Stopped) != 1 || (len(fakeDocker.Stopped) != 0 && fakeDocker.Stopped[0] != "1234" && fakeDocker.Stopped[0] != "4567") {
if len(fakeDocker.Stopped) != 1 || (fakeDocker.Stopped[0] != "1234" && fakeDocker.Stopped[0] != "4567") {
t.Errorf("Wrong containers were stopped: %v", fakeDocker.Stopped)
}
}

View File

@ -22,7 +22,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
)
const ConfigSourceAnnotationKey = "kubernetes/config.source"
const ConfigSourceAnnotationKey = "kubernetes.io/config.source"
// PodOperation defines what changes will be made on a pod configuration.
type PodOperation int