From 5a4621f45188c2756d6845626448b242871fbbc3 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Thu, 10 Jul 2014 10:26:40 -0700 Subject: [PATCH 01/45] Try to grab live data if the cache has errors. --- pkg/registry/pod_registry.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pkg/registry/pod_registry.go b/pkg/registry/pod_registry.go index bc6415bc294..cb4feb067c0 100644 --- a/pkg/registry/pod_registry.go +++ b/pkg/registry/pod_registry.go @@ -85,8 +85,14 @@ func (storage *PodRegistryStorage) fillPodInfo(pod *api.Pod) { if storage.podCache != nil { info, err := storage.podCache.GetPodInfo(pod.CurrentState.Host, pod.ID) if err != nil { - glog.Errorf("Error getting container info: %#v", err) - return + glog.Errorf("Error getting container info from cache: %#v", err) + if storage.podInfoGetter != nil { + info, err = storage.podInfoGetter.GetPodInfo(pod.CurrentState.Host, pod.ID) + } + if err != nil { + glog.Errorf("Error getting fresh container info: %#v", err) + return + } } pod.CurrentState.Info = info } From 31e40e1a6889d5387b2ead8b2e6990d9d531c132 Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Wed, 25 Jun 2014 21:24:26 -0400 Subject: [PATCH 02/45] bump(code.google.com/p/go-uuid/uuid): 3da0bf12e76e1048469b95fb20e29f1bc9799ae1 --- .../code.google.com/p/go-uuid/uuid/LICENSE | 27 ++ .../src/code.google.com/p/go-uuid/uuid/dce.go | 84 ++++ .../src/code.google.com/p/go-uuid/uuid/doc.go | 8 + .../code.google.com/p/go-uuid/uuid/hash.go | 53 +++ .../code.google.com/p/go-uuid/uuid/node.go | 101 +++++ .../code.google.com/p/go-uuid/uuid/time.go | 132 ++++++ .../code.google.com/p/go-uuid/uuid/util.go | 43 ++ .../code.google.com/p/go-uuid/uuid/uuid.go | 163 ++++++++ .../p/go-uuid/uuid/uuid_test.go | 390 ++++++++++++++++++ .../p/go-uuid/uuid/version1.go | 41 ++ .../p/go-uuid/uuid/version4.go | 25 ++ 11 files changed, 1067 insertions(+) create mode 100644 third_party/src/code.google.com/p/go-uuid/uuid/LICENSE create mode 100755 third_party/src/code.google.com/p/go-uuid/uuid/dce.go create mode 100755 third_party/src/code.google.com/p/go-uuid/uuid/doc.go create mode 100644 third_party/src/code.google.com/p/go-uuid/uuid/hash.go create mode 100755 third_party/src/code.google.com/p/go-uuid/uuid/node.go create mode 100755 third_party/src/code.google.com/p/go-uuid/uuid/time.go create mode 100644 third_party/src/code.google.com/p/go-uuid/uuid/util.go create mode 100755 third_party/src/code.google.com/p/go-uuid/uuid/uuid.go create mode 100755 third_party/src/code.google.com/p/go-uuid/uuid/uuid_test.go create mode 100644 third_party/src/code.google.com/p/go-uuid/uuid/version1.go create mode 100644 third_party/src/code.google.com/p/go-uuid/uuid/version4.go diff --git a/third_party/src/code.google.com/p/go-uuid/uuid/LICENSE b/third_party/src/code.google.com/p/go-uuid/uuid/LICENSE new file mode 100644 index 00000000000..ab6b011a109 --- /dev/null +++ b/third_party/src/code.google.com/p/go-uuid/uuid/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/third_party/src/code.google.com/p/go-uuid/uuid/dce.go b/third_party/src/code.google.com/p/go-uuid/uuid/dce.go new file mode 100755 index 00000000000..50a0f2d0992 --- /dev/null +++ b/third_party/src/code.google.com/p/go-uuid/uuid/dce.go @@ -0,0 +1,84 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" + "fmt" + "os" +) + +// A Domain represents a Version 2 domain +type Domain byte + +// Domain constants for DCE Security (Version 2) UUIDs. +const ( + Person = Domain(0) + Group = Domain(1) + Org = Domain(2) +) + +// NewDCESecurity returns a DCE Security (Version 2) UUID. +// +// The domain should be one of Person, Group or Org. +// On a POSIX system the id should be the users UID for the Person +// domain and the users GID for the Group. The meaning of id for +// the domain Org or on non-POSIX systems is site defined. +// +// For a given domain/id pair the same token may be returned for up to +// 7 minutes and 10 seconds. +func NewDCESecurity(domain Domain, id uint32) UUID { + uuid := NewUUID() + if uuid != nil { + uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2 + uuid[9] = byte(domain) + binary.BigEndian.PutUint32(uuid[0:], id) + } + return uuid +} + +// NewDCEPerson returns a DCE Security (Version 2) UUID in the person +// domain with the id returned by os.Getuid. +// +// NewDCEPerson(Person, uint32(os.Getuid())) +func NewDCEPerson() UUID { + return NewDCESecurity(Person, uint32(os.Getuid())) +} + +// NewDCEGroup returns a DCE Security (Version 2) UUID in the group +// domain with the id returned by os.Getgid. +// +// NewDCEGroup(Group, uint32(os.Getgid())) +func NewDCEGroup() UUID { + return NewDCESecurity(Group, uint32(os.Getgid())) +} + +// Domain returns the domain for a Version 2 UUID or false. +func (uuid UUID) Domain() (Domain, bool) { + if v, _ := uuid.Version(); v != 2 { + return 0, false + } + return Domain(uuid[9]), true +} + +// Id returns the id for a Version 2 UUID or false. +func (uuid UUID) Id() (uint32, bool) { + if v, _ := uuid.Version(); v != 2 { + return 0, false + } + return binary.BigEndian.Uint32(uuid[0:4]), true +} + +func (d Domain) String() string { + switch d { + case Person: + return "Person" + case Group: + return "Group" + case Org: + return "Org" + } + return fmt.Sprintf("Domain%d", int(d)) +} diff --git a/third_party/src/code.google.com/p/go-uuid/uuid/doc.go b/third_party/src/code.google.com/p/go-uuid/uuid/doc.go new file mode 100755 index 00000000000..d8bd013e689 --- /dev/null +++ b/third_party/src/code.google.com/p/go-uuid/uuid/doc.go @@ -0,0 +1,8 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// The uuid package generates and inspects UUIDs. +// +// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security Services. +package uuid diff --git a/third_party/src/code.google.com/p/go-uuid/uuid/hash.go b/third_party/src/code.google.com/p/go-uuid/uuid/hash.go new file mode 100644 index 00000000000..cdd4192fd9b --- /dev/null +++ b/third_party/src/code.google.com/p/go-uuid/uuid/hash.go @@ -0,0 +1,53 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "crypto/md5" + "crypto/sha1" + "hash" +) + +// Well known Name Space IDs and UUIDs +var ( + NameSpace_DNS = Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8") + NameSpace_URL = Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8") + NameSpace_OID = Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8") + NameSpace_X500 = Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8") + NIL = Parse("00000000-0000-0000-0000-000000000000") +) + +// NewHash returns a new UUID dervied from the hash of space concatenated with +// data generated by h. The hash should be at least 16 byte in length. The +// first 16 bytes of the hash are used to form the UUID. The version of the +// UUID will be the lower 4 bits of version. NewHash is used to implement +// NewMD5 and NewSHA1. +func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID { + h.Reset() + h.Write(space) + h.Write([]byte(data)) + s := h.Sum(nil) + uuid := make([]byte, 16) + copy(uuid, s) + uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4) + uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant + return uuid +} + +// NewMD5 returns a new MD5 (Version 3) UUID based on the +// supplied name space and data. +// +// NewHash(md5.New(), space, data, 3) +func NewMD5(space UUID, data []byte) UUID { + return NewHash(md5.New(), space, data, 3) +} + +// NewSHA1 returns a new SHA1 (Version 5) UUID based on the +// supplied name space and data. +// +// NewHash(sha1.New(), space, data, 5) +func NewSHA1(space UUID, data []byte) UUID { + return NewHash(sha1.New(), space, data, 5) +} diff --git a/third_party/src/code.google.com/p/go-uuid/uuid/node.go b/third_party/src/code.google.com/p/go-uuid/uuid/node.go new file mode 100755 index 00000000000..dd0a8ac189a --- /dev/null +++ b/third_party/src/code.google.com/p/go-uuid/uuid/node.go @@ -0,0 +1,101 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import "net" + +var ( + interfaces []net.Interface // cached list of interfaces + ifname string // name of interface being used + nodeID []byte // hardware for version 1 UUIDs +) + +// NodeInterface returns the name of the interface from which the NodeID was +// derived. The interface "user" is returned if the NodeID was set by +// SetNodeID. +func NodeInterface() string { + return ifname +} + +// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs. +// If name is "" then the first usable interface found will be used or a random +// Node ID will be generated. If a named interface cannot be found then false +// is returned. +// +// SetNodeInterface never fails when name is "". +func SetNodeInterface(name string) bool { + if interfaces == nil { + var err error + interfaces, err = net.Interfaces() + if err != nil && name != "" { + return false + } + } + + for _, ifs := range interfaces { + if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) { + if setNodeID(ifs.HardwareAddr) { + ifname = ifs.Name + return true + } + } + } + + // We found no interfaces with a valid hardware address. If name + // does not specify a specific interface generate a random Node ID + // (section 4.1.6) + if name == "" { + if nodeID == nil { + nodeID = make([]byte, 6) + } + randomBits(nodeID) + return true + } + return false +} + +// NodeID returns a slice of a copy of the current Node ID, setting the Node ID +// if not already set. +func NodeID() []byte { + if nodeID == nil { + SetNodeInterface("") + } + nid := make([]byte, 6) + copy(nid, nodeID) + return nid +} + +// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes +// of id are used. If id is less than 6 bytes then false is returned and the +// Node ID is not set. +func SetNodeID(id []byte) bool { + if setNodeID(id) { + ifname = "user" + return true + } + return false +} + +func setNodeID(id []byte) bool { + if len(id) < 6 { + return false + } + if nodeID == nil { + nodeID = make([]byte, 6) + } + copy(nodeID, id) + return true +} + +// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is +// not valid. The NodeID is only well defined for version 1 and 2 UUIDs. +func (uuid UUID) NodeID() []byte { + if len(uuid) != 16 { + return nil + } + node := make([]byte, 6) + copy(node, uuid[10:]) + return node +} diff --git a/third_party/src/code.google.com/p/go-uuid/uuid/time.go b/third_party/src/code.google.com/p/go-uuid/uuid/time.go new file mode 100755 index 00000000000..b9369c200b9 --- /dev/null +++ b/third_party/src/code.google.com/p/go-uuid/uuid/time.go @@ -0,0 +1,132 @@ +// Copyright 2014 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" + "sync" + "time" +) + +// A Time represents a time as the number of 100's of nanoseconds since 15 Oct +// 1582. +type Time int64 + +const ( + lillian = 2299160 // Julian day of 15 Oct 1582 + unix = 2440587 // Julian day of 1 Jan 1970 + epoch = unix - lillian // Days between epochs + g1582 = epoch * 86400 // seconds between epochs + g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs +) + +var ( + mu sync.Mutex + lasttime uint64 // last time we returned + clock_seq uint16 // clock sequence for this run + + timeNow = time.Now // for testing +) + +// UnixTime converts t the number of seconds and nanoseconds using the Unix +// epoch of 1 Jan 1970. +func (t Time) UnixTime() (sec, nsec int64) { + sec = int64(t - g1582ns100) + nsec = (sec % 10000000) * 100 + sec /= 10000000 + return sec, nsec +} + +// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and +// adjusts the clock sequence as needed. An error is returned if the current +// time cannot be determined. +func GetTime() (Time, error) { + defer mu.Unlock() + mu.Lock() + return getTime() +} + +func getTime() (Time, error) { + t := timeNow() + + // If we don't have a clock sequence already, set one. + if clock_seq == 0 { + setClockSequence(-1) + } + now := uint64(t.UnixNano()/100) + g1582ns100 + + // If time has gone backwards with this clock sequence then we + // increment the clock sequence + if now <= lasttime { + clock_seq = ((clock_seq + 1) & 0x3fff) | 0x8000 + } + lasttime = now + return Time(now), nil +} + +// ClockSequence returns the current clock sequence, generating one if not +// already set. The clock sequence is only used for Version 1 UUIDs. +// +// The uuid package does not use global static storage for the clock sequence or +// the last time a UUID was generated. Unless SetClockSequence a new random +// clock sequence is generated the first time a clock sequence is requested by +// ClockSequence, GetTime, or NewUUID. (section 4.2.1.1) sequence is generated +// for +func ClockSequence() int { + defer mu.Unlock() + mu.Lock() + return clockSequence() +} + +func clockSequence() int { + if clock_seq == 0 { + setClockSequence(-1) + } + return int(clock_seq & 0x3fff) +} + +// SetClockSeq sets the clock sequence to the lower 14 bits of seq. Setting to +// -1 causes a new sequence to be generated. +func SetClockSequence(seq int) { + defer mu.Unlock() + mu.Lock() + setClockSequence(seq) +} + +func setClockSequence(seq int) { + if seq == -1 { + var b [2]byte + randomBits(b[:]) // clock sequence + seq = int(b[0])<<8 | int(b[1]) + } + old_seq := clock_seq + clock_seq = uint16(seq&0x3fff) | 0x8000 // Set our variant + if old_seq != clock_seq { + lasttime = 0 + } +} + +// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in +// uuid. It returns false if uuid is not valid. The time is only well defined +// for version 1 and 2 UUIDs. +func (uuid UUID) Time() (Time, bool) { + if len(uuid) != 16 { + return 0, false + } + time := int64(binary.BigEndian.Uint32(uuid[0:4])) + time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32 + time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48 + return Time(time), true +} + +// ClockSequence returns the clock sequence encoded in uuid. It returns false +// if uuid is not valid. The clock sequence is only well defined for version 1 +// and 2 UUIDs. +func (uuid UUID) ClockSequence() (int, bool) { + if len(uuid) != 16 { + return 0, false + } + return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff, true +} diff --git a/third_party/src/code.google.com/p/go-uuid/uuid/util.go b/third_party/src/code.google.com/p/go-uuid/uuid/util.go new file mode 100644 index 00000000000..de40b102c4b --- /dev/null +++ b/third_party/src/code.google.com/p/go-uuid/uuid/util.go @@ -0,0 +1,43 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "io" +) + +// randomBits completely fills slice b with random data. +func randomBits(b []byte) { + if _, err := io.ReadFull(rander, b); err != nil { + panic(err.Error()) // rand should never fail + } +} + +// xvalues returns the value of a byte as a hexadecimal digit or 255. +var xvalues = []byte{ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255, + 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, +} + +// xtob converts the the first two hex bytes of x into a byte. +func xtob(x string) (byte, bool) { + b1 := xvalues[x[0]] + b2 := xvalues[x[1]] + return (b1 << 4) | b2, b1 != 255 && b2 != 255 +} diff --git a/third_party/src/code.google.com/p/go-uuid/uuid/uuid.go b/third_party/src/code.google.com/p/go-uuid/uuid/uuid.go new file mode 100755 index 00000000000..2920fae6326 --- /dev/null +++ b/third_party/src/code.google.com/p/go-uuid/uuid/uuid.go @@ -0,0 +1,163 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "bytes" + "crypto/rand" + "fmt" + "io" + "strings" +) + +// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC +// 4122. +type UUID []byte + +// A Version represents a UUIDs version. +type Version byte + +// A Variant represents a UUIDs variant. +type Variant byte + +// Constants returned by Variant. +const ( + Invalid = Variant(iota) // Invalid UUID + RFC4122 // The variant specified in RFC4122 + Reserved // Reserved, NCS backward compatibility. + Microsoft // Reserved, Microsoft Corporation backward compatibility. + Future // Reserved for future definition. +) + +var rander = rand.Reader // random function + +// New returns a new random (version 4) UUID as a string. It is a convenience +// function for NewRandom().String(). +func New() string { + return NewRandom().String() +} + +// Parse decodes s into a UUID or returns nil. Both the UUID form of +// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and +// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded. +func Parse(s string) UUID { + if len(s) == 36+9 { + if strings.ToLower(s[:9]) != "urn:uuid:" { + return nil + } + s = s[9:] + } else if len(s) != 36 { + return nil + } + if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' { + return nil + } + uuid := make([]byte, 16) + for i, x := range []int{ + 0, 2, 4, 6, + 9, 11, + 14, 16, + 19, 21, + 24, 26, 28, 30, 32, 34} { + if v, ok := xtob(s[x:]); !ok { + return nil + } else { + uuid[i] = v + } + } + return uuid +} + +// Equal returns true if uuid1 and uuid2 are equal. +func Equal(uuid1, uuid2 UUID) bool { + return bytes.Equal(uuid1, uuid2) +} + +// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +// , or "" if uuid is invalid. +func (uuid UUID) String() string { + if uuid == nil || len(uuid) != 16 { + return "" + } + b := []byte(uuid) + return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x", + b[:4], b[4:6], b[6:8], b[8:10], b[10:]) +} + +// URN returns the RFC 2141 URN form of uuid, +// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid. +func (uuid UUID) URN() string { + if uuid == nil || len(uuid) != 16 { + return "" + } + b := []byte(uuid) + return fmt.Sprintf("urn:uuid:%08x-%04x-%04x-%04x-%012x", + b[:4], b[4:6], b[6:8], b[8:10], b[10:]) +} + +// Variant returns the variant encoded in uuid. It returns Invalid if +// uuid is invalid. +func (uuid UUID) Variant() Variant { + if len(uuid) != 16 { + return Invalid + } + switch { + case (uuid[8] & 0xc0) == 0x80: + return RFC4122 + case (uuid[8] & 0xe0) == 0xc0: + return Microsoft + case (uuid[8] & 0xe0) == 0xe0: + return Future + default: + return Reserved + } + panic("unreachable") +} + +// Version returns the verison of uuid. It returns false if uuid is not +// valid. +func (uuid UUID) Version() (Version, bool) { + if len(uuid) != 16 { + return 0, false + } + return Version(uuid[6] >> 4), true +} + +func (v Version) String() string { + if v > 15 { + return fmt.Sprintf("BAD_VERSION_%d", v) + } + return fmt.Sprintf("VERSION_%d", v) +} + +func (v Variant) String() string { + switch v { + case RFC4122: + return "RFC4122" + case Reserved: + return "Reserved" + case Microsoft: + return "Microsoft" + case Future: + return "Future" + case Invalid: + return "Invalid" + } + return fmt.Sprintf("BadVariant%d", int(v)) +} + +// SetRand sets the random number generator to r, which implents io.Reader. +// If r.Read returns an error when the package requests random data then +// a panic will be issued. +// +// Calling SetRand with nil sets the random number generator to the default +// generator. +func SetRand(r io.Reader) { + if r == nil { + rander = rand.Reader + return + } + rander = r +} diff --git a/third_party/src/code.google.com/p/go-uuid/uuid/uuid_test.go b/third_party/src/code.google.com/p/go-uuid/uuid/uuid_test.go new file mode 100755 index 00000000000..417ebeb26aa --- /dev/null +++ b/third_party/src/code.google.com/p/go-uuid/uuid/uuid_test.go @@ -0,0 +1,390 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "bytes" + "fmt" + "os" + "strings" + "testing" + "time" +) + +type test struct { + in string + version Version + variant Variant + isuuid bool +} + +var tests = []test{ + {"f47ac10b-58cc-0372-8567-0e02b2c3d479", 0, RFC4122, true}, + {"f47ac10b-58cc-1372-8567-0e02b2c3d479", 1, RFC4122, true}, + {"f47ac10b-58cc-2372-8567-0e02b2c3d479", 2, RFC4122, true}, + {"f47ac10b-58cc-3372-8567-0e02b2c3d479", 3, RFC4122, true}, + {"f47ac10b-58cc-4372-8567-0e02b2c3d479", 4, RFC4122, true}, + {"f47ac10b-58cc-5372-8567-0e02b2c3d479", 5, RFC4122, true}, + {"f47ac10b-58cc-6372-8567-0e02b2c3d479", 6, RFC4122, true}, + {"f47ac10b-58cc-7372-8567-0e02b2c3d479", 7, RFC4122, true}, + {"f47ac10b-58cc-8372-8567-0e02b2c3d479", 8, RFC4122, true}, + {"f47ac10b-58cc-9372-8567-0e02b2c3d479", 9, RFC4122, true}, + {"f47ac10b-58cc-a372-8567-0e02b2c3d479", 10, RFC4122, true}, + {"f47ac10b-58cc-b372-8567-0e02b2c3d479", 11, RFC4122, true}, + {"f47ac10b-58cc-c372-8567-0e02b2c3d479", 12, RFC4122, true}, + {"f47ac10b-58cc-d372-8567-0e02b2c3d479", 13, RFC4122, true}, + {"f47ac10b-58cc-e372-8567-0e02b2c3d479", 14, RFC4122, true}, + {"f47ac10b-58cc-f372-8567-0e02b2c3d479", 15, RFC4122, true}, + + {"urn:uuid:f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true}, + {"URN:UUID:f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-1567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-2567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-3567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-4567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-5567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-6567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-7567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-8567-0e02b2c3d479", 4, RFC4122, true}, + {"f47ac10b-58cc-4372-9567-0e02b2c3d479", 4, RFC4122, true}, + {"f47ac10b-58cc-4372-a567-0e02b2c3d479", 4, RFC4122, true}, + {"f47ac10b-58cc-4372-b567-0e02b2c3d479", 4, RFC4122, true}, + {"f47ac10b-58cc-4372-c567-0e02b2c3d479", 4, Microsoft, true}, + {"f47ac10b-58cc-4372-d567-0e02b2c3d479", 4, Microsoft, true}, + {"f47ac10b-58cc-4372-e567-0e02b2c3d479", 4, Future, true}, + {"f47ac10b-58cc-4372-f567-0e02b2c3d479", 4, Future, true}, + + {"f47ac10b158cc-5372-a567-0e02b2c3d479", 0, Invalid, false}, + {"f47ac10b-58cc25372-a567-0e02b2c3d479", 0, Invalid, false}, + {"f47ac10b-58cc-53723a567-0e02b2c3d479", 0, Invalid, false}, + {"f47ac10b-58cc-5372-a56740e02b2c3d479", 0, Invalid, false}, + {"f47ac10b-58cc-5372-a567-0e02-2c3d479", 0, Invalid, false}, + {"g47ac10b-58cc-4372-a567-0e02b2c3d479", 0, Invalid, false}, +} + +var constants = []struct { + c interface{} + name string +}{ + {Person, "Person"}, + {Group, "Group"}, + {Org, "Org"}, + {Invalid, "Invalid"}, + {RFC4122, "RFC4122"}, + {Reserved, "Reserved"}, + {Microsoft, "Microsoft"}, + {Future, "Future"}, + {Domain(17), "Domain17"}, + {Variant(42), "BadVariant42"}, +} + +func testTest(t *testing.T, in string, tt test) { + uuid := Parse(in) + if ok := (uuid != nil); ok != tt.isuuid { + t.Errorf("Parse(%s) got %v expected %v\b", in, ok, tt.isuuid) + } + if uuid == nil { + return + } + + if v := uuid.Variant(); v != tt.variant { + t.Errorf("Variant(%s) got %d expected %d\b", in, v, tt.variant) + } + if v, _ := uuid.Version(); v != tt.version { + t.Errorf("Version(%s) got %d expected %d\b", in, v, tt.version) + } +} + +func TestUUID(t *testing.T) { + for _, tt := range tests { + testTest(t, tt.in, tt) + testTest(t, strings.ToUpper(tt.in), tt) + } +} + +func TestConstants(t *testing.T) { + for x, tt := range constants { + v, ok := tt.c.(fmt.Stringer) + if !ok { + t.Errorf("%x: %v: not a stringer", x, v) + } else if s := v.String(); s != tt.name { + v, _ := tt.c.(int) + t.Errorf("%x: Constant %T:%d gives %q, expected %q\n", x, tt.c, v, s, tt.name) + } + } +} + +func TestRandomUUID(t *testing.T) { + m := make(map[string]bool) + for x := 1; x < 32; x++ { + uuid := NewRandom() + s := uuid.String() + if m[s] { + t.Errorf("NewRandom returned duplicated UUID %s\n", s) + } + m[s] = true + if v, _ := uuid.Version(); v != 4 { + t.Errorf("Random UUID of version %s\n", v) + } + if uuid.Variant() != RFC4122 { + t.Errorf("Random UUID is variant %d\n", uuid.Variant()) + } + } +} + +func TestNew(t *testing.T) { + m := make(map[string]bool) + for x := 1; x < 32; x++ { + s := New() + if m[s] { + t.Errorf("New returned duplicated UUID %s\n", s) + } + m[s] = true + uuid := Parse(s) + if uuid == nil { + t.Errorf("New returned %q which does not decode\n", s) + continue + } + if v, _ := uuid.Version(); v != 4 { + t.Errorf("Random UUID of version %s\n", v) + } + if uuid.Variant() != RFC4122 { + t.Errorf("Random UUID is variant %d\n", uuid.Variant()) + } + } +} + +func clockSeq(t *testing.T, uuid UUID) int { + seq, ok := uuid.ClockSequence() + if !ok { + t.Fatalf("%s: invalid clock sequence\n", uuid) + } + return seq +} + +func TestClockSeq(t *testing.T) { + // Fake time.Now for this test to return a monotonically advancing time; restore it at end. + defer func(orig func() time.Time) { timeNow = orig }(timeNow) + monTime := time.Now() + timeNow = func() time.Time { + monTime = monTime.Add(1 * time.Second) + return monTime + } + + SetClockSequence(-1) + uuid1 := NewUUID() + uuid2 := NewUUID() + + if clockSeq(t, uuid1) != clockSeq(t, uuid2) { + t.Errorf("clock sequence %d != %d\n", clockSeq(t, uuid1), clockSeq(t, uuid2)) + } + + SetClockSequence(-1) + uuid2 = NewUUID() + + // Just on the very off chance we generated the same sequence + // two times we try again. + if clockSeq(t, uuid1) == clockSeq(t, uuid2) { + SetClockSequence(-1) + uuid2 = NewUUID() + } + if clockSeq(t, uuid1) == clockSeq(t, uuid2) { + t.Errorf("Duplicate clock sequence %d\n", clockSeq(t, uuid1)) + } + + SetClockSequence(0x1234) + uuid1 = NewUUID() + if seq := clockSeq(t, uuid1); seq != 0x1234 { + t.Errorf("%s: expected seq 0x1234 got 0x%04x\n", uuid1, seq) + } +} + +func TestCoding(t *testing.T) { + text := "7d444840-9dc0-11d1-b245-5ffdce74fad2" + urn := "urn:uuid:7d444840-9dc0-11d1-b245-5ffdce74fad2" + data := UUID{ + 0x7d, 0x44, 0x48, 0x40, + 0x9d, 0xc0, + 0x11, 0xd1, + 0xb2, 0x45, + 0x5f, 0xfd, 0xce, 0x74, 0xfa, 0xd2, + } + if v := data.String(); v != text { + t.Errorf("%x: encoded to %s, expected %s\n", data, v, text) + } + if v := data.URN(); v != urn { + t.Errorf("%x: urn is %s, expected %s\n", data, v, urn) + } + + uuid := Parse(text) + if !Equal(uuid, data) { + t.Errorf("%s: decoded to %s, expected %s\n", text, uuid, data) + } +} + +func TestVersion1(t *testing.T) { + uuid1 := NewUUID() + uuid2 := NewUUID() + + if Equal(uuid1, uuid2) { + t.Errorf("%s:duplicate uuid\n", uuid1) + } + if v, _ := uuid1.Version(); v != 1 { + t.Errorf("%s: version %s expected 1\n", uuid1, v) + } + if v, _ := uuid2.Version(); v != 1 { + t.Errorf("%s: version %s expected 1\n", uuid2, v) + } + n1 := uuid1.NodeID() + n2 := uuid2.NodeID() + if !bytes.Equal(n1, n2) { + t.Errorf("Different nodes %x != %x\n", n1, n2) + } + t1, ok := uuid1.Time() + if !ok { + t.Errorf("%s: invalid time\n", uuid1) + } + t2, ok := uuid2.Time() + if !ok { + t.Errorf("%s: invalid time\n", uuid2) + } + q1, ok := uuid1.ClockSequence() + if !ok { + t.Errorf("%s: invalid clock sequence\n", uuid1) + } + q2, ok := uuid2.ClockSequence() + if !ok { + t.Errorf("%s: invalid clock sequence", uuid2) + } + + switch { + case t1 == t2 && q1 == q2: + t.Errorf("time stopped\n") + case t1 > t2 && q1 == q2: + t.Errorf("time reversed\n") + case t1 < t2 && q1 != q2: + t.Errorf("clock sequence chaned unexpectedly\n") + } +} + +func TestNodeAndTime(t *testing.T) { + // Time is February 5, 1998 12:30:23.136364800 AM GMT + + uuid := Parse("7d444840-9dc0-11d1-b245-5ffdce74fad2") + node := []byte{0x5f, 0xfd, 0xce, 0x74, 0xfa, 0xd2} + + ts, ok := uuid.Time() + if ok { + c := time.Unix(ts.UnixTime()) + want := time.Date(1998, 2, 5, 0, 30, 23, 136364800, time.UTC) + if !c.Equal(want) { + t.Errorf("Got time %v, want %v", c, want) + } + } else { + t.Errorf("%s: bad time\n", uuid) + } + if !bytes.Equal(node, uuid.NodeID()) { + t.Errorf("Expected node %v got %v\n", node, uuid.NodeID()) + } +} + +func TestMD5(t *testing.T) { + uuid := NewMD5(NameSpace_DNS, []byte("python.org")).String() + want := "6fa459ea-ee8a-3ca4-894e-db77e160355e" + if uuid != want { + t.Errorf("MD5: got %q expected %q\n", uuid, want) + } +} + +func TestSHA1(t *testing.T) { + uuid := NewSHA1(NameSpace_DNS, []byte("python.org")).String() + want := "886313e1-3b8a-5372-9b90-0c9aee199e5d" + if uuid != want { + t.Errorf("SHA1: got %q expected %q\n", uuid, want) + } +} + +func TestNodeID(t *testing.T) { + nid := []byte{1, 2, 3, 4, 5, 6} + SetNodeInterface("") + s := NodeInterface() + if s == "" || s == "user" { + t.Errorf("NodeInterface %q after SetInteface\n", s) + } + node1 := NodeID() + if node1 == nil { + t.Errorf("NodeID nil after SetNodeInterface\n", s) + } + SetNodeID(nid) + s = NodeInterface() + if s != "user" { + t.Errorf("Expected NodeInterface %q got %q\n", "user", s) + } + node2 := NodeID() + if node2 == nil { + t.Errorf("NodeID nil after SetNodeID\n", s) + } + if bytes.Equal(node1, node2) { + t.Errorf("NodeID not changed after SetNodeID\n", s) + } else if !bytes.Equal(nid, node2) { + t.Errorf("NodeID is %x, expected %x\n", node2, nid) + } +} + +func testDCE(t *testing.T, name string, uuid UUID, domain Domain, id uint32) { + if uuid == nil { + t.Errorf("%s failed\n", name) + return + } + if v, _ := uuid.Version(); v != 2 { + t.Errorf("%s: %s: expected version 2, got %s\n", name, uuid, v) + return + } + if v, ok := uuid.Domain(); !ok || v != domain { + if !ok { + t.Errorf("%s: %d: Domain failed\n", name, uuid) + } else { + t.Errorf("%s: %s: expected domain %d, got %d\n", name, uuid, domain, v) + } + } + if v, ok := uuid.Id(); !ok || v != id { + if !ok { + t.Errorf("%s: %d: Id failed\n", name, uuid) + } else { + t.Errorf("%s: %s: expected id %d, got %d\n", name, uuid, id, v) + } + } +} + +func TestDCE(t *testing.T) { + testDCE(t, "NewDCESecurity", NewDCESecurity(42, 12345678), 42, 12345678) + testDCE(t, "NewDCEPerson", NewDCEPerson(), Person, uint32(os.Getuid())) + testDCE(t, "NewDCEGroup", NewDCEGroup(), Group, uint32(os.Getgid())) +} + +type badRand struct{} + +func (r badRand) Read(buf []byte) (int, error) { + for i, _ := range buf { + buf[i] = byte(i) + } + return len(buf), nil +} + +func TestBadRand(t *testing.T) { + SetRand(badRand{}) + uuid1 := New() + uuid2 := New() + if uuid1 != uuid2 { + t.Errorf("execpted duplicates, got %q and %q\n", uuid1, uuid2) + } + SetRand(nil) + uuid1 = New() + uuid2 = New() + if uuid1 == uuid2 { + t.Errorf("unexecpted duplicates, got %q\n", uuid1) + } +} diff --git a/third_party/src/code.google.com/p/go-uuid/uuid/version1.go b/third_party/src/code.google.com/p/go-uuid/uuid/version1.go new file mode 100644 index 00000000000..63580044b6c --- /dev/null +++ b/third_party/src/code.google.com/p/go-uuid/uuid/version1.go @@ -0,0 +1,41 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" +) + +// NewUUID returns a Version 1 UUID based on the current NodeID and clock +// sequence, and the current time. If the NodeID has not been set by SetNodeID +// or SetNodeInterface then it will be set automatically. If the NodeID cannot +// be set NewUUID returns nil. If clock sequence has not been set by +// SetClockSequence then it will be set automatically. If GetTime fails to +// return the current NewUUID returns nil. +func NewUUID() UUID { + if nodeID == nil { + SetNodeInterface("") + } + + now, err := GetTime() + if err != nil { + return nil + } + + uuid := make([]byte, 16) + + time_low := uint32(now & 0xffffffff) + time_mid := uint16((now >> 32) & 0xffff) + time_hi := uint16((now >> 48) & 0x0fff) + time_hi |= 0x1000 // Version 1 + + binary.BigEndian.PutUint32(uuid[0:], time_low) + binary.BigEndian.PutUint16(uuid[4:], time_mid) + binary.BigEndian.PutUint16(uuid[6:], time_hi) + binary.BigEndian.PutUint16(uuid[8:], clock_seq) + copy(uuid[10:], nodeID) + + return uuid +} diff --git a/third_party/src/code.google.com/p/go-uuid/uuid/version4.go b/third_party/src/code.google.com/p/go-uuid/uuid/version4.go new file mode 100644 index 00000000000..b3d4a368dd0 --- /dev/null +++ b/third_party/src/code.google.com/p/go-uuid/uuid/version4.go @@ -0,0 +1,25 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +// Random returns a Random (Version 4) UUID or panics. +// +// The strength of the UUIDs is based on the strength of the crypto/rand +// package. +// +// A note about uniqueness derived from from the UUID Wikipedia entry: +// +// Randomly generated UUIDs have 122 random bits. One's annual risk of being +// hit by a meteorite is estimated to be one chance in 17 billion, that +// means the probability is about 0.00000000006 (6 × 10−11), +// equivalent to the odds of creating a few tens of trillions of UUIDs in a +// year and having one duplicate. +func NewRandom() UUID { + uuid := make([]byte, 16) + randomBits([]byte(uuid)) + uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4 + uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10 + return uuid +} From 5896ac14da2582cbb02a3cf1387f58644144b9d7 Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Mon, 30 Jun 2014 17:44:46 -0400 Subject: [PATCH 03/45] Ensure pod and manifest always have a UUID * Fixes implication #2 in docs/identifiers.md * Replication controller lets the apiserver set the pod ID --- pkg/api/types.go | 1 + pkg/controller/replication_controller.go | 4 - pkg/controller/replication_controller_test.go | 20 +++- pkg/registry/controller_registry.go | 10 +- pkg/registry/pod_registry.go | 6 +- pkg/registry/pod_registry_test.go | 98 +++++++++++++++++++ 6 files changed, 125 insertions(+), 14 deletions(-) diff --git a/pkg/api/types.go b/pkg/api/types.go index 9bac8e1cfb1..cf5de924ad5 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -50,6 +50,7 @@ type ContainerManifest struct { // Required: This must be a supported version string, such as "v1beta1". Version string `yaml:"version" json:"version"` // Required: This must be a DNS_SUBDOMAIN. + // TODO: ID on Manifest is deprecated and will be removed in the future. ID string `yaml:"id" json:"id"` Volumes []Volume `yaml:"volumes" json:"volumes"` Containers []Container `yaml:"containers" json:"containers"` diff --git a/pkg/controller/replication_controller.go b/pkg/controller/replication_controller.go index 771d05d1595..dfdc0bd23d0 100644 --- a/pkg/controller/replication_controller.go +++ b/pkg/controller/replication_controller.go @@ -19,7 +19,6 @@ package controller import ( "encoding/json" "fmt" - "math/rand" "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" @@ -64,9 +63,6 @@ func (r RealPodControl) createReplica(controllerSpec api.ReplicationController) labels["replicationController"] = controllerSpec.ID } pod := api.Pod{ - JSONBase: api.JSONBase{ - ID: fmt.Sprintf("%08x", rand.Uint32()), - }, DesiredState: controllerSpec.DesiredState.PodTemplate.DesiredState, Labels: controllerSpec.DesiredState.PodTemplate.Labels, } diff --git a/pkg/controller/replication_controller_test.go b/pkg/controller/replication_controller_test.go index 9492e4fd658..4b147f83fdd 100644 --- a/pkg/controller/replication_controller_test.go +++ b/pkg/controller/replication_controller_test.go @@ -200,12 +200,22 @@ func TestCreateReplica(t *testing.T) { podControl.createReplica(controllerSpec) - //expectedPod := Pod{ - // Labels: controllerSpec.DesiredState.PodTemplate.Labels, - // DesiredState: controllerSpec.DesiredState.PodTemplate.DesiredState, - //} - // TODO: fix this so that it validates the body. + expectedPod := api.Pod{ + JSONBase: api.JSONBase{ + Kind: "Pod", + }, + Labels: controllerSpec.DesiredState.PodTemplate.Labels, + DesiredState: controllerSpec.DesiredState.PodTemplate.DesiredState, + } fakeHandler.ValidateRequest(t, makeURL("/pods"), "POST", nil) + actualPod := api.Pod{} + if err := json.Unmarshal([]byte(fakeHandler.RequestBody), &actualPod); err != nil { + t.Errorf("Unexpected error: %#v", err) + } + if !reflect.DeepEqual(expectedPod, actualPod) { + t.Logf("Body: %s", fakeHandler.RequestBody) + t.Errorf("Unexpected mismatch. Expected %#v, Got: %#v", expectedPod, actualPod) + } } func TestHandleWatchResponseNotSet(t *testing.T) { diff --git a/pkg/registry/controller_registry.go b/pkg/registry/controller_registry.go index 296ce9c756a..6aa0dcbe99d 100644 --- a/pkg/registry/controller_registry.go +++ b/pkg/registry/controller_registry.go @@ -20,6 +20,7 @@ import ( "fmt" "time" + "code.google.com/p/go-uuid/uuid" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" @@ -79,9 +80,12 @@ func (storage *ControllerRegistryStorage) Create(obj interface{}) (<-chan interf if !ok { return nil, fmt.Errorf("not a replication controller: %#v", obj) } - if controller.ID == "" { - return nil, fmt.Errorf("ID should not be empty: %#v", controller) + if len(controller.ID) == 0 { + controller.ID = uuid.NewUUID().String() } + // Pod Manifest ID should be assigned by the pod API + controller.DesiredState.PodTemplate.DesiredState.Manifest.ID = "" + return apiserver.MakeAsync(func() (interface{}, error) { err := storage.registry.CreateController(controller) if err != nil { @@ -96,7 +100,7 @@ func (storage *ControllerRegistryStorage) Update(obj interface{}) (<-chan interf if !ok { return nil, fmt.Errorf("not a replication controller: %#v", obj) } - if controller.ID == "" { + if len(controller.ID) == 0 { return nil, fmt.Errorf("ID should not be empty: %#v", controller) } return apiserver.MakeAsync(func() (interface{}, error) { diff --git a/pkg/registry/pod_registry.go b/pkg/registry/pod_registry.go index c63991dc15f..ebbccdc2689 100644 --- a/pkg/registry/pod_registry.go +++ b/pkg/registry/pod_registry.go @@ -21,6 +21,7 @@ import ( "strings" "time" + "code.google.com/p/go-uuid/uuid" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" @@ -185,8 +186,9 @@ func (storage *PodRegistryStorage) Extract(body []byte) (interface{}, error) { func (storage *PodRegistryStorage) Create(obj interface{}) (<-chan interface{}, error) { pod := obj.(api.Pod) if len(pod.ID) == 0 { - return nil, fmt.Errorf("id is unspecified: %#v", pod) + pod.ID = uuid.NewUUID().String() } + pod.DesiredState.Manifest.ID = pod.ID return apiserver.MakeAsync(func() (interface{}, error) { // TODO(lavalamp): Separate scheduler more cleanly. @@ -205,7 +207,7 @@ func (storage *PodRegistryStorage) Create(obj interface{}) (<-chan interface{}, func (storage *PodRegistryStorage) Update(obj interface{}) (<-chan interface{}, error) { pod := obj.(api.Pod) if len(pod.ID) == 0 { - return nil, fmt.Errorf("id is unspecified: %#v", pod) + return nil, fmt.Errorf("ID should not be empty: %#v", pod) } return apiserver.MakeAsync(func() (interface{}, error) { diff --git a/pkg/registry/pod_registry_test.go b/pkg/registry/pod_registry_test.go index 8d547d02f49..160bc374f5e 100644 --- a/pkg/registry/pod_registry_test.go +++ b/pkg/registry/pod_registry_test.go @@ -35,6 +35,104 @@ func expectNoError(t *testing.T, err error) { } } +func expectApiStatusError(t *testing.T, ch <-chan interface{}, msg string) { + out := <-ch + status, ok := out.(*api.Status) + if !ok { + t.Errorf("Expected an api.Status object, was %#v", out) + return + } + if msg != status.Details { + t.Errorf("Expected %#v, was %s", msg, status.Details) + } +} + +func expectPod(t *testing.T, ch <-chan interface{}) (*api.Pod, bool) { + out := <-ch + pod, ok := out.(*api.Pod) + if !ok || pod == nil { + t.Errorf("Expected an api.Pod object, was %#v", out) + return nil, false + } + return pod, true +} + +func TestCreatePodRegistryError(t *testing.T) { + mockRegistry := &MockPodRegistry{ + err: fmt.Errorf("test error"), + } + storage := PodRegistryStorage{ + scheduler: &MockScheduler{}, + registry: mockRegistry, + } + pod := api.Pod{} + ch, err := storage.Create(pod) + if err != nil { + t.Errorf("Expected %#v, Got %#v", nil, err) + } + expectApiStatusError(t, ch, mockRegistry.err.Error()) +} + +type MockScheduler struct { + err error + pod api.Pod + machine string +} + +func (m *MockScheduler) Schedule(pod api.Pod, lister scheduler.MinionLister) (string, error) { + m.pod = pod + return m.machine, m.err +} + +func TestCreatePodSchedulerError(t *testing.T) { + mockScheduler := MockScheduler{ + err: fmt.Errorf("test error"), + } + storage := PodRegistryStorage{ + scheduler: &mockScheduler, + } + pod := api.Pod{} + ch, err := storage.Create(pod) + if err != nil { + t.Errorf("Expected %#v, Got %#v", nil, err) + } + expectApiStatusError(t, ch, mockScheduler.err.Error()) +} + +type MockPodStorageRegistry struct { + MockPodRegistry + machine string +} + +func (r *MockPodStorageRegistry) CreatePod(machine string, pod api.Pod) error { + r.MockPodRegistry.pod = &pod + r.machine = machine + return r.MockPodRegistry.err +} + +func TestCreatePodSetsIds(t *testing.T) { + mockRegistry := &MockPodStorageRegistry{ + MockPodRegistry: MockPodRegistry{err: fmt.Errorf("test error")}, + } + storage := PodRegistryStorage{ + scheduler: &MockScheduler{machine: "test"}, + registry: mockRegistry, + } + pod := api.Pod{} + ch, err := storage.Create(pod) + if err != nil { + t.Errorf("Expected %#v, Got %#v", nil, err) + } + expectApiStatusError(t, ch, mockRegistry.err.Error()) + + if len(mockRegistry.MockPodRegistry.pod.ID) == 0 { + t.Errorf("Expected pod ID to be set, Got %#v", pod) + } + if mockRegistry.MockPodRegistry.pod.DesiredState.Manifest.ID != mockRegistry.MockPodRegistry.pod.ID { + t.Errorf("Expected manifest ID to be equal to pod ID, Got %#v", pod) + } +} + func TestListPodsError(t *testing.T) { mockRegistry := MockPodRegistry{ err: fmt.Errorf("test error"), From 93def86a9e8e23818b6d0a86049e62b29adb6344 Mon Sep 17 00:00:00 2001 From: Kouhei Ueno Date: Fri, 11 Jul 2014 21:04:34 +0900 Subject: [PATCH 04/45] fix go lint errors in util --- pkg/util/fake_handler.go | 3 +++ pkg/util/set.go | 4 ++-- pkg/util/util_test.go | 16 ++++++++-------- pkg/util/validation.go | 6 +++--- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/pkg/util/fake_handler.go b/pkg/util/fake_handler.go index 3bc3b82c142..624e226b92a 100644 --- a/pkg/util/fake_handler.go +++ b/pkg/util/fake_handler.go @@ -26,6 +26,8 @@ import ( type TestInterface interface { Errorf(format string, args ...interface{}) } + +// LogInterface is a simple interface to allow injection of Logf to report serving errors. type LogInterface interface { Logf(format string, args ...interface{}) } @@ -53,6 +55,7 @@ func (f *FakeHandler) ServeHTTP(response http.ResponseWriter, request *http.Requ f.RequestBody = string(bodyReceived) } +// ValidateRequest verifies that FakeHandler received a request with expected path, method, and body. func (f FakeHandler) ValidateRequest(t TestInterface, expectedPath, expectedMethod string, body *string) { if f.RequestReceived.URL.Path != expectedPath { t.Errorf("Unexpected request path for request %#v, received: %q, expected: %q", f.RequestReceived, f.RequestReceived.URL.Path, expectedPath) diff --git a/pkg/util/set.go b/pkg/util/set.go index 7edb99d6e09..850b7f4eb20 100644 --- a/pkg/util/set.go +++ b/pkg/util/set.go @@ -22,7 +22,7 @@ import ( type empty struct{} -// A set of strings, implemented via map[string]struct{} for minimal memory consumption. +// StringSet is a set of strings, implemented via map[string]struct{} for minimal memory consumption. type StringSet map[string]empty // NewStringSet creates a StringSet from a list of values. @@ -50,7 +50,7 @@ func (s StringSet) Has(item string) bool { return contained } -// Return the contents as a sorted string slice. +// List returns the contents as a sorted string slice. func (s StringSet) List() []string { res := make([]string, 0, len(s)) for key := range s { diff --git a/pkg/util/util_test.go b/pkg/util/util_test.go index e5d35ace438..906af1cb509 100644 --- a/pkg/util/util_test.go +++ b/pkg/util/util_test.go @@ -74,10 +74,10 @@ type IntOrStringHolder struct { func TestIntOrStringUnmarshalYAML(t *testing.T) { { - yaml_code_int := "val: 123\n" + yamlCodeInt := "val: 123\n" var result IntOrStringHolder - if err := yaml.Unmarshal([]byte(yaml_code_int), &result); err != nil { + if err := yaml.Unmarshal([]byte(yamlCodeInt), &result); err != nil { t.Errorf("Failed to unmarshal: %v", err) } if result.IOrS.Kind != IntstrInt || result.IOrS.IntVal != 123 { @@ -86,10 +86,10 @@ func TestIntOrStringUnmarshalYAML(t *testing.T) { } { - yaml_code_str := "val: \"123\"\n" + yamlCodeStr := "val: \"123\"\n" var result IntOrStringHolder - if err := yaml.Unmarshal([]byte(yaml_code_str), &result); err != nil { + if err := yaml.Unmarshal([]byte(yamlCodeStr), &result); err != nil { t.Errorf("Failed to unmarshal: %v", err) } if result.IOrS.Kind != IntstrString || result.IOrS.StrVal != "123" { @@ -134,10 +134,10 @@ func TestIntOrStringMarshalYAML(t *testing.T) { func TestIntOrStringUnmarshalJSON(t *testing.T) { { - json_code_int := "{\"val\": 123}" + jsonCodeInt := "{\"val\": 123}" var result IntOrStringHolder - if err := json.Unmarshal([]byte(json_code_int), &result); err != nil { + if err := json.Unmarshal([]byte(jsonCodeInt), &result); err != nil { t.Errorf("Failed to unmarshal: %v", err) } if result.IOrS.Kind != IntstrInt || result.IOrS.IntVal != 123 { @@ -146,10 +146,10 @@ func TestIntOrStringUnmarshalJSON(t *testing.T) { } { - json_code_str := "{\"val\": \"123\"}" + jsonCodeStr := "{\"val\": \"123\"}" var result IntOrStringHolder - if err := json.Unmarshal([]byte(json_code_str), &result); err != nil { + if err := json.Unmarshal([]byte(jsonCodeStr), &result); err != nil { t.Errorf("Failed to unmarshal: %v", err) } if result.IOrS.Kind != IntstrString || result.IOrS.StrVal != "123" { diff --git a/pkg/util/validation.go b/pkg/util/validation.go index a78855a4ce2..ede2cd63a3f 100644 --- a/pkg/util/validation.go +++ b/pkg/util/validation.go @@ -22,7 +22,7 @@ import ( const dnsLabelFmt string = "[a-z0-9]([-a-z0-9]*[a-z0-9])?" -var dnsLabelRegexp *regexp.Regexp = regexp.MustCompile("^" + dnsLabelFmt + "$") +var dnsLabelRegexp = regexp.MustCompile("^" + dnsLabelFmt + "$") const dnsLabelMaxLength int = 63 @@ -34,7 +34,7 @@ func IsDNSLabel(value string) bool { const dnsSubdomainFmt string = dnsLabelFmt + "(\\." + dnsLabelFmt + ")*" -var dnsSubdomainRegexp *regexp.Regexp = regexp.MustCompile("^" + dnsSubdomainFmt + "$") +var dnsSubdomainRegexp = regexp.MustCompile("^" + dnsSubdomainFmt + "$") const dnsSubdomainMaxLength int = 253 @@ -46,7 +46,7 @@ func IsDNSSubdomain(value string) bool { const cIdentifierFmt string = "[A-Za-z_][A-Za-z0-9_]*" -var cIdentifierRegexp *regexp.Regexp = regexp.MustCompile("^" + cIdentifierFmt + "$") +var cIdentifierRegexp = regexp.MustCompile("^" + cIdentifierFmt + "$") // IsCIdentifier tests for a string that conforms the definition of an identifier // in C. This checks the format, but not the length. From e268e4bb7a9c4e6e4e5026481b9a27cbe211e0ce Mon Sep 17 00:00:00 2001 From: Kouhei Ueno Date: Fri, 11 Jul 2014 22:01:12 +0900 Subject: [PATCH 05/45] Add comments to pkg/scheduler/ to pass golint --- pkg/scheduler/firstfit.go | 6 ++++-- pkg/scheduler/listers.go | 11 ++++++----- pkg/scheduler/random.go | 4 +++- pkg/scheduler/roundrobin.go | 2 ++ 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/pkg/scheduler/firstfit.go b/pkg/scheduler/firstfit.go index e98c9a96932..35073072e5e 100644 --- a/pkg/scheduler/firstfit.go +++ b/pkg/scheduler/firstfit.go @@ -24,12 +24,14 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" ) +// FirstFitScheduler is a Scheduler interface implementation which uses first fit algorithm. type FirstFitScheduler struct { podLister PodLister // TODO: *rand.Rand is *not* threadsafe random *rand.Rand } +// MakeFirstFitScheduler creates a new instance of FirstFitScheduler. func MakeFirstFitScheduler(podLister PodLister, random *rand.Rand) Scheduler { return &FirstFitScheduler{ podLister: podLister, @@ -48,6 +50,7 @@ func (s *FirstFitScheduler) containsPort(pod api.Pod, port api.Port) bool { return false } +// Schedule schedules a pod on the first machine which matches its requirement. func (s *FirstFitScheduler) Schedule(pod api.Pod, minionLister MinionLister) (string, error) { machines, err := minionLister.List() if err != nil { @@ -80,7 +83,6 @@ func (s *FirstFitScheduler) Schedule(pod api.Pod, minionLister MinionLister) (st } if len(machineOptions) == 0 { return "", fmt.Errorf("failed to find fit for %#v", pod) - } else { - return machineOptions[s.random.Int()%len(machineOptions)], nil } + return machineOptions[s.random.Int()%len(machineOptions)], nil } diff --git a/pkg/scheduler/listers.go b/pkg/scheduler/listers.go index ad7a41562f7..96b216d8b44 100644 --- a/pkg/scheduler/listers.go +++ b/pkg/scheduler/listers.go @@ -21,27 +21,28 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" ) -// Anything that can list minions for a scheduler. +// MinionLister interface represents anything that can list minions for a scheduler. type MinionLister interface { List() (machines []string, err error) } -// Make a MinionLister from a []string +// FakeMinionLister implements MinionLister on a []string for test purposes. type FakeMinionLister []string -// Returns minions as a []string +// List returns minions as a []string func (f FakeMinionLister) List() ([]string, error) { return []string(f), nil } -// Anything that can list pods for a scheduler +// PodLister interface represents anything that can list pods for a scheduler type PodLister interface { ListPods(labels.Selector) ([]api.Pod, error) } -// Make a MinionLister from an []api.Pods +// FakePodLister implements PodLister on an []api.Pods for test purposes. type FakePodLister []api.Pod +// ListPods returns []api.Pod matching a query. func (f FakePodLister) ListPods(s labels.Selector) (selected []api.Pod, err error) { for _, pod := range f { if s.Matches(labels.Set(pod.Labels)) { diff --git a/pkg/scheduler/random.go b/pkg/scheduler/random.go index 6d268a33f09..f488548ce90 100644 --- a/pkg/scheduler/random.go +++ b/pkg/scheduler/random.go @@ -22,18 +22,20 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" ) -// RandomScheduler choses machines uniformly at random. +// RandomScheduler chooses machines uniformly at random. type RandomScheduler struct { // TODO: rand.Rand is *NOT* thread safe. random *rand.Rand } +// MakeRandomScheduler creates a new RandomScheduler instance. func MakeRandomScheduler(random *rand.Rand) Scheduler { return &RandomScheduler{ random: random, } } +// Schedule schedules a given pod to a random machine. func (s *RandomScheduler) Schedule(pod api.Pod, minionLister MinionLister) (string, error) { machines, err := minionLister.List() if err != nil { diff --git a/pkg/scheduler/roundrobin.go b/pkg/scheduler/roundrobin.go index 6d1df35fee6..eadbab0cabf 100644 --- a/pkg/scheduler/roundrobin.go +++ b/pkg/scheduler/roundrobin.go @@ -25,12 +25,14 @@ type RoundRobinScheduler struct { currentIndex int } +// MakeRoundRobinScheduler creates a new RoundRobinScheduler instance. func MakeRoundRobinScheduler() Scheduler { return &RoundRobinScheduler{ currentIndex: -1, } } +// Schedule schedules a pod on the machine next to the last scheduled machine. func (s *RoundRobinScheduler) Schedule(pod api.Pod, minionLister MinionLister) (string, error) { machines, err := minionLister.List() if err != nil { From e53658a3ed1236ce0835378de2a21e9ac9265ee3 Mon Sep 17 00:00:00 2001 From: Kouhei Ueno Date: Fri, 11 Jul 2014 22:08:53 +0900 Subject: [PATCH 06/45] s/controllerId/controllerID/ to make golint happy. --- pkg/registry/interfaces.go | 4 ++-- pkg/registry/memory_registry.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/registry/interfaces.go b/pkg/registry/interfaces.go index f3190b18da4..3a6752509bd 100644 --- a/pkg/registry/interfaces.go +++ b/pkg/registry/interfaces.go @@ -38,10 +38,10 @@ type PodRegistry interface { // ControllerRegistry is an interface for things that know how to store Controllers. type ControllerRegistry interface { ListControllers() ([]api.ReplicationController, error) - GetController(controllerId string) (*api.ReplicationController, error) + GetController(controllerID string) (*api.ReplicationController, error) CreateController(controller api.ReplicationController) error UpdateController(controller api.ReplicationController) error - DeleteController(controllerId string) error + DeleteController(controllerID string) error } // ServiceRegistry is an interface for things that know how to store services. diff --git a/pkg/registry/memory_registry.go b/pkg/registry/memory_registry.go index bd0e25cf103..63dee81d0cb 100644 --- a/pkg/registry/memory_registry.go +++ b/pkg/registry/memory_registry.go @@ -93,8 +93,8 @@ func (registry *MemoryRegistry) CreateController(controller api.ReplicationContr return nil } -func (registry *MemoryRegistry) DeleteController(controllerId string) error { - delete(registry.controllerData, controllerId) +func (registry *MemoryRegistry) DeleteController(controllerID string) error { + delete(registry.controllerData, controllerID) return nil } From bcbdbf6558b18ff8c52ec3bf2865f3e2ee35c514 Mon Sep 17 00:00:00 2001 From: Kouhei Ueno Date: Fri, 11 Jul 2014 22:46:22 +0900 Subject: [PATCH 07/45] use New instead of Make and add comments to make golint happy --- pkg/master/master.go | 2 +- pkg/registry/controller_registry.go | 11 +++++++++-- pkg/registry/interfaces.go | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/pkg/master/master.go b/pkg/master/master.go index 5b17457fcb3..80831fa095e 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -87,7 +87,7 @@ func (m *Master) init(cloud cloudprovider.Interface, podInfoGetter client.PodInf s := scheduler.MakeFirstFitScheduler(m.podRegistry, m.random) m.storage = map[string]apiserver.RESTStorage{ "pods": registry.MakePodRegistryStorage(m.podRegistry, podInfoGetter, s, m.minionRegistry, cloud, podCache), - "replicationControllers": registry.MakeControllerRegistryStorage(m.controllerRegistry, m.podRegistry), + "replicationControllers": registry.NewControllerRegistryStorage(m.controllerRegistry, m.podRegistry), "services": registry.MakeServiceRegistryStorage(m.serviceRegistry, cloud, m.minionRegistry), "minions": registry.MakeMinionRegistryStorage(m.minionRegistry), } diff --git a/pkg/registry/controller_registry.go b/pkg/registry/controller_registry.go index 296ce9c756a..a43fc51fd57 100644 --- a/pkg/registry/controller_registry.go +++ b/pkg/registry/controller_registry.go @@ -25,7 +25,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" ) -// Implementation of RESTStorage for the api server. +// ControllerRegistryStorage is an implementation of RESTStorage for the api server. type ControllerRegistryStorage struct { registry ControllerRegistry podRegistry PodRegistry @@ -33,7 +33,8 @@ type ControllerRegistryStorage struct { pollPeriod time.Duration } -func MakeControllerRegistryStorage(registry ControllerRegistry, podRegistry PodRegistry) apiserver.RESTStorage { +// NewControllerRegistryStorage creates a new NewControllerRegistryStorage instance. +func NewControllerRegistryStorage(registry ControllerRegistry, podRegistry PodRegistry) apiserver.RESTStorage { return &ControllerRegistryStorage{ registry: registry, podRegistry: podRegistry, @@ -41,6 +42,7 @@ func MakeControllerRegistryStorage(registry ControllerRegistry, podRegistry PodR } } +// List obtains a list of ReplicationControllers that match selector. func (storage *ControllerRegistryStorage) List(selector labels.Selector) (interface{}, error) { result := api.ReplicationControllerList{} controllers, err := storage.registry.ListControllers() @@ -54,6 +56,7 @@ func (storage *ControllerRegistryStorage) List(selector labels.Selector) (interf return result, err } +// Get obtains the ReplicationController specified by its id. func (storage *ControllerRegistryStorage) Get(id string) (interface{}, error) { controller, err := storage.registry.GetController(id) if err != nil { @@ -62,18 +65,21 @@ func (storage *ControllerRegistryStorage) Get(id string) (interface{}, error) { return controller, err } +// Delete asynchronously deletes the ReplicationController specified by its id. func (storage *ControllerRegistryStorage) Delete(id string) (<-chan interface{}, error) { return apiserver.MakeAsync(func() (interface{}, error) { return api.Status{Status: api.StatusSuccess}, storage.registry.DeleteController(id) }), nil } +// Extract deserializes user provided data into an api.ReplicationController. func (storage *ControllerRegistryStorage) Extract(body []byte) (interface{}, error) { result := api.ReplicationController{} err := api.DecodeInto(body, &result) return result, err } +// Create registers a given new ReplicationController instance to storage.registry. func (storage *ControllerRegistryStorage) Create(obj interface{}) (<-chan interface{}, error) { controller, ok := obj.(api.ReplicationController) if !ok { @@ -91,6 +97,7 @@ func (storage *ControllerRegistryStorage) Create(obj interface{}) (<-chan interf }), nil } +// Update replaces a given ReplicationController instance with an existing instnace in storage.registry. func (storage *ControllerRegistryStorage) Update(obj interface{}) (<-chan interface{}, error) { controller, ok := obj.(api.ReplicationController) if !ok { diff --git a/pkg/registry/interfaces.go b/pkg/registry/interfaces.go index 3a6752509bd..4c52195625d 100644 --- a/pkg/registry/interfaces.go +++ b/pkg/registry/interfaces.go @@ -35,7 +35,7 @@ type PodRegistry interface { DeletePod(podID string) error } -// ControllerRegistry is an interface for things that know how to store Controllers. +// ControllerRegistry is an interface for things that know how to store ReplicationControllers. type ControllerRegistry interface { ListControllers() ([]api.ReplicationController, error) GetController(controllerID string) (*api.ReplicationController, error) From 3ce6fc116df8964444a5cba7c42a855ed4f3dba4 Mon Sep 17 00:00:00 2001 From: Christophe Labouisse Date: Fri, 11 Jul 2014 16:55:47 +0200 Subject: [PATCH 08/45] Fixed SCRIPT_DIR computation when CDPATH is set. --- release/build-release.sh | 2 +- release/release.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/release/build-release.sh b/release/build-release.sh index 19a9a48fb21..061203e3f62 100755 --- a/release/build-release.sh +++ b/release/build-release.sh @@ -21,7 +21,7 @@ set -eu set -o pipefail IFS=$'\n\t' -SCRIPT_DIR=$(cd $(dirname $0); pwd) +SCRIPT_DIR=$(CDPATH="" cd $(dirname $0); pwd) INSTANCE_PREFIX=$1 diff --git a/release/release.sh b/release/release.sh index 70650f2d390..241444ff2ad 100755 --- a/release/release.sh +++ b/release/release.sh @@ -23,7 +23,7 @@ # exit on any error set -e -SCRIPT_DIR=$(cd $(dirname $0); pwd) +SCRIPT_DIR=$(CDPATH="" cd $(dirname $0); pwd) source $SCRIPT_DIR/config.sh From 6312ffebcf9f065fcb078f9121d8d034f2ca3cc5 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Wed, 9 Jul 2014 16:53:31 -0700 Subject: [PATCH 09/45] Address some comments from thockin@ --- pkg/api/types.go | 9 ++++----- pkg/kubelet/health_check.go | 3 +++ pkg/kubelet/health_check_test.go | 4 ++-- pkg/kubelet/kubelet.go | 2 +- pkg/kubelet/kubelet_test.go | 3 +-- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/pkg/api/types.go b/pkg/api/types.go index 9bac8e1cfb1..7eb90d1784d 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -116,11 +116,10 @@ type HTTPGetProbe struct { // LivenessProbe describes a liveness probe to be examined to the container. type LivenessProbe struct { - Enabled bool `yaml:"enabled,omitempty" json:"enabled,omitempty"` // Type of liveness probe. Current legal values "http" Type string `yaml:"type,omitempty" json:"type,omitempty"` // HTTPGetProbe parameters, required if Type == 'http' - HTTPGet HTTPGetProbe `yaml:"httpGet,omitempty" json:"httpGet,omitempty"` + HTTPGet *HTTPGetProbe `yaml:"httpGet,omitempty" json:"httpGet,omitempty"` // Length of time before health checking is activated. In seconds. InitialDelaySeconds int64 `yaml:"initialDelaySeconds,omitempty" json:"initialDelaySeconds,omitempty"` } @@ -141,9 +140,9 @@ type Container struct { // Optional: Defaults to unlimited. Memory int `yaml:"memory,omitempty" json:"memory,omitempty"` // Optional: Defaults to unlimited. - CPU int `yaml:"cpu,omitempty" json:"cpu,omitempty"` - VolumeMounts []VolumeMount `yaml:"volumeMounts,omitempty" json:"volumeMounts,omitempty"` - LivenessProbe LivenessProbe `yaml:"livenessProbe,omitempty" json:"livenessProbe,omitempty"` + CPU int `yaml:"cpu,omitempty" json:"cpu,omitempty"` + VolumeMounts []VolumeMount `yaml:"volumeMounts,omitempty" json:"volumeMounts,omitempty"` + LivenessProbe *LivenessProbe `yaml:"livenessProbe,omitempty" json:"livenessProbe,omitempty"` } // Percentile represents a pair which contains a percentage from 0 to 100 and diff --git a/pkg/kubelet/health_check.go b/pkg/kubelet/health_check.go index 7bf9933edf0..d05774dc4a3 100644 --- a/pkg/kubelet/health_check.go +++ b/pkg/kubelet/health_check.go @@ -79,6 +79,9 @@ func (h *HTTPHealthChecker) findPort(container api.Container, portName string) i // IsHealthy checks if the container is healthy by trying sending HTTP Get requests to the container. func (h *HTTPHealthChecker) IsHealthy(container api.Container) (bool, error) { params := container.LivenessProbe.HTTPGet + if params == nil { + return true, fmt.Errorf("Error, no HTTP parameters specified: %v", container) + } port := h.findPort(container, params.Port) if port == -1 { var err error diff --git a/pkg/kubelet/health_check_test.go b/pkg/kubelet/health_check_test.go index 6aa18dc0425..3c6a6a88d60 100644 --- a/pkg/kubelet/health_check_test.go +++ b/pkg/kubelet/health_check_test.go @@ -46,8 +46,8 @@ func TestHttpHealth(t *testing.T) { } container := api.Container{ - LivenessProbe: api.LivenessProbe{ - HTTPGet: api.HTTPGetProbe{ + LivenessProbe: &api.LivenessProbe{ + HTTPGet: &api.HTTPGetProbe{ Port: "8080", Path: "/foo/bar", }, diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index 7167de30d50..bae98141467 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -991,7 +991,7 @@ func (kl *Kubelet) GetMachineStats() (*api.ContainerStats, error) { func (kl *Kubelet) healthy(container api.Container, dockerContainer *docker.APIContainers) (bool, error) { // Give the container 60 seconds to start up. - if !container.LivenessProbe.Enabled { + if container.LivenessProbe == nil { return true, nil } if time.Now().Unix()-dockerContainer.Created < container.LivenessProbe.InitialDelaySeconds { diff --git a/pkg/kubelet/kubelet_test.go b/pkg/kubelet/kubelet_test.go index ce071451074..1a73c43a3c7 100644 --- a/pkg/kubelet/kubelet_test.go +++ b/pkg/kubelet/kubelet_test.go @@ -448,8 +448,7 @@ func TestSyncManifestsUnhealthy(t *testing.T) { ID: "foo", Containers: []api.Container{ {Name: "bar", - LivenessProbe: api.LivenessProbe{ - Enabled: true, + LivenessProbe: &api.LivenessProbe{ // Always returns healthy == false Type: "false", }, From 1b4dfe7d1489e5045a216f51d90f626261b49c81 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Fri, 11 Jul 2014 10:02:59 -0700 Subject: [PATCH 10/45] Move from bool to HealthCheckStatus --- pkg/kubelet/health_check.go | 34 ++++++++++++++++++++------------ pkg/kubelet/health_check_test.go | 4 ++-- pkg/kubelet/kubelet.go | 12 +++++------ pkg/kubelet/kubelet_test.go | 4 ++-- 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/pkg/kubelet/health_check.go b/pkg/kubelet/health_check.go index d05774dc4a3..62a015df08d 100644 --- a/pkg/kubelet/health_check.go +++ b/pkg/kubelet/health_check.go @@ -25,10 +25,16 @@ import ( "github.com/golang/glog" ) -// HealthChecker is an abstract interface for container health checker. +type HealthCheckStatus int + +const ( + CheckHealthy HealthCheckStatus = 0 + CheckUnhealthy HealthCheckStatus = 1 + CheckUnknown HealthCheckStatus = 2 +) + type HealthChecker interface { - // IsHealthy checks if the container is healthy. - IsHealthy(container api.Container) (bool, error) + HealthCheck(container api.Container) (HealthCheckStatus, error) } type httpDoInterface interface { @@ -51,14 +57,13 @@ type MuxHealthChecker struct { checkers map[string]HealthChecker } -// IsHealthy checks the health of the container by delegating to an appropriate HealthChecker according to container.LivenessProbe.Type. -func (m *MuxHealthChecker) IsHealthy(container api.Container) (bool, error) { +func (m *MuxHealthChecker) HealthCheck(container api.Container) (HealthCheckStatus, error) { checker, ok := m.checkers[container.LivenessProbe.Type] if !ok || checker == nil { glog.Warningf("Failed to find health checker for %s %s", container.Name, container.LivenessProbe.Type) - return true, nil + return CheckUnknown, nil } - return checker.IsHealthy(container) + return checker.HealthCheck(container) } // HTTPHealthChecker is an implementation of HealthChecker which checks container health by sending HTTP Get requests. @@ -76,18 +81,17 @@ func (h *HTTPHealthChecker) findPort(container api.Container, portName string) i return -1 } -// IsHealthy checks if the container is healthy by trying sending HTTP Get requests to the container. -func (h *HTTPHealthChecker) IsHealthy(container api.Container) (bool, error) { +func (h *HTTPHealthChecker) HealthCheck(container api.Container) (HealthCheckStatus, error) { params := container.LivenessProbe.HTTPGet if params == nil { - return true, fmt.Errorf("Error, no HTTP parameters specified: %v", container) + return CheckUnknown, fmt.Errorf("Error, no HTTP parameters specified: %v", container) } port := h.findPort(container, params.Port) if port == -1 { var err error port, err = strconv.ParseInt(params.Port, 10, 0) if err != nil { - return true, err + return CheckUnknown, err } } var host string @@ -103,7 +107,11 @@ func (h *HTTPHealthChecker) IsHealthy(container api.Container) (bool, error) { } if err != nil { // At this point, if it fails, its either a policy (unlikely) or HTTP protocol (likely) error. - return false, nil + return CheckUnhealthy, nil } - return res.StatusCode == http.StatusOK, nil + if res.StatusCode == http.StatusOK { + return CheckHealthy, nil + } + glog.V(1).Infof("Health check failed for %v, Response: %v", container, *res) + return CheckUnhealthy, nil } diff --git a/pkg/kubelet/health_check_test.go b/pkg/kubelet/health_check_test.go index 3c6a6a88d60..557f34702b8 100644 --- a/pkg/kubelet/health_check_test.go +++ b/pkg/kubelet/health_check_test.go @@ -55,8 +55,8 @@ func TestHttpHealth(t *testing.T) { }, } - ok, err := check.IsHealthy(container) - if !ok { + ok, err := check.HealthCheck(container) + if ok != CheckHealthy { t.Error("Unexpected unhealthy") } if err != nil { diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index bae98141467..39ad4b1dd8f 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -732,7 +732,7 @@ func (kl *Kubelet) syncManifest(manifest *api.ContainerManifest, keepChannel cha glog.V(1).Infof("health check errored: %v", err) continue } - if !healthy { + if healthy != CheckHealthy { glog.V(1).Infof("manifest %s container %s is unhealthy.", manifest.ID, container.Name) if err != nil { glog.V(1).Infof("Failed to get container info %v, for %s", err, containerID) @@ -989,16 +989,16 @@ func (kl *Kubelet) GetMachineStats() (*api.ContainerStats, error) { return kl.statsFromContainerPath("/") } -func (kl *Kubelet) healthy(container api.Container, dockerContainer *docker.APIContainers) (bool, error) { +func (kl *Kubelet) healthy(container api.Container, dockerContainer *docker.APIContainers) (HealthCheckStatus, error) { // Give the container 60 seconds to start up. if container.LivenessProbe == nil { - return true, nil + return CheckHealthy, nil } if time.Now().Unix()-dockerContainer.Created < container.LivenessProbe.InitialDelaySeconds { - return true, nil + return CheckHealthy, nil } if kl.HealthChecker == nil { - return true, nil + return CheckHealthy, nil } - return kl.HealthChecker.IsHealthy(container) + return kl.HealthChecker.HealthCheck(container) } diff --git a/pkg/kubelet/kubelet_test.go b/pkg/kubelet/kubelet_test.go index 1a73c43a3c7..a3a74381e19 100644 --- a/pkg/kubelet/kubelet_test.go +++ b/pkg/kubelet/kubelet_test.go @@ -424,8 +424,8 @@ func TestSyncManifestsDeletes(t *testing.T) { type FalseHealthChecker struct{} -func (f *FalseHealthChecker) IsHealthy(container api.Container) (bool, error) { - return false, nil +func (f *FalseHealthChecker) HealthCheck(container api.Container) (HealthCheckStatus, error) { + return CheckUnhealthy, nil } func TestSyncManifestsUnhealthy(t *testing.T) { From 7d5398bda7cadfab1e1f815d042accae22c86d5b Mon Sep 17 00:00:00 2001 From: Tim Hockin Date: Fri, 11 Jul 2014 11:22:22 -0700 Subject: [PATCH 11/45] Fix go idiom feedback --- pkg/util/util.go | 12 ++--- pkg/util/util_test.go | 118 ++++++++++++++---------------------------- 2 files changed, 46 insertions(+), 84 deletions(-) diff --git a/pkg/util/util.go b/pkg/util/util.go index d5b91a0592b..c1443d7d989 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -85,14 +85,14 @@ const ( // SetYAML implements the yaml.Setter interface. func (intstr *IntOrString) SetYAML(tag string, value interface{}) bool { - if intVal, ok := value.(int); ok { + switch v := value.(type) { + case int: intstr.Kind = IntstrInt - intstr.IntVal = intVal + intstr.IntVal = v return true - } - if strVal, ok := value.(string); ok { + case string: intstr.Kind = IntstrString - intstr.StrVal = strVal + intstr.StrVal = v return true } return false @@ -129,6 +129,6 @@ func (intstr IntOrString) MarshalJSON() ([]byte, error) { case IntstrString: return json.Marshal(intstr.StrVal) default: - panic("impossible IntOrString.Kind") + return []byte{}, fmt.Errorf("impossible IntOrString.Kind") } } diff --git a/pkg/util/util_test.go b/pkg/util/util_test.go index 906af1cb509..e5bea8e2f12 100644 --- a/pkg/util/util_test.go +++ b/pkg/util/util_test.go @@ -73,121 +73,83 @@ type IntOrStringHolder struct { } func TestIntOrStringUnmarshalYAML(t *testing.T) { - { - yamlCodeInt := "val: 123\n" - - var result IntOrStringHolder - if err := yaml.Unmarshal([]byte(yamlCodeInt), &result); err != nil { - t.Errorf("Failed to unmarshal: %v", err) - } - if result.IOrS.Kind != IntstrInt || result.IOrS.IntVal != 123 { - t.Errorf("Failed to unmarshal int-typed IntOrString: %v", result) - } + cases := []struct { + input string + result IntOrString + }{ + {"val: 123\n", IntOrString{Kind: IntstrInt, IntVal: 123}}, + {"val: \"123\"\n", IntOrString{Kind: IntstrString, StrVal: "123"}}, } - { - yamlCodeStr := "val: \"123\"\n" - + for _, c := range cases { var result IntOrStringHolder - if err := yaml.Unmarshal([]byte(yamlCodeStr), &result); err != nil { + if err := yaml.Unmarshal([]byte(c.input), &result); err != nil { t.Errorf("Failed to unmarshal: %v", err) } - if result.IOrS.Kind != IntstrString || result.IOrS.StrVal != "123" { - t.Errorf("Failed to unmarshal string-typed IntOrString: %v", result) + if result.IOrS != c.result { + t.Errorf("Failed to unmarshal IntOrString: got %+v", result) } } } func TestIntOrStringMarshalYAML(t *testing.T) { - { - input := IntOrStringHolder{ - IOrS: IntOrString{ - Kind: IntstrInt, - IntVal: 123, - }, - } - result, err := yaml.Marshal(&input) - if err != nil { - t.Errorf("Failed to marshal: %v", err) - } - if string(result) != "val: 123\n" { - t.Errorf("Failed to marshal int-typed IntOrString: %q", string(result)) - } + cases := []struct { + input IntOrString + result string + }{ + {IntOrString{Kind: IntstrInt, IntVal: 123}, "val: 123\n"}, + {IntOrString{Kind: IntstrString, StrVal: "123"}, "val: \"123\"\n"}, } - { - input := IntOrStringHolder{ - IOrS: IntOrString{ - Kind: IntstrString, - StrVal: "123", - }, - } + for _, c := range cases { + input := IntOrStringHolder{c.input} result, err := yaml.Marshal(&input) if err != nil { t.Errorf("Failed to marshal: %v", err) } - if string(result) != "val: \"123\"\n" { - t.Errorf("Failed to marshal string-typed IntOrString: %q", string(result)) + if string(result) != c.result { + t.Errorf("Failed to marshal IntOrString: got %q", string(result)) } } } func TestIntOrStringUnmarshalJSON(t *testing.T) { - { - jsonCodeInt := "{\"val\": 123}" - - var result IntOrStringHolder - if err := json.Unmarshal([]byte(jsonCodeInt), &result); err != nil { - t.Errorf("Failed to unmarshal: %v", err) - } - if result.IOrS.Kind != IntstrInt || result.IOrS.IntVal != 123 { - t.Errorf("Failed to unmarshal int-typed IntOrString: %v", result) - } + cases := []struct { + input string + result IntOrString + }{ + {"{\"val\": 123}", IntOrString{Kind: IntstrInt, IntVal: 123}}, + {"{\"val\": \"123\"}", IntOrString{Kind: IntstrString, StrVal: "123"}}, } - { - jsonCodeStr := "{\"val\": \"123\"}" - + for _, c := range cases { var result IntOrStringHolder - if err := json.Unmarshal([]byte(jsonCodeStr), &result); err != nil { + if err := json.Unmarshal([]byte(c.input), &result); err != nil { t.Errorf("Failed to unmarshal: %v", err) } - if result.IOrS.Kind != IntstrString || result.IOrS.StrVal != "123" { - t.Errorf("Failed to unmarshal string-typed IntOrString: %v", result) + if result.IOrS != c.result { + t.Errorf("Failed to unmarshal IntOrString: got %+v", result) } } } func TestIntOrStringMarshalJSON(t *testing.T) { - { - input := IntOrStringHolder{ - IOrS: IntOrString{ - Kind: IntstrInt, - IntVal: 123, - }, - } - result, err := json.Marshal(&input) - if err != nil { - t.Errorf("Failed to marshal: %v", err) - } - if string(result) != "{\"val\":123}" { - t.Errorf("Failed to marshal int-typed IntOrString: %q", string(result)) - } + cases := []struct { + input IntOrString + result string + }{ + {IntOrString{Kind: IntstrInt, IntVal: 123}, "{\"val\":123}"}, + {IntOrString{Kind: IntstrString, StrVal: "123"}, "{\"val\":\"123\"}"}, } - { - input := IntOrStringHolder{ - IOrS: IntOrString{ - Kind: IntstrString, - StrVal: "123", - }, - } + for _, c := range cases { + input := IntOrStringHolder{c.input} result, err := json.Marshal(&input) if err != nil { t.Errorf("Failed to marshal: %v", err) } - if string(result) != "{\"val\":\"123\"}" { - t.Errorf("Failed to marshal string-typed IntOrString: %q", string(result)) + if string(result) != c.result { + t.Errorf("Failed to marshal IntOrString: got %q", string(result)) } } } From 9a9d140a8a0a9824d54fe6c48d967b8032e7123e Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Fri, 11 Jul 2014 14:41:15 -0400 Subject: [PATCH 12/45] -cover causes races in Go 1.2 Make -covermode=atomic the default until we drop 1.2 support Enable Go 1.2 in Travis --- .travis.yml | 2 ++ hack/install-std-race.sh | 28 ++++++++++++++++++++++++++++ hack/test-go.sh | 6 ++++-- 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100755 hack/install-std-race.sh diff --git a/.travis.yml b/.travis.yml index 639ae950665..70d60fcf169 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ language: go go: - 1.3 + - 1.2 - tip install: @@ -9,6 +10,7 @@ install: - go get github.com/coreos/etcd - ./hack/verify-gofmt.sh - ./hack/verify-boilerplate.sh + - ./hack/install-std-race.sh - ./hack/build-go.sh script: diff --git a/hack/install-std-race.sh b/hack/install-std-race.sh new file mode 100755 index 00000000000..a57bcfffe33 --- /dev/null +++ b/hack/install-std-race.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# 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. + +# This script installs std -race on Travis (see https://code.google.com/p/go/issues/detail?id=6479) + +set -e + +if [ "${TRAVIS}" == "true" ]; then + GO_VERSION=($(go version)) + + if [ ${GO_VERSION[2]} \< "go1.3" ]; then + echo "Installing the -race compatible version of the std go library" + go install -a -race std + fi +fi diff --git a/hack/test-go.sh b/hack/test-go.sh index f104317c4ed..a1753175118 100755 --- a/hack/test-go.sh +++ b/hack/test-go.sh @@ -35,14 +35,16 @@ find_test_dirs() { ) } +# -covermode=atomic becomes default with -race in Go >=1.3 +KUBE_COVER="-cover -covermode=atomic -coverprofile=\"tmp.out\"" cd "${KUBE_TARGET}" if [ "$1" != "" ]; then - go test -race -cover -coverprofile="tmp.out" "$KUBE_GO_PACKAGE/$1" "${@:2}" + go test -race $KUBE_COVER "$KUBE_GO_PACKAGE/$1" "${@:2}" exit 0 fi for package in $(find_test_dirs); do - go test -race -cover -coverprofile="tmp.out" "${KUBE_GO_PACKAGE}/${package}" "${@:2}" + go test -race $KUBE_COVER "${KUBE_GO_PACKAGE}/${package}" "${@:2}" done From e9edfc754e78fda231f13039737bf4d327697c98 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Thu, 10 Jul 2014 12:45:01 -0700 Subject: [PATCH 13/45] Add initial validation of Service objects --- pkg/api/validation.go | 11 +++++++++++ pkg/api/validation_test.go | 33 ++++++++++++++++++++++++++++++++ pkg/registry/service_registry.go | 5 +++-- 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/pkg/api/validation.go b/pkg/api/validation.go index d58cbdf0521..e3572d4a3c4 100644 --- a/pkg/api/validation.go +++ b/pkg/api/validation.go @@ -246,3 +246,14 @@ func ValidateManifest(manifest *ContainerManifest) []error { allErrs.Append(validateContainers(manifest.Containers, allVolumes)...) return []error(allErrs) } + +func ValidateService(service *Service) []error { + allErrs := errorList{} + if service.ID == "" { + allErrs.Append(fmt.Errorf("ID should not be empty: %#v", *service)) + } + if len(service.Selector) == 0 { + allErrs.Append(fmt.Errorf("Service %#v missing a selector", *service)) + } + return []error(allErrs) +} diff --git a/pkg/api/validation_test.go b/pkg/api/validation_test.go index 34bf26bc1ee..84ae5ff20c5 100644 --- a/pkg/api/validation_test.go +++ b/pkg/api/validation_test.go @@ -262,3 +262,36 @@ func TestValidateManifest(t *testing.T) { } } } + +func TestValidateService(t *testing.T) { + errs := ValidateService(&Service{ + JSONBase: JSONBase{ID: "foo"}, + Selector: map[string]string{ + "foo": "bar", + }, + }) + if len(errs) != 0 { + t.Errorf("Unexpected non-zero error list: %#v", errs) + } + + errs = ValidateService(&Service{ + Selector: map[string]string{ + "foo": "bar", + }, + }) + if len(errs) != 1 { + t.Errorf("Unexpected error list: %#v", errs) + } + + errs = ValidateService(&Service{ + JSONBase: JSONBase{ID: "foo"}, + }) + if len(errs) != 1 { + t.Errorf("Unexpected error list: %#v", errs) + } + + errs = ValidateService(&Service{}) + if len(errs) != 2 { + t.Errorf("Unexpected error list: %#v", errs) + } +} diff --git a/pkg/registry/service_registry.go b/pkg/registry/service_registry.go index 89455a85c50..6d4e0d8fd4d 100644 --- a/pkg/registry/service_registry.go +++ b/pkg/registry/service_registry.go @@ -112,8 +112,9 @@ func (sr *ServiceRegistryStorage) Extract(body []byte) (interface{}, error) { func (sr *ServiceRegistryStorage) Create(obj interface{}) (<-chan interface{}, error) { srv := obj.(api.Service) - if srv.ID == "" { - return nil, fmt.Errorf("ID should not be empty: %#v", srv) + errs := api.ValidateService(&srv) + if len(errs) > 0 { + return nil, fmt.Errorf("Validation errors: %v", errs) } return apiserver.MakeAsync(func() (interface{}, error) { // TODO: Consider moving this to a rectification loop, so that we make/remove external load balancers From b59dcbbb807d1a243f90e4bcab26ffe8f29e2ed0 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Thu, 10 Jul 2014 15:05:45 -0700 Subject: [PATCH 14/45] Fix tests. --- pkg/registry/service_registry_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/registry/service_registry_test.go b/pkg/registry/service_registry_test.go index 023101aae2f..d16f4e8ea23 100644 --- a/pkg/registry/service_registry_test.go +++ b/pkg/registry/service_registry_test.go @@ -33,6 +33,7 @@ func TestServiceRegistry(t *testing.T) { svc := api.Service{ JSONBase: api.JSONBase{ID: "foo"}, + Selector: map[string]string{"bar": "baz"}, } c, _ := storage.Create(svc) <-c @@ -56,6 +57,7 @@ func TestServiceRegistryExternalService(t *testing.T) { svc := api.Service{ JSONBase: api.JSONBase{ID: "foo"}, + Selector: map[string]string{"bar": "baz"}, CreateExternalLoadBalancer: true, } c, _ := storage.Create(svc) @@ -82,6 +84,7 @@ func TestServiceRegistryExternalServiceError(t *testing.T) { svc := api.Service{ JSONBase: api.JSONBase{ID: "foo"}, + Selector: map[string]string{"bar": "baz"}, CreateExternalLoadBalancer: true, } c, _ := storage.Create(svc) @@ -106,6 +109,7 @@ func TestServiceRegistryDelete(t *testing.T) { svc := api.Service{ JSONBase: api.JSONBase{ID: "foo"}, + Selector: map[string]string{"bar": "baz"}, } memory.CreateService(svc) @@ -131,6 +135,7 @@ func TestServiceRegistryDeleteExternal(t *testing.T) { svc := api.Service{ JSONBase: api.JSONBase{ID: "foo"}, + Selector: map[string]string{"bar": "baz"}, CreateExternalLoadBalancer: true, } memory.CreateService(svc) From 5bf08205803b93b8d45b50638f3d2911eb9e05c0 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Thu, 10 Jul 2014 21:14:46 -0700 Subject: [PATCH 15/45] Fix integration tests. --- cmd/integration/integration.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cmd/integration/integration.go b/cmd/integration/integration.go index 50f55df4513..0295b829c65 100644 --- a/cmd/integration/integration.go +++ b/cmd/integration/integration.go @@ -145,13 +145,19 @@ func runAtomicPutTest(c *client.Client) { Labels: map[string]string{ "name": "atomicService", }, + // This is here because validation requires it. + Selector: map[string]string{ + "foo": "bar", + }, }, ).Do().Into(&svc) if err != nil { glog.Fatalf("Failed creating atomicService: %v", err) } glog.Info("Created atomicService") - testLabels := labels.Set{} + testLabels := labels.Set{ + "foo": "bar", + } for i := 0; i < 5; i++ { // a: z, b: y, etc... testLabels[string([]byte{byte('a' + i)})] = string([]byte{byte('z' - i)}) From c9babe619e46b76a08d9d98fe0cdb84268043d6c Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Fri, 11 Jul 2014 14:24:31 -0700 Subject: [PATCH 16/45] Updated to use the makeInvalidError helper function. --- pkg/api/validation.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/api/validation.go b/pkg/api/validation.go index e3572d4a3c4..a73373efa97 100644 --- a/pkg/api/validation.go +++ b/pkg/api/validation.go @@ -250,10 +250,10 @@ func ValidateManifest(manifest *ContainerManifest) []error { func ValidateService(service *Service) []error { allErrs := errorList{} if service.ID == "" { - allErrs.Append(fmt.Errorf("ID should not be empty: %#v", *service)) + allErrs.Append(makeInvalidError("Service.ID", service.ID)) } if len(service.Selector) == 0 { - allErrs.Append(fmt.Errorf("Service %#v missing a selector", *service)) + allErrs.Append(makeInvalidError("Service.Selector", service.Selector)) } return []error(allErrs) } From ba5cadef5940285cb8d6ab6e10793185e055e9e6 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Fri, 11 Jul 2014 16:12:09 -0700 Subject: [PATCH 17/45] Add some sanity checks to the kube-up script. --- cluster/kube-up.sh | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/cluster/kube-up.sh b/cluster/kube-up.sh index 3a4f97ececc..bb5d6223166 100755 --- a/cluster/kube-up.sh +++ b/cluster/kube-up.sh @@ -133,6 +133,25 @@ until $(curl --insecure --user ${user}:${passwd} --max-time 1 \ sleep 2 done +# Basic sanity checking +for (( i=0; i<${#MINION_NAMES[@]}; i++)); do + # Make sure docker is installed + gcutil ssh ${MINION_NAMES[$i]} which docker > /dev/null + if [ "$?" != "0" ]; then + echo "Docker failed to install on ${MINION_NAMES[$i]} your cluster is unlikely to work correctly" + echo "Please run ./cluster/kube-down.sh and re-create the cluster. (sorry!)" + exit 1 + fi + + # Make sure the kubelet is running + gcutil ssh ${MINION_NAMES[$i]} /etc/init.d/kubelet status + if [ "$?" != "0" ]; then + echo "Kubelet failed to install on ${MINION_NAMES[$i]} your cluster is unlikely to work correctly" + echo "Please run ./cluster/kube-down.sh and re-create the cluster. (sorry!)" + exit 1 + fi +done + echo echo "Kubernetes cluster is running. Access the master at:" echo From bf137c38e7cf14d658824f10d4c09a7eb1f5c9be Mon Sep 17 00:00:00 2001 From: Kouhei Ueno Date: Sat, 12 Jul 2014 13:16:16 +0900 Subject: [PATCH 18/45] remove comments on Make.+() --- pkg/scheduler/firstfit.go | 1 - pkg/scheduler/random.go | 1 - pkg/scheduler/roundrobin.go | 1 - 3 files changed, 3 deletions(-) diff --git a/pkg/scheduler/firstfit.go b/pkg/scheduler/firstfit.go index 35073072e5e..e1e088c5cc4 100644 --- a/pkg/scheduler/firstfit.go +++ b/pkg/scheduler/firstfit.go @@ -31,7 +31,6 @@ type FirstFitScheduler struct { random *rand.Rand } -// MakeFirstFitScheduler creates a new instance of FirstFitScheduler. func MakeFirstFitScheduler(podLister PodLister, random *rand.Rand) Scheduler { return &FirstFitScheduler{ podLister: podLister, diff --git a/pkg/scheduler/random.go b/pkg/scheduler/random.go index f488548ce90..23a076636a8 100644 --- a/pkg/scheduler/random.go +++ b/pkg/scheduler/random.go @@ -28,7 +28,6 @@ type RandomScheduler struct { random *rand.Rand } -// MakeRandomScheduler creates a new RandomScheduler instance. func MakeRandomScheduler(random *rand.Rand) Scheduler { return &RandomScheduler{ random: random, diff --git a/pkg/scheduler/roundrobin.go b/pkg/scheduler/roundrobin.go index eadbab0cabf..02ad62a0881 100644 --- a/pkg/scheduler/roundrobin.go +++ b/pkg/scheduler/roundrobin.go @@ -25,7 +25,6 @@ type RoundRobinScheduler struct { currentIndex int } -// MakeRoundRobinScheduler creates a new RoundRobinScheduler instance. func MakeRoundRobinScheduler() Scheduler { return &RoundRobinScheduler{ currentIndex: -1, From f93289142859ba66c000d7b1647498d0e555894a Mon Sep 17 00:00:00 2001 From: Kouhei Ueno Date: Sat, 12 Jul 2014 13:21:48 +0900 Subject: [PATCH 19/45] removes a comment on New and a typo fix --- pkg/registry/controller_registry.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/registry/controller_registry.go b/pkg/registry/controller_registry.go index a43fc51fd57..ae59cd37ec0 100644 --- a/pkg/registry/controller_registry.go +++ b/pkg/registry/controller_registry.go @@ -33,7 +33,6 @@ type ControllerRegistryStorage struct { pollPeriod time.Duration } -// NewControllerRegistryStorage creates a new NewControllerRegistryStorage instance. func NewControllerRegistryStorage(registry ControllerRegistry, podRegistry PodRegistry) apiserver.RESTStorage { return &ControllerRegistryStorage{ registry: registry, @@ -97,7 +96,7 @@ func (storage *ControllerRegistryStorage) Create(obj interface{}) (<-chan interf }), nil } -// Update replaces a given ReplicationController instance with an existing instnace in storage.registry. +// Update replaces a given ReplicationController instance with an existing instance in storage.registry. func (storage *ControllerRegistryStorage) Update(obj interface{}) (<-chan interface{}, error) { controller, ok := obj.(api.ReplicationController) if !ok { From 4d47db8b5f215f8643c2e66152bfdd935e88384d Mon Sep 17 00:00:00 2001 From: Claire Li Date: Fri, 11 Jul 2014 21:34:42 -0700 Subject: [PATCH 20/45] pkg/proxy: remove unnecessary go routine --- pkg/proxy/proxier.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/proxy/proxier.go b/pkg/proxy/proxier.go index 6c1a045a769..70eda18d090 100644 --- a/pkg/proxy/proxier.go +++ b/pkg/proxy/proxier.go @@ -83,7 +83,7 @@ func (proxier Proxier) AcceptHandler(service string, listener net.Listener) { inConn.Close() continue } - go ProxyConnection(inConn.(*net.TCPConn), outConn.(*net.TCPConn)) + ProxyConnection(inConn.(*net.TCPConn), outConn.(*net.TCPConn)) } } From abcbce55ce3648b319dc6c59f567bdca253688f6 Mon Sep 17 00:00:00 2001 From: Claire Li Date: Fri, 11 Jul 2014 21:51:24 -0700 Subject: [PATCH 21/45] pkg/proxy: remove unnecessary io.EOF checking --- pkg/proxy/proxier.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/proxy/proxier.go b/pkg/proxy/proxier.go index 6c1a045a769..d10f05d9b42 100644 --- a/pkg/proxy/proxier.go +++ b/pkg/proxy/proxier.go @@ -41,7 +41,7 @@ func CopyBytes(in, out *net.TCPConn) { glog.Infof("Copying from %v <-> %v <-> %v <-> %v", in.RemoteAddr(), in.LocalAddr(), out.LocalAddr(), out.RemoteAddr()) _, err := io.Copy(in, out) - if err != nil && err != io.EOF { + if err != nil { glog.Errorf("I/O error: %v", err) } From ba07dc804c9c182b20075e5ce903570482e36ba0 Mon Sep 17 00:00:00 2001 From: Claire Li Date: Fri, 11 Jul 2014 22:08:53 -0700 Subject: [PATCH 22/45] pkg/proxy: use net.SplitHostPort instead of strings.Split --- pkg/proxy/roundrobbin.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pkg/proxy/roundrobbin.go b/pkg/proxy/roundrobbin.go index 9d9f2413540..e1526be1998 100644 --- a/pkg/proxy/roundrobbin.go +++ b/pkg/proxy/roundrobbin.go @@ -23,7 +23,6 @@ import ( "net" "reflect" "strconv" - "strings" "sync" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" @@ -57,11 +56,11 @@ func (impl LoadBalancerRR) LoadBalance(service string, srcAddr net.Addr) (string } func (impl LoadBalancerRR) IsValid(spec string) bool { - index := strings.Index(spec, ":") - if index == -1 { + _, port, err := net.SplitHostPort(spec) + if err != nil { return false } - value, err := strconv.Atoi(spec[index+1:]) + value, err := strconv.Atoi(port) if err != nil { return false } From 094b78d7dead5ca474a8489c3494801a5398ea5f Mon Sep 17 00:00:00 2001 From: Claire Li Date: Fri, 11 Jul 2014 22:13:29 -0700 Subject: [PATCH 23/45] pkg/proxy: filtering before comparing --- pkg/proxy/roundrobbin.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/proxy/roundrobbin.go b/pkg/proxy/roundrobbin.go index 9d9f2413540..bb93922798e 100644 --- a/pkg/proxy/roundrobbin.go +++ b/pkg/proxy/roundrobbin.go @@ -85,9 +85,10 @@ func (impl LoadBalancerRR) OnUpdate(endpoints []api.Endpoints) { // First update / add all new endpoints for services. for _, value := range endpoints { existingEndpoints, exists := impl.endpointsMap[value.Name] - if !exists || !reflect.DeepEqual(value.Endpoints, existingEndpoints) { + validEndpoints := impl.FilterValidEndpoints(value.Endpoints) + if !exists || !reflect.DeepEqual(existingEndpoints, validEndpoints) { glog.Infof("LoadBalancerRR: Setting endpoints for %s to %+v", value.Name, value.Endpoints) - impl.endpointsMap[value.Name] = impl.FilterValidEndpoints(value.Endpoints) + impl.endpointsMap[value.Name] = validEndpoints // Start RR from the beginning if added or updated. impl.rrIndex[value.Name] = 0 } From 78cad3fd5e03df2f63623ca654f813a5f29bb982 Mon Sep 17 00:00:00 2001 From: Claire Li Date: Fri, 11 Jul 2014 22:41:06 -0700 Subject: [PATCH 24/45] pkg/proxy/config: clean up file.Run() --- pkg/proxy/config/file.go | 59 +++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/pkg/proxy/config/file.go b/pkg/proxy/config/file.go index 6ae0a09db8d..65a1b9705bf 100644 --- a/pkg/proxy/config/file.go +++ b/pkg/proxy/config/file.go @@ -77,35 +77,38 @@ func (impl ConfigSourceFile) Run() { data, err := ioutil.ReadFile(impl.filename) if err != nil { glog.Errorf("Couldn't read file: %s : %v", impl.filename, err) - } else { - var config ConfigFile - err = json.Unmarshal(data, &config) - if err != nil { - glog.Errorf("Couldn't unmarshal configuration from file : %s %v", data, err) - } else { - if !bytes.Equal(lastData, data) { - lastData = data - // Ok, we have a valid configuration, send to channel for - // rejiggering. - newServices := make([]api.Service, len(config.Services)) - newEndpoints := make([]api.Endpoints, len(config.Services)) - for i, service := range config.Services { - newServices[i] = api.Service{JSONBase: api.JSONBase{ID: service.Name}, Port: service.Port} - newEndpoints[i] = api.Endpoints{Name: service.Name, Endpoints: service.Endpoints} - } - if !reflect.DeepEqual(lastServices, newServices) { - serviceUpdate := ServiceUpdate{Op: SET, Services: newServices} - impl.serviceChannel <- serviceUpdate - lastServices = newServices - } - if !reflect.DeepEqual(lastEndpoints, newEndpoints) { - endpointsUpdate := EndpointsUpdate{Op: SET, Endpoints: newEndpoints} - impl.endpointsChannel <- endpointsUpdate - lastEndpoints = newEndpoints - } - } - } + continue } + + if bytes.Equal(lastData, data) { + continue + } + lastData = data + + config := new(ConfigFile) + if err = json.Unmarshal(data, config); err != nil { + glog.Errorf("Couldn't unmarshal configuration from file : %s %v", data, err) + continue + } + // Ok, we have a valid configuration, send to channel for + // rejiggering. + newServices := make([]api.Service, len(config.Services)) + newEndpoints := make([]api.Endpoints, len(config.Services)) + for i, service := range config.Services { + newServices[i] = api.Service{JSONBase: api.JSONBase{ID: service.Name}, Port: service.Port} + newEndpoints[i] = api.Endpoints{Name: service.Name, Endpoints: service.Endpoints} + } + if !reflect.DeepEqual(lastServices, newServices) { + serviceUpdate := ServiceUpdate{Op: SET, Services: newServices} + impl.serviceChannel <- serviceUpdate + lastServices = newServices + } + if !reflect.DeepEqual(lastEndpoints, newEndpoints) { + endpointsUpdate := EndpointsUpdate{Op: SET, Endpoints: newEndpoints} + impl.endpointsChannel <- endpointsUpdate + lastEndpoints = newEndpoints + } + time.Sleep(5 * time.Second) } } From 12c9a732055fe378e864eaf8c24d724c046ed902 Mon Sep 17 00:00:00 2001 From: Kouhei Ueno Date: Sat, 12 Jul 2014 14:44:13 +0900 Subject: [PATCH 25/45] Add comments on registry/etcd_registry.go --- pkg/registry/etcd_registry.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pkg/registry/etcd_registry.go b/pkg/registry/etcd_registry.go index 1f171e0d240..5f9c75be758 100644 --- a/pkg/registry/etcd_registry.go +++ b/pkg/registry/etcd_registry.go @@ -58,6 +58,7 @@ func (registry *EtcdRegistry) helper() *tools.EtcdHelper { return &tools.EtcdHelper{registry.etcdClient} } +// ListPods obtains a list of pods that match selector. func (registry *EtcdRegistry) ListPods(selector labels.Selector) ([]api.Pod, error) { pods := []api.Pod{} machines, err := registry.machines.List() @@ -80,6 +81,7 @@ func (registry *EtcdRegistry) ListPods(selector labels.Selector) ([]api.Pod, err return pods, nil } +// GetPod gets a specific pod specified by its ID. func (registry *EtcdRegistry) GetPod(podID string) (*api.Pod, error) { pod, _, err := registry.findPod(podID) return &pod, err @@ -89,6 +91,7 @@ func makeContainerKey(machine string) string { return "/registry/hosts/" + machine + "/kubelet" } +// CreatePod creates a pod based on a specification, schedule it onto a specific machine. func (registry *EtcdRegistry) CreatePod(machineIn string, pod api.Pod) error { podOut, machine, err := registry.findPod(pod.ID) if err == nil { @@ -126,6 +129,7 @@ func (registry *EtcdRegistry) UpdatePod(pod api.Pod) error { return fmt.Errorf("unimplemented!") } +// DeletePod deletes an existing pod specified by its ID. func (registry *EtcdRegistry) DeletePod(podID string) error { _, machine, err := registry.findPod(podID) if err != nil { @@ -190,6 +194,7 @@ func (registry *EtcdRegistry) findPod(podID string) (api.Pod, string, error) { return api.Pod{}, "", fmt.Errorf("pod not found %s", podID) } +// ListControllers obtains a list of ReplicationControllers. func (registry *EtcdRegistry) ListControllers() ([]api.ReplicationController, error) { var controllers []api.ReplicationController err := registry.helper().ExtractList("/registry/controllers", &controllers) @@ -200,6 +205,7 @@ func makeControllerKey(id string) string { return "/registry/controllers/" + id } +// GetController gets a specific ReplicationController specified by its ID. func (registry *EtcdRegistry) GetController(controllerID string) (*api.ReplicationController, error) { var controller api.ReplicationController key := makeControllerKey(controllerID) @@ -210,15 +216,18 @@ func (registry *EtcdRegistry) GetController(controllerID string) (*api.Replicati return &controller, nil } +// CreateController creates a new ReplicationController. func (registry *EtcdRegistry) CreateController(controller api.ReplicationController) error { // TODO : check for existence here and error. return registry.UpdateController(controller) } +// UpdateController replaces an existing ReplicationController. func (registry *EtcdRegistry) UpdateController(controller api.ReplicationController) error { return registry.helper().SetObj(makeControllerKey(controller.ID), controller) } +// DeleteController deletes a ReplicationController specified by its ID. func (registry *EtcdRegistry) DeleteController(controllerID string) error { key := makeControllerKey(controllerID) _, err := registry.etcdClient.Delete(key, false) @@ -229,16 +238,19 @@ func makeServiceKey(name string) string { return "/registry/services/specs/" + name } +// ListServices obtains a list of Services. func (registry *EtcdRegistry) ListServices() (api.ServiceList, error) { var list api.ServiceList err := registry.helper().ExtractList("/registry/services/specs", &list.Items) return list, err } +// CreateService creates a new Service. func (registry *EtcdRegistry) CreateService(svc api.Service) error { return registry.helper().SetObj(makeServiceKey(svc.ID), svc) } +// GetService obtains a Service specified by its name. func (registry *EtcdRegistry) GetService(name string) (*api.Service, error) { key := makeServiceKey(name) var svc api.Service @@ -249,6 +261,7 @@ func (registry *EtcdRegistry) GetService(name string) (*api.Service, error) { return &svc, nil } +// DeleteService deletes a Service specified by its name. func (registry *EtcdRegistry) DeleteService(name string) error { key := makeServiceKey(name) _, err := registry.etcdClient.Delete(key, true) @@ -260,10 +273,13 @@ func (registry *EtcdRegistry) DeleteService(name string) error { return err } +// UpdateService replaces an existing Service. func (registry *EtcdRegistry) UpdateService(svc api.Service) error { + // TODO : check for existence here and error. return registry.CreateService(svc) } +// UpdateEndpoints update Endpoints of a Service. func (registry *EtcdRegistry) UpdateEndpoints(e api.Endpoints) error { return registry.helper().SetObj("/registry/services/endpoints/"+e.Name, e) } From 2b183943c2e094a48b029b0a30a4d3d4b04b258e Mon Sep 17 00:00:00 2001 From: Claire Li Date: Fri, 11 Jul 2014 22:59:50 -0700 Subject: [PATCH 26/45] pkg/selector: set the cap for slice in SelectorFromSet --- pkg/labels/selector.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/labels/selector.go b/pkg/labels/selector.go index 668699ee8ab..ac9c534d921 100644 --- a/pkg/labels/selector.go +++ b/pkg/labels/selector.go @@ -89,7 +89,7 @@ func try(selectorPiece, op string) (lhs, rhs string, ok bool) { // Given a Set, return a Selector which will match exactly that Set. func SelectorFromSet(ls Set) Selector { - var items []Selector + items := make([]Selector, 0, len(ls)) for label, value := range ls { items = append(items, &hasTerm{label: label, value: value}) } From d0aad153ee117f11effe4b8911c7815f04ecdca6 Mon Sep 17 00:00:00 2001 From: Claire Li Date: Fri, 11 Jul 2014 23:09:43 -0700 Subject: [PATCH 27/45] pkg/master: correct error string for GetPodInfo --- pkg/master/pod_cache.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/master/pod_cache.go b/pkg/master/pod_cache.go index d075e6cde57..14416af2ba5 100644 --- a/pkg/master/pod_cache.go +++ b/pkg/master/pod_cache.go @@ -56,7 +56,7 @@ func (p *PodCache) GetPodInfo(host, podID string) (api.PodInfo, error) { defer p.podLock.Unlock() value, ok := p.podInfo[podID] if !ok { - return nil, errors.New("No cached pod info") + return nil, errors.New("no cached pod info") } else { return value, nil } From 663b80b6a367d6c7f723088dbd253eeceb333e4c Mon Sep 17 00:00:00 2001 From: Kouhei Ueno Date: Sat, 12 Jul 2014 15:37:24 +0900 Subject: [PATCH 28/45] make hack/config-go.sh sourceable from zsh --- hack/config-go.sh | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/hack/config-go.sh b/hack/config-go.sh index 988952b42a5..3043fc3c191 100755 --- a/hack/config-go.sh +++ b/hack/config-go.sh @@ -17,7 +17,7 @@ # This script sets up a go workspace locally and builds all go components. # You can 'source' this file if you want to set up GOPATH in your local shell. -if [ "$(which go)" == "" ]; then +if [[ "$(which go)" == "" ]]; then echo "Can't find 'go' in PATH, please fix and retry." echo "See http://golang.org/doc/install for installation instructions." exit 1 @@ -29,7 +29,7 @@ fi if [ "${TRAVIS}" != "true" ]; then GO_VERSION=($(go version)) - if [ ${GO_VERSION[2]} \< "go1.2" ]; then + if [[ ${GO_VERSION[2]} < "go1.2" ]]; then echo "Detected go version: ${GO_VERSION}." echo "Kubernetes requires go version 1.2 or greater." echo "Please install Go version 1.2 or later" @@ -37,10 +37,9 @@ if [ "${TRAVIS}" != "true" ]; then fi fi -pushd $(dirname "${BASH_SOURCE}")/.. >/dev/null -KUBE_REPO_ROOT="${PWD}" +KUBE_REPO_REL_ROOT="$(dirname ${BASH_SOURCE:-$0})/.." +KUBE_REPO_ROOT="$(readlink -f ${KUBE_REPO_REL_ROOT})" KUBE_TARGET="${KUBE_REPO_ROOT}/output/go" -popd >/dev/null mkdir -p "${KUBE_TARGET}" From 2a03a4d502095f09520e666b32b88e1549a1ee0d Mon Sep 17 00:00:00 2001 From: Kouhei Ueno Date: Sat, 12 Jul 2014 15:06:36 +0900 Subject: [PATCH 29/45] make random.go threadsafe --- pkg/scheduler/random.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/scheduler/random.go b/pkg/scheduler/random.go index 23a076636a8..6cf0e9057bc 100644 --- a/pkg/scheduler/random.go +++ b/pkg/scheduler/random.go @@ -18,14 +18,15 @@ package scheduler import ( "math/rand" + "sync" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" ) // RandomScheduler chooses machines uniformly at random. type RandomScheduler struct { - // TODO: rand.Rand is *NOT* thread safe. - random *rand.Rand + random *rand.Rand + randomLock sync.Mutex } func MakeRandomScheduler(random *rand.Rand) Scheduler { @@ -40,5 +41,8 @@ func (s *RandomScheduler) Schedule(pod api.Pod, minionLister MinionLister) (stri if err != nil { return "", err } + + s.randomLock.Lock() + defer s.randomLock.Unlock() return machines[s.random.Int()%len(machines)], nil } From 070b4ffe9f9960b5f06efa68c48b0ef09cdd52dd Mon Sep 17 00:00:00 2001 From: Kouhei Ueno Date: Sat, 12 Jul 2014 15:06:51 +0900 Subject: [PATCH 30/45] make firstfit.go threadsafe and fix its comments --- pkg/scheduler/firstfit.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pkg/scheduler/firstfit.go b/pkg/scheduler/firstfit.go index e1e088c5cc4..250fa99faf1 100644 --- a/pkg/scheduler/firstfit.go +++ b/pkg/scheduler/firstfit.go @@ -19,16 +19,17 @@ package scheduler import ( "fmt" "math/rand" + "sync" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" ) -// FirstFitScheduler is a Scheduler interface implementation which uses first fit algorithm. +// FirstFitScheduler is a Scheduler which schedules a Pod on a random machine which matches its requirement. type FirstFitScheduler struct { - podLister PodLister - // TODO: *rand.Rand is *not* threadsafe - random *rand.Rand + podLister PodLister + random *rand.Rand + randomLock sync.Mutex } func MakeFirstFitScheduler(podLister PodLister, random *rand.Rand) Scheduler { @@ -49,7 +50,7 @@ func (s *FirstFitScheduler) containsPort(pod api.Pod, port api.Port) bool { return false } -// Schedule schedules a pod on the first machine which matches its requirement. +// Schedule schedules a pod on a random machine which matches its requirement. func (s *FirstFitScheduler) Schedule(pod api.Pod, minionLister MinionLister) (string, error) { machines, err := minionLister.List() if err != nil { @@ -83,5 +84,7 @@ func (s *FirstFitScheduler) Schedule(pod api.Pod, minionLister MinionLister) (st if len(machineOptions) == 0 { return "", fmt.Errorf("failed to find fit for %#v", pod) } + s.randomLock.Lock() + defer s.randomLock.Unlock() return machineOptions[s.random.Int()%len(machineOptions)], nil } From c875a6d3ba1e9ebe74fe20448da9be4745337e98 Mon Sep 17 00:00:00 2001 From: Kouhei Ueno Date: Sat, 12 Jul 2014 23:14:39 +0900 Subject: [PATCH 31/45] rename FirstFitScheduler to RandomFitScheduler --- pkg/master/master.go | 2 +- pkg/scheduler/{firstfit.go => randomfit.go} | 12 ++++++------ .../{firstfit_test.go => randomfit_test.go} | 16 ++++++++-------- 3 files changed, 15 insertions(+), 15 deletions(-) rename pkg/scheduler/{firstfit.go => randomfit.go} (83%) rename pkg/scheduler/{firstfit_test.go => randomfit_test.go} (78%) diff --git a/pkg/master/master.go b/pkg/master/master.go index 80831fa095e..74c40d0081b 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -84,7 +84,7 @@ func (m *Master) init(cloud cloudprovider.Interface, podInfoGetter client.PodInf m.random = rand.New(rand.NewSource(int64(time.Now().Nanosecond()))) podCache := NewPodCache(podInfoGetter, m.podRegistry, time.Second*30) go podCache.Loop() - s := scheduler.MakeFirstFitScheduler(m.podRegistry, m.random) + s := scheduler.MakeRandomFitScheduler(m.podRegistry, m.random) m.storage = map[string]apiserver.RESTStorage{ "pods": registry.MakePodRegistryStorage(m.podRegistry, podInfoGetter, s, m.minionRegistry, cloud, podCache), "replicationControllers": registry.NewControllerRegistryStorage(m.controllerRegistry, m.podRegistry), diff --git a/pkg/scheduler/firstfit.go b/pkg/scheduler/randomfit.go similarity index 83% rename from pkg/scheduler/firstfit.go rename to pkg/scheduler/randomfit.go index 250fa99faf1..20193fde255 100644 --- a/pkg/scheduler/firstfit.go +++ b/pkg/scheduler/randomfit.go @@ -25,21 +25,21 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" ) -// FirstFitScheduler is a Scheduler which schedules a Pod on a random machine which matches its requirement. -type FirstFitScheduler struct { +// RandomFitScheduler is a Scheduler which schedules a Pod on a random machine which matches its requirement. +type RandomFitScheduler struct { podLister PodLister random *rand.Rand randomLock sync.Mutex } -func MakeFirstFitScheduler(podLister PodLister, random *rand.Rand) Scheduler { - return &FirstFitScheduler{ +func MakeRandomFitScheduler(podLister PodLister, random *rand.Rand) Scheduler { + return &RandomFitScheduler{ podLister: podLister, random: random, } } -func (s *FirstFitScheduler) containsPort(pod api.Pod, port api.Port) bool { +func (s *RandomFitScheduler) containsPort(pod api.Pod, port api.Port) bool { for _, container := range pod.DesiredState.Manifest.Containers { for _, podPort := range container.Ports { if podPort.HostPort == port.HostPort { @@ -51,7 +51,7 @@ func (s *FirstFitScheduler) containsPort(pod api.Pod, port api.Port) bool { } // Schedule schedules a pod on a random machine which matches its requirement. -func (s *FirstFitScheduler) Schedule(pod api.Pod, minionLister MinionLister) (string, error) { +func (s *RandomFitScheduler) Schedule(pod api.Pod, minionLister MinionLister) (string, error) { machines, err := minionLister.List() if err != nil { return "", err diff --git a/pkg/scheduler/firstfit_test.go b/pkg/scheduler/randomfit_test.go similarity index 78% rename from pkg/scheduler/firstfit_test.go rename to pkg/scheduler/randomfit_test.go index 57e8444b0e1..1cdf505b9e1 100644 --- a/pkg/scheduler/firstfit_test.go +++ b/pkg/scheduler/randomfit_test.go @@ -23,31 +23,31 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" ) -func TestFirstFitSchedulerNothingScheduled(t *testing.T) { +func TestRandomFitSchedulerNothingScheduled(t *testing.T) { fakeRegistry := FakePodLister{} r := rand.New(rand.NewSource(0)) st := schedulerTester{ t: t, - scheduler: MakeFirstFitScheduler(&fakeRegistry, r), + scheduler: MakeRandomFitScheduler(&fakeRegistry, r), minionLister: FakeMinionLister{"m1", "m2", "m3"}, } st.expectSchedule(api.Pod{}, "m3") } -func TestFirstFitSchedulerFirstScheduled(t *testing.T) { +func TestRandomFitSchedulerFirstScheduled(t *testing.T) { fakeRegistry := FakePodLister{ makePod("m1", 8080), } r := rand.New(rand.NewSource(0)) st := schedulerTester{ t: t, - scheduler: MakeFirstFitScheduler(fakeRegistry, r), + scheduler: MakeRandomFitScheduler(fakeRegistry, r), minionLister: FakeMinionLister{"m1", "m2", "m3"}, } st.expectSchedule(makePod("", 8080), "m3") } -func TestFirstFitSchedulerFirstScheduledComplicated(t *testing.T) { +func TestRandomFitSchedulerFirstScheduledComplicated(t *testing.T) { fakeRegistry := FakePodLister{ makePod("m1", 80, 8080), makePod("m2", 8081, 8082, 8083), @@ -56,13 +56,13 @@ func TestFirstFitSchedulerFirstScheduledComplicated(t *testing.T) { r := rand.New(rand.NewSource(0)) st := schedulerTester{ t: t, - scheduler: MakeFirstFitScheduler(fakeRegistry, r), + scheduler: MakeRandomFitScheduler(fakeRegistry, r), minionLister: FakeMinionLister{"m1", "m2", "m3"}, } st.expectSchedule(makePod("", 8080, 8081), "m3") } -func TestFirstFitSchedulerFirstScheduledImpossible(t *testing.T) { +func TestRandomFitSchedulerFirstScheduledImpossible(t *testing.T) { fakeRegistry := FakePodLister{ makePod("m1", 8080), makePod("m2", 8081), @@ -71,7 +71,7 @@ func TestFirstFitSchedulerFirstScheduledImpossible(t *testing.T) { r := rand.New(rand.NewSource(0)) st := schedulerTester{ t: t, - scheduler: MakeFirstFitScheduler(fakeRegistry, r), + scheduler: MakeRandomFitScheduler(fakeRegistry, r), minionLister: FakeMinionLister{"m1", "m2", "m3"}, } st.expectFailure(makePod("", 8080, 8081)) From cdc44387c626be9e9ad24b884664470dca13fcc2 Mon Sep 17 00:00:00 2001 From: Claire Li Date: Sat, 12 Jul 2014 10:44:29 -0700 Subject: [PATCH 32/45] pkg/kubelet: clean up server http error handling --- pkg/kubelet/kubelet_server.go | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/pkg/kubelet/kubelet_server.go b/pkg/kubelet/kubelet_server.go index 5154c7ddc98..0e6d7cc8fb5 100644 --- a/pkg/kubelet/kubelet_server.go +++ b/pkg/kubelet/kubelet_server.go @@ -46,8 +46,7 @@ type kubeletInterface interface { } func (s *KubeletServer) error(w http.ResponseWriter, err error) { - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprintf(w, "Internal Error: %v", err) + http.Error(w, fmt.Sprintf("Internal Error: %v", err), http.StatusInternalServerError) } func (s *KubeletServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { @@ -87,20 +86,17 @@ func (s *KubeletServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { case u.Path == "/podInfo": podID := u.Query().Get("podID") if len(podID) == 0 { - w.WriteHeader(http.StatusBadRequest) - fmt.Fprint(w, "Missing 'podID=' query entry.") + http.Error(w, "Missing 'podID=' query entry.", http.StatusBadRequest) return } info, err := s.Kubelet.GetPodInfo(podID) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprintf(w, "Internal Error: %v", err) + s.error(w, err) return } data, err := json.Marshal(info) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprintf(w, "Internal Error: %v", err) + s.error(w, err) return } w.WriteHeader(http.StatusOK) @@ -109,8 +105,7 @@ func (s *KubeletServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { case strings.HasPrefix(u.Path, "/stats"): s.serveStats(w, req) default: - w.WriteHeader(http.StatusNotFound) - fmt.Fprint(w, "Not found.") + http.Error(w, "Not found.", http.StatusNotFound) } } @@ -130,13 +125,11 @@ func (s *KubeletServer) serveStats(w http.ResponseWriter, req *http.Request) { case 3: stats, err = s.Kubelet.GetContainerStats(components[1], components[2]) default: - w.WriteHeader(http.StatusNotFound) - fmt.Fprint(w, "unknown resource.") + http.Error(w, "unknown resource.", http.StatusNotFound) return } if err != nil { - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprintf(w, "Internal Error: %v", err) + s.error(w, err) return } if stats == nil { @@ -146,8 +139,7 @@ func (s *KubeletServer) serveStats(w http.ResponseWriter, req *http.Request) { } data, err := json.Marshal(stats) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprintf(w, "Internal Error: %v", err) + s.error(w, err) return } w.WriteHeader(http.StatusOK) From 10de5b12fe44d93cc5bdd5b6a53691cca8d8283f Mon Sep 17 00:00:00 2001 From: Claire Li Date: Sat, 12 Jul 2014 11:03:08 -0700 Subject: [PATCH 33/45] pkg/kubelet: explicitly return nil as error and nil as a result on error --- pkg/kubelet/kubelet.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index 39ad4b1dd8f..98f23e4ee54 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -192,7 +192,7 @@ func (kl *Kubelet) getDockerContainers() (map[DockerID]docker.APIContainers, err result := map[DockerID]docker.APIContainers{} containerList, err := kl.DockerClient.ListContainers(docker.ListContainersOptions{}) if err != nil { - return result, err + return nil, err } for _, value := range containerList { // Skip containers that we didn't create to allow users to manually @@ -202,7 +202,7 @@ func (kl *Kubelet) getDockerContainers() (map[DockerID]docker.APIContainers, err } result[DockerID(value.ID)] = value } - return result, err + return result, nil } // Return Docker's container ID for a manifest's container. Returns an empty string if it doesn't exist. From ba9c370014a54ac85ebeaeb7306a9460ba4b35c4 Mon Sep 17 00:00:00 2001 From: Claire Li Date: Fri, 11 Jul 2014 23:29:51 -0700 Subject: [PATCH 34/45] pkg/controller: cleanup replication_controller.go --- pkg/controller/replication_controller.go | 44 +++++++++++------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/pkg/controller/replication_controller.go b/pkg/controller/replication_controller.go index 4c834a58004..0ad529983e9 100644 --- a/pkg/controller/replication_controller.go +++ b/pkg/controller/replication_controller.go @@ -100,10 +100,9 @@ func (rm *ReplicationManager) Run(period time.Duration) { func (rm *ReplicationManager) watchControllers() { watchChannel := make(chan *etcd.Response) stop := make(chan bool) - defer func() { - // Ensure that the call to watch ends. - close(stop) - }() + // Ensure that the call to watch ends. + defer close(stop) + go func() { defer util.HandleCrash() _, err := rm.etcdClient.Watch("/registry/controllers", 0, true, watchChannel, stop) @@ -137,30 +136,29 @@ func (rm *ReplicationManager) watchControllers() { } func (rm *ReplicationManager) handleWatchResponse(response *etcd.Response) (*api.ReplicationController, error) { - if response.Action == "set" { - if response.Node != nil { - var controllerSpec api.ReplicationController - err := json.Unmarshal([]byte(response.Node.Value), &controllerSpec) - if err != nil { - return nil, err - } - return &controllerSpec, nil + switch response.Action { + case "set": + if response.Node == nil { + return nil, fmt.Errorf("response node is null %#v", response) } - return nil, fmt.Errorf("response node is null %#v", response) - } else if response.Action == "delete" { + var controllerSpec api.ReplicationController + if err := json.Unmarshal([]byte(response.Node.Value), &controllerSpec); err != nil { + return nil, err + } + return &controllerSpec, nil + case "delete": // Ensure that the final state of a replication controller is applied before it is deleted. // Otherwise, a replication controller could be modified and then deleted (for example, from 3 to 0 // replicas), and it would be non-deterministic which of its pods continued to exist. - if response.PrevNode != nil { - var controllerSpec api.ReplicationController - if err := json.Unmarshal([]byte(response.PrevNode.Value), &controllerSpec); err != nil { - return nil, err - } - return &controllerSpec, nil + if response.PrevNode == nil { + return nil, fmt.Errorf("previous node is null %#v", response) } - return nil, fmt.Errorf("previous node is null %#v", response) + var controllerSpec api.ReplicationController + if err := json.Unmarshal([]byte(response.PrevNode.Value), &controllerSpec); err != nil { + return nil, err + } + return &controllerSpec, nil } - return nil, nil } @@ -190,7 +188,7 @@ func (rm *ReplicationManager) syncReplicationController(controllerSpec api.Repli rm.podControl.createReplica(controllerSpec) } } else if diff > 0 { - glog.Info("Too many replicas, deleting") + glog.Infof("Too many replicas, deleting %d\n", diff) for i := 0; i < diff; i++ { rm.podControl.deletePod(filteredList[i].ID) } From 96187c10f09ec18d839a30f17fa550bad49142cf Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Sat, 12 Jul 2014 21:46:28 -0700 Subject: [PATCH 35/45] Fix build script for os x. --- hack/config-go.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/hack/config-go.sh b/hack/config-go.sh index 3043fc3c191..77682275639 100755 --- a/hack/config-go.sh +++ b/hack/config-go.sh @@ -37,8 +37,13 @@ if [ "${TRAVIS}" != "true" ]; then fi fi -KUBE_REPO_REL_ROOT="$(dirname ${BASH_SOURCE:-$0})/.." -KUBE_REPO_ROOT="$(readlink -f ${KUBE_REPO_REL_ROOT})" +if [[ "$OSTYPE" == *darwin* ]]; then + KUBE_REPO_ROOT="${PWD}" +else + KUBE_REPO_REL_ROOT="$(dirname ${BASH_SOURCE:-$0})/.." + KUBE_REPO_ROOT="$(readlink -f ${KUBE_REPO_REL_ROOT})" +fi + KUBE_TARGET="${KUBE_REPO_ROOT}/output/go" mkdir -p "${KUBE_TARGET}" From 23e9c39a9618818c07e54378e2cca336b38c4c20 Mon Sep 17 00:00:00 2001 From: Albert Zhang Date: Sun, 13 Jul 2014 18:47:52 +0800 Subject: [PATCH 36/45] make build script works on os x if the current dir is not the kubernetes root --- hack/config-go.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/config-go.sh b/hack/config-go.sh index 77682275639..e25421b374a 100755 --- a/hack/config-go.sh +++ b/hack/config-go.sh @@ -38,7 +38,7 @@ if [ "${TRAVIS}" != "true" ]; then fi if [[ "$OSTYPE" == *darwin* ]]; then - KUBE_REPO_ROOT="${PWD}" + KUBE_REPO_ROOT="${PWD}/$(dirname ${BASH_SOURCE:-$0})/.." else KUBE_REPO_REL_ROOT="$(dirname ${BASH_SOURCE:-$0})/.." KUBE_REPO_ROOT="$(readlink -f ${KUBE_REPO_REL_ROOT})" From 3a955722902e4a3abbb927d758535ac4feaa82d6 Mon Sep 17 00:00:00 2001 From: Yifan Gu Date: Mon, 14 Jul 2014 13:12:44 -0700 Subject: [PATCH 37/45] Changed the keepChannel to buffered channel. Since the keepChannel is used to send data, not just for synchronizaiton, so make it a buffered channel can avoid unnecessary goroutine yields. --- pkg/kubelet/kubelet.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index 98f23e4ee54..263f6c89f82 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -44,6 +44,8 @@ import ( "gopkg.in/v1/yaml" ) +const defaultChanSize = 1024 + // DockerContainerData is the structured representation of the JSON object returned by Docker inspect type DockerContainerData struct { state struct { @@ -758,7 +760,7 @@ func (kl *Kubelet) SyncManifests(config []api.ContainerManifest) error { glog.Infof("Desired: %+v", config) var err error dockerIdsToKeep := map[DockerID]empty{} - keepChannel := make(chan DockerID) + keepChannel := make(chan DockerID, defaultChanSize) waitGroup := sync.WaitGroup{} // Check for any containers that need starting From 7925cce67cb4e6cae7cc6c45cea15698ac154bd6 Mon Sep 17 00:00:00 2001 From: Julia Ferraioli Date: Mon, 14 Jul 2014 14:00:57 -0700 Subject: [PATCH 38/45] adding firewall instructions to the README --- examples/update-demo/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/examples/update-demo/README.md b/examples/update-demo/README.md index 612d69dd89f..4a8783949ed 100644 --- a/examples/update-demo/README.md +++ b/examples/update-demo/README.md @@ -29,6 +29,12 @@ This example also assumes that you have [Docker](http://docker.io) installed on It also assumes that ```$DOCKER_USER``` is set to your docker user id. +You may need to open the firewall for port 8080 using the [console][cloud-console] or the `gcutil` tool. The following command will allow traffic from any source to instances tagged `kubernetes-minion`: + +```shell +$ gcutil addfirewall --allowed=tcp:8080 --target_tags=kubernetes-minion kubernetes-minion-8080 +``` + ### Step One: Build the image $ cd kubernetes/examples/update-demo/image @@ -83,3 +89,5 @@ We will now update the servers that are running out in your cluster. $ cluster/kubecfg.sh -u=30s rollingupdate dataController Watch the UX, it will update one pod every 30 seconds until all of the pods have the new color. + +[cloud-console]: https://console.developer.google.com From 0e798bcc3cfab14e6eb6e2bd9071075936fb6d15 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Sat, 12 Jul 2014 21:46:01 -0700 Subject: [PATCH 39/45] Add link env vars. --- pkg/api/types.go | 5 ++ pkg/registry/manifest_factory_test.go | 105 ++++++++++++++++++++++---- pkg/registry/service_registry.go | 35 +++++++++ 3 files changed, 131 insertions(+), 14 deletions(-) diff --git a/pkg/api/types.go b/pkg/api/types.go index 6bc42f34589..e6400b27733 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -18,6 +18,8 @@ package api import ( "github.com/fsouza/go-dockerclient" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) // Common string formats @@ -267,6 +269,9 @@ type Service struct { // This service will route traffic to pods having labels matching this selector. Selector map[string]string `json:"selector,omitempty" yaml:"selector,omitempty"` CreateExternalLoadBalancer bool `json:"createExternalLoadBalancer,omitempty" yaml:"createExternalLoadBalancer,omitempty"` + + // Container port to connect to, either a name or a port number + ContainerPort util.IntOrString `json:"containerPort,omitempty" yaml:"containerPort,omitempty"` } // Endpoints is a collection of endpoints that implement the actual service, for example: diff --git a/pkg/registry/manifest_factory_test.go b/pkg/registry/manifest_factory_test.go index a215106e437..05710d60424 100644 --- a/pkg/registry/manifest_factory_test.go +++ b/pkg/registry/manifest_factory_test.go @@ -17,9 +17,11 @@ limitations under the License. package registry import ( + "reflect" "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) func TestMakeManifestNoServices(t *testing.T) { @@ -59,6 +61,10 @@ func TestMakeManifestServices(t *testing.T) { { JSONBase: api.JSONBase{ID: "test"}, Port: 8080, + ContainerPort: util.IntOrString{ + Kind: util.IntstrInt, + IntVal: 900, + }, }, }, }, @@ -80,12 +86,44 @@ func TestMakeManifestServices(t *testing.T) { }) expectNoError(t, err) container := manifest.Containers[0] - if len(container.Env) != 2 || - container.Env[0].Name != "TEST_SERVICE_PORT" || - container.Env[0].Value != "8080" || - container.Env[1].Name != "SERVICE_HOST" || - container.Env[1].Value != "machine" { - t.Errorf("Expected 2 env vars, got: %#v", manifest) + envs := []api.EnvVar{ + { + Name: "TEST_SERVICE_PORT", + Value: "8080", + }, + { + Name: "TEST_PORT", + Value: "tcp://machine:8080", + }, + { + Name: "TEST_PORT_900_TCP", + Value: "tcp://machine:8080", + }, + { + Name: "TEST_PORT_900_TCP_PROTO", + Value: "tcp", + }, + { + Name: "TEST_PORT_900_TCP_PORT", + Value: "8080", + }, + { + Name: "TEST_PORT_900_TCP_ADDR", + Value: "machine", + }, + { + Name: "SERVICE_HOST", + Value: "machine", + }, + } + if len(container.Env) != 7 { + t.Errorf("Expected 7 env vars, got %d: %#v", len(container.Env), manifest) + return + } + for ix := range container.Env { + if !reflect.DeepEqual(envs[ix], container.Env[ix]) { + t.Errorf("expected %#v, got %#v", envs[ix], container.Env[ix]) + } } } @@ -96,6 +134,10 @@ func TestMakeManifestServicesExistingEnvVar(t *testing.T) { { JSONBase: api.JSONBase{ID: "test"}, Port: 8080, + ContainerPort: util.IntOrString{ + Kind: util.IntstrInt, + IntVal: 900, + }, }, }, }, @@ -122,13 +164,48 @@ func TestMakeManifestServicesExistingEnvVar(t *testing.T) { }) expectNoError(t, err) container := manifest.Containers[0] - if len(container.Env) != 3 || - container.Env[0].Name != "foo" || - container.Env[0].Value != "bar" || - container.Env[1].Name != "TEST_SERVICE_PORT" || - container.Env[1].Value != "8080" || - container.Env[2].Name != "SERVICE_HOST" || - container.Env[2].Value != "machine" { - t.Errorf("Expected no env vars, got: %#v", manifest) + + envs := []api.EnvVar{ + { + Name: "foo", + Value: "bar", + }, + { + Name: "TEST_SERVICE_PORT", + Value: "8080", + }, + { + Name: "TEST_PORT", + Value: "tcp://machine:8080", + }, + { + Name: "TEST_PORT_900_TCP", + Value: "tcp://machine:8080", + }, + { + Name: "TEST_PORT_900_TCP_PROTO", + Value: "tcp", + }, + { + Name: "TEST_PORT_900_TCP_PORT", + Value: "8080", + }, + { + Name: "TEST_PORT_900_TCP_ADDR", + Value: "machine", + }, + { + Name: "SERVICE_HOST", + Value: "machine", + }, + } + if len(container.Env) != 8 { + t.Errorf("Expected 8 env vars, got: %#v", manifest) + return + } + for ix := range container.Env { + if !reflect.DeepEqual(envs[ix], container.Env[ix]) { + t.Errorf("expected %#v, got %#v", envs[ix], container.Env[ix]) + } } } diff --git a/pkg/registry/service_registry.go b/pkg/registry/service_registry.go index 6d4e0d8fd4d..70f8bb75f51 100644 --- a/pkg/registry/service_registry.go +++ b/pkg/registry/service_registry.go @@ -25,6 +25,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) type ServiceRegistryStorage struct { @@ -41,6 +42,39 @@ func MakeServiceRegistryStorage(registry ServiceRegistry, cloud cloudprovider.In } } +func makeLinkVariables(service api.Service, machine string) []api.EnvVar { + prefix := strings.ToUpper(service.ID) + var port string + if service.ContainerPort.Kind == util.IntstrString { + port = service.ContainerPort.StrVal + } else { + port = strconv.Itoa(service.ContainerPort.IntVal) + } + portPrefix := prefix + "_PORT_" + port + "_TCP" + return []api.EnvVar{ + { + Name: prefix + "_PORT", + Value: fmt.Sprintf("tcp://%s:%d", machine, service.Port), + }, + { + Name: portPrefix, + Value: fmt.Sprintf("tcp://%s:%d", machine, service.Port), + }, + { + Name: portPrefix + "_PROTO", + Value: "tcp", + }, + { + Name: portPrefix + "_PORT", + Value: strconv.Itoa(service.Port), + }, + { + Name: portPrefix + "_ADDR", + Value: machine, + }, + } +} + // GetServiceEnvironmentVariables populates a list of environment variables that are use // in the container environment to get access to services. func GetServiceEnvironmentVariables(registry ServiceRegistry, machine string) ([]api.EnvVar, error) { @@ -53,6 +87,7 @@ func GetServiceEnvironmentVariables(registry ServiceRegistry, machine string) ([ name := strings.ToUpper(service.ID) + "_SERVICE_PORT" value := strconv.Itoa(service.Port) result = append(result, api.EnvVar{Name: name, Value: value}) + result = append(result, makeLinkVariables(service, machine)...) } result = append(result, api.EnvVar{Name: "SERVICE_HOST", Value: machine}) return result, nil From 3cc5ff15cf32a3b90b4583c4428540101ba6455f Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Sat, 12 Jul 2014 00:15:30 -0700 Subject: [PATCH 40/45] Update services to use the internal IP and port, now that its available. --- pkg/api/types.go | 6 ++-- pkg/registry/endpoints.go | 33 +++++++++++++++++---- pkg/registry/endpoints_test.go | 54 ++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 8 deletions(-) diff --git a/pkg/api/types.go b/pkg/api/types.go index e6400b27733..2a5c550f937 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -17,9 +17,8 @@ limitations under the License. package api import ( - "github.com/fsouza/go-dockerclient" - "github.com/GoogleCloudPlatform/kubernetes/pkg/util" + "github.com/fsouza/go-dockerclient" ) // Common string formats @@ -270,7 +269,8 @@ type Service struct { Selector map[string]string `json:"selector,omitempty" yaml:"selector,omitempty"` CreateExternalLoadBalancer bool `json:"createExternalLoadBalancer,omitempty" yaml:"createExternalLoadBalancer,omitempty"` - // Container port to connect to, either a name or a port number + // ContainerPort is the name of the port on the container to direct traffic to. + // Optional, if unspecified use the first port on the container. ContainerPort util.IntOrString `json:"containerPort,omitempty" yaml:"containerPort,omitempty"` } diff --git a/pkg/registry/endpoints.go b/pkg/registry/endpoints.go index ab00a911c58..10a1db2be7b 100644 --- a/pkg/registry/endpoints.go +++ b/pkg/registry/endpoints.go @@ -17,11 +17,13 @@ limitations under the License. package registry import ( + "fmt" "net" "strconv" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/golang/glog" ) @@ -37,6 +39,26 @@ type EndpointController struct { podRegistry PodRegistry } +func findPort(manifest *api.ContainerManifest, portName util.IntOrString) (int, error) { + if ((portName.Kind == util.IntstrString && len(portName.StrVal) == 0) || + (portName.Kind == util.IntstrInt && portName.IntVal == 0)) && + len(manifest.Containers[0].Ports) > 0 { + return manifest.Containers[0].Ports[0].ContainerPort, nil + } + if portName.Kind == util.IntstrInt { + return portName.IntVal, nil + } + name := portName.StrVal + for _, container := range manifest.Containers { + for _, port := range container.Ports { + if port.Name == name { + return port.ContainerPort, nil + } + } + } + return -1, fmt.Errorf("no suitable port for manifest: %s", manifest.ID) +} + func (e *EndpointController) SyncServiceEndpoints() error { services, err := e.serviceRegistry.ListServices() if err != nil { @@ -52,11 +74,12 @@ func (e *EndpointController) SyncServiceEndpoints() error { } endpoints := make([]string, len(pods)) for ix, pod := range pods { - // TODO: Use port names in the service object, don't just use port #0 - endpoints[ix] = net.JoinHostPort( - pod.CurrentState.Host, - strconv.Itoa(pod.DesiredState.Manifest.Containers[0].Ports[0].HostPort), - ) + port, err := findPort(&pod.DesiredState.Manifest, service.ContainerPort) + if err != nil { + glog.Errorf("Failed to find port for service: %v, %v", service, err) + continue + } + endpoints[ix] = net.JoinHostPort(pod.CurrentState.PodIP, strconv.Itoa(port)) } err = e.serviceRegistry.UpdateEndpoints(api.Endpoints{ Name: service.ID, diff --git a/pkg/registry/endpoints_test.go b/pkg/registry/endpoints_test.go index 71250d04c69..8581bbae701 100644 --- a/pkg/registry/endpoints_test.go +++ b/pkg/registry/endpoints_test.go @@ -21,8 +21,62 @@ import ( "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) +func TestFindPort(t *testing.T) { + manifest := api.ContainerManifest{ + Containers: []api.Container{ + { + Ports: []api.Port{ + { + Name: "foo", + ContainerPort: 8080, + HostPort: 9090, + }, + { + Name: "bar", + ContainerPort: 8000, + HostPort: 9000, + }, + }, + }, + }, + } + port, err := findPort(&manifest, util.IntOrString{Kind: util.IntstrString, StrVal: "foo"}) + expectNoError(t, err) + if port != 8080 { + t.Errorf("Expected 8080, Got %d", port) + } + port, err = findPort(&manifest, util.IntOrString{Kind: util.IntstrString, StrVal: "bar"}) + expectNoError(t, err) + if port != 8000 { + t.Errorf("Expected 8000, Got %d", port) + } + port, err = findPort(&manifest, util.IntOrString{Kind: util.IntstrInt, IntVal: 8000}) + if port != 8000 { + t.Errorf("Expected 8000, Got %d", port) + } + port, err = findPort(&manifest, util.IntOrString{Kind: util.IntstrInt, IntVal: 7000}) + if port != 7000 { + t.Errorf("Expected 7000, Got %d", port) + } + port, err = findPort(&manifest, util.IntOrString{Kind: util.IntstrString, StrVal: "baz"}) + if err == nil { + t.Error("unexpected non-error") + } + port, err = findPort(&manifest, util.IntOrString{Kind: util.IntstrString, StrVal: ""}) + expectNoError(t, err) + if port != 8080 { + t.Errorf("Expected 8080, Got %d", port) + } + port, err = findPort(&manifest, util.IntOrString{}) + expectNoError(t, err) + if port != 8080 { + t.Errorf("Expected 8080, Got %d", port) + } +} + func TestSyncEndpointsEmpty(t *testing.T) { serviceRegistry := MockServiceRegistry{} podRegistry := MockPodRegistry{} From 9f99767b6c771f0c3192d64aa5f2a4520fa410c2 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Fri, 11 Jul 2014 16:45:09 -0700 Subject: [PATCH 41/45] Add a /healthz handler to the kubelet server, so that the master can validate kubelet health. --- pkg/kubelet/kubelet_server.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/kubelet/kubelet_server.go b/pkg/kubelet/kubelet_server.go index 0e6d7cc8fb5..8b62ed4b69d 100644 --- a/pkg/kubelet/kubelet_server.go +++ b/pkg/kubelet/kubelet_server.go @@ -58,6 +58,10 @@ func (s *KubeletServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } switch { + case u.Path == "/healthz": + w.WriteHeader(http.StatusOK) + w.Write([]byte("ok")) + return case u.Path == "/container" || u.Path == "/containers": defer req.Body.Close() data, err := ioutil.ReadAll(req.Body) From ae179121c689550313beb5ee12dca676d6313196 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Mon, 14 Jul 2014 17:00:23 -0700 Subject: [PATCH 42/45] Address comments. --- pkg/healthz/doc.go | 20 ++++++++++++++++++++ pkg/healthz/healthz.go | 31 +++++++++++++++++++++++++++++++ pkg/kubelet/kubelet.go | 6 ++++-- pkg/kubelet/kubelet_server.go | 11 ++++------- 4 files changed, 59 insertions(+), 9 deletions(-) create mode 100644 pkg/healthz/doc.go create mode 100644 pkg/healthz/healthz.go diff --git a/pkg/healthz/doc.go b/pkg/healthz/doc.go new file mode 100644 index 00000000000..16414f578dd --- /dev/null +++ b/pkg/healthz/doc.go @@ -0,0 +1,20 @@ +/* +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 healthz implements basic http server health checking. +// Usage: +// import _ "healthz" registers a handler on the path '/healthz', that serves 200s +package healthz diff --git a/pkg/healthz/healthz.go b/pkg/healthz/healthz.go new file mode 100644 index 00000000000..8ae6b97f73f --- /dev/null +++ b/pkg/healthz/healthz.go @@ -0,0 +1,31 @@ +/* +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 healthz + +import ( + "net/http" +) + +func init() { + http.HandleFunc("/healthz", handleHealthz) +} + +func handleHealthz(w http.ResponseWriter, r *http.Request) { + // TODO Support user supplied health functions too. + w.WriteHeader(http.StatusOK) + w.Write([]byte("ok")) +} diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index 263f6c89f82..5bf45fcd0c8 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -34,6 +34,7 @@ import ( "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + _ "github.com/GoogleCloudPlatform/kubernetes/pkg/healthz" "github.com/GoogleCloudPlatform/kubernetes/pkg/tools" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/coreos/go-etcd/etcd" @@ -145,8 +146,9 @@ func (kl *Kubelet) RunKubelet(dockerEndpoint, configPath, manifestURL, etcdServe if address != "" { glog.Infof("Starting to listen on %s:%d", address, port) handler := KubeletServer{ - Kubelet: kl, - UpdateChannel: updateChannel, + Kubelet: kl, + UpdateChannel: updateChannel, + DelegateHandler: http.DefaultServeMux, } s := &http.Server{ Addr: net.JoinHostPort(address, strconv.FormatUint(uint64(port), 10)), diff --git a/pkg/kubelet/kubelet_server.go b/pkg/kubelet/kubelet_server.go index 8b62ed4b69d..adfc13e283e 100644 --- a/pkg/kubelet/kubelet_server.go +++ b/pkg/kubelet/kubelet_server.go @@ -33,8 +33,9 @@ import ( // KubeletServer is a http.Handler which exposes kubelet functionality over HTTP. type KubeletServer struct { - Kubelet kubeletInterface - UpdateChannel chan<- manifestUpdate + Kubelet kubeletInterface + UpdateChannel chan<- manifestUpdate + DelegateHandler http.Handler } // kubeletInterface contains all the kubelet methods required by the server. @@ -58,10 +59,6 @@ func (s *KubeletServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } switch { - case u.Path == "/healthz": - w.WriteHeader(http.StatusOK) - w.Write([]byte("ok")) - return case u.Path == "/container" || u.Path == "/containers": defer req.Body.Close() data, err := ioutil.ReadAll(req.Body) @@ -109,7 +106,7 @@ func (s *KubeletServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { case strings.HasPrefix(u.Path, "/stats"): s.serveStats(w, req) default: - http.Error(w, "Not found.", http.StatusNotFound) + s.DelegateHandler.ServeHTTP(w, req) } } From f7bd5a6f0f5d6881684b408dc423eb3eb30c4e4e Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Mon, 14 Jul 2014 21:13:06 -0700 Subject: [PATCH 43/45] Add a template printer to kubecfg. --- cmd/kubecfg/kubecfg.go | 16 ++++++++++++++++ pkg/kubecfg/resource_printer.go | 17 +++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/cmd/kubecfg/kubecfg.go b/cmd/kubecfg/kubecfg.go index a26c788c555..0e1d399807c 100644 --- a/cmd/kubecfg/kubecfg.go +++ b/cmd/kubecfg/kubecfg.go @@ -24,6 +24,7 @@ import ( "os" "strconv" "strings" + "text/template" "time" kube_client "github.com/GoogleCloudPlatform/kubernetes/pkg/client" @@ -50,6 +51,7 @@ var ( verbose = flag.Bool("verbose", false, "If true, print extra information") proxy = flag.Bool("proxy", false, "If true, run a proxy to the api server") www = flag.String("www", "", "If -proxy is true, use this directory to serve static files") + templateFile = flag.String("template", "", "If present load this file as a golang template and us it for output printing") ) func usage() { @@ -188,6 +190,20 @@ func executeAPIRequest(method string, s *kube_client.Client) bool { printer = &kubecfg.IdentityPrinter{} } else if *yaml { printer = &kubecfg.YAMLPrinter{} + } else if len(*templateFile) > 0 { + data, err := ioutil.ReadFile(*templateFile) + if err != nil { + glog.Fatalf("Error reading template %s, %v\n", *templateFile, err) + return false + } + tmpl, err := template.New("output").Parse(string(data)) + if err != nil { + glog.Fatalf("Error parsing template %s, %v\n", string(data), err) + return false + } + printer = &kubecfg.TemplatePrinter{ + Template: tmpl, + } } else { printer = &kubecfg.HumanReadablePrinter{} } diff --git a/pkg/kubecfg/resource_printer.go b/pkg/kubecfg/resource_printer.go index bb6066a5a96..39864f75859 100644 --- a/pkg/kubecfg/resource_printer.go +++ b/pkg/kubecfg/resource_printer.go @@ -22,6 +22,7 @@ import ( "io" "strings" "text/tabwriter" + "text/template" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" @@ -235,3 +236,19 @@ func (h *HumanReadablePrinter) PrintObj(obj interface{}, output io.Writer) error return err } } + +type TemplatePrinter struct { + Template *template.Template +} + +func (t *TemplatePrinter) Print(data []byte, w io.Writer) error { + obj, err := api.Decode(data) + if err != nil { + return err + } + return t.PrintObj(obj, w) +} + +func (t *TemplatePrinter) PrintObj(obj interface{}, w io.Writer) error { + return t.Template.Execute(w, obj) +} From ac1be8cc97a55025ccc5658979ec45c4ba51b044 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Mon, 14 Jul 2014 21:33:02 -0700 Subject: [PATCH 44/45] Move guestbook.md to README.md for default display by github. --- README.md | 2 +- examples/guestbook/{guestbook.md => README.md} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename examples/guestbook/{guestbook.md => README.md} (100%) diff --git a/README.md b/README.md index 7ff2fc0194c..45707ba0d0f 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ If you are running both a remote kubernetes cluster and the local cluster, you c The folks at [CoreOS](http://coreos.com) have [instructions for running Kubernetes on CoreOS](http://coreos.com/blog/running-kubernetes-example-on-CoreOS-part-1/) ## Where to go next? -[Detailed example application](https://github.com/GoogleCloudPlatform/kubernetes/blob/master/examples/guestbook/guestbook.md) +[Detailed example application](https://github.com/GoogleCloudPlatform/kubernetes/blob/master/examples/guestbook/README.md) [Example of dynamic updates](https://github.com/GoogleCloudPlatform/kubernetes/blob/master/examples/update-demo/README.md) diff --git a/examples/guestbook/guestbook.md b/examples/guestbook/README.md similarity index 100% rename from examples/guestbook/guestbook.md rename to examples/guestbook/README.md From b2ef24fb48725b7a17fbb0d3c279811cde9d50e9 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Mon, 14 Jul 2014 21:29:31 -0700 Subject: [PATCH 45/45] Address comments. --- cmd/kubecfg/kubecfg.go | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/cmd/kubecfg/kubecfg.go b/cmd/kubecfg/kubecfg.go index 0e1d399807c..39392922e65 100644 --- a/cmd/kubecfg/kubecfg.go +++ b/cmd/kubecfg/kubecfg.go @@ -51,7 +51,8 @@ var ( verbose = flag.Bool("verbose", false, "If true, print extra information") proxy = flag.Bool("proxy", false, "If true, run a proxy to the api server") www = flag.String("www", "", "If -proxy is true, use this directory to serve static files") - templateFile = flag.String("template", "", "If present load this file as a golang template and us it for output printing") + templateFile = flag.String("template_file", "", "If present load this file as a golang template and us it for output printing") + templateStr = flag.String("template", "", "If present parse this string as a golang template and us it for output printing") ) func usage() { @@ -186,15 +187,22 @@ func executeAPIRequest(method string, s *kube_client.Client) bool { } var printer kubecfg.ResourcePrinter - if *json { + switch { + case *json: printer = &kubecfg.IdentityPrinter{} - } else if *yaml { + case *yaml: printer = &kubecfg.YAMLPrinter{} - } else if len(*templateFile) > 0 { - data, err := ioutil.ReadFile(*templateFile) - if err != nil { - glog.Fatalf("Error reading template %s, %v\n", *templateFile, err) - return false + case len(*templateFile) > 0 || len(*templateStr) > 0: + var data []byte + if len(*templateFile) > 0 { + var err error + data, err = ioutil.ReadFile(*templateFile) + if err != nil { + glog.Fatalf("Error reading template %s, %v\n", *templateFile, err) + return false + } + } else { + data = []byte(*templateStr) } tmpl, err := template.New("output").Parse(string(data)) if err != nil { @@ -204,7 +212,7 @@ func executeAPIRequest(method string, s *kube_client.Client) bool { printer = &kubecfg.TemplatePrinter{ Template: tmpl, } - } else { + default: printer = &kubecfg.HumanReadablePrinter{} }