Merge branch 'master' into fix/golint

Conflicts:
	pkg/master/master.go
	pkg/master/pod_cache.go
	pkg/proxy/config/file.go
	pkg/proxy/proxier.go
	pkg/proxy/roundrobbin.go
	pkg/scheduler/randomfit.go
	pkg/scheduler/randomfit_test.go
This commit is contained in:
Yuki Yugui Sonoda 2014-07-15 20:18:21 +09:00
commit 41febcee5e
63 changed files with 1885 additions and 292 deletions

View File

@ -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:

View File

@ -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)

View File

@ -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

View File

@ -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)})

View File

@ -24,6 +24,7 @@ import (
"os"
"strconv"
"strings"
"text/template"
"time"
kube_client "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
@ -50,6 +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_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() {
@ -184,11 +187,32 @@ 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 {
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 {
glog.Fatalf("Error parsing template %s, %v\n", string(data), err)
return false
}
printer = &kubecfg.TemplatePrinter{
Template: tmpl,
}
default:
printer = &kubecfg.HumanReadablePrinter{}
}

View File

@ -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

View File

@ -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,14 @@ if [ "${TRAVIS}" != "true" ]; then
fi
fi
pushd $(dirname "${BASH_SOURCE}")/.. >/dev/null
KUBE_REPO_ROOT="${PWD}"
if [[ "$OSTYPE" == *darwin* ]]; then
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})"
fi
KUBE_TARGET="${KUBE_REPO_ROOT}/output/go"
popd >/dev/null
mkdir -p "${KUBE_TARGET}"

28
hack/install-std-race.sh Executable file
View File

@ -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

View File

@ -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

View File

@ -17,6 +17,7 @@ limitations under the License.
package api
import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/fsouza/go-dockerclient"
)
@ -50,6 +51,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"`
@ -116,11 +118,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 +142,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
@ -267,6 +268,10 @@ 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"`
// 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"`
}
// Endpoints is a collection of endpoints that implement the actual service, for example:

View File

@ -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(makeInvalidError("Service.ID", service.ID))
}
if len(service.Selector) == 0 {
allErrs.Append(makeInvalidError("Service.Selector", service.Selector))
}
return []error(allErrs)
}

View File

@ -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)
}
}

View File

@ -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,
}
@ -104,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)
@ -141,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
}
@ -194,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)
}

View File

@ -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) {

20
pkg/healthz/doc.go Normal file
View File

@ -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

31
pkg/healthz/healthz.go Normal file
View File

@ -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"))
}

View File

@ -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)
}

View File

@ -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,15 +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 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
@ -100,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
}

View File

@ -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",
},
@ -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 {

View File

@ -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"
@ -44,6 +45,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 {
@ -143,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)),
@ -192,7 +196,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 +206,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.
@ -732,7 +736,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)
@ -758,7 +762,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
@ -989,16 +993,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.Enabled {
return true, nil
if container.LivenessProbe == 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)
}

View File

@ -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.
@ -46,8 +47,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 +87,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 +106,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.")
s.DelegateHandler.ServeHTTP(w, req)
}
}
@ -130,13 +126,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 +140,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)

View File

@ -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) {
@ -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",
},

View File

@ -89,7 +89,7 @@ func try(selectorPiece, op string) (lhs, rhs string, ok bool) {
// SelectorFromSet returns a Selector which will match exactly the given 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})
}

View File

@ -84,10 +84,10 @@ 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.NewFirstFitScheduler(m.podRegistry, m.random)
s := scheduler.NewRandomFitScheduler(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),
}

View File

@ -57,7 +57,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")
}
return value, nil
}

View File

@ -81,35 +81,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 serviceConfig
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 := &serviceConfig{}
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)
}
}

View File

@ -43,7 +43,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)
}
@ -88,7 +88,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))
}
}

View File

@ -23,7 +23,6 @@ import (
"net"
"reflect"
"strconv"
"strings"
"sync"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
@ -59,13 +58,12 @@ func (impl LoadBalancerRR) LoadBalance(service string, srcAddr net.Addr) (string
return endpoint, nil
}
// isValid returns true if spec is valid.
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
}
@ -93,9 +91,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
}

View File

@ -20,12 +20,13 @@ 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"
)
// 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 +34,7 @@ type ControllerRegistryStorage struct {
pollPeriod time.Duration
}
func MakeControllerRegistryStorage(registry ControllerRegistry, podRegistry PodRegistry) apiserver.RESTStorage {
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,26 +65,32 @@ 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 {
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 {
@ -91,12 +100,13 @@ func (storage *ControllerRegistryStorage) Create(obj interface{}) (<-chan interf
}), nil
}
// 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 {
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) {

View File

@ -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,

View File

@ -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{}

View File

@ -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)
}

View File

@ -35,13 +35,13 @@ 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)
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.

View File

@ -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])
}
}
}

View File

@ -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
}

View File

@ -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"
@ -85,8 +86,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
netContainerInfo, ok := info["net"]
@ -185,8 +192,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 +213,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) {

View File

@ -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"),

View File

@ -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
@ -112,8 +147,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

View File

@ -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)

View File

@ -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)) {

View File

@ -18,14 +18,15 @@ package scheduler
import (
"math/rand"
"sync"
"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
random *rand.Rand
randomLock sync.Mutex
}
func MakeRandomScheduler(random *rand.Rand) Scheduler {
@ -34,10 +35,14 @@ func MakeRandomScheduler(random *rand.Rand) Scheduler {
}
}
// 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 {
return "", err
}
s.randomLock.Lock()
defer s.randomLock.Unlock()
return machines[s.random.Int()%len(machines)], nil
}

View File

@ -19,25 +19,27 @@ package scheduler
import (
"fmt"
"math/rand"
"sync"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
)
type FirstFitScheduler struct {
podLister PodLister
// TODO: *rand.Rand is *not* threadsafe
random *rand.Rand
// 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 NewFirstFitScheduler(podLister PodLister, random *rand.Rand) Scheduler {
return &FirstFitScheduler{
func NewRandomFitScheduler(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 {
@ -48,7 +50,8 @@ func (s *FirstFitScheduler) containsPort(pod api.Pod, port api.Port) bool {
return false
}
func (s *FirstFitScheduler) Schedule(pod api.Pod, minionLister MinionLister) (string, error) {
// Schedule schedules a pod on a random machine which matches its requirement.
func (s *RandomFitScheduler) Schedule(pod api.Pod, minionLister MinionLister) (string, error) {
machines, err := minionLister.List()
if err != nil {
return "", err
@ -80,7 +83,8 @@ 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
}
s.randomLock.Lock()
defer s.randomLock.Unlock()
return machineOptions[s.random.Int()%len(machineOptions)], nil
}

View File

@ -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: NewFirstFitScheduler(&fakeRegistry, r),
scheduler: NewRandomFitScheduler(&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: NewFirstFitScheduler(fakeRegistry, r),
scheduler: NewRandomFitScheduler(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: NewFirstFitScheduler(fakeRegistry, r),
scheduler: NewRandomFitScheduler(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: NewFirstFitScheduler(fakeRegistry, r),
scheduler: NewRandomFitScheduler(fakeRegistry, r),
minionLister: FakeMinionLister{"m1", "m2", "m3"},
}
st.expectFailure(makePod("", 8080, 8081))

View File

@ -31,6 +31,7 @@ func MakeRoundRobinScheduler() Scheduler {
}
}
// 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 {

View File

@ -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)

View File

@ -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 {

View File

@ -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")
}
}

View File

@ -73,121 +73,83 @@ type IntOrStringHolder struct {
}
func TestIntOrStringUnmarshalYAML(t *testing.T) {
{
yaml_code_int := "val: 123\n"
var result IntOrStringHolder
if err := yaml.Unmarshal([]byte(yaml_code_int), &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"}},
}
{
yaml_code_str := "val: \"123\"\n"
for _, c := range cases {
var result IntOrStringHolder
if err := yaml.Unmarshal([]byte(yaml_code_str), &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) {
{
json_code_int := "{\"val\": 123}"
var result IntOrStringHolder
if err := json.Unmarshal([]byte(json_code_int), &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"}},
}
{
json_code_str := "{\"val\": \"123\"}"
for _, c := range cases {
var result IntOrStringHolder
if err := json.Unmarshal([]byte(json_code_str), &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))
}
}
}

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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))
}

View File

@ -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

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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 × 1011),
// 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
}