Browse code

bump(github.com/GoogleCloudPlatform/kubernetes): f9e5477e2b75132df507743748af695ef8ca0837

Clayton Coleman authored on 2014/10/03 11:23:59
Showing 160 changed files
... ...
@@ -15,6 +15,11 @@
15 15
 			"Rev": "7dda39b2e7d5e265014674c5af696ba4186679e9"
16 16
 		},
17 17
 		{
18
+			"ImportPath": "code.google.com/p/go.net/context",
19
+			"Comment": "null-144",
20
+			"Rev": "ad01a6fcc8a19d3a4478c836895ffe883bd2ceab"
21
+		},
22
+		{
18 23
 			"ImportPath": "code.google.com/p/go.net/html",
19 24
 			"Comment": "null-144",
20 25
 			"Rev": "ad01a6fcc8a19d3a4478c836895ffe883bd2ceab"
... ...
@@ -55,163 +60,168 @@
55 55
 		},
56 56
 		{
57 57
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/api",
58
-			"Comment": "v0.3-51-gd65f8e3",
59
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
58
+			"Comment": "v0.3-285-gf9e5477",
59
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
60 60
 		},
61 61
 		{
62 62
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver",
63
-			"Comment": "v0.3-51-gd65f8e3",
64
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
63
+			"Comment": "v0.3-285-gf9e5477",
64
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
65 65
 		},
66 66
 		{
67 67
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/capabilities",
68
-			"Comment": "v0.3-51-gd65f8e3",
69
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
68
+			"Comment": "v0.3-285-gf9e5477",
69
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
70 70
 		},
71 71
 		{
72 72
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/client",
73
-			"Comment": "v0.3-51-gd65f8e3",
74
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
73
+			"Comment": "v0.3-285-gf9e5477",
74
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
75 75
 		},
76 76
 		{
77 77
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider",
78
-			"Comment": "v0.3-51-gd65f8e3",
79
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
78
+			"Comment": "v0.3-285-gf9e5477",
79
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
80 80
 		},
81 81
 		{
82 82
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/constraint",
83
-			"Comment": "v0.3-51-gd65f8e3",
84
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
83
+			"Comment": "v0.3-285-gf9e5477",
84
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
85 85
 		},
86 86
 		{
87 87
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/controller",
88
-			"Comment": "v0.3-51-gd65f8e3",
89
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
88
+			"Comment": "v0.3-285-gf9e5477",
89
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
90 90
 		},
91 91
 		{
92 92
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/conversion",
93
-			"Comment": "v0.3-51-gd65f8e3",
94
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
93
+			"Comment": "v0.3-285-gf9e5477",
94
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
95 95
 		},
96 96
 		{
97 97
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/health",
98
-			"Comment": "v0.3-51-gd65f8e3",
99
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
98
+			"Comment": "v0.3-285-gf9e5477",
99
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
100 100
 		},
101 101
 		{
102 102
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/healthz",
103
-			"Comment": "v0.3-51-gd65f8e3",
104
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
103
+			"Comment": "v0.3-285-gf9e5477",
104
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
105 105
 		},
106 106
 		{
107 107
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/httplog",
108
-			"Comment": "v0.3-51-gd65f8e3",
109
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
108
+			"Comment": "v0.3-285-gf9e5477",
109
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
110 110
 		},
111 111
 		{
112 112
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/kubecfg",
113
-			"Comment": "v0.3-51-gd65f8e3",
114
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
113
+			"Comment": "v0.3-285-gf9e5477",
114
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
115 115
 		},
116 116
 		{
117 117
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet",
118
-			"Comment": "v0.3-51-gd65f8e3",
119
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
118
+			"Comment": "v0.3-285-gf9e5477",
119
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
120 120
 		},
121 121
 		{
122 122
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/labels",
123
-			"Comment": "v0.3-51-gd65f8e3",
124
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
123
+			"Comment": "v0.3-285-gf9e5477",
124
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
125 125
 		},
126 126
 		{
127 127
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/master",
128
-			"Comment": "v0.3-51-gd65f8e3",
129
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
128
+			"Comment": "v0.3-285-gf9e5477",
129
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
130 130
 		},
131 131
 		{
132 132
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/proxy",
133
-			"Comment": "v0.3-51-gd65f8e3",
134
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
133
+			"Comment": "v0.3-285-gf9e5477",
134
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
135 135
 		},
136 136
 		{
137 137
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/binding",
138
-			"Comment": "v0.3-51-gd65f8e3",
139
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
138
+			"Comment": "v0.3-285-gf9e5477",
139
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
140 140
 		},
141 141
 		{
142 142
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/controller",
143
-			"Comment": "v0.3-51-gd65f8e3",
144
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
143
+			"Comment": "v0.3-285-gf9e5477",
144
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
145 145
 		},
146 146
 		{
147 147
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/endpoint",
148
-			"Comment": "v0.3-51-gd65f8e3",
149
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
148
+			"Comment": "v0.3-285-gf9e5477",
149
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
150 150
 		},
151 151
 		{
152 152
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/etcd",
153
-			"Comment": "v0.3-51-gd65f8e3",
154
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
153
+			"Comment": "v0.3-285-gf9e5477",
154
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
155 155
 		},
156 156
 		{
157 157
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/minion",
158
-			"Comment": "v0.3-51-gd65f8e3",
159
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
158
+			"Comment": "v0.3-285-gf9e5477",
159
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
160 160
 		},
161 161
 		{
162 162
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod",
163
-			"Comment": "v0.3-51-gd65f8e3",
164
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
163
+			"Comment": "v0.3-285-gf9e5477",
164
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
165 165
 		},
166 166
 		{
167 167
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/service",
168
-			"Comment": "v0.3-51-gd65f8e3",
169
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
168
+			"Comment": "v0.3-285-gf9e5477",
169
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
170
+		},
171
+		{
172
+			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/resources",
173
+			"Comment": "v0.3-285-gf9e5477",
174
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
170 175
 		},
171 176
 		{
172 177
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime",
173
-			"Comment": "v0.3-51-gd65f8e3",
174
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
178
+			"Comment": "v0.3-285-gf9e5477",
179
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
175 180
 		},
176 181
 		{
177 182
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/scheduler",
178
-			"Comment": "v0.3-51-gd65f8e3",
179
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
183
+			"Comment": "v0.3-285-gf9e5477",
184
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
180 185
 		},
181 186
 		{
182 187
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/service",
183
-			"Comment": "v0.3-51-gd65f8e3",
184
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
188
+			"Comment": "v0.3-285-gf9e5477",
189
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
185 190
 		},
186 191
 		{
187 192
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/tools",
188
-			"Comment": "v0.3-51-gd65f8e3",
189
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
193
+			"Comment": "v0.3-285-gf9e5477",
194
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
190 195
 		},
191 196
 		{
192 197
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/util",
193
-			"Comment": "v0.3-51-gd65f8e3",
194
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
198
+			"Comment": "v0.3-285-gf9e5477",
199
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
195 200
 		},
196 201
 		{
197 202
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/version",
198
-			"Comment": "v0.3-51-gd65f8e3",
199
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
203
+			"Comment": "v0.3-285-gf9e5477",
204
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
200 205
 		},
201 206
 		{
202 207
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/volume",
203
-			"Comment": "v0.3-51-gd65f8e3",
204
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
208
+			"Comment": "v0.3-285-gf9e5477",
209
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
205 210
 		},
206 211
 		{
207 212
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/pkg/watch",
208
-			"Comment": "v0.3-51-gd65f8e3",
209
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
213
+			"Comment": "v0.3-285-gf9e5477",
214
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
210 215
 		},
211 216
 		{
212 217
 			"ImportPath": "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/scheduler",
213
-			"Comment": "v0.3-51-gd65f8e3",
214
-			"Rev": "d65f8e3b90b8fde6a86d17a8869a8777471de86d"
218
+			"Comment": "v0.3-285-gf9e5477",
219
+			"Rev": "f9e5477e2b75132df507743748af695ef8ca0837"
215 220
 		},
216 221
 		{
217 222
 			"ImportPath": "github.com/SeanDolphin/bqschema",
... ...
@@ -391,11 +401,6 @@
391 391
 			"Rev": "5a6d06c02600b1e57e55a9d9f71dbac1bfc9fe6c"
392 392
 		},
393 393
 		{
394
-			"ImportPath": "github.com/jteeuwen/go-bindata",
395
-			"Comment": "v3.0.6",
396
-			"Rev": "1743f47c0112eff669b0199889670d61384b4730"
397
-		},
398
-		{
399 394
 			"ImportPath": "github.com/google/gofuzz",
400 395
 			"Rev": "aef70dacbc78771e35beb261bb3a72986adf7906"
401 396
 		},
... ...
@@ -405,6 +410,11 @@
405 405
 			"Rev": "262d116c059935fdcdc2350bd56d70a09ad71640"
406 406
 		},
407 407
 		{
408
+			"ImportPath": "github.com/jteeuwen/go-bindata",
409
+			"Comment": "v3.0.6",
410
+			"Rev": "1743f47c0112eff669b0199889670d61384b4730"
411
+		},
412
+		{
408 413
 			"ImportPath": "github.com/kr/pretty",
409 414
 			"Comment": "go.weekly.2011-12-22-24-gf31442d",
410 415
 			"Rev": "f31442d60e51465c69811e2107ae978868dbea5c"
411 416
new file mode 100644
... ...
@@ -0,0 +1,431 @@
0
+// Copyright 2014 The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+// Package context defines the Context type, which carries deadlines,
5
+// cancelation signals, and other request-scoped values across API boundaries
6
+// and between processes.
7
+//
8
+// Incoming requests to a server should create a Context, and outgoing calls to
9
+// servers should accept a Context.  The chain of function calls between must
10
+// propagate the Context, optionally replacing it with a modified copy created
11
+// using WithDeadline, WithTimeout, WithCancel, or WithValue.
12
+//
13
+// Programs that use Contexts should follow these rules to keep interfaces
14
+// consistent across packages and enable static analysis tools to check context
15
+// propagation:
16
+//
17
+// Do not store Contexts inside a struct type; instead, pass a Context
18
+// explicitly to each function that needs it.  The Context should be the first
19
+// parameter, typically named ctx:
20
+//
21
+// 	func DoSomething(ctx context.Context, arg Arg) error {
22
+// 		// ... use ctx ...
23
+// 	}
24
+//
25
+// Do not pass a nil Context, even if a function permits it.  Pass context.TODO
26
+// if you are unsure about which Context to use.
27
+//
28
+// Use context Values only for request-scoped data that transits processes and
29
+// APIs, not for passing optional parameters to functions.
30
+//
31
+// The same Context may be passed to functions running in different goroutines;
32
+// Contexts are safe for simultaneous use by multiple goroutines.
33
+//
34
+// See http://blog.golang.org/context for example code for a server that uses
35
+// Contexts.
36
+package context
37
+
38
+import (
39
+	"errors"
40
+	"fmt"
41
+	"sync"
42
+	"time"
43
+)
44
+
45
+// A Context carries a deadline, a cancelation signal, and other values across
46
+// API boundaries.
47
+//
48
+// Context's methods may be called by multiple goroutines simultaneously.
49
+type Context interface {
50
+	// Deadline returns the time when work done on behalf of this context
51
+	// should be canceled.  Deadline returns ok==false when no deadline is
52
+	// set.  Successive calls to Deadline return the same results.
53
+	Deadline() (deadline time.Time, ok bool)
54
+
55
+	// Done returns a channel that's closed when work done on behalf of this
56
+	// context should be canceled.  Done may return nil if this context can
57
+	// never be canceled.  Successive calls to Done return the same value.
58
+	//
59
+	// WithCancel arranges for Done to be closed when cancel is called;
60
+	// WithDeadline arranges for Done to be closed when the deadline
61
+	// expires; WithTimeout arranges for Done to be closed when the timeout
62
+	// elapses.
63
+	//
64
+	// Done is provided for use in select statements:
65
+	//
66
+	// 	// DoSomething calls DoSomethingSlow and returns as soon as
67
+	// 	// it returns or ctx.Done is closed.
68
+	// 	func DoSomething(ctx context.Context) (Result, error) {
69
+	// 		c := make(chan Result, 1)
70
+	// 		go func() { c <- DoSomethingSlow(ctx) }()
71
+	// 		select {
72
+	// 		case res := <-c:
73
+	// 			return res, nil
74
+	// 		case <-ctx.Done():
75
+	// 			return nil, ctx.Err()
76
+	// 		}
77
+	// 	}
78
+	//
79
+	// See http://blog.golang.org/pipelines for more examples of how to use
80
+	// a Done channel for cancelation.
81
+	Done() <-chan struct{}
82
+
83
+	// Err returns a non-nil error value after Done is closed.  Err returns
84
+	// Canceled if the context was canceled or DeadlineExceeded if the
85
+	// context's deadline passed.  No other values for Err are defined.
86
+	// After Done is closed, successive calls to Err return the same value.
87
+	Err() error
88
+
89
+	// Value returns the value associated with this context for key, or nil
90
+	// if no value is associated with key.  Successive calls to Value with
91
+	// the same key returns the same result.
92
+	//
93
+	// Use context values only for request-scoped data that transits
94
+	// processes and API boundaries, not for passing optional parameters to
95
+	// functions.
96
+	//
97
+	// A key identifies a specific value in a Context.  Functions that wish
98
+	// to store values in Context typically allocate a key in a global
99
+	// variable then use that key as the argument to context.WithValue and
100
+	// Context.Value.  A key can be any type that supports equality;
101
+	// packages should define keys as an unexported type to avoid
102
+	// collisions.
103
+	//
104
+	// Packages that define a Context key should provide type-safe accessors
105
+	// for the values stores using that key:
106
+	//
107
+	// 	// Package user defines a User type that's stored in Contexts.
108
+	// 	package user
109
+	//
110
+	// 	import "code.google.com/p/go.net/context"
111
+	//
112
+	// 	// User is the type of value stored in the Contexts.
113
+	// 	type User struct {...}
114
+	//
115
+	// 	// key is an unexported type for keys defined in this package.
116
+	// 	// This prevents collisions with keys defined in other packages.
117
+	// 	type key int
118
+	//
119
+	// 	// userKey is the key for user.User values in Contexts.  It is
120
+	// 	// unexported; clients use user.NewContext and user.FromContext
121
+	// 	// instead of using this key directly.
122
+	// 	var userKey key = 0
123
+	//
124
+	// 	// NewContext returns a new Context that carries value u.
125
+	// 	func NewContext(ctx context.Context, u *User) context.Context {
126
+	// 		return context.WithValue(userKey, u)
127
+	// 	}
128
+	//
129
+	// 	// FromContext returns the User value stored in ctx, if any.
130
+	// 	func FromContext(ctx context.Context) (*User, bool) {
131
+	// 		u, ok := ctx.Value(userKey).(*User)
132
+	// 		return u, ok
133
+	// 	}
134
+	Value(key interface{}) interface{}
135
+}
136
+
137
+// Canceled is the error returned by Context.Err when the context is canceled.
138
+var Canceled = errors.New("context canceled")
139
+
140
+// DeadlineExceeded is the error returned by Context.Err when the context's
141
+// deadline passes.
142
+var DeadlineExceeded = errors.New("context deadline exceeded")
143
+
144
+// An emptyCtx is never canceled, has no values, and has no deadline.
145
+type emptyCtx int
146
+
147
+func (emptyCtx) Deadline() (deadline time.Time, ok bool) {
148
+	return
149
+}
150
+
151
+func (emptyCtx) Done() <-chan struct{} {
152
+	return nil
153
+}
154
+
155
+func (emptyCtx) Err() error {
156
+	return nil
157
+}
158
+
159
+func (emptyCtx) Value(key interface{}) interface{} {
160
+	return nil
161
+}
162
+
163
+func (n emptyCtx) String() string {
164
+	switch n {
165
+	case background:
166
+		return "context.Background"
167
+	case todo:
168
+		return "context.TODO"
169
+	}
170
+	return "unknown empty Context"
171
+}
172
+
173
+const (
174
+	background emptyCtx = 1
175
+	todo       emptyCtx = 2
176
+)
177
+
178
+// Background returns a non-nil, empty Context. It is never canceled, has no
179
+// values, and has no deadline.  It is typically used by the main function,
180
+// initialization, and tests, and as the top-level Context for incoming
181
+// requests.
182
+func Background() Context {
183
+	return background
184
+}
185
+
186
+// TODO returns a non-nil, empty Context.  Code should use context.TODO when
187
+// it's unclear which Context to use or it's is not yet available (because the
188
+// surrounding function has not yet been extended to accept a Context
189
+// parameter).  TODO is recognized by static analysis tools that determine
190
+// whether Contexts are propagated correctly in a program.
191
+func TODO() Context {
192
+	return todo
193
+}
194
+
195
+// A CancelFunc tells an operation to abandon its work.
196
+// A CancelFunc does not wait for the work to stop.
197
+// After the first call, subsequent calls to a CancelFunc do nothing.
198
+type CancelFunc func()
199
+
200
+// WithCancel returns a copy of parent with a new Done channel. The returned
201
+// context's Done channel is closed when the returned cancel function is called
202
+// or when the parent context's Done channel is closed, whichever happens first.
203
+func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
204
+	c := newCancelCtx(parent)
205
+	propagateCancel(parent, &c)
206
+	return &c, func() { c.cancel(true, Canceled) }
207
+}
208
+
209
+// newCancelCtx returns an initialized cancelCtx.
210
+func newCancelCtx(parent Context) cancelCtx {
211
+	return cancelCtx{
212
+		Context: parent,
213
+		done:    make(chan struct{}),
214
+	}
215
+}
216
+
217
+// propagateCancel arranges for child to be canceled when parent is.
218
+func propagateCancel(parent Context, child canceler) {
219
+	if parent.Done() == nil {
220
+		return // parent is never canceled
221
+	}
222
+	if p, ok := parentCancelCtx(parent); ok {
223
+		p.mu.Lock()
224
+		if p.err != nil {
225
+			// parent has already been canceled
226
+			child.cancel(false, p.err)
227
+		} else {
228
+			if p.children == nil {
229
+				p.children = make(map[canceler]bool)
230
+			}
231
+			p.children[child] = true
232
+		}
233
+		p.mu.Unlock()
234
+	} else {
235
+		go func() {
236
+			select {
237
+			case <-parent.Done():
238
+				child.cancel(false, parent.Err())
239
+			case <-child.Done():
240
+			}
241
+		}()
242
+	}
243
+}
244
+
245
+// parentCancelCtx follows a chain of parent references until it finds a
246
+// *cancelCtx.  This function understands how each of the concrete types in this
247
+// package represents its parent.
248
+func parentCancelCtx(parent Context) (*cancelCtx, bool) {
249
+	for {
250
+		switch c := parent.(type) {
251
+		case *cancelCtx:
252
+			return c, true
253
+		case *timerCtx:
254
+			return &c.cancelCtx, true
255
+		case *valueCtx:
256
+			parent = c.Context
257
+		default:
258
+			return nil, false
259
+		}
260
+	}
261
+}
262
+
263
+// A canceler is a context type that can be canceled directly.  The
264
+// implementations are *cancelCtx and *timerCtx.
265
+type canceler interface {
266
+	cancel(removeFromParent bool, err error)
267
+	Done() <-chan struct{}
268
+}
269
+
270
+// A cancelCtx can be canceled.  When canceled, it also cancels any children
271
+// that implement canceler.
272
+type cancelCtx struct {
273
+	Context
274
+
275
+	done chan struct{} // closed by the first cancel call.
276
+
277
+	mu       sync.Mutex
278
+	children map[canceler]bool // set to nil by the first cancel call
279
+	err      error             // set to non-nil by the first cancel call
280
+}
281
+
282
+func (c *cancelCtx) Done() <-chan struct{} {
283
+	return c.done
284
+}
285
+
286
+func (c *cancelCtx) Err() error {
287
+	c.mu.Lock()
288
+	defer c.mu.Unlock()
289
+	return c.err
290
+}
291
+
292
+func (c *cancelCtx) String() string {
293
+	return fmt.Sprintf("%v.WithCancel", c.Context)
294
+}
295
+
296
+// cancel closes c.done, cancels each of c's children, and, if
297
+// removeFromParent is true, removes c from its parent's children.
298
+func (c *cancelCtx) cancel(removeFromParent bool, err error) {
299
+	if err == nil {
300
+		panic("context: internal error: missing cancel error")
301
+	}
302
+	c.mu.Lock()
303
+	if c.err != nil {
304
+		c.mu.Unlock()
305
+		return // already canceled
306
+	}
307
+	c.err = err
308
+	close(c.done)
309
+	for child := range c.children {
310
+		// NOTE: acquiring the child's lock while holding parent's lock.
311
+		child.cancel(false, err)
312
+	}
313
+	c.children = nil
314
+	c.mu.Unlock()
315
+
316
+	if removeFromParent {
317
+		if p, ok := parentCancelCtx(c.Context); ok {
318
+			p.mu.Lock()
319
+			if p.children != nil {
320
+				delete(p.children, c)
321
+			}
322
+			p.mu.Unlock()
323
+		}
324
+	}
325
+}
326
+
327
+// WithDeadline returns a copy of the parent context with the deadline adjusted
328
+// to be no later than d.  If the parent's deadline is already earlier than d,
329
+// WithDeadline(parent, d) is semantically equivalent to parent.  The returned
330
+// context's Done channel is closed when the deadline expires, when the returned
331
+// cancel function is called, or when the parent context's Done channel is
332
+// closed, whichever happens first.
333
+//
334
+// Canceling this context releases resources associated with the deadline
335
+// timer, so code should call cancel as soon as the operations running in this
336
+// Context complete.
337
+func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
338
+	if cur, ok := parent.Deadline(); ok && cur.Before(deadline) {
339
+		// The current deadline is already sooner than the new one.
340
+		return WithCancel(parent)
341
+	}
342
+	c := &timerCtx{
343
+		cancelCtx: newCancelCtx(parent),
344
+		deadline:  deadline,
345
+	}
346
+	propagateCancel(parent, c)
347
+	d := deadline.Sub(time.Now())
348
+	if d <= 0 {
349
+		c.cancel(true, DeadlineExceeded) // deadline has already passed
350
+		return c, func() { c.cancel(true, Canceled) }
351
+	}
352
+	c.mu.Lock()
353
+	defer c.mu.Unlock()
354
+	if c.err == nil {
355
+		c.timer = time.AfterFunc(d, func() {
356
+			c.cancel(true, DeadlineExceeded)
357
+		})
358
+	}
359
+	return c, func() { c.cancel(true, Canceled) }
360
+}
361
+
362
+// A timerCtx carries a timer and a deadline.  It embeds a cancelCtx to
363
+// implement Done and Err.  It implements cancel by stopping its timer then
364
+// delegating to cancelCtx.cancel.
365
+type timerCtx struct {
366
+	cancelCtx
367
+	timer *time.Timer // Under cancelCtx.mu.
368
+
369
+	deadline time.Time
370
+}
371
+
372
+func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
373
+	return c.deadline, true
374
+}
375
+
376
+func (c *timerCtx) String() string {
377
+	return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now()))
378
+}
379
+
380
+func (c *timerCtx) cancel(removeFromParent bool, err error) {
381
+	c.cancelCtx.cancel(removeFromParent, err)
382
+	c.mu.Lock()
383
+	if c.timer != nil {
384
+		c.timer.Stop()
385
+		c.timer = nil
386
+	}
387
+	c.mu.Unlock()
388
+}
389
+
390
+// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
391
+//
392
+// Canceling this context releases resources associated with the deadline
393
+// timer, so code should call cancel as soon as the operations running in this
394
+// Context complete:
395
+//
396
+// 	func slowOperationWithTimeout(ctx context.Context) (Result, error) {
397
+// 		ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
398
+// 		defer cancel()  // releases resources if slowOperation completes before timeout elapses
399
+// 		return slowOperation(ctx)
400
+// 	}
401
+func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
402
+	return WithDeadline(parent, time.Now().Add(timeout))
403
+}
404
+
405
+// WithValue returns a copy of parent in which the value associated with key is
406
+// val.
407
+//
408
+// Use context Values only for request-scoped data that transits processes and
409
+// APIs, not for passing optional parameters to functions.
410
+func WithValue(parent Context, key interface{}, val interface{}) Context {
411
+	return &valueCtx{parent, key, val}
412
+}
413
+
414
+// A valueCtx carries a key-value pair.  It implements Value for that key and
415
+// delegates all other calls to the embedded Context.
416
+type valueCtx struct {
417
+	Context
418
+	key, val interface{}
419
+}
420
+
421
+func (c *valueCtx) String() string {
422
+	return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val)
423
+}
424
+
425
+func (c *valueCtx) Value(key interface{}) interface{} {
426
+	if c.key == key {
427
+		return c.val
428
+	}
429
+	return c.Context.Value(key)
430
+}
0 431
new file mode 100644
... ...
@@ -0,0 +1,553 @@
0
+// Copyright 2014 The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+package context
5
+
6
+import (
7
+	"fmt"
8
+	"math/rand"
9
+	"runtime"
10
+	"strings"
11
+	"sync"
12
+	"testing"
13
+	"time"
14
+)
15
+
16
+// otherContext is a Context that's not one of the types defined in context.go.
17
+// This lets us test code paths that differ based on the underlying type of the
18
+// Context.
19
+type otherContext struct {
20
+	Context
21
+}
22
+
23
+func TestBackground(t *testing.T) {
24
+	c := Background()
25
+	if c == nil {
26
+		t.Fatalf("Background returned nil")
27
+	}
28
+	select {
29
+	case x := <-c.Done():
30
+		t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
31
+	default:
32
+	}
33
+	if got, want := fmt.Sprint(c), "context.Background"; got != want {
34
+		t.Errorf("Background().String() = %q want %q", got, want)
35
+	}
36
+}
37
+
38
+func TestTODO(t *testing.T) {
39
+	c := TODO()
40
+	if c == nil {
41
+		t.Fatalf("TODO returned nil")
42
+	}
43
+	select {
44
+	case x := <-c.Done():
45
+		t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
46
+	default:
47
+	}
48
+	if got, want := fmt.Sprint(c), "context.TODO"; got != want {
49
+		t.Errorf("TODO().String() = %q want %q", got, want)
50
+	}
51
+}
52
+
53
+func TestWithCancel(t *testing.T) {
54
+	c1, cancel := WithCancel(Background())
55
+
56
+	if got, want := fmt.Sprint(c1), "context.Background.WithCancel"; got != want {
57
+		t.Errorf("c1.String() = %q want %q", got, want)
58
+	}
59
+
60
+	o := otherContext{c1}
61
+	c2, _ := WithCancel(o)
62
+	contexts := []Context{c1, o, c2}
63
+
64
+	for i, c := range contexts {
65
+		if d := c.Done(); d == nil {
66
+			t.Errorf("c[%d].Done() == %v want non-nil", i, d)
67
+		}
68
+		if e := c.Err(); e != nil {
69
+			t.Errorf("c[%d].Err() == %v want nil", i, e)
70
+		}
71
+
72
+		select {
73
+		case x := <-c.Done():
74
+			t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
75
+		default:
76
+		}
77
+	}
78
+
79
+	cancel()
80
+	time.Sleep(100 * time.Millisecond) // let cancelation propagate
81
+
82
+	for i, c := range contexts {
83
+		select {
84
+		case <-c.Done():
85
+		default:
86
+			t.Errorf("<-c[%d].Done() blocked, but shouldn't have", i)
87
+		}
88
+		if e := c.Err(); e != Canceled {
89
+			t.Errorf("c[%d].Err() == %v want %v", i, e, Canceled)
90
+		}
91
+	}
92
+}
93
+
94
+func TestParentFinishesChild(t *testing.T) {
95
+	// Context tree:
96
+	// parent -> cancelChild
97
+	// parent -> valueChild -> timerChild
98
+	parent, cancel := WithCancel(Background())
99
+	cancelChild, stop := WithCancel(parent)
100
+	defer stop()
101
+	valueChild := WithValue(parent, "key", "value")
102
+	timerChild, stop := WithTimeout(valueChild, 10000*time.Hour)
103
+	defer stop()
104
+
105
+	select {
106
+	case x := <-parent.Done():
107
+		t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
108
+	case x := <-cancelChild.Done():
109
+		t.Errorf("<-cancelChild.Done() == %v want nothing (it should block)", x)
110
+	case x := <-timerChild.Done():
111
+		t.Errorf("<-timerChild.Done() == %v want nothing (it should block)", x)
112
+	case x := <-valueChild.Done():
113
+		t.Errorf("<-valueChild.Done() == %v want nothing (it should block)", x)
114
+	default:
115
+	}
116
+
117
+	// The parent's children should contain the two cancelable children.
118
+	pc := parent.(*cancelCtx)
119
+	cc := cancelChild.(*cancelCtx)
120
+	tc := timerChild.(*timerCtx)
121
+	pc.mu.Lock()
122
+	if len(pc.children) != 2 || !pc.children[cc] || !pc.children[tc] {
123
+		t.Errorf("bad linkage: pc.children = %v, want %v and %v",
124
+			pc.children, cc, tc)
125
+	}
126
+	pc.mu.Unlock()
127
+
128
+	if p, ok := parentCancelCtx(cc.Context); !ok || p != pc {
129
+		t.Errorf("bad linkage: parentCancelCtx(cancelChild.Context) = %v, %v want %v, true", p, ok, pc)
130
+	}
131
+	if p, ok := parentCancelCtx(tc.Context); !ok || p != pc {
132
+		t.Errorf("bad linkage: parentCancelCtx(timerChild.Context) = %v, %v want %v, true", p, ok, pc)
133
+	}
134
+
135
+	cancel()
136
+
137
+	pc.mu.Lock()
138
+	if len(pc.children) != 0 {
139
+		t.Errorf("pc.cancel didn't clear pc.children = %v", pc.children)
140
+	}
141
+	pc.mu.Unlock()
142
+
143
+	// parent and children should all be finished.
144
+	check := func(ctx Context, name string) {
145
+		select {
146
+		case <-ctx.Done():
147
+		default:
148
+			t.Errorf("<-%s.Done() blocked, but shouldn't have", name)
149
+		}
150
+		if e := ctx.Err(); e != Canceled {
151
+			t.Errorf("%s.Err() == %v want %v", name, e, Canceled)
152
+		}
153
+	}
154
+	check(parent, "parent")
155
+	check(cancelChild, "cancelChild")
156
+	check(valueChild, "valueChild")
157
+	check(timerChild, "timerChild")
158
+
159
+	// WithCancel should return a canceled context on a canceled parent.
160
+	precanceledChild := WithValue(parent, "key", "value")
161
+	select {
162
+	case <-precanceledChild.Done():
163
+	default:
164
+		t.Errorf("<-precanceledChild.Done() blocked, but shouldn't have")
165
+	}
166
+	if e := precanceledChild.Err(); e != Canceled {
167
+		t.Errorf("precanceledChild.Err() == %v want %v", e, Canceled)
168
+	}
169
+}
170
+
171
+func TestChildFinishesFirst(t *testing.T) {
172
+	cancelable, stop := WithCancel(Background())
173
+	defer stop()
174
+	for _, parent := range []Context{Background(), cancelable} {
175
+		child, cancel := WithCancel(parent)
176
+
177
+		select {
178
+		case x := <-parent.Done():
179
+			t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
180
+		case x := <-child.Done():
181
+			t.Errorf("<-child.Done() == %v want nothing (it should block)", x)
182
+		default:
183
+		}
184
+
185
+		cc := child.(*cancelCtx)
186
+		pc, pcok := parent.(*cancelCtx) // pcok == false when parent == Background()
187
+		if p, ok := parentCancelCtx(cc.Context); ok != pcok || (ok && pc != p) {
188
+			t.Errorf("bad linkage: parentCancelCtx(cc.Context) = %v, %v want %v, %v", p, ok, pc, pcok)
189
+		}
190
+
191
+		if pcok {
192
+			pc.mu.Lock()
193
+			if len(pc.children) != 1 || !pc.children[cc] {
194
+				t.Errorf("bad linkage: pc.children = %v, cc = %v", pc.children, cc)
195
+			}
196
+			pc.mu.Unlock()
197
+		}
198
+
199
+		cancel()
200
+
201
+		if pcok {
202
+			pc.mu.Lock()
203
+			if len(pc.children) != 0 {
204
+				t.Errorf("child's cancel didn't remove self from pc.children = %v", pc.children)
205
+			}
206
+			pc.mu.Unlock()
207
+		}
208
+
209
+		// child should be finished.
210
+		select {
211
+		case <-child.Done():
212
+		default:
213
+			t.Errorf("<-child.Done() blocked, but shouldn't have")
214
+		}
215
+		if e := child.Err(); e != Canceled {
216
+			t.Errorf("child.Err() == %v want %v", e, Canceled)
217
+		}
218
+
219
+		// parent should not be finished.
220
+		select {
221
+		case x := <-parent.Done():
222
+			t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
223
+		default:
224
+		}
225
+		if e := parent.Err(); e != nil {
226
+			t.Errorf("parent.Err() == %v want nil", e)
227
+		}
228
+	}
229
+}
230
+
231
+func testDeadline(c Context, wait time.Duration, t *testing.T) {
232
+	select {
233
+	case <-time.After(wait):
234
+		t.Fatalf("context should have timed out")
235
+	case <-c.Done():
236
+	}
237
+	if e := c.Err(); e != DeadlineExceeded {
238
+		t.Errorf("c.Err() == %v want %v", e, DeadlineExceeded)
239
+	}
240
+}
241
+
242
+func TestDeadline(t *testing.T) {
243
+	c, _ := WithDeadline(Background(), time.Now().Add(100*time.Millisecond))
244
+	if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) {
245
+		t.Errorf("c.String() = %q want prefix %q", got, prefix)
246
+	}
247
+	testDeadline(c, 200*time.Millisecond, t)
248
+
249
+	c, _ = WithDeadline(Background(), time.Now().Add(100*time.Millisecond))
250
+	o := otherContext{c}
251
+	testDeadline(o, 200*time.Millisecond, t)
252
+
253
+	c, _ = WithDeadline(Background(), time.Now().Add(100*time.Millisecond))
254
+	o = otherContext{c}
255
+	c, _ = WithDeadline(o, time.Now().Add(300*time.Millisecond))
256
+	testDeadline(c, 200*time.Millisecond, t)
257
+}
258
+
259
+func TestTimeout(t *testing.T) {
260
+	c, _ := WithTimeout(Background(), 100*time.Millisecond)
261
+	if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) {
262
+		t.Errorf("c.String() = %q want prefix %q", got, prefix)
263
+	}
264
+	testDeadline(c, 200*time.Millisecond, t)
265
+
266
+	c, _ = WithTimeout(Background(), 100*time.Millisecond)
267
+	o := otherContext{c}
268
+	testDeadline(o, 200*time.Millisecond, t)
269
+
270
+	c, _ = WithTimeout(Background(), 100*time.Millisecond)
271
+	o = otherContext{c}
272
+	c, _ = WithTimeout(o, 300*time.Millisecond)
273
+	testDeadline(c, 200*time.Millisecond, t)
274
+}
275
+
276
+func TestCanceledTimeout(t *testing.T) {
277
+	c, _ := WithTimeout(Background(), 200*time.Millisecond)
278
+	o := otherContext{c}
279
+	c, cancel := WithTimeout(o, 400*time.Millisecond)
280
+	cancel()
281
+	time.Sleep(100 * time.Millisecond) // let cancelation propagate
282
+	select {
283
+	case <-c.Done():
284
+	default:
285
+		t.Errorf("<-c.Done() blocked, but shouldn't have")
286
+	}
287
+	if e := c.Err(); e != Canceled {
288
+		t.Errorf("c.Err() == %v want %v", e, Canceled)
289
+	}
290
+}
291
+
292
+type key1 int
293
+type key2 int
294
+
295
+var k1 = key1(1)
296
+var k2 = key2(1) // same int as k1, different type
297
+var k3 = key2(3) // same type as k2, different int
298
+
299
+func TestValues(t *testing.T) {
300
+	check := func(c Context, nm, v1, v2, v3 string) {
301
+		if v, ok := c.Value(k1).(string); ok == (len(v1) == 0) || v != v1 {
302
+			t.Errorf(`%s.Value(k1).(string) = %q, %t want %q, %t`, nm, v, ok, v1, len(v1) != 0)
303
+		}
304
+		if v, ok := c.Value(k2).(string); ok == (len(v2) == 0) || v != v2 {
305
+			t.Errorf(`%s.Value(k2).(string) = %q, %t want %q, %t`, nm, v, ok, v2, len(v2) != 0)
306
+		}
307
+		if v, ok := c.Value(k3).(string); ok == (len(v3) == 0) || v != v3 {
308
+			t.Errorf(`%s.Value(k3).(string) = %q, %t want %q, %t`, nm, v, ok, v3, len(v3) != 0)
309
+		}
310
+	}
311
+
312
+	c0 := Background()
313
+	check(c0, "c0", "", "", "")
314
+
315
+	c1 := WithValue(Background(), k1, "c1k1")
316
+	check(c1, "c1", "c1k1", "", "")
317
+
318
+	if got, want := fmt.Sprint(c1), `context.Background.WithValue(1, "c1k1")`; got != want {
319
+		t.Errorf("c.String() = %q want %q", got, want)
320
+	}
321
+
322
+	c2 := WithValue(c1, k2, "c2k2")
323
+	check(c2, "c2", "c1k1", "c2k2", "")
324
+
325
+	c3 := WithValue(c2, k3, "c3k3")
326
+	check(c3, "c2", "c1k1", "c2k2", "c3k3")
327
+
328
+	c4 := WithValue(c3, k1, nil)
329
+	check(c4, "c4", "", "c2k2", "c3k3")
330
+
331
+	o0 := otherContext{Background()}
332
+	check(o0, "o0", "", "", "")
333
+
334
+	o1 := otherContext{WithValue(Background(), k1, "c1k1")}
335
+	check(o1, "o1", "c1k1", "", "")
336
+
337
+	o2 := WithValue(o1, k2, "o2k2")
338
+	check(o2, "o2", "c1k1", "o2k2", "")
339
+
340
+	o3 := otherContext{c4}
341
+	check(o3, "o3", "", "c2k2", "c3k3")
342
+
343
+	o4 := WithValue(o3, k3, nil)
344
+	check(o4, "o4", "", "c2k2", "")
345
+}
346
+
347
+func TestAllocs(t *testing.T) {
348
+	bg := Background()
349
+	for _, test := range []struct {
350
+		desc       string
351
+		f          func()
352
+		limit      float64
353
+		gccgoLimit float64
354
+	}{
355
+		{
356
+			desc:       "Background()",
357
+			f:          func() { Background() },
358
+			limit:      0,
359
+			gccgoLimit: 0,
360
+		},
361
+		{
362
+			desc: fmt.Sprintf("WithValue(bg, %v, nil)", k1),
363
+			f: func() {
364
+				c := WithValue(bg, k1, nil)
365
+				c.Value(k1)
366
+			},
367
+			limit:      1,
368
+			gccgoLimit: 3,
369
+		},
370
+		{
371
+			desc: "WithTimeout(bg, 15*time.Millisecond)",
372
+			f: func() {
373
+				c, _ := WithTimeout(bg, 15*time.Millisecond)
374
+				<-c.Done()
375
+			},
376
+			limit:      8,
377
+			gccgoLimit: 13,
378
+		},
379
+		{
380
+			desc: "WithCancel(bg)",
381
+			f: func() {
382
+				c, cancel := WithCancel(bg)
383
+				cancel()
384
+				<-c.Done()
385
+			},
386
+			limit:      5,
387
+			gccgoLimit: 8,
388
+		},
389
+		{
390
+			desc: "WithTimeout(bg, 100*time.Millisecond)",
391
+			f: func() {
392
+				c, cancel := WithTimeout(bg, 100*time.Millisecond)
393
+				cancel()
394
+				<-c.Done()
395
+			},
396
+			limit:      8,
397
+			gccgoLimit: 25,
398
+		},
399
+	} {
400
+		limit := test.limit
401
+		if runtime.Compiler == "gccgo" {
402
+			// gccgo does not yet do escape analysis.
403
+			// TOOD(iant): Remove this when gccgo does do escape analysis.
404
+			limit = test.gccgoLimit
405
+		}
406
+		if n := testing.AllocsPerRun(100, test.f); n > limit {
407
+			t.Errorf("%s allocs = %f want %d", test.desc, n, int(limit))
408
+		}
409
+	}
410
+}
411
+
412
+func TestSimultaneousCancels(t *testing.T) {
413
+	root, cancel := WithCancel(Background())
414
+	m := map[Context]CancelFunc{root: cancel}
415
+	q := []Context{root}
416
+	// Create a tree of contexts.
417
+	for len(q) != 0 && len(m) < 100 {
418
+		parent := q[0]
419
+		q = q[1:]
420
+		for i := 0; i < 4; i++ {
421
+			ctx, cancel := WithCancel(parent)
422
+			m[ctx] = cancel
423
+			q = append(q, ctx)
424
+		}
425
+	}
426
+	// Start all the cancels in a random order.
427
+	var wg sync.WaitGroup
428
+	wg.Add(len(m))
429
+	for _, cancel := range m {
430
+		go func(cancel CancelFunc) {
431
+			cancel()
432
+			wg.Done()
433
+		}(cancel)
434
+	}
435
+	// Wait on all the contexts in a random order.
436
+	for ctx := range m {
437
+		select {
438
+		case <-ctx.Done():
439
+		case <-time.After(1 * time.Second):
440
+			buf := make([]byte, 10<<10)
441
+			n := runtime.Stack(buf, true)
442
+			t.Fatalf("timed out waiting for <-ctx.Done(); stacks:\n%s", buf[:n])
443
+		}
444
+	}
445
+	// Wait for all the cancel functions to return.
446
+	done := make(chan struct{})
447
+	go func() {
448
+		wg.Wait()
449
+		close(done)
450
+	}()
451
+	select {
452
+	case <-done:
453
+	case <-time.After(1 * time.Second):
454
+		buf := make([]byte, 10<<10)
455
+		n := runtime.Stack(buf, true)
456
+		t.Fatalf("timed out waiting for cancel functions; stacks:\n%s", buf[:n])
457
+	}
458
+}
459
+
460
+func TestInterlockedCancels(t *testing.T) {
461
+	parent, cancelParent := WithCancel(Background())
462
+	child, cancelChild := WithCancel(parent)
463
+	go func() {
464
+		parent.Done()
465
+		cancelChild()
466
+	}()
467
+	cancelParent()
468
+	select {
469
+	case <-child.Done():
470
+	case <-time.After(1 * time.Second):
471
+		buf := make([]byte, 10<<10)
472
+		n := runtime.Stack(buf, true)
473
+		t.Fatalf("timed out waiting for child.Done(); stacks:\n%s", buf[:n])
474
+	}
475
+}
476
+
477
+func TestLayersCancel(t *testing.T) {
478
+	testLayers(t, time.Now().UnixNano(), false)
479
+}
480
+
481
+func TestLayersTimeout(t *testing.T) {
482
+	testLayers(t, time.Now().UnixNano(), true)
483
+}
484
+
485
+func testLayers(t *testing.T, seed int64, testTimeout bool) {
486
+	rand.Seed(seed)
487
+	errorf := func(format string, a ...interface{}) {
488
+		t.Errorf(fmt.Sprintf("seed=%d: %s", seed, format), a...)
489
+	}
490
+	const (
491
+		timeout   = 200 * time.Millisecond
492
+		minLayers = 30
493
+	)
494
+	type value int
495
+	var (
496
+		vals      []*value
497
+		cancels   []CancelFunc
498
+		numTimers int
499
+		ctx       = Background()
500
+	)
501
+	for i := 0; i < minLayers || numTimers == 0 || len(cancels) == 0 || len(vals) == 0; i++ {
502
+		switch rand.Intn(3) {
503
+		case 0:
504
+			v := new(value)
505
+			ctx = WithValue(ctx, v, v)
506
+			vals = append(vals, v)
507
+		case 1:
508
+			var cancel CancelFunc
509
+			ctx, cancel = WithCancel(ctx)
510
+			cancels = append(cancels, cancel)
511
+		case 2:
512
+			var cancel CancelFunc
513
+			ctx, cancel = WithTimeout(ctx, timeout)
514
+			cancels = append(cancels, cancel)
515
+			numTimers++
516
+		}
517
+	}
518
+	checkValues := func(when string) {
519
+		for _, key := range vals {
520
+			if val := ctx.Value(key).(*value); key != val {
521
+				errorf("%s: ctx.Value(%p) = %p want %p", when, key, val, key)
522
+			}
523
+		}
524
+	}
525
+	select {
526
+	case <-ctx.Done():
527
+		errorf("ctx should not be canceled yet")
528
+	default:
529
+	}
530
+	if s, prefix := fmt.Sprint(ctx), "context.Background."; !strings.HasPrefix(s, prefix) {
531
+		t.Errorf("ctx.String() = %q want prefix %q", s, prefix)
532
+	}
533
+	t.Log(ctx)
534
+	checkValues("before cancel")
535
+	if testTimeout {
536
+		select {
537
+		case <-ctx.Done():
538
+		case <-time.After(timeout + timeout/10):
539
+			errorf("ctx should have timed out")
540
+		}
541
+		checkValues("after timeout")
542
+	} else {
543
+		cancel := cancels[rand.Intn(len(cancels))]
544
+		cancel()
545
+		select {
546
+		case <-ctx.Done():
547
+		default:
548
+			errorf("ctx should be canceled")
549
+		}
550
+		checkValues("after cancel")
551
+	}
552
+}
0 553
new file mode 100644
... ...
@@ -0,0 +1,26 @@
0
+// Copyright 2014 The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+package context_test
5
+
6
+import (
7
+	"fmt"
8
+	"time"
9
+
10
+	"code.google.com/p/go.net/context"
11
+)
12
+
13
+func ExampleWithTimeout() {
14
+	// Pass a context with a timeout to tell a blocking function that it
15
+	// should abandon its work after the timeout elapses.
16
+	ctx, _ := context.WithTimeout(context.Background(), 100*time.Millisecond)
17
+	select {
18
+	case <-time.After(200 * time.Millisecond):
19
+		fmt.Println("overslept")
20
+	case <-ctx.Done():
21
+		fmt.Println(ctx.Err()) // prints "context deadline exceeded"
22
+	}
23
+	// Output:
24
+	// context deadline exceeded
25
+}
0 26
new file mode 100644
... ...
@@ -0,0 +1,73 @@
0
+/*
1
+Copyright 2014 Google Inc. All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package api
17
+
18
+import (
19
+	stderrs "errors"
20
+
21
+	"code.google.com/p/go.net/context"
22
+)
23
+
24
+// Context carries values across API boundaries.
25
+type Context interface {
26
+	Value(key interface{}) interface{}
27
+}
28
+
29
+// The key type is unexported to prevent collisions
30
+type key int
31
+
32
+// namespaceKey is the context key for the request namespace.
33
+const namespaceKey key = 0
34
+
35
+// NewContext instantiates a base context object for request flows.
36
+func NewContext() Context {
37
+	return context.TODO()
38
+}
39
+
40
+// NewDefaultContext instantiates a base context object for request flows in the default namespace
41
+func NewDefaultContext() Context {
42
+	return WithNamespace(NewContext(), NamespaceDefault)
43
+}
44
+
45
+// WithValue returns a copy of parent in which the value associated with key is val.
46
+func WithValue(parent Context, key interface{}, val interface{}) Context {
47
+	internalCtx, ok := parent.(context.Context)
48
+	if !ok {
49
+		panic(stderrs.New("Invalid context type"))
50
+	}
51
+	return context.WithValue(internalCtx, key, val)
52
+}
53
+
54
+// WithNamespace returns a copy of parent in which the namespace value is set
55
+func WithNamespace(parent Context, namespace string) Context {
56
+	return WithValue(parent, namespaceKey, namespace)
57
+}
58
+
59
+// NamespaceFrom returns the value of the namespace key on the ctx
60
+func NamespaceFrom(ctx Context) (string, bool) {
61
+	namespace, ok := ctx.Value(namespaceKey).(string)
62
+	return namespace, ok
63
+}
64
+
65
+// ValidNamespace returns false if the namespace on the context differs from the resource.  If the resource has no namespace, it is set to the value in the context.
66
+func ValidNamespace(ctx Context, resource *JSONBase) bool {
67
+	ns, ok := NamespaceFrom(ctx)
68
+	if len(resource.Namespace) == 0 {
69
+		resource.Namespace = ns
70
+	}
71
+	return ns == resource.Namespace && ok
72
+}
0 73
new file mode 100644
... ...
@@ -0,0 +1,62 @@
0
+/*
1
+Copyright 2014 Google Inc. All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package api_test
17
+
18
+import (
19
+	"testing"
20
+
21
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
22
+)
23
+
24
+// TestNamespaceContext validates that a namespace can be get/set on a context object
25
+func TestNamespaceContext(t *testing.T) {
26
+	ctx := api.NewDefaultContext()
27
+	result, ok := api.NamespaceFrom(ctx)
28
+	if !ok {
29
+		t.Errorf("Error getting namespace")
30
+	}
31
+	if api.NamespaceDefault != result {
32
+		t.Errorf("Expected: %v, Actual: %v", api.NamespaceDefault, result)
33
+	}
34
+
35
+	ctx = api.NewContext()
36
+	result, ok = api.NamespaceFrom(ctx)
37
+	if ok {
38
+		t.Errorf("Should not be ok because there is no namespace on the context")
39
+	}
40
+}
41
+
42
+// TestValidNamespace validates that namespace rules are enforced on a resource prior to create or update
43
+func TestValidNamespace(t *testing.T) {
44
+	ctx := api.NewDefaultContext()
45
+	namespace, _ := api.NamespaceFrom(ctx)
46
+	resource := api.ReplicationController{}
47
+	if !api.ValidNamespace(ctx, &resource.JSONBase) {
48
+		t.Errorf("expected success")
49
+	}
50
+	if namespace != resource.Namespace {
51
+		t.Errorf("expected resource to have the default namespace assigned during validation")
52
+	}
53
+	resource = api.ReplicationController{JSONBase: api.JSONBase{Namespace: "other"}}
54
+	if api.ValidNamespace(ctx, &resource.JSONBase) {
55
+		t.Errorf("Expected error that resource and context errors do not match because resource has different namespace")
56
+	}
57
+	ctx = api.NewContext()
58
+	if api.ValidNamespace(ctx, &resource.JSONBase) {
59
+		t.Errorf("Expected error that resource and context errors do not match since context has no namespace")
60
+	}
61
+}
... ...
@@ -21,6 +21,7 @@ import (
21 21
 	"net/http"
22 22
 
23 23
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
24
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
24 25
 )
25 26
 
26 27
 // statusError is an error intended for consumption by a REST API server.
... ...
@@ -38,6 +39,16 @@ func (e *statusError) Status() api.Status {
38 38
 	return e.status
39 39
 }
40 40
 
41
+// FromObject generates an statusError from an api.Status, if that is the type of obj; otherwise,
42
+// returns an error created by fmt.Errorf.
43
+func FromObject(obj runtime.Object) error {
44
+	switch t := obj.(type) {
45
+	case *api.Status:
46
+		return &statusError{*t}
47
+	}
48
+	return fmt.Errorf("unexpected object: %v", obj)
49
+}
50
+
41 51
 // NewNotFound returns a new error which indicates that the resource of the kind and the name was not found.
42 52
 func NewNotFound(kind, name string) error {
43 53
 	return &statusError{api.Status{
... ...
@@ -23,31 +23,32 @@ import (
23 23
 	"testing"
24 24
 
25 25
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
26
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
26 27
 )
27 28
 
28 29
 func TestErrorNew(t *testing.T) {
29 30
 	err := NewAlreadyExists("test", "1")
30 31
 	if !IsAlreadyExists(err) {
31
-		t.Errorf("expected to be already_exists")
32
+		t.Errorf("expected to be %s", api.StatusReasonAlreadyExists)
32 33
 	}
33 34
 	if IsConflict(err) {
34
-		t.Errorf("expected to not be confict")
35
+		t.Errorf("expected to not be %s", api.StatusReasonConflict)
35 36
 	}
36 37
 	if IsNotFound(err) {
37 38
 		t.Errorf(fmt.Sprintf("expected to not be %s", api.StatusReasonNotFound))
38 39
 	}
39 40
 	if IsInvalid(err) {
40
-		t.Errorf("expected to not be invalid")
41
+		t.Errorf("expected to not be %s", api.StatusReasonInvalid)
41 42
 	}
42 43
 
43 44
 	if !IsConflict(NewConflict("test", "2", errors.New("message"))) {
44 45
 		t.Errorf("expected to be conflict")
45 46
 	}
46 47
 	if !IsNotFound(NewNotFound("test", "3")) {
47
-		t.Errorf("expected to be not found")
48
+		t.Errorf("expected to be %s", api.StatusReasonNotFound)
48 49
 	}
49 50
 	if !IsInvalid(NewInvalid("test", "2", nil)) {
50
-		t.Errorf("expected to be invalid")
51
+		t.Errorf("expected to be %s", api.StatusReasonInvalid)
51 52
 	}
52 53
 }
53 54
 
... ...
@@ -121,7 +122,7 @@ func TestNewInvalid(t *testing.T) {
121 121
 			t.Errorf("%d: unexpected status: %#v", i, status)
122 122
 		}
123 123
 		if !reflect.DeepEqual(expected, status.Details) {
124
-			t.Errorf("%d: expected %#v, got %#v", expected, status.Details)
124
+			t.Errorf("%d: expected %#v, got %#v", i, expected, status.Details)
125 125
 		}
126 126
 	}
127 127
 }
... ...
@@ -131,3 +132,23 @@ func Test_reasonForError(t *testing.T) {
131 131
 		t.Errorf("unexpected reason type: %#v", a)
132 132
 	}
133 133
 }
134
+
135
+type TestType struct{}
136
+
137
+func (*TestType) IsAnAPIObject() {}
138
+
139
+func TestFromObject(t *testing.T) {
140
+	table := []struct {
141
+		obj     runtime.Object
142
+		message string
143
+	}{
144
+		{&api.Status{Message: "foobar"}, "foobar"},
145
+		{&TestType{}, "unexpected object: &{}"},
146
+	}
147
+
148
+	for _, item := range table {
149
+		if e, a := item.message, FromObject(item.obj).Error(); e != a {
150
+			t.Errorf("Expected %v, got %v", e, a)
151
+		}
152
+	}
153
+}
... ...
@@ -28,22 +28,23 @@ import (
28 28
 // CauseType in api/types.go.
29 29
 type ValidationErrorType string
30 30
 
31
+// TODO: These values are duplicated in api/types.go, but there's a circular dep.  Fix it.
31 32
 const (
32 33
 	// ValidationErrorTypeNotFound is used to report failure to find a requested value
33 34
 	// (e.g. looking up an ID).
34
-	ValidationErrorTypeNotFound ValidationErrorType = "fieldValueNotFound"
35
+	ValidationErrorTypeNotFound ValidationErrorType = "FieldValueNotFound"
35 36
 	// ValidationErrorTypeRequired is used to report required values that are not
36 37
 	// provided (e.g. empty strings, null values, or empty arrays).
37
-	ValidationErrorTypeRequired ValidationErrorType = "fieldValueRequired"
38
+	ValidationErrorTypeRequired ValidationErrorType = "FieldValueRequired"
38 39
 	// ValidationErrorTypeDuplicate is used to report collisions of values that must be
39 40
 	// unique (e.g. unique IDs).
40
-	ValidationErrorTypeDuplicate ValidationErrorType = "fieldValueDuplicate"
41
+	ValidationErrorTypeDuplicate ValidationErrorType = "FieldValueDuplicate"
41 42
 	// ValidationErrorTypeInvalid is used to report malformed values (e.g. failed regex
42 43
 	// match).
43
-	ValidationErrorTypeInvalid ValidationErrorType = "fieldValueInvalid"
44
+	ValidationErrorTypeInvalid ValidationErrorType = "FieldValueInvalid"
44 45
 	// ValidationErrorTypeNotSupported is used to report valid (as per formatting rules)
45 46
 	// values that can not be handled (e.g. an enumerated string).
46
-	ValidationErrorTypeNotSupported ValidationErrorType = "fieldValueNotSupported"
47
+	ValidationErrorTypeNotSupported ValidationErrorType = "FieldValueNotSupported"
47 48
 )
48 49
 
49 50
 func ValueOf(t ValidationErrorType) string {
... ...
@@ -49,16 +49,36 @@ var Codec = v1beta1.Codec
49 49
 // TODO: when versioning changes, make this part of each API definition.
50 50
 var ResourceVersioner = runtime.NewJSONBaseResourceVersioner()
51 51
 
52
+// SelfLinker can set or get the SelfLink field of all API types.
53
+// TODO: when versioning changes, make this part of each API definition.
54
+// TODO(lavalamp): Combine SelfLinker & ResourceVersioner interfaces, force all uses
55
+// to go through the InterfacesFor method below.
56
+var SelfLinker = runtime.NewJSONBaseSelfLinker()
57
+
58
+// VersionInterfaces contains the interfaces one should use for dealing with types of a particular version.
59
+type VersionInterfaces struct {
60
+	runtime.Codec
61
+	runtime.ResourceVersioner
62
+	runtime.SelfLinker
63
+}
64
+
52 65
 // InterfacesFor returns the default Codec and ResourceVersioner for a given version
53 66
 // string, or an error if the version is not known.
54
-func InterfacesFor(version string) (codec runtime.Codec, versioner runtime.ResourceVersioner, err error) {
67
+func InterfacesFor(version string) (*VersionInterfaces, error) {
55 68
 	switch version {
56 69
 	case "v1beta1":
57
-		codec, versioner = v1beta1.Codec, ResourceVersioner
70
+		return &VersionInterfaces{
71
+			Codec:             v1beta1.Codec,
72
+			ResourceVersioner: ResourceVersioner,
73
+			SelfLinker:        SelfLinker,
74
+		}, nil
58 75
 	case "v1beta2":
59
-		codec, versioner = v1beta2.Codec, ResourceVersioner
76
+		return &VersionInterfaces{
77
+			Codec:             v1beta2.Codec,
78
+			ResourceVersioner: ResourceVersioner,
79
+			SelfLinker:        SelfLinker,
80
+		}, nil
60 81
 	default:
61
-		err = fmt.Errorf("unsupported storage version: %s (valid: %s)", version, strings.Join(Versions, ", "))
82
+		return nil, fmt.Errorf("unsupported storage version: %s (valid: %s)", version, strings.Join(Versions, ", "))
62 83
 	}
63
-	return
64 84
 }
... ...
@@ -85,7 +85,7 @@ var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs(
85 85
 func TestInternalRoundTrip(t *testing.T) {
86 86
 	latest := "v1beta2"
87 87
 
88
-	for k, _ := range internal.Scheme.KnownTypes("") {
88
+	for k := range internal.Scheme.KnownTypes("") {
89 89
 		obj, err := internal.Scheme.New("", k)
90 90
 		if err != nil {
91 91
 			t.Errorf("%s: unexpected error: %v", k, err)
... ...
@@ -146,11 +146,11 @@ func TestCodec(t *testing.T) {
146 146
 }
147 147
 
148 148
 func TestInterfacesFor(t *testing.T) {
149
-	if _, _, err := InterfacesFor(""); err == nil {
149
+	if _, err := InterfacesFor(""); err == nil {
150 150
 		t.Fatalf("unexpected non-error: %v", err)
151 151
 	}
152 152
 	for i, version := range append([]string{Version, OldestVersion}, Versions...) {
153
-		if codec, versioner, err := InterfacesFor(version); err != nil || codec == nil || versioner == nil {
153
+		if vi, err := InterfacesFor(version); err != nil || vi == nil {
154 154
 			t.Fatalf("%d: unexpected result: %v", i, err)
155 155
 		}
156 156
 	}
157 157
new file mode 100644
... ...
@@ -0,0 +1,52 @@
0
+/*
1
+Copyright 2014 Google Inc. All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package api
17
+
18
+import (
19
+	"fmt"
20
+	"regexp"
21
+
22
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
23
+)
24
+
25
+var versionFromSelfLink = regexp.MustCompile("/api/([^/]*)/")
26
+
27
+// GetReference returns an ObjectReference which refers to the given
28
+// object, or an error if the object doesn't follow the conventions
29
+// that would allow this.
30
+func GetReference(obj runtime.Object) (*ObjectReference, error) {
31
+	jsonBase, err := runtime.FindJSONBase(obj)
32
+	if err != nil {
33
+		return nil, err
34
+	}
35
+	_, kind, err := Scheme.ObjectVersionAndKind(obj)
36
+	if err != nil {
37
+		return nil, err
38
+	}
39
+	version := versionFromSelfLink.FindStringSubmatch(jsonBase.SelfLink())
40
+	if len(version) < 2 {
41
+		return nil, fmt.Errorf("unexpected self link format: %v", jsonBase.SelfLink())
42
+	}
43
+	return &ObjectReference{
44
+		Kind:       kind,
45
+		APIVersion: version[1],
46
+		// TODO: correct Name and UID when JSONBase makes a distinction
47
+		Name:            jsonBase.ID(),
48
+		UID:             jsonBase.ID(),
49
+		ResourceVersion: jsonBase.ResourceVersion(),
50
+	}, nil
51
+}
0 52
new file mode 100644
... ...
@@ -0,0 +1,95 @@
0
+/*
1
+Copyright 2014 Google Inc. All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package api
17
+
18
+import (
19
+	"reflect"
20
+	"testing"
21
+
22
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
23
+)
24
+
25
+type FakeAPIObject struct{}
26
+
27
+func (*FakeAPIObject) IsAnAPIObject() {}
28
+
29
+func TestGetReference(t *testing.T) {
30
+	table := map[string]struct {
31
+		obj       runtime.Object
32
+		ref       *ObjectReference
33
+		shouldErr bool
34
+	}{
35
+		"pod": {
36
+			obj: &Pod{
37
+				JSONBase: JSONBase{
38
+					ID:              "foo",
39
+					ResourceVersion: 42,
40
+					SelfLink:        "/api/v1beta1/pods/foo",
41
+				},
42
+			},
43
+			ref: &ObjectReference{
44
+				Kind:            "Pod",
45
+				APIVersion:      "v1beta1",
46
+				Name:            "foo",
47
+				UID:             "foo",
48
+				ResourceVersion: 42,
49
+			},
50
+		},
51
+		"serviceList": {
52
+			obj: &ServiceList{
53
+				JSONBase: JSONBase{
54
+					ID:              "foo",
55
+					ResourceVersion: 42,
56
+					SelfLink:        "/api/v1beta2/services",
57
+				},
58
+			},
59
+			ref: &ObjectReference{
60
+				Kind:            "ServiceList",
61
+				APIVersion:      "v1beta2",
62
+				Name:            "foo",
63
+				UID:             "foo",
64
+				ResourceVersion: 42,
65
+			},
66
+		},
67
+		"badSelfLink": {
68
+			obj: &ServiceList{
69
+				JSONBase: JSONBase{
70
+					ID:              "foo",
71
+					ResourceVersion: 42,
72
+					SelfLink:        "v1beta2/services",
73
+				},
74
+			},
75
+			shouldErr: true,
76
+		},
77
+		"error": {
78
+			obj:       &FakeAPIObject{},
79
+			ref:       nil,
80
+			shouldErr: true,
81
+		},
82
+	}
83
+
84
+	for name, item := range table {
85
+		ref, err := GetReference(item.obj)
86
+		if e, a := item.shouldErr, (err != nil); e != a {
87
+			t.Errorf("%v: expected %v, got %v", name, e, a)
88
+			continue
89
+		}
90
+		if e, a := item.ref, ref; !reflect.DeepEqual(e, a) {
91
+			t.Errorf("%v: expected %#v, got %#v", name, e, a)
92
+		}
93
+	}
94
+}
... ...
@@ -39,5 +39,7 @@ func init() {
39 39
 		&Endpoints{},
40 40
 		&EndpointsList{},
41 41
 		&Binding{},
42
+		&Event{},
43
+		&EventList{},
42 44
 	)
43 45
 }
... ...
@@ -147,25 +147,14 @@ func runTest(t *testing.T, codec runtime.Codec, source runtime.Object) {
147 147
 }
148 148
 
149 149
 func TestTypes(t *testing.T) {
150
-	table := []runtime.Object{
151
-		&api.PodList{},
152
-		&api.Pod{},
153
-		&api.ServiceList{},
154
-		&api.Service{},
155
-		&api.ReplicationControllerList{},
156
-		&api.ReplicationController{},
157
-		&api.MinionList{},
158
-		&api.Minion{},
159
-		&api.Status{},
160
-		&api.ServerOpList{},
161
-		&api.ServerOp{},
162
-		&api.ContainerManifestList{},
163
-		&api.Endpoints{},
164
-		&api.Binding{},
165
-	}
166
-	for _, item := range table {
150
+	for kind := range api.Scheme.KnownTypes("") {
167 151
 		// Try a few times, since runTest uses random values.
168 152
 		for i := 0; i < *fuzzIters; i++ {
153
+			item, err := api.Scheme.New("", kind)
154
+			if err != nil {
155
+				t.Errorf("Couldn't make a %v? %v", kind, err)
156
+				continue
157
+			}
169 158
 			runTest(t, v1beta1.Codec, item)
170 159
 			runTest(t, v1beta2.Codec, item)
171 160
 			runTest(t, api.Codec, item)
172 161
new file mode 100644
... ...
@@ -0,0 +1,42 @@
0
+/*
1
+Copyright 2014 Google Inc. All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+// Package testapi provides a helper for retrieving the KUBE_API_VERSION environment variable.
17
+package testapi
18
+
19
+import (
20
+	"os"
21
+
22
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
23
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
24
+)
25
+
26
+// Version returns the API version to test against as set by the KUBE_API_VERSION env var.
27
+func Version() string {
28
+	version := os.Getenv("KUBE_API_VERSION")
29
+	if version == "" {
30
+		version = latest.Version
31
+	}
32
+	return version
33
+}
34
+
35
+func CodecForVersionOrDie() runtime.Codec {
36
+	interfaces, err := latest.InterfacesFor(Version())
37
+	if err != nil {
38
+		panic(err)
39
+	}
40
+	return interfaces.Codec
41
+}
... ...
@@ -17,6 +17,8 @@ limitations under the License.
17 17
 package api
18 18
 
19 19
 import (
20
+	"strings"
21
+
20 22
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
21 23
 	"github.com/fsouza/go-dockerclient"
22 24
 )
... ...
@@ -83,22 +85,32 @@ type Volume struct {
83 83
 
84 84
 type VolumeSource struct {
85 85
 	// Only one of the following sources may be specified
86
-	// HostDirectory represents a pre-existing directory on the host machine that is directly
86
+	// HostDir represents a pre-existing directory on the host machine that is directly
87 87
 	// exposed to the container. This is generally used for system agents or other privileged
88 88
 	// things that are allowed to see the host machine. Most containers will NOT need this.
89 89
 	// TODO(jonesdl) We need to restrict who can use host directory mounts and
90 90
 	// who can/can not mount host directories as read/write.
91
-	HostDirectory *HostDirectory `yaml:"hostDir" json:"hostDir"`
92
-	// EmptyDirectory represents a temporary directory that shares a pod's lifetime.
93
-	EmptyDirectory *EmptyDirectory `yaml:"emptyDir" json:"emptyDir"`
91
+	HostDir *HostDir `yaml:"hostDir" json:"hostDir"`
92
+	// EmptyDir represents a temporary directory that shares a pod's lifetime.
93
+	EmptyDir *EmptyDir `yaml:"emptyDir" json:"emptyDir"`
94 94
 }
95 95
 
96
-// HostDirectory represents bare host directory volume.
97
-type HostDirectory struct {
96
+// HostDir represents bare host directory volume.
97
+type HostDir struct {
98 98
 	Path string `yaml:"path" json:"path"`
99 99
 }
100 100
 
101
-type EmptyDirectory struct{}
101
+type EmptyDir struct{}
102
+
103
+// Protocol defines network protocols supported for things like conatiner ports.
104
+type Protocol string
105
+
106
+const (
107
+	// ProtocolTCP is the TCP protocol.
108
+	ProtocolTCP Protocol = "TCP"
109
+	// ProtocolUDP is the UDP protocol.
110
+	ProtocolUDP Protocol = "UDP"
111
+)
102 112
 
103 113
 // Port represents a network port in a single container.
104 114
 type Port struct {
... ...
@@ -109,8 +121,8 @@ type Port struct {
109 109
 	HostPort int `yaml:"hostPort,omitempty" json:"hostPort,omitempty"`
110 110
 	// Required: This must be a valid port number, 0 < x < 65536.
111 111
 	ContainerPort int `yaml:"containerPort" json:"containerPort"`
112
-	// Optional: Supports "TCP" and "UDP".  Defaults to "TCP".
113
-	Protocol string `yaml:"protocol,omitempty" json:"protocol,omitempty"`
112
+	// Optional: Defaults to "TCP".
113
+	Protocol Protocol `yaml:"protocol,omitempty" json:"protocol,omitempty"`
114 114
 	// Optional: What host IP to bind the external port to.
115 115
 	HostIP string `yaml:"hostIP,omitempty" json:"hostIP,omitempty"`
116 116
 }
... ...
@@ -161,8 +173,6 @@ type ExecAction struct {
161 161
 // LivenessProbe describes a liveness probe to be examined to the container.
162 162
 // TODO: pass structured data to the actions, and document that data here.
163 163
 type LivenessProbe struct {
164
-	// Type of liveness probe.  Current legal values "http", "tcp"
165
-	Type string `yaml:"type,omitempty" json:"type,omitempty"`
166 164
 	// HTTPGetProbe parameters, required if Type == 'http'
167 165
 	HTTPGet *HTTPGetAction `yaml:"httpGet,omitempty" json:"httpGet,omitempty"`
168 166
 	// TCPSocketProbe parameter, required if Type == 'tcp'
... ...
@@ -173,6 +183,38 @@ type LivenessProbe struct {
173 173
 	InitialDelaySeconds int64 `yaml:"initialDelaySeconds,omitempty" json:"initialDelaySeconds,omitempty"`
174 174
 }
175 175
 
176
+// PullPolicy describes a policy for if/when to pull a container image
177
+type PullPolicy string
178
+
179
+const (
180
+	// Always attempt to pull the latest image.  Container will fail If the pull fails.
181
+	PullAlways PullPolicy = "PullAlways"
182
+	// Never pull an image, only use a local image.  Container will fail if the image isn't present
183
+	PullNever PullPolicy = "PullNever"
184
+	// Pull if the image isn't present on disk. Container will fail if the image isn't present and the pull fails.
185
+	PullIfNotPresent PullPolicy = "PullIfNotPresent"
186
+)
187
+
188
+func IsPullAlways(p PullPolicy) bool {
189
+	// Default to pull always
190
+	if len(p) == 0 {
191
+		return true
192
+	}
193
+	return pullPoliciesEqual(p, PullAlways)
194
+}
195
+
196
+func IsPullNever(p PullPolicy) bool {
197
+	return pullPoliciesEqual(p, PullNever)
198
+}
199
+
200
+func IsPullIfNotPresent(p PullPolicy) bool {
201
+	return pullPoliciesEqual(p, PullIfNotPresent)
202
+}
203
+
204
+func pullPoliciesEqual(p1, p2 PullPolicy) bool {
205
+	return strings.ToLower(string(p1)) == strings.ToLower(string(p2))
206
+}
207
+
176 208
 // Container represents a single container that is expected to be run on the host.
177 209
 type Container struct {
178 210
 	// Required: This must be a DNS_LABEL.  Each container in a pod must
... ...
@@ -195,6 +237,8 @@ type Container struct {
195 195
 	Lifecycle     *Lifecycle     `yaml:"lifecycle,omitempty" json:"lifecycle,omitempty"`
196 196
 	// Optional: Default to false.
197 197
 	Privileged bool `json:"privileged,omitempty" yaml:"privileged,omitempty"`
198
+	// Optional: Policy for pulling images for this container
199
+	ImagePullPolicy PullPolicy `json:"imagePullPolicy" yaml:"imagePullPolicy"`
198 200
 }
199 201
 
200 202
 // Handler defines a specific action that should be taken
... ...
@@ -219,14 +263,6 @@ type Lifecycle struct {
219 219
 	PreStop *Handler `yaml:"preStop,omitempty" json:"preStop,omitempty"`
220 220
 }
221 221
 
222
-// Event is the representation of an event logged to etcd backends.
223
-type Event struct {
224
-	Event     string             `json:"event,omitempty"`
225
-	Manifest  *ContainerManifest `json:"manifest,omitempty"`
226
-	Container *Container         `json:"container,omitempty"`
227
-	Timestamp int64              `json:"timestamp"`
228
-}
229
-
230 222
 // The below types are used by kube_client and api_server.
231 223
 
232 224
 // JSONBase is shared by all objects sent to, or returned from the client.
... ...
@@ -237,8 +273,16 @@ type JSONBase struct {
237 237
 	SelfLink          string    `json:"selfLink,omitempty" yaml:"selfLink,omitempty"`
238 238
 	ResourceVersion   uint64    `json:"resourceVersion,omitempty" yaml:"resourceVersion,omitempty"`
239 239
 	APIVersion        string    `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
240
+	Namespace         string    `json:"namespace",omitempty" yaml:"namespace,omitempty"`
240 241
 }
241 242
 
243
+const (
244
+	// NamespaceDefault means the object is in the default namespace which is applied when not specified by clients
245
+	NamespaceDefault string = "default"
246
+	// NamespaceAll is the default argument to specify on a context when you want to list or filter resources across all namespaces
247
+	NamespaceAll string = ""
248
+)
249
+
242 250
 // PodStatus represents a status of a pod.
243 251
 type PodStatus string
244 252
 
... ...
@@ -288,8 +332,7 @@ type ContainerStatus struct {
288 288
 }
289 289
 
290 290
 // PodInfo contains one entry for every container with available info.
291
-// TODO(dchen1107): Replace docker.Container below with ContainerStatus defined above.
292
-type PodInfo map[string]docker.Container
291
+type PodInfo map[string]ContainerStatus
293 292
 
294 293
 type RestartPolicyAlways struct{}
295 294
 
... ...
@@ -390,8 +433,8 @@ type Service struct {
390 390
 
391 391
 	// Required.
392 392
 	Port int `json:"port" yaml:"port"`
393
-	// Optional: Supports "TCP" and "UDP".  Defaults to "TCP".
394
-	Protocol string `yaml:"protocol,omitempty" json:"protocol,omitempty"`
393
+	// Optional: Defaults to "TCP".
394
+	Protocol Protocol `yaml:"protocol,omitempty" json:"protocol,omitempty"`
395 395
 
396 396
 	// This service's labels.
397 397
 	Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
... ...
@@ -424,12 +467,26 @@ type EndpointsList struct {
424 424
 
425 425
 func (*EndpointsList) IsAnAPIObject() {}
426 426
 
427
+// NodeResources represents resources on a Kubernetes system node
428
+// see https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/resources.md for more details.
429
+type NodeResources struct {
430
+	// Capacity represents the available resources.
431
+	Capacity ResourceList `json:"capacity,omitempty" yaml:"capacity,omitempty"`
432
+}
433
+
434
+type ResourceName string
435
+
436
+// TODO Replace this with a more complete "Quantity" struct
437
+type ResourceList map[ResourceName]util.IntOrString
438
+
427 439
 // Minion is a worker node in Kubernetenes.
428 440
 // The name of the minion according to etcd is in JSONBase.ID.
429 441
 type Minion struct {
430 442
 	JSONBase `json:",inline" yaml:",inline"`
431 443
 	// Queried from cloud provider, if available.
432 444
 	HostIP string `json:"hostIP,omitempty" yaml:"hostIP,omitempty"`
445
+	// Resources available on the node
446
+	NodeResources NodeResources `json:"resources,omitempty" yaml:"resources,omitempty"`
433 447
 }
434 448
 
435 449
 func (*Minion) IsAnAPIObject() {}
... ...
@@ -456,12 +513,12 @@ func (*Binding) IsAnAPIObject() {}
456 456
 // import both.
457 457
 type Status struct {
458 458
 	JSONBase `json:",inline" yaml:",inline"`
459
-	// One of: "success", "failure", "working" (for operations not yet completed)
459
+	// One of: "Success", "Failure", "Working" (for operations not yet completed)
460 460
 	Status string `json:"status,omitempty" yaml:"status,omitempty"`
461 461
 	// A human-readable description of the status of this operation.
462 462
 	Message string `json:"message,omitempty" yaml:"message,omitempty"`
463 463
 	// A machine-readable description of why this operation is in the
464
-	// "failure" or "working" status. If this value is empty there
464
+	// "Failure" or "Working" status. If this value is empty there
465 465
 	// is no information available. A Reason clarifies an HTTP status
466 466
 	// code but does not override it.
467 467
 	Reason StatusReason `json:"reason,omitempty" yaml:"reason,omitempty"`
... ...
@@ -496,9 +553,9 @@ type StatusDetails struct {
496 496
 
497 497
 // Values of Status.Status
498 498
 const (
499
-	StatusSuccess = "success"
500
-	StatusFailure = "failure"
501
-	StatusWorking = "working"
499
+	StatusSuccess = "Success"
500
+	StatusFailure = "Failure"
501
+	StatusWorking = "Working"
502 502
 )
503 503
 
504 504
 // StatusReason is an enumeration of possible failure causes.  Each StatusReason
... ...
@@ -523,7 +580,7 @@ const (
523 523
 	//   "Location" - HTTP header populated with a URL that can retrieved the final
524 524
 	//                status of this operation.
525 525
 	// Status code 202
526
-	StatusReasonWorking StatusReason = "working"
526
+	StatusReasonWorking StatusReason = "Working"
527 527
 
528 528
 	// StatusReasonNotFound means one or more resources required for this operation
529 529
 	// could not be found.
... ...
@@ -533,21 +590,21 @@ const (
533 533
 	//                   resource.
534 534
 	//   "id"   string - the identifier of the missing resource
535 535
 	// Status code 404
536
-	StatusReasonNotFound StatusReason = "not_found"
536
+	StatusReasonNotFound StatusReason = "NotFound"
537 537
 
538 538
 	// StatusReasonAlreadyExists means the resource you are creating already exists.
539 539
 	// Details (optional):
540 540
 	//   "kind" string - the kind attribute of the conflicting resource
541 541
 	//   "id"   string - the identifier of the conflicting resource
542 542
 	// Status code 409
543
-	StatusReasonAlreadyExists StatusReason = "already_exists"
543
+	StatusReasonAlreadyExists StatusReason = "AlreadyExists"
544 544
 
545 545
 	// StatusReasonConflict means the requested update operation cannot be completed
546 546
 	// due to a conflict in the operation. The client may need to alter the request.
547 547
 	// Each resource may define custom details that indicate the nature of the
548 548
 	// conflict.
549 549
 	// Status code 409
550
-	StatusReasonConflict StatusReason = "conflict"
550
+	StatusReasonConflict StatusReason = "Conflict"
551 551
 
552 552
 	// StatusReasonInvalid means the requested create or update operation cannot be
553 553
 	// completed due to invalid data provided as part of the request. The client may
... ...
@@ -560,7 +617,7 @@ const (
560 560
 	//                   provided resource that was invalid.  The code, message, and
561 561
 	//                   field attributes will be set.
562 562
 	// Status code 422
563
-	StatusReasonInvalid StatusReason = "invalid"
563
+	StatusReasonInvalid StatusReason = "Invalid"
564 564
 )
565 565
 
566 566
 // StatusCause provides more information about an api.Status failure, including
... ...
@@ -586,25 +643,25 @@ type StatusCause struct {
586 586
 
587 587
 // CauseType is a machine readable value providing more detail about what
588 588
 // occured in a status response. An operation may have multiple causes for a
589
-// status (whether failure, success, or working).
589
+// status (whether Failure, Success, or Working).
590 590
 type CauseType string
591 591
 
592 592
 const (
593 593
 	// CauseTypeFieldValueNotFound is used to report failure to find a requested value
594 594
 	// (e.g. looking up an ID).
595
-	CauseTypeFieldValueNotFound CauseType = "fieldValueNotFound"
595
+	CauseTypeFieldValueNotFound CauseType = "FieldValueNotFound"
596 596
 	// CauseTypeFieldValueInvalid is used to report required values that are not
597 597
 	// provided (e.g. empty strings, null values, or empty arrays).
598
-	CauseTypeFieldValueRequired CauseType = "fieldValueRequired"
598
+	CauseTypeFieldValueRequired CauseType = "FieldValueRequired"
599 599
 	// CauseTypeFieldValueDuplicate is used to report collisions of values that must be
600 600
 	// unique (e.g. unique IDs).
601
-	CauseTypeFieldValueDuplicate CauseType = "fieldValueDuplicate"
601
+	CauseTypeFieldValueDuplicate CauseType = "FieldValueDuplicate"
602 602
 	// CauseTypeFieldValueInvalid is used to report malformed values (e.g. failed regex
603 603
 	// match).
604
-	CauseTypeFieldValueInvalid CauseType = "fieldValueInvalid"
604
+	CauseTypeFieldValueInvalid CauseType = "FieldValueInvalid"
605 605
 	// CauseTypeFieldValueNotSupported is used to report valid (as per formatting rules)
606 606
 	// values that can not be handled (e.g. an enumerated string).
607
-	CauseTypeFieldValueNotSupported CauseType = "fieldValueNotSupported"
607
+	CauseTypeFieldValueNotSupported CauseType = "FieldValueNotSupported"
608 608
 )
609 609
 
610 610
 // ServerOp is an operation delivered to API clients.
... ...
@@ -621,3 +678,63 @@ type ServerOpList struct {
621 621
 }
622 622
 
623 623
 func (*ServerOpList) IsAnAPIObject() {}
624
+
625
+// ObjectReference contains enough information to let you inspect or modify the referred object.
626
+type ObjectReference struct {
627
+	Kind            string `json:"kind,omitempty" yaml:"kind,omitempty"`
628
+	Name            string `json:"name,omitempty" yaml:"name,omitempty"`
629
+	UID             string `json:"uid,omitempty" yaml:"uid,omitempty"`
630
+	APIVersion      string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
631
+	ResourceVersion uint64 `json:"resourceVersion,omitempty" yaml:"resourceVersion,omitempty"`
632
+
633
+	// Optional. If referring to a piece of an object instead of an entire object, this string
634
+	// should contain a valid field access statement. For example,
635
+	// if the object reference is to a container within a pod, this would take on a value like:
636
+	// "desiredState.manifest.containers[2]". Such statements are valid language constructs in
637
+	// both go and JavaScript. This is syntax is chosen only to have some well-defined way of
638
+	// referencing a part of an object.
639
+	// TODO: this design is not final and this field is subject to change in the future.
640
+	FieldPath string `json:"fieldPath,omitempty" yaml:"fieldPath,omitempty"`
641
+}
642
+
643
+// Event is a report of an event somewhere in the cluster.
644
+// TODO: Decide whether to store these separately or with the object they apply to.
645
+type Event struct {
646
+	JSONBase `yaml:",inline" json:",inline"`
647
+
648
+	// Required. The object that this event is about.
649
+	InvolvedObject ObjectReference `json:"involvedObject,omitempty" yaml:"involvedObject,omitempty"`
650
+
651
+	// Should be a short, machine understandable string that describes the current status
652
+	// of the referred object. This should not give the reason for being in this state.
653
+	// Examples: "running", "cantStart", "cantSchedule", "deleted".
654
+	// It's OK for components to make up statuses to report here, but the same string should
655
+	// always be used for the same status.
656
+	// TODO: define a way of making sure these are consistent and don't collide.
657
+	// TODO: provide exact specification for format.
658
+	Status string `json:"status,omitempty" yaml:"status,omitempty"`
659
+
660
+	// Optional; this should be a short, machine understandable string that gives the reason
661
+	// for the transition into the object's current status. For example, if ObjectStatus is
662
+	// "cantStart", StatusReason might be "imageNotFound".
663
+	// TODO: provide exact specification for format.
664
+	Reason string `json:"reason,omitempty" yaml:"reason,omitempty"`
665
+
666
+	// Optional. A human-readable description of the status of this operation.
667
+	// TODO: decide on maximum length.
668
+	Message string `json:"message,omitempty" yaml:"message,omitempty"`
669
+
670
+	// Optional. The component reporting this event. Should be a short machine understandable string.
671
+	// TODO: provide exact specification for format.
672
+	Source string `json:"source,omitempty" yaml:"source,omitempty"`
673
+}
674
+
675
+func (*Event) IsAnAPIObject() {}
676
+
677
+// EventList is a list of events.
678
+type EventList struct {
679
+	JSONBase `yaml:",inline" json:",inline"`
680
+	Items    []Event `yaml:"items,omitempty" json:"items,omitempty"`
681
+}
682
+
683
+func (*EventList) IsAnAPIObject() {}
... ...
@@ -41,5 +41,7 @@ func init() {
41 41
 		&Endpoints{},
42 42
 		&EndpointsList{},
43 43
 		&Binding{},
44
+		&Event{},
45
+		&EventList{},
44 46
 	)
45 47
 }
... ...
@@ -83,22 +83,32 @@ type Volume struct {
83 83
 
84 84
 type VolumeSource struct {
85 85
 	// Only one of the following sources may be specified
86
-	// HostDirectory represents a pre-existing directory on the host machine that is directly
86
+	// HostDir represents a pre-existing directory on the host machine that is directly
87 87
 	// exposed to the container. This is generally used for system agents or other privileged
88 88
 	// things that are allowed to see the host machine. Most containers will NOT need this.
89 89
 	// TODO(jonesdl) We need to restrict who can use host directory mounts and
90 90
 	// who can/can not mount host directories as read/write.
91
-	HostDirectory *HostDirectory `yaml:"hostDir" json:"hostDir"`
92
-	// EmptyDirectory represents a temporary directory that shares a pod's lifetime.
93
-	EmptyDirectory *EmptyDirectory `yaml:"emptyDir" json:"emptyDir"`
91
+	HostDir *HostDir `yaml:"hostDir" json:"hostDir"`
92
+	// EmptyDir represents a temporary directory that shares a pod's lifetime.
93
+	EmptyDir *EmptyDir `yaml:"emptyDir" json:"emptyDir"`
94 94
 }
95 95
 
96
-// HostDirectory represents bare host directory volume.
97
-type HostDirectory struct {
96
+// HostDir represents bare host directory volume.
97
+type HostDir struct {
98 98
 	Path string `yaml:"path" json:"path"`
99 99
 }
100 100
 
101
-type EmptyDirectory struct{}
101
+type EmptyDir struct{}
102
+
103
+// Protocol defines network protocols supported for things like conatiner ports.
104
+type Protocol string
105
+
106
+const (
107
+	// ProtocolTCP is the TCP protocol.
108
+	ProtocolTCP Protocol = "TCP"
109
+	// ProtocolUDP is the UDP protocol.
110
+	ProtocolUDP Protocol = "UDP"
111
+)
102 112
 
103 113
 // Port represents a network port in a single container.
104 114
 type Port struct {
... ...
@@ -109,8 +119,8 @@ type Port struct {
109 109
 	HostPort int `yaml:"hostPort,omitempty" json:"hostPort,omitempty"`
110 110
 	// Required: This must be a valid port number, 0 < x < 65536.
111 111
 	ContainerPort int `yaml:"containerPort" json:"containerPort"`
112
-	// Optional: Supports "TCP" and "UDP".  Defaults to "TCP".
113
-	Protocol string `yaml:"protocol,omitempty" json:"protocol,omitempty"`
112
+	// Optional: Defaults to "TCP".
113
+	Protocol Protocol `yaml:"protocol,omitempty" json:"protocol,omitempty"`
114 114
 	// Optional: What host IP to bind the external port to.
115 115
 	HostIP string `yaml:"hostIP,omitempty" json:"hostIP,omitempty"`
116 116
 }
... ...
@@ -171,8 +181,6 @@ type ExecAction struct {
171 171
 // LivenessProbe describes a liveness probe to be examined to the container.
172 172
 // TODO: pass structured data to the actions, and document that data here.
173 173
 type LivenessProbe struct {
174
-	// Type of liveness probe.  Current legal values "http", "tcp"
175
-	Type string `yaml:"type,omitempty" json:"type,omitempty"`
176 174
 	// HTTPGetProbe parameters, required if Type == 'http'
177 175
 	HTTPGet *HTTPGetAction `yaml:"httpGet,omitempty" json:"httpGet,omitempty"`
178 176
 	// TCPSocketProbe parameter, required if Type == 'tcp'
... ...
@@ -183,6 +191,18 @@ type LivenessProbe struct {
183 183
 	InitialDelaySeconds int64 `yaml:"initialDelaySeconds,omitempty" json:"initialDelaySeconds,omitempty"`
184 184
 }
185 185
 
186
+// PullPolicy describes a policy for if/when to pull a container image
187
+type PullPolicy string
188
+
189
+const (
190
+	// Always attempt to pull the latest image.  Container will fail If the pull fails.
191
+	PullAlways PullPolicy = "PullAlways"
192
+	// Never pull an image, only use a local image.  Container will fail if the image isn't present
193
+	PullNever PullPolicy = "PullNever"
194
+	// Pull if the image isn't present on disk. Container will fail if the image isn't present and the pull fails.
195
+	PullIfNotPresent PullPolicy = "PullIfNotPresent"
196
+)
197
+
186 198
 // Container represents a single container that is expected to be run on the host.
187 199
 type Container struct {
188 200
 	// Required: This must be a DNS_LABEL.  Each container in a pod must
... ...
@@ -205,6 +225,8 @@ type Container struct {
205 205
 	Lifecycle     *Lifecycle     `yaml:"lifecycle,omitempty" json:"lifecycle,omitempty"`
206 206
 	// Optional: Default to false.
207 207
 	Privileged bool `json:"privileged,omitempty" yaml:"privileged,omitempty"`
208
+	// Optional: Policy for pulling images for this container
209
+	ImagePullPolicy PullPolicy `json:"imagePullPolicy" yaml:"imagePullPolicy"`
208 210
 }
209 211
 
210 212
 // Handler defines a specific action that should be taken
... ...
@@ -230,14 +252,6 @@ type Lifecycle struct {
230 230
 	PreStop *Handler `yaml:"preStop,omitempty" json:"preStop,omitempty"`
231 231
 }
232 232
 
233
-// Event is the representation of an event logged to etcd backends.
234
-type Event struct {
235
-	Event     string             `json:"event,omitempty"`
236
-	Manifest  *ContainerManifest `json:"manifest,omitempty"`
237
-	Container *Container         `json:"container,omitempty"`
238
-	Timestamp int64              `json:"timestamp"`
239
-}
240
-
241 233
 // The below types are used by kube_client and api_server.
242 234
 
243 235
 // JSONBase is shared by all objects sent to, or returned from the client.
... ...
@@ -248,6 +262,7 @@ type JSONBase struct {
248 248
 	SelfLink          string    `json:"selfLink,omitempty" yaml:"selfLink,omitempty"`
249 249
 	ResourceVersion   uint64    `json:"resourceVersion,omitempty" yaml:"resourceVersion,omitempty"`
250 250
 	APIVersion        string    `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
251
+	Namespace         string    `json:"namespace",omitempty" yaml:"namespace,omitempty"`
251 252
 }
252 253
 
253 254
 func (*JSONBase) IsAnAPIObject() {}
... ...
@@ -301,7 +316,7 @@ type ContainerStatus struct {
301 301
 }
302 302
 
303 303
 // PodInfo contains one entry for every container with available info.
304
-type PodInfo map[string]docker.Container
304
+type PodInfo map[string]ContainerStatus
305 305
 
306 306
 type RestartPolicyAlways struct{}
307 307
 
... ...
@@ -402,8 +417,8 @@ type Service struct {
402 402
 
403 403
 	// Required.
404 404
 	Port int `json:"port" yaml:"port"`
405
-	// Optional: Supports "TCP" and "UDP".  Defaults to "TCP".
406
-	Protocol string `yaml:"protocol,omitempty" json:"protocol,omitempty"`
405
+	// Optional: Defaults to "TCP".
406
+	Protocol Protocol `yaml:"protocol,omitempty" json:"protocol,omitempty"`
407 407
 
408 408
 	// This service's labels.
409 409
 	Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
... ...
@@ -436,12 +451,25 @@ type EndpointsList struct {
436 436
 
437 437
 func (*EndpointsList) IsAnAPIObject() {}
438 438
 
439
+// NodeResources represents resources on a Kubernetes system node
440
+// see https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/resources.md for more details.
441
+type NodeResources struct {
442
+	// Capacity represents the available resources.
443
+	Capacity ResourceList `json:"capacity,omitempty" yaml:"capacity,omitempty"`
444
+}
445
+
446
+type ResourceName string
447
+
448
+type ResourceList map[ResourceName]util.IntOrString
449
+
439 450
 // Minion is a worker node in Kubernetenes.
440 451
 // The name of the minion according to etcd is in JSONBase.ID.
441 452
 type Minion struct {
442 453
 	JSONBase `json:",inline" yaml:",inline"`
443 454
 	// Queried from cloud provider, if available.
444 455
 	HostIP string `json:"hostIP,omitempty" yaml:"hostIP,omitempty"`
456
+	// Resources available on the node
457
+	NodeResources NodeResources `json:"resources,omitempty" yaml:"resources,omitempty"`
445 458
 }
446 459
 
447 460
 func (*Minion) IsAnAPIObject() {}
... ...
@@ -471,12 +499,12 @@ func (*Binding) IsAnAPIObject() {}
471 471
 // import both.
472 472
 type Status struct {
473 473
 	JSONBase `json:",inline" yaml:",inline"`
474
-	// One of: "success", "failure", "working" (for operations not yet completed)
474
+	// One of: "Success", "Failure", "Working" (for operations not yet completed)
475 475
 	Status string `json:"status,omitempty" yaml:"status,omitempty"`
476 476
 	// A human-readable description of the status of this operation.
477 477
 	Message string `json:"message,omitempty" yaml:"message,omitempty"`
478 478
 	// A machine-readable description of why this operation is in the
479
-	// "failure" or "working" status. If this value is empty there
479
+	// "Failure" or "Working" status. If this value is empty there
480 480
 	// is no information available. A Reason clarifies an HTTP status
481 481
 	// code but does not override it.
482 482
 	Reason StatusReason `json:"reason,omitempty" yaml:"reason,omitempty"`
... ...
@@ -511,9 +539,9 @@ type StatusDetails struct {
511 511
 
512 512
 // Values of Status.Status
513 513
 const (
514
-	StatusSuccess = "success"
515
-	StatusFailure = "failure"
516
-	StatusWorking = "working"
514
+	StatusSuccess = "Success"
515
+	StatusFailure = "Failure"
516
+	StatusWorking = "Working"
517 517
 )
518 518
 
519 519
 // StatusReason is an enumeration of possible failure causes.  Each StatusReason
... ...
@@ -538,7 +566,7 @@ const (
538 538
 	//   "Location" - HTTP header populated with a URL that can retrieved the final
539 539
 	//                status of this operation.
540 540
 	// Status code 202
541
-	StatusReasonWorking StatusReason = "working"
541
+	StatusReasonWorking StatusReason = "Working"
542 542
 
543 543
 	// StatusReasonNotFound means one or more resources required for this operation
544 544
 	// could not be found.
... ...
@@ -548,21 +576,21 @@ const (
548 548
 	//                   resource.
549 549
 	//   "id"   string - the identifier of the missing resource
550 550
 	// Status code 404
551
-	StatusReasonNotFound StatusReason = "notFound"
551
+	StatusReasonNotFound StatusReason = "NotFound"
552 552
 
553 553
 	// StatusReasonAlreadyExists means the resource you are creating already exists.
554 554
 	// Details (optional):
555 555
 	//   "kind" string - the kind attribute of the conflicting resource
556 556
 	//   "id"   string - the identifier of the conflicting resource
557 557
 	// Status code 409
558
-	StatusReasonAlreadyExists StatusReason = "alreadyExists"
558
+	StatusReasonAlreadyExists StatusReason = "AlreadyExists"
559 559
 
560 560
 	// StatusReasonConflict means the requested update operation cannot be completed
561 561
 	// due to a conflict in the operation. The client may need to alter the request.
562 562
 	// Each resource may define custom details that indicate the nature of the
563 563
 	// conflict.
564 564
 	// Status code 409
565
-	StatusReasonConflict StatusReason = "conflict"
565
+	StatusReasonConflict StatusReason = "Conflict"
566 566
 )
567 567
 
568 568
 // StatusCause provides more information about an api.Status failure, including
... ...
@@ -588,25 +616,25 @@ type StatusCause struct {
588 588
 
589 589
 // CauseType is a machine readable value providing more detail about what
590 590
 // occured in a status response. An operation may have multiple causes for a
591
-// status (whether failure, success, or working).
591
+// status (whether Failure, Success, or Working).
592 592
 type CauseType string
593 593
 
594 594
 const (
595 595
 	// CauseTypeFieldValueNotFound is used to report failure to find a requested value
596 596
 	// (e.g. looking up an ID).
597
-	CauseTypeFieldValueNotFound CauseType = "fieldValueNotFound"
597
+	CauseTypeFieldValueNotFound CauseType = "FieldValueNotFound"
598 598
 	// CauseTypeFieldValueInvalid is used to report required values that are not
599 599
 	// provided (e.g. empty strings, null values, or empty arrays).
600
-	CauseTypeFieldValueRequired CauseType = "fieldValueRequired"
600
+	CauseTypeFieldValueRequired CauseType = "FieldValueRequired"
601 601
 	// CauseTypeFieldValueDuplicate is used to report collisions of values that must be
602 602
 	// unique (e.g. unique IDs).
603
-	CauseTypeFieldValueDuplicate CauseType = "fieldValueDuplicate"
603
+	CauseTypeFieldValueDuplicate CauseType = "FieldValueDuplicate"
604 604
 	// CauseTypeFieldValueInvalid is used to report malformed values (e.g. failed regex
605 605
 	// match).
606
-	CauseTypeFieldValueInvalid CauseType = "fieldValueInvalid"
606
+	CauseTypeFieldValueInvalid CauseType = "FieldValueInvalid"
607 607
 	// CauseTypeFieldValueNotSupported is used to report valid (as per formatting rules)
608 608
 	// values that can not be handled (e.g. an enumerated string).
609
-	CauseTypeFieldValueNotSupported CauseType = "fieldValueNotSupported"
609
+	CauseTypeFieldValueNotSupported CauseType = "FieldValueNotSupported"
610 610
 )
611 611
 
612 612
 // ServerOp is an operation delivered to API clients.
... ...
@@ -623,3 +651,63 @@ type ServerOpList struct {
623 623
 }
624 624
 
625 625
 func (*ServerOpList) IsAnAPIObject() {}
626
+
627
+// ObjectReference contains enough information to let you inspect or modify the referred object.
628
+type ObjectReference struct {
629
+	Kind            string `json:"kind,omitempty" yaml:"kind,omitempty"`
630
+	Name            string `json:"name,omitempty" yaml:"name,omitempty"`
631
+	UID             string `json:"uid,omitempty" yaml:"uid,omitempty"`
632
+	APIVersion      string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
633
+	ResourceVersion uint64 `json:"resourceVersion,omitempty" yaml:"resourceVersion,omitempty"`
634
+
635
+	// Optional. If referring to a piece of an object instead of an entire object, this string
636
+	// should contain a valid field access statement. For example,
637
+	// if the object reference is to a container within a pod, this would take on a value like:
638
+	// "desiredState.manifest.containers[2]". Such statements are valid language constructs in
639
+	// both go and JavaScript. This is syntax is chosen only to have some well-defined way of
640
+	// referencing a part of an object.
641
+	// TODO: this design is not final and this field is subject to change in the future.
642
+	FieldPath string `json:"fieldPath,omitempty" yaml:"fieldPath,omitempty"`
643
+}
644
+
645
+// Event is a report of an event somewhere in the cluster.
646
+// TODO: Decide whether to store these separately or with the object they apply to.
647
+type Event struct {
648
+	JSONBase `yaml:",inline" json:",inline"`
649
+
650
+	// Required. The object that this event is about.
651
+	InvolvedObject ObjectReference `json:"involvedObject,omitempty" yaml:"involvedObject,omitempty"`
652
+
653
+	// Should be a short, machine understandable string that describes the current status
654
+	// of the referred object. This should not give the reason for being in this state.
655
+	// Examples: "running", "cantStart", "cantSchedule", "deleted".
656
+	// It's OK for components to make up statuses to report here, but the same string should
657
+	// always be used for the same status.
658
+	// TODO: define a way of making sure these are consistent and don't collide.
659
+	// TODO: provide exact specification for format.
660
+	Status string `json:"status,omitempty" yaml:"status,omitempty"`
661
+
662
+	// Optional; this should be a short, machine understandable string that gives the reason
663
+	// for the transition into the object's current status. For example, if ObjectStatus is
664
+	// "cantStart", StatusReason might be "imageNotFound".
665
+	// TODO: provide exact specification for format.
666
+	Reason string `json:"reason,omitempty" yaml:"reason,omitempty"`
667
+
668
+	// Optional. A human-readable description of the status of this operation.
669
+	// TODO: decide on maximum length.
670
+	Message string `json:"message,omitempty" yaml:"message,omitempty"`
671
+
672
+	// Optional. The component reporting this event. Should be a short machine understandable string.
673
+	// TODO: provide exact specification for format.
674
+	Source string `json:"source,omitempty" yaml:"source,omitempty"`
675
+}
676
+
677
+func (*Event) IsAnAPIObject() {}
678
+
679
+// EventList is a list of events.
680
+type EventList struct {
681
+	JSONBase `yaml:",inline" json:",inline"`
682
+	Items    []Event `yaml:"items,omitempty" json:"items,omitempty"`
683
+}
684
+
685
+func (*EventList) IsAnAPIObject() {}
... ...
@@ -41,5 +41,7 @@ func init() {
41 41
 		&Endpoints{},
42 42
 		&EndpointsList{},
43 43
 		&Binding{},
44
+		&Event{},
45
+		&EventList{},
44 46
 	)
45 47
 }
... ...
@@ -83,22 +83,32 @@ type Volume struct {
83 83
 
84 84
 type VolumeSource struct {
85 85
 	// Only one of the following sources may be specified
86
-	// HostDirectory represents a pre-existing directory on the host machine that is directly
86
+	// HostDir represents a pre-existing directory on the host machine that is directly
87 87
 	// exposed to the container. This is generally used for system agents or other privileged
88 88
 	// things that are allowed to see the host machine. Most containers will NOT need this.
89 89
 	// TODO(jonesdl) We need to restrict who can use host directory mounts and
90 90
 	// who can/can not mount host directories as read/write.
91
-	HostDirectory *HostDirectory `yaml:"hostDir" json:"hostDir"`
92
-	// EmptyDirectory represents a temporary directory that shares a pod's lifetime.
93
-	EmptyDirectory *EmptyDirectory `yaml:"emptyDir" json:"emptyDir"`
91
+	HostDir *HostDir `yaml:"hostDir" json:"hostDir"`
92
+	// EmptyDir represents a temporary directory that shares a pod's lifetime.
93
+	EmptyDir *EmptyDir `yaml:"emptyDir" json:"emptyDir"`
94 94
 }
95 95
 
96
-// HostDirectory represents bare host directory volume.
97
-type HostDirectory struct {
96
+// HostDir represents bare host directory volume.
97
+type HostDir struct {
98 98
 	Path string `yaml:"path" json:"path"`
99 99
 }
100 100
 
101
-type EmptyDirectory struct{}
101
+type EmptyDir struct{}
102
+
103
+// Protocol defines network protocols supported for things like conatiner ports.
104
+type Protocol string
105
+
106
+const (
107
+	// ProtocolTCP is the TCP protocol.
108
+	ProtocolTCP Protocol = "TCP"
109
+	// ProtocolUDP is the UDP protocol.
110
+	ProtocolUDP Protocol = "UDP"
111
+)
102 112
 
103 113
 // Port represents a network port in a single container.
104 114
 type Port struct {
... ...
@@ -109,8 +119,8 @@ type Port struct {
109 109
 	HostPort int `yaml:"hostPort,omitempty" json:"hostPort,omitempty"`
110 110
 	// Required: This must be a valid port number, 0 < x < 65536.
111 111
 	ContainerPort int `yaml:"containerPort" json:"containerPort"`
112
-	// Optional: Supports "TCP" and "UDP".  Defaults to "TCP".
113
-	Protocol string `yaml:"protocol,omitempty" json:"protocol,omitempty"`
112
+	// Optional: Defaults to "TCP".
113
+	Protocol Protocol `yaml:"protocol,omitempty" json:"protocol,omitempty"`
114 114
 	// Optional: What host IP to bind the external port to.
115 115
 	HostIP string `yaml:"hostIP,omitempty" json:"hostIP,omitempty"`
116 116
 }
... ...
@@ -170,8 +180,6 @@ type ExecAction struct {
170 170
 // LivenessProbe describes a liveness probe to be examined to the container.
171 171
 // TODO: pass structured data to the actions, and document that data here.
172 172
 type LivenessProbe struct {
173
-	// Type of liveness probe.  Current legal values "http", "tcp"
174
-	Type string `yaml:"type,omitempty" json:"type,omitempty"`
175 173
 	// HTTPGetProbe parameters, required if Type == 'http'
176 174
 	HTTPGet *HTTPGetAction `yaml:"httpGet,omitempty" json:"httpGet,omitempty"`
177 175
 	// TCPSocketProbe parameter, required if Type == 'tcp'
... ...
@@ -182,6 +190,18 @@ type LivenessProbe struct {
182 182
 	InitialDelaySeconds int64 `yaml:"initialDelaySeconds,omitempty" json:"initialDelaySeconds,omitempty"`
183 183
 }
184 184
 
185
+// PullPolicy describes a policy for if/when to pull a container image
186
+type PullPolicy string
187
+
188
+const (
189
+	// Always attempt to pull the latest image.  Container will fail If the pull fails.
190
+	PullAlways PullPolicy = "PullAlways"
191
+	// Never pull an image, only use a local image.  Container will fail if the image isn't present
192
+	PullNever PullPolicy = "PullNever"
193
+	// Pull if the image isn't present on disk. Container will fail if the image isn't present and the pull fails.
194
+	PullIfNotPresent PullPolicy = "PullIfNotPresent"
195
+)
196
+
185 197
 // Container represents a single container that is expected to be run on the host.
186 198
 type Container struct {
187 199
 	// Required: This must be a DNS_LABEL.  Each container in a pod must
... ...
@@ -204,6 +224,8 @@ type Container struct {
204 204
 	Lifecycle     *Lifecycle     `yaml:"lifecycle,omitempty" json:"lifecycle,omitempty"`
205 205
 	// Optional: Default to false.
206 206
 	Privileged bool `json:"privileged,omitempty" yaml:"privileged,omitempty"`
207
+	// Optional: Policy for pulling images for this container
208
+	ImagePullPolicy PullPolicy `json:"imagePullPolicy" yaml:"imagePullPolicy"`
207 209
 }
208 210
 
209 211
 // Handler defines a specific action that should be taken
... ...
@@ -228,14 +250,6 @@ type Lifecycle struct {
228 228
 	PreStop *Handler `yaml:"preStop,omitempty" json:"preStop,omitempty"`
229 229
 }
230 230
 
231
-// Event is the representation of an event logged to etcd backends.
232
-type Event struct {
233
-	Event     string             `json:"event,omitempty"`
234
-	Manifest  *ContainerManifest `json:"manifest,omitempty"`
235
-	Container *Container         `json:"container,omitempty"`
236
-	Timestamp int64              `json:"timestamp"`
237
-}
238
-
239 231
 // The below types are used by kube_client and api_server.
240 232
 
241 233
 // JSONBase is shared by all objects sent to, or returned from the client.
... ...
@@ -246,6 +260,7 @@ type JSONBase struct {
246 246
 	SelfLink          string    `json:"selfLink,omitempty" yaml:"selfLink,omitempty"`
247 247
 	ResourceVersion   uint64    `json:"resourceVersion,omitempty" yaml:"resourceVersion,omitempty"`
248 248
 	APIVersion        string    `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
249
+	Namespace         string    `json:"namespace",omitempty" yaml:"namespace,omitempty"`
249 250
 }
250 251
 
251 252
 // PodStatus represents a status of a pod.
... ...
@@ -298,7 +313,7 @@ type ContainerStatus struct {
298 298
 
299 299
 // PodInfo contains one entry for every container with available info.
300 300
 // TODO(dchen1107): Replace docker.Container below with ContainerStatus defined above.
301
-type PodInfo map[string]docker.Container
301
+type PodInfo map[string]ContainerStatus
302 302
 
303 303
 type RestartPolicyAlways struct{}
304 304
 
... ...
@@ -399,8 +414,8 @@ type Service struct {
399 399
 
400 400
 	// Required.
401 401
 	Port int `json:"port" yaml:"port"`
402
-	// Optional: Supports "TCP" and "UDP".  Defaults to "TCP".
403
-	Protocol string `yaml:"protocol,omitempty" json:"protocol,omitempty"`
402
+	// Optional: Defaults to "TCP".
403
+	Protocol Protocol `yaml:"protocol,omitempty" json:"protocol,omitempty"`
404 404
 
405 405
 	// This service's labels.
406 406
 	Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
... ...
@@ -433,12 +448,25 @@ type EndpointsList struct {
433 433
 
434 434
 func (*EndpointsList) IsAnAPIObject() {}
435 435
 
436
+// NodeResources represents resources on a Kubernetes system node
437
+// see https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/resources.md for more details.
438
+type NodeResources struct {
439
+	// Capacity represents the available resources.
440
+	Capacity ResourceList `json:"capacity,omitempty" yaml:"capacity,omitempty"`
441
+}
442
+
443
+type ResourceName string
444
+
445
+type ResourceList map[ResourceName]util.IntOrString
446
+
436 447
 // Minion is a worker node in Kubernetenes.
437 448
 // The name of the minion according to etcd is in JSONBase.ID.
438 449
 type Minion struct {
439 450
 	JSONBase `json:",inline" yaml:",inline"`
440 451
 	// Queried from cloud provider, if available.
441 452
 	HostIP string `json:"hostIP,omitempty" yaml:"hostIP,omitempty"`
453
+	// Resources available on the node
454
+	NodeResources NodeResources `json:"resources,omitempty" yaml:"resources,omitempty"`
442 455
 }
443 456
 
444 457
 func (*Minion) IsAnAPIObject() {}
... ...
@@ -468,12 +496,12 @@ func (*Binding) IsAnAPIObject() {}
468 468
 // import both.
469 469
 type Status struct {
470 470
 	JSONBase `json:",inline" yaml:",inline"`
471
-	// One of: "success", "failure", "working" (for operations not yet completed)
471
+	// One of: "Success", "Failure", "Working" (for operations not yet completed)
472 472
 	Status string `json:"status,omitempty" yaml:"status,omitempty"`
473 473
 	// A human-readable description of the status of this operation.
474 474
 	Message string `json:"message,omitempty" yaml:"message,omitempty"`
475 475
 	// A machine-readable description of why this operation is in the
476
-	// "failure" or "working" status. If this value is empty there
476
+	// "Failure" or "Working" status. If this value is empty there
477 477
 	// is no information available. A Reason clarifies an HTTP status
478 478
 	// code but does not override it.
479 479
 	Reason StatusReason `json:"reason,omitempty" yaml:"reason,omitempty"`
... ...
@@ -508,9 +536,9 @@ type StatusDetails struct {
508 508
 
509 509
 // Values of Status.Status
510 510
 const (
511
-	StatusSuccess = "success"
512
-	StatusFailure = "failure"
513
-	StatusWorking = "working"
511
+	StatusSuccess = "Success"
512
+	StatusFailure = "Failure"
513
+	StatusWorking = "Working"
514 514
 )
515 515
 
516 516
 // StatusReason is an enumeration of possible failure causes.  Each StatusReason
... ...
@@ -535,7 +563,7 @@ const (
535 535
 	//   "Location" - HTTP header populated with a URL that can retrieved the final
536 536
 	//                status of this operation.
537 537
 	// Status code 202
538
-	StatusReasonWorking StatusReason = "working"
538
+	StatusReasonWorking StatusReason = "Working"
539 539
 
540 540
 	// StatusReasonNotFound means one or more resources required for this operation
541 541
 	// could not be found.
... ...
@@ -545,21 +573,21 @@ const (
545 545
 	//                   resource.
546 546
 	//   "id"   string - the identifier of the missing resource
547 547
 	// Status code 404
548
-	StatusReasonNotFound StatusReason = "not_found"
548
+	StatusReasonNotFound StatusReason = "NotFound"
549 549
 
550 550
 	// StatusReasonAlreadyExists means the resource you are creating already exists.
551 551
 	// Details (optional):
552 552
 	//   "kind" string - the kind attribute of the conflicting resource
553 553
 	//   "id"   string - the identifier of the conflicting resource
554 554
 	// Status code 409
555
-	StatusReasonAlreadyExists StatusReason = "already_exists"
555
+	StatusReasonAlreadyExists StatusReason = "AlreadyExists"
556 556
 
557 557
 	// StatusReasonConflict means the requested update operation cannot be completed
558 558
 	// due to a conflict in the operation. The client may need to alter the request.
559 559
 	// Each resource may define custom details that indicate the nature of the
560 560
 	// conflict.
561 561
 	// Status code 409
562
-	StatusReasonConflict StatusReason = "conflict"
562
+	StatusReasonConflict StatusReason = "Conflict"
563 563
 
564 564
 	// StatusReasonInvalid means the requested create or update operation cannot be
565 565
 	// completed due to invalid data provided as part of the request. The client may
... ...
@@ -572,7 +600,7 @@ const (
572 572
 	//                   provided resource that was invalid.  The code, message, and
573 573
 	//                   field attributes will be set.
574 574
 	// Status code 422
575
-	StatusReasonInvalid StatusReason = "invalid"
575
+	StatusReasonInvalid StatusReason = "Invalid"
576 576
 )
577 577
 
578 578
 // StatusCause provides more information about an api.Status failure, including
... ...
@@ -598,25 +626,25 @@ type StatusCause struct {
598 598
 
599 599
 // CauseType is a machine readable value providing more detail about what
600 600
 // occured in a status response. An operation may have multiple causes for a
601
-// status (whether failure, success, or working).
601
+// status (whether Failure, Success, or Working).
602 602
 type CauseType string
603 603
 
604 604
 const (
605 605
 	// CauseTypeFieldValueNotFound is used to report failure to find a requested value
606 606
 	// (e.g. looking up an ID).
607
-	CauseTypeFieldValueNotFound CauseType = "fieldValueNotFound"
607
+	CauseTypeFieldValueNotFound CauseType = "FieldValueNotFound"
608 608
 	// CauseTypeFieldValueInvalid is used to report required values that are not
609 609
 	// provided (e.g. empty strings, null values, or empty arrays).
610
-	CauseTypeFieldValueRequired CauseType = "fieldValueRequired"
610
+	CauseTypeFieldValueRequired CauseType = "FieldValueRequired"
611 611
 	// CauseTypeFieldValueDuplicate is used to report collisions of values that must be
612 612
 	// unique (e.g. unique IDs).
613
-	CauseTypeFieldValueDuplicate CauseType = "fieldValueDuplicate"
613
+	CauseTypeFieldValueDuplicate CauseType = "FieldValueDuplicate"
614 614
 	// CauseTypeFieldValueInvalid is used to report malformed values (e.g. failed regex
615 615
 	// match).
616
-	CauseTypeFieldValueInvalid CauseType = "fieldValueInvalid"
616
+	CauseTypeFieldValueInvalid CauseType = "FieldValueInvalid"
617 617
 	// CauseTypeFieldValueNotSupported is used to report valid (as per formatting rules)
618 618
 	// values that can not be handled (e.g. an enumerated string).
619
-	CauseTypeFieldValueNotSupported CauseType = "fieldValueNotSupported"
619
+	CauseTypeFieldValueNotSupported CauseType = "FieldValueNotSupported"
620 620
 )
621 621
 
622 622
 // ServerOp is an operation delivered to API clients.
... ...
@@ -633,3 +661,63 @@ type ServerOpList struct {
633 633
 }
634 634
 
635 635
 func (*ServerOpList) IsAnAPIObject() {}
636
+
637
+// ObjectReference contains enough information to let you inspect or modify the referred object.
638
+type ObjectReference struct {
639
+	Kind            string `json:"kind,omitempty" yaml:"kind,omitempty"`
640
+	Name            string `json:"name,omitempty" yaml:"name,omitempty"`
641
+	UID             string `json:"uid,omitempty" yaml:"uid,omitempty"`
642
+	APIVersion      string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
643
+	ResourceVersion uint64 `json:"resourceVersion,omitempty" yaml:"resourceVersion,omitempty"`
644
+
645
+	// Optional. If referring to a piece of an object instead of an entire object, this string
646
+	// should contain a valid field access statement. For example,
647
+	// if the object reference is to a container within a pod, this would take on a value like:
648
+	// "desiredState.manifest.containers[2]". Such statements are valid language constructs in
649
+	// both go and JavaScript. This is syntax is chosen only to have some well-defined way of
650
+	// referencing a part of an object.
651
+	// TODO: this design is not final and this field is subject to change in the future.
652
+	FieldPath string `json:"fieldPath,omitempty" yaml:"fieldPath,omitempty"`
653
+}
654
+
655
+// Event is a report of an event somewhere in the cluster.
656
+// TODO: Decide whether to store these separately or with the object they apply to.
657
+type Event struct {
658
+	JSONBase `yaml:",inline" json:",inline"`
659
+
660
+	// Required. The object that this event is about.
661
+	InvolvedObject ObjectReference `json:"involvedObject,omitempty" yaml:"involvedObject,omitempty"`
662
+
663
+	// Should be a short, machine understandable string that describes the current status
664
+	// of the referred object. This should not give the reason for being in this state.
665
+	// Examples: "running", "cantStart", "cantSchedule", "deleted".
666
+	// It's OK for components to make up statuses to report here, but the same string should
667
+	// always be used for the same status.
668
+	// TODO: define a way of making sure these are consistent and don't collide.
669
+	// TODO: provide exact specification for format.
670
+	Status string `json:"status,omitempty" yaml:"status,omitempty"`
671
+
672
+	// Optional; this should be a short, machine understandable string that gives the reason
673
+	// for the transition into the object's current status. For example, if ObjectStatus is
674
+	// "cantStart", StatusReason might be "imageNotFound".
675
+	// TODO: provide exact specification for format.
676
+	Reason string `json:"reason,omitempty" yaml:"reason,omitempty"`
677
+
678
+	// Optional. A human-readable description of the status of this operation.
679
+	// TODO: decide on maximum length.
680
+	Message string `json:"message,omitempty" yaml:"message,omitempty"`
681
+
682
+	// Optional. The component reporting this event. Should be a short machine understandable string.
683
+	// TODO: provide exact specification for format.
684
+	Source string `json:"source,omitempty" yaml:"source,omitempty"`
685
+}
686
+
687
+func (*Event) IsAnAPIObject() {}
688
+
689
+// EventList is a list of events.
690
+type EventList struct {
691
+	JSONBase `yaml:",inline" json:",inline"`
692
+	Items    []Event `yaml:"items,omitempty" json:"items,omitempty"`
693
+}
694
+
695
+func (*EventList) IsAnAPIObject() {}
... ...
@@ -26,7 +26,7 @@ import (
26 26
 // Many fields in this API have formatting requirements.  The commonly used
27 27
 // formats are defined here.
28 28
 //
29
-// C_IDENTIFIER:  This is a string that conforms the definition of an "identifier"
29
+// C_IDENTIFIER:  This is a string that conforms to the definition of an "identifier"
30 30
 //     in the C language.  This is captured by the following regex:
31 31
 //         [A-Za-z_][A-Za-z0-9_]*
32 32
 //     This defines the format, but not the length restriction, which should be
... ...
@@ -44,109 +44,162 @@ import (
44 44
 //     or more simply:
45 45
 //         DNS_LABEL(\.DNS_LABEL)*
46 46
 
47
-// ContainerManifest corresponds to the Container Manifest format, documented at:
48
-// https://developers.google.com/compute/docs/containers/container_vms#container_manifest
49
-// This is used as the representation of Kubernetes workloads.
50
-type ContainerManifest struct {
51
-	// Required: This must be a supported version string, such as "v1beta1".
52
-	Version string `yaml:"version" json:"version"`
53
-	// Required: This must be a DNS_SUBDOMAIN.
54
-	// TODO: ID on Manifest is deprecated and will be removed in the future.
55
-	ID string `yaml:"id" json:"id"`
56
-	// TODO: UUID on Manifest is deprecated in the future once we are done
57
-	// with the API refactoring. It is required for now to determine the instance
58
-	// of a Pod.
59
-	UUID          string        `yaml:"uuid,omitempty" json:"uuid,omitempty"`
60
-	Volumes       []Volume      `yaml:"volumes" json:"volumes"`
61
-	Containers    []Container   `yaml:"containers" json:"containers"`
62
-	RestartPolicy RestartPolicy `json:"restartPolicy,omitempty" yaml:"restartPolicy,omitempty"`
63
-}
47
+// TypeMeta describes an individual object in an API response or request
48
+// with strings representing the type of the object and its API schema version.
49
+// Structures that are versioned or persisted should inline TypeMeta.
50
+type TypeMeta struct {
51
+	// Kind is a string value representing the REST resource this object represents.
52
+	// Servers may infer this from the endpoint the client submits requests to.
53
+	Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
64 54
 
65
-// ContainerManifestList is used to communicate container manifests to kubelet.
66
-type ContainerManifestList struct {
67
-	JSONBase `json:",inline" yaml:",inline"`
68
-	Items    []ContainerManifest `json:"items,omitempty" yaml:"items,omitempty"`
69
-}
55
+	// APIVersion defines the versioned schema of this representation of an object.
56
+	// Servers should convert recognized schemas to the latest internal value, and
57
+	// may reject unrecognized values.
58
+	APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
59
+}
60
+
61
+// ListMeta describes metadata that synthetic resources must have, including lists and
62
+// various status objects.
63
+type ListMeta struct {
64
+	// TODO: SelfLink
65
+
66
+	// An opaque value that represents the version of this response for use with optimistic
67
+	// concurrency and change monitoring endpoints.  Clients must treat these values as opaque
68
+	// and values may only be valid for a particular resource or set of resources. Only servers
69
+	// will generate resource versions.
70
+	ResourceVersion string `json:"resourceVersion,omitempty" yaml:"resourceVersion,omitempty"`
71
+}
72
+
73
+// ObjectMeta is metadata that all persisted resources must have, which includes all objects
74
+// users must create.
75
+type ObjectMeta struct {
76
+	// Name is unique within a namespace.  Name is required when creating resources, although
77
+	// some resources may allow a client to request the generation of an appropriate name
78
+	// automatically. Name is primarily intended for creation idempotence and configuration
79
+	// definition.
80
+	Name string `json:"name,omitempty" yaml:"name,omitempty"`
81
+
82
+	// Namespace defines the space within which name must be unique. An empty namespace is
83
+	// equivalent to the "default" namespace, but "default" is the canonical representation.
84
+	// Not all objects are required to be scoped to a namespace - the value of this field for
85
+	// those objects will be empty.
86
+	Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
87
+
88
+	// TODO: SelfLink
89
+
90
+	// UID is the unique in time and space value for this object. It is typically generated by
91
+	// the server on successful creation of a resource and is not allowed to change on PUT
92
+	// operations.
93
+	UID string `json:"uid,omitempty" yaml:"uid,omitempty"`
94
+
95
+	// An opaque value that represents the version of this resource. May be used for optimistic
96
+	// concurrency, change detection, and the watch operation on a resource or set of resources.
97
+	// Clients must treat these values as opaque and values may only be valid for a particular
98
+	// resource or set of resources. Only servers will generate resource versions.
99
+	ResourceVersion string `json:"resourceVersion,omitempty" yaml:"resourceVersion,omitempty"`
100
+
101
+	// CreationTimestamp is a timestamp representing the server time when this object was
102
+	// created. It is not guaranteed to be set in happens-before order across separate operations.
103
+	// Clients may not set this value. It is represented in RFC3339 form and is in UTC.
104
+	CreationTimestamp util.Time `json:"creationTimestamp,omitempty" yaml:"creationTimestamp,omitempty"`
105
+
106
+	// Labels are key value pairs that may be used to scope and select individual resources.
107
+	// TODO: replace map[string]string with labels.LabelSet type
108
+	Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
70 109
 
71
-func (*ContainerManifestList) IsAnAPIObject() {}
110
+	// Annotations are unstructured key value data stored with a resource that may be set by
111
+	// external tooling. They are not queryable and should be preserved when modifying
112
+	// objects.
113
+	Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"`
114
+}
72 115
 
73 116
 // Volume represents a named volume in a pod that may be accessed by any containers in the pod.
74 117
 type Volume struct {
75 118
 	// Required: This must be a DNS_LABEL.  Each volume in a pod must have
76 119
 	// a unique name.
77
-	Name string `yaml:"name" json:"name"`
120
+	Name string `json:"name" yaml:"name"`
78 121
 	// Source represents the location and type of a volume to mount.
79 122
 	// This is optional for now. If not specified, the Volume is implied to be an EmptyDir.
80 123
 	// This implied behavior is deprecated and will be removed in a future version.
81
-	Source *VolumeSource `yaml:"source" json:"source"`
124
+	Source *VolumeSource `json:"source" yaml:"source"`
82 125
 }
83 126
 
84 127
 type VolumeSource struct {
85 128
 	// Only one of the following sources may be specified
86
-	// HostDirectory represents a pre-existing directory on the host machine that is directly
129
+	// HostDir represents a pre-existing directory on the host machine that is directly
87 130
 	// exposed to the container. This is generally used for system agents or other privileged
88 131
 	// things that are allowed to see the host machine. Most containers will NOT need this.
89
-	// TODO(jonesdl) We need to restrict who can use host directory mounts and
90
-	// who can/can not mount host directories as read/write.
91
-	HostDirectory *HostDirectory `yaml:"hostDir" json:"hostDir"`
92
-	// EmptyDirectory represents a temporary directory that shares a pod's lifetime.
93
-	EmptyDirectory *EmptyDirectory `yaml:"emptyDir" json:"emptyDir"`
132
+	// TODO(jonesdl) We need to restrict who can use host directory mounts and who can/can not
133
+	// mount host directories as read/write.
134
+	HostDir *HostDir `json:"hostDir" yaml:"hostDir"`
135
+	// EmptyDir represents a temporary directory that shares a pod's lifetime.
136
+	EmptyDir *EmptyDir `json:"emptyDir" yaml:"emptyDir"`
94 137
 }
95 138
 
96
-// HostDirectory represents bare host directory volume.
97
-type HostDirectory struct {
98
-	Path string `yaml:"path" json:"path"`
139
+// HostDir represents bare host directory volume.
140
+type HostDir struct {
141
+	Path string `json:"path" yaml:"path"`
99 142
 }
100 143
 
101
-type EmptyDirectory struct{}
144
+type EmptyDir struct{}
145
+
146
+// Protocol defines network protocols supported for things like conatiner ports.
147
+type Protocol string
148
+
149
+const (
150
+	// ProtocolTCP is the TCP protocol.
151
+	ProtocolTCP Protocol = "TCP"
152
+	// ProtocolUDP is the UDP protocol.
153
+	ProtocolUDP Protocol = "UDP"
154
+)
102 155
 
103 156
 // Port represents a network port in a single container.
104 157
 type Port struct {
105 158
 	// Optional: If specified, this must be a DNS_LABEL.  Each named port
106 159
 	// in a pod must have a unique name.
107
-	Name string `yaml:"name,omitempty" json:"name,omitempty"`
160
+	Name string `json:"name,omitempty" yaml:"name,omitempty"`
108 161
 	// Optional: If specified, this must be a valid port number, 0 < x < 65536.
109
-	HostPort int `yaml:"hostPort,omitempty" json:"hostPort,omitempty"`
162
+	HostPort int `json:"hostPort,omitempty" yaml:"hostPort,omitempty"`
110 163
 	// Required: This must be a valid port number, 0 < x < 65536.
111
-	ContainerPort int `yaml:"containerPort" json:"containerPort"`
164
+	ContainerPort int `json:"containerPort" yaml:"containerPort"`
112 165
 	// Optional: Supports "TCP" and "UDP".  Defaults to "TCP".
113
-	Protocol string `yaml:"protocol,omitempty" json:"protocol,omitempty"`
166
+	Protocol Protocol `json:"protocol,omitempty" yaml:"protocol,omitempty"`
114 167
 	// Optional: What host IP to bind the external port to.
115
-	HostIP string `yaml:"hostIP,omitempty" json:"hostIP,omitempty"`
168
+	HostIP string `json:"hostIP,omitempty" yaml:"hostIP,omitempty"`
116 169
 }
117 170
 
118 171
 // VolumeMount describes a mounting of a Volume within a container.
119 172
 type VolumeMount struct {
120 173
 	// Required: This must match the Name of a Volume [above].
121
-	Name string `yaml:"name" json:"name"`
174
+	Name string `json:"name" yaml:"name"`
122 175
 	// Optional: Defaults to false (read-write).
123
-	ReadOnly bool `yaml:"readOnly,omitempty" json:"readOnly,omitempty"`
176
+	ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"`
124 177
 	// Required.
125
-	MountPath string `yaml:"mountPath,omitempty" json:"mountPath,omitempty"`
178
+	MountPath string `json:"mountPath,omitempty" yaml:"mountPath,omitempty"`
126 179
 }
127 180
 
128 181
 // EnvVar represents an environment variable present in a Container.
129 182
 type EnvVar struct {
130 183
 	// Required: This must be a C_IDENTIFIER.
131
-	Name string `yaml:"name" json:"name"`
184
+	Name string `json:"name" yaml:"name"`
132 185
 	// Optional: defaults to "".
133
-	Value string `yaml:"value,omitempty" json:"value,omitempty"`
186
+	Value string `json:"value,omitempty" yaml:"value,omitempty"`
134 187
 }
135 188
 
136 189
 // HTTPGetAction describes an action based on HTTP Get requests.
137 190
 type HTTPGetAction struct {
138 191
 	// Optional: Path to access on the HTTP server.
139
-	Path string `yaml:"path,omitempty" json:"path,omitempty"`
192
+	Path string `json:"path,omitempty" yaml:"path,omitempty"`
140 193
 	// Required: Name or number of the port to access on the container.
141
-	Port util.IntOrString `yaml:"port,omitempty" json:"port,omitempty"`
194
+	Port util.IntOrString `json:"port,omitempty" yaml:"port,omitempty"`
142 195
 	// Optional: Host name to connect to, defaults to the pod IP.
143
-	Host string `yaml:"host,omitempty" json:"host,omitempty"`
196
+	Host string `json:"host,omitempty" yaml:"host,omitempty"`
144 197
 }
145 198
 
146 199
 // TCPSocketAction describes an action based on opening a socket
147 200
 type TCPSocketAction struct {
148 201
 	// Required: Port to connect to.
149
-	Port util.IntOrString `yaml:"port,omitempty" json:"port,omitempty"`
202
+	Port util.IntOrString `json:"port,omitempty" yaml:"port,omitempty"`
150 203
 }
151 204
 
152 205
 // ExecAction describes a "run in container" action.
... ...
@@ -155,46 +208,60 @@ type ExecAction struct {
155 155
 	// command  is root ('/') in the container's filesystem.  The command is simply exec'd, it is
156 156
 	// not run inside a shell, so traditional shell instructions ('|', etc) won't work.  To use
157 157
 	// a shell, you need to explicitly call out to that shell.
158
-	Command []string `yaml:"command,omitempty" json:"command,omitempty"`
158
+	Command []string `json:"command,omitempty" yaml:"command,omitempty"`
159 159
 }
160 160
 
161
-// LivenessProbe describes a liveness probe to be examined to the container.
161
+// LivenessProbe describes how to probe a container for liveness.
162 162
 // TODO: pass structured data to the actions, and document that data here.
163 163
 type LivenessProbe struct {
164
-	// Type of liveness probe.  Current legal values "http", "tcp"
165
-	Type string `yaml:"type,omitempty" json:"type,omitempty"`
166
-	// HTTPGetProbe parameters, required if Type == 'http'
167
-	HTTPGet *HTTPGetAction `yaml:"httpGet,omitempty" json:"httpGet,omitempty"`
168
-	// TCPSocketProbe parameter, required if Type == 'tcp'
169
-	TCPSocket *TCPSocketAction `yaml:"tcpSocket,omitempty" json:"tcpSocket,omitempty"`
170
-	// ExecProbe parameter, required if Type == 'exec'
171
-	Exec *ExecAction `yaml:"exec,omitempty" json:"exec,omitempty"`
164
+	// Type of liveness probe.  Current legal values "HTTP", "TCP"
165
+	Type string `json:"type,omitempty" yaml:"type,omitempty"`
166
+	// HTTPGetProbe parameters, required if Type == 'HTTP'
167
+	HTTPGet *HTTPGetAction `json:"httpGet,omitempty" yaml:"httpGet,omitempty"`
168
+	// TCPSocketProbe parameter, required if Type == 'TCP'
169
+	TCPSocket *TCPSocketAction `json:"tcpSocket,omitempty" yaml:"tcpSocket,omitempty"`
170
+	// ExecProbe parameter, required if Type == 'Exec'
171
+	Exec *ExecAction `json:"exec,omitempty" yaml:"exec,omitempty"`
172 172
 	// Length of time before health checking is activated.  In seconds.
173
-	InitialDelaySeconds int64 `yaml:"initialDelaySeconds,omitempty" json:"initialDelaySeconds,omitempty"`
173
+	InitialDelaySeconds int64 `json:"initialDelaySeconds,omitempty" yaml:"initialDelaySeconds,omitempty"`
174 174
 }
175 175
 
176
+// PullPolicy describes a policy for if/when to pull a container image
177
+type PullPolicy string
178
+
179
+const (
180
+	// Always attempt to pull the latest image.  Container will fail If the pull fails.
181
+	PullAlways PullPolicy = "PullAlways"
182
+	// Never pull an image, only use a local image.  Container will fail if the image isn't present
183
+	PullNever PullPolicy = "PullNever"
184
+	// Pull if the image isn't present on disk. Container will fail if the image isn't present and the pull fails.
185
+	PullIfNotPresent PullPolicy = "PullIfNotPresent"
186
+)
187
+
176 188
 // Container represents a single container that is expected to be run on the host.
177 189
 type Container struct {
178 190
 	// Required: This must be a DNS_LABEL.  Each container in a pod must
179 191
 	// have a unique name.
180
-	Name string `yaml:"name" json:"name"`
192
+	Name string `json:"name" yaml:"name"`
181 193
 	// Required.
182
-	Image string `yaml:"image" json:"image"`
194
+	Image string `json:"image" yaml:"image"`
183 195
 	// Optional: Defaults to whatever is defined in the image.
184
-	Command []string `yaml:"command,omitempty" json:"command,omitempty"`
196
+	Command []string `json:"command,omitempty" yaml:"command,omitempty"`
185 197
 	// Optional: Defaults to Docker's default.
186
-	WorkingDir string   `yaml:"workingDir,omitempty" json:"workingDir,omitempty"`
187
-	Ports      []Port   `yaml:"ports,omitempty" json:"ports,omitempty"`
188
-	Env        []EnvVar `yaml:"env,omitempty" json:"env,omitempty"`
198
+	WorkingDir string   `json:"workingDir,omitempty" yaml:"workingDir,omitempty"`
199
+	Ports      []Port   `json:"ports,omitempty" yaml:"ports,omitempty"`
200
+	Env        []EnvVar `json:"env,omitempty" yaml:"env,omitempty"`
189 201
 	// Optional: Defaults to unlimited.
190
-	Memory int `yaml:"memory,omitempty" json:"memory,omitempty"`
202
+	Memory int `json:"memory,omitempty" yaml:"memory,omitempty"`
191 203
 	// Optional: Defaults to unlimited.
192
-	CPU           int            `yaml:"cpu,omitempty" json:"cpu,omitempty"`
193
-	VolumeMounts  []VolumeMount  `yaml:"volumeMounts,omitempty" json:"volumeMounts,omitempty"`
194
-	LivenessProbe *LivenessProbe `yaml:"livenessProbe,omitempty" json:"livenessProbe,omitempty"`
195
-	Lifecycle     *Lifecycle     `yaml:"lifecycle,omitempty" json:"lifecycle,omitempty"`
204
+	CPU           int            `json:"cpu,omitempty" yaml:"cpu,omitempty"`
205
+	VolumeMounts  []VolumeMount  `json:"volumeMounts,omitempty" yaml:"volumeMounts,omitempty"`
206
+	LivenessProbe *LivenessProbe `json:"livenessProbe,omitempty" yaml:"livenessProbe,omitempty"`
207
+	Lifecycle     *Lifecycle     `json:"lifecycle,omitempty" yaml:"lifecycle,omitempty"`
196 208
 	// Optional: Default to false.
197 209
 	Privileged bool `json:"privileged,omitempty" yaml:"privileged,omitempty"`
210
+	// Optional: Policy for pulling images for this container
211
+	ImagePullPolicy PullPolicy `json:"imagePullPolicy" yaml:"imagePullPolicy"`
198 212
 }
199 213
 
200 214
 // Handler defines a specific action that should be taken
... ...
@@ -202,9 +269,9 @@ type Container struct {
202 202
 type Handler struct {
203 203
 	// One and only one of the following should be specified.
204 204
 	// Exec specifies the action to take.
205
-	Exec *ExecAction `yaml:"exec,omitempty" json:"exec,omitempty"`
205
+	Exec *ExecAction `json:"exec,omitempty" yaml:"exec,omitempty"`
206 206
 	// HTTPGet specifies the http request to perform.
207
-	HTTPGet *HTTPGetAction `yaml:"httpGet,omitempty" json:"httpGet,omitempty"`
207
+	HTTPGet *HTTPGetAction `json:"httpGet,omitempty" yaml:"httpGet,omitempty"`
208 208
 }
209 209
 
210 210
 // Lifecycle describes actions that the management system should take in response to container lifecycle
... ...
@@ -213,43 +280,30 @@ type Handler struct {
213 213
 type Lifecycle struct {
214 214
 	// PostStart is called immediately after a container is created.  If the handler fails, the container
215 215
 	// is terminated and restarted.
216
-	PostStart *Handler `yaml:"postStart,omitempty" json:"postStart,omitempty"`
216
+	PostStart *Handler `json:"postStart,omitempty" yaml:"postStart,omitempty"`
217 217
 	// PreStop is called immediately before a container is terminated.  The reason for termination is
218 218
 	// passed to the handler.  Regardless of the outcome of the handler, the container is eventually terminated.
219
-	PreStop *Handler `yaml:"preStop,omitempty" json:"preStop,omitempty"`
220
-}
221
-
222
-// Event is the representation of an event logged to etcd backends.
223
-type Event struct {
224
-	Event     string             `json:"event,omitempty"`
225
-	Manifest  *ContainerManifest `json:"manifest,omitempty"`
226
-	Container *Container         `json:"container,omitempty"`
227
-	Timestamp int64              `json:"timestamp"`
219
+	PreStop *Handler `json:"preStop,omitempty" yaml:"preStop,omitempty"`
228 220
 }
229 221
 
230
-// The below types are used by kube_client and api_server.
231
-
232
-// JSONBase is shared by all objects sent to, or returned from the client.
233
-type JSONBase struct {
234
-	Kind              string    `json:"kind,omitempty" yaml:"kind,omitempty"`
235
-	ID                string    `json:"id,omitempty" yaml:"id,omitempty"`
236
-	CreationTimestamp util.Time `json:"creationTimestamp,omitempty" yaml:"creationTimestamp,omitempty"`
237
-	SelfLink          string    `json:"selfLink,omitempty" yaml:"selfLink,omitempty"`
238
-	ResourceVersion   uint64    `json:"resourceVersion,omitempty" yaml:"resourceVersion,omitempty"`
239
-	APIVersion        string    `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
240
-}
222
+// PodCondition is a label for the condition of a pod at the current time.
223
+type PodCondition string
241 224
 
242
-// PodStatus represents a status of a pod.
243
-type PodStatus string
244
-
245
-// These are the valid statuses of pods.
225
+// These are the valid states of pods.
246 226
 const (
247
-	// PodWaiting means that we're waiting for the pod to begin running.
248
-	PodWaiting PodStatus = "Waiting"
249
-	// PodRunning means that the pod is up and running.
250
-	PodRunning PodStatus = "Running"
251
-	// PodTerminated means that the pod has stopped.
252
-	PodTerminated PodStatus = "Terminated"
227
+	// PodPending means the pod has been accepted by the system, but one or more of the containers
228
+	// has not been started. This includes time before being bound to a node, as well as time spent
229
+	// pulling images onto the host.
230
+	PodPending PodCondition = "Pending"
231
+	// PodRunning means the pod has been bound to a node and all of the containers have been started.
232
+	// At least one container is still running or is in the process of being restarted.
233
+	PodRunning PodCondition = "Running"
234
+	// PodSucceeded means that all containers in the pod have voluntarily terminated with a container
235
+	// exit code of 0.
236
+	PodSucceeded PodCondition = "Succeeded"
237
+	// PodFailed means that all containers in the pod have terminated, and at least one container has
238
+	// terminated in a failure (exited with a non-zero exit code or was stopped by the system).
239
+	PodFailed PodCondition = "Failed"
253 240
 )
254 241
 
255 242
 type ContainerStateWaiting struct {
... ...
@@ -308,13 +362,23 @@ type RestartPolicy struct {
308 308
 	Never     *RestartPolicyNever     `json:"never,omitempty" yaml:"never,omitempty"`
309 309
 }
310 310
 
311
-// PodState is the state of a pod, used as either input (desired state) or output (current state).
312
-type PodState struct {
313
-	Manifest ContainerManifest `json:"manifest,omitempty" yaml:"manifest,omitempty"`
314
-	Status   PodStatus         `json:"status,omitempty" yaml:"status,omitempty"`
315
-	Host     string            `json:"host,omitempty" yaml:"host,omitempty"`
316
-	HostIP   string            `json:"hostIP,omitempty" yaml:"hostIP,omitempty"`
317
-	PodIP    string            `json:"podIP,omitempty" yaml:"podIP,omitempty"`
311
+// PodSpec is a description of a pod
312
+type PodSpec struct {
313
+	Volumes       []Volume      `json:"volumes" yaml:"volumes"`
314
+	Containers    []Container   `json:"containers" yaml:"containers"`
315
+	RestartPolicy RestartPolicy `json:"restartPolicy,omitempty" yaml:"restartPolicy,omitempty"`
316
+}
317
+
318
+// PodStatus represents information about the status of a pod. Status may trail the actual
319
+// state of a system.
320
+type PodStatus struct {
321
+	Condition PodCondition `json:"condition,omitempty" yaml:"condition,omitempty"`
322
+
323
+	// Host is the name of the node that this Pod is currently bound to, or empty if no
324
+	// assignment has been done.
325
+	Host   string `json:"host,omitempty" yaml:"host,omitempty"`
326
+	HostIP string `json:"hostIP,omitempty" yaml:"hostIP,omitempty"`
327
+	PodIP  string `json:"podIP,omitempty" yaml:"podIP,omitempty"`
318 328
 
319 329
 	// The key of this map is the *name* of the container within the manifest; it has one
320 330
 	// entry per container in the manifest. The value of this map is currently the output
... ...
@@ -327,141 +391,239 @@ type PodState struct {
327 327
 
328 328
 // PodList is a list of Pods.
329 329
 type PodList struct {
330
-	JSONBase `json:",inline" yaml:",inline"`
331
-	Items    []Pod `json:"items" yaml:"items,omitempty"`
332
-}
330
+	TypeMeta `json:",inline" yaml:",inline"`
331
+	Metadata ListMeta `json:"metadata,inline" yaml:"metadata,inline"`
333 332
 
334
-func (*PodList) IsAnAPIObject() {}
333
+	Items []Pod `json:"items" yaml:"items"`
334
+}
335 335
 
336
-// Pod is a collection of containers, used as either input (create, update) or as output (list, get).
336
+// Pod is a collection of containers that can run on a host. This resource is created
337
+// by clients and scheduled onto hosts.  BoundPod represents the state of this resource
338
+// to hosts.
337 339
 type Pod struct {
338
-	JSONBase     `json:",inline" yaml:",inline"`
339
-	Labels       map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
340
-	DesiredState PodState          `json:"desiredState,omitempty" yaml:"desiredState,omitempty"`
341
-	CurrentState PodState          `json:"currentState,omitempty" yaml:"currentState,omitempty"`
340
+	TypeMeta `json:",inline" yaml:",inline"`
341
+	Metadata ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
342
+
343
+	// Spec defines the behavior of a pod.
344
+	Spec PodSpec `json:"spec,omitempty" yaml:"spec,omitempty"`
345
+
346
+	// Status represents the current information about a pod. This data may not be up
347
+	// to date.
348
+	Status PodStatus `json:"status,omitempty" yaml:"status,omitempty"`
342 349
 }
343 350
 
344
-func (*Pod) IsAnAPIObject() {}
351
+// PodTemplateSpec describes the data a pod should have when created from a template
352
+type PodTemplateSpec struct {
353
+	// Metadata of the pods created from this template.
354
+	Metadata ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
345 355
 
346
-// ReplicationControllerState is the state of a replication controller, either input (create, update) or as output (list, get).
347
-type ReplicationControllerState struct {
348
-	Replicas        int               `json:"replicas" yaml:"replicas"`
349
-	ReplicaSelector map[string]string `json:"replicaSelector,omitempty" yaml:"replicaSelector,omitempty"`
350
-	PodTemplate     PodTemplate       `json:"podTemplate,omitempty" yaml:"podTemplate,omitempty"`
356
+	// Spec defines the behavior of a pod.
357
+	Spec PodSpec `json:"spec,omitempty" yaml:"spec,omitempty"`
351 358
 }
352 359
 
353
-// ReplicationControllerList is a collection of replication controllers.
354
-type ReplicationControllerList struct {
355
-	JSONBase `json:",inline" yaml:",inline"`
356
-	Items    []ReplicationController `json:"items,omitempty" yaml:"items,omitempty"`
360
+// PodTemplate describes a template for creating copies of a predefined pod.
361
+type PodTemplate struct {
362
+	TypeMeta `json:",inline" yaml:",inline"`
363
+	Metadata ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
364
+
365
+	// Spec defines the behavior of a pod.
366
+	Spec PodTemplateSpec `json:"spec,omitempty" yaml:"spec,omitempty"`
357 367
 }
358 368
 
359
-func (*ReplicationControllerList) IsAnAPIObject() {}
369
+// BoundPod is a collection of containers that should be run on a host. A BoundPod
370
+// defines how a Pod may change after a Binding is created. A Pod is a request to
371
+// execute a pod, whereas a BoundPod is the specification that would be run on a server.
372
+type BoundPod struct {
373
+	Metadata ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
360 374
 
361
-// ReplicationController represents the configuration of a replication controller.
362
-type ReplicationController struct {
363
-	JSONBase     `json:",inline" yaml:",inline"`
364
-	DesiredState ReplicationControllerState `json:"desiredState,omitempty" yaml:"desiredState,omitempty"`
365
-	CurrentState ReplicationControllerState `json:"currentState,omitempty" yaml:"currentState,omitempty"`
366
-	Labels       map[string]string          `json:"labels,omitempty" yaml:"labels,omitempty"`
375
+	// Spec defines the behavior of a pod.
376
+	Spec PodSpec `json:"spec,omitempty" yaml:"spec,omitempty"`
367 377
 }
368 378
 
369
-func (*ReplicationController) IsAnAPIObject() {}
379
+// BoundPods is a list of Pods bound to a common server. The resource version of
380
+// the pod list is guaranteed to only change when the list of bound pods changes.
381
+type BoundPods struct {
382
+	TypeMeta `json:",inline" yaml:",inline"`
383
+	Metadata ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
370 384
 
371
-// PodTemplate holds the information used for creating pods.
372
-type PodTemplate struct {
373
-	DesiredState PodState          `json:"desiredState,omitempty" yaml:"desiredState,omitempty"`
374
-	Labels       map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
385
+	// Host is the name of a node that these pods were bound to.
386
+	Host string `json:"host" yaml:"host"`
387
+
388
+	// Items is the list of all pods bound to a given host.
389
+	Items []BoundPod `json:"items" yaml:"items"`
375 390
 }
376 391
 
377
-// ServiceList holds a list of services.
378
-type ServiceList struct {
379
-	JSONBase `json:",inline" yaml:",inline"`
380
-	Items    []Service `json:"items" yaml:"items"`
392
+// ReplicationControllerSpec is the specification of a replication controller.
393
+type ReplicationControllerSpec struct {
394
+	// Replicas is the number of desired replicas.
395
+	Replicas int `json:"replicas" yaml:"replicas"`
396
+
397
+	// Selector is a label query over pods that should match the Replicas count.
398
+	Selector map[string]string `json:"selector,omitempty" yaml:"selector,omitempty"`
399
+
400
+	// Template is a reference to an object that describes the pod that will be created if
401
+	// insufficient replicas are detected.
402
+	Template ObjectReference `json:"template,omitempty" yaml:"template,omitempty"`
381 403
 }
382 404
 
383
-func (*ServiceList) IsAnAPIObject() {}
405
+// ReplicationControllerStatus represents the current status of a replication
406
+// controller.
407
+type ReplicationControllerStatus struct {
408
+	// Replicas is the number of actual replicas.
409
+	Replicas int `json:"replicas" yaml:"replicas"`
410
+}
384 411
 
385
-// Service is a named abstraction of software service (for example, mysql) consisting of local port
386
-// (for example 3306) that the proxy listens on, and the selector that determines which pods
387
-// will answer requests sent through the proxy.
388
-type Service struct {
389
-	JSONBase `json:",inline" yaml:",inline"`
412
+// ReplicationController represents the configuration of a replication controller.
413
+type ReplicationController struct {
414
+	TypeMeta `json:",inline" yaml:",inline"`
415
+	Metadata ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
390 416
 
391
-	// Required.
417
+	// Spec defines the desired behavior of this replication controller.
418
+	Spec ReplicationControllerSpec `json:"spec,omitempty" yaml:"spec,omitempty"`
419
+
420
+	// Status is the current status of this replication controller. This data may be
421
+	// out of date by some window of time.
422
+	Status ReplicationControllerStatus `json:"status,omitempty" yaml:"status,omitempty"`
423
+}
424
+
425
+// ReplicationControllerList is a collection of replication controllers.
426
+type ReplicationControllerList struct {
427
+	TypeMeta `json:",inline" yaml:",inline"`
428
+	Metadata ListMeta `json:"metadata,inline" yaml:"metadata,inline"`
429
+
430
+	Items []ReplicationController `json:"items" yaml:"items"`
431
+}
432
+
433
+// ServiceStatus represents the current status of a service
434
+type ServiceStatus struct {
435
+}
436
+
437
+// ServiceSpec describes the attributes that a user creates on a service
438
+type ServiceSpec struct {
439
+	// Port is the TCP or UDP port that will be made available to each pod for connecting to the pods
440
+	// proxied by this service.
392 441
 	Port int `json:"port" yaml:"port"`
393
-	// Optional: Supports "TCP" and "UDP".  Defaults to "TCP".
394
-	Protocol string `yaml:"protocol,omitempty" json:"protocol,omitempty"`
395 442
 
396
-	// This service's labels.
397
-	Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
443
+	// Optional: Supports "TCP" and "UDP".  Defaults to "TCP".
444
+	Protocol Protocol `json:"protocol,omitempty" yaml:"protocol,omitempty"`
398 445
 
399 446
 	// This service will route traffic to pods having labels matching this selector.
400
-	Selector                   map[string]string `json:"selector,omitempty" yaml:"selector,omitempty"`
401
-	CreateExternalLoadBalancer bool              `json:"createExternalLoadBalancer,omitempty" yaml:"createExternalLoadBalancer,omitempty"`
447
+	Selector map[string]string `json:"selector,omitempty" yaml:"selector,omitempty"`
448
+
449
+	// CreateExternalLoadBalancer indicates whether a load balancer should be created for this service.
450
+	CreateExternalLoadBalancer bool `json:"createExternalLoadBalancer,omitempty" yaml:"createExternalLoadBalancer,omitempty"`
402 451
 
403 452
 	// ContainerPort is the name of the port on the container to direct traffic to.
404 453
 	// Optional, if unspecified use the first port on the container.
405 454
 	ContainerPort util.IntOrString `json:"containerPort,omitempty" yaml:"containerPort,omitempty"`
406 455
 }
407 456
 
408
-func (*Service) IsAnAPIObject() {}
457
+// Service is a named abstraction of software service (for example, mysql) consisting of local port
458
+// (for example 3306) that the proxy listens on, and the selector that determines which pods
459
+// will answer requests sent through the proxy.
460
+type Service struct {
461
+	TypeMeta `json:",inline" yaml:",inline"`
462
+	Metadata ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
463
+
464
+	// Spec defines the behavior of a service.
465
+	Spec ServiceSpec `json:"spec,omitempty" yaml:"spec,omitempty"`
466
+
467
+	// Status represents the current status of a service.
468
+	Status ServiceStatus `json:"status,omitempty" yaml:"status,omitempty"`
469
+}
470
+
471
+// ServiceList holds a list of services.
472
+type ServiceList struct {
473
+	TypeMeta `json:",inline" yaml:",inline"`
474
+	Metadata ListMeta `json:"metadata,inline" yaml:"metadata,inline"`
475
+
476
+	Items []Service `json:"items" yaml:"items"`
477
+}
409 478
 
410 479
 // Endpoints is a collection of endpoints that implement the actual service, for example:
411 480
 // Name: "mysql", Endpoints: ["10.10.1.1:1909", "10.10.2.2:8834"]
412 481
 type Endpoints struct {
413
-	JSONBase  `json:",inline" yaml:",inline"`
414
-	Endpoints []string `json:"endpoints,omitempty" yaml:"endpoints,omitempty"`
415
-}
482
+	TypeMeta `json:",inline" yaml:",inline"`
483
+	Metadata ObjectMeta `json:"metadata,inline" yaml:"metadata,inline"`
416 484
 
417
-func (*Endpoints) IsAnAPIObject() {}
485
+	// Endpoints is the list of host ports that satisfy the service selector
486
+	Endpoints []string `json:"endpoints" yaml:"endpoints"`
487
+}
418 488
 
419 489
 // EndpointsList is a list of endpoints.
420 490
 type EndpointsList struct {
421
-	JSONBase `json:",inline" yaml:",inline"`
422
-	Items    []Endpoints `json:"items,omitempty" yaml:"items,omitempty"`
491
+	TypeMeta `json:",inline" yaml:",inline"`
492
+	Metadata ListMeta `json:"metadata,inline" yaml:"metadata,inline"`
493
+
494
+	Items []Endpoints `json:"items" yaml:"items"`
423 495
 }
424 496
 
425
-func (*EndpointsList) IsAnAPIObject() {}
497
+// NodeSpec describes the attributes that a node is created with.
498
+type NodeSpec struct {
499
+}
426 500
 
427
-// Minion is a worker node in Kubernetenes.
428
-// The name of the minion according to etcd is in JSONBase.ID.
429
-type Minion struct {
430
-	JSONBase `json:",inline" yaml:",inline"`
431
-	// Queried from cloud provider, if available.
432
-	HostIP string `json:"hostIP,omitempty" yaml:"hostIP,omitempty"`
501
+// NodeStatus is information about the current status of a node.
502
+type NodeStatus struct {
503
+}
504
+
505
+// NodeResources represents resources on a Kubernetes system node
506
+// see https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/resources.md for more details.
507
+type NodeResources struct {
508
+	// Capacity represents the available resources.
509
+	Capacity ResourceList `json:"capacity,omitempty" yaml:"capacity,omitempty"`
433 510
 }
434 511
 
435
-func (*Minion) IsAnAPIObject() {}
512
+type ResourceName string
436 513
 
437
-// MinionList is a list of minions.
438
-type MinionList struct {
439
-	JSONBase `json:",inline" yaml:",inline"`
440
-	Items    []Minion `json:"items,omitempty" yaml:"items,omitempty"`
514
+type ResourceList map[ResourceName]util.IntOrString
515
+
516
+// Node is a worker node in Kubernetenes.
517
+// The name of the node according to etcd is in JSONBase.ID.
518
+type Node struct {
519
+	TypeMeta `json:",inline" yaml:",inline"`
520
+	Metadata ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
521
+
522
+	// Spec defines the behavior of a node.
523
+	Spec NodeSpec `json:"spec,omitempty" yaml:"spec,omitempty"`
524
+
525
+	// Status describes the current status of a Node
526
+	Status NodeStatus `json:"status,omitempty" yaml:"status,omitempty"`
527
+
528
+	// NodeResources describe the resoruces available on the node.
529
+	NodeResources NodeResources `json:"resources,omitempty" yaml:"resources,omitempty"`
441 530
 }
442 531
 
443
-func (*MinionList) IsAnAPIObject() {}
532
+// NodeList is a list of minions.
533
+type NodeList struct {
534
+	TypeMeta `json:",inline" yaml:",inline"`
535
+	Metadata ListMeta `json:"metadata,inline" yaml:"metadata,inline"`
444 536
 
445
-// Binding is written by a scheduler to cause a pod to be bound to a host.
446
-type Binding struct {
447
-	JSONBase `json:",inline" yaml:",inline"`
448
-	PodID    string `json:"podID" yaml:"podID"`
449
-	Host     string `json:"host" yaml:"host"`
537
+	Items []Node `json:"items" yaml:"items"`
450 538
 }
451 539
 
452
-func (*Binding) IsAnAPIObject() {}
540
+// Binding is written by a scheduler to cause a pod to be bound to a node. Name is not
541
+// required for Bindings.
542
+type Binding struct {
543
+	TypeMeta `json:",inline" yaml:",inline"`
544
+	Metadata ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
545
+
546
+	// PodID is a Pod name to be bound to a node.
547
+	PodID string `json:"podID" yaml:"podID"`
548
+	// Host is the name of a node to bind to.
549
+	Host string `json:"host" yaml:"host"`
550
+}
453 551
 
454 552
 // Status is a return value for calls that don't return other objects.
455
-// TODO: this could go in apiserver, but I'm including it here so clients needn't
456
-// import both.
457 553
 type Status struct {
458
-	JSONBase `json:",inline" yaml:",inline"`
459
-	// One of: "success", "failure", "working" (for operations not yet completed)
554
+	TypeMeta `json:",inline" yaml:",inline"`
555
+	Metadata ListMeta `json:"metadata,inline" yaml:"metadata,inline"`
556
+
557
+	// One of: "Success", "Failure", "Working" (for operations not yet completed)
460 558
 	Status string `json:"status,omitempty" yaml:"status,omitempty"`
461 559
 	// A human-readable description of the status of this operation.
462 560
 	Message string `json:"message,omitempty" yaml:"message,omitempty"`
463 561
 	// A machine-readable description of why this operation is in the
464
-	// "failure" or "working" status. If this value is empty there
562
+	// "Failure" or "Working" status. If this value is empty there
465 563
 	// is no information available. A Reason clarifies an HTTP status
466 564
 	// code but does not override it.
467 565
 	Reason StatusReason `json:"reason,omitempty" yaml:"reason,omitempty"`
... ...
@@ -474,8 +636,6 @@ type Status struct {
474 474
 	Code int `json:"code,omitempty" yaml:"code,omitempty"`
475 475
 }
476 476
 
477
-func (*Status) IsAnAPIObject() {}
478
-
479 477
 // StatusDetails is a set of additional properties that MAY be set by the
480 478
 // server to provide additional information about a response. The Reason
481 479
 // field of a Status object defines what attributes will be set. Clients
... ...
@@ -496,9 +656,9 @@ type StatusDetails struct {
496 496
 
497 497
 // Values of Status.Status
498 498
 const (
499
-	StatusSuccess = "success"
500
-	StatusFailure = "failure"
501
-	StatusWorking = "working"
499
+	StatusSuccess = "Success"
500
+	StatusFailure = "Failure"
501
+	StatusWorking = "Working"
502 502
 )
503 503
 
504 504
 // StatusReason is an enumeration of possible failure causes.  Each StatusReason
... ...
@@ -523,7 +683,7 @@ const (
523 523
 	//   "Location" - HTTP header populated with a URL that can retrieved the final
524 524
 	//                status of this operation.
525 525
 	// Status code 202
526
-	StatusReasonWorking StatusReason = "working"
526
+	StatusReasonWorking StatusReason = "Working"
527 527
 
528 528
 	// StatusReasonNotFound means one or more resources required for this operation
529 529
 	// could not be found.
... ...
@@ -533,21 +693,21 @@ const (
533 533
 	//                   resource.
534 534
 	//   "id"   string - the identifier of the missing resource
535 535
 	// Status code 404
536
-	StatusReasonNotFound StatusReason = "not_found"
536
+	StatusReasonNotFound StatusReason = "NotFound"
537 537
 
538 538
 	// StatusReasonAlreadyExists means the resource you are creating already exists.
539 539
 	// Details (optional):
540 540
 	//   "kind" string - the kind attribute of the conflicting resource
541 541
 	//   "id"   string - the identifier of the conflicting resource
542 542
 	// Status code 409
543
-	StatusReasonAlreadyExists StatusReason = "already_exists"
543
+	StatusReasonAlreadyExists StatusReason = "AlreadyExists"
544 544
 
545 545
 	// StatusReasonConflict means the requested update operation cannot be completed
546 546
 	// due to a conflict in the operation. The client may need to alter the request.
547 547
 	// Each resource may define custom details that indicate the nature of the
548 548
 	// conflict.
549 549
 	// Status code 409
550
-	StatusReasonConflict StatusReason = "conflict"
550
+	StatusReasonConflict StatusReason = "Conflict"
551 551
 
552 552
 	// StatusReasonInvalid means the requested create or update operation cannot be
553 553
 	// completed due to invalid data provided as part of the request. The client may
... ...
@@ -560,7 +720,7 @@ const (
560 560
 	//                   provided resource that was invalid.  The code, message, and
561 561
 	//                   field attributes will be set.
562 562
 	// Status code 422
563
-	StatusReasonInvalid StatusReason = "invalid"
563
+	StatusReasonInvalid StatusReason = "Invalid"
564 564
 )
565 565
 
566 566
 // StatusCause provides more information about an api.Status failure, including
... ...
@@ -586,38 +746,117 @@ type StatusCause struct {
586 586
 
587 587
 // CauseType is a machine readable value providing more detail about what
588 588
 // occured in a status response. An operation may have multiple causes for a
589
-// status (whether failure, success, or working).
589
+// status (whether Failure, Success, or Working).
590 590
 type CauseType string
591 591
 
592 592
 const (
593 593
 	// CauseTypeFieldValueNotFound is used to report failure to find a requested value
594 594
 	// (e.g. looking up an ID).
595
-	CauseTypeFieldValueNotFound CauseType = "fieldValueNotFound"
595
+	CauseTypeFieldValueNotFound CauseType = "FieldValueNotFound"
596 596
 	// CauseTypeFieldValueInvalid is used to report required values that are not
597 597
 	// provided (e.g. empty strings, null values, or empty arrays).
598
-	CauseTypeFieldValueRequired CauseType = "fieldValueRequired"
598
+	CauseTypeFieldValueRequired CauseType = "FieldValueRequired"
599 599
 	// CauseTypeFieldValueDuplicate is used to report collisions of values that must be
600 600
 	// unique (e.g. unique IDs).
601
-	CauseTypeFieldValueDuplicate CauseType = "fieldValueDuplicate"
601
+	CauseTypeFieldValueDuplicate CauseType = "FieldValueDuplicate"
602 602
 	// CauseTypeFieldValueInvalid is used to report malformed values (e.g. failed regex
603 603
 	// match).
604
-	CauseTypeFieldValueInvalid CauseType = "fieldValueInvalid"
604
+	CauseTypeFieldValueInvalid CauseType = "FieldValueInvalid"
605 605
 	// CauseTypeFieldValueNotSupported is used to report valid (as per formatting rules)
606 606
 	// values that can not be handled (e.g. an enumerated string).
607
-	CauseTypeFieldValueNotSupported CauseType = "fieldValueNotSupported"
607
+	CauseTypeFieldValueNotSupported CauseType = "FieldValueNotSupported"
608 608
 )
609 609
 
610
-// ServerOp is an operation delivered to API clients.
611
-type ServerOp struct {
612
-	JSONBase `yaml:",inline" json:",inline"`
610
+// Operation is a request from a client that has not yet been satisfied. The name of an
611
+// Operation is assigned by the server when an operation is started, and can be used by
612
+// clients to retrieve the final result of the operation at a later time.
613
+type Operation struct {
614
+	TypeMeta `json:",inline" yaml:",inline"`
615
+	Metadata ObjectMeta `json:"metadata,inline" yaml:"metadata,inline"`
616
+}
617
+
618
+// OperationList is a list of operations, as delivered to API clients.
619
+type OperationList struct {
620
+	TypeMeta `json:",inline" yaml:",inline"`
621
+	Metadata ListMeta `json:"metadata,inline" yaml:"metadata,inline"`
622
+
623
+	Items []Operation `json:"items" yaml:"items"`
613 624
 }
614 625
 
615
-func (*ServerOp) IsAnAPIObject() {}
626
+// ObjectReference contains enough information to let you inspect or modify the referred object.
627
+type ObjectReference struct {
628
+	Kind            string `json:"kind,omitempty" yaml:"kind,omitempty"`
629
+	Name            string `json:"name,omitempty" yaml:"name,omitempty"`
630
+	UID             string `json:"uid,omitempty" yaml:"uid,omitempty"`
631
+	APIVersion      string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
632
+	ResourceVersion uint64 `json:"resourceVersion,omitempty" yaml:"resourceVersion,omitempty"`
616 633
 
617
-// ServerOpList is a list of operations, as delivered to API clients.
618
-type ServerOpList struct {
619
-	JSONBase `yaml:",inline" json:",inline"`
620
-	Items    []ServerOp `yaml:"items,omitempty" json:"items,omitempty"`
634
+	// Optional. If referring to a piece of an object instead of an entire object, this string
635
+	// should contain a valid field access statement. For example,
636
+	// if the object reference is to a container within a pod, this would take on a value like:
637
+	// "spec.containers[2]". Such statements are valid language constructs in
638
+	// both go and JavaScript. This is syntax is chosen only to have some well-defined way of
639
+	// referencing a part of an object.
640
+	// TODO: this design is not final and this field is subject to change in the future.
641
+	FieldPath string `json:"fieldPath,omitempty" yaml:"fieldPath,omitempty"`
621 642
 }
622 643
 
623
-func (*ServerOpList) IsAnAPIObject() {}
644
+// Event is a report of an event somewhere in the cluster.
645
+// TODO: Decide whether to store these separately or with the object they apply to.
646
+type Event struct {
647
+	TypeMeta `json:",inline" yaml:",inline"`
648
+	Metadata ObjectMeta `json:"metadata,inline" yaml:"metadata,inline"`
649
+
650
+	// Required. The object that this event is about.
651
+	InvolvedObject ObjectReference `json:"involvedObject,omitempty" yaml:"involvedObject,omitempty"`
652
+
653
+	// Should be a short, machine understandable string that describes the current status
654
+	// of the referred object. This should not give the reason for being in this state.
655
+	// Examples: "Running", "CantStart", "CantSchedule", "Deleted".
656
+	// It's OK for components to make up statuses to report here, but the same string should
657
+	// always be used for the same status.
658
+	// TODO: define a way of making sure these are consistent and don't collide.
659
+	// TODO: provide exact specification for format.
660
+	Condition string `json:"condition,omitempty" yaml:"condition,omitempty"`
661
+
662
+	// Optional; this should be a short, machine understandable string that gives the reason
663
+	// for the transition into the object's current status. For example, if ObjectStatus is
664
+	// "cantStart", StatusReason might be "imageNotFound".
665
+	// TODO: provide exact specification for format.
666
+	Reason string `json:"reason,omitempty" yaml:"reason,omitempty"`
667
+
668
+	// Optional. A human-readable description of the status of this operation.
669
+	// TODO: decide on maximum length.
670
+	Message string `json:"message,omitempty" yaml:"message,omitempty"`
671
+
672
+	// Optional. The component reporting this event. Should be a short machine understandable string.
673
+	// TODO: provide exact specification for format.
674
+	Source string `json:"source,omitempty" yaml:"source,omitempty"`
675
+}
676
+
677
+// EventList is a list of events.
678
+type EventList struct {
679
+	JSONBase `json:",inline" yaml:",inline"`
680
+	Items    []Event `json:"items" yaml:"items"`
681
+}
682
+
683
+// TODO: for readability
684
+func (*Pod) IsAnAPIObject()                       {}
685
+func (*PodList) IsAnAPIObject()                   {}
686
+func (*PodTemplate) IsAnAPIObject()               {}
687
+func (*BoundPod) IsAnAPIObject()                  {}
688
+func (*BoundPods) IsAnAPIObject()                 {}
689
+func (*ReplicationController) IsAnAPIObject()     {}
690
+func (*ReplicationControllerList) IsAnAPIObject() {}
691
+func (*Service) IsAnAPIObject()                   {}
692
+func (*ServiceList) IsAnAPIObject()               {}
693
+func (*Endpoints) IsAnAPIObject()                 {}
694
+func (*EndpointsList) IsAnAPIObject()             {}
695
+func (*Node) IsAnAPIObject()                      {}
696
+func (*NodeList) IsAnAPIObject()                  {}
697
+func (*Binding) IsAnAPIObject()                   {}
698
+func (*Status) IsAnAPIObject()                    {}
699
+func (*Operation) IsAnAPIObject()                 {}
700
+func (*OperationList) IsAnAPIObject()             {}
701
+func (*Event) IsAnAPIObject()                     {}
702
+func (*EventList) IsAnAPIObject()                 {}
... ...
@@ -56,11 +56,11 @@ func validateVolumes(volumes []api.Volume) (util.StringSet, errs.ErrorList) {
56 56
 func validateSource(source *api.VolumeSource) errs.ErrorList {
57 57
 	numVolumes := 0
58 58
 	allErrs := errs.ErrorList{}
59
-	if source.HostDirectory != nil {
59
+	if source.HostDir != nil {
60 60
 		numVolumes++
61
-		allErrs = append(allErrs, validateHostDir(source.HostDirectory).Prefix("hostDirectory")...)
61
+		allErrs = append(allErrs, validateHostDir(source.HostDir).Prefix("hostDirectory")...)
62 62
 	}
63
-	if source.EmptyDirectory != nil {
63
+	if source.EmptyDir != nil {
64 64
 		numVolumes++
65 65
 		//EmptyDirs have nothing to validate
66 66
 	}
... ...
@@ -70,7 +70,7 @@ func validateSource(source *api.VolumeSource) errs.ErrorList {
70 70
 	return allErrs
71 71
 }
72 72
 
73
-func validateHostDir(hostDir *api.HostDirectory) errs.ErrorList {
73
+func validateHostDir(hostDir *api.HostDir) errs.ErrorList {
74 74
 	allErrs := errs.ErrorList{}
75 75
 	if hostDir.Path == "" {
76 76
 		allErrs = append(allErrs, errs.NewNotFound("path", hostDir.Path))
... ...
@@ -78,7 +78,7 @@ func validateHostDir(hostDir *api.HostDirectory) errs.ErrorList {
78 78
 	return allErrs
79 79
 }
80 80
 
81
-var supportedPortProtocols = util.NewStringSet("TCP", "UDP")
81
+var supportedPortProtocols = util.NewStringSet(string(api.ProtocolTCP), string(api.ProtocolUDP))
82 82
 
83 83
 func validatePorts(ports []api.Port) errs.ErrorList {
84 84
 	allErrs := errs.ErrorList{}
... ...
@@ -106,7 +106,7 @@ func validatePorts(ports []api.Port) errs.ErrorList {
106 106
 		}
107 107
 		if len(port.Protocol) == 0 {
108 108
 			port.Protocol = "TCP"
109
-		} else if !supportedPortProtocols.Has(strings.ToUpper(port.Protocol)) {
109
+		} else if !supportedPortProtocols.Has(strings.ToUpper(string(port.Protocol))) {
110 110
 			pErrs = append(pErrs, errs.NewFieldNotSupported("protocol", port.Protocol))
111 111
 		}
112 112
 		allErrs = append(allErrs, pErrs.PrefixIndex(i)...)
... ...
@@ -164,7 +164,7 @@ func AccumulateUniquePorts(containers []api.Container, accumulator map[int]bool,
164 164
 				continue
165 165
 			}
166 166
 			if accumulator[port] {
167
-				cErrs = append(cErrs, errs.NewFieldDuplicate("Port", port))
167
+				cErrs = append(cErrs, errs.NewFieldDuplicate("port", port))
168 168
 			} else {
169 169
 				accumulator[port] = true
170 170
 			}
... ...
@@ -313,6 +313,9 @@ func ValidatePod(pod *api.Pod) errs.ErrorList {
313 313
 	if len(pod.ID) == 0 {
314 314
 		allErrs = append(allErrs, errs.NewFieldRequired("id", pod.ID))
315 315
 	}
316
+	if !util.IsDNSSubdomain(pod.Namespace) {
317
+		allErrs = append(allErrs, errs.NewFieldInvalid("namespace", pod.Namespace))
318
+	}
316 319
 	allErrs = append(allErrs, ValidatePodState(&pod.DesiredState).Prefix("desiredState")...)
317 320
 	return allErrs
318 321
 }
... ...
@@ -325,12 +328,15 @@ func ValidateService(service *api.Service) errs.ErrorList {
325 325
 	} else if !util.IsDNS952Label(service.ID) {
326 326
 		allErrs = append(allErrs, errs.NewFieldInvalid("id", service.ID))
327 327
 	}
328
+	if !util.IsDNSSubdomain(service.Namespace) {
329
+		allErrs = append(allErrs, errs.NewFieldInvalid("namespace", service.Namespace))
330
+	}
328 331
 	if !util.IsValidPortNum(service.Port) {
329
-		allErrs = append(allErrs, errs.NewFieldInvalid("Service.Port", service.Port))
332
+		allErrs = append(allErrs, errs.NewFieldInvalid("port", service.Port))
330 333
 	}
331 334
 	if len(service.Protocol) == 0 {
332 335
 		service.Protocol = "TCP"
333
-	} else if !supportedPortProtocols.Has(strings.ToUpper(service.Protocol)) {
336
+	} else if !supportedPortProtocols.Has(strings.ToUpper(string(service.Protocol))) {
334 337
 		allErrs = append(allErrs, errs.NewFieldNotSupported("protocol", service.Protocol))
335 338
 	}
336 339
 	if labels.Set(service.Selector).AsSelector().Empty() {
... ...
@@ -345,6 +351,9 @@ func ValidateReplicationController(controller *api.ReplicationController) errs.E
345 345
 	if len(controller.ID) == 0 {
346 346
 		allErrs = append(allErrs, errs.NewFieldRequired("id", controller.ID))
347 347
 	}
348
+	if !util.IsDNSSubdomain(controller.Namespace) {
349
+		allErrs = append(allErrs, errs.NewFieldInvalid("namespace", controller.Namespace))
350
+	}
348 351
 	allErrs = append(allErrs, ValidateReplicationControllerState(&controller.DesiredState).Prefix("desiredState")...)
349 352
 	return allErrs
350 353
 }
... ...
@@ -37,9 +37,9 @@ func expectPrefix(t *testing.T, prefix string, errs errors.ErrorList) {
37 37
 func TestValidateVolumes(t *testing.T) {
38 38
 	successCase := []api.Volume{
39 39
 		{Name: "abc"},
40
-		{Name: "123", Source: &api.VolumeSource{HostDirectory: &api.HostDirectory{"/mnt/path2"}}},
41
-		{Name: "abc-123", Source: &api.VolumeSource{HostDirectory: &api.HostDirectory{"/mnt/path3"}}},
42
-		{Name: "empty", Source: &api.VolumeSource{EmptyDirectory: &api.EmptyDirectory{}}},
40
+		{Name: "123", Source: &api.VolumeSource{HostDir: &api.HostDir{"/mnt/path2"}}},
41
+		{Name: "abc-123", Source: &api.VolumeSource{HostDir: &api.HostDir{"/mnt/path3"}}},
42
+		{Name: "empty", Source: &api.VolumeSource{EmptyDir: &api.EmptyDir{}}},
43 43
 	}
44 44
 	names, errs := validateVolumes(successCase)
45 45
 	if len(errs) != 0 {
... ...
@@ -309,8 +309,8 @@ func TestValidateManifest(t *testing.T) {
309 309
 		{
310 310
 			Version: "v1beta1",
311 311
 			ID:      "abc",
312
-			Volumes: []api.Volume{{Name: "vol1", Source: &api.VolumeSource{HostDirectory: &api.HostDirectory{"/mnt/vol1"}}},
313
-				{Name: "vol2", Source: &api.VolumeSource{HostDirectory: &api.HostDirectory{"/mnt/vol2"}}}},
312
+			Volumes: []api.Volume{{Name: "vol1", Source: &api.VolumeSource{HostDir: &api.HostDir{"/mnt/vol1"}}},
313
+				{Name: "vol2", Source: &api.VolumeSource{HostDir: &api.HostDir{"/mnt/vol2"}}}},
314 314
 			Containers: []api.Container{
315 315
 				{
316 316
 					Name:       "abc",
... ...
@@ -366,7 +366,7 @@ func TestValidateManifest(t *testing.T) {
366 366
 
367 367
 func TestValidatePod(t *testing.T) {
368 368
 	errs := ValidatePod(&api.Pod{
369
-		JSONBase: api.JSONBase{ID: "foo"},
369
+		JSONBase: api.JSONBase{ID: "foo", Namespace: api.NamespaceDefault},
370 370
 		Labels: map[string]string{
371 371
 			"foo": "bar",
372 372
 		},
... ...
@@ -384,7 +384,7 @@ func TestValidatePod(t *testing.T) {
384 384
 		t.Errorf("Unexpected non-zero error list: %#v", errs)
385 385
 	}
386 386
 	errs = ValidatePod(&api.Pod{
387
-		JSONBase: api.JSONBase{ID: "foo"},
387
+		JSONBase: api.JSONBase{ID: "foo", Namespace: api.NamespaceDefault},
388 388
 		Labels: map[string]string{
389 389
 			"foo": "bar",
390 390
 		},
... ...
@@ -397,7 +397,7 @@ func TestValidatePod(t *testing.T) {
397 397
 	}
398 398
 
399 399
 	errs = ValidatePod(&api.Pod{
400
-		JSONBase: api.JSONBase{ID: "foo"},
400
+		JSONBase: api.JSONBase{ID: "foo", Namespace: api.NamespaceDefault},
401 401
 		Labels: map[string]string{
402 402
 			"foo": "bar",
403 403
 		},
... ...
@@ -424,6 +424,7 @@ func TestValidateService(t *testing.T) {
424 424
 		{
425 425
 			name: "missing id",
426 426
 			svc: api.Service{
427
+				JSONBase: api.JSONBase{Namespace: api.NamespaceDefault},
427 428
 				Port:     8675,
428 429
 				Selector: map[string]string{"foo": "bar"},
429 430
 			},
... ...
@@ -431,9 +432,19 @@ func TestValidateService(t *testing.T) {
431 431
 			numErrs: 1,
432 432
 		},
433 433
 		{
434
+			name: "missing namespace",
435
+			svc: api.Service{
436
+				JSONBase: api.JSONBase{ID: "foo"},
437
+				Port:     8675,
438
+				Selector: map[string]string{"foo": "bar"},
439
+			},
440
+			// Should fail because the Namespace is missing.
441
+			numErrs: 1,
442
+		},
443
+		{
434 444
 			name: "invalid id",
435 445
 			svc: api.Service{
436
-				JSONBase: api.JSONBase{ID: "123abc"},
446
+				JSONBase: api.JSONBase{ID: "123abc", Namespace: api.NamespaceDefault},
437 447
 				Port:     8675,
438 448
 				Selector: map[string]string{"foo": "bar"},
439 449
 			},
... ...
@@ -443,7 +454,7 @@ func TestValidateService(t *testing.T) {
443 443
 		{
444 444
 			name: "missing port",
445 445
 			svc: api.Service{
446
-				JSONBase: api.JSONBase{ID: "abc123"},
446
+				JSONBase: api.JSONBase{ID: "abc123", Namespace: api.NamespaceDefault},
447 447
 				Selector: map[string]string{"foo": "bar"},
448 448
 			},
449 449
 			// Should fail because the port number is missing/invalid.
... ...
@@ -452,7 +463,7 @@ func TestValidateService(t *testing.T) {
452 452
 		{
453 453
 			name: "invalid port",
454 454
 			svc: api.Service{
455
-				JSONBase: api.JSONBase{ID: "abc123"},
455
+				JSONBase: api.JSONBase{ID: "abc123", Namespace: api.NamespaceDefault},
456 456
 				Port:     65536,
457 457
 				Selector: map[string]string{"foo": "bar"},
458 458
 			},
... ...
@@ -462,7 +473,7 @@ func TestValidateService(t *testing.T) {
462 462
 		{
463 463
 			name: "invalid protocol",
464 464
 			svc: api.Service{
465
-				JSONBase: api.JSONBase{ID: "abc123"},
465
+				JSONBase: api.JSONBase{ID: "abc123", Namespace: api.NamespaceDefault},
466 466
 				Port:     8675,
467 467
 				Protocol: "INVALID",
468 468
 				Selector: map[string]string{"foo": "bar"},
... ...
@@ -473,7 +484,7 @@ func TestValidateService(t *testing.T) {
473 473
 		{
474 474
 			name: "missing selector",
475 475
 			svc: api.Service{
476
-				JSONBase: api.JSONBase{ID: "foo"},
476
+				JSONBase: api.JSONBase{ID: "foo", Namespace: api.NamespaceDefault},
477 477
 				Port:     8675,
478 478
 			},
479 479
 			// Should fail because the selector is missing.
... ...
@@ -482,7 +493,7 @@ func TestValidateService(t *testing.T) {
482 482
 		{
483 483
 			name: "valid 1",
484 484
 			svc: api.Service{
485
-				JSONBase: api.JSONBase{ID: "abc123"},
485
+				JSONBase: api.JSONBase{ID: "abc123", Namespace: api.NamespaceDefault},
486 486
 				Port:     1,
487 487
 				Protocol: "TCP",
488 488
 				Selector: map[string]string{"foo": "bar"},
... ...
@@ -492,7 +503,7 @@ func TestValidateService(t *testing.T) {
492 492
 		{
493 493
 			name: "valid 2",
494 494
 			svc: api.Service{
495
-				JSONBase: api.JSONBase{ID: "abc123"},
495
+				JSONBase: api.JSONBase{ID: "abc123", Namespace: api.NamespaceDefault},
496 496
 				Port:     65535,
497 497
 				Protocol: "UDP",
498 498
 				Selector: map[string]string{"foo": "bar"},
... ...
@@ -502,7 +513,7 @@ func TestValidateService(t *testing.T) {
502 502
 		{
503 503
 			name: "valid 3",
504 504
 			svc: api.Service{
505
-				JSONBase: api.JSONBase{ID: "abc123"},
505
+				JSONBase: api.JSONBase{ID: "abc123", Namespace: api.NamespaceDefault},
506 506
 				Port:     80,
507 507
 				Selector: map[string]string{"foo": "bar"},
508 508
 			},
... ...
@@ -519,7 +530,7 @@ func TestValidateService(t *testing.T) {
519 519
 
520 520
 	svc := api.Service{
521 521
 		Port:     6502,
522
-		JSONBase: api.JSONBase{ID: "foo"},
522
+		JSONBase: api.JSONBase{ID: "foo", Namespace: api.NamespaceDefault},
523 523
 		Selector: map[string]string{"foo": "bar"},
524 524
 	}
525 525
 	errs := ValidateService(&svc)
... ...
@@ -544,14 +555,14 @@ func TestValidateReplicationController(t *testing.T) {
544 544
 
545 545
 	successCases := []api.ReplicationController{
546 546
 		{
547
-			JSONBase: api.JSONBase{ID: "abc"},
547
+			JSONBase: api.JSONBase{ID: "abc", Namespace: api.NamespaceDefault},
548 548
 			DesiredState: api.ReplicationControllerState{
549 549
 				ReplicaSelector: validSelector,
550 550
 				PodTemplate:     validPodTemplate,
551 551
 			},
552 552
 		},
553 553
 		{
554
-			JSONBase: api.JSONBase{ID: "abc-123"},
554
+			JSONBase: api.JSONBase{ID: "abc-123", Namespace: api.NamespaceDefault},
555 555
 			DesiredState: api.ReplicationControllerState{
556 556
 				ReplicaSelector: validSelector,
557 557
 				PodTemplate:     validPodTemplate,
... ...
@@ -566,33 +577,40 @@ func TestValidateReplicationController(t *testing.T) {
566 566
 
567 567
 	errorCases := map[string]api.ReplicationController{
568 568
 		"zero-length ID": {
569
-			JSONBase: api.JSONBase{ID: ""},
569
+			JSONBase: api.JSONBase{ID: "", Namespace: api.NamespaceDefault},
570
+			DesiredState: api.ReplicationControllerState{
571
+				ReplicaSelector: validSelector,
572
+				PodTemplate:     validPodTemplate,
573
+			},
574
+		},
575
+		"missing-namespace": {
576
+			JSONBase: api.JSONBase{ID: "abc-123"},
570 577
 			DesiredState: api.ReplicationControllerState{
571 578
 				ReplicaSelector: validSelector,
572 579
 				PodTemplate:     validPodTemplate,
573 580
 			},
574 581
 		},
575 582
 		"empty selector": {
576
-			JSONBase: api.JSONBase{ID: "abc"},
583
+			JSONBase: api.JSONBase{ID: "abc", Namespace: api.NamespaceDefault},
577 584
 			DesiredState: api.ReplicationControllerState{
578 585
 				PodTemplate: validPodTemplate,
579 586
 			},
580 587
 		},
581 588
 		"selector_doesnt_match": {
582
-			JSONBase: api.JSONBase{ID: "abc"},
589
+			JSONBase: api.JSONBase{ID: "abc", Namespace: api.NamespaceDefault},
583 590
 			DesiredState: api.ReplicationControllerState{
584 591
 				ReplicaSelector: map[string]string{"foo": "bar"},
585 592
 				PodTemplate:     validPodTemplate,
586 593
 			},
587 594
 		},
588 595
 		"invalid manifest": {
589
-			JSONBase: api.JSONBase{ID: "abc"},
596
+			JSONBase: api.JSONBase{ID: "abc", Namespace: api.NamespaceDefault},
590 597
 			DesiredState: api.ReplicationControllerState{
591 598
 				ReplicaSelector: validSelector,
592 599
 			},
593 600
 		},
594 601
 		"negative_replicas": {
595
-			JSONBase: api.JSONBase{ID: "abc"},
602
+			JSONBase: api.JSONBase{ID: "abc", Namespace: api.NamespaceDefault},
596 603
 			DesiredState: api.ReplicationControllerState{
597 604
 				Replicas:        -1,
598 605
 				ReplicaSelector: validSelector,
... ...
@@ -608,6 +626,7 @@ func TestValidateReplicationController(t *testing.T) {
608 608
 			field := errs[i].(errors.ValidationError).Field
609 609
 			if !strings.HasPrefix(field, "desiredState.podTemplate.") &&
610 610
 				field != "id" &&
611
+				field != "namespace" &&
611 612
 				field != "desiredState.replicaSelector" &&
612 613
 				field != "desiredState.replicas" {
613 614
 				t.Errorf("%s: missing prefix for: %v", k, errs[i])
... ...
@@ -45,11 +45,11 @@ const (
45 45
 	StatusUnprocessableEntity = 422
46 46
 )
47 47
 
48
-// Handle returns a Handler function that expose the provided storage interfaces
48
+// Handle returns a Handler function that exposes the provided storage interfaces
49 49
 // as RESTful resources at prefix, serialized by codec, and also includes the support
50 50
 // http resources.
51
-func Handle(storage map[string]RESTStorage, codec runtime.Codec, prefix string) http.Handler {
52
-	group := NewAPIGroup(storage, codec)
51
+func Handle(storage map[string]RESTStorage, codec runtime.Codec, prefix string, selfLinker runtime.SelfLinker) http.Handler {
52
+	group := NewAPIGroup(storage, codec, prefix, selfLinker)
53 53
 
54 54
 	mux := http.NewServeMux()
55 55
 	group.InstallREST(mux, prefix)
... ...
@@ -72,11 +72,13 @@ type APIGroup struct {
72 72
 // This is a helper method for registering multiple sets of REST handlers under different
73 73
 // prefixes onto a server.
74 74
 // TODO: add multitype codec serialization
75
-func NewAPIGroup(storage map[string]RESTStorage, codec runtime.Codec) *APIGroup {
75
+func NewAPIGroup(storage map[string]RESTStorage, codec runtime.Codec, canonicalPrefix string, selfLinker runtime.SelfLinker) *APIGroup {
76 76
 	return &APIGroup{RESTHandler{
77
-		storage: storage,
78
-		codec:   codec,
79
-		ops:     NewOperations(),
77
+		storage:         storage,
78
+		codec:           codec,
79
+		canonicalPrefix: canonicalPrefix,
80
+		selfLinker:      selfLinker,
81
+		ops:             NewOperations(),
80 82
 		// Delay just long enough to handle most simple write operations
81 83
 		asyncOpWait: time.Millisecond * 25,
82 84
 	}}
... ...
@@ -45,6 +45,7 @@ func convert(obj runtime.Object) (runtime.Object, error) {
45 45
 }
46 46
 
47 47
 var codec = latest.Codec
48
+var selfLinker = latest.SelfLinker
48 49
 
49 50
 func init() {
50 51
 	api.Scheme.AddKnownTypes("", &Simple{}, &SimpleList{})
... ...
@@ -88,18 +89,18 @@ type SimpleRESTStorage struct {
88 88
 	injectedFunction func(obj runtime.Object) (returnObj runtime.Object, err error)
89 89
 }
90 90
 
91
-func (storage *SimpleRESTStorage) List(label, field labels.Selector) (runtime.Object, error) {
91
+func (storage *SimpleRESTStorage) List(ctx api.Context, label, field labels.Selector) (runtime.Object, error) {
92 92
 	result := &SimpleList{
93 93
 		Items: storage.list,
94 94
 	}
95 95
 	return result, storage.errors["list"]
96 96
 }
97 97
 
98
-func (storage *SimpleRESTStorage) Get(id string) (runtime.Object, error) {
98
+func (storage *SimpleRESTStorage) Get(ctx api.Context, id string) (runtime.Object, error) {
99 99
 	return api.Scheme.CopyOrDie(&storage.item), storage.errors["get"]
100 100
 }
101 101
 
102
-func (storage *SimpleRESTStorage) Delete(id string) (<-chan runtime.Object, error) {
102
+func (storage *SimpleRESTStorage) Delete(ctx api.Context, id string) (<-chan runtime.Object, error) {
103 103
 	storage.deleted = id
104 104
 	if err := storage.errors["delete"]; err != nil {
105 105
 		return nil, err
... ...
@@ -116,7 +117,7 @@ func (storage *SimpleRESTStorage) New() runtime.Object {
116 116
 	return &Simple{}
117 117
 }
118 118
 
119
-func (storage *SimpleRESTStorage) Create(obj runtime.Object) (<-chan runtime.Object, error) {
119
+func (storage *SimpleRESTStorage) Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) {
120 120
 	storage.created = obj.(*Simple)
121 121
 	if err := storage.errors["create"]; err != nil {
122 122
 		return nil, err
... ...
@@ -129,7 +130,7 @@ func (storage *SimpleRESTStorage) Create(obj runtime.Object) (<-chan runtime.Obj
129 129
 	}), nil
130 130
 }
131 131
 
132
-func (storage *SimpleRESTStorage) Update(obj runtime.Object) (<-chan runtime.Object, error) {
132
+func (storage *SimpleRESTStorage) Update(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) {
133 133
 	storage.updated = obj.(*Simple)
134 134
 	if err := storage.errors["update"]; err != nil {
135 135
 		return nil, err
... ...
@@ -143,7 +144,7 @@ func (storage *SimpleRESTStorage) Update(obj runtime.Object) (<-chan runtime.Obj
143 143
 }
144 144
 
145 145
 // Implement ResourceWatcher.
146
-func (storage *SimpleRESTStorage) Watch(label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
146
+func (storage *SimpleRESTStorage) Watch(ctx api.Context, label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
147 147
 	storage.requestedLabelSelector = label
148 148
 	storage.requestedFieldSelector = field
149 149
 	storage.requestedResourceVersion = resourceVersion
... ...
@@ -155,7 +156,7 @@ func (storage *SimpleRESTStorage) Watch(label, field labels.Selector, resourceVe
155 155
 }
156 156
 
157 157
 // Implement Redirector.
158
-func (storage *SimpleRESTStorage) ResourceLocation(id string) (string, error) {
158
+func (storage *SimpleRESTStorage) ResourceLocation(ctx api.Context, id string) (string, error) {
159 159
 	storage.requestedResourceLocationID = id
160 160
 	if err := storage.errors["resourceLocation"]; err != nil {
161 161
 		return "", err
... ...
@@ -193,7 +194,7 @@ func TestNotFound(t *testing.T) {
193 193
 	}
194 194
 	handler := Handle(map[string]RESTStorage{
195 195
 		"foo": &SimpleRESTStorage{},
196
-	}, codec, "/prefix/version")
196
+	}, codec, "/prefix/version", selfLinker)
197 197
 	server := httptest.NewServer(handler)
198 198
 	client := http.Client{}
199 199
 	for k, v := range cases {
... ...
@@ -214,7 +215,7 @@ func TestNotFound(t *testing.T) {
214 214
 }
215 215
 
216 216
 func TestVersion(t *testing.T) {
217
-	handler := Handle(map[string]RESTStorage{}, codec, "/prefix/version")
217
+	handler := Handle(map[string]RESTStorage{}, codec, "/prefix/version", selfLinker)
218 218
 	server := httptest.NewServer(handler)
219 219
 	client := http.Client{}
220 220
 
... ...
@@ -243,7 +244,11 @@ func TestSimpleList(t *testing.T) {
243 243
 	storage := map[string]RESTStorage{}
244 244
 	simpleStorage := SimpleRESTStorage{}
245 245
 	storage["simple"] = &simpleStorage
246
-	handler := Handle(storage, codec, "/prefix/version")
246
+	selfLinker := &setTestSelfLinker{
247
+		t:           t,
248
+		expectedSet: "/prefix/version/simple",
249
+	}
250
+	handler := Handle(storage, codec, "/prefix/version", selfLinker)
247 251
 	server := httptest.NewServer(handler)
248 252
 
249 253
 	resp, err := http.Get(server.URL + "/prefix/version/simple")
... ...
@@ -254,6 +259,9 @@ func TestSimpleList(t *testing.T) {
254 254
 	if resp.StatusCode != http.StatusOK {
255 255
 		t.Errorf("Unexpected status: %d, Expected: %d, %#v", resp.StatusCode, http.StatusOK, resp)
256 256
 	}
257
+	if !selfLinker.called {
258
+		t.Errorf("Never set self link")
259
+	}
257 260
 }
258 261
 
259 262
 func TestErrorList(t *testing.T) {
... ...
@@ -262,7 +270,7 @@ func TestErrorList(t *testing.T) {
262 262
 		errors: map[string]error{"list": fmt.Errorf("test Error")},
263 263
 	}
264 264
 	storage["simple"] = &simpleStorage
265
-	handler := Handle(storage, codec, "/prefix/version")
265
+	handler := Handle(storage, codec, "/prefix/version", selfLinker)
266 266
 	server := httptest.NewServer(handler)
267 267
 
268 268
 	resp, err := http.Get(server.URL + "/prefix/version/simple")
... ...
@@ -286,7 +294,7 @@ func TestNonEmptyList(t *testing.T) {
286 286
 		},
287 287
 	}
288 288
 	storage["simple"] = &simpleStorage
289
-	handler := Handle(storage, codec, "/prefix/version")
289
+	handler := Handle(storage, codec, "/prefix/version", selfLinker)
290 290
 	server := httptest.NewServer(handler)
291 291
 
292 292
 	resp, err := http.Get(server.URL + "/prefix/version/simple")
... ...
@@ -320,8 +328,12 @@ func TestGet(t *testing.T) {
320 320
 			Name: "foo",
321 321
 		},
322 322
 	}
323
+	selfLinker := &setTestSelfLinker{
324
+		t:           t,
325
+		expectedSet: "/prefix/version/simple/id",
326
+	}
323 327
 	storage["simple"] = &simpleStorage
324
-	handler := Handle(storage, codec, "/prefix/version")
328
+	handler := Handle(storage, codec, "/prefix/version", selfLinker)
325 329
 	server := httptest.NewServer(handler)
326 330
 
327 331
 	resp, err := http.Get(server.URL + "/prefix/version/simple/id")
... ...
@@ -334,6 +346,9 @@ func TestGet(t *testing.T) {
334 334
 	if itemOut.Name != simpleStorage.item.Name {
335 335
 		t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body))
336 336
 	}
337
+	if !selfLinker.called {
338
+		t.Errorf("Never set self link")
339
+	}
337 340
 }
338 341
 
339 342
 func TestGetMissing(t *testing.T) {
... ...
@@ -342,7 +357,7 @@ func TestGetMissing(t *testing.T) {
342 342
 		errors: map[string]error{"get": apierrs.NewNotFound("simple", "id")},
343 343
 	}
344 344
 	storage["simple"] = &simpleStorage
345
-	handler := Handle(storage, codec, "/prefix/version")
345
+	handler := Handle(storage, codec, "/prefix/version", selfLinker)
346 346
 	server := httptest.NewServer(handler)
347 347
 
348 348
 	resp, err := http.Get(server.URL + "/prefix/version/simple/id")
... ...
@@ -360,7 +375,7 @@ func TestDelete(t *testing.T) {
360 360
 	simpleStorage := SimpleRESTStorage{}
361 361
 	ID := "id"
362 362
 	storage["simple"] = &simpleStorage
363
-	handler := Handle(storage, codec, "/prefix/version")
363
+	handler := Handle(storage, codec, "/prefix/version", selfLinker)
364 364
 	server := httptest.NewServer(handler)
365 365
 
366 366
 	client := http.Client{}
... ...
@@ -382,7 +397,7 @@ func TestDeleteMissing(t *testing.T) {
382 382
 		errors: map[string]error{"delete": apierrs.NewNotFound("simple", ID)},
383 383
 	}
384 384
 	storage["simple"] = &simpleStorage
385
-	handler := Handle(storage, codec, "/prefix/version")
385
+	handler := Handle(storage, codec, "/prefix/version", selfLinker)
386 386
 	server := httptest.NewServer(handler)
387 387
 
388 388
 	client := http.Client{}
... ...
@@ -402,7 +417,11 @@ func TestUpdate(t *testing.T) {
402 402
 	simpleStorage := SimpleRESTStorage{}
403 403
 	ID := "id"
404 404
 	storage["simple"] = &simpleStorage
405
-	handler := Handle(storage, codec, "/prefix/version")
405
+	selfLinker := &setTestSelfLinker{
406
+		t:           t,
407
+		expectedSet: "/prefix/version/simple/" + ID,
408
+	}
409
+	handler := Handle(storage, codec, "/prefix/version", selfLinker)
406 410
 	server := httptest.NewServer(handler)
407 411
 
408 412
 	item := &Simple{
... ...
@@ -423,6 +442,9 @@ func TestUpdate(t *testing.T) {
423 423
 	if simpleStorage.updated.Name != item.Name {
424 424
 		t.Errorf("Unexpected update value %#v, expected %#v.", simpleStorage.updated, item)
425 425
 	}
426
+	if !selfLinker.called {
427
+		t.Errorf("Never set self link")
428
+	}
426 429
 }
427 430
 
428 431
 func TestUpdateMissing(t *testing.T) {
... ...
@@ -432,7 +454,7 @@ func TestUpdateMissing(t *testing.T) {
432 432
 		errors: map[string]error{"update": apierrs.NewNotFound("simple", ID)},
433 433
 	}
434 434
 	storage["simple"] = &simpleStorage
435
-	handler := Handle(storage, codec, "/prefix/version")
435
+	handler := Handle(storage, codec, "/prefix/version", selfLinker)
436 436
 	server := httptest.NewServer(handler)
437 437
 
438 438
 	item := &Simple{
... ...
@@ -459,7 +481,7 @@ func TestCreate(t *testing.T) {
459 459
 	simpleStorage := &SimpleRESTStorage{}
460 460
 	handler := Handle(map[string]RESTStorage{
461 461
 		"foo": simpleStorage,
462
-	}, codec, "/prefix/version")
462
+	}, codec, "/prefix/version", selfLinker)
463 463
 	handler.(*defaultAPIServer).group.handler.asyncOpWait = 0
464 464
 	server := httptest.NewServer(handler)
465 465
 	client := http.Client{}
... ...
@@ -500,7 +522,7 @@ func TestCreateNotFound(t *testing.T) {
500 500
 			// See https://github.com/GoogleCloudPlatform/kubernetes/pull/486#discussion_r15037092.
501 501
 			errors: map[string]error{"create": apierrs.NewNotFound("simple", "id")},
502 502
 		},
503
-	}, codec, "/prefix/version")
503
+	}, codec, "/prefix/version", selfLinker)
504 504
 	server := httptest.NewServer(handler)
505 505
 	client := http.Client{}
506 506
 
... ...
@@ -533,6 +555,23 @@ func TestParseTimeout(t *testing.T) {
533 533
 	}
534 534
 }
535 535
 
536
+type setTestSelfLinker struct {
537
+	t           *testing.T
538
+	expectedSet string
539
+	id          string
540
+	called      bool
541
+}
542
+
543
+func (s *setTestSelfLinker) ID(runtime.Object) (string, error)     { return s.id, nil }
544
+func (*setTestSelfLinker) SelfLink(runtime.Object) (string, error) { return "", nil }
545
+func (s *setTestSelfLinker) SetSelfLink(obj runtime.Object, selfLink string) error {
546
+	if e, a := s.expectedSet, selfLink; e != a {
547
+		s.t.Errorf("expected '%v', got '%v'", e, a)
548
+	}
549
+	s.called = true
550
+	return nil
551
+}
552
+
536 553
 func TestSyncCreate(t *testing.T) {
537 554
 	storage := SimpleRESTStorage{
538 555
 		injectedFunction: func(obj runtime.Object) (runtime.Object, error) {
... ...
@@ -540,14 +579,19 @@ func TestSyncCreate(t *testing.T) {
540 540
 			return obj, nil
541 541
 		},
542 542
 	}
543
+	selfLinker := &setTestSelfLinker{
544
+		t:           t,
545
+		id:          "bar",
546
+		expectedSet: "/prefix/version/foo/bar",
547
+	}
543 548
 	handler := Handle(map[string]RESTStorage{
544 549
 		"foo": &storage,
545
-	}, codec, "/prefix/version")
550
+	}, codec, "/prefix/version", selfLinker)
546 551
 	server := httptest.NewServer(handler)
547 552
 	client := http.Client{}
548 553
 
549 554
 	simple := &Simple{
550
-		Name: "foo",
555
+		Name: "bar",
551 556
 	}
552 557
 	data, _ := codec.Encode(simple)
553 558
 	request, err := http.NewRequest("POST", server.URL+"/prefix/version/foo?sync=true", bytes.NewBuffer(data))
... ...
@@ -579,6 +623,9 @@ func TestSyncCreate(t *testing.T) {
579 579
 	if response.StatusCode != http.StatusOK {
580 580
 		t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusOK, response)
581 581
 	}
582
+	if !selfLinker.called {
583
+		t.Errorf("Never set self link")
584
+	}
582 585
 }
583 586
 
584 587
 func expectApiStatus(t *testing.T, method, url string, data []byte, code int) *api.Status {
... ...
@@ -611,7 +658,7 @@ func TestAsyncDelayReturnsError(t *testing.T) {
611 611
 			return nil, apierrs.NewAlreadyExists("foo", "bar")
612 612
 		},
613 613
 	}
614
-	handler := Handle(map[string]RESTStorage{"foo": &storage}, codec, "/prefix/version")
614
+	handler := Handle(map[string]RESTStorage{"foo": &storage}, codec, "/prefix/version", selfLinker)
615 615
 	handler.(*defaultAPIServer).group.handler.asyncOpWait = time.Millisecond / 2
616 616
 	server := httptest.NewServer(handler)
617 617
 
... ...
@@ -629,11 +676,16 @@ func TestAsyncCreateError(t *testing.T) {
629 629
 			return nil, apierrs.NewAlreadyExists("foo", "bar")
630 630
 		},
631 631
 	}
632
-	handler := Handle(map[string]RESTStorage{"foo": &storage}, codec, "/prefix/version")
632
+	selfLinker := &setTestSelfLinker{
633
+		t:           t,
634
+		id:          "bar",
635
+		expectedSet: "/prefix/version/foo/bar",
636
+	}
637
+	handler := Handle(map[string]RESTStorage{"foo": &storage}, codec, "/prefix/version", selfLinker)
633 638
 	handler.(*defaultAPIServer).group.handler.asyncOpWait = 0
634 639
 	server := httptest.NewServer(handler)
635 640
 
636
-	simple := &Simple{Name: "foo"}
641
+	simple := &Simple{Name: "bar"}
637 642
 	data, _ := codec.Encode(simple)
638 643
 
639 644
 	status := expectApiStatus(t, "POST", fmt.Sprintf("%s/prefix/version/foo", server.URL), data, http.StatusAccepted)
... ...
@@ -654,7 +706,7 @@ func TestAsyncCreateError(t *testing.T) {
654 654
 	expectedStatus := &api.Status{
655 655
 		Status:  api.StatusFailure,
656 656
 		Code:    http.StatusConflict,
657
-		Reason:  "already_exists",
657
+		Reason:  "AlreadyExists",
658 658
 		Message: expectedErr.Error(),
659 659
 		Details: &api.StatusDetails{
660 660
 			Kind: "foo",
... ...
@@ -667,6 +719,9 @@ func TestAsyncCreateError(t *testing.T) {
667 667
 			t.Logf("Details %#v, Got %#v", *expectedStatus.Details, *finalStatus.Details)
668 668
 		}
669 669
 	}
670
+	if !selfLinker.called {
671
+		t.Errorf("Never set self link")
672
+	}
670 673
 }
671 674
 
672 675
 type UnregisteredAPIObject struct {
... ...
@@ -723,7 +778,7 @@ func TestSyncCreateTimeout(t *testing.T) {
723 723
 	}
724 724
 	handler := Handle(map[string]RESTStorage{
725 725
 		"foo": &storage,
726
-	}, codec, "/prefix/version")
726
+	}, codec, "/prefix/version", selfLinker)
727 727
 	server := httptest.NewServer(handler)
728 728
 
729 729
 	simple := &Simple{Name: "foo"}
... ...
@@ -753,7 +808,10 @@ func TestCORSAllowedOrigins(t *testing.T) {
753 753
 			t.Errorf("unexpected error: %v", err)
754 754
 		}
755 755
 
756
-		handler := CORS(Handle(map[string]RESTStorage{}, codec, "/prefix/version"), allowedOriginRegexps, nil, nil, "true")
756
+		handler := CORS(
757
+			Handle(map[string]RESTStorage{}, codec, "/prefix/version", selfLinker),
758
+			allowedOriginRegexps, nil, nil, "true",
759
+		)
757 760
 		server := httptest.NewServer(handler)
758 761
 		client := http.Client{}
759 762
 
... ...
@@ -22,6 +22,7 @@ import (
22 22
 
23 23
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
24 24
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
25
+	"github.com/golang/glog"
25 26
 )
26 27
 
27 28
 // statusError is an object that can be converted into an api.Status
... ...
@@ -44,6 +45,11 @@ func errToAPIStatus(err error) *api.Status {
44 44
 		case tools.IsEtcdTestFailed(err):
45 45
 			status = http.StatusConflict
46 46
 		}
47
+		// Log errors that were not converted to an error status
48
+		// by REST storage - these typically indicate programmer
49
+		// error by not using pkg/api/errors, or unexpected failure
50
+		// cases.
51
+		glog.V(1).Infof("An unchecked error was received: %v", err)
47 52
 		return &api.Status{
48 53
 			Status:  api.StatusFailure,
49 54
 			Code:    status,
... ...
@@ -39,7 +39,7 @@ func TestErrorsToAPIStatus(t *testing.T) {
39 39
 		errors.NewAlreadyExists("foo", "bar"): {
40 40
 			Status:  api.StatusFailure,
41 41
 			Code:    http.StatusConflict,
42
-			Reason:  "already_exists",
42
+			Reason:  "AlreadyExists",
43 43
 			Message: "foo \"bar\" already exists",
44 44
 			Details: &api.StatusDetails{
45 45
 				Kind: "foo",
... ...
@@ -49,7 +49,7 @@ func TestErrorsToAPIStatus(t *testing.T) {
49 49
 		errors.NewConflict("foo", "bar", stderrs.New("failure")): {
50 50
 			Status:  api.StatusFailure,
51 51
 			Code:    http.StatusConflict,
52
-			Reason:  "conflict",
52
+			Reason:  "Conflict",
53 53
 			Message: "foo \"bar\" cannot be updated: failure",
54 54
 			Details: &api.StatusDetails{
55 55
 				Kind: "foo",
... ...
@@ -17,6 +17,7 @@ limitations under the License.
17 17
 package apiserver
18 18
 
19 19
 import (
20
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
20 21
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
21 22
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
22 23
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
... ...
@@ -30,20 +31,20 @@ type RESTStorage interface {
30 30
 	New() runtime.Object
31 31
 
32 32
 	// List selects resources in the storage which match to the selector.
33
-	List(label, field labels.Selector) (runtime.Object, error)
33
+	List(ctx api.Context, label, field labels.Selector) (runtime.Object, error)
34 34
 
35 35
 	// Get finds a resource in the storage by id and returns it.
36 36
 	// Although it can return an arbitrary error value, IsNotFound(err) is true for the
37 37
 	// returned error value err when the specified resource is not found.
38
-	Get(id string) (runtime.Object, error)
38
+	Get(ctx api.Context, id string) (runtime.Object, error)
39 39
 
40 40
 	// Delete finds a resource in the storage and deletes it.
41 41
 	// Although it can return an arbitrary error value, IsNotFound(err) is true for the
42 42
 	// returned error value err when the specified resource is not found.
43
-	Delete(id string) (<-chan runtime.Object, error)
43
+	Delete(ctx api.Context, id string) (<-chan runtime.Object, error)
44 44
 
45
-	Create(runtime.Object) (<-chan runtime.Object, error)
46
-	Update(runtime.Object) (<-chan runtime.Object, error)
45
+	Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error)
46
+	Update(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error)
47 47
 }
48 48
 
49 49
 // ResourceWatcher should be implemented by all RESTStorage objects that
... ...
@@ -53,11 +54,11 @@ type ResourceWatcher interface {
53 53
 	// are supported; an error should be returned if 'field' tries to select on a field that
54 54
 	// isn't supported. 'resourceVersion' allows for continuing/starting a watch at a
55 55
 	// particular version.
56
-	Watch(label, field labels.Selector, resourceVersion uint64) (watch.Interface, error)
56
+	Watch(ctx api.Context, label, field labels.Selector, resourceVersion uint64) (watch.Interface, error)
57 57
 }
58 58
 
59 59
 // Redirector know how to return a remote resource's location.
60 60
 type Redirector interface {
61 61
 	// ResourceLocation should return the remote location of the given resource, or an error.
62
-	ResourceLocation(id string) (remoteLocation string, err error)
62
+	ResourceLocation(ctx api.Context, id string) (remoteLocation string, err error)
63 63
 }
... ...
@@ -127,7 +127,7 @@ func TestApiServerMinionProxy(t *testing.T) {
127 127
 	proxyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
128 128
 		w.Write([]byte(req.URL.Path))
129 129
 	}))
130
-	server := httptest.NewServer(Handle(nil, nil, "/prefix"))
130
+	server := httptest.NewServer(Handle(nil, nil, "/prefix", selfLinker))
131 131
 	proxy, _ := url.Parse(proxyServer.URL)
132 132
 	resp, err := http.Get(fmt.Sprintf("%s/proxy/minion/%s%s", server.URL, proxy.Host, "/test"))
133 133
 	if err != nil {
... ...
@@ -63,12 +63,13 @@ func (h *OperationHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
63 63
 
64 64
 // Operation represents an ongoing action which the server is performing.
65 65
 type Operation struct {
66
-	ID       string
67
-	result   runtime.Object
68
-	awaiting <-chan runtime.Object
69
-	finished *time.Time
70
-	lock     sync.Mutex
71
-	notify   chan struct{}
66
+	ID        string
67
+	result    runtime.Object
68
+	onReceive func(runtime.Object)
69
+	awaiting  <-chan runtime.Object
70
+	finished  *time.Time
71
+	lock      sync.Mutex
72
+	notify    chan struct{}
72 73
 }
73 74
 
74 75
 // Operations tracks all the ongoing operations.
... ...
@@ -90,13 +91,15 @@ func NewOperations() *Operations {
90 90
 	return ops
91 91
 }
92 92
 
93
-// NewOperation adds a new operation. It is lock-free.
94
-func (ops *Operations) NewOperation(from <-chan runtime.Object) *Operation {
93
+// NewOperation adds a new operation. It is lock-free. 'onReceive' will be called
94
+// with the value read from 'from', when it is read.
95
+func (ops *Operations) NewOperation(from <-chan runtime.Object, onReceive func(runtime.Object)) *Operation {
95 96
 	id := atomic.AddInt64(&ops.lastID, 1)
96 97
 	op := &Operation{
97
-		ID:       strconv.FormatInt(id, 10),
98
-		awaiting: from,
99
-		notify:   make(chan struct{}),
98
+		ID:        strconv.FormatInt(id, 10),
99
+		awaiting:  from,
100
+		onReceive: onReceive,
101
+		notify:    make(chan struct{}),
100 102
 	}
101 103
 	go op.wait()
102 104
 	go ops.insert(op)
... ...
@@ -159,6 +162,9 @@ func (op *Operation) wait() {
159 159
 
160 160
 	op.lock.Lock()
161 161
 	defer op.lock.Unlock()
162
+	if op.onReceive != nil {
163
+		op.onReceive(result)
164
+	}
162 165
 	op.result = result
163 166
 	finished := time.Now()
164 167
 	op.finished = &finished
... ...
@@ -35,7 +35,8 @@ func TestOperation(t *testing.T) {
35 35
 	ops := NewOperations()
36 36
 
37 37
 	c := make(chan runtime.Object)
38
-	op := ops.NewOperation(c)
38
+	called := make(chan struct{})
39
+	op := ops.NewOperation(c, func(runtime.Object) { go close(called) })
39 40
 	// Allow context switch, so that op's ID can get added to the map and Get will work.
40 41
 	// This is just so we can test Get. Ordinary users have no need to call Get immediately
41 42
 	// after calling NewOperation, because it returns the operation directly.
... ...
@@ -72,6 +73,11 @@ func TestOperation(t *testing.T) {
72 72
 		t.Errorf("Unexpectedly slow completion")
73 73
 	}
74 74
 
75
+	_, open := <-called
76
+	if open {
77
+		t.Errorf("expected hook to be called!")
78
+	}
79
+
75 80
 	time.Sleep(100 * time.Millisecond)
76 81
 	finished := atomic.LoadInt32(&waited)
77 82
 	if finished != waiters {
... ...
@@ -107,7 +113,7 @@ func TestOperationsList(t *testing.T) {
107 107
 	}
108 108
 	handler := Handle(map[string]RESTStorage{
109 109
 		"foo": simpleStorage,
110
-	}, codec, "/prefix/version")
110
+	}, codec, "/prefix/version", selfLinker)
111 111
 	handler.(*defaultAPIServer).group.handler.asyncOpWait = 0
112 112
 	server := httptest.NewServer(handler)
113 113
 	client := http.Client{}
... ...
@@ -163,7 +169,7 @@ func TestOpGet(t *testing.T) {
163 163
 	}
164 164
 	handler := Handle(map[string]RESTStorage{
165 165
 		"foo": simpleStorage,
166
-	}, codec, "/prefix/version")
166
+	}, codec, "/prefix/version", selfLinker)
167 167
 	handler.(*defaultAPIServer).group.handler.asyncOpWait = 0
168 168
 	server := httptest.NewServer(handler)
169 169
 	client := http.Client{}
... ...
@@ -26,6 +26,7 @@ import (
26 26
 	"path"
27 27
 	"strings"
28 28
 
29
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
29 30
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
30 31
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
31 32
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
... ...
@@ -76,6 +77,7 @@ type ProxyHandler struct {
76 76
 }
77 77
 
78 78
 func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
79
+	ctx := api.NewContext()
79 80
 	parts := strings.SplitN(req.URL.Path, "/", 3)
80 81
 	if len(parts) < 2 {
81 82
 		notFound(w, req)
... ...
@@ -101,7 +103,7 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
101 101
 		return
102 102
 	}
103 103
 
104
-	location, err := redirector.ResourceLocation(id)
104
+	location, err := redirector.ResourceLocation(ctx, id)
105 105
 	if err != nil {
106 106
 		status := errToAPIStatus(err)
107 107
 		writeJSON(status.Code, r.codec, status, w)
... ...
@@ -161,7 +161,7 @@ func TestProxy(t *testing.T) {
161 161
 		}
162 162
 		handler := Handle(map[string]RESTStorage{
163 163
 			"foo": simpleStorage,
164
-		}, codec, "/prefix/version")
164
+		}, codec, "/prefix/version", selfLinker)
165 165
 		server := httptest.NewServer(handler)
166 166
 
167 167
 		req, err := http.NewRequest(
... ...
@@ -19,6 +19,7 @@ package apiserver
19 19
 import (
20 20
 	"net/http"
21 21
 
22
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
22 23
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
23 24
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
24 25
 )
... ...
@@ -29,6 +30,7 @@ type RedirectHandler struct {
29 29
 }
30 30
 
31 31
 func (r *RedirectHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
32
+	ctx := api.NewContext()
32 33
 	parts := splitPath(req.URL.Path)
33 34
 	if len(parts) != 2 || req.Method != "GET" {
34 35
 		notFound(w, req)
... ...
@@ -50,7 +52,7 @@ func (r *RedirectHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
50 50
 		return
51 51
 	}
52 52
 
53
-	location, err := redirector.ResourceLocation(id)
53
+	location, err := redirector.ResourceLocation(ctx, id)
54 54
 	if err != nil {
55 55
 		status := errToAPIStatus(err)
56 56
 		writeJSON(status.Code, r.codec, status, w)
... ...
@@ -30,7 +30,7 @@ func TestRedirect(t *testing.T) {
30 30
 	}
31 31
 	handler := Handle(map[string]RESTStorage{
32 32
 		"foo": simpleStorage,
33
-	}, codec, "/prefix/version")
33
+	}, codec, "/prefix/version", selfLinker)
34 34
 	server := httptest.NewServer(handler)
35 35
 
36 36
 	dontFollow := errors.New("don't follow")
... ...
@@ -18,19 +18,24 @@ package apiserver
18 18
 
19 19
 import (
20 20
 	"net/http"
21
+	"path"
21 22
 	"time"
22 23
 
23 24
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
24 25
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
25 26
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
26 27
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
28
+
29
+	"github.com/golang/glog"
27 30
 )
28 31
 
29 32
 type RESTHandler struct {
30
-	storage     map[string]RESTStorage
31
-	codec       runtime.Codec
32
-	ops         *Operations
33
-	asyncOpWait time.Duration
33
+	storage         map[string]RESTStorage
34
+	codec           runtime.Codec
35
+	canonicalPrefix string
36
+	selfLinker      runtime.SelfLinker
37
+	ops             *Operations
38
+	asyncOpWait     time.Duration
34 39
 }
35 40
 
36 41
 // ServeHTTP handles requests to all RESTStorage objects.
... ...
@@ -50,6 +55,37 @@ func (h *RESTHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
50 50
 	h.handleRESTStorage(parts, req, w, storage)
51 51
 }
52 52
 
53
+// Sets the SelfLink field of the object.
54
+func (h *RESTHandler) setSelfLink(obj runtime.Object, req *http.Request) error {
55
+	newURL := *req.URL
56
+	newURL.Path = path.Join(h.canonicalPrefix, req.URL.Path)
57
+	newURL.RawQuery = ""
58
+	newURL.Fragment = ""
59
+	return h.selfLinker.SetSelfLink(obj, newURL.String())
60
+}
61
+
62
+// Like setSelfLink, but appends the object's id.
63
+func (h *RESTHandler) setSelfLinkAddID(obj runtime.Object, req *http.Request) error {
64
+	id, err := h.selfLinker.ID(obj)
65
+	if err != nil {
66
+		return err
67
+	}
68
+	newURL := *req.URL
69
+	newURL.Path = path.Join(h.canonicalPrefix, req.URL.Path, id)
70
+	newURL.RawQuery = ""
71
+	newURL.Fragment = ""
72
+	return h.selfLinker.SetSelfLink(obj, newURL.String())
73
+}
74
+
75
+// curry adapts either of the self link setting functions into a function appropriate for operation's hook.
76
+func curry(f func(runtime.Object, *http.Request) error, req *http.Request) func(runtime.Object) {
77
+	return func(obj runtime.Object) {
78
+		if err := f(obj, req); err != nil {
79
+			glog.Errorf("unable to set self link for %#v: %v", obj, err)
80
+		}
81
+	}
82
+}
83
+
53 84
 // handleRESTStorage is the main dispatcher for a storage object.  It switches on the HTTP method, and then
54 85
 // on path length, according to the following table:
55 86
 //   Method     Path          Action
... ...
@@ -64,6 +100,8 @@ func (h *RESTHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
64 64
 //    timeout=<duration> Timeout for synchronous requests, only applies if sync=true
65 65
 //    labels=<label-selector> Used for filtering list operations
66 66
 func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w http.ResponseWriter, storage RESTStorage) {
67
+	// TODO for now, we perform all operations in the default namespace
68
+	ctx := api.NewDefaultContext()
67 69
 	sync := req.URL.Query().Get("sync") == "true"
68 70
 	timeout := parseTimeout(req.URL.Query().Get("timeout"))
69 71
 	switch req.Method {
... ...
@@ -80,18 +118,26 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt
80 80
 				errorJSON(err, h.codec, w)
81 81
 				return
82 82
 			}
83
-			list, err := storage.List(label, field)
83
+			list, err := storage.List(ctx, label, field)
84 84
 			if err != nil {
85 85
 				errorJSON(err, h.codec, w)
86 86
 				return
87 87
 			}
88
+			if err := h.setSelfLink(list, req); err != nil {
89
+				errorJSON(err, h.codec, w)
90
+				return
91
+			}
88 92
 			writeJSON(http.StatusOK, h.codec, list, w)
89 93
 		case 2:
90
-			item, err := storage.Get(parts[1])
94
+			item, err := storage.Get(ctx, parts[1])
91 95
 			if err != nil {
92 96
 				errorJSON(err, h.codec, w)
93 97
 				return
94 98
 			}
99
+			if err := h.setSelfLink(item, req); err != nil {
100
+				errorJSON(err, h.codec, w)
101
+				return
102
+			}
95 103
 			writeJSON(http.StatusOK, h.codec, item, w)
96 104
 		default:
97 105
 			notFound(w, req)
... ...
@@ -113,12 +159,12 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt
113 113
 			errorJSON(err, h.codec, w)
114 114
 			return
115 115
 		}
116
-		out, err := storage.Create(obj)
116
+		out, err := storage.Create(ctx, obj)
117 117
 		if err != nil {
118 118
 			errorJSON(err, h.codec, w)
119 119
 			return
120 120
 		}
121
-		op := h.createOperation(out, sync, timeout)
121
+		op := h.createOperation(out, sync, timeout, curry(h.setSelfLinkAddID, req))
122 122
 		h.finishReq(op, req, w)
123 123
 
124 124
 	case "DELETE":
... ...
@@ -126,12 +172,12 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt
126 126
 			notFound(w, req)
127 127
 			return
128 128
 		}
129
-		out, err := storage.Delete(parts[1])
129
+		out, err := storage.Delete(ctx, parts[1])
130 130
 		if err != nil {
131 131
 			errorJSON(err, h.codec, w)
132 132
 			return
133 133
 		}
134
-		op := h.createOperation(out, sync, timeout)
134
+		op := h.createOperation(out, sync, timeout, nil)
135 135
 		h.finishReq(op, req, w)
136 136
 
137 137
 	case "PUT":
... ...
@@ -150,12 +196,12 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt
150 150
 			errorJSON(err, h.codec, w)
151 151
 			return
152 152
 		}
153
-		out, err := storage.Update(obj)
153
+		out, err := storage.Update(ctx, obj)
154 154
 		if err != nil {
155 155
 			errorJSON(err, h.codec, w)
156 156
 			return
157 157
 		}
158
-		op := h.createOperation(out, sync, timeout)
158
+		op := h.createOperation(out, sync, timeout, curry(h.setSelfLink, req))
159 159
 		h.finishReq(op, req, w)
160 160
 
161 161
 	default:
... ...
@@ -164,8 +210,8 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt
164 164
 }
165 165
 
166 166
 // createOperation creates an operation to process a channel response.
167
-func (h *RESTHandler) createOperation(out <-chan runtime.Object, sync bool, timeout time.Duration) *Operation {
168
-	op := h.ops.NewOperation(out)
167
+func (h *RESTHandler) createOperation(out <-chan runtime.Object, sync bool, timeout time.Duration, onReceive func(runtime.Object)) *Operation {
168
+	op := h.ops.NewOperation(out, onReceive)
169 169
 	if sync {
170 170
 		op.WaitFor(timeout)
171 171
 	} else if h.asyncOpWait != 0 {
... ...
@@ -24,6 +24,7 @@ import (
24 24
 	"strings"
25 25
 
26 26
 	"code.google.com/p/go.net/websocket"
27
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
27 28
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
28 29
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
29 30
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
... ...
@@ -61,6 +62,7 @@ func isWebsocketRequest(req *http.Request) bool {
61 61
 
62 62
 // ServeHTTP processes watch requests.
63 63
 func (h *WatchHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
64
+	ctx := api.NewContext()
64 65
 	parts := splitPath(req.URL.Path)
65 66
 	if len(parts) < 1 || req.Method != "GET" {
66 67
 		notFound(w, req)
... ...
@@ -73,7 +75,7 @@ func (h *WatchHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
73 73
 	}
74 74
 	if watcher, ok := storage.(ResourceWatcher); ok {
75 75
 		label, field, resourceVersion := getWatchParams(req.URL.Query())
76
-		watching, err := watcher.Watch(label, field, resourceVersion)
76
+		watching, err := watcher.Watch(ctx, label, field, resourceVersion)
77 77
 		if err != nil {
78 78
 			errorJSON(err, h.codec, w)
79 79
 			return
... ...
@@ -49,7 +49,7 @@ func TestWatchWebsocket(t *testing.T) {
49 49
 	_ = ResourceWatcher(simpleStorage) // Give compile error if this doesn't work.
50 50
 	handler := Handle(map[string]RESTStorage{
51 51
 		"foo": simpleStorage,
52
-	}, codec, "/prefix/version")
52
+	}, codec, "/prefix/version", selfLinker)
53 53
 	server := httptest.NewServer(handler)
54 54
 
55 55
 	dest, _ := url.Parse(server.URL)
... ...
@@ -95,7 +95,7 @@ func TestWatchHTTP(t *testing.T) {
95 95
 	simpleStorage := &SimpleRESTStorage{}
96 96
 	handler := Handle(map[string]RESTStorage{
97 97
 		"foo": simpleStorage,
98
-	}, codec, "/prefix/version")
98
+	}, codec, "/prefix/version", selfLinker)
99 99
 	server := httptest.NewServer(handler)
100 100
 	client := http.Client{}
101 101
 
... ...
@@ -148,7 +148,7 @@ func TestWatchParamParsing(t *testing.T) {
148 148
 	simpleStorage := &SimpleRESTStorage{}
149 149
 	handler := Handle(map[string]RESTStorage{
150 150
 		"foo": simpleStorage,
151
-	}, codec, "/prefix/version")
151
+	}, codec, "/prefix/version", selfLinker)
152 152
 	server := httptest.NewServer(handler)
153 153
 
154 154
 	dest, _ := url.Parse(server.URL)
... ...
@@ -210,7 +210,7 @@ func TestWatchProtocolSelection(t *testing.T) {
210 210
 	simpleStorage := &SimpleRESTStorage{}
211 211
 	handler := Handle(map[string]RESTStorage{
212 212
 		"foo": simpleStorage,
213
-	}, codec, "/prefix/version")
213
+	}, codec, "/prefix/version", selfLinker)
214 214
 	server := httptest.NewServer(handler)
215 215
 	client := http.Client{}
216 216
 
... ...
@@ -22,6 +22,7 @@ import (
22 22
 	"reflect"
23 23
 	"time"
24 24
 
25
+	apierrs "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
25 26
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
26 27
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
27 28
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
... ...
@@ -101,7 +102,7 @@ func (r *Reflector) listAndWatch() {
101 101
 			return
102 102
 		}
103 103
 		if err := r.watchHandler(w, &resourceVersion); err != nil {
104
-			glog.Errorf("failed to watch %v: %v", r.expectedType, err)
104
+			glog.Errorf("watch of %v ended with error: %v", r.expectedType, err)
105 105
 			return
106 106
 		}
107 107
 	}
... ...
@@ -131,6 +132,9 @@ func (r *Reflector) watchHandler(w watch.Interface, resourceVersion *uint64) err
131 131
 		if !ok {
132 132
 			break
133 133
 		}
134
+		if event.Type == watch.Error {
135
+			return apierrs.FromObject(event.Object)
136
+		}
134 137
 		if e, a := r.expectedType, reflect.TypeOf(event.Object); e != a {
135 138
 			glog.Errorf("expected type %v, but watch event object had type %v", e, a)
136 139
 			continue
... ...
@@ -162,6 +166,6 @@ func (r *Reflector) watchHandler(w watch.Interface, resourceVersion *uint64) err
162 162
 		glog.Errorf("unexpected watch close - watch lasted less than a second and no items received")
163 163
 		return errors.New("very short watch")
164 164
 	}
165
-	glog.Infof("unexpected watch close - %v total items received", eventCount)
165
+	glog.V(4).Infof("watch close - %v total items received", eventCount)
166 166
 	return nil
167 167
 }
... ...
@@ -17,20 +17,11 @@ limitations under the License.
17 17
 package client
18 18
 
19 19
 import (
20
-	"crypto/tls"
21
-	"crypto/x509"
22 20
 	"encoding/json"
23 21
 	"fmt"
24
-	"io/ioutil"
25
-	"net/http"
26
-	"net/url"
27
-	"strings"
28
-	"time"
29 22
 
30 23
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
31
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
32 24
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
33
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
34 25
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
35 26
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
36 27
 )
... ...
@@ -48,37 +39,38 @@ type Interface interface {
48 48
 
49 49
 // PodInterface has methods to work with Pod resources.
50 50
 type PodInterface interface {
51
-	ListPods(selector labels.Selector) (*api.PodList, error)
52
-	GetPod(id string) (*api.Pod, error)
53
-	DeletePod(id string) error
54
-	CreatePod(*api.Pod) (*api.Pod, error)
55
-	UpdatePod(*api.Pod) (*api.Pod, error)
51
+	ListPods(ctx api.Context, selector labels.Selector) (*api.PodList, error)
52
+	GetPod(ctx api.Context, id string) (*api.Pod, error)
53
+	DeletePod(ctx api.Context, id string) error
54
+	CreatePod(ctx api.Context, pod *api.Pod) (*api.Pod, error)
55
+	UpdatePod(ctx api.Context, pod *api.Pod) (*api.Pod, error)
56 56
 }
57 57
 
58 58
 // ReplicationControllerInterface has methods to work with ReplicationController resources.
59 59
 type ReplicationControllerInterface interface {
60
-	ListReplicationControllers(selector labels.Selector) (*api.ReplicationControllerList, error)
61
-	GetReplicationController(id string) (*api.ReplicationController, error)
62
-	CreateReplicationController(*api.ReplicationController) (*api.ReplicationController, error)
63
-	UpdateReplicationController(*api.ReplicationController) (*api.ReplicationController, error)
64
-	DeleteReplicationController(string) error
65
-	WatchReplicationControllers(label, field labels.Selector, resourceVersion uint64) (watch.Interface, error)
60
+	ListReplicationControllers(ctx api.Context, selector labels.Selector) (*api.ReplicationControllerList, error)
61
+	GetReplicationController(ctx api.Context, id string) (*api.ReplicationController, error)
62
+	CreateReplicationController(ctx api.Context, ctrl *api.ReplicationController) (*api.ReplicationController, error)
63
+	UpdateReplicationController(ctx api.Context, ctrl *api.ReplicationController) (*api.ReplicationController, error)
64
+	DeleteReplicationController(ctx api.Context, id string) error
65
+	WatchReplicationControllers(ctx api.Context, label, field labels.Selector, resourceVersion uint64) (watch.Interface, error)
66 66
 }
67 67
 
68 68
 // ServiceInterface has methods to work with Service resources.
69 69
 type ServiceInterface interface {
70
-	ListServices(selector labels.Selector) (*api.ServiceList, error)
71
-	GetService(id string) (*api.Service, error)
72
-	CreateService(*api.Service) (*api.Service, error)
73
-	UpdateService(*api.Service) (*api.Service, error)
74
-	DeleteService(string) error
75
-	WatchServices(label, field labels.Selector, resourceVersion uint64) (watch.Interface, error)
70
+	ListServices(ctx api.Context, selector labels.Selector) (*api.ServiceList, error)
71
+	GetService(ctx api.Context, id string) (*api.Service, error)
72
+	CreateService(ctx api.Context, srv *api.Service) (*api.Service, error)
73
+	UpdateService(ctx api.Context, srv *api.Service) (*api.Service, error)
74
+	DeleteService(ctx api.Context, id string) error
75
+	WatchServices(ctx api.Context, label, field labels.Selector, resourceVersion uint64) (watch.Interface, error)
76 76
 }
77 77
 
78 78
 // EndpointsInterface has methods to work with Endpoints resources
79 79
 type EndpointsInterface interface {
80
-	ListEndpoints(selector labels.Selector) (*api.EndpointsList, error)
81
-	WatchEndpoints(label, field labels.Selector, resourceVersion uint64) (watch.Interface, error)
80
+	ListEndpoints(ctx api.Context, selector labels.Selector) (*api.EndpointsList, error)
81
+	GetEndpoints(ctx api.Context, id string) (*api.Endpoints, error)
82
+	WatchEndpoints(ctx api.Context, label, field labels.Selector, resourceVersion uint64) (watch.Interface, error)
82 83
 }
83 84
 
84 85
 // VersionInterface has a method to retrieve the server version.
... ...
@@ -90,233 +82,45 @@ type MinionInterface interface {
90 90
 	ListMinions() (*api.MinionList, error)
91 91
 }
92 92
 
93
-// Client is the actual implementation of a Kubernetes client.
94
-type Client struct {
95
-	*RESTClient
96
-}
97
-
98
-// New creates a Kubernetes client. This client works with pods, replication controllers
99
-// and services. It allows operations such as list, get, update and delete on these objects.
100
-// host must be a host string, a host:port combo, or an http or https URL.  Passing a prefix
101
-// to a URL will prepend the server path. The API version to use may be specified or left
102
-// empty to use the client preferred version. Returns an error if host cannot be converted to
103
-// a valid URL.
104
-func New(host, version string, auth *AuthInfo) (*Client, error) {
105
-	if version == "" {
106
-		// Clients default to the preferred code API version
107
-		// TODO: implement version negotation (highest version supported by server)
108
-		version = latest.Version
109
-	}
110
-	serverCodec, _, err := latest.InterfacesFor(version)
111
-	if err != nil {
112
-		return nil, fmt.Errorf("API version '%s' is not recognized (valid values: %s)", version, strings.Join(latest.Versions, ", "))
113
-	}
114
-	prefix := fmt.Sprintf("/api/%s/", version)
115
-	restClient, err := NewRESTClient(host, auth, prefix, serverCodec)
116
-	if err != nil {
117
-		return nil, fmt.Errorf("API URL '%s' is not valid: %v", host, err)
118
-	}
119
-	return &Client{restClient}, nil
120
-}
121
-
122
-// NewOrDie creates a Kubernetes client and panics if the provided host is invalid.
123
-func NewOrDie(host, version string, auth *AuthInfo) *Client {
124
-	client, err := New(host, version, auth)
125
-	if err != nil {
126
-		panic(err)
127
-	}
128
-	return client
129
-}
130
-
131
-// StatusErr might get returned from an api call if your request is still being processed
132
-// and hence the expected return data is not available yet.
133
-type StatusErr struct {
134
-	Status api.Status
93
+// APIStatus is exposed by errors that can be converted to an api.Status object
94
+// for finer grained details.
95
+type APIStatus interface {
96
+	Status() api.Status
135 97
 }
136 98
 
137
-func (s *StatusErr) Error() string {
138
-	return fmt.Sprintf("Status: %v (%#v)", s.Status.Status, s.Status)
139
-}
140
-
141
-// AuthInfo is used to store authorization information.
142
-type AuthInfo struct {
143
-	User     string
144
-	Password string
145
-	CAFile   string
146
-	CertFile string
147
-	KeyFile  string
148
-}
149
-
150
-// RESTClient holds common code used to work with API resources that follow the
151
-// Kubernetes API pattern.
152
-// Host is the http://... base for the URL
153
-type RESTClient struct {
154
-	host       string
155
-	prefix     string
156
-	secure     bool
157
-	auth       *AuthInfo
158
-	httpClient *http.Client
159
-	Sync       bool
160
-	PollPeriod time.Duration
161
-	Timeout    time.Duration
162
-	Codec      runtime.Codec
163
-}
164
-
165
-// NewRESTClient creates a new RESTClient. This client performs generic REST functions
166
-// such as Get, Put, Post, and Delete on specified paths.
167
-func NewRESTClient(host string, auth *AuthInfo, path string, c runtime.Codec) (*RESTClient, error) {
168
-	prefix, err := normalizePrefix(host, path)
169
-	if err != nil {
170
-		return nil, err
171
-	}
172
-	base := *prefix
173
-	base.Path = ""
174
-	base.RawQuery = ""
175
-	base.Fragment = ""
176
-
177
-	var config *tls.Config
178
-	if auth != nil && len(auth.CertFile) != 0 {
179
-		cert, err := tls.LoadX509KeyPair(auth.CertFile, auth.KeyFile)
180
-		if err != nil {
181
-			return nil, err
182
-		}
183
-		data, err := ioutil.ReadFile(auth.CAFile)
184
-		if err != nil {
185
-			return nil, err
186
-		}
187
-		certPool := x509.NewCertPool()
188
-		certPool.AppendCertsFromPEM(data)
189
-		config = &tls.Config{
190
-			Certificates: []tls.Certificate{
191
-				cert,
192
-			},
193
-			RootCAs:    certPool,
194
-			ClientCAs:  certPool,
195
-			ClientAuth: tls.RequireAndVerifyClientCert,
196
-		}
197
-	} else {
198
-		config = &tls.Config{
199
-			InsecureSkipVerify: true,
200
-		}
201
-	}
202
-
203
-	return &RESTClient{
204
-		host:   base.String(),
205
-		prefix: prefix.Path,
206
-		secure: prefix.Scheme == "https",
207
-		auth:   auth,
208
-		httpClient: &http.Client{
209
-			Transport: &http.Transport{
210
-				TLSClientConfig: config,
211
-			},
212
-		},
213
-		Sync:       false,
214
-		PollPeriod: time.Second * 2,
215
-		Timeout:    time.Second * 20,
216
-		Codec:      c,
217
-	}, nil
218
-}
219
-
220
-// normalizePrefix ensures the passed initial value is valid.
221
-func normalizePrefix(host, prefix string) (*url.URL, error) {
222
-	if host == "" {
223
-		return nil, fmt.Errorf("host must be a URL or a host:port pair")
224
-	}
225
-	base := host
226
-	hostURL, err := url.Parse(base)
227
-	if err != nil {
228
-		return nil, err
229
-	}
230
-	if hostURL.Scheme == "" {
231
-		hostURL, err = url.Parse("http://" + base)
232
-		if err != nil {
233
-			return nil, err
234
-		}
235
-		if hostURL.Path != "" && hostURL.Path != "/" {
236
-			return nil, fmt.Errorf("host must be a URL or a host:port pair: %s", base)
237
-		}
238
-	}
239
-	hostURL.Path += prefix
240
-
241
-	return hostURL, nil
242
-}
243
-
244
-// Secure returns true if the client is configured for secure connections.
245
-func (c *RESTClient) Secure() bool {
246
-	return c.secure
247
-}
248
-
249
-// doRequest executes a request, adds authentication (if auth != nil), and HTTPS
250
-// cert ignoring.
251
-func (c *RESTClient) doRequest(request *http.Request) ([]byte, error) {
252
-	if c.auth != nil {
253
-		request.SetBasicAuth(c.auth.User, c.auth.Password)
254
-	}
255
-	response, err := c.httpClient.Do(request)
256
-	if err != nil {
257
-		return nil, err
258
-	}
259
-	defer response.Body.Close()
260
-	body, err := ioutil.ReadAll(response.Body)
261
-	if err != nil {
262
-		return body, err
263
-	}
264
-
265
-	// Did the server give us a status response?
266
-	isStatusResponse := false
267
-	var status api.Status
268
-	if err := latest.Codec.DecodeInto(body, &status); err == nil && status.Status != "" {
269
-		isStatusResponse = true
270
-	}
271
-
272
-	switch {
273
-	case response.StatusCode == http.StatusConflict:
274
-		// Return error given by server, if there was one.
275
-		if isStatusResponse {
276
-			return nil, &StatusErr{status}
277
-		}
278
-		fallthrough
279
-	case response.StatusCode < http.StatusOK || response.StatusCode > http.StatusPartialContent:
280
-		return nil, fmt.Errorf("request [%#v] failed (%d) %s: %s", request, response.StatusCode, response.Status, string(body))
281
-	}
282
-
283
-	// If the server gave us a status back, look at what it was.
284
-	if isStatusResponse && status.Status != api.StatusSuccess {
285
-		// "Working" requests need to be handled specially.
286
-		// "Failed" requests are clearly just an error and it makes sense to return them as such.
287
-		return nil, &StatusErr{status}
288
-	}
289
-	return body, err
99
+// Client is the implementation of a Kubernetes client.
100
+type Client struct {
101
+	*RESTClient
290 102
 }
291 103
 
292 104
 // ListPods takes a selector, and returns the list of pods that match that selector.
293
-func (c *Client) ListPods(selector labels.Selector) (result *api.PodList, err error) {
105
+func (c *Client) ListPods(ctx api.Context, selector labels.Selector) (result *api.PodList, err error) {
294 106
 	result = &api.PodList{}
295 107
 	err = c.Get().Path("pods").SelectorParam("labels", selector).Do().Into(result)
296 108
 	return
297 109
 }
298 110
 
299 111
 // GetPod takes the id of the pod, and returns the corresponding Pod object, and an error if it occurs
300
-func (c *Client) GetPod(id string) (result *api.Pod, err error) {
112
+func (c *Client) GetPod(ctx api.Context, id string) (result *api.Pod, err error) {
301 113
 	result = &api.Pod{}
302 114
 	err = c.Get().Path("pods").Path(id).Do().Into(result)
303 115
 	return
304 116
 }
305 117
 
306 118
 // DeletePod takes the id of the pod, and returns an error if one occurs
307
-func (c *Client) DeletePod(id string) error {
119
+func (c *Client) DeletePod(ctx api.Context, id string) error {
308 120
 	return c.Delete().Path("pods").Path(id).Do().Error()
309 121
 }
310 122
 
311 123
 // CreatePod takes the representation of a pod.  Returns the server's representation of the pod, and an error, if it occurs.
312
-func (c *Client) CreatePod(pod *api.Pod) (result *api.Pod, err error) {
124
+func (c *Client) CreatePod(ctx api.Context, pod *api.Pod) (result *api.Pod, err error) {
313 125
 	result = &api.Pod{}
314 126
 	err = c.Post().Path("pods").Body(pod).Do().Into(result)
315 127
 	return
316 128
 }
317 129
 
318 130
 // UpdatePod takes the representation of a pod to update.  Returns the server's representation of the pod, and an error, if it occurs.
319
-func (c *Client) UpdatePod(pod *api.Pod) (result *api.Pod, err error) {
131
+func (c *Client) UpdatePod(ctx api.Context, pod *api.Pod) (result *api.Pod, err error) {
320 132
 	result = &api.Pod{}
321 133
 	if pod.ResourceVersion == 0 {
322 134
 		err = fmt.Errorf("invalid update object, missing resource version: %v", pod)
... ...
@@ -327,28 +131,28 @@ func (c *Client) UpdatePod(pod *api.Pod) (result *api.Pod, err error) {
327 327
 }
328 328
 
329 329
 // ListReplicationControllers takes a selector, and returns the list of replication controllers that match that selector.
330
-func (c *Client) ListReplicationControllers(selector labels.Selector) (result *api.ReplicationControllerList, err error) {
330
+func (c *Client) ListReplicationControllers(ctx api.Context, selector labels.Selector) (result *api.ReplicationControllerList, err error) {
331 331
 	result = &api.ReplicationControllerList{}
332 332
 	err = c.Get().Path("replicationControllers").SelectorParam("labels", selector).Do().Into(result)
333 333
 	return
334 334
 }
335 335
 
336 336
 // GetReplicationController returns information about a particular replication controller.
337
-func (c *Client) GetReplicationController(id string) (result *api.ReplicationController, err error) {
337
+func (c *Client) GetReplicationController(ctx api.Context, id string) (result *api.ReplicationController, err error) {
338 338
 	result = &api.ReplicationController{}
339 339
 	err = c.Get().Path("replicationControllers").Path(id).Do().Into(result)
340 340
 	return
341 341
 }
342 342
 
343 343
 // CreateReplicationController creates a new replication controller.
344
-func (c *Client) CreateReplicationController(controller *api.ReplicationController) (result *api.ReplicationController, err error) {
344
+func (c *Client) CreateReplicationController(ctx api.Context, controller *api.ReplicationController) (result *api.ReplicationController, err error) {
345 345
 	result = &api.ReplicationController{}
346 346
 	err = c.Post().Path("replicationControllers").Body(controller).Do().Into(result)
347 347
 	return
348 348
 }
349 349
 
350 350
 // UpdateReplicationController updates an existing replication controller.
351
-func (c *Client) UpdateReplicationController(controller *api.ReplicationController) (result *api.ReplicationController, err error) {
351
+func (c *Client) UpdateReplicationController(ctx api.Context, controller *api.ReplicationController) (result *api.ReplicationController, err error) {
352 352
 	result = &api.ReplicationController{}
353 353
 	if controller.ResourceVersion == 0 {
354 354
 		err = fmt.Errorf("invalid update object, missing resource version: %v", controller)
... ...
@@ -359,12 +163,12 @@ func (c *Client) UpdateReplicationController(controller *api.ReplicationControll
359 359
 }
360 360
 
361 361
 // DeleteReplicationController deletes an existing replication controller.
362
-func (c *Client) DeleteReplicationController(id string) error {
362
+func (c *Client) DeleteReplicationController(ctx api.Context, id string) error {
363 363
 	return c.Delete().Path("replicationControllers").Path(id).Do().Error()
364 364
 }
365 365
 
366 366
 // WatchReplicationControllers returns a watch.Interface that watches the requested controllers.
367
-func (c *Client) WatchReplicationControllers(label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
367
+func (c *Client) WatchReplicationControllers(ctx api.Context, label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
368 368
 	return c.Get().
369 369
 		Path("watch").
370 370
 		Path("replicationControllers").
... ...
@@ -375,28 +179,28 @@ func (c *Client) WatchReplicationControllers(label, field labels.Selector, resou
375 375
 }
376 376
 
377 377
 // ListServices takes a selector, and returns the list of services that match that selector
378
-func (c *Client) ListServices(selector labels.Selector) (result *api.ServiceList, err error) {
378
+func (c *Client) ListServices(ctx api.Context, selector labels.Selector) (result *api.ServiceList, err error) {
379 379
 	result = &api.ServiceList{}
380 380
 	err = c.Get().Path("services").SelectorParam("labels", selector).Do().Into(result)
381 381
 	return
382 382
 }
383 383
 
384 384
 // GetService returns information about a particular service.
385
-func (c *Client) GetService(id string) (result *api.Service, err error) {
385
+func (c *Client) GetService(ctx api.Context, id string) (result *api.Service, err error) {
386 386
 	result = &api.Service{}
387 387
 	err = c.Get().Path("services").Path(id).Do().Into(result)
388 388
 	return
389 389
 }
390 390
 
391 391
 // CreateService creates a new service.
392
-func (c *Client) CreateService(svc *api.Service) (result *api.Service, err error) {
392
+func (c *Client) CreateService(ctx api.Context, svc *api.Service) (result *api.Service, err error) {
393 393
 	result = &api.Service{}
394 394
 	err = c.Post().Path("services").Body(svc).Do().Into(result)
395 395
 	return
396 396
 }
397 397
 
398 398
 // UpdateService updates an existing service.
399
-func (c *Client) UpdateService(svc *api.Service) (result *api.Service, err error) {
399
+func (c *Client) UpdateService(ctx api.Context, svc *api.Service) (result *api.Service, err error) {
400 400
 	result = &api.Service{}
401 401
 	if svc.ResourceVersion == 0 {
402 402
 		err = fmt.Errorf("invalid update object, missing resource version: %v", svc)
... ...
@@ -407,12 +211,12 @@ func (c *Client) UpdateService(svc *api.Service) (result *api.Service, err error
407 407
 }
408 408
 
409 409
 // DeleteService deletes an existing service.
410
-func (c *Client) DeleteService(id string) error {
410
+func (c *Client) DeleteService(ctx api.Context, id string) error {
411 411
 	return c.Delete().Path("services").Path(id).Do().Error()
412 412
 }
413 413
 
414 414
 // WatchServices returns a watch.Interface that watches the requested services.
415
-func (c *Client) WatchServices(label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
415
+func (c *Client) WatchServices(ctx api.Context, label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
416 416
 	return c.Get().
417 417
 		Path("watch").
418 418
 		Path("services").
... ...
@@ -423,14 +227,21 @@ func (c *Client) WatchServices(label, field labels.Selector, resourceVersion uin
423 423
 }
424 424
 
425 425
 // ListEndpoints takes a selector, and returns the list of endpoints that match that selector
426
-func (c *Client) ListEndpoints(selector labels.Selector) (result *api.EndpointsList, err error) {
426
+func (c *Client) ListEndpoints(ctx api.Context, selector labels.Selector) (result *api.EndpointsList, err error) {
427 427
 	result = &api.EndpointsList{}
428 428
 	err = c.Get().Path("endpoints").SelectorParam("labels", selector).Do().Into(result)
429 429
 	return
430 430
 }
431 431
 
432
+// GetEndpoints returns information about the endpoints for a particular service.
433
+func (c *Client) GetEndpoints(ctx api.Context, id string) (result *api.Endpoints, err error) {
434
+	result = &api.Endpoints{}
435
+	err = c.Get().Path("endpoints").Path(id).Do().Into(result)
436
+	return
437
+}
438
+
432 439
 // WatchEndpoints returns a watch.Interface that watches the requested endpoints for a service.
433
-func (c *Client) WatchEndpoints(label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
440
+func (c *Client) WatchEndpoints(ctx api.Context, label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
434 441
 	return c.Get().
435 442
 		Path("watch").
436 443
 		Path("endpoints").
... ...
@@ -440,6 +251,26 @@ func (c *Client) WatchEndpoints(label, field labels.Selector, resourceVersion ui
440 440
 		Watch()
441 441
 }
442 442
 
443
+func (c *Client) CreateEndpoints(ctx api.Context, endpoints *api.Endpoints) (*api.Endpoints, error) {
444
+	result := &api.Endpoints{}
445
+	err := c.Post().Path("endpoints").Body(endpoints).Do().Into(result)
446
+	return result, err
447
+}
448
+
449
+func (c *Client) UpdateEndpoints(ctx api.Context, endpoints *api.Endpoints) (*api.Endpoints, error) {
450
+	result := &api.Endpoints{}
451
+	if endpoints.ResourceVersion == 0 {
452
+		return nil, fmt.Errorf("invalid update object, missing resource version: %v", endpoints)
453
+	}
454
+	err := c.Put().
455
+		Path("endpoints").
456
+		Path(endpoints.ID).
457
+		Body(endpoints).
458
+		Do().
459
+		Into(result)
460
+	return result, err
461
+}
462
+
443 463
 // ServerVersion retrieves and parses the server's version.
444 464
 func (c *Client) ServerVersion() (*version.Info, error) {
445 465
 	body, err := c.Get().AbsPath("/version").Do().Raw()
... ...
@@ -27,8 +27,6 @@ import (
27 27
 
28 28
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
29 29
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
30
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
31
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2"
32 30
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
33 31
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
34 32
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
... ...
@@ -38,83 +36,126 @@ import (
38 38
 // TODO: Move this to a common place, it's needed in multiple tests.
39 39
 const apiPath = "/api/v1beta1"
40 40
 
41
-func TestChecksCodec(t *testing.T) {
42
-	testCases := map[string]struct {
43
-		Err    bool
44
-		Prefix string
45
-		Codec  runtime.Codec
46
-	}{
47
-		"v1beta1": {false, "/api/v1beta1/", v1beta1.Codec},
48
-		"":        {false, "/api/v1beta1/", v1beta1.Codec},
49
-		"v1beta2": {false, "/api/v1beta2/", v1beta2.Codec},
50
-		"v1beta3": {true, "", nil},
51
-	}
52
-	for version, expected := range testCases {
53
-		client, err := New("127.0.0.1", version, nil)
54
-		switch {
55
-		case err == nil && expected.Err:
56
-			t.Errorf("expected error but was nil")
57
-			continue
58
-		case err != nil && !expected.Err:
59
-			t.Errorf("unexpected error %v", err)
60
-			continue
61
-		case err != nil:
62
-			continue
63
-		}
64
-		if e, a := expected.Prefix, client.prefix; e != a {
65
-			t.Errorf("expected %#v, got %#v", e, a)
66
-		}
67
-		if e, a := expected.Codec, client.Codec; e != a {
68
-			t.Errorf("expected %#v, got %#v", e, a)
69
-		}
41
+type testRequest struct {
42
+	Method  string
43
+	Path    string
44
+	Header  string
45
+	Query   url.Values
46
+	Body    runtime.Object
47
+	RawBody *string
48
+}
49
+
50
+type Response struct {
51
+	StatusCode int
52
+	Body       runtime.Object
53
+	RawBody    *string
54
+}
55
+
56
+type testClient struct {
57
+	*Client
58
+	Request  testRequest
59
+	Response Response
60
+	Error    bool
61
+	server   *httptest.Server
62
+	handler  *util.FakeHandler
63
+	// For query args, an optional function to validate the contents
64
+	// useful when the contents can change but still be correct.
65
+	// Maps from query arg key to validator.
66
+	// If no validator is present, string equality is used.
67
+	QueryValidator map[string]func(string, string) bool
68
+}
69
+
70
+func (c *testClient) Setup() *testClient {
71
+	c.handler = &util.FakeHandler{
72
+		StatusCode: c.Response.StatusCode,
73
+	}
74
+	if responseBody := body(c.Response.Body, c.Response.RawBody); responseBody != nil {
75
+		c.handler.ResponseBody = *responseBody
76
+	}
77
+	c.server = httptest.NewServer(c.handler)
78
+	if c.Client == nil {
79
+		c.Client = NewOrDie(&Config{
80
+			Host:    c.server.URL,
81
+			Version: "v1beta1",
82
+		})
83
+	}
84
+	c.QueryValidator = map[string]func(string, string) bool{}
85
+	return c
86
+}
87
+
88
+func (c *testClient) Validate(t *testing.T, received runtime.Object, err error) {
89
+	c.ValidateCommon(t, err)
90
+
91
+	if c.Response.Body != nil && !reflect.DeepEqual(c.Response.Body, received) {
92
+		t.Errorf("bad response for request %#v: expected %s, got %s", c.Request, c.Response.Body, received)
70 93
 	}
71 94
 }
72 95
 
73
-func TestValidatesHostParameter(t *testing.T) {
74
-	testCases := map[string]struct {
75
-		Host   string
76
-		Prefix string
77
-		Err    bool
78
-	}{
79
-		"127.0.0.1":          {"http://127.0.0.1", "/api/v1beta1/", false},
80
-		"127.0.0.1:8080":     {"http://127.0.0.1:8080", "/api/v1beta1/", false},
81
-		"foo.bar.com":        {"http://foo.bar.com", "/api/v1beta1/", false},
82
-		"http://host/server": {"http://host", "/server/api/v1beta1/", false},
83
-		"host/server":        {"", "", true},
84
-	}
85
-	for k, expected := range testCases {
86
-		c, err := NewRESTClient(k, nil, "/api/v1beta1/", v1beta1.Codec)
87
-		switch {
88
-		case err == nil && expected.Err:
89
-			t.Errorf("expected error but was nil")
90
-			continue
91
-		case err != nil && !expected.Err:
92
-			t.Errorf("unexpected error %v", err)
93
-			continue
94
-		case err != nil:
95
-			continue
96
+func (c *testClient) ValidateRaw(t *testing.T, received []byte, err error) {
97
+	c.ValidateCommon(t, err)
98
+
99
+	if c.Response.Body != nil && !reflect.DeepEqual(c.Response.Body, received) {
100
+		t.Errorf("bad response for request %#v: expected %s, got %s", c.Request, c.Response.Body, received)
101
+	}
102
+}
103
+
104
+func (c *testClient) ValidateCommon(t *testing.T, err error) {
105
+	defer c.server.Close()
106
+
107
+	if c.Error {
108
+		if err == nil {
109
+			t.Errorf("error expected for %#v, got none", c.Request)
110
+		}
111
+		return
112
+	}
113
+	if err != nil {
114
+		t.Errorf("no error expected for %#v, got: %v", c.Request, err)
115
+	}
116
+
117
+	if c.handler.RequestReceived == nil {
118
+		t.Errorf("handler had an empty request, %#v", c)
119
+		return
120
+	}
121
+
122
+	requestBody := body(c.Request.Body, c.Request.RawBody)
123
+	actualQuery := c.handler.RequestReceived.URL.Query()
124
+	// We check the query manually, so blank it out so that FakeHandler.ValidateRequest
125
+	// won't check it.
126
+	c.handler.RequestReceived.URL.RawQuery = ""
127
+	c.handler.ValidateRequest(t, path.Join(apiPath, c.Request.Path), c.Request.Method, requestBody)
128
+	for key, values := range c.Request.Query {
129
+		validator, ok := c.QueryValidator[key]
130
+		if !ok {
131
+			validator = func(a, b string) bool { return a == b }
96 132
 		}
97
-		if e, a := expected.Host, c.host; e != a {
98
-			t.Errorf("%s: expected host %s, got %s", k, e, a)
99
-			continue
133
+		observed := actualQuery.Get(key)
134
+		if !validator(values[0], observed) {
135
+			t.Errorf("Unexpected query arg for key: %s.  Expected %s, Received %s", key, values[0], observed)
100 136
 		}
101
-		if e, a := expected.Prefix, c.prefix; e != a {
102
-			t.Errorf("%s: expected prefix %s, got %s", k, e, a)
103
-			continue
137
+	}
138
+	if c.Request.Header != "" {
139
+		if c.handler.RequestReceived.Header.Get(c.Request.Header) == "" {
140
+			t.Errorf("header %q not found in request %#v", c.Request.Header, c.handler.RequestReceived)
104 141
 		}
105 142
 	}
143
+
144
+	if expected, received := requestBody, c.handler.RequestBody; expected != nil && *expected != received {
145
+		t.Errorf("bad body for request %#v: expected %s, got %s", c.Request, *expected, received)
146
+	}
106 147
 }
107 148
 
108 149
 func TestListEmptyPods(t *testing.T) {
150
+	ctx := api.NewContext()
109 151
 	c := &testClient{
110 152
 		Request:  testRequest{Method: "GET", Path: "/pods"},
111 153
 		Response: Response{StatusCode: 200, Body: &api.PodList{}},
112 154
 	}
113
-	podList, err := c.Setup().ListPods(labels.Everything())
155
+	podList, err := c.Setup().ListPods(ctx, labels.Everything())
114 156
 	c.Validate(t, podList, err)
115 157
 }
116 158
 
117 159
 func TestListPods(t *testing.T) {
160
+	ctx := api.NewDefaultContext()
118 161
 	c := &testClient{
119 162
 		Request: testRequest{Method: "GET", Path: "/pods"},
120 163
 		Response: Response{StatusCode: 200,
... ...
@@ -133,7 +174,7 @@ func TestListPods(t *testing.T) {
133 133
 			},
134 134
 		},
135 135
 	}
136
-	receivedPodList, err := c.Setup().ListPods(labels.Everything())
136
+	receivedPodList, err := c.Setup().ListPods(ctx, labels.Everything())
137 137
 	c.Validate(t, receivedPodList, err)
138 138
 }
139 139
 
... ...
@@ -144,6 +185,7 @@ func validateLabels(a, b string) bool {
144 144
 }
145 145
 
146 146
 func TestListPodsLabels(t *testing.T) {
147
+	ctx := api.NewDefaultContext()
147 148
 	c := &testClient{
148 149
 		Request: testRequest{Method: "GET", Path: "/pods", Query: url.Values{"labels": []string{"foo=bar,name=baz"}}},
149 150
 		Response: Response{
... ...
@@ -166,11 +208,12 @@ func TestListPodsLabels(t *testing.T) {
166 166
 	c.Setup()
167 167
 	c.QueryValidator["labels"] = validateLabels
168 168
 	selector := labels.Set{"foo": "bar", "name": "baz"}.AsSelector()
169
-	receivedPodList, err := c.ListPods(selector)
169
+	receivedPodList, err := c.ListPods(ctx, selector)
170 170
 	c.Validate(t, receivedPodList, err)
171 171
 }
172 172
 
173 173
 func TestGetPod(t *testing.T) {
174
+	ctx := api.NewDefaultContext()
174 175
 	c := &testClient{
175 176
 		Request: testRequest{Method: "GET", Path: "/pods/foo"},
176 177
 		Response: Response{
... ...
@@ -186,7 +229,7 @@ func TestGetPod(t *testing.T) {
186 186
 			},
187 187
 		},
188 188
 	}
189
-	receivedPod, err := c.Setup().GetPod("foo")
189
+	receivedPod, err := c.Setup().GetPod(ctx, "foo")
190 190
 	c.Validate(t, receivedPod, err)
191 191
 }
192 192
 
... ...
@@ -195,7 +238,7 @@ func TestDeletePod(t *testing.T) {
195 195
 		Request:  testRequest{Method: "DELETE", Path: "/pods/foo"},
196 196
 		Response: Response{StatusCode: 200},
197 197
 	}
198
-	err := c.Setup().DeletePod("foo")
198
+	err := c.Setup().DeletePod(api.NewDefaultContext(), "foo")
199 199
 	c.Validate(t, nil, err)
200 200
 }
201 201
 
... ...
@@ -216,7 +259,7 @@ func TestCreatePod(t *testing.T) {
216 216
 			Body:       requestPod,
217 217
 		},
218 218
 	}
219
-	receivedPod, err := c.Setup().CreatePod(requestPod)
219
+	receivedPod, err := c.Setup().CreatePod(api.NewDefaultContext(), requestPod)
220 220
 	c.Validate(t, receivedPod, err)
221 221
 }
222 222
 
... ...
@@ -235,7 +278,7 @@ func TestUpdatePod(t *testing.T) {
235 235
 		Request:  testRequest{Method: "PUT", Path: "/pods/foo"},
236 236
 		Response: Response{StatusCode: 200, Body: requestPod},
237 237
 	}
238
-	receivedPod, err := c.Setup().UpdatePod(requestPod)
238
+	receivedPod, err := c.Setup().UpdatePod(api.NewDefaultContext(), requestPod)
239 239
 	c.Validate(t, receivedPod, err)
240 240
 }
241 241
 
... ...
@@ -259,7 +302,7 @@ func TestListControllers(t *testing.T) {
259 259
 			},
260 260
 		},
261 261
 	}
262
-	receivedControllerList, err := c.Setup().ListReplicationControllers(labels.Everything())
262
+	receivedControllerList, err := c.Setup().ListReplicationControllers(api.NewContext(), labels.Everything())
263 263
 	c.Validate(t, receivedControllerList, err)
264 264
 
265 265
 }
... ...
@@ -281,7 +324,7 @@ func TestGetController(t *testing.T) {
281 281
 			},
282 282
 		},
283 283
 	}
284
-	receivedController, err := c.Setup().GetReplicationController("foo")
284
+	receivedController, err := c.Setup().GetReplicationController(api.NewDefaultContext(), "foo")
285 285
 	c.Validate(t, receivedController, err)
286 286
 }
287 287
 
... ...
@@ -305,7 +348,7 @@ func TestUpdateController(t *testing.T) {
305 305
 			},
306 306
 		},
307 307
 	}
308
-	receivedController, err := c.Setup().UpdateReplicationController(requestController)
308
+	receivedController, err := c.Setup().UpdateReplicationController(api.NewDefaultContext(), requestController)
309 309
 	c.Validate(t, receivedController, err)
310 310
 }
311 311
 
... ...
@@ -314,7 +357,7 @@ func TestDeleteController(t *testing.T) {
314 314
 		Request:  testRequest{Method: "DELETE", Path: "/replicationControllers/foo"},
315 315
 		Response: Response{StatusCode: 200},
316 316
 	}
317
-	err := c.Setup().DeleteReplicationController("foo")
317
+	err := c.Setup().DeleteReplicationController(api.NewDefaultContext(), "foo")
318 318
 	c.Validate(t, nil, err)
319 319
 }
320 320
 
... ...
@@ -338,7 +381,7 @@ func TestCreateController(t *testing.T) {
338 338
 			},
339 339
 		},
340 340
 	}
341
-	receivedController, err := c.Setup().CreateReplicationController(requestController)
341
+	receivedController, err := c.Setup().CreateReplicationController(api.NewDefaultContext(), requestController)
342 342
 	c.Validate(t, receivedController, err)
343 343
 }
344 344
 
... ...
@@ -351,108 +394,6 @@ func body(obj runtime.Object, raw *string) *string {
351 351
 	return raw
352 352
 }
353 353
 
354
-type testRequest struct {
355
-	Method  string
356
-	Path    string
357
-	Header  string
358
-	Query   url.Values
359
-	Body    runtime.Object
360
-	RawBody *string
361
-}
362
-
363
-type Response struct {
364
-	StatusCode int
365
-	Body       runtime.Object
366
-	RawBody    *string
367
-}
368
-
369
-type testClient struct {
370
-	*Client
371
-	Request  testRequest
372
-	Response Response
373
-	Error    bool
374
-	server   *httptest.Server
375
-	handler  *util.FakeHandler
376
-	// For query args, an optional function to validate the contents
377
-	// useful when the contents can change but still be correct.
378
-	// Maps from query arg key to validator.
379
-	// If no validator is present, string equality is used.
380
-	QueryValidator map[string]func(string, string) bool
381
-}
382
-
383
-func (c *testClient) Setup() *testClient {
384
-	c.handler = &util.FakeHandler{
385
-		StatusCode: c.Response.StatusCode,
386
-	}
387
-	if responseBody := body(c.Response.Body, c.Response.RawBody); responseBody != nil {
388
-		c.handler.ResponseBody = *responseBody
389
-	}
390
-	c.server = httptest.NewServer(c.handler)
391
-	if c.Client == nil {
392
-		c.Client = NewOrDie("localhost", "v1beta1", nil)
393
-	}
394
-	c.Client.host = c.server.URL
395
-	c.Client.prefix = "/api/v1beta1/"
396
-	c.QueryValidator = map[string]func(string, string) bool{}
397
-	return c
398
-}
399
-
400
-func (c *testClient) Validate(t *testing.T, received runtime.Object, err error) {
401
-	c.ValidateCommon(t, err)
402
-
403
-	if c.Response.Body != nil && !reflect.DeepEqual(c.Response.Body, received) {
404
-		t.Errorf("bad response for request %#v: expected %s, got %s", c.Request, c.Response.Body, received)
405
-	}
406
-}
407
-
408
-func (c *testClient) ValidateRaw(t *testing.T, received []byte, err error) {
409
-	c.ValidateCommon(t, err)
410
-
411
-	if c.Response.Body != nil && !reflect.DeepEqual(c.Response.Body, received) {
412
-		t.Errorf("bad response for request %#v: expected %s, got %s", c.Request, c.Response.Body, received)
413
-	}
414
-}
415
-
416
-func (c *testClient) ValidateCommon(t *testing.T, err error) {
417
-	defer c.server.Close()
418
-
419
-	if c.Error {
420
-		if err == nil {
421
-			t.Errorf("error expected for %#v, got none", c.Request)
422
-		}
423
-		return
424
-	}
425
-	if err != nil {
426
-		t.Errorf("no error expected for %#v, got: %v", c.Request, err)
427
-	}
428
-
429
-	requestBody := body(c.Request.Body, c.Request.RawBody)
430
-	actualQuery := c.handler.RequestReceived.URL.Query()
431
-	// We check the query manually, so blank it out so that FakeHandler.ValidateRequest
432
-	// won't check it.
433
-	c.handler.RequestReceived.URL.RawQuery = ""
434
-	c.handler.ValidateRequest(t, path.Join(apiPath, c.Request.Path), c.Request.Method, requestBody)
435
-	for key, values := range c.Request.Query {
436
-		validator, ok := c.QueryValidator[key]
437
-		if !ok {
438
-			validator = func(a, b string) bool { return a == b }
439
-		}
440
-		observed := actualQuery.Get(key)
441
-		if !validator(values[0], observed) {
442
-			t.Errorf("Unexpected query arg for key: %s.  Expected %s, Received %s", key, values[0], observed)
443
-		}
444
-	}
445
-	if c.Request.Header != "" {
446
-		if c.handler.RequestReceived.Header.Get(c.Request.Header) == "" {
447
-			t.Errorf("header %q not found in request %#v", c.Request.Header, c.handler.RequestReceived)
448
-		}
449
-	}
450
-
451
-	if expected, received := requestBody, c.handler.RequestBody; expected != nil && *expected != received {
452
-		t.Errorf("bad body for request %#v: expected %s, got %s", c.Request, *expected, received)
453
-	}
454
-}
455
-
456 354
 func TestListServices(t *testing.T) {
457 355
 	c := &testClient{
458 356
 		Request: testRequest{Method: "GET", Path: "/services"},
... ...
@@ -473,7 +414,7 @@ func TestListServices(t *testing.T) {
473 473
 			},
474 474
 		},
475 475
 	}
476
-	receivedServiceList, err := c.Setup().ListServices(labels.Everything())
476
+	receivedServiceList, err := c.Setup().ListServices(api.NewDefaultContext(), labels.Everything())
477 477
 	c.Validate(t, receivedServiceList, err)
478 478
 }
479 479
 
... ...
@@ -500,7 +441,7 @@ func TestListServicesLabels(t *testing.T) {
500 500
 	c.Setup()
501 501
 	c.QueryValidator["labels"] = validateLabels
502 502
 	selector := labels.Set{"foo": "bar", "name": "baz"}.AsSelector()
503
-	receivedServiceList, err := c.ListServices(selector)
503
+	receivedServiceList, err := c.ListServices(api.NewDefaultContext(), selector)
504 504
 	c.Validate(t, receivedServiceList, err)
505 505
 }
506 506
 
... ...
@@ -509,16 +450,16 @@ func TestGetService(t *testing.T) {
509 509
 		Request:  testRequest{Method: "GET", Path: "/services/1"},
510 510
 		Response: Response{StatusCode: 200, Body: &api.Service{JSONBase: api.JSONBase{ID: "service-1"}}},
511 511
 	}
512
-	response, err := c.Setup().GetService("1")
512
+	response, err := c.Setup().GetService(api.NewDefaultContext(), "1")
513 513
 	c.Validate(t, response, err)
514 514
 }
515 515
 
516 516
 func TestCreateService(t *testing.T) {
517
-	c := (&testClient{
517
+	c := &testClient{
518 518
 		Request:  testRequest{Method: "POST", Path: "/services", Body: &api.Service{JSONBase: api.JSONBase{ID: "service-1"}}},
519 519
 		Response: Response{StatusCode: 200, Body: &api.Service{JSONBase: api.JSONBase{ID: "service-1"}}},
520
-	}).Setup()
521
-	response, err := c.Setup().CreateService(&api.Service{JSONBase: api.JSONBase{ID: "service-1"}})
520
+	}
521
+	response, err := c.Setup().CreateService(api.NewDefaultContext(), &api.Service{JSONBase: api.JSONBase{ID: "service-1"}})
522 522
 	c.Validate(t, response, err)
523 523
 }
524 524
 
... ...
@@ -528,7 +469,7 @@ func TestUpdateService(t *testing.T) {
528 528
 		Request:  testRequest{Method: "PUT", Path: "/services/service-1", Body: svc},
529 529
 		Response: Response{StatusCode: 200, Body: svc},
530 530
 	}
531
-	response, err := c.Setup().UpdateService(svc)
531
+	response, err := c.Setup().UpdateService(api.NewDefaultContext(), svc)
532 532
 	c.Validate(t, response, err)
533 533
 }
534 534
 
... ...
@@ -537,102 +478,35 @@ func TestDeleteService(t *testing.T) {
537 537
 		Request:  testRequest{Method: "DELETE", Path: "/services/1"},
538 538
 		Response: Response{StatusCode: 200},
539 539
 	}
540
-	err := c.Setup().DeleteService("1")
540
+	err := c.Setup().DeleteService(api.NewDefaultContext(), "1")
541 541
 	c.Validate(t, nil, err)
542 542
 }
543 543
 
544
-func TestDoRequest(t *testing.T) {
545
-	invalid := "aaaaa"
546
-	testClients := []testClient{
547
-		{Request: testRequest{Method: "GET", Path: "good"}, Response: Response{StatusCode: 200}},
548
-		{Request: testRequest{Method: "GET", Path: "bad%ZZ"}, Error: true},
549
-		{Client: NewOrDie("localhost", "v1beta1", &AuthInfo{"foo", "bar", "", "", ""}), Request: testRequest{Method: "GET", Path: "auth", Header: "Authorization"}, Response: Response{StatusCode: 200}},
550
-		{Client: &Client{&RESTClient{httpClient: http.DefaultClient}}, Request: testRequest{Method: "GET", Path: "nocertificate"}, Error: true},
551
-		{Request: testRequest{Method: "GET", Path: "error"}, Response: Response{StatusCode: 500}, Error: true},
552
-		{Request: testRequest{Method: "POST", Path: "faildecode"}, Response: Response{StatusCode: 200, RawBody: &invalid}},
553
-		{Request: testRequest{Method: "GET", Path: "failread"}, Response: Response{StatusCode: 200, RawBody: &invalid}},
554
-	}
555
-	for _, c := range testClients {
556
-		client := c.Setup()
557
-		prefix, _ := url.Parse(client.host)
558
-		prefix.Path = client.prefix + c.Request.Path
559
-		request := &http.Request{
560
-			Method: c.Request.Method,
561
-			Header: make(http.Header),
562
-			URL:    prefix,
563
-		}
564
-		response, err := client.doRequest(request)
565
-		c.ValidateRaw(t, response, err)
566
-	}
567
-}
568
-
569
-func TestDoRequestAccepted(t *testing.T) {
570
-	status := &api.Status{Status: api.StatusWorking}
571
-	expectedBody, _ := latest.Codec.Encode(status)
572
-	fakeHandler := util.FakeHandler{
573
-		StatusCode:   202,
574
-		ResponseBody: string(expectedBody),
575
-		T:            t,
576
-	}
577
-	testServer := httptest.NewServer(&fakeHandler)
578
-	request, _ := http.NewRequest("GET", testServer.URL+"/foo/bar", nil)
579
-	auth := AuthInfo{User: "user", Password: "pass"}
580
-	c, err := New(testServer.URL, "", &auth)
581
-	if err != nil {
582
-		t.Fatalf("unexpected error: %v", err)
583
-	}
584
-	body, err := c.doRequest(request)
585
-	if request.Header["Authorization"] == nil {
586
-		t.Errorf("Request is missing authorization header: %#v", *request)
587
-	}
588
-	if err == nil {
589
-		t.Error("Unexpected non-error")
590
-		return
591
-	}
592
-	se, ok := err.(*StatusErr)
593
-	if !ok {
594
-		t.Errorf("Unexpected kind of error: %#v", err)
595
-		return
596
-	}
597
-	if !reflect.DeepEqual(&se.Status, status) {
598
-		t.Errorf("Unexpected status: %#v", se.Status)
599
-	}
600
-	if body != nil {
601
-		t.Errorf("Expected nil body, but saw: '%s'", body)
544
+func TestListEndpooints(t *testing.T) {
545
+	c := &testClient{
546
+		Request: testRequest{Method: "GET", Path: "/endpoints"},
547
+		Response: Response{StatusCode: 200,
548
+			Body: &api.EndpointsList{
549
+				Items: []api.Endpoints{
550
+					{
551
+						JSONBase:  api.JSONBase{ID: "endpoint-1"},
552
+						Endpoints: []string{"10.245.1.2:8080", "10.245.1.3:8080"},
553
+					},
554
+				},
555
+			},
556
+		},
602 557
 	}
603
-	fakeHandler.ValidateRequest(t, "/foo/bar", "GET", nil)
558
+	receivedEndpointsList, err := c.Setup().ListEndpoints(api.NewDefaultContext(), labels.Everything())
559
+	c.Validate(t, receivedEndpointsList, err)
604 560
 }
605 561
 
606
-func TestDoRequestAcceptedSuccess(t *testing.T) {
607
-	status := &api.Status{Status: api.StatusSuccess}
608
-	expectedBody, _ := latest.Codec.Encode(status)
609
-	fakeHandler := util.FakeHandler{
610
-		StatusCode:   202,
611
-		ResponseBody: string(expectedBody),
612
-		T:            t,
613
-	}
614
-	testServer := httptest.NewServer(&fakeHandler)
615
-	request, _ := http.NewRequest("GET", testServer.URL+"/foo/bar", nil)
616
-	auth := AuthInfo{User: "user", Password: "pass"}
617
-	c, err := New(testServer.URL, "", &auth)
618
-	if err != nil {
619
-		t.Fatalf("unexpected error: %v", err)
620
-	}
621
-	body, err := c.doRequest(request)
622
-	if request.Header["Authorization"] == nil {
623
-		t.Errorf("Request is missing authorization header: %#v", *request)
624
-	}
625
-	if err != nil {
626
-		t.Errorf("Unexpected error %#v", err)
627
-	}
628
-	statusOut, err := latest.Codec.Decode(body)
629
-	if err != nil {
630
-		t.Errorf("Unexpected error %#v", err)
631
-	}
632
-	if !reflect.DeepEqual(status, statusOut) {
633
-		t.Errorf("Unexpected mis-match. Expected %#v.  Saw %#v", status, statusOut)
562
+func TestGetEndpoints(t *testing.T) {
563
+	c := &testClient{
564
+		Request:  testRequest{Method: "GET", Path: "/endpoints/endpoint-1"},
565
+		Response: Response{StatusCode: 200, Body: &api.Endpoints{JSONBase: api.JSONBase{ID: "endpoint-1"}}},
634 566
 	}
635
-	fakeHandler.ValidateRequest(t, "/foo/bar", "GET", nil)
567
+	response, err := c.Setup().GetEndpoints(api.NewDefaultContext(), "endpoint-1")
568
+	c.Validate(t, response, err)
636 569
 }
637 570
 
638 571
 func TestGetServerVersion(t *testing.T) {
... ...
@@ -651,7 +525,7 @@ func TestGetServerVersion(t *testing.T) {
651 651
 		w.WriteHeader(http.StatusOK)
652 652
 		w.Write(output)
653 653
 	}))
654
-	client := NewOrDie(server.URL, "", nil)
654
+	client := NewOrDie(&Config{Host: server.URL})
655 655
 
656 656
 	got, err := client.ServerVersion()
657 657
 	if err != nil {
... ...
@@ -26,7 +26,8 @@ import (
26 26
 // for a controller's ReplicaSelector equals the Replicas count.
27 27
 func (c *Client) ControllerHasDesiredReplicas(controller api.ReplicationController) wait.ConditionFunc {
28 28
 	return func() (bool, error) {
29
-		pods, err := c.ListPods(labels.Set(controller.DesiredState.ReplicaSelector).AsSelector())
29
+		ctx := api.WithNamespace(api.NewContext(), controller.Namespace)
30
+		pods, err := c.ListPods(ctx, labels.Set(controller.DesiredState.ReplicaSelector).AsSelector())
30 31
 		if err != nil {
31 32
 			return false, err
32 33
 		}
... ...
@@ -14,6 +14,34 @@ See the License for the specific language governing permissions and
14 14
 limitations under the License.
15 15
 */
16 16
 
17
-// Package client contains the implementation of the client side communication with the
18
-// Kubernetes master.
17
+/*
18
+Package client contains the implementation of the client side communication with the
19
+Kubernetes master. The Client class provides methods for reading, creating, updating,
20
+and deleting pods, replication controllers, services, and minions.
21
+
22
+Most consumers should use the Config object to create a Client:
23
+
24
+    config := &client.Config{
25
+      Host:     "http://localhost:8080",
26
+      Username: "test",
27
+      Password: "password",
28
+    }
29
+    client, err := client.New(&config)
30
+    if err != nil {
31
+      // handle error
32
+    }
33
+    client.ListPods()
34
+
35
+More advanced consumers may wish to provide their own transport via a http.RoundTripper:
36
+
37
+    config := &client.Config{
38
+      Host:      "https://localhost:8080",
39
+      Transport: oauthclient.Transport(),
40
+    }
41
+    client, err := client.New(&config)
42
+
43
+The RESTClient type implements the Kubernetes API conventions (see `docs/api-conventions.md`)
44
+for a given API path and is intended for use by consumers implementing their own Kubernetes
45
+compatible APIs.
46
+*/
19 47
 package client
... ...
@@ -42,97 +42,102 @@ type Fake struct {
42 42
 	Watch         watch.Interface
43 43
 }
44 44
 
45
-func (c *Fake) ListPods(selector labels.Selector) (*api.PodList, error) {
45
+func (c *Fake) ListPods(ctx api.Context, selector labels.Selector) (*api.PodList, error) {
46 46
 	c.Actions = append(c.Actions, FakeAction{Action: "list-pods"})
47 47
 	return api.Scheme.CopyOrDie(&c.Pods).(*api.PodList), nil
48 48
 }
49 49
 
50
-func (c *Fake) GetPod(name string) (*api.Pod, error) {
50
+func (c *Fake) GetPod(ctx api.Context, name string) (*api.Pod, error) {
51 51
 	c.Actions = append(c.Actions, FakeAction{Action: "get-pod", Value: name})
52 52
 	return &api.Pod{}, nil
53 53
 }
54 54
 
55
-func (c *Fake) DeletePod(name string) error {
55
+func (c *Fake) DeletePod(ctx api.Context, name string) error {
56 56
 	c.Actions = append(c.Actions, FakeAction{Action: "delete-pod", Value: name})
57 57
 	return nil
58 58
 }
59 59
 
60
-func (c *Fake) CreatePod(pod *api.Pod) (*api.Pod, error) {
60
+func (c *Fake) CreatePod(ctx api.Context, pod *api.Pod) (*api.Pod, error) {
61 61
 	c.Actions = append(c.Actions, FakeAction{Action: "create-pod"})
62 62
 	return &api.Pod{}, nil
63 63
 }
64 64
 
65
-func (c *Fake) UpdatePod(pod *api.Pod) (*api.Pod, error) {
65
+func (c *Fake) UpdatePod(ctx api.Context, pod *api.Pod) (*api.Pod, error) {
66 66
 	c.Actions = append(c.Actions, FakeAction{Action: "update-pod", Value: pod.ID})
67 67
 	return &api.Pod{}, nil
68 68
 }
69 69
 
70
-func (c *Fake) ListReplicationControllers(selector labels.Selector) (*api.ReplicationControllerList, error) {
70
+func (c *Fake) ListReplicationControllers(ctx api.Context, selector labels.Selector) (*api.ReplicationControllerList, error) {
71 71
 	c.Actions = append(c.Actions, FakeAction{Action: "list-controllers"})
72 72
 	return &api.ReplicationControllerList{}, nil
73 73
 }
74 74
 
75
-func (c *Fake) GetReplicationController(name string) (*api.ReplicationController, error) {
75
+func (c *Fake) GetReplicationController(ctx api.Context, name string) (*api.ReplicationController, error) {
76 76
 	c.Actions = append(c.Actions, FakeAction{Action: "get-controller", Value: name})
77 77
 	return api.Scheme.CopyOrDie(&c.Ctrl).(*api.ReplicationController), nil
78 78
 }
79 79
 
80
-func (c *Fake) CreateReplicationController(controller *api.ReplicationController) (*api.ReplicationController, error) {
80
+func (c *Fake) CreateReplicationController(ctx api.Context, controller *api.ReplicationController) (*api.ReplicationController, error) {
81 81
 	c.Actions = append(c.Actions, FakeAction{Action: "create-controller", Value: controller})
82 82
 	return &api.ReplicationController{}, nil
83 83
 }
84 84
 
85
-func (c *Fake) UpdateReplicationController(controller *api.ReplicationController) (*api.ReplicationController, error) {
85
+func (c *Fake) UpdateReplicationController(ctx api.Context, controller *api.ReplicationController) (*api.ReplicationController, error) {
86 86
 	c.Actions = append(c.Actions, FakeAction{Action: "update-controller", Value: controller})
87 87
 	return &api.ReplicationController{}, nil
88 88
 }
89 89
 
90
-func (c *Fake) DeleteReplicationController(controller string) error {
90
+func (c *Fake) DeleteReplicationController(ctx api.Context, controller string) error {
91 91
 	c.Actions = append(c.Actions, FakeAction{Action: "delete-controller", Value: controller})
92 92
 	return nil
93 93
 }
94 94
 
95
-func (c *Fake) WatchReplicationControllers(label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
95
+func (c *Fake) WatchReplicationControllers(ctx api.Context, label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
96 96
 	c.Actions = append(c.Actions, FakeAction{Action: "watch-controllers", Value: resourceVersion})
97 97
 	return c.Watch, nil
98 98
 }
99 99
 
100
-func (c *Fake) ListServices(selector labels.Selector) (*api.ServiceList, error) {
100
+func (c *Fake) ListServices(ctx api.Context, selector labels.Selector) (*api.ServiceList, error) {
101 101
 	c.Actions = append(c.Actions, FakeAction{Action: "list-services"})
102 102
 	return &c.ServiceList, c.Err
103 103
 }
104 104
 
105
-func (c *Fake) GetService(name string) (*api.Service, error) {
105
+func (c *Fake) GetService(ctx api.Context, name string) (*api.Service, error) {
106 106
 	c.Actions = append(c.Actions, FakeAction{Action: "get-service", Value: name})
107 107
 	return &api.Service{}, nil
108 108
 }
109 109
 
110
-func (c *Fake) CreateService(service *api.Service) (*api.Service, error) {
110
+func (c *Fake) CreateService(ctx api.Context, service *api.Service) (*api.Service, error) {
111 111
 	c.Actions = append(c.Actions, FakeAction{Action: "create-service", Value: service})
112 112
 	return &api.Service{}, nil
113 113
 }
114 114
 
115
-func (c *Fake) UpdateService(service *api.Service) (*api.Service, error) {
115
+func (c *Fake) UpdateService(ctx api.Context, service *api.Service) (*api.Service, error) {
116 116
 	c.Actions = append(c.Actions, FakeAction{Action: "update-service", Value: service})
117 117
 	return &api.Service{}, nil
118 118
 }
119 119
 
120
-func (c *Fake) DeleteService(service string) error {
120
+func (c *Fake) DeleteService(ctx api.Context, service string) error {
121 121
 	c.Actions = append(c.Actions, FakeAction{Action: "delete-service", Value: service})
122 122
 	return nil
123 123
 }
124 124
 
125
-func (c *Fake) WatchServices(label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
125
+func (c *Fake) WatchServices(ctx api.Context, label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
126 126
 	c.Actions = append(c.Actions, FakeAction{Action: "watch-services", Value: resourceVersion})
127 127
 	return c.Watch, c.Err
128 128
 }
129 129
 
130
-func (c *Fake) ListEndpoints(selector labels.Selector) (*api.EndpointsList, error) {
130
+func (c *Fake) ListEndpoints(ctx api.Context, selector labels.Selector) (*api.EndpointsList, error) {
131 131
 	c.Actions = append(c.Actions, FakeAction{Action: "list-endpoints"})
132 132
 	return api.Scheme.CopyOrDie(&c.EndpointsList).(*api.EndpointsList), c.Err
133 133
 }
134 134
 
135
-func (c *Fake) WatchEndpoints(label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
135
+func (c *Fake) GetEndpoints(ctx api.Context, name string) (*api.Endpoints, error) {
136
+	c.Actions = append(c.Actions, FakeAction{Action: "get-endpoints"})
137
+	return &api.Endpoints{}, nil
138
+}
139
+
140
+func (c *Fake) WatchEndpoints(ctx api.Context, label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
136 141
 	c.Actions = append(c.Actions, FakeAction{Action: "watch-endpoints", Value: resourceVersion})
137 142
 	return c.Watch, c.Err
138 143
 }
139 144
new file mode 100644
... ...
@@ -0,0 +1,31 @@
0
+/*
1
+Copyright 2014 Google Inc. All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package client
17
+
18
+// FlagSet abstracts the flag interface for compatibility with both Golang "flag"
19
+// and cobra pflags (Posix style).
20
+type FlagSet interface {
21
+	StringVar(p *string, name, value, usage string)
22
+	BoolVar(p *bool, name string, value bool, usage string)
23
+}
24
+
25
+// BindClientConfigFlags registers a standard set of CLI flags for connecting to a Kubernetes API server.
26
+func BindClientConfigFlags(flags FlagSet, config *Config) {
27
+	flags.StringVar(&config.Host, "master", config.Host, "The address of the Kubernetes API server")
28
+	flags.StringVar(&config.Version, "api_version", config.Version, "The API version to use when talking to the server")
29
+	flags.BoolVar(&config.Insecure, "insecure_skip_tls_verify", config.Insecure, "If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure.")
30
+}
0 31
new file mode 100644
... ...
@@ -0,0 +1,238 @@
0
+/*
1
+Copyright 2014 Google Inc. All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package client
17
+
18
+import (
19
+	"fmt"
20
+	"net/http"
21
+	"net/url"
22
+	"path"
23
+	"strings"
24
+
25
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
26
+)
27
+
28
+// Config holds the common attributes that can be passed to a Kubernetes client on
29
+// initialization.
30
+type Config struct {
31
+	// Host must be a host string, a host:port pair, or a URL to the base of the API.
32
+	Host string
33
+	// Prefix is the sub path of the server. If not specified, the client will set
34
+	// a default value.  Use "/" to indicate the server root should be used
35
+	Prefix string
36
+	// Version is the API version to talk to. If not specified, the client will use
37
+	// the preferred version.
38
+	Version string
39
+
40
+	// Server requires Basic authentication
41
+	Username string
42
+	Password string
43
+
44
+	// Server requires Bearer authentication. This client will not attempt to use
45
+	// refresh tokens for an OAuth2 flow.
46
+	// TODO: demonstrate an OAuth2 compatible client.
47
+	BearerToken string
48
+
49
+	// Server requires TLS client certificate authentication
50
+	CertFile string
51
+	KeyFile  string
52
+	CAFile   string
53
+
54
+	// Server should be accessed without verifying the TLS
55
+	// certificate. For testing only.
56
+	Insecure bool
57
+
58
+	// Transport may be used for custom HTTP behavior. This attribute may not
59
+	// be specified with the TLS client certificate options.
60
+	Transport http.RoundTripper
61
+}
62
+
63
+// New creates a Kubernetes client for the given config. This client works with pods,
64
+// replication controllers and services. It allows operations such as list, get, update
65
+// and delete on these objects. An error is returned if the provided configuration
66
+// is not valid.
67
+func New(c *Config) (*Client, error) {
68
+	config := *c
69
+	if config.Prefix == "" {
70
+		config.Prefix = "/api"
71
+	}
72
+	client, err := RESTClientFor(&config)
73
+	if err != nil {
74
+		return nil, err
75
+	}
76
+	return &Client{client}, nil
77
+}
78
+
79
+// NewOrDie creates a Kubernetes client and panics if the provided API version is not recognized.
80
+func NewOrDie(c *Config) *Client {
81
+	client, err := New(c)
82
+	if err != nil {
83
+		panic(err)
84
+	}
85
+	return client
86
+}
87
+
88
+// RESTClientFor returns a RESTClient that satisfies the requested attributes on a client Config
89
+// object.
90
+func RESTClientFor(config *Config) (*RESTClient, error) {
91
+	version := defaultVersionFor(config)
92
+
93
+	// Set version
94
+	versionInterfaces, err := latest.InterfacesFor(version)
95
+	if err != nil {
96
+		return nil, fmt.Errorf("API version '%s' is not recognized (valid values: %s)", version, strings.Join(latest.Versions, ", "))
97
+	}
98
+
99
+	baseURL, err := defaultServerUrlFor(config)
100
+	if err != nil {
101
+		return nil, err
102
+	}
103
+
104
+	client := NewRESTClient(baseURL, versionInterfaces.Codec)
105
+
106
+	transport, err := TransportFor(config)
107
+	if err != nil {
108
+		return nil, err
109
+	}
110
+
111
+	if transport != http.DefaultTransport {
112
+		client.Client = &http.Client{Transport: transport}
113
+	}
114
+	return client, nil
115
+}
116
+
117
+// TransportFor returns an http.RoundTripper that will provide the authentication
118
+// or transport level security defined by the provided Config. Will return the
119
+// default http.DefaultTransport if no special case behavior is needed.
120
+func TransportFor(config *Config) (http.RoundTripper, error) {
121
+	// Set transport level security
122
+	if config.Transport != nil && (config.CertFile != "" || config.Insecure) {
123
+		return nil, fmt.Errorf("using a custom transport with TLS certificate options or the insecure flag is not allowed")
124
+	}
125
+	var transport http.RoundTripper
126
+	switch {
127
+	case config.Transport != nil:
128
+		transport = config.Transport
129
+	case config.CertFile != "":
130
+		t, err := NewClientCertTLSTransport(config.CertFile, config.KeyFile, config.CAFile)
131
+		if err != nil {
132
+			return nil, err
133
+		}
134
+		transport = t
135
+	case config.Insecure:
136
+		transport = NewUnsafeTLSTransport()
137
+	default:
138
+		transport = http.DefaultTransport
139
+	}
140
+
141
+	// Set authentication wrappers
142
+	hasBasicAuth := config.Username != "" || config.Password != ""
143
+	if hasBasicAuth && config.BearerToken != "" {
144
+		return nil, fmt.Errorf("username/password or bearer token may be set, but not both")
145
+	}
146
+	switch {
147
+	case config.BearerToken != "":
148
+		transport = NewBearerAuthRoundTripper(config.BearerToken, transport)
149
+	case hasBasicAuth:
150
+		transport = NewBasicAuthRoundTripper(config.Username, config.Password, transport)
151
+	}
152
+
153
+	// TODO: use the config context to wrap a transport
154
+
155
+	return transport, nil
156
+}
157
+
158
+// DefaultServerURL converts a host, host:port, or URL string to the default base server API path
159
+// to use with a Client at a given API version following the standard conventions for a
160
+// Kubernetes API.
161
+func DefaultServerURL(host, prefix, version string, defaultSecure bool) (*url.URL, error) {
162
+	if host == "" {
163
+		return nil, fmt.Errorf("host must be a URL or a host:port pair")
164
+	}
165
+	if version == "" {
166
+		return nil, fmt.Errorf("version must be set")
167
+	}
168
+	base := host
169
+	hostURL, err := url.Parse(base)
170
+	if err != nil {
171
+		return nil, err
172
+	}
173
+	if hostURL.Scheme == "" {
174
+		scheme := "http://"
175
+		if defaultSecure {
176
+			scheme = "https://"
177
+		}
178
+		hostURL, err = url.Parse(scheme + base)
179
+		if err != nil {
180
+			return nil, err
181
+		}
182
+		if hostURL.Path != "" && hostURL.Path != "/" {
183
+			return nil, fmt.Errorf("host must be a URL or a host:port pair: %s", base)
184
+		}
185
+	}
186
+
187
+	// If the user specified a URL without a path component (http://server.com), automatically
188
+	// append the default prefix
189
+	if hostURL.Path == "" {
190
+		if prefix == "" {
191
+			prefix = "/"
192
+		}
193
+		hostURL.Path = prefix
194
+	}
195
+
196
+	// Add the version to the end of the path
197
+	hostURL.Path = path.Join(hostURL.Path, version)
198
+
199
+	return hostURL, nil
200
+}
201
+
202
+// IsConfigTransportSecure returns true iff the provided config will result in a protected
203
+// connection to the server when it is passed to client.New() or client.RESTClientFor().
204
+// Use to determine when to send credentials over the wire.
205
+//
206
+// Note: the Insecure flag is ignored when testing for this value, so MITM attacks are
207
+// still possible.
208
+func IsConfigTransportSecure(config *Config) bool {
209
+	baseURL, err := defaultServerUrlFor(config)
210
+	if err != nil {
211
+		return false
212
+	}
213
+	return baseURL.Scheme == "https"
214
+}
215
+
216
+// defaultServerUrlFor is shared between IsConfigSecure and RESTClientFor
217
+func defaultServerUrlFor(config *Config) (*url.URL, error) {
218
+	version := defaultVersionFor(config)
219
+	// TODO: move the default to secure when the apiserver supports TLS by default
220
+	defaultSecure := config.CertFile != ""
221
+	host := config.Host
222
+	if host == "" {
223
+		host = "localhost"
224
+	}
225
+	return DefaultServerURL(host, config.Prefix, version, defaultSecure)
226
+}
227
+
228
+// defaultVersionFor is shared between defaultServerUrlFor and RESTClientFor
229
+func defaultVersionFor(config *Config) string {
230
+	version := config.Version
231
+	if version == "" {
232
+		// Clients default to the preferred code API version
233
+		// TODO: implement version negotiation (highest version supported by server)
234
+		version = latest.Version
235
+	}
236
+	return version
237
+}
0 238
new file mode 100644
... ...
@@ -0,0 +1,86 @@
0
+/*
1
+Copyright 2014 Google Inc. All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package client
17
+
18
+import (
19
+	"net/http"
20
+	"testing"
21
+)
22
+
23
+func TestTransportFor(t *testing.T) {
24
+	testCases := map[string]struct {
25
+		Config  *Config
26
+		Err     bool
27
+		Default bool
28
+	}{
29
+		"default transport": {
30
+			Config: &Config{},
31
+		},
32
+	}
33
+	for k, testCase := range testCases {
34
+		transport, err := TransportFor(testCase.Config)
35
+		switch {
36
+		case testCase.Err && err == nil:
37
+			t.Errorf("%s: unexpected non-error", k)
38
+			continue
39
+		case !testCase.Err && err != nil:
40
+			t.Errorf("%s: unexpected error: %v", k, err)
41
+			continue
42
+		}
43
+		if testCase.Default && transport != http.DefaultTransport {
44
+			t.Errorf("%s: expected the default transport, got %#v", k, transport)
45
+		}
46
+	}
47
+}
48
+
49
+func TestIsConfigTransportSecure(t *testing.T) {
50
+	testCases := []struct {
51
+		Config *Config
52
+		Secure bool
53
+	}{
54
+		{
55
+			Config: &Config{},
56
+			Secure: false,
57
+		},
58
+		{
59
+			Config: &Config{
60
+				Host: "https://localhost",
61
+			},
62
+			Secure: true,
63
+		},
64
+		{
65
+			Config: &Config{
66
+				Host:     "localhost",
67
+				CertFile: "foo",
68
+			},
69
+			Secure: true,
70
+		},
71
+		{
72
+			Config: &Config{
73
+				Host:     "///:://localhost",
74
+				CertFile: "foo",
75
+			},
76
+			Secure: false,
77
+		},
78
+	}
79
+	for _, testCase := range testCases {
80
+		secure := IsConfigTransportSecure(testCase.Config)
81
+		if testCase.Secure != secure {
82
+			t.Errorf("expected %d for %#v", testCase.Secure, testCase.Config)
83
+		}
84
+	}
85
+}
... ...
@@ -32,7 +32,9 @@ import (
32 32
 
33 33
 func TestHTTPPodInfoGetter(t *testing.T) {
34 34
 	expectObj := api.PodInfo{
35
-		"myID": docker.Container{ID: "myID"},
35
+		"myID": api.ContainerStatus{
36
+			DetailInfo: docker.Container{ID: "myID"},
37
+		},
36 38
 	}
37 39
 	body, err := json.Marshal(expectObj)
38 40
 	if err != nil {
... ...
@@ -67,14 +69,17 @@ func TestHTTPPodInfoGetter(t *testing.T) {
67 67
 	}
68 68
 
69 69
 	// reflect.DeepEqual(expectObj, gotObj) doesn't handle blank times well
70
-	if len(gotObj) != len(expectObj) || expectObj["myID"].ID != gotObj["myID"].ID {
70
+	if len(gotObj) != len(expectObj) ||
71
+		expectObj["myID"].DetailInfo.ID != gotObj["myID"].DetailInfo.ID {
71 72
 		t.Errorf("Unexpected response.  Expected: %#v, received %#v", expectObj, gotObj)
72 73
 	}
73 74
 }
74 75
 
75 76
 func TestHTTPPodInfoGetterNotFound(t *testing.T) {
76 77
 	expectObj := api.PodInfo{
77
-		"myID": docker.Container{ID: "myID"},
78
+		"myID": api.ContainerStatus{
79
+			DetailInfo: docker.Container{ID: "myID"},
80
+		},
78 81
 	}
79 82
 	_, err := json.Marshal(expectObj)
80 83
 	if err != nil {
... ...
@@ -40,56 +40,6 @@ import (
40 40
 // are therefore not allowed to set manually.
41 41
 var specialParams = util.NewStringSet("sync", "timeout")
42 42
 
43
-// Verb begins a request with a verb (GET, POST, PUT, DELETE).
44
-//
45
-// Example usage of Client's request building interface:
46
-// auth, err := LoadAuth(filename)
47
-// c := New(url, auth)
48
-// resp, err := c.Verb("GET").
49
-//	Path("pods").
50
-//	SelectorParam("labels", "area=staging").
51
-//	Timeout(10*time.Second).
52
-//	Do()
53
-// if err != nil { ... }
54
-// list, ok := resp.(*api.PodList)
55
-//
56
-func (c *RESTClient) Verb(verb string) *Request {
57
-	return &Request{
58
-		verb:       verb,
59
-		c:          c,
60
-		path:       c.prefix,
61
-		sync:       c.Sync,
62
-		timeout:    c.Timeout,
63
-		params:     map[string]string{},
64
-		pollPeriod: c.PollPeriod,
65
-	}
66
-}
67
-
68
-// Post begins a POST request. Short for c.Verb("POST").
69
-func (c *RESTClient) Post() *Request {
70
-	return c.Verb("POST")
71
-}
72
-
73
-// Put begins a PUT request. Short for c.Verb("PUT").
74
-func (c *RESTClient) Put() *Request {
75
-	return c.Verb("PUT")
76
-}
77
-
78
-// Get begins a GET request. Short for c.Verb("GET").
79
-func (c *RESTClient) Get() *Request {
80
-	return c.Verb("GET")
81
-}
82
-
83
-// Delete begins a DELETE request. Short for c.Verb("DELETE").
84
-func (c *RESTClient) Delete() *Request {
85
-	return c.Verb("DELETE")
86
-}
87
-
88
-// PollFor makes a request to do a single poll of the completion of the given operation.
89
-func (c *RESTClient) PollFor(operationID string) *Request {
90
-	return c.Get().Path("operations").Path(operationID).Sync(false).PollPeriod(0)
91
-}
92
-
93 43
 // Request allows for building up a request to a server in a chained fashion.
94 44
 // Any errors are stored until the end of your call, so you only have to
95 45
 // check once.
... ...
@@ -232,7 +182,8 @@ func (r *Request) PollPeriod(d time.Duration) *Request {
232 232
 }
233 233
 
234 234
 func (r *Request) finalURL() string {
235
-	finalURL := r.c.host + r.path
235
+	finalURL := *r.c.baseURL
236
+	finalURL.Path = r.path
236 237
 	query := url.Values{}
237 238
 	for key, value := range r.params {
238 239
 		query.Add(key, value)
... ...
@@ -245,8 +196,8 @@ func (r *Request) finalURL() string {
245 245
 			query.Add("timeout", r.timeout.String())
246 246
 		}
247 247
 	}
248
-	finalURL += "?" + query.Encode()
249
-	return finalURL
248
+	finalURL.RawQuery = query.Encode()
249
+	return finalURL.String()
250 250
 }
251 251
 
252 252
 // Watch attempts to begin watching the requested location.
... ...
@@ -259,10 +210,11 @@ func (r *Request) Watch() (watch.Interface, error) {
259 259
 	if err != nil {
260 260
 		return nil, err
261 261
 	}
262
-	if r.c.auth != nil {
263
-		req.SetBasicAuth(r.c.auth.User, r.c.auth.Password)
262
+	client := r.c.Client
263
+	if client == nil {
264
+		client = http.DefaultClient
264 265
 	}
265
-	response, err := r.c.httpClient.Do(req)
266
+	response, err := client.Do(req)
266 267
 	if err != nil {
267 268
 		return nil, err
268 269
 	}
... ...
@@ -272,6 +224,27 @@ func (r *Request) Watch() (watch.Interface, error) {
272 272
 	return watch.NewStreamWatcher(watchjson.NewDecoder(response.Body, r.c.Codec)), nil
273 273
 }
274 274
 
275
+// Stream formats and executes the request, and offers streaming of the response.
276
+// Returns io.ReadCloser which could be used for streaming of the response, or an error
277
+func (r *Request) Stream() (io.ReadCloser, error) {
278
+	if r.err != nil {
279
+		return nil, r.err
280
+	}
281
+	req, err := http.NewRequest(r.verb, r.finalURL(), nil)
282
+	if err != nil {
283
+		return nil, err
284
+	}
285
+	client := r.c.Client
286
+	if client == nil {
287
+		client = http.DefaultClient
288
+	}
289
+	response, err := client.Do(req)
290
+	if err != nil {
291
+		return nil, err
292
+	}
293
+	return response.Body, nil
294
+}
295
+
275 296
 // Do formats and executes the request. Returns the API object received, or an error.
276 297
 func (r *Request) Do() Result {
277 298
 	for {
... ...
@@ -284,10 +257,11 @@ func (r *Request) Do() Result {
284 284
 		}
285 285
 		respBody, err := r.c.doRequest(req)
286 286
 		if err != nil {
287
-			if statusErr, ok := err.(*StatusErr); ok {
288
-				if statusErr.Status.Status == api.StatusWorking && r.pollPeriod != 0 {
289
-					if statusErr.Status.Details != nil {
290
-						id := statusErr.Status.Details.ID
287
+			if s, ok := err.(APIStatus); ok {
288
+				status := s.Status()
289
+				if status.Status == api.StatusWorking && r.pollPeriod != 0 {
290
+					if status.Details != nil {
291
+						id := status.Details.ID
291 292
 						if len(id) > 0 {
292 293
 							glog.Infof("Waiting for completion of /operations/%s", id)
293 294
 							time.Sleep(r.pollPeriod)
... ...
@@ -48,8 +48,7 @@ func TestDoRequestNewWay(t *testing.T) {
48 48
 		T:            t,
49 49
 	}
50 50
 	testServer := httptest.NewServer(&fakeHandler)
51
-	auth := AuthInfo{User: "user", Password: "pass"}
52
-	c := NewOrDie(testServer.URL, "v1beta2", &auth)
51
+	c := NewOrDie(&Config{Host: testServer.URL, Version: "v1beta2", Username: "user", Password: "pass"})
53 52
 	obj, err := c.Verb("POST").
54 53
 		Path("foo/bar").
55 54
 		Path("baz").
... ...
@@ -83,8 +82,7 @@ func TestDoRequestNewWayReader(t *testing.T) {
83 83
 		T:            t,
84 84
 	}
85 85
 	testServer := httptest.NewServer(&fakeHandler)
86
-	auth := AuthInfo{User: "user", Password: "pass"}
87
-	c := NewOrDie(testServer.URL, "v1beta1", &auth)
86
+	c := NewOrDie(&Config{Host: testServer.URL, Version: "v1beta1", Username: "user", Password: "pass"})
88 87
 	obj, err := c.Verb("POST").
89 88
 		Path("foo/bar").
90 89
 		Path("baz").
... ...
@@ -120,8 +118,7 @@ func TestDoRequestNewWayObj(t *testing.T) {
120 120
 		T:            t,
121 121
 	}
122 122
 	testServer := httptest.NewServer(&fakeHandler)
123
-	auth := AuthInfo{User: "user", Password: "pass"}
124
-	c := NewOrDie(testServer.URL, "v1beta2", &auth)
123
+	c := NewOrDie(&Config{Host: testServer.URL, Version: "v1beta2", Username: "user", Password: "pass"})
125 124
 	obj, err := c.Verb("POST").
126 125
 		Path("foo/bar").
127 126
 		Path("baz").
... ...
@@ -170,8 +167,7 @@ func TestDoRequestNewWayFile(t *testing.T) {
170 170
 		T:            t,
171 171
 	}
172 172
 	testServer := httptest.NewServer(&fakeHandler)
173
-	auth := AuthInfo{User: "user", Password: "pass"}
174
-	c := NewOrDie(testServer.URL, "v1beta1", &auth)
173
+	c := NewOrDie(&Config{Host: testServer.URL, Version: "v1beta1", Username: "user", Password: "pass"})
175 174
 	obj, err := c.Verb("POST").
176 175
 		Path("foo/bar").
177 176
 		Path("baz").
... ...
@@ -196,7 +192,7 @@ func TestDoRequestNewWayFile(t *testing.T) {
196 196
 }
197 197
 
198 198
 func TestVerbs(t *testing.T) {
199
-	c := NewOrDie("localhost", "", nil)
199
+	c := NewOrDie(&Config{})
200 200
 	if r := c.Post(); r.verb != "POST" {
201 201
 		t.Errorf("Post verb is wrong")
202 202
 	}
... ...
@@ -213,7 +209,7 @@ func TestVerbs(t *testing.T) {
213 213
 
214 214
 func TestAbsPath(t *testing.T) {
215 215
 	expectedPath := "/bar/foo"
216
-	c := NewOrDie("localhost", "", nil)
216
+	c := NewOrDie(&Config{})
217 217
 	r := c.Post().Path("/foo").AbsPath(expectedPath)
218 218
 	if r.path != expectedPath {
219 219
 		t.Errorf("unexpected path: %s, expected %s", r.path, expectedPath)
... ...
@@ -221,7 +217,7 @@ func TestAbsPath(t *testing.T) {
221 221
 }
222 222
 
223 223
 func TestSync(t *testing.T) {
224
-	c := NewOrDie("localhost", "", nil)
224
+	c := NewOrDie(&Config{})
225 225
 	r := c.Get()
226 226
 	if r.sync {
227 227
 		t.Errorf("sync has wrong default")
... ...
@@ -248,7 +244,7 @@ func TestUintParam(t *testing.T) {
248 248
 	}
249 249
 
250 250
 	for _, item := range table {
251
-		c := NewOrDie("localhost", "", nil)
251
+		c := NewOrDie(&Config{})
252 252
 		r := c.Get().AbsPath("").UintParam(item.name, item.testVal)
253 253
 		if e, a := item.expectStr, r.finalURL(); e != a {
254 254
 			t.Errorf("expected %v, got %v", e, a)
... ...
@@ -267,7 +263,7 @@ func TestUnacceptableParamNames(t *testing.T) {
267 267
 	}
268 268
 
269 269
 	for _, item := range table {
270
-		c := NewOrDie("localhost", "", nil)
270
+		c := NewOrDie(&Config{})
271 271
 		r := c.Get().setParam(item.name, item.testVal)
272 272
 		if e, a := item.expectSuccess, r.err == nil; e != a {
273 273
 			t.Errorf("expected %v, got %v (%v)", e, a, r.err)
... ...
@@ -276,7 +272,7 @@ func TestUnacceptableParamNames(t *testing.T) {
276 276
 }
277 277
 
278 278
 func TestSetPollPeriod(t *testing.T) {
279
-	c := NewOrDie("localhost", "", nil)
279
+	c := NewOrDie(&Config{})
280 280
 	r := c.Get()
281 281
 	if r.pollPeriod == 0 {
282 282
 		t.Errorf("polling should be on by default")
... ...
@@ -306,8 +302,7 @@ func TestPolling(t *testing.T) {
306 306
 		w.Write(data)
307 307
 	}))
308 308
 
309
-	auth := AuthInfo{User: "user", Password: "pass"}
310
-	c := NewOrDie(testServer.URL, "v1beta1", &auth)
309
+	c := NewOrDie(&Config{Host: testServer.URL, Version: "v1beta1", Username: "user", Password: "pass"})
311 310
 
312 311
 	trials := []func(){
313 312
 		func() {
... ...
@@ -332,7 +327,7 @@ func TestPolling(t *testing.T) {
332 332
 				t.Errorf("Unexpected non error: %v", obj)
333 333
 				return
334 334
 			}
335
-			if se, ok := err.(*StatusErr); !ok || se.Status.Status != api.StatusWorking {
335
+			if se, ok := err.(APIStatus); !ok || se.Status().Status != api.StatusWorking {
336 336
 				t.Errorf("Unexpected kind of error: %#v", err)
337 337
 				return
338 338
 			}
... ...
@@ -347,7 +342,7 @@ func TestPolling(t *testing.T) {
347 347
 	}
348 348
 }
349 349
 
350
-func authFromReq(r *http.Request) (*AuthInfo, bool) {
350
+func authFromReq(r *http.Request) (*Config, bool) {
351 351
 	auth, ok := r.Header["Authorization"]
352 352
 	if !ok {
353 353
 		return nil, false
... ...
@@ -366,16 +361,16 @@ func authFromReq(r *http.Request) (*AuthInfo, bool) {
366 366
 	if len(parts) != 2 {
367 367
 		return nil, false
368 368
 	}
369
-	return &AuthInfo{User: parts[0], Password: parts[1]}, true
369
+	return &Config{Username: parts[0], Password: parts[1]}, true
370 370
 }
371 371
 
372 372
 // checkAuth sets errors if the auth found in r doesn't match the expectation.
373 373
 // TODO: Move to util, test in more places.
374
-func checkAuth(t *testing.T, expect AuthInfo, r *http.Request) {
374
+func checkAuth(t *testing.T, expect *Config, r *http.Request) {
375 375
 	foundAuth, found := authFromReq(r)
376 376
 	if !found {
377 377
 		t.Errorf("no auth found")
378
-	} else if e, a := expect, *foundAuth; !reflect.DeepEqual(e, a) {
378
+	} else if e, a := expect, foundAuth; !reflect.DeepEqual(e, a) {
379 379
 		t.Fatalf("Wrong basic auth: wanted %#v, got %#v", e, a)
380 380
 	}
381 381
 }
... ...
@@ -390,7 +385,7 @@ func TestWatch(t *testing.T) {
390 390
 		{watch.Deleted, &api.Pod{JSONBase: api.JSONBase{ID: "last"}}},
391 391
 	}
392 392
 
393
-	auth := AuthInfo{User: "user", Password: "pass"}
393
+	auth := &Config{Username: "user", Password: "pass"}
394 394
 	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
395 395
 		checkAuth(t, auth, r)
396 396
 		flusher, ok := w.(http.Flusher)
... ...
@@ -411,7 +406,12 @@ func TestWatch(t *testing.T) {
411 411
 		}
412 412
 	}))
413 413
 
414
-	s, err := New(testServer.URL, "v1beta1", &auth)
414
+	s, err := New(&Config{
415
+		Host:     testServer.URL,
416
+		Version:  "v1beta1",
417
+		Username: "user",
418
+		Password: "pass",
419
+	})
415 420
 	if err != nil {
416 421
 		t.Fatalf("unexpected error: %v", err)
417 422
 	}
... ...
@@ -439,3 +439,42 @@ func TestWatch(t *testing.T) {
439 439
 		t.Fatal("Unexpected non-close")
440 440
 	}
441 441
 }
442
+
443
+func TestStream(t *testing.T) {
444
+	auth := &Config{Username: "user", Password: "pass"}
445
+	expectedBody := "expected body"
446
+
447
+	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
448
+		checkAuth(t, auth, r)
449
+		flusher, ok := w.(http.Flusher)
450
+		if !ok {
451
+			panic("need flusher!")
452
+		}
453
+		w.Header().Set("Transfer-Encoding", "chunked")
454
+		w.WriteHeader(http.StatusOK)
455
+		w.Write([]byte(expectedBody))
456
+		flusher.Flush()
457
+	}))
458
+
459
+	s, err := New(&Config{
460
+		Host:     testServer.URL,
461
+		Version:  "v1beta1",
462
+		Username: "user",
463
+		Password: "pass",
464
+	})
465
+	if err != nil {
466
+		t.Fatalf("unexpected error: %v", err)
467
+	}
468
+	readCloser, err := s.Get().Path("path/to/stream/thing").Stream()
469
+	if err != nil {
470
+		t.Fatalf("unexpected error: %v", err)
471
+	}
472
+	defer readCloser.Close()
473
+	buf := new(bytes.Buffer)
474
+	buf.ReadFrom(readCloser)
475
+	resultBody := buf.String()
476
+
477
+	if expectedBody != resultBody {
478
+		t.Errorf("Expected %s, got %s", expectedBody, resultBody)
479
+	}
480
+}
442 481
new file mode 100644
... ...
@@ -0,0 +1,172 @@
0
+/*
1
+Copyright 2014 Google Inc. All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package client
17
+
18
+import (
19
+	"fmt"
20
+	"io/ioutil"
21
+	"net/http"
22
+	"net/url"
23
+	"strings"
24
+	"time"
25
+
26
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
27
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
28
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
29
+)
30
+
31
+// RESTClient imposes common Kubernetes API conventions on a set of resource paths.
32
+// The baseURL is expected to point to an HTTP or HTTPS path that is the parent
33
+// of one or more resources.  The server should return a decodable API resource
34
+// object, or an api.Status object which contains information about the reason for
35
+// any failure.
36
+//
37
+// Most consumers should use client.New() to get a Kubernetes API client.
38
+type RESTClient struct {
39
+	baseURL *url.URL
40
+
41
+	// Codec is the encoding and decoding scheme that applies to a particular set of
42
+	// REST resources.
43
+	Codec runtime.Codec
44
+
45
+	// Set specific behavior of the client.  If not set http.DefaultClient will be
46
+	// used.
47
+	Client *http.Client
48
+
49
+	Sync       bool
50
+	PollPeriod time.Duration
51
+	Timeout    time.Duration
52
+}
53
+
54
+// NewRESTClient creates a new RESTClient. This client performs generic REST functions
55
+// such as Get, Put, Post, and Delete on specified paths.  Codec controls encoding and
56
+// decoding of responses from the server.
57
+func NewRESTClient(baseURL *url.URL, c runtime.Codec) *RESTClient {
58
+	base := *baseURL
59
+	if !strings.HasSuffix(base.Path, "/") {
60
+		base.Path += "/"
61
+	}
62
+	base.RawQuery = ""
63
+	base.Fragment = ""
64
+
65
+	return &RESTClient{
66
+		baseURL: &base,
67
+		Codec:   c,
68
+
69
+		// Make asynchronous requests by default
70
+		// TODO: flip me to the default
71
+		Sync: false,
72
+		// Poll frequently when asynchronous requests are provided
73
+		PollPeriod: time.Second * 2,
74
+	}
75
+}
76
+
77
+// doRequest executes a request against a server
78
+func (c *RESTClient) doRequest(request *http.Request) ([]byte, error) {
79
+	client := c.Client
80
+	if client == nil {
81
+		client = http.DefaultClient
82
+	}
83
+
84
+	response, err := client.Do(request)
85
+	if err != nil {
86
+		return nil, err
87
+	}
88
+	defer response.Body.Close()
89
+	body, err := ioutil.ReadAll(response.Body)
90
+	if err != nil {
91
+		return body, err
92
+	}
93
+
94
+	// Did the server give us a status response?
95
+	isStatusResponse := false
96
+	var status api.Status
97
+	if err := c.Codec.DecodeInto(body, &status); err == nil && status.Status != "" {
98
+		isStatusResponse = true
99
+	}
100
+
101
+	switch {
102
+	case response.StatusCode < http.StatusOK || response.StatusCode > http.StatusPartialContent:
103
+		if !isStatusResponse {
104
+			return nil, fmt.Errorf("request [%#v] failed (%d) %s: %s", request, response.StatusCode, response.Status, string(body))
105
+		}
106
+		return nil, errors.FromObject(&status)
107
+	}
108
+
109
+	// If the server gave us a status back, look at what it was.
110
+	if isStatusResponse && status.Status != api.StatusSuccess {
111
+		// "Working" requests need to be handled specially.
112
+		// "Failed" requests are clearly just an error and it makes sense to return them as such.
113
+		return nil, errors.FromObject(&status)
114
+	}
115
+
116
+	return body, err
117
+}
118
+
119
+// Verb begins a request with a verb (GET, POST, PUT, DELETE).
120
+//
121
+// Example usage of RESTClient's request building interface:
122
+// c := NewRESTClient(url, codec)
123
+// resp, err := c.Verb("GET").
124
+//  Path("pods").
125
+//  SelectorParam("labels", "area=staging").
126
+//  Timeout(10*time.Second).
127
+//  Do()
128
+// if err != nil { ... }
129
+// list, ok := resp.(*api.PodList)
130
+//
131
+func (c *RESTClient) Verb(verb string) *Request {
132
+	// TODO: uncomment when Go 1.2 support is dropped
133
+	//var timeout time.Duration = 0
134
+	// if c.Client != nil {
135
+	// 	timeout = c.Client.Timeout
136
+	// }
137
+	return &Request{
138
+		verb:       verb,
139
+		c:          c,
140
+		path:       c.baseURL.Path,
141
+		sync:       c.Sync,
142
+		timeout:    c.Timeout,
143
+		params:     map[string]string{},
144
+		pollPeriod: c.PollPeriod,
145
+	}
146
+}
147
+
148
+// Post begins a POST request. Short for c.Verb("POST").
149
+func (c *RESTClient) Post() *Request {
150
+	return c.Verb("POST")
151
+}
152
+
153
+// Put begins a PUT request. Short for c.Verb("PUT").
154
+func (c *RESTClient) Put() *Request {
155
+	return c.Verb("PUT")
156
+}
157
+
158
+// Get begins a GET request. Short for c.Verb("GET").
159
+func (c *RESTClient) Get() *Request {
160
+	return c.Verb("GET")
161
+}
162
+
163
+// Delete begins a DELETE request. Short for c.Verb("DELETE").
164
+func (c *RESTClient) Delete() *Request {
165
+	return c.Verb("DELETE")
166
+}
167
+
168
+// PollFor makes a request to do a single poll of the completion of the given operation.
169
+func (c *RESTClient) PollFor(operationID string) *Request {
170
+	return c.Get().Path("operations").Path(operationID).Sync(false).PollPeriod(0)
171
+}
0 172
new file mode 100644
... ...
@@ -0,0 +1,241 @@
0
+/*
1
+Copyright 2014 Google Inc. All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package client
17
+
18
+import (
19
+	"net/http"
20
+	"net/http/httptest"
21
+	"net/url"
22
+	"reflect"
23
+	"testing"
24
+
25
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
26
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
27
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
28
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2"
29
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
30
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
31
+)
32
+
33
+func TestChecksCodec(t *testing.T) {
34
+	testCases := map[string]struct {
35
+		Err    bool
36
+		Prefix string
37
+		Codec  runtime.Codec
38
+	}{
39
+		"v1beta1": {false, "/v1beta1/", v1beta1.Codec},
40
+		"":        {false, "/v1beta1/", v1beta1.Codec},
41
+		"v1beta2": {false, "/v1beta2/", v1beta2.Codec},
42
+		"v1beta3": {true, "", nil},
43
+	}
44
+	for version, expected := range testCases {
45
+		client, err := RESTClientFor(&Config{Host: "127.0.0.1", Version: version})
46
+		switch {
47
+		case err == nil && expected.Err:
48
+			t.Errorf("expected error but was nil")
49
+			continue
50
+		case err != nil && !expected.Err:
51
+			t.Errorf("unexpected error %v", err)
52
+			continue
53
+		case err != nil:
54
+			continue
55
+		}
56
+		if e, a := expected.Prefix, client.baseURL.Path; e != a {
57
+			t.Errorf("expected %#v, got %#v", e, a)
58
+		}
59
+		if e, a := expected.Codec, client.Codec; e != a {
60
+			t.Errorf("expected %#v, got %#v", e, a)
61
+		}
62
+	}
63
+}
64
+
65
+func TestValidatesHostParameter(t *testing.T) {
66
+	testCases := []struct {
67
+		Host   string
68
+		Prefix string
69
+
70
+		URL string
71
+		Err bool
72
+	}{
73
+		{"127.0.0.1", "", "http://127.0.0.1/v1beta1/", false},
74
+		{"127.0.0.1:8080", "", "http://127.0.0.1:8080/v1beta1/", false},
75
+		{"foo.bar.com", "", "http://foo.bar.com/v1beta1/", false},
76
+		{"http://host/prefix", "", "http://host/prefix/v1beta1/", false},
77
+		{"http://host", "", "http://host/v1beta1/", false},
78
+		{"http://host", "/", "http://host/v1beta1/", false},
79
+		{"http://host", "/other", "http://host/other/v1beta1/", false},
80
+		{"host/server", "", "", true},
81
+	}
82
+	for i, testCase := range testCases {
83
+		c, err := RESTClientFor(&Config{Host: testCase.Host, Prefix: testCase.Prefix, Version: "v1beta1"})
84
+		switch {
85
+		case err == nil && testCase.Err:
86
+			t.Errorf("expected error but was nil")
87
+			continue
88
+		case err != nil && !testCase.Err:
89
+			t.Errorf("unexpected error %v", err)
90
+			continue
91
+		case err != nil:
92
+			continue
93
+		}
94
+		if e, a := testCase.URL, c.baseURL.String(); e != a {
95
+			t.Errorf("%d: expected host %s, got %s", i, e, a)
96
+			continue
97
+		}
98
+	}
99
+}
100
+
101
+func TestDoRequest(t *testing.T) {
102
+	invalid := "aaaaa"
103
+	uri, _ := url.Parse("http://localhost")
104
+	testClients := []testClient{
105
+		{Request: testRequest{Method: "GET", Path: "good"}, Response: Response{StatusCode: 200}},
106
+		{Request: testRequest{Method: "GET", Path: "bad%ZZ"}, Error: true},
107
+		{Client: &Client{&RESTClient{baseURL: uri}}, Request: testRequest{Method: "GET", Path: "nocertificate"}, Error: true},
108
+		{Request: testRequest{Method: "GET", Path: "error"}, Response: Response{StatusCode: 500}, Error: true},
109
+		{Request: testRequest{Method: "POST", Path: "faildecode"}, Response: Response{StatusCode: 200, RawBody: &invalid}},
110
+		{Request: testRequest{Method: "GET", Path: "failread"}, Response: Response{StatusCode: 200, RawBody: &invalid}},
111
+	}
112
+	for _, c := range testClients {
113
+		client := c.Setup()
114
+		prefix := *client.baseURL
115
+		prefix.Path += c.Request.Path
116
+		request := &http.Request{
117
+			Method: c.Request.Method,
118
+			Header: make(http.Header),
119
+			URL:    &prefix,
120
+		}
121
+		response, err := client.doRequest(request)
122
+		//t.Logf("dorequest: %#v\n%#v\n%v", request.URL, response, err)
123
+		c.ValidateRaw(t, response, err)
124
+	}
125
+}
126
+
127
+func TestDoRequestBearer(t *testing.T) {
128
+	status := &api.Status{Status: api.StatusWorking}
129
+	expectedBody, _ := latest.Codec.Encode(status)
130
+	fakeHandler := util.FakeHandler{
131
+		StatusCode:   202,
132
+		ResponseBody: string(expectedBody),
133
+		T:            t,
134
+	}
135
+	testServer := httptest.NewServer(&fakeHandler)
136
+	request, _ := http.NewRequest("GET", testServer.URL+"/foo/bar", nil)
137
+	c, err := RESTClientFor(&Config{Host: testServer.URL, BearerToken: "test"})
138
+	if err != nil {
139
+		t.Fatalf("unexpected error: %v", err)
140
+	}
141
+	c.doRequest(request)
142
+	if fakeHandler.RequestReceived.Header.Get("Authorization") != "Bearer test" {
143
+		t.Errorf("Request is missing authorization header: %#v", *request)
144
+	}
145
+}
146
+
147
+func TestDoRequestAccepted(t *testing.T) {
148
+	status := &api.Status{Status: api.StatusWorking}
149
+	expectedBody, _ := latest.Codec.Encode(status)
150
+	fakeHandler := util.FakeHandler{
151
+		StatusCode:   202,
152
+		ResponseBody: string(expectedBody),
153
+		T:            t,
154
+	}
155
+	testServer := httptest.NewServer(&fakeHandler)
156
+	request, _ := http.NewRequest("GET", testServer.URL+"/foo/bar", nil)
157
+	c, err := RESTClientFor(&Config{Host: testServer.URL, Username: "test"})
158
+	if err != nil {
159
+		t.Fatalf("unexpected error: %v", err)
160
+	}
161
+	body, err := c.doRequest(request)
162
+	if fakeHandler.RequestReceived.Header["Authorization"] == nil {
163
+		t.Errorf("Request is missing authorization header: %#v", *request)
164
+	}
165
+	if err == nil {
166
+		t.Error("Unexpected non-error")
167
+		return
168
+	}
169
+	se, ok := err.(APIStatus)
170
+	if !ok {
171
+		t.Errorf("Unexpected kind of error: %#v", err)
172
+		return
173
+	}
174
+	if !reflect.DeepEqual(se.Status(), *status) {
175
+		t.Errorf("Unexpected status: %#v %#v", se.Status(), status)
176
+	}
177
+	if body != nil {
178
+		t.Errorf("Expected nil body, but saw: '%s'", body)
179
+	}
180
+	fakeHandler.ValidateRequest(t, "/foo/bar", "GET", nil)
181
+}
182
+
183
+func TestDoRequestAcceptedSuccess(t *testing.T) {
184
+	status := &api.Status{Status: api.StatusSuccess}
185
+	expectedBody, _ := latest.Codec.Encode(status)
186
+	fakeHandler := util.FakeHandler{
187
+		StatusCode:   202,
188
+		ResponseBody: string(expectedBody),
189
+		T:            t,
190
+	}
191
+	testServer := httptest.NewServer(&fakeHandler)
192
+	request, _ := http.NewRequest("GET", testServer.URL+"/foo/bar", nil)
193
+	c, err := RESTClientFor(&Config{Host: testServer.URL, Username: "user", Password: "pass"})
194
+	if err != nil {
195
+		t.Fatalf("unexpected error: %v", err)
196
+	}
197
+	body, err := c.doRequest(request)
198
+	if fakeHandler.RequestReceived.Header["Authorization"] == nil {
199
+		t.Errorf("Request is missing authorization header: %#v", *request)
200
+	}
201
+	if err != nil {
202
+		t.Errorf("Unexpected error %#v", err)
203
+	}
204
+	statusOut, err := latest.Codec.Decode(body)
205
+	if err != nil {
206
+		t.Errorf("Unexpected error %#v", err)
207
+	}
208
+	if !reflect.DeepEqual(status, statusOut) {
209
+		t.Errorf("Unexpected mis-match. Expected %#v.  Saw %#v", status, statusOut)
210
+	}
211
+	fakeHandler.ValidateRequest(t, "/foo/bar", "GET", nil)
212
+}
213
+
214
+func TestDoRequestFailed(t *testing.T) {
215
+	status := &api.Status{Status: api.StatusFailure, Reason: api.StatusReasonInvalid, Details: &api.StatusDetails{ID: "test", Kind: "test"}}
216
+	expectedBody, _ := latest.Codec.Encode(status)
217
+	fakeHandler := util.FakeHandler{
218
+		StatusCode:   404,
219
+		ResponseBody: string(expectedBody),
220
+		T:            t,
221
+	}
222
+	testServer := httptest.NewServer(&fakeHandler)
223
+	request, _ := http.NewRequest("GET", testServer.URL+"/foo/bar", nil)
224
+	c, err := RESTClientFor(&Config{Host: testServer.URL})
225
+	if err != nil {
226
+		t.Fatalf("unexpected error: %v", err)
227
+	}
228
+	body, err := c.doRequest(request)
229
+	if err == nil || body != nil {
230
+		t.Errorf("unexpected non-error: %#v", body)
231
+	}
232
+	ss, ok := err.(APIStatus)
233
+	if !ok {
234
+		t.Errorf("unexpected error type %v", err)
235
+	}
236
+	actual := ss.Status()
237
+	if !reflect.DeepEqual(status, &actual) {
238
+		t.Errorf("Unexpected mis-match. Expected %#v.  Saw %#v", status, actual)
239
+	}
240
+}
0 241
new file mode 100644
... ...
@@ -0,0 +1,101 @@
0
+/*
1
+Copyright 2014 Google Inc. All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package client
17
+
18
+import (
19
+	"crypto/tls"
20
+	"crypto/x509"
21
+	"fmt"
22
+	"io/ioutil"
23
+	"net/http"
24
+)
25
+
26
+type basicAuthRoundTripper struct {
27
+	username string
28
+	password string
29
+	rt       http.RoundTripper
30
+}
31
+
32
+func NewBasicAuthRoundTripper(username, password string, rt http.RoundTripper) http.RoundTripper {
33
+	return &basicAuthRoundTripper{username, password, rt}
34
+}
35
+
36
+func (rt *basicAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
37
+	req = cloneRequest(req)
38
+	req.SetBasicAuth(rt.username, rt.password)
39
+	return rt.rt.RoundTrip(req)
40
+}
41
+
42
+type bearerAuthRoundTripper struct {
43
+	bearer string
44
+	rt     http.RoundTripper
45
+}
46
+
47
+func NewBearerAuthRoundTripper(bearer string, rt http.RoundTripper) http.RoundTripper {
48
+	return &bearerAuthRoundTripper{bearer, rt}
49
+}
50
+
51
+func (rt *bearerAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
52
+	req = cloneRequest(req)
53
+	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", rt.bearer))
54
+	return rt.rt.RoundTrip(req)
55
+}
56
+
57
+func NewClientCertTLSTransport(certFile, keyFile, caFile string) (*http.Transport, error) {
58
+	cert, err := tls.LoadX509KeyPair(certFile, keyFile)
59
+	if err != nil {
60
+		return nil, err
61
+	}
62
+	data, err := ioutil.ReadFile(caFile)
63
+	if err != nil {
64
+		return nil, err
65
+	}
66
+	certPool := x509.NewCertPool()
67
+	certPool.AppendCertsFromPEM(data)
68
+	return &http.Transport{
69
+		TLSClientConfig: &tls.Config{
70
+			Certificates: []tls.Certificate{
71
+				cert,
72
+			},
73
+			RootCAs:    certPool,
74
+			ClientCAs:  certPool,
75
+			ClientAuth: tls.RequireAndVerifyClientCert,
76
+		},
77
+	}, nil
78
+}
79
+
80
+func NewUnsafeTLSTransport() *http.Transport {
81
+	return &http.Transport{
82
+		TLSClientConfig: &tls.Config{
83
+			InsecureSkipVerify: true,
84
+		},
85
+	}
86
+}
87
+
88
+// cloneRequest returns a clone of the provided *http.Request.
89
+// The clone is a shallow copy of the struct and its Header map.
90
+func cloneRequest(r *http.Request) *http.Request {
91
+	// shallow copy of the struct
92
+	r2 := new(http.Request)
93
+	*r2 = *r
94
+	// deep copy of the Header
95
+	r2.Header = make(http.Header)
96
+	for k, s := range r.Header {
97
+		r2.Header[k] = s
98
+	}
99
+	return r2
100
+}
0 101
new file mode 100644
... ...
@@ -0,0 +1,71 @@
0
+/*
1
+Copyright 2014 Google Inc. All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package client
17
+
18
+import (
19
+	"encoding/base64"
20
+	"net/http"
21
+	"testing"
22
+)
23
+
24
+func TestUnsecuredTLSTransport(t *testing.T) {
25
+	transport := NewUnsafeTLSTransport()
26
+	if !transport.TLSClientConfig.InsecureSkipVerify {
27
+		t.Errorf("expected transport to be insecure")
28
+	}
29
+}
30
+
31
+type testRoundTripper struct {
32
+	Request  *http.Request
33
+	Response *http.Response
34
+	Err      error
35
+}
36
+
37
+func (rt *testRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
38
+	rt.Request = req
39
+	return rt.Response, rt.Err
40
+}
41
+
42
+func TestBearerAuthRoundTripper(t *testing.T) {
43
+	rt := &testRoundTripper{}
44
+	req := &http.Request{}
45
+	NewBearerAuthRoundTripper("test", rt).RoundTrip(req)
46
+	if rt.Request == nil {
47
+		t.Fatalf("unexpected nil request", rt)
48
+	}
49
+	if rt.Request == req {
50
+		t.Fatalf("round tripper should have copied request object: %#v", rt.Request)
51
+	}
52
+	if rt.Request.Header.Get("Authorization") != "Bearer test" {
53
+		t.Errorf("unexpected authorization header: %#v", rt.Request)
54
+	}
55
+}
56
+
57
+func TestBasicAuthRoundTripper(t *testing.T) {
58
+	rt := &testRoundTripper{}
59
+	req := &http.Request{}
60
+	NewBasicAuthRoundTripper("user", "pass", rt).RoundTrip(req)
61
+	if rt.Request == nil {
62
+		t.Fatalf("unexpected nil request", rt)
63
+	}
64
+	if rt.Request == req {
65
+		t.Fatalf("round tripper should have copied request object: %#v", rt.Request)
66
+	}
67
+	if rt.Request.Header.Get("Authorization") != "Basic "+base64.StdEncoding.EncodeToString([]byte("user:pass")) {
68
+		t.Errorf("unexpected authorization header: %#v", rt.Request)
69
+	}
70
+}
... ...
@@ -89,20 +89,20 @@ func mockInstancesResp(instances []ec2.Instance) (aws *AWSCloud) {
89 89
 			func(instanceIds []string, filter *ec2.Filter) (resp *ec2.InstancesResp, err error) {
90 90
 				return &ec2.InstancesResp{"",
91 91
 					[]ec2.Reservation{
92
-						ec2.Reservation{"", "", "", nil, instances}}}, nil
92
+						{"", "", "", nil, instances}}}, nil
93 93
 			}},
94 94
 		nil}
95 95
 }
96 96
 
97 97
 func TestList(t *testing.T) {
98 98
 	instances := make([]ec2.Instance, 4)
99
-	instances[0].Tags = []ec2.Tag{ec2.Tag{"Name", "foo"}}
99
+	instances[0].Tags = []ec2.Tag{{"Name", "foo"}}
100 100
 	instances[0].PrivateDNSName = "instance1"
101
-	instances[1].Tags = []ec2.Tag{ec2.Tag{"Name", "bar"}}
101
+	instances[1].Tags = []ec2.Tag{{"Name", "bar"}}
102 102
 	instances[1].PrivateDNSName = "instance2"
103
-	instances[2].Tags = []ec2.Tag{ec2.Tag{"Name", "baz"}}
103
+	instances[2].Tags = []ec2.Tag{{"Name", "baz"}}
104 104
 	instances[2].PrivateDNSName = "instance3"
105
-	instances[3].Tags = []ec2.Tag{ec2.Tag{"Name", "quux"}}
105
+	instances[3].Tags = []ec2.Tag{{"Name", "quux"}}
106 106
 	instances[3].PrivateDNSName = "instance4"
107 107
 
108 108
 	aws := mockInstancesResp(instances)
... ...
@@ -30,6 +30,7 @@ import (
30 30
 	"code.google.com/p/goauth2/compute/serviceaccount"
31 31
 	compute "code.google.com/p/google-api-go-client/compute/v1"
32 32
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
33
+	"github.com/golang/glog"
33 34
 )
34 35
 
35 36
 // GCECloud is an implementation of Interface, TCPLoadBalancer and Instances for Google Compute Engine.
... ...
@@ -105,10 +106,7 @@ func (gce *GCECloud) Zones() (cloudprovider.Zones, bool) {
105 105
 }
106 106
 
107 107
 func makeHostLink(projectID, zone, host string) string {
108
-	ix := strings.Index(host, ".")
109
-	if ix != -1 {
110
-		host = host[:ix]
111
-	}
108
+	host = canonicalizeInstanceName(host)
112 109
 	return fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%s/zones/%s/instances/%s",
113 110
 		projectID, zone, host)
114 111
 }
... ...
@@ -189,10 +187,23 @@ func (gce *GCECloud) DeleteTCPLoadBalancer(name, region string) error {
189 189
 	return err
190 190
 }
191 191
 
192
+// Take a GCE instance 'hostname' and break it down to something that can be fed
193
+// to the GCE API client library.  Basically this means reducing 'kubernetes-
194
+// minion-2.c.my-proj.internal' to 'kubernetes-minion-2' if necessary.
195
+func canonicalizeInstanceName(name string) string {
196
+	ix := strings.Index(name, ".")
197
+	if ix != -1 {
198
+		name = name[:ix]
199
+	}
200
+	return name
201
+}
202
+
192 203
 // IPAddress is an implementation of Instances.IPAddress.
193 204
 func (gce *GCECloud) IPAddress(instance string) (net.IP, error) {
205
+	instance = canonicalizeInstanceName(instance)
194 206
 	res, err := gce.service.Instances.Get(gce.projectID, gce.zone, instance).Do()
195 207
 	if err != nil {
208
+		glog.Errorf("Failed to retrieve TargetInstance resource for instance:%s", instance)
196 209
 		return nil, err
197 210
 	}
198 211
 	ip := net.ParseIP(res.NetworkInterfaces[0].AccessConfigs[0].NatIP)
... ...
@@ -18,9 +18,9 @@ package ovirt_cloud
18 18
 
19 19
 import (
20 20
 	"encoding/xml"
21
+	"fmt"
21 22
 	"io"
22 23
 	"io/ioutil"
23
-	"fmt"
24 24
 	"net"
25 25
 	"net/http"
26 26
 	"net/url"
... ...
@@ -32,8 +32,8 @@ import (
32 32
 )
33 33
 
34 34
 type OVirtCloud struct {
35
-	VmsRequest         *url.URL
36
-	HostsRequest       *url.URL
35
+	VmsRequest   *url.URL
36
+	HostsRequest *url.URL
37 37
 }
38 38
 
39 39
 type OVirtApiConfig struct {
... ...
@@ -43,18 +43,18 @@ type OVirtApiConfig struct {
43 43
 		Password string `gcfg:"password"`
44 44
 	}
45 45
 	Filters struct {
46
-		VmsQuery   string `gcfg:"vms"`
46
+		VmsQuery string `gcfg:"vms"`
47 47
 	}
48 48
 }
49 49
 
50 50
 type XmlVmInfo struct {
51
-	Hostname        string `xml:"guest_info>fqdn"`
52
-	State		string `xml:"status>state"`
51
+	Hostname string `xml:"guest_info>fqdn"`
52
+	State    string `xml:"status>state"`
53 53
 }
54 54
 
55 55
 type XmlVmsList struct {
56
-	XMLName         xml.Name     `xml:"vms"`
57
-	Vm		[]XmlVmInfo  `xml:"vm"`
56
+	XMLName xml.Name    `xml:"vms"`
57
+	Vm      []XmlVmInfo `xml:"vm"`
58 58
 }
59 59
 
60 60
 func init() {
... ...
@@ -74,7 +74,7 @@ func newOVirtCloud(config io.Reader) (*OVirtCloud, error) {
74 74
 	/* defaults */
75 75
 	oVirtConfig.Connection.Username = "admin@internal"
76 76
 
77
-	if  err := gcfg.ReadInto(&oVirtConfig, config); err != nil {
77
+	if err := gcfg.ReadInto(&oVirtConfig, config); err != nil {
78 78
 		return nil, err
79 79
 	}
80 80
 
... ...
@@ -83,7 +83,7 @@ func newOVirtCloud(config io.Reader) (*OVirtCloud, error) {
83 83
 	}
84 84
 
85 85
 	request, err := url.Parse(oVirtConfig.Connection.ApiEntry)
86
-	if  err != nil {
86
+	if err != nil {
87 87
 		return nil, err
88 88
 	}
89 89
 
... ...
@@ -121,7 +121,7 @@ func getInstancesFromXml(body io.Reader) ([]string, error) {
121 121
 	}
122 122
 
123 123
 	content, err := ioutil.ReadAll(body)
124
-	if  err != nil {
124
+	if err != nil {
125 125
 		return nil, err
126 126
 	}
127 127
 
... ...
@@ -146,7 +146,7 @@ func getInstancesFromXml(body io.Reader) ([]string, error) {
146 146
 // List enumerates the set of minions instances known by the cloud provider
147 147
 func (v *OVirtCloud) List(filter string) ([]string, error) {
148 148
 	response, err := http.Get(v.VmsRequest.String())
149
-	if  err != nil {
149
+	if err != nil {
150 150
 		return nil, err
151 151
 	}
152 152
 
... ...
@@ -42,7 +42,7 @@ func RegisterCloudProvider(name string, cloud Factory) {
42 42
 	if found {
43 43
 		glog.Fatalf("Cloud provider %q was registered twice", name)
44 44
 	}
45
-	glog.Infof("Registered cloud provider %q", name)
45
+	glog.V(1).Infof("Registered cloud provider %q", name)
46 46
 	providers[name] = cloud
47 47
 }
48 48
 
... ...
@@ -42,9 +42,9 @@ type ReplicationManager struct {
42 42
 // created as an interface to allow testing.
43 43
 type PodControlInterface interface {
44 44
 	// createReplica creates new replicated pods according to the spec.
45
-	createReplica(controllerSpec api.ReplicationController)
45
+	createReplica(ctx api.Context, controllerSpec api.ReplicationController)
46 46
 	// deletePod deletes the pod identified by podID.
47
-	deletePod(podID string) error
47
+	deletePod(ctx api.Context, podID string) error
48 48
 }
49 49
 
50 50
 // RealPodControl is the default implementation of PodControllerInterface.
... ...
@@ -52,7 +52,7 @@ type RealPodControl struct {
52 52
 	kubeClient client.Interface
53 53
 }
54 54
 
55
-func (r RealPodControl) createReplica(controllerSpec api.ReplicationController) {
55
+func (r RealPodControl) createReplica(ctx api.Context, controllerSpec api.ReplicationController) {
56 56
 	labels := controllerSpec.DesiredState.PodTemplate.Labels
57 57
 	// TODO: don't fail to set this label just because the map isn't created.
58 58
 	if labels != nil {
... ...
@@ -62,14 +62,14 @@ func (r RealPodControl) createReplica(controllerSpec api.ReplicationController)
62 62
 		DesiredState: controllerSpec.DesiredState.PodTemplate.DesiredState,
63 63
 		Labels:       controllerSpec.DesiredState.PodTemplate.Labels,
64 64
 	}
65
-	_, err := r.kubeClient.CreatePod(pod)
65
+	_, err := r.kubeClient.CreatePod(ctx, pod)
66 66
 	if err != nil {
67 67
 		glog.Errorf("%#v\n", err)
68 68
 	}
69 69
 }
70 70
 
71
-func (r RealPodControl) deletePod(podID string) error {
72
-	return r.kubeClient.DeletePod(podID)
71
+func (r RealPodControl) deletePod(ctx api.Context, podID string) error {
72
+	return r.kubeClient.DeletePod(ctx, podID)
73 73
 }
74 74
 
75 75
 // NewReplicationManager creates a new ReplicationManager.
... ...
@@ -93,7 +93,9 @@ func (rm *ReplicationManager) Run(period time.Duration) {
93 93
 
94 94
 // resourceVersion is a pointer to the resource version to use/update.
95 95
 func (rm *ReplicationManager) watchControllers(resourceVersion *uint64) {
96
+	ctx := api.NewContext()
96 97
 	watching, err := rm.kubeClient.WatchReplicationControllers(
98
+		ctx,
97 99
 		labels.Everything(),
98 100
 		labels.Everything(),
99 101
 		*resourceVersion,
... ...
@@ -115,7 +117,7 @@ func (rm *ReplicationManager) watchControllers(resourceVersion *uint64) {
115 115
 				// that called us call us again.
116 116
 				return
117 117
 			}
118
-			glog.Infof("Got watch: %#v", event)
118
+			glog.V(4).Infof("Got watch: %#v", event)
119 119
 			rc, ok := event.Object.(*api.ReplicationController)
120 120
 			if !ok {
121 121
 				glog.Errorf("unexpected object: %#v", event.Object)
... ...
@@ -125,7 +127,7 @@ func (rm *ReplicationManager) watchControllers(resourceVersion *uint64) {
125 125
 			*resourceVersion = rc.ResourceVersion + 1
126 126
 			// Sync even if this is a deletion event, to ensure that we leave
127 127
 			// it in the desired state.
128
-			glog.Infof("About to sync from watch: %v", rc.ID)
128
+			glog.V(4).Infof("About to sync from watch: %v", rc.ID)
129 129
 			rm.syncHandler(*rc)
130 130
 		}
131 131
 	}
... ...
@@ -143,7 +145,8 @@ func (rm *ReplicationManager) filterActivePods(pods []api.Pod) []api.Pod {
143 143
 
144 144
 func (rm *ReplicationManager) syncReplicationController(controllerSpec api.ReplicationController) error {
145 145
 	s := labels.Set(controllerSpec.DesiredState.ReplicaSelector).AsSelector()
146
-	podList, err := rm.kubeClient.ListPods(s)
146
+	ctx := api.WithNamespace(api.NewContext(), controllerSpec.Namespace)
147
+	podList, err := rm.kubeClient.ListPods(ctx, s)
147 148
 	if err != nil {
148 149
 		return err
149 150
 	}
... ...
@@ -153,22 +156,22 @@ func (rm *ReplicationManager) syncReplicationController(controllerSpec api.Repli
153 153
 		diff *= -1
154 154
 		wait := sync.WaitGroup{}
155 155
 		wait.Add(diff)
156
-		glog.Infof("Too few replicas, creating %d\n", diff)
156
+		glog.V(2).Infof("Too few replicas, creating %d\n", diff)
157 157
 		for i := 0; i < diff; i++ {
158 158
 			go func() {
159 159
 				defer wait.Done()
160
-				rm.podControl.createReplica(controllerSpec)
160
+				rm.podControl.createReplica(ctx, controllerSpec)
161 161
 			}()
162 162
 		}
163 163
 		wait.Wait()
164 164
 	} else if diff > 0 {
165
-		glog.Infof("Too many replicas, deleting %d\n", diff)
165
+		glog.V(2).Infof("Too many replicas, deleting %d\n", diff)
166 166
 		wait := sync.WaitGroup{}
167 167
 		wait.Add(diff)
168 168
 		for i := 0; i < diff; i++ {
169 169
 			go func(ix int) {
170 170
 				defer wait.Done()
171
-				rm.podControl.deletePod(filteredList[ix].ID)
171
+				rm.podControl.deletePod(ctx, filteredList[ix].ID)
172 172
 			}(i)
173 173
 		}
174 174
 		wait.Wait()
... ...
@@ -180,7 +183,8 @@ func (rm *ReplicationManager) synchronize() {
180 180
 	// TODO: remove this method completely and rely on the watch.
181 181
 	// Add resource version tracking to watch to make this work.
182 182
 	var controllerSpecs []api.ReplicationController
183
-	list, err := rm.kubeClient.ListReplicationControllers(labels.Everything())
183
+	ctx := api.NewContext()
184
+	list, err := rm.kubeClient.ListReplicationControllers(ctx, labels.Everything())
184 185
 	if err != nil {
185 186
 		glog.Errorf("Synchronization error: %v (%#v)", err, err)
186 187
 		return
... ...
@@ -191,7 +195,7 @@ func (rm *ReplicationManager) synchronize() {
191 191
 	for ix := range controllerSpecs {
192 192
 		go func(ix int) {
193 193
 			defer wg.Done()
194
-			glog.Infof("periodic sync of %v", controllerSpecs[ix].ID)
194
+			glog.V(4).Infof("periodic sync of %v", controllerSpecs[ix].ID)
195 195
 			err := rm.syncHandler(controllerSpecs[ix])
196 196
 			if err != nil {
197 197
 				glog.Errorf("Error synchronizing: %#v", err)
... ...
@@ -21,6 +21,7 @@ import (
21 21
 	"fmt"
22 22
 	"net/http"
23 23
 	"net/http/httptest"
24
+	"path"
24 25
 	"reflect"
25 26
 	"sync"
26 27
 	"testing"
... ...
@@ -28,7 +29,7 @@ import (
28 28
 
29 29
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
30 30
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
31
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
31
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
32 32
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
33 33
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
34 34
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
... ...
@@ -38,11 +39,8 @@ import (
38 38
 	"github.com/coreos/go-etcd/etcd"
39 39
 )
40 40
 
41
-// TODO: Move this to a common place, it's needed in multiple tests.
42
-var apiPath = "/api/v1beta1"
43
-
44 41
 func makeURL(suffix string) string {
45
-	return apiPath + suffix
42
+	return path.Join("/api", testapi.Version(), suffix)
46 43
 }
47 44
 
48 45
 type FakePodControl struct {
... ...
@@ -51,13 +49,13 @@ type FakePodControl struct {
51 51
 	lock           sync.Mutex
52 52
 }
53 53
 
54
-func (f *FakePodControl) createReplica(spec api.ReplicationController) {
54
+func (f *FakePodControl) createReplica(ctx api.Context, spec api.ReplicationController) {
55 55
 	f.lock.Lock()
56 56
 	defer f.lock.Unlock()
57 57
 	f.controllerSpec = append(f.controllerSpec, spec)
58 58
 }
59 59
 
60
-func (f *FakePodControl) deletePod(podID string) error {
60
+func (f *FakePodControl) deletePod(ctx api.Context, podID string) error {
61 61
 	f.lock.Lock()
62 62
 	defer f.lock.Unlock()
63 63
 	f.deletePodID = append(f.deletePodID, podID)
... ...
@@ -116,8 +114,8 @@ func TestSyncReplicationControllerDoesNothing(t *testing.T) {
116 116
 		StatusCode:   200,
117 117
 		ResponseBody: string(body),
118 118
 	}
119
-	testServer := httptest.NewTLSServer(&fakeHandler)
120
-	client := client.NewOrDie(testServer.URL, "v1beta1", nil)
119
+	testServer := httptest.NewServer(&fakeHandler)
120
+	client := client.NewOrDie(&client.Config{Host: testServer.URL, Version: testapi.Version()})
121 121
 
122 122
 	fakePodControl := FakePodControl{}
123 123
 
... ...
@@ -136,8 +134,8 @@ func TestSyncReplicationControllerDeletes(t *testing.T) {
136 136
 		StatusCode:   200,
137 137
 		ResponseBody: string(body),
138 138
 	}
139
-	testServer := httptest.NewTLSServer(&fakeHandler)
140
-	client := client.NewOrDie(testServer.URL, "v1beta1", nil)
139
+	testServer := httptest.NewServer(&fakeHandler)
140
+	client := client.NewOrDie(&client.Config{Host: testServer.URL, Version: testapi.Version()})
141 141
 
142 142
 	fakePodControl := FakePodControl{}
143 143
 
... ...
@@ -151,13 +149,13 @@ func TestSyncReplicationControllerDeletes(t *testing.T) {
151 151
 }
152 152
 
153 153
 func TestSyncReplicationControllerCreates(t *testing.T) {
154
-	body, _ := latest.Codec.Encode(newPodList(0))
154
+	body := runtime.EncodeOrDie(testapi.CodecForVersionOrDie(), newPodList(0))
155 155
 	fakeHandler := util.FakeHandler{
156 156
 		StatusCode:   200,
157 157
 		ResponseBody: string(body),
158 158
 	}
159
-	testServer := httptest.NewTLSServer(&fakeHandler)
160
-	client := client.NewOrDie(testServer.URL, "v1beta1", nil)
159
+	testServer := httptest.NewServer(&fakeHandler)
160
+	client := client.NewOrDie(&client.Config{Host: testServer.URL, Version: testapi.Version()})
161 161
 
162 162
 	fakePodControl := FakePodControl{}
163 163
 
... ...
@@ -171,13 +169,14 @@ func TestSyncReplicationControllerCreates(t *testing.T) {
171 171
 }
172 172
 
173 173
 func TestCreateReplica(t *testing.T) {
174
-	body, _ := v1beta1.Codec.Encode(&api.Pod{})
174
+	ctx := api.NewDefaultContext()
175
+	body := runtime.EncodeOrDie(testapi.CodecForVersionOrDie(), &api.Pod{})
175 176
 	fakeHandler := util.FakeHandler{
176 177
 		StatusCode:   200,
177 178
 		ResponseBody: string(body),
178 179
 	}
179
-	testServer := httptest.NewTLSServer(&fakeHandler)
180
-	client := client.NewOrDie(testServer.URL, "v1beta1", nil)
180
+	testServer := httptest.NewServer(&fakeHandler)
181
+	client := client.NewOrDie(&client.Config{Host: testServer.URL, Version: testapi.Version()})
181 182
 
182 183
 	podControl := RealPodControl{
183 184
 		kubeClient: client,
... ...
@@ -206,12 +205,12 @@ func TestCreateReplica(t *testing.T) {
206 206
 		},
207 207
 	}
208 208
 
209
-	podControl.createReplica(controllerSpec)
209
+	podControl.createReplica(ctx, controllerSpec)
210 210
 
211 211
 	expectedPod := api.Pod{
212 212
 		JSONBase: api.JSONBase{
213 213
 			Kind:       "Pod",
214
-			APIVersion: latest.Version,
214
+			APIVersion: testapi.Version(),
215 215
 		},
216 216
 		Labels:       controllerSpec.DesiredState.PodTemplate.Labels,
217 217
 		DesiredState: controllerSpec.DesiredState.PodTemplate.DesiredState,
... ...
@@ -229,7 +228,7 @@ func TestCreateReplica(t *testing.T) {
229 229
 
230 230
 func TestSynchonize(t *testing.T) {
231 231
 	controllerSpec1 := api.ReplicationController{
232
-		JSONBase: api.JSONBase{APIVersion: "v1beta1"},
232
+		JSONBase: api.JSONBase{APIVersion: testapi.Version()},
233 233
 		DesiredState: api.ReplicationControllerState{
234 234
 			Replicas: 4,
235 235
 			PodTemplate: api.PodTemplate{
... ...
@@ -250,7 +249,7 @@ func TestSynchonize(t *testing.T) {
250 250
 		},
251 251
 	}
252 252
 	controllerSpec2 := api.ReplicationController{
253
-		JSONBase: api.JSONBase{APIVersion: "v1beta1"},
253
+		JSONBase: api.JSONBase{APIVersion: testapi.Version()},
254 254
 		DesiredState: api.ReplicationControllerState{
255 255
 			Replicas: 3,
256 256
 			PodTemplate: api.PodTemplate{
... ...
@@ -289,7 +288,7 @@ func TestSynchonize(t *testing.T) {
289 289
 
290 290
 	fakePodHandler := util.FakeHandler{
291 291
 		StatusCode:   200,
292
-		ResponseBody: "{\"apiVersion\": \"" + latest.Version + "\", \"kind\": \"PodList\"}",
292
+		ResponseBody: "{\"apiVersion\": \"" + testapi.Version() + "\", \"kind\": \"PodList\"}",
293 293
 		T:            t,
294 294
 	}
295 295
 	fakeControllerHandler := util.FakeHandler{
... ...
@@ -303,14 +302,14 @@ func TestSynchonize(t *testing.T) {
303 303
 		T: t,
304 304
 	}
305 305
 	mux := http.NewServeMux()
306
-	mux.Handle("/api/v1beta1/pods/", &fakePodHandler)
307
-	mux.Handle("/api/v1beta1/replicationControllers/", &fakeControllerHandler)
306
+	mux.Handle("/api/"+testapi.Version()+"/pods/", &fakePodHandler)
307
+	mux.Handle("/api/"+testapi.Version()+"/replicationControllers/", &fakeControllerHandler)
308 308
 	mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
309 309
 		w.WriteHeader(http.StatusNotFound)
310 310
 		t.Errorf("Unexpected request for %v", req.RequestURI)
311 311
 	})
312 312
 	testServer := httptest.NewServer(mux)
313
-	client := client.NewOrDie(testServer.URL, "v1beta1", nil)
313
+	client := client.NewOrDie(&client.Config{Host: testServer.URL, Version: testapi.Version()})
314 314
 	manager := NewReplicationManager(client)
315 315
 	fakePodControl := FakePodControl{}
316 316
 	manager.podControl = &fakePodControl
... ...
@@ -325,7 +324,7 @@ type FakeWatcher struct {
325 325
 	*client.Fake
326 326
 }
327 327
 
328
-func (fw FakeWatcher) WatchReplicationControllers(l, f labels.Selector, rv uint64) (watch.Interface, error) {
328
+func (fw FakeWatcher) WatchReplicationControllers(ctx api.Context, l, f labels.Selector, rv uint64) (watch.Interface, error) {
329 329
 	return fw.w, nil
330 330
 }
331 331
 
... ...
@@ -57,3 +57,7 @@ func (e *ExecHealthChecker) HealthCheck(podFullName string, currentState api.Pod
57 57
 	}
58 58
 	return Healthy, nil
59 59
 }
60
+
61
+func (e *ExecHealthChecker) CanCheck(probe *api.LivenessProbe) bool {
62
+	return probe.Exec != nil
63
+}
... ...
@@ -49,22 +49,19 @@ func TestExec(t *testing.T) {
49 49
 	checker := ExecHealthChecker{&fake}
50 50
 	tests := []healthCheckTest{
51 51
 		// Missing parameters
52
-		{Unknown, &api.LivenessProbe{Type: "exec"}, true, nil, nil},
52
+		{Unknown, &api.LivenessProbe{}, true, nil, nil},
53 53
 		// Ok
54 54
 		{Healthy, &api.LivenessProbe{
55
-			Type: "exec",
56 55
 			Exec: &api.ExecAction{Command: []string{"ls", "-l"}},
57 56
 		}, false, []byte("OK"), nil},
58 57
 		// Run returns error
59 58
 		{Unknown, &api.LivenessProbe{
60
-			Type: "exec",
61 59
 			Exec: &api.ExecAction{
62 60
 				Command: []string{"ls", "-l"},
63 61
 			},
64 62
 		}, true, []byte("OK, NOT"), fmt.Errorf("test error")},
65 63
 		// Command error
66 64
 		{Unhealthy, &api.LivenessProbe{
67
-			Type: "exec",
68 65
 			Exec: &api.ExecAction{
69 66
 				Command: []string{"ls", "-l"},
70 67
 			},
... ...
@@ -36,54 +36,61 @@ const (
36 36
 // HealthChecker defines an abstract interface for checking container health.
37 37
 type HealthChecker interface {
38 38
 	HealthCheck(podFullName string, currentState api.PodState, container api.Container) (Status, error)
39
+	CanCheck(probe *api.LivenessProbe) bool
39 40
 }
40 41
 
41
-// protects checkers
42
+// protects allCheckers
42 43
 var checkerLock = sync.Mutex{}
43
-var checkers = map[string]HealthChecker{}
44
+var allCheckers = []HealthChecker{}
44 45
 
45 46
 // AddHealthChecker adds a health checker to the list of known HealthChecker objects.
46 47
 // Any subsequent call to NewHealthChecker will know about this HealthChecker.
47
-// Panics if 'key' is already present.
48
-func AddHealthChecker(key string, checker HealthChecker) {
48
+func AddHealthChecker(checker HealthChecker) {
49 49
 	checkerLock.Lock()
50 50
 	defer checkerLock.Unlock()
51
-	if _, found := checkers[key]; found {
52
-		glog.Fatalf("HealthChecker already defined for key %s.", key)
53
-	}
54
-	checkers[key] = checker
51
+	allCheckers = append(allCheckers, checker)
55 52
 }
56 53
 
57 54
 // NewHealthChecker creates a new HealthChecker which supports multiple types of liveness probes.
58 55
 func NewHealthChecker() HealthChecker {
59 56
 	checkerLock.Lock()
60 57
 	defer checkerLock.Unlock()
61
-	input := map[string]HealthChecker{}
62
-	for key, value := range checkers {
63
-		input[key] = value
64
-	}
65 58
 	return &muxHealthChecker{
66
-		checkers: input,
59
+		checkers: append([]HealthChecker{}, allCheckers...),
67 60
 	}
68 61
 }
69 62
 
70 63
 // muxHealthChecker bundles multiple implementations of HealthChecker of different types.
71 64
 type muxHealthChecker struct {
72
-	checkers map[string]HealthChecker
65
+	// Given a LivenessProbe, cycle through each known checker and see if it supports
66
+	// the specific kind of probe (by returning non-nil).
67
+	checkers []HealthChecker
68
+}
69
+
70
+func (m *muxHealthChecker) findCheckerFor(probe *api.LivenessProbe) HealthChecker {
71
+	for i := range m.checkers {
72
+		if m.checkers[i].CanCheck(probe) {
73
+			return m.checkers[i]
74
+		}
75
+	}
76
+	return nil
73 77
 }
74 78
 
75 79
 // HealthCheck delegates the health-checking of the container to one of the bundled implementations.
76
-// It chooses an implementation according to container.LivenessProbe.Type.
77
-// If there is no matching health checker it returns Unknown, nil.
80
+// If there is no health checker that can check container it returns Unknown, nil.
78 81
 func (m *muxHealthChecker) HealthCheck(podFullName string, currentState api.PodState, container api.Container) (Status, error) {
79
-	checker, ok := m.checkers[container.LivenessProbe.Type]
80
-	if !ok || checker == nil {
81
-		glog.Warningf("Failed to find health checker for %s %s", container.Name, container.LivenessProbe.Type)
82
+	checker := m.findCheckerFor(container.LivenessProbe)
83
+	if checker == nil {
84
+		glog.Warningf("Failed to find health checker for %s %+v", container.Name, container.LivenessProbe)
82 85
 		return Unknown, nil
83 86
 	}
84 87
 	return checker.HealthCheck(podFullName, currentState, container)
85 88
 }
86 89
 
90
+func (m *muxHealthChecker) CanCheck(probe *api.LivenessProbe) bool {
91
+	return m.findCheckerFor(probe) != nil
92
+}
93
+
87 94
 // findPortByName is a helper function to look up a port in a container by name.
88 95
 // Returns the HostPort if found, -1 if not found.
89 96
 func findPortByName(container api.Container, portName string) int {
... ...
@@ -30,7 +30,7 @@ import (
30 30
 const statusServerEarlyShutdown = -1
31 31
 
32 32
 func TestHealthChecker(t *testing.T) {
33
-	AddHealthChecker("http", &HTTPHealthChecker{client: &http.Client{}})
33
+	AddHealthChecker(&HTTPHealthChecker{client: &http.Client{}})
34 34
 	var healthCheckerTests = []struct {
35 35
 		status int
36 36
 		health Status
... ...
@@ -64,7 +64,6 @@ func TestHealthChecker(t *testing.T) {
64 64
 					Path: "/foo/bar",
65 65
 					Host: host,
66 66
 				},
67
-				Type: "http",
68 67
 			},
69 68
 		}
70 69
 		hc := NewHealthChecker()
... ...
@@ -100,19 +99,16 @@ func TestFindPortByName(t *testing.T) {
100 100
 
101 101
 func TestMuxHealthChecker(t *testing.T) {
102 102
 	muxHealthCheckerTests := []struct {
103
-		health    Status
104
-		probeType string
103
+		health Status
105 104
 	}{
106
-		{Healthy, "http"},
107
-		{Unknown, "ftp"},
105
+		// TODO: This test should run through a few different checker types.
106
+		{Healthy},
108 107
 	}
109 108
 	mc := &muxHealthChecker{
110
-		checkers: make(map[string]HealthChecker),
111
-	}
112
-	hc := &HTTPHealthChecker{
113
-		client: &http.Client{},
109
+		checkers: []HealthChecker{
110
+			&HTTPHealthChecker{client: &http.Client{}},
111
+		},
114 112
 	}
115
-	mc.checkers["http"] = hc
116 113
 	for _, muxHealthCheckerTest := range muxHealthCheckerTests {
117 114
 		tt := muxHealthCheckerTest
118 115
 		ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
... ...
@@ -131,7 +127,6 @@ func TestMuxHealthChecker(t *testing.T) {
131 131
 				HTTPGet: &api.HTTPGetAction{},
132 132
 			},
133 133
 		}
134
-		container.LivenessProbe.Type = tt.probeType
135 134
 		container.LivenessProbe.HTTPGet.Port = util.NewIntOrStringFromString(port)
136 135
 		container.LivenessProbe.HTTPGet.Host = host
137 136
 		health, err := mc.HealthCheck("test", api.PodState{}, container)
... ...
@@ -105,3 +105,7 @@ func (h *HTTPHealthChecker) HealthCheck(podFullName string, currentState api.Pod
105 105
 	}
106 106
 	return DoHTTPCheck(formatURL(host, port, path), h.client)
107 107
 }
108
+
109
+func (h *HTTPHealthChecker) CanCheck(probe *api.LivenessProbe) bool {
110
+	return probe.HTTPGet != nil
111
+}
... ...
@@ -51,7 +51,6 @@ func TestGetURLParts(t *testing.T) {
51 51
 			Ports: []api.Port{{Name: "found", HostPort: 93}},
52 52
 			LivenessProbe: &api.LivenessProbe{
53 53
 				HTTPGet: test.probe,
54
-				Type:    "http",
55 54
 			},
56 55
 		}
57 56
 		host, port, path, err := getURLParts(state, container)
... ...
@@ -117,7 +116,6 @@ func TestHTTPHealthChecker(t *testing.T) {
117 117
 		container := api.Container{
118 118
 			LivenessProbe: &api.LivenessProbe{
119 119
 				HTTPGet: test.probe,
120
-				Type:    "http",
121 120
 			},
122 121
 		}
123 122
 		params := container.LivenessProbe.HTTPGet
... ...
@@ -81,3 +81,7 @@ func (t *TCPHealthChecker) HealthCheck(podFullName string, currentState api.PodS
81 81
 	}
82 82
 	return DoTCPCheck(net.JoinHostPort(host, strconv.Itoa(port)))
83 83
 }
84
+
85
+func (t *TCPHealthChecker) CanCheck(probe *api.LivenessProbe) bool {
86
+	return probe.TCPSocket != nil
87
+}
... ...
@@ -49,7 +49,6 @@ func TestGetTCPAddrParts(t *testing.T) {
49 49
 			Ports: []api.Port{{Name: "found", HostPort: 93}},
50 50
 			LivenessProbe: &api.LivenessProbe{
51 51
 				TCPSocket: test.probe,
52
-				Type:      "tcp",
53 52
 			},
54 53
 		}
55 54
 		host, port, err := getTCPAddrParts(state, container)
... ...
@@ -95,7 +94,6 @@ func TestTcpHealthChecker(t *testing.T) {
95 95
 		container := api.Container{
96 96
 			LivenessProbe: &api.LivenessProbe{
97 97
 				TCPSocket: test.probe,
98
-				Type:      "tcp",
99 98
 			},
100 99
 		}
101 100
 		params := container.LivenessProbe.TCPSocket
... ...
@@ -148,7 +148,7 @@ func (rl *respLogger) Addf(format string, data ...interface{}) {
148 148
 // Log is intended to be called once at the end of your request handler, via defer
149 149
 func (rl *respLogger) Log() {
150 150
 	latency := time.Since(rl.startTime)
151
-	glog.Infof("%s %s: (%v) %v%v%v", rl.req.Method, rl.req.RequestURI, latency, rl.status, rl.statusStack, rl.addedInfo)
151
+	glog.V(2).Infof("%s %s: (%v) %v%v%v", rl.req.Method, rl.req.RequestURI, latency, rl.status, rl.statusStack, rl.addedInfo)
152 152
 }
153 153
 
154 154
 // Header implements http.ResponseWriter.
... ...
@@ -51,9 +51,18 @@ func promptForString(field string, r io.Reader) string {
51 51
 	return result
52 52
 }
53 53
 
54
+type AuthInfo struct {
55
+	User     string
56
+	Password string
57
+	CAFile   string
58
+	CertFile string
59
+	KeyFile  string
60
+	Insecure *bool
61
+}
62
+
54 63
 // LoadAuthInfo parses an AuthInfo object from a file path. It prompts user and creates file if it doesn't exist.
55
-func LoadAuthInfo(path string, r io.Reader) (*client.AuthInfo, error) {
56
-	var auth client.AuthInfo
64
+func LoadAuthInfo(path string, r io.Reader) (*AuthInfo, error) {
65
+	var auth AuthInfo
57 66
 	if _, err := os.Stat(path); os.IsNotExist(err) {
58 67
 		auth.User = promptForString("Username", r)
59 68
 		auth.Password = promptForString("Password", r)
... ...
@@ -83,15 +92,15 @@ func LoadAuthInfo(path string, r io.Reader) (*client.AuthInfo, error) {
83 83
 //     with the first container in the pod.  There is no support yet for
84 84
 //     updating more complex replication controllers.  If this is blank then no
85 85
 //     update of the image is performed.
86
-func Update(name string, client client.Interface, updatePeriod time.Duration, imageName string) error {
87
-	controller, err := client.GetReplicationController(name)
86
+func Update(ctx api.Context, name string, client client.Interface, updatePeriod time.Duration, imageName string) error {
87
+	controller, err := client.GetReplicationController(ctx, name)
88 88
 	if err != nil {
89 89
 		return err
90 90
 	}
91 91
 
92 92
 	if len(imageName) != 0 {
93 93
 		controller.DesiredState.PodTemplate.DesiredState.Manifest.Containers[0].Image = imageName
94
-		controller, err = client.UpdateReplicationController(controller)
94
+		controller, err = client.UpdateReplicationController(ctx, controller)
95 95
 		if err != nil {
96 96
 			return err
97 97
 		}
... ...
@@ -99,7 +108,7 @@ func Update(name string, client client.Interface, updatePeriod time.Duration, im
99 99
 
100 100
 	s := labels.Set(controller.DesiredState.ReplicaSelector).AsSelector()
101 101
 
102
-	podList, err := client.ListPods(s)
102
+	podList, err := client.ListPods(ctx, s)
103 103
 	if err != nil {
104 104
 		return err
105 105
 	}
... ...
@@ -110,14 +119,14 @@ func Update(name string, client client.Interface, updatePeriod time.Duration, im
110 110
 	for _, pod := range podList.Items {
111 111
 		// We delete the pod here, the controller will recreate it.  This will result in pulling
112 112
 		// a new Docker image.  This isn't a full "update" but it's what we support for now.
113
-		err = client.DeletePod(pod.ID)
113
+		err = client.DeletePod(ctx, pod.ID)
114 114
 		if err != nil {
115 115
 			return err
116 116
 		}
117 117
 		time.Sleep(updatePeriod)
118 118
 	}
119 119
 	return wait.Poll(time.Second*5, time.Second*300, func() (bool, error) {
120
-		podList, err := client.ListPods(s)
120
+		podList, err := client.ListPods(ctx, s)
121 121
 		if err != nil {
122 122
 			return false, err
123 123
 		}
... ...
@@ -126,18 +135,18 @@ func Update(name string, client client.Interface, updatePeriod time.Duration, im
126 126
 }
127 127
 
128 128
 // StopController stops a controller named 'name' by setting replicas to zero.
129
-func StopController(name string, client client.Interface) error {
130
-	return ResizeController(name, 0, client)
129
+func StopController(ctx api.Context, name string, client client.Interface) error {
130
+	return ResizeController(ctx, name, 0, client)
131 131
 }
132 132
 
133 133
 // ResizeController resizes a controller named 'name' by setting replicas to 'replicas'.
134
-func ResizeController(name string, replicas int, client client.Interface) error {
135
-	controller, err := client.GetReplicationController(name)
134
+func ResizeController(ctx api.Context, name string, replicas int, client client.Interface) error {
135
+	controller, err := client.GetReplicationController(ctx, name)
136 136
 	if err != nil {
137 137
 		return err
138 138
 	}
139 139
 	controller.DesiredState.Replicas = replicas
140
-	controllerOut, err := client.UpdateReplicationController(controller)
140
+	controllerOut, err := client.UpdateReplicationController(ctx, controller)
141 141
 	if err != nil {
142 142
 		return err
143 143
 	}
... ...
@@ -190,7 +199,7 @@ func portsFromString(spec string) []api.Port {
190 190
 }
191 191
 
192 192
 // RunController creates a new replication controller named 'name' which creates 'replicas' pods running 'image'.
193
-func RunController(image, name string, replicas int, client client.Interface, portSpec string, servicePort int) error {
193
+func RunController(ctx api.Context, image, name string, replicas int, client client.Interface, portSpec string, servicePort int) error {
194 194
 	if servicePort > 0 && !util.IsDNSLabel(name) {
195 195
 		return fmt.Errorf("Service creation requested, but an invalid name for a service was provided (%s). Service names must be valid DNS labels.", name)
196 196
 	}
... ...
@@ -223,7 +232,7 @@ func RunController(image, name string, replicas int, client client.Interface, po
223 223
 		},
224 224
 	}
225 225
 
226
-	controllerOut, err := client.CreateReplicationController(controller)
226
+	controllerOut, err := client.CreateReplicationController(ctx, controller)
227 227
 	if err != nil {
228 228
 		return err
229 229
 	}
... ...
@@ -234,7 +243,7 @@ func RunController(image, name string, replicas int, client client.Interface, po
234 234
 	fmt.Print(string(data))
235 235
 
236 236
 	if servicePort > 0 {
237
-		svc, err := createService(name, servicePort, client)
237
+		svc, err := createService(ctx, name, servicePort, client)
238 238
 		if err != nil {
239 239
 			return err
240 240
 		}
... ...
@@ -247,7 +256,7 @@ func RunController(image, name string, replicas int, client client.Interface, po
247 247
 	return nil
248 248
 }
249 249
 
250
-func createService(name string, port int, client client.Interface) (*api.Service, error) {
250
+func createService(ctx api.Context, name string, port int, client client.Interface) (*api.Service, error) {
251 251
 	svc := &api.Service{
252 252
 		JSONBase: api.JSONBase{ID: name},
253 253
 		Port:     port,
... ...
@@ -258,19 +267,19 @@ func createService(name string, port int, client client.Interface) (*api.Service
258 258
 			"simpleService": name,
259 259
 		},
260 260
 	}
261
-	svc, err := client.CreateService(svc)
261
+	svc, err := client.CreateService(ctx, svc)
262 262
 	return svc, err
263 263
 }
264 264
 
265 265
 // DeleteController deletes a replication controller named 'name', requires that the controller
266 266
 // already be stopped.
267
-func DeleteController(name string, client client.Interface) error {
268
-	controller, err := client.GetReplicationController(name)
267
+func DeleteController(ctx api.Context, name string, client client.Interface) error {
268
+	controller, err := client.GetReplicationController(ctx, name)
269 269
 	if err != nil {
270 270
 		return err
271 271
 	}
272 272
 	if controller.DesiredState.Replicas != 0 {
273 273
 		return fmt.Errorf("controller has non-zero replicas (%d), please stop it first", controller.DesiredState.Replicas)
274 274
 	}
275
-	return client.DeleteReplicationController(name)
275
+	return client.DeleteReplicationController(ctx, name)
276 276
 }
... ...
@@ -43,7 +43,7 @@ func TestUpdateWithPods(t *testing.T) {
43 43
 			},
44 44
 		},
45 45
 	}
46
-	Update("foo", &fakeClient, 0, "")
46
+	Update(api.NewDefaultContext(), "foo", &fakeClient, 0, "")
47 47
 	if len(fakeClient.Actions) != 5 {
48 48
 		t.Fatalf("Unexpected action list %#v", fakeClient.Actions)
49 49
 	}
... ...
@@ -57,7 +57,7 @@ func TestUpdateWithPods(t *testing.T) {
57 57
 
58 58
 func TestUpdateNoPods(t *testing.T) {
59 59
 	fakeClient := client.Fake{}
60
-	Update("foo", &fakeClient, 0, "")
60
+	Update(api.NewDefaultContext(), "foo", &fakeClient, 0, "")
61 61
 	if len(fakeClient.Actions) != 2 {
62 62
 		t.Errorf("Unexpected action list %#v", fakeClient.Actions)
63 63
 	}
... ...
@@ -87,7 +87,7 @@ func TestUpdateWithNewImage(t *testing.T) {
87 87
 			},
88 88
 		},
89 89
 	}
90
-	Update("foo", &fakeClient, 0, "fooImage:2")
90
+	Update(api.NewDefaultContext(), "foo", &fakeClient, 0, "fooImage:2")
91 91
 	if len(fakeClient.Actions) != 6 {
92 92
 		t.Errorf("Unexpected action list %#v", fakeClient.Actions)
93 93
 	}
... ...
@@ -109,7 +109,7 @@ func TestRunController(t *testing.T) {
109 109
 	name := "name"
110 110
 	image := "foo/bar"
111 111
 	replicas := 3
112
-	RunController(image, name, replicas, &fakeClient, "8080:80", -1)
112
+	RunController(api.NewDefaultContext(), image, name, replicas, &fakeClient, "8080:80", -1)
113 113
 	if len(fakeClient.Actions) != 1 || fakeClient.Actions[0].Action != "create-controller" {
114 114
 		t.Errorf("Unexpected actions: %#v", fakeClient.Actions)
115 115
 	}
... ...
@@ -126,7 +126,7 @@ func TestRunControllerWithService(t *testing.T) {
126 126
 	name := "name"
127 127
 	image := "foo/bar"
128 128
 	replicas := 3
129
-	RunController(image, name, replicas, &fakeClient, "", 8000)
129
+	RunController(api.NewDefaultContext(), image, name, replicas, &fakeClient, "", 8000)
130 130
 	if len(fakeClient.Actions) != 2 ||
131 131
 		fakeClient.Actions[0].Action != "create-controller" ||
132 132
 		fakeClient.Actions[1].Action != "create-service" {
... ...
@@ -143,7 +143,7 @@ func TestRunControllerWithService(t *testing.T) {
143 143
 func TestStopController(t *testing.T) {
144 144
 	fakeClient := client.Fake{}
145 145
 	name := "name"
146
-	StopController(name, &fakeClient)
146
+	StopController(api.NewDefaultContext(), name, &fakeClient)
147 147
 	if len(fakeClient.Actions) != 2 {
148 148
 		t.Errorf("Unexpected actions: %#v", fakeClient.Actions)
149 149
 	}
... ...
@@ -162,7 +162,7 @@ func TestResizeController(t *testing.T) {
162 162
 	fakeClient := client.Fake{}
163 163
 	name := "name"
164 164
 	replicas := 17
165
-	ResizeController(name, replicas, &fakeClient)
165
+	ResizeController(api.NewDefaultContext(), name, replicas, &fakeClient)
166 166
 	if len(fakeClient.Actions) != 2 {
167 167
 		t.Errorf("Unexpected actions: %#v", fakeClient.Actions)
168 168
 	}
... ...
@@ -180,7 +180,7 @@ func TestResizeController(t *testing.T) {
180 180
 func TestCloudCfgDeleteController(t *testing.T) {
181 181
 	fakeClient := client.Fake{}
182 182
 	name := "name"
183
-	err := DeleteController(name, &fakeClient)
183
+	err := DeleteController(api.NewDefaultContext(), name, &fakeClient)
184 184
 	if err != nil {
185 185
 		t.Errorf("Unexpected error: %v", err)
186 186
 	}
... ...
@@ -206,7 +206,7 @@ func TestCloudCfgDeleteControllerWithReplicas(t *testing.T) {
206 206
 		},
207 207
 	}
208 208
 	name := "name"
209
-	err := DeleteController(name, &fakeClient)
209
+	err := DeleteController(api.NewDefaultContext(), name, &fakeClient)
210 210
 	if len(fakeClient.Actions) != 1 {
211 211
 		t.Errorf("Unexpected actions: %#v", fakeClient.Actions)
212 212
 	}
... ...
@@ -222,12 +222,12 @@ func TestCloudCfgDeleteControllerWithReplicas(t *testing.T) {
222 222
 func TestLoadAuthInfo(t *testing.T) {
223 223
 	loadAuthInfoTests := []struct {
224 224
 		authData string
225
-		authInfo *client.AuthInfo
225
+		authInfo *AuthInfo
226 226
 		r        io.Reader
227 227
 	}{
228 228
 		{
229 229
 			`{"user": "user", "password": "pass"}`,
230
-			&client.AuthInfo{User: "user", Password: "pass"},
230
+			&AuthInfo{User: "user", Password: "pass"},
231 231
 			nil,
232 232
 		},
233 233
 		{
... ...
@@ -235,7 +235,7 @@ func TestLoadAuthInfo(t *testing.T) {
235 235
 		},
236 236
 		{
237 237
 			"missing",
238
-			&client.AuthInfo{User: "user", Password: "pass"},
238
+			&AuthInfo{User: "user", Password: "pass"},
239 239
 			bytes.NewBufferString("user\npass"),
240 240
 		},
241 241
 	}
... ...
@@ -61,7 +61,16 @@ func (s *ProxyServer) doError(w http.ResponseWriter, err error) {
61 61
 }
62 62
 
63 63
 func (s *ProxyServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
64
-	result := s.Client.Verb(r.Method).AbsPath(r.URL.Path).Body(r.Body).Do()
64
+	url := r.URL
65
+	selector := url.Query().Get("labels")
66
+	fieldSelector := url.Query().Get("fields")
67
+	result := s.Client.
68
+		Verb(r.Method).
69
+		AbsPath(r.URL.Path).
70
+		ParseSelectorParam("labels", selector).
71
+		ParseSelectorParam("fields", fieldSelector).
72
+		Body(r.Body).
73
+		Do()
65 74
 	if result.Error() != nil {
66 75
 		s.doError(w, result.Error())
67 76
 		return
... ...
@@ -168,9 +168,9 @@ func (s *podStorage) merge(source string, change interface{}) (adds, updates, de
168 168
 	switch update.Op {
169 169
 	case kubelet.ADD, kubelet.UPDATE:
170 170
 		if update.Op == kubelet.ADD {
171
-			glog.Infof("Adding new pods from source %s : %v", source, update.Pods)
171
+			glog.V(4).Infof("Adding new pods from source %s : %v", source, update.Pods)
172 172
 		} else {
173
-			glog.Infof("Updating pods from source %s : %v", source, update.Pods)
173
+			glog.V(4).Infof("Updating pods from source %s : %v", source, update.Pods)
174 174
 		}
175 175
 
176 176
 		filtered := filterInvalidPods(update.Pods, source)
... ...
@@ -193,7 +193,7 @@ func (s *podStorage) merge(source string, change interface{}) (adds, updates, de
193 193
 		}
194 194
 
195 195
 	case kubelet.REMOVE:
196
-		glog.Infof("Removing a pod %v", update)
196
+		glog.V(4).Infof("Removing a pod %v", update)
197 197
 		for _, value := range update.Pods {
198 198
 			name := value.Name
199 199
 			if existing, found := pods[name]; found {
... ...
@@ -206,7 +206,7 @@ func (s *podStorage) merge(source string, change interface{}) (adds, updates, de
206 206
 		}
207 207
 
208 208
 	case kubelet.SET:
209
-		glog.Infof("Setting pods for source %s : %v", source, update)
209
+		glog.V(4).Infof("Setting pods for source %s : %v", source, update)
210 210
 		// Clear the old map entries by just creating a new map
211 211
 		oldPods := pods
212 212
 		pods = make(map[string]*kubelet.Pod)
... ...
@@ -238,7 +238,7 @@ func (s *podStorage) merge(source string, change interface{}) (adds, updates, de
238 238
 		}
239 239
 
240 240
 	default:
241
-		glog.Infof("Received invalid update type: %v", update)
241
+		glog.Warningf("Received invalid update type: %v", update)
242 242
 
243 243
 	}
244 244
 
... ...
@@ -259,7 +259,7 @@ func filterInvalidPods(pods []kubelet.Pod, source string) (filtered []*kubelet.P
259 259
 			errors = append(errors, errs...)
260 260
 		}
261 261
 		if len(errors) > 0 {
262
-			glog.Warningf("Pod %d from %s failed validation, ignoring: %v", i+1, source, errors)
262
+			glog.Warningf("Pod %d (%s) from %s failed validation, ignoring: %v", i+1, pods[i].Name, source, errors)
263 263
 			continue
264 264
 		}
265 265
 		filtered = append(filtered, &pods[i])
... ...
@@ -54,31 +54,31 @@ func NewSourceEtcd(key string, client tools.EtcdClient, updates chan<- interface
54 54
 		helper:  helper,
55 55
 		updates: updates,
56 56
 	}
57
-	glog.Infof("Watching etcd for %s", key)
57
+	glog.V(1).Infof("Watching etcd for %s", key)
58 58
 	go util.Forever(source.run, time.Second)
59 59
 	return source
60 60
 }
61 61
 
62 62
 func (s *SourceEtcd) run() {
63
-	watching, err := s.helper.Watch(s.key, 0)
64
-	if err != nil {
65
-		glog.Errorf("Failed to initialize etcd watch: %v", err)
66
-		return
67
-	}
63
+	watching := s.helper.Watch(s.key, 0)
68 64
 	for {
69 65
 		select {
70 66
 		case event, ok := <-watching.ResultChan():
71 67
 			if !ok {
72 68
 				return
73 69
 			}
74
-
70
+			if event.Type == watch.Error {
71
+				glog.Errorf("Watch error: %v", event.Object)
72
+				watching.Stop()
73
+				return
74
+			}
75 75
 			pods, err := eventToPods(event)
76 76
 			if err != nil {
77 77
 				glog.Errorf("Failed to parse result from etcd watch: %v", err)
78 78
 				continue
79 79
 			}
80 80
 
81
-			glog.Infof("Received state from etcd watch: %+v", pods)
81
+			glog.V(4).Infof("Received state from etcd watch: %+v", pods)
82 82
 			s.updates <- kubelet.PodUpdate{pods, kubelet.SET}
83 83
 		}
84 84
 	}
... ...
@@ -45,7 +45,7 @@ func NewSourceFile(path string, period time.Duration, updates chan<- interface{}
45 45
 		path:    path,
46 46
 		updates: updates,
47 47
 	}
48
-	glog.Infof("Watching file %s", path)
48
+	glog.V(1).Infof("Watching file %s", path)
49 49
 	go util.Forever(config.run, period)
50 50
 	return config
51 51
 }
... ...
@@ -44,7 +44,7 @@ func NewSourceURL(url string, period time.Duration, updates chan<- interface{})
44 44
 		updates: updates,
45 45
 		data:    nil,
46 46
 	}
47
-	glog.Infof("Watching URL %s", url)
47
+	glog.V(1).Infof("Watching URL %s", url)
48 48
 	go util.Forever(config.run, period)
49 49
 	return config
50 50
 }
... ...
@@ -79,7 +79,7 @@ func TestExtractInvalidManifest(t *testing.T) {
79 79
 			desc: "Unspecified container name",
80 80
 			manifests: []api.ContainerManifest{
81 81
 				{
82
-					Version: "v1beta1",
82
+					Version:    "v1beta1",
83 83
 					Containers: []api.Container{{Name: ""}},
84 84
 				},
85 85
 			},
... ...
@@ -88,7 +88,7 @@ func TestExtractInvalidManifest(t *testing.T) {
88 88
 			desc: "Invalid container name",
89 89
 			manifests: []api.ContainerManifest{
90 90
 				{
91
-					Version: "v1beta1",
91
+					Version:    "v1beta1",
92 92
 					Containers: []api.Container{{Name: "_INVALID_"}},
93 93
 				},
94 94
 			},
... ...
@@ -28,12 +28,12 @@ func TestDockerConfigJSONDecode(t *testing.T) {
28 28
 	input := []byte(`{"http://foo.example.com":{"username": "foo", "password": "bar", "email": "foo@example.com"}, "http://bar.example.com":{"username": "bar", "password": "baz", "email": "bar@example.com"}}`)
29 29
 
30 30
 	expect := dockerConfig(map[string]dockerConfigEntry{
31
-		"http://foo.example.com": dockerConfigEntry{
31
+		"http://foo.example.com": {
32 32
 			Username: "foo",
33 33
 			Password: "bar",
34 34
 			Email:    "foo@example.com",
35 35
 		},
36
-		"http://bar.example.com": dockerConfigEntry{
36
+		"http://bar.example.com": {
37 37
 			Username: "bar",
38 38
 			Password: "baz",
39 39
 			Email:    "bar@example.com",
... ...
@@ -171,12 +171,12 @@ func TestDecodeDockerConfigFieldAuth(t *testing.T) {
171 171
 
172 172
 func TestDockerKeyringFromConfig(t *testing.T) {
173 173
 	cfg := dockerConfig(map[string]dockerConfigEntry{
174
-		"http://foo.example.com": dockerConfigEntry{
174
+		"http://foo.example.com": {
175 175
 			Username: "foo",
176 176
 			Password: "bar",
177 177
 			Email:    "foo@example.com",
178 178
 		},
179
-		"https://bar.example.com": dockerConfigEntry{
179
+		"https://bar.example.com": {
180 180
 			Username: "bar",
181 181
 			Password: "baz",
182 182
 			Email:    "bar@example.com",
... ...
@@ -20,14 +20,15 @@ import (
20 20
 	"errors"
21 21
 	"fmt"
22 22
 	"hash/adler32"
23
+	"io"
23 24
 	"math/rand"
24 25
 	"os/exec"
25 26
 	"sort"
26 27
 	"strconv"
27 28
 	"strings"
28
-	"io"
29 29
 
30 30
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
31
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
31 32
 	"github.com/fsouza/go-dockerclient"
32 33
 	"github.com/golang/glog"
33 34
 )
... ...
@@ -46,6 +47,7 @@ type DockerInterface interface {
46 46
 	CreateContainer(docker.CreateContainerOptions) (*docker.Container, error)
47 47
 	StartContainer(id string, hostConfig *docker.HostConfig) error
48 48
 	StopContainer(id string, timeout uint) error
49
+	InspectImage(image string) (*docker.Image, error)
49 50
 	PullImage(opts docker.PullImageOptions, auth docker.AuthConfiguration) error
50 51
 	Logs(opts docker.LogsOptions) error
51 52
 }
... ...
@@ -56,6 +58,7 @@ type DockerID string
56 56
 // DockerPuller is an abstract interface for testability.  It abstracts image pull operations.
57 57
 type DockerPuller interface {
58 58
 	Pull(image string) error
59
+	IsImagePresent(image string) (bool, error)
59 60
 }
60 61
 
61 62
 // dockerPuller is the default implementation of DockerPuller.
... ...
@@ -64,8 +67,13 @@ type dockerPuller struct {
64 64
 	keyring *dockerKeyring
65 65
 }
66 66
 
67
+type throttledDockerPuller struct {
68
+	puller  dockerPuller
69
+	limiter util.RateLimiter
70
+}
71
+
67 72
 // NewDockerPuller creates a new instance of the default implementation of DockerPuller.
68
-func NewDockerPuller(client DockerInterface) DockerPuller {
73
+func NewDockerPuller(client DockerInterface, qps float32, burst int) DockerPuller {
69 74
 	dp := dockerPuller{
70 75
 		client:  client,
71 76
 		keyring: newDockerKeyring(),
... ...
@@ -75,14 +83,19 @@ func NewDockerPuller(client DockerInterface) DockerPuller {
75 75
 	if err == nil {
76 76
 		cfg.addToKeyring(dp.keyring)
77 77
 	} else {
78
-		glog.Errorf("Unable to parse docker config file: %v", err)
78
+		glog.Errorf("Unable to parse Docker config file: %v", err)
79 79
 	}
80 80
 
81 81
 	if dp.keyring.count() == 0 {
82
-		glog.Infof("Continuing with empty docker keyring")
82
+		glog.V(1).Infof("Continuing with empty Docker keyring")
83
+	}
84
+	if qps == 0.0 {
85
+		return dp
86
+	}
87
+	return &throttledDockerPuller{
88
+		puller:  dp,
89
+		limiter: util.NewTokenBucketRateLimiter(qps, burst),
83 90
 	}
84
-
85
-	return dp
86 91
 }
87 92
 
88 93
 type dockerContainerCommandRunner struct{}
... ...
@@ -130,6 +143,31 @@ func (p dockerPuller) Pull(image string) error {
130 130
 	return p.client.PullImage(opts, creds)
131 131
 }
132 132
 
133
+func (p throttledDockerPuller) Pull(image string) error {
134
+	if p.limiter.CanAccept() {
135
+		return p.puller.Pull(image)
136
+	}
137
+	return fmt.Errorf("pull QPS exceeded.")
138
+}
139
+
140
+func (p dockerPuller) IsImagePresent(name string) (bool, error) {
141
+	image, _ := parseImageName(name)
142
+	_, err := p.client.InspectImage(image)
143
+	if err == nil {
144
+		return true, nil
145
+	}
146
+	// This is super brittle, but its the best we got.
147
+	// TODO: Land code in the docker client to use docker.Error here instead.
148
+	if err.Error() == "no such image" {
149
+		return false, nil
150
+	}
151
+	return false, err
152
+}
153
+
154
+func (p throttledDockerPuller) IsImagePresent(name string) (bool, error) {
155
+	return p.puller.IsImagePresent(name)
156
+}
157
+
133 158
 // DockerContainers is a map of containers
134 159
 type DockerContainers map[DockerID]*docker.APIContainers
135 160
 
... ...
@@ -158,10 +196,11 @@ func (c DockerContainers) FindContainersByPodFullName(podFullName string) map[st
158 158
 	return containers
159 159
 }
160 160
 
161
-// GetKubeletDockerContainers returns a map of docker containers that we manage. The map key is the docker container ID
162
-func GetKubeletDockerContainers(client DockerInterface) (DockerContainers, error) {
161
+// GetKubeletDockerContainers takes client and boolean whether to list all container or just the running ones.
162
+// Returns a map of docker containers that we manage. The map key is the docker container ID
163
+func GetKubeletDockerContainers(client DockerInterface, allContainers bool) (DockerContainers, error) {
163 164
 	result := make(DockerContainers)
164
-	containers, err := client.ListContainers(docker.ListContainersOptions{})
165
+	containers, err := client.ListContainers(docker.ListContainersOptions{All: allContainers})
165 166
 	if err != nil {
166 167
 		return nil, err
167 168
 	}
... ...
@@ -169,7 +208,10 @@ func GetKubeletDockerContainers(client DockerInterface) (DockerContainers, error
169 169
 		container := &containers[i]
170 170
 		// Skip containers that we didn't create to allow users to manually
171 171
 		// spin up their own containers if they want.
172
-		if !strings.HasPrefix(container.Names[0], "/"+containerNamePrefix+"--") {
172
+		// TODO(dchen1107): Remove the old separator "--" by end of Oct
173
+		if !strings.HasPrefix(container.Names[0], "/"+containerNamePrefix+"_") &&
174
+			!strings.HasPrefix(container.Names[0], "/"+containerNamePrefix+"--") {
175
+			glog.Infof("Docker Container:%s is not managed by kubelet.", container.Names[0])
173 176
 			continue
174 177
 		}
175 178
 		result[DockerID(container.ID)] = container
... ...
@@ -228,6 +270,25 @@ func GetKubeletDockerContainerLogs(client DockerInterface, containerID, tail str
228 228
 	return
229 229
 }
230 230
 
231
+func generateContainerStatus(inspectResult *docker.Container) api.ContainerStatus {
232
+	if inspectResult == nil {
233
+		// Why did we not get an error?
234
+		return api.ContainerStatus{}
235
+	}
236
+
237
+	var containerStatus api.ContainerStatus
238
+
239
+	if inspectResult.State.Running {
240
+		containerStatus.State.Running = &api.ContainerStateRunning{}
241
+	} else {
242
+		containerStatus.State.Termination = &api.ContainerStateTerminated{
243
+			ExitCode: inspectResult.State.ExitCode,
244
+		}
245
+	}
246
+	containerStatus.DetailInfo = *inspectResult
247
+	return containerStatus
248
+}
249
+
231 250
 // ErrNoContainersInPod is returned when there are no containers for a given pod
232 251
 var ErrNoContainersInPod = errors.New("no containers exist for this pod")
233 252
 
... ...
@@ -249,7 +310,9 @@ func GetDockerPodInfo(client DockerInterface, podFullName, uuid string) (api.Pod
249 249
 			continue
250 250
 		}
251 251
 		// We assume docker return us a list of containers in time order
252
-		if _, ok := info[dockerContainerName]; ok {
252
+		if containerStatus, found := info[dockerContainerName]; found {
253
+			containerStatus.RestartCount += 1
254
+			info[dockerContainerName] = containerStatus
253 255
 			continue
254 256
 		}
255 257
 
... ...
@@ -257,12 +320,7 @@ func GetDockerPodInfo(client DockerInterface, podFullName, uuid string) (api.Pod
257 257
 		if err != nil {
258 258
 			return nil, err
259 259
 		}
260
-		if inspectResult == nil {
261
-			// Why did we not get an error?
262
-			info[dockerContainerName] = docker.Container{}
263
-		} else {
264
-			info[dockerContainerName] = *inspectResult
265
-		}
260
+		info[dockerContainerName] = generateContainerStatus(inspectResult)
266 261
 	}
267 262
 	if len(info) == 0 {
268 263
 		return nil, ErrNoContainersInPod
... ...
@@ -271,20 +329,6 @@ func GetDockerPodInfo(client DockerInterface, podFullName, uuid string) (api.Pod
271 271
 	return info, nil
272 272
 }
273 273
 
274
-// Converts "-" to "_-_" and "_" to "___" so that we can use "--" to meaningfully separate parts of a docker name.
275
-func escapeDash(in string) (out string) {
276
-	out = strings.Replace(in, "_", "___", -1)
277
-	out = strings.Replace(out, "-", "_-_", -1)
278
-	return
279
-}
280
-
281
-// Reverses the transformation of escapeDash.
282
-func unescapeDash(in string) (out string) {
283
-	out = strings.Replace(in, "_-_", "-", -1)
284
-	out = strings.Replace(out, "___", "_", -1)
285
-	return
286
-}
287
-
288 274
 const containerNamePrefix = "k8s"
289 275
 
290 276
 func HashContainer(container *api.Container) uint64 {
... ...
@@ -295,52 +339,52 @@ func HashContainer(container *api.Container) uint64 {
295 295
 
296 296
 // Creates a name which can be reversed to identify both full pod name and container name.
297 297
 func BuildDockerName(manifestUUID, podFullName string, container *api.Container) string {
298
-	containerName := escapeDash(container.Name) + "." + strconv.FormatUint(HashContainer(container), 16)
298
+	containerName := container.Name + "." + strconv.FormatUint(HashContainer(container), 16)
299 299
 	// Note, manifest.ID could be blank.
300 300
 	if len(manifestUUID) == 0 {
301
-		return fmt.Sprintf("%s--%s--%s--%08x",
301
+		return fmt.Sprintf("%s_%s_%s_%08x",
302 302
 			containerNamePrefix,
303 303
 			containerName,
304
-			escapeDash(podFullName),
304
+			podFullName,
305 305
 			rand.Uint32())
306 306
 	} else {
307
-		return fmt.Sprintf("%s--%s--%s--%s--%08x",
307
+		return fmt.Sprintf("%s_%s_%s_%s_%08x",
308 308
 			containerNamePrefix,
309 309
 			containerName,
310
-			escapeDash(podFullName),
311
-			escapeDash(manifestUUID),
310
+			podFullName,
311
+			manifestUUID,
312 312
 			rand.Uint32())
313 313
 	}
314 314
 }
315 315
 
316
-// Upacks a container name, returning the pod full name and container name we would have used to
317
-// construct the docker name. If the docker name isn't one we created, we may return empty strings.
316
+// Unpacks a container name, returning the pod full name and container name we would have used to
317
+// construct the docker name. If the docker name isn't the one we created, we may return empty strings.
318 318
 func ParseDockerName(name string) (podFullName, uuid, containerName string, hash uint64) {
319 319
 	// For some reason docker appears to be appending '/' to names.
320 320
 	// If it's there, strip it.
321 321
 	if name[0] == '/' {
322 322
 		name = name[1:]
323 323
 	}
324
-	parts := strings.Split(name, "--")
324
+	parts := strings.Split(name, "_")
325 325
 	if len(parts) == 0 || parts[0] != containerNamePrefix {
326 326
 		return
327 327
 	}
328 328
 	if len(parts) > 1 {
329 329
 		pieces := strings.Split(parts[1], ".")
330
-		containerName = unescapeDash(pieces[0])
330
+		containerName = pieces[0]
331 331
 		if len(pieces) > 1 {
332 332
 			var err error
333 333
 			hash, err = strconv.ParseUint(pieces[1], 16, 32)
334 334
 			if err != nil {
335
-				glog.Infof("invalid container hash: %s", pieces[1])
335
+				glog.Warningf("invalid container hash: %s", pieces[1])
336 336
 			}
337 337
 		}
338 338
 	}
339 339
 	if len(parts) > 2 {
340
-		podFullName = unescapeDash(parts[2])
340
+		podFullName = parts[2]
341 341
 	}
342 342
 	if len(parts) > 4 {
343
-		uuid = unescapeDash(parts[3])
343
+		uuid = parts[3]
344 344
 	}
345 345
 	return
346 346
 }
... ...
@@ -51,18 +51,18 @@ func TestGetContainerID(t *testing.T) {
51 51
 	fakeDocker.ContainerList = []docker.APIContainers{
52 52
 		{
53 53
 			ID:    "foobar",
54
-			Names: []string{"/k8s--foo--qux--1234"},
54
+			Names: []string{"/k8s_foo_qux_1234"},
55 55
 		},
56 56
 		{
57 57
 			ID:    "barbar",
58
-			Names: []string{"/k8s--bar--qux--2565"},
58
+			Names: []string{"/k8s_bar_qux_2565"},
59 59
 		},
60 60
 	}
61 61
 	fakeDocker.Container = &docker.Container{
62 62
 		ID: "foobar",
63 63
 	}
64 64
 
65
-	dockerContainers, err := GetKubeletDockerContainers(fakeDocker)
65
+	dockerContainers, err := GetKubeletDockerContainers(fakeDocker, false)
66 66
 	if err != nil {
67 67
 		t.Errorf("Expected no error, Got %#v", err)
68 68
 	}
... ...
@@ -83,31 +83,37 @@ func TestGetContainerID(t *testing.T) {
83 83
 	}
84 84
 }
85 85
 
86
-func verifyPackUnpack(t *testing.T, podNamespace, podName, containerName string) {
86
+func verifyPackUnpack(t *testing.T, podNamespace, manifestUUID, podName, containerName string) {
87 87
 	container := &api.Container{Name: containerName}
88 88
 	hasher := adler32.New()
89 89
 	data := fmt.Sprintf("%#v", *container)
90 90
 	hasher.Write([]byte(data))
91 91
 	computedHash := uint64(hasher.Sum32())
92 92
 	podFullName := fmt.Sprintf("%s.%s", podName, podNamespace)
93
-	name := BuildDockerName("", podFullName, container)
94
-	returnedPodFullName, _, returnedContainerName, hash := ParseDockerName(name)
95
-	if podFullName != returnedPodFullName || containerName != returnedContainerName || computedHash != hash {
96
-		t.Errorf("For (%s, %s, %d), unpacked (%s, %s, %d)", podFullName, containerName, computedHash, returnedPodFullName, returnedContainerName, hash)
93
+	name := BuildDockerName(manifestUUID, podFullName, container)
94
+	returnedPodFullName, returnedUUID, returnedContainerName, hash := ParseDockerName(name)
95
+	if podFullName != returnedPodFullName || manifestUUID != returnedUUID || containerName != returnedContainerName || computedHash != hash {
96
+		t.Errorf("For (%s, %s, %s, %d), unpacked (%s, %s, %s, %d)", podFullName, manifestUUID, containerName, computedHash, returnedPodFullName, returnedUUID, returnedContainerName, hash)
97 97
 	}
98 98
 }
99 99
 
100 100
 func TestContainerManifestNaming(t *testing.T) {
101
-	verifyPackUnpack(t, "file", "manifest1234", "container5678")
102
-	verifyPackUnpack(t, "file", "manifest--", "container__")
103
-	verifyPackUnpack(t, "file", "--manifest", "__container")
104
-	verifyPackUnpack(t, "", "m___anifest_", "container-_-")
105
-	verifyPackUnpack(t, "other", "_m___anifest", "-_-container")
101
+	manifestUUID := "d1b925c9-444a-11e4-a576-42010af0a203"
102
+	verifyPackUnpack(t, "file", manifestUUID, "manifest1234", "container5678")
103
+	verifyPackUnpack(t, "file", manifestUUID, "mani-fest-1234", "container5678")
104
+	// UUID is same as pod name
105
+	verifyPackUnpack(t, "file", manifestUUID, manifestUUID, "container123")
106
+	// empty namespace
107
+	verifyPackUnpack(t, "", manifestUUID, manifestUUID, "container123")
108
+	// No UUID
109
+	verifyPackUnpack(t, "other", "", manifestUUID, "container456")
110
+	// No Container name
111
+	verifyPackUnpack(t, "other", "", manifestUUID, "")
106 112
 
107 113
 	container := &api.Container{Name: "container"}
108 114
 	podName := "foo"
109 115
 	podNamespace := "test"
110
-	name := fmt.Sprintf("k8s--%s--%s.%s--12345", container.Name, podName, podNamespace)
116
+	name := fmt.Sprintf("k8s_%s_%s.%s_12345", container.Name, podName, podNamespace)
111 117
 
112 118
 	podFullName := fmt.Sprintf("%s.%s", podName, podNamespace)
113 119
 	returnedPodFullName, _, returnedContainerName, hash := ParseDockerName(name)
... ...
@@ -81,7 +81,7 @@ func (f *FakeDockerClient) CreateContainer(c docker.CreateContainerOptions) (*do
81 81
 	// This is not a very good fake. We'll just add this container's name to the list.
82 82
 	// Docker likes to add a '/', so copy that behavior.
83 83
 	name := "/" + c.Name
84
-	f.ContainerList = append(f.ContainerList, docker.APIContainers{ID: name, Names: []string{name}})
84
+	f.ContainerList = append(f.ContainerList, docker.APIContainers{ID: name, Names: []string{name}, Image: c.Config.Image})
85 85
 	return &docker.Container{ID: name}, nil
86 86
 }
87 87
 
... ...
@@ -130,10 +130,15 @@ func (f *FakeDockerClient) PullImage(opts docker.PullImageOptions, auth docker.A
130 130
 	return f.Err
131 131
 }
132 132
 
133
+func (f *FakeDockerClient) InspectImage(name string) (*docker.Image, error) {
134
+	return nil, f.Err
135
+}
136
+
133 137
 // FakeDockerPuller is a stub implementation of DockerPuller.
134 138
 type FakeDockerPuller struct {
135 139
 	sync.Mutex
136 140
 
141
+	HasImages    []string
137 142
 	ImagesPulled []string
138 143
 
139 144
 	// Every pull will return the first error here, and then reslice
... ...
@@ -153,3 +158,17 @@ func (f *FakeDockerPuller) Pull(image string) (err error) {
153 153
 	}
154 154
 	return err
155 155
 }
156
+
157
+func (f *FakeDockerPuller) IsImagePresent(name string) (bool, error) {
158
+	f.Lock()
159
+	defer f.Unlock()
160
+	if f.HasImages == nil {
161
+		return true, nil
162
+	}
163
+	for _, s := range f.HasImages {
164
+		if s == name {
165
+			return true, nil
166
+		}
167
+	}
168
+	return false, nil
169
+}
... ...
@@ -18,10 +18,10 @@ package kubelet
18 18
 
19 19
 import (
20 20
 	"fmt"
21
+	"io"
21 22
 	"net"
22
-	"strconv"
23 23
 	"net/http"
24
-	"io"
24
+	"strconv"
25 25
 
26 26
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
27 27
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
... ...
@@ -77,8 +77,8 @@ func (h *httpActionHandler) Run(podFullName, uuid string, container *api.Contain
77 77
 			return err
78 78
 		}
79 79
 		netInfo, found := info[networkContainerName]
80
-		if found && netInfo.NetworkSettings != nil {
81
-			host = netInfo.NetworkSettings.IPAddress
80
+		if found && netInfo.DetailInfo.NetworkSettings != nil {
81
+			host = netInfo.DetailInfo.NetworkSettings.IPAddress
82 82
 		} else {
83 83
 			return fmt.Errorf("failed to find networking container: %v", info)
84 84
 		}
... ...
@@ -101,7 +101,7 @@ func (h *httpActionHandler) Run(podFullName, uuid string, container *api.Contain
101 101
 // FlushWriter provides wrapper for responseWriter with HTTP streaming capabilities
102 102
 type FlushWriter struct {
103 103
 	flusher http.Flusher
104
-	writer io.Writer
104
+	writer  io.Writer
105 105
 }
106 106
 
107 107
 // Write is a FlushWriter implementation of the io.Writer that sends any buffered data to the client.
... ...
@@ -17,16 +17,15 @@ limitations under the License.
17 17
 package kubelet
18 18
 
19 19
 import (
20
-	"encoding/json"
21 20
 	"errors"
22 21
 	"fmt"
22
+	"io"
23 23
 	"net/http"
24 24
 	"path"
25 25
 	"strconv"
26 26
 	"strings"
27 27
 	"sync"
28 28
 	"time"
29
-	"io"
30 29
 
31 30
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
32 31
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
... ...
@@ -36,7 +35,6 @@ import (
36 36
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
37 37
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
38 38
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/volume"
39
-	"github.com/coreos/go-etcd/etcd"
40 39
 	"github.com/fsouza/go-dockerclient"
41 40
 	"github.com/golang/glog"
42 41
 	"github.com/google/cadvisor/info"
... ...
@@ -69,17 +67,23 @@ func NewMainKubelet(
69 69
 	cc CadvisorInterface,
70 70
 	ec tools.EtcdClient,
71 71
 	rd string,
72
-	ri time.Duration) *Kubelet {
72
+	ni string,
73
+	ri time.Duration,
74
+	pullQPS float32,
75
+	pullBurst int) *Kubelet {
73 76
 	return &Kubelet{
74
-		hostname:       hn,
75
-		dockerClient:   dc,
76
-		cadvisorClient: cc,
77
-		etcdClient:     ec,
78
-		rootDirectory:  rd,
79
-		resyncInterval: ri,
80
-		podWorkers:     newPodWorkers(),
81
-		runner:         dockertools.NewDockerContainerCommandRunner(),
82
-		httpClient:     &http.Client{},
77
+		hostname:              hn,
78
+		dockerClient:          dc,
79
+		cadvisorClient:        cc,
80
+		etcdClient:            ec,
81
+		rootDirectory:         rd,
82
+		resyncInterval:        ri,
83
+		networkContainerImage: ni,
84
+		podWorkers:            newPodWorkers(),
85
+		runner:                dockertools.NewDockerContainerCommandRunner(),
86
+		httpClient:            &http.Client{},
87
+		pullQPS:               pullQPS,
88
+		pullBurst:             pullBurst,
83 89
 	}
84 90
 }
85 91
 
... ...
@@ -87,11 +91,12 @@ func NewMainKubelet(
87 87
 // TODO: add more integration tests, and expand parameter list as needed.
88 88
 func NewIntegrationTestKubelet(hn string, dc dockertools.DockerInterface) *Kubelet {
89 89
 	return &Kubelet{
90
-		hostname:       hn,
91
-		dockerClient:   dc,
92
-		dockerPuller:   &dockertools.FakeDockerPuller{},
93
-		resyncInterval: 3 * time.Second,
94
-		podWorkers:     newPodWorkers(),
90
+		hostname:              hn,
91
+		dockerClient:          dc,
92
+		dockerPuller:          &dockertools.FakeDockerPuller{},
93
+		networkContainerImage: NetworkContainerImage,
94
+		resyncInterval:        3 * time.Second,
95
+		podWorkers:            newPodWorkers(),
95 96
 	}
96 97
 }
97 98
 
... ...
@@ -101,11 +106,12 @@ type httpGetInterface interface {
101 101
 
102 102
 // Kubelet is the main kubelet implementation.
103 103
 type Kubelet struct {
104
-	hostname       string
105
-	dockerClient   dockertools.DockerInterface
106
-	rootDirectory  string
107
-	podWorkers     podWorkers
108
-	resyncInterval time.Duration
104
+	hostname              string
105
+	dockerClient          dockertools.DockerInterface
106
+	rootDirectory         string
107
+	networkContainerImage string
108
+	podWorkers            podWorkers
109
+	resyncInterval        time.Duration
109 110
 
110 111
 	// Optional, no events will be sent without it
111 112
 	etcdClient tools.EtcdClient
... ...
@@ -121,6 +127,10 @@ type Kubelet struct {
121 121
 	runner dockertools.ContainerCommandRunner
122 122
 	// Optional, client for http requests, defaults to empty client
123 123
 	httpClient httpGetInterface
124
+	// Optional, maximum pull QPS from the docker registry, 0.0 means unlimited.
125
+	pullQPS float32
126
+	// Optional, maximum burst QPS from the docker registry, must be positive if QPS is > 0.0
127
+	pullBurst int
124 128
 }
125 129
 
126 130
 // Run starts the kubelet reacting to config updates
... ...
@@ -129,7 +139,7 @@ func (kl *Kubelet) Run(updates <-chan PodUpdate) {
129 129
 		kl.logServer = http.StripPrefix("/logs/", http.FileServer(http.Dir("/var/log/")))
130 130
 	}
131 131
 	if kl.dockerPuller == nil {
132
-		kl.dockerPuller = dockertools.NewDockerPuller(kl.dockerClient)
132
+		kl.dockerPuller = dockertools.NewDockerPuller(kl.dockerClient, kl.pullQPS, kl.pullBurst)
133 133
 	}
134 134
 	if kl.healthChecker == nil {
135 135
 		kl.healthChecker = health.NewHealthChecker()
... ...
@@ -174,27 +184,9 @@ func (self *podWorkers) Run(podFullName string, action func()) {
174 174
 	}()
175 175
 }
176 176
 
177
-// LogEvent logs an event to the etcd backend.
177
+// LogEvent reports an event.
178 178
 func (kl *Kubelet) LogEvent(event *api.Event) error {
179
-	if kl.etcdClient == nil {
180
-		return fmt.Errorf("no etcd client connection")
181
-	}
182
-	event.Timestamp = time.Now().Unix()
183
-	data, err := json.Marshal(event)
184
-	if err != nil {
185
-		return err
186
-	}
187
-
188
-	var response *etcd.Response
189
-	response, err = kl.etcdClient.AddChild(fmt.Sprintf("/events/%s", event.Container.Name), string(data), 60*60*48 /* 2 days */)
190
-	// TODO(bburns) : examine response here.
191
-	if err != nil {
192
-		glog.Errorf("Error writing event: %s\n", err)
193
-		if response != nil {
194
-			glog.Infof("Response was: %v\n", *response)
195
-		}
196
-	}
197
-	return err
179
+	return nil
198 180
 }
199 181
 
200 182
 func makeEnvironmentVariables(container *api.Container) []string {
... ...
@@ -234,13 +226,13 @@ func makePortsAndBindings(container *api.Container) (map[docker.Port]struct{}, m
234 234
 		// Some of this port stuff is under-documented voodoo.
235 235
 		// See http://stackoverflow.com/questions/20428302/binding-a-port-to-a-host-interface-using-the-rest-api
236 236
 		var protocol string
237
-		switch strings.ToUpper(port.Protocol) {
237
+		switch strings.ToUpper(string(port.Protocol)) {
238 238
 		case "UDP":
239 239
 			protocol = "/udp"
240 240
 		case "TCP":
241 241
 			protocol = "/tcp"
242 242
 		default:
243
-			glog.Infof("Unknown protocol '%s': defaulting to TCP", port.Protocol)
243
+			glog.Warningf("Unknown protocol '%s': defaulting to TCP", port.Protocol)
244 244
 			protocol = "/tcp"
245 245
 		}
246 246
 		dockerPort := docker.Port(strconv.Itoa(interiorPort) + protocol)
... ...
@@ -365,30 +357,22 @@ func (kl *Kubelet) killContainer(dockerContainer *docker.APIContainers) error {
365 365
 }
366 366
 
367 367
 func (kl *Kubelet) killContainerByID(ID, name string) error {
368
-	glog.Infof("Killing: %s", ID)
368
+	glog.V(2).Infof("Killing: %s", ID)
369 369
 	err := kl.dockerClient.StopContainer(ID, 10)
370 370
 	if len(name) == 0 {
371 371
 		return err
372 372
 	}
373
-	podFullName, uuid, containerName, _ := dockertools.ParseDockerName(name)
374
-	kl.LogEvent(&api.Event{
375
-		Event: "STOP",
376
-		Manifest: &api.ContainerManifest{
377
-			//TODO: This should be reported using either the apiserver schema or the kubelet schema
378
-			ID:   podFullName,
379
-			UUID: uuid,
380
-		},
381
-		Container: &api.Container{
382
-			Name: containerName,
383
-		},
384
-	})
373
+
374
+	// TODO(lavalamp): restore event logging:
375
+	// podFullName, uuid, containerName, _ := dockertools.ParseDockerName(name)
376
+	// kl.LogEvent(&api.Event{})
385 377
 
386 378
 	return err
387 379
 }
388 380
 
389 381
 const (
390 382
 	networkContainerName  = "net"
391
-	networkContainerImage = "kubernetes/pause:latest"
383
+	NetworkContainerImage = "kubernetes/pause:latest"
392 384
 )
393 385
 
394 386
 // createNetworkContainer starts the network container for a pod. Returns the docker container ID of the newly created container.
... ...
@@ -401,10 +385,19 @@ func (kl *Kubelet) createNetworkContainer(pod *Pod) (dockertools.DockerID, error
401 401
 	}
402 402
 	container := &api.Container{
403 403
 		Name:  networkContainerName,
404
-		Image: networkContainerImage,
404
+		Image: kl.networkContainerImage,
405 405
 		Ports: ports,
406 406
 	}
407
-	kl.dockerPuller.Pull(networkContainerImage)
407
+	// TODO: make this a TTL based pull (if image older than X policy, pull)
408
+	ok, err := kl.dockerPuller.IsImagePresent(container.Image)
409
+	if err != nil {
410
+		return "", err
411
+	}
412
+	if !ok {
413
+		if err := kl.dockerPuller.Pull(container.Image); err != nil {
414
+			return "", err
415
+		}
416
+	}
408 417
 	return kl.runContainer(pod, container, nil, "")
409 418
 }
410 419
 
... ...
@@ -453,7 +446,7 @@ func (kl *Kubelet) syncPod(pod *Pod, dockerContainers dockertools.DockerContaine
453 453
 	if networkDockerContainer, found, _ := dockerContainers.FindPodContainer(podFullName, uuid, networkContainerName); found {
454 454
 		netID = dockertools.DockerID(networkDockerContainer.ID)
455 455
 	} else {
456
-		glog.Infof("Network container doesn't exist, creating")
456
+		glog.V(3).Infof("Network container doesn't exist, creating")
457 457
 		count, err := kl.deleteAllContainers(pod, podFullName, dockerContainers)
458 458
 		if err != nil {
459 459
 			return err
... ...
@@ -466,7 +459,7 @@ func (kl *Kubelet) syncPod(pod *Pod, dockerContainers dockertools.DockerContaine
466 466
 		netID = dockerNetworkID
467 467
 		if count > 0 {
468 468
 			// relist everything, otherwise we'll think we're ok
469
-			dockerContainers, err = dockertools.GetKubeletDockerContainers(kl.dockerClient)
469
+			dockerContainers, err = dockertools.GetKubeletDockerContainers(kl.dockerClient, false)
470 470
 			if err != nil {
471 471
 				glog.Errorf("Error listing containers %#v", dockerContainers)
472 472
 				return err
... ...
@@ -488,15 +481,15 @@ func (kl *Kubelet) syncPod(pod *Pod, dockerContainers dockertools.DockerContaine
488 488
 			podFullName, uuid)
489 489
 	}
490 490
 	netInfo, found := info[networkContainerName]
491
-	if found && netInfo.NetworkSettings != nil {
492
-		podState.PodIP = netInfo.NetworkSettings.IPAddress
491
+	if found && netInfo.DetailInfo.NetworkSettings != nil {
492
+		podState.PodIP = netInfo.DetailInfo.NetworkSettings.IPAddress
493 493
 	}
494 494
 
495 495
 	for _, container := range pod.Manifest.Containers {
496 496
 		expectedHash := dockertools.HashContainer(&container)
497 497
 		if dockerContainer, found, hash := dockerContainers.FindPodContainer(podFullName, uuid, container.Name); found {
498 498
 			containerID := dockertools.DockerID(dockerContainer.ID)
499
-			glog.V(1).Infof("pod %s container %s exists as %v", podFullName, container.Name, containerID)
499
+			glog.V(3).Infof("pod %s container %s exists as %v", podFullName, container.Name, containerID)
500 500
 
501 501
 			// look for changes in the container.
502 502
 			if hash == 0 || hash == expectedHash {
... ...
@@ -513,7 +506,7 @@ func (kl *Kubelet) syncPod(pod *Pod, dockerContainers dockertools.DockerContaine
513 513
 				}
514 514
 				glog.V(1).Infof("pod %s container %s is unhealthy.", podFullName, container.Name, healthy)
515 515
 			} else {
516
-				glog.V(1).Infof("container hash changed %d vs %d.", hash, expectedHash)
516
+				glog.V(3).Infof("container hash changed %d vs %d.", hash, expectedHash)
517 517
 			}
518 518
 			if err := kl.killContainer(dockerContainer); err != nil {
519 519
 				glog.V(1).Infof("Failed to kill container %s: %v", dockerContainer.ID, err)
... ...
@@ -531,24 +524,33 @@ func (kl *Kubelet) syncPod(pod *Pod, dockerContainers dockertools.DockerContaine
531 531
 
532 532
 		if len(recentContainers) > 0 && pod.Manifest.RestartPolicy.Always == nil {
533 533
 			if pod.Manifest.RestartPolicy.Never != nil {
534
-				glog.Infof("Already ran container with name %s--%s--%s, do nothing",
534
+				glog.V(3).Infof("Already ran container with name %s--%s--%s, do nothing",
535 535
 					podFullName, uuid, container.Name)
536 536
 				continue
537 537
 			}
538 538
 			if pod.Manifest.RestartPolicy.OnFailure != nil {
539 539
 				// Check the exit code of last run
540 540
 				if recentContainers[0].State.ExitCode == 0 {
541
-					glog.Infof("Already successfully ran container with name %s--%s--%s, do nothing",
541
+					glog.V(3).Infof("Already successfully ran container with name %s--%s--%s, do nothing",
542 542
 						podFullName, uuid, container.Name)
543 543
 					continue
544 544
 				}
545 545
 			}
546 546
 		}
547 547
 
548
-		glog.Infof("Container with name %s--%s--%s doesn't exist, creating %#v", podFullName, uuid, container.Name, container)
549
-		if err := kl.dockerPuller.Pull(container.Image); err != nil {
550
-			glog.Errorf("Failed to pull image %s: %v skipping pod %s container %s.", container.Image, err, podFullName, container.Name)
551
-			continue
548
+		glog.V(3).Infof("Container with name %s--%s--%s doesn't exist, creating %#v", podFullName, uuid, container.Name, container)
549
+		if !api.IsPullNever(container.ImagePullPolicy) {
550
+			present, err := kl.dockerPuller.IsImagePresent(container.Image)
551
+			if err != nil {
552
+				glog.Errorf("Failed to inspect image: %s: %#v skipping pod %s container %s", container.Image, err, podFullName, container.Name)
553
+				continue
554
+			}
555
+			if api.IsPullAlways(container.ImagePullPolicy) || !present {
556
+				if err := kl.dockerPuller.Pull(container.Image); err != nil {
557
+					glog.Errorf("Failed to pull image %s: %v skipping pod %s container %s.", container.Image, err, podFullName, container.Name)
558
+					continue
559
+				}
560
+			}
552 561
 		}
553 562
 		// TODO(dawnchen): Check RestartPolicy.DelaySeconds before restart a container
554 563
 		containerID, err := kl.runContainer(pod, &container, podVolumes, "container:"+string(netID))
... ...
@@ -607,11 +609,11 @@ func (kl *Kubelet) reconcileVolumes(pods []Pod) error {
607 607
 		if _, ok := desiredVolumes[name]; !ok {
608 608
 			//TODO (jonesdl) We should somehow differentiate between volumes that are supposed
609 609
 			//to be deleted and volumes that are leftover after a crash.
610
-			glog.Infof("Orphaned volume %s found, tearing down volume", name)
610
+			glog.Warningf("Orphaned volume %s found, tearing down volume", name)
611 611
 			//TODO (jonesdl) This should not block other kubelet synchronization procedures
612 612
 			err := vol.TearDown()
613 613
 			if err != nil {
614
-				glog.Infof("Could not tear down volume %s (%s)", name, err)
614
+				glog.Errorf("Could not tear down volume %s (%s)", name, err)
615 615
 			}
616 616
 		}
617 617
 	}
... ...
@@ -620,11 +622,11 @@ func (kl *Kubelet) reconcileVolumes(pods []Pod) error {
620 620
 
621 621
 // SyncPods synchronizes the configured list of pods (desired state) with the host current state.
622 622
 func (kl *Kubelet) SyncPods(pods []Pod) error {
623
-	glog.Infof("Desired [%s]: %+v", kl.hostname, pods)
623
+	glog.V(4).Infof("Desired [%s]: %+v", kl.hostname, pods)
624 624
 	var err error
625 625
 	desiredContainers := make(map[podContainer]empty)
626 626
 
627
-	dockerContainers, err := dockertools.GetKubeletDockerContainers(kl.dockerClient)
627
+	dockerContainers, err := dockertools.GetKubeletDockerContainers(kl.dockerClient, false)
628 628
 	if err != nil {
629 629
 		glog.Errorf("Error listing containers %#v", dockerContainers)
630 630
 		return err
... ...
@@ -652,7 +654,7 @@ func (kl *Kubelet) SyncPods(pods []Pod) error {
652 652
 	}
653 653
 
654 654
 	// Kill any containers we don't need
655
-	existingContainers, err := dockertools.GetKubeletDockerContainers(kl.dockerClient)
655
+	existingContainers, err := dockertools.GetKubeletDockerContainers(kl.dockerClient, false)
656 656
 	if err != nil {
657 657
 		glog.Errorf("Error listing containers: %v", err)
658 658
 		return err
... ...
@@ -703,13 +705,13 @@ func (kl *Kubelet) syncLoop(updates <-chan PodUpdate, handler SyncHandler) {
703 703
 		case u := <-updates:
704 704
 			switch u.Op {
705 705
 			case SET:
706
-				glog.Infof("Containers changed [%s]", kl.hostname)
706
+				glog.V(3).Infof("Containers changed [%s]", kl.hostname)
707 707
 				pods = u.Pods
708 708
 				pods = filterHostPortConflicts(pods)
709 709
 
710 710
 			case UPDATE:
711 711
 				//TODO: implement updates of containers
712
-				glog.Infof("Containers updated, not implemented [%s]", kl.hostname)
712
+				glog.Warningf("Containers updated, not implemented [%s]", kl.hostname)
713 713
 				continue
714 714
 
715 715
 			default:
... ...
@@ -748,17 +750,21 @@ func (kl *Kubelet) statsFromContainerPath(containerPath string, req *info.Contai
748 748
 }
749 749
 
750 750
 // GetKubeletContainerLogs returns logs from the container
751
+// The second parameter of GetPodInfo and FindPodContainer methods represents pod UUID, which is allowed to be blank
751 752
 func (kl *Kubelet) GetKubeletContainerLogs(podFullName, containerName, tail string, follow bool, stdout, stderr io.Writer) error {
752
-	dockerContainers, err := dockertools.GetKubeletDockerContainers(kl.dockerClient)
753
+	_, err := kl.GetPodInfo(podFullName, "")
754
+	if err == dockertools.ErrNoContainersInPod {
755
+		return fmt.Errorf("Pod not found (%s)\n", podFullName)
756
+	}
757
+	dockerContainers, err := dockertools.GetKubeletDockerContainers(kl.dockerClient, true)
753 758
 	if err != nil {
754 759
 		return err
755 760
 	}
756
-	var uuid string
757
-	dockerContainer, found, _ := dockerContainers.FindPodContainer(podFullName, uuid, containerName)
761
+	dockerContainer, found, _ := dockerContainers.FindPodContainer(podFullName, "", containerName)
758 762
 	if !found {
759
-		return fmt.Errorf("container not found (%s)\n", containerName)
763
+		return fmt.Errorf("Container not found (%s)\n", containerName)
760 764
 	}
761
-	return dockertools.GetKubeletDockerContainerLogs(kl.dockerClient, dockerContainer.ID, tail , follow, stdout, stderr)
765
+	return dockertools.GetKubeletDockerContainerLogs(kl.dockerClient, dockerContainer.ID, tail, follow, stdout, stderr)
762 766
 }
763 767
 
764 768
 // GetPodInfo returns information from Docker about the containers in a pod
... ...
@@ -771,7 +777,7 @@ func (kl *Kubelet) GetContainerInfo(podFullName, uuid, containerName string, req
771 771
 	if kl.cadvisorClient == nil {
772 772
 		return nil, nil
773 773
 	}
774
-	dockerContainers, err := dockertools.GetKubeletDockerContainers(kl.dockerClient)
774
+	dockerContainers, err := dockertools.GetKubeletDockerContainers(kl.dockerClient, false)
775 775
 	if err != nil {
776 776
 		return nil, err
777 777
 	}
... ...
@@ -816,7 +822,7 @@ func (kl *Kubelet) RunInContainer(podFullName, uuid, container string, cmd []str
816 816
 	if kl.runner == nil {
817 817
 		return nil, fmt.Errorf("no runner specified.")
818 818
 	}
819
-	dockerContainers, err := dockertools.GetKubeletDockerContainers(kl.dockerClient)
819
+	dockerContainers, err := dockertools.GetKubeletDockerContainers(kl.dockerClient, false)
820 820
 	if err != nil {
821 821
 		return nil, err
822 822
 	}
... ...
@@ -17,12 +17,12 @@ limitations under the License.
17 17
 package kubelet
18 18
 
19 19
 import (
20
-	"encoding/json"
21 20
 	"fmt"
22 21
 	"net/http"
23 22
 	"reflect"
24 23
 	"regexp"
25 24
 	"strconv"
25
+	"strings"
26 26
 	"sync"
27 27
 	"testing"
28 28
 	"time"
... ...
@@ -84,11 +84,11 @@ func TestKillContainerWithError(t *testing.T) {
84 84
 		ContainerList: []docker.APIContainers{
85 85
 			{
86 86
 				ID:    "1234",
87
-				Names: []string{"/k8s--foo--qux--1234"},
87
+				Names: []string{"/k8s_foo_qux_1234"},
88 88
 			},
89 89
 			{
90 90
 				ID:    "5678",
91
-				Names: []string{"/k8s--bar--qux--5678"},
91
+				Names: []string{"/k8s_bar_qux_5678"},
92 92
 			},
93 93
 		},
94 94
 	}
... ...
@@ -106,11 +106,11 @@ func TestKillContainer(t *testing.T) {
106 106
 	fakeDocker.ContainerList = []docker.APIContainers{
107 107
 		{
108 108
 			ID:    "1234",
109
-			Names: []string{"/k8s--foo--qux--1234"},
109
+			Names: []string{"/k8s_foo_qux_1234"},
110 110
 		},
111 111
 		{
112 112
 			ID:    "5678",
113
-			Names: []string{"/k8s--bar--qux--5678"},
113
+			Names: []string{"/k8s_bar_qux_5678"},
114 114
 		},
115 115
 	}
116 116
 	fakeDocker.Container = &docker.Container{
... ...
@@ -155,13 +155,13 @@ func TestSyncPodsDoesNothing(t *testing.T) {
155 155
 	container := api.Container{Name: "bar"}
156 156
 	fakeDocker.ContainerList = []docker.APIContainers{
157 157
 		{
158
-			// format is k8s--<container-id>--<pod-fullname>
159
-			Names: []string{"/k8s--bar." + strconv.FormatUint(dockertools.HashContainer(&container), 16) + "--foo.test"},
158
+			// format is k8s_<container-id>_<pod-fullname>
159
+			Names: []string{"/k8s_bar." + strconv.FormatUint(dockertools.HashContainer(&container), 16) + "_foo.test"},
160 160
 			ID:    "1234",
161 161
 		},
162 162
 		{
163 163
 			// network container
164
-			Names: []string{"/k8s--net--foo.test--"},
164
+			Names: []string{"/k8s_net_foo.test_"},
165 165
 			ID:    "9876",
166 166
 		},
167 167
 	}
... ...
@@ -207,6 +207,7 @@ func matchString(t *testing.T, pattern, str string) bool {
207 207
 
208 208
 func TestSyncPodsCreatesNetAndContainer(t *testing.T) {
209 209
 	kubelet, _, fakeDocker := newTestKubelet(t)
210
+	kubelet.networkContainerImage = "custom_image_name"
210 211
 	fakeDocker.ContainerList = []docker.APIContainers{}
211 212
 	err := kubelet.SyncPods([]Pod{
212 213
 		{
... ...
@@ -229,9 +230,60 @@ func TestSyncPodsCreatesNetAndContainer(t *testing.T) {
229 229
 		"list", "list", "create", "start", "list", "inspect", "list", "create", "start"})
230 230
 
231 231
 	fakeDocker.Lock()
232
+
233
+	found := false
234
+	for _, c := range fakeDocker.ContainerList {
235
+		if c.Image == "custom_image_name" && strings.HasPrefix(c.Names[0], "/k8s_net") {
236
+			found = true
237
+		}
238
+	}
239
+	if !found {
240
+		t.Errorf("Custom net container not found: %v", fakeDocker.ContainerList)
241
+	}
242
+
232 243
 	if len(fakeDocker.Created) != 2 ||
233
-		!matchString(t, "k8s--net\\.[a-f0-9]+--foo.test--", fakeDocker.Created[0]) ||
234
-		!matchString(t, "k8s--bar\\.[a-f0-9]+--foo.test--", fakeDocker.Created[1]) {
244
+		!matchString(t, "k8s_net\\.[a-f0-9]+_foo.test_", fakeDocker.Created[0]) ||
245
+		!matchString(t, "k8s_bar\\.[a-f0-9]+_foo.test_", fakeDocker.Created[1]) {
246
+		t.Errorf("Unexpected containers created %v", fakeDocker.Created)
247
+	}
248
+	fakeDocker.Unlock()
249
+}
250
+
251
+func TestSyncPodsCreatesNetAndContainerPullsImage(t *testing.T) {
252
+	kubelet, _, fakeDocker := newTestKubelet(t)
253
+	puller := kubelet.dockerPuller.(*dockertools.FakeDockerPuller)
254
+	puller.HasImages = []string{}
255
+	kubelet.networkContainerImage = "custom_image_name"
256
+	fakeDocker.ContainerList = []docker.APIContainers{}
257
+	err := kubelet.SyncPods([]Pod{
258
+		{
259
+			Name:      "foo",
260
+			Namespace: "test",
261
+			Manifest: api.ContainerManifest{
262
+				ID: "foo",
263
+				Containers: []api.Container{
264
+					{Name: "bar"},
265
+				},
266
+			},
267
+		},
268
+	})
269
+	if err != nil {
270
+		t.Errorf("unexpected error: %v", err)
271
+	}
272
+	kubelet.drainWorkers()
273
+
274
+	verifyCalls(t, fakeDocker, []string{
275
+		"list", "list", "create", "start", "list", "inspect", "list", "create", "start"})
276
+
277
+	fakeDocker.Lock()
278
+
279
+	if !reflect.DeepEqual(puller.ImagesPulled, []string{"custom_image_name", ""}) {
280
+		t.Errorf("Unexpected pulled containers: %v", puller.ImagesPulled)
281
+	}
282
+
283
+	if len(fakeDocker.Created) != 2 ||
284
+		!matchString(t, "k8s_net\\.[a-f0-9]+_foo.test_", fakeDocker.Created[0]) ||
285
+		!matchString(t, "k8s_bar\\.[a-f0-9]+_foo.test_", fakeDocker.Created[1]) {
235 286
 		t.Errorf("Unexpected containers created %v", fakeDocker.Created)
236 287
 	}
237 288
 	fakeDocker.Unlock()
... ...
@@ -242,7 +294,7 @@ func TestSyncPodsWithNetCreatesContainer(t *testing.T) {
242 242
 	fakeDocker.ContainerList = []docker.APIContainers{
243 243
 		{
244 244
 			// network container
245
-			Names: []string{"/k8s--net--foo.test--"},
245
+			Names: []string{"/k8s_net_foo.test_"},
246 246
 			ID:    "9876",
247 247
 		},
248 248
 	}
... ...
@@ -268,7 +320,7 @@ func TestSyncPodsWithNetCreatesContainer(t *testing.T) {
268 268
 
269 269
 	fakeDocker.Lock()
270 270
 	if len(fakeDocker.Created) != 1 ||
271
-		!matchString(t, "k8s--bar\\.[a-f0-9]+--foo.test--", fakeDocker.Created[0]) {
271
+		!matchString(t, "k8s_bar\\.[a-f0-9]+_foo.test_", fakeDocker.Created[0]) {
272 272
 		t.Errorf("Unexpected containers created %v", fakeDocker.Created)
273 273
 	}
274 274
 	fakeDocker.Unlock()
... ...
@@ -281,7 +333,7 @@ func TestSyncPodsWithNetCreatesContainerCallsHandler(t *testing.T) {
281 281
 	fakeDocker.ContainerList = []docker.APIContainers{
282 282
 		{
283 283
 			// network container
284
-			Names: []string{"/k8s--net--foo.test--"},
284
+			Names: []string{"/k8s_net_foo.test_"},
285 285
 			ID:    "9876",
286 286
 		},
287 287
 	}
... ...
@@ -318,7 +370,7 @@ func TestSyncPodsWithNetCreatesContainerCallsHandler(t *testing.T) {
318 318
 
319 319
 	fakeDocker.Lock()
320 320
 	if len(fakeDocker.Created) != 1 ||
321
-		!matchString(t, "k8s--bar\\.[a-f0-9]+--foo.test--", fakeDocker.Created[0]) {
321
+		!matchString(t, "k8s_bar\\.[a-f0-9]+_foo.test_", fakeDocker.Created[0]) {
322 322
 		t.Errorf("Unexpected containers created %v", fakeDocker.Created)
323 323
 	}
324 324
 	fakeDocker.Unlock()
... ...
@@ -331,8 +383,8 @@ func TestSyncPodsDeletesWithNoNetContainer(t *testing.T) {
331 331
 	kubelet, _, fakeDocker := newTestKubelet(t)
332 332
 	fakeDocker.ContainerList = []docker.APIContainers{
333 333
 		{
334
-			// format is k8s--<container-id>--<pod-fullname>
335
-			Names: []string{"/k8s--bar--foo.test"},
334
+			// format is k8s_<container-id>_<pod-fullname>
335
+			Names: []string{"/k8s_bar_foo.test"},
336 336
 			ID:    "1234",
337 337
 		},
338 338
 	}
... ...
@@ -373,12 +425,12 @@ func TestSyncPodsDeletes(t *testing.T) {
373 373
 	fakeDocker.ContainerList = []docker.APIContainers{
374 374
 		{
375 375
 			// the k8s prefix is required for the kubelet to manage the container
376
-			Names: []string{"/k8s--foo--bar.test"},
376
+			Names: []string{"/k8s_foo_bar.test"},
377 377
 			ID:    "1234",
378 378
 		},
379 379
 		{
380 380
 			// network container
381
-			Names: []string{"/k8s--net--foo.test--"},
381
+			Names: []string{"/k8s_net_foo.test_"},
382 382
 			ID:    "9876",
383 383
 		},
384 384
 		{
... ...
@@ -411,22 +463,22 @@ func TestSyncPodDeletesDuplicate(t *testing.T) {
411 411
 	dockerContainers := dockertools.DockerContainers{
412 412
 		"1234": &docker.APIContainers{
413 413
 			// the k8s prefix is required for the kubelet to manage the container
414
-			Names: []string{"/k8s--foo--bar.test--1"},
414
+			Names: []string{"/k8s_foo_bar.test_1"},
415 415
 			ID:    "1234",
416 416
 		},
417 417
 		"9876": &docker.APIContainers{
418 418
 			// network container
419
-			Names: []string{"/k8s--net--bar.test--"},
419
+			Names: []string{"/k8s_net_bar.test_"},
420 420
 			ID:    "9876",
421 421
 		},
422 422
 		"4567": &docker.APIContainers{
423 423
 			// Duplicate for the same container.
424
-			Names: []string{"/k8s--foo--bar.test--2"},
424
+			Names: []string{"/k8s_foo_bar.test_2"},
425 425
 			ID:    "4567",
426 426
 		},
427 427
 		"2304": &docker.APIContainers{
428 428
 			// Container for another pod, untouched.
429
-			Names: []string{"/k8s--baz--fiz.test--6"},
429
+			Names: []string{"/k8s_baz_fiz.test_6"},
430 430
 			ID:    "2304",
431 431
 		},
432 432
 	}
... ...
@@ -458,18 +510,22 @@ func (f *FalseHealthChecker) HealthCheck(podFullName string, state api.PodState,
458 458
 	return health.Unhealthy, nil
459 459
 }
460 460
 
461
+func (f *FalseHealthChecker) CanCheck(probe *api.LivenessProbe) bool {
462
+	return true
463
+}
464
+
461 465
 func TestSyncPodBadHash(t *testing.T) {
462 466
 	kubelet, _, fakeDocker := newTestKubelet(t)
463 467
 	kubelet.healthChecker = &FalseHealthChecker{}
464 468
 	dockerContainers := dockertools.DockerContainers{
465 469
 		"1234": &docker.APIContainers{
466 470
 			// the k8s prefix is required for the kubelet to manage the container
467
-			Names: []string{"/k8s--bar.1234--foo.test"},
471
+			Names: []string{"/k8s_bar.1234_foo.test"},
468 472
 			ID:    "1234",
469 473
 		},
470 474
 		"9876": &docker.APIContainers{
471 475
 			// network container
472
-			Names: []string{"/k8s--net--foo.test--"},
476
+			Names: []string{"/k8s_net_foo.test_"},
473 477
 			ID:    "9876",
474 478
 		},
475 479
 	}
... ...
@@ -506,12 +562,12 @@ func TestSyncPodUnhealthy(t *testing.T) {
506 506
 	dockerContainers := dockertools.DockerContainers{
507 507
 		"1234": &docker.APIContainers{
508 508
 			// the k8s prefix is required for the kubelet to manage the container
509
-			Names: []string{"/k8s--bar--foo.test"},
509
+			Names: []string{"/k8s_bar_foo.test"},
510 510
 			ID:    "1234",
511 511
 		},
512 512
 		"9876": &docker.APIContainers{
513 513
 			// network container
514
-			Names: []string{"/k8s--net--foo.test--"},
514
+			Names: []string{"/k8s_net_foo.test_"},
515 515
 			ID:    "9876",
516 516
 		},
517 517
 	}
... ...
@@ -523,8 +579,7 @@ func TestSyncPodUnhealthy(t *testing.T) {
523 523
 			Containers: []api.Container{
524 524
 				{Name: "bar",
525 525
 					LivenessProbe: &api.LivenessProbe{
526
-						// Always returns healthy == false
527
-						Type: "false",
526
+					// Always returns healthy == false
528 527
 					},
529 528
 				},
530 529
 			},
... ...
@@ -547,53 +602,6 @@ func TestSyncPodUnhealthy(t *testing.T) {
547 547
 	}
548 548
 }
549 549
 
550
-func TestEventWriting(t *testing.T) {
551
-	kubelet, fakeEtcd, _ := newTestKubelet(t)
552
-	expectedEvent := api.Event{
553
-		Event: "test",
554
-		Container: &api.Container{
555
-			Name: "foo",
556
-		},
557
-	}
558
-	err := kubelet.LogEvent(&expectedEvent)
559
-	if err != nil {
560
-		t.Errorf("unexpected error: %v", err)
561
-	}
562
-
563
-	if fakeEtcd.Ix != 1 {
564
-		t.Errorf("Unexpected number of children added: %d, expected 1", fakeEtcd.Ix)
565
-	}
566
-	response, err := fakeEtcd.Get("/events/foo/1", false, false)
567
-	if err != nil {
568
-		t.Errorf("unexpected error: %v", err)
569
-	}
570
-
571
-	var event api.Event
572
-	err = json.Unmarshal([]byte(response.Node.Value), &event)
573
-	if err != nil {
574
-		t.Errorf("unexpected error: %v", err)
575
-	}
576
-
577
-	if event.Event != expectedEvent.Event ||
578
-		event.Container.Name != expectedEvent.Container.Name {
579
-		t.Errorf("Event's don't match.  Expected: %#v Saw: %#v", expectedEvent, event)
580
-	}
581
-}
582
-
583
-func TestEventWritingError(t *testing.T) {
584
-	kubelet, fakeEtcd, _ := newTestKubelet(t)
585
-	fakeEtcd.Err = fmt.Errorf("test error")
586
-	err := kubelet.LogEvent(&api.Event{
587
-		Event: "test",
588
-		Container: &api.Container{
589
-			Name: "foo",
590
-		},
591
-	})
592
-	if err == nil {
593
-		t.Errorf("Unexpected non-error")
594
-	}
595
-}
596
-
597 550
 func TestMakeEnvVariables(t *testing.T) {
598 551
 	container := api.Container{
599 552
 		Env: []api.EnvVar{
... ...
@@ -626,14 +634,14 @@ func TestMountExternalVolumes(t *testing.T) {
626 626
 			{
627 627
 				Name: "host-dir",
628 628
 				Source: &api.VolumeSource{
629
-					HostDirectory: &api.HostDirectory{"/dir/path"},
629
+					HostDir: &api.HostDir{"/dir/path"},
630 630
 				},
631 631
 			},
632 632
 		},
633 633
 	}
634 634
 	podVolumes, _ := kubelet.mountExternalVolumes(&manifest)
635 635
 	expectedPodVolumes := make(volumeMap)
636
-	expectedPodVolumes["host-dir"] = &volume.HostDirectory{"/dir/path"}
636
+	expectedPodVolumes["host-dir"] = &volume.HostDir{"/dir/path"}
637 637
 	if len(expectedPodVolumes) != len(podVolumes) {
638 638
 		t.Errorf("Unexpected volumes. Expected %#v got %#v.  Manifest was: %#v", expectedPodVolumes, podVolumes, manifest)
639 639
 	}
... ...
@@ -676,9 +684,9 @@ func TestMakeVolumesAndBinds(t *testing.T) {
676 676
 	}
677 677
 
678 678
 	podVolumes := volumeMap{
679
-		"disk":  &volume.HostDirectory{"/mnt/disk"},
680
-		"disk4": &volume.HostDirectory{"/mnt/host"},
681
-		"disk5": &volume.EmptyDirectory{"disk5", "podID", "/var/lib/kubelet"},
679
+		"disk":  &volume.HostDir{"/mnt/disk"},
680
+		"disk4": &volume.HostDir{"/mnt/host"},
681
+		"disk5": &volume.EmptyDir{"disk5", "podID", "/var/lib/kubelet"},
682 682
 	}
683 683
 
684 684
 	binds := makeBinds(&pod, &container, podVolumes)
... ...
@@ -824,7 +832,7 @@ func TestGetContainerInfo(t *testing.T) {
824 824
 			ID: containerID,
825 825
 			// pod id: qux
826 826
 			// container id: foo
827
-			Names: []string{"/k8s--foo--qux--1234"},
827
+			Names: []string{"/k8s_foo_qux_1234"},
828 828
 		},
829 829
 	}
830 830
 
... ...
@@ -874,7 +882,7 @@ func TestGetContainerInfoWithoutCadvisor(t *testing.T) {
874 874
 			ID: "foobar",
875 875
 			// pod id: qux
876 876
 			// container id: foo
877
-			Names: []string{"/k8s--foo--qux--uuid--1234"},
877
+			Names: []string{"/k8s_foo_qux_uuid_1234"},
878 878
 		},
879 879
 	}
880 880
 
... ...
@@ -903,7 +911,7 @@ func TestGetContainerInfoWhenCadvisorFailed(t *testing.T) {
903 903
 			ID: containerID,
904 904
 			// pod id: qux
905 905
 			// container id: foo
906
-			Names: []string{"/k8s--foo--qux--uuid--1234"},
906
+			Names: []string{"/k8s_foo_qux_uuid_1234"},
907 907
 		},
908 908
 	}
909 909
 
... ...
@@ -982,7 +990,7 @@ func TestRunInContainer(t *testing.T) {
982 982
 	fakeDocker.ContainerList = []docker.APIContainers{
983 983
 		{
984 984
 			ID:    containerID,
985
-			Names: []string{"/k8s--" + containerName + "--" + podName + "." + podNamespace + "--1234"},
985
+			Names: []string{"/k8s_" + containerName + "_" + podName + "." + podNamespace + "_1234"},
986 986
 		},
987 987
 	}
988 988
 
... ...
@@ -1016,7 +1024,7 @@ func TestRunHandlerExec(t *testing.T) {
1016 1016
 	fakeDocker.ContainerList = []docker.APIContainers{
1017 1017
 		{
1018 1018
 			ID:    containerID,
1019
-			Names: []string{"/k8s--" + containerName + "--" + podName + "." + podNamespace + "--1234"},
1019
+			Names: []string{"/k8s_" + containerName + "_" + podName + "." + podNamespace + "_1234"},
1020 1020
 		},
1021 1021
 	}
1022 1022
 
... ...
@@ -1120,7 +1128,7 @@ func TestSyncPodEventHandlerFails(t *testing.T) {
1120 1120
 	dockerContainers := dockertools.DockerContainers{
1121 1121
 		"9876": &docker.APIContainers{
1122 1122
 			// network container
1123
-			Names: []string{"/k8s--net--foo.test--"},
1123
+			Names: []string{"/k8s_net_foo.test_"},
1124 1124
 			ID:    "9876",
1125 1125
 		},
1126 1126
 	}
... ...
@@ -48,7 +48,7 @@ type Server struct {
48 48
 
49 49
 // ListenAndServeKubeletServer initializes a server to respond to HTTP network requests on the Kubelet.
50 50
 func ListenAndServeKubeletServer(host HostInterface, updates chan<- interface{}, address string, port uint) {
51
-	glog.Infof("Starting to listen on %s:%d", address, port)
51
+	glog.V(1).Infof("Starting to listen on %s:%d", address, port)
52 52
 	handler := NewServer(host, updates)
53 53
 	s := &http.Server{
54 54
 		Addr:           net.JoinHostPort(address, strconv.FormatUint(uint64(port), 10)),
... ...
@@ -172,7 +172,7 @@ func (s *Server) handleContainerLogs(w http.ResponseWriter, req *http.Request) {
172 172
 		http.Error(w, `{"message": "Missing container name."}`, http.StatusBadRequest)
173 173
 		return
174 174
 	}
175
-	
175
+
176 176
 	uriValues := u.Query()
177 177
 	follow, _ := strconv.ParseBool(uriValues.Get("follow"))
178 178
 	tail := uriValues.Get("tail")
... ...
@@ -20,6 +20,7 @@ import (
20 20
 	"bytes"
21 21
 	"encoding/json"
22 22
 	"fmt"
23
+	"io"
23 24
 	"io/ioutil"
24 25
 	"net/http"
25 26
 	"net/http/httptest"
... ...
@@ -27,7 +28,6 @@ import (
27 27
 	"reflect"
28 28
 	"strings"
29 29
 	"testing"
30
-	"io"
31 30
 
32 31
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
33 32
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
... ...
@@ -36,13 +36,13 @@ import (
36 36
 )
37 37
 
38 38
 type fakeKubelet struct {
39
-	infoFunc           func(name string) (api.PodInfo, error)
40
-	containerInfoFunc  func(podFullName, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error)
41
-	rootInfoFunc       func(query *info.ContainerInfoRequest) (*info.ContainerInfo, error)
42
-	machineInfoFunc    func() (*info.MachineInfo, error)
43
-	logFunc            func(w http.ResponseWriter, req *http.Request)
44
-	runFunc            func(podFullName, uuid, containerName string, cmd []string) ([]byte, error)
45
-	containerLogsFunc  func(podFullName, containerName, tail string, follow bool, stdout, stderr io.Writer)  error
39
+	infoFunc          func(name string) (api.PodInfo, error)
40
+	containerInfoFunc func(podFullName, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error)
41
+	rootInfoFunc      func(query *info.ContainerInfoRequest) (*info.ContainerInfo, error)
42
+	machineInfoFunc   func() (*info.MachineInfo, error)
43
+	logFunc           func(w http.ResponseWriter, req *http.Request)
44
+	runFunc           func(podFullName, uuid, containerName string, cmd []string) ([]byte, error)
45
+	containerLogsFunc func(podFullName, containerName, tail string, follow bool, stdout, stderr io.Writer) error
46 46
 }
47 47
 
48 48
 func (fk *fakeKubelet) GetPodInfo(name, uuid string) (api.PodInfo, error) {
... ...
@@ -146,7 +146,11 @@ func TestContainers(t *testing.T) {
146 146
 
147 147
 func TestPodInfo(t *testing.T) {
148 148
 	fw := newServerTest()
149
-	expected := api.PodInfo{"goodpod": docker.Container{ID: "myContainerID"}}
149
+	expected := api.PodInfo{
150
+		"goodpod": api.ContainerStatus{
151
+			DetailInfo: docker.Container{ID: "myContainerID"},
152
+		},
153
+	}
150 154
 	fw.fakeKubelet.infoFunc = func(name string) (api.PodInfo, error) {
151 155
 		if name == "goodpod.etcd" {
152 156
 			return expected, nil
... ...
@@ -355,119 +359,118 @@ func TestServeRunInContainerWithUUID(t *testing.T) {
355 355
 }
356 356
 
357 357
 func TestContainerLogs(t *testing.T) {
358
-    fw := newServerTest()
359
-    output := "foo bar"
360
-    podName := "foo"
361
-    expectedPodName := podName + ".etcd"
362
-    expectedContainerName := "baz"
363
-    expectedTail := ""
364
-    expectedFollow := false
365
-    // expected := api.Container{"goodpod": docker.Container{ID: "myContainerID"}}
366
-    fw.fakeKubelet.containerLogsFunc = func(podFullName, containerName, tail string, follow bool, stdout, stderr io.Writer) error {
367
-            if podFullName != expectedPodName {
368
-                    t.Errorf("expected %s, got %s", expectedPodName, podFullName)
369
-            }
370
-			if containerName != expectedContainerName {
371
-				t.Errorf("expected %s, got %s", expectedContainerName, containerName)
372
-			}
373
-            if tail != expectedTail {
374
-                    t.Errorf("expected %s, got %s", expectedTail, tail)
375
-            }
376
-            if follow != expectedFollow {
377
-                    t.Errorf("expected %t, got %t", expectedFollow, follow)
378
-            }
379
-            return nil
380
-    }
381
-    resp, err := http.Get(fw.testHTTPServer.URL+"/containerLogs/" + podName + "/" + expectedContainerName)
382
-    if err != nil {
383
-            t.Errorf("Got error GETing: %v", err)
384
-    }
385
-    defer resp.Body.Close()
386
-
387
-    body, err := ioutil.ReadAll(resp.Body)
388
-    if err != nil {
389
-            t.Errorf("Error reading container logs: %v", err)
390
-    }
391
-    result := string(body)
392
-    if result != string(body) {
393
-            t.Errorf("Expected: '%v', got: '%v'", output, result)
394
-    }
358
+	fw := newServerTest()
359
+	output := "foo bar"
360
+	podName := "foo"
361
+	expectedPodName := podName + ".etcd"
362
+	expectedContainerName := "baz"
363
+	expectedTail := ""
364
+	expectedFollow := false
365
+	fw.fakeKubelet.containerLogsFunc = func(podFullName, containerName, tail string, follow bool, stdout, stderr io.Writer) error {
366
+		if podFullName != expectedPodName {
367
+			t.Errorf("expected %s, got %s", expectedPodName, podFullName)
368
+		}
369
+		if containerName != expectedContainerName {
370
+			t.Errorf("expected %s, got %s", expectedContainerName, containerName)
371
+		}
372
+		if tail != expectedTail {
373
+			t.Errorf("expected %s, got %s", expectedTail, tail)
374
+		}
375
+		if follow != expectedFollow {
376
+			t.Errorf("expected %t, got %t", expectedFollow, follow)
377
+		}
378
+		return nil
379
+	}
380
+	resp, err := http.Get(fw.testHTTPServer.URL + "/containerLogs/" + podName + "/" + expectedContainerName)
381
+	if err != nil {
382
+		t.Errorf("Got error GETing: %v", err)
383
+	}
384
+	defer resp.Body.Close()
385
+
386
+	body, err := ioutil.ReadAll(resp.Body)
387
+	if err != nil {
388
+		t.Errorf("Error reading container logs: %v", err)
389
+	}
390
+	result := string(body)
391
+	if result != string(body) {
392
+		t.Errorf("Expected: '%v', got: '%v'", output, result)
393
+	}
395 394
 }
396 395
 
397 396
 func TestContainerLogsWithTail(t *testing.T) {
398
-    fw := newServerTest()
399
-    output := "foo bar"
400
-    podName := "foo"
401
-    expectedPodName := podName + ".etcd"
402
-    expectedContainerName := "baz"
403
-    expectedTail := "5"
404
-    expectedFollow := false
405
-    fw.fakeKubelet.containerLogsFunc = func(podFullName, containerName, tail string, follow bool, stdout, stderr io.Writer) error {
406
-            if podFullName != expectedPodName {
407
-                    t.Errorf("expected %s, got %s", expectedPodName, podFullName)
408
-            }
409
-			if containerName != expectedContainerName {
410
-				t.Errorf("expected %s, got %s", expectedContainerName, containerName)
411
-			}
412
-            if tail != expectedTail {
413
-                    t.Errorf("expected %s, got %s", expectedTail, tail)
414
-            }
415
-            if follow != expectedFollow {
416
-                    t.Errorf("expected %t, got %t", expectedFollow, follow)
417
-            }
418
-            return nil
419
-    }
420
-    resp, err := http.Get(fw.testHTTPServer.URL+"/containerLogs/" + podName + "/" + expectedContainerName + "?tail=5")
421
-    if err != nil {
422
-            t.Errorf("Got error GETing: %v", err)
423
-    }
424
-    defer resp.Body.Close()
425
-
426
-    body, err := ioutil.ReadAll(resp.Body)
427
-    if err != nil {
428
-            t.Errorf("Error reading container logs: %v", err)
429
-    }
430
-    result := string(body)
431
-    if result != string(body) {
432
-            t.Errorf("Expected: '%v', got: '%v'", output, result)
433
-    }
397
+	fw := newServerTest()
398
+	output := "foo bar"
399
+	podName := "foo"
400
+	expectedPodName := podName + ".etcd"
401
+	expectedContainerName := "baz"
402
+	expectedTail := "5"
403
+	expectedFollow := false
404
+	fw.fakeKubelet.containerLogsFunc = func(podFullName, containerName, tail string, follow bool, stdout, stderr io.Writer) error {
405
+		if podFullName != expectedPodName {
406
+			t.Errorf("expected %s, got %s", expectedPodName, podFullName)
407
+		}
408
+		if containerName != expectedContainerName {
409
+			t.Errorf("expected %s, got %s", expectedContainerName, containerName)
410
+		}
411
+		if tail != expectedTail {
412
+			t.Errorf("expected %s, got %s", expectedTail, tail)
413
+		}
414
+		if follow != expectedFollow {
415
+			t.Errorf("expected %t, got %t", expectedFollow, follow)
416
+		}
417
+		return nil
418
+	}
419
+	resp, err := http.Get(fw.testHTTPServer.URL + "/containerLogs/" + podName + "/" + expectedContainerName + "?tail=5")
420
+	if err != nil {
421
+		t.Errorf("Got error GETing: %v", err)
422
+	}
423
+	defer resp.Body.Close()
424
+
425
+	body, err := ioutil.ReadAll(resp.Body)
426
+	if err != nil {
427
+		t.Errorf("Error reading container logs: %v", err)
428
+	}
429
+	result := string(body)
430
+	if result != string(body) {
431
+		t.Errorf("Expected: '%v', got: '%v'", output, result)
432
+	}
434 433
 }
435 434
 
436 435
 func TestContainerLogsWithFollow(t *testing.T) {
437
-    fw := newServerTest()
438
-    output := "foo bar"
439
-    podName := "foo"
440
-    expectedPodName := podName + ".etcd"
441
-    expectedContainerName := "baz"
442
-    expectedTail := ""
443
-    expectedFollow := true
444
-    fw.fakeKubelet.containerLogsFunc = func(podFullName, containerName, tail string, follow bool, stdout, stderr io.Writer) error {
445
-            if podFullName != expectedPodName {
446
-                    t.Errorf("expected %s, got %s", expectedPodName, podFullName)
447
-            }
448
-			if containerName != expectedContainerName {
449
-				t.Errorf("expected %s, got %s", expectedContainerName, containerName)
450
-			}
451
-            if tail != expectedTail {
452
-                    t.Errorf("expected %s, got %s", expectedTail, tail)
453
-            }
454
-            if follow != expectedFollow {
455
-                    t.Errorf("expected %t, got %t", expectedFollow, follow)
456
-            }
457
-            return nil
458
-    }
459
-    resp, err := http.Get(fw.testHTTPServer.URL+"/containerLogs/" + podName + "/" + expectedContainerName + "?follow=1")
460
-    if err != nil {
461
-            t.Errorf("Got error GETing: %v", err)
462
-    }
463
-    defer resp.Body.Close()
464
-
465
-    body, err := ioutil.ReadAll(resp.Body)
466
-    if err != nil {
467
-            t.Errorf("Error reading container logs: %v", err)
468
-    }
469
-    result := string(body)
470
-    if result != string(body) {
471
-            t.Errorf("Expected: '%v', got: '%v'", output, result)
472
-    }
436
+	fw := newServerTest()
437
+	output := "foo bar"
438
+	podName := "foo"
439
+	expectedPodName := podName + ".etcd"
440
+	expectedContainerName := "baz"
441
+	expectedTail := ""
442
+	expectedFollow := true
443
+	fw.fakeKubelet.containerLogsFunc = func(podFullName, containerName, tail string, follow bool, stdout, stderr io.Writer) error {
444
+		if podFullName != expectedPodName {
445
+			t.Errorf("expected %s, got %s", expectedPodName, podFullName)
446
+		}
447
+		if containerName != expectedContainerName {
448
+			t.Errorf("expected %s, got %s", expectedContainerName, containerName)
449
+		}
450
+		if tail != expectedTail {
451
+			t.Errorf("expected %s, got %s", expectedTail, tail)
452
+		}
453
+		if follow != expectedFollow {
454
+			t.Errorf("expected %t, got %t", expectedFollow, follow)
455
+		}
456
+		return nil
457
+	}
458
+	resp, err := http.Get(fw.testHTTPServer.URL + "/containerLogs/" + podName + "/" + expectedContainerName + "?follow=1")
459
+	if err != nil {
460
+		t.Errorf("Got error GETing: %v", err)
461
+	}
462
+	defer resp.Body.Close()
463
+
464
+	body, err := ioutil.ReadAll(resp.Body)
465
+	if err != nil {
466
+		t.Errorf("Error reading container logs: %v", err)
467
+	}
468
+	result := string(body)
469
+	if result != string(body) {
470
+		t.Errorf("Expected: '%v', got: '%v'", output, result)
471
+	}
473 472
 }
... ...
@@ -33,6 +33,7 @@ func TestValidatePodNoName(t *testing.T) {
33 33
 		"zero-length name":         {Name: "", Manifest: api.ContainerManifest{Version: "v1beta1"}},
34 34
 		"name > 255 characters":    {Name: strings.Repeat("a", 256), Manifest: api.ContainerManifest{Version: "v1beta1"}},
35 35
 		"name not a DNS subdomain": {Name: "a.b.c.", Manifest: api.ContainerManifest{Version: "v1beta1"}},
36
+		"name with underscore":     {Name: "a_b_c", Manifest: api.ContainerManifest{Version: "v1beta1"}},
36 37
 	}
37 38
 	for k, v := range errorCases {
38 39
 		if errs := ValidatePod(&v); len(errs) == 0 {
... ...
@@ -73,22 +73,26 @@ func NewEtcdHelper(etcdServers []string, version string) (helper tools.EtcdHelpe
73 73
 	if version == "" {
74 74
 		version = latest.Version
75 75
 	}
76
-	codec, versioner, err := latest.InterfacesFor(version)
76
+	versionInterfaces, err := latest.InterfacesFor(version)
77 77
 	if err != nil {
78 78
 		return helper, err
79 79
 	}
80
-	return tools.EtcdHelper{client, codec, versioner}, nil
80
+	return tools.EtcdHelper{client, versionInterfaces.Codec, versionInterfaces.ResourceVersioner}, nil
81 81
 }
82 82
 
83 83
 // New returns a new instance of Master connected to the given etcd server.
84 84
 func New(c *Config) *Master {
85 85
 	minionRegistry := makeMinionRegistry(c)
86
+	serviceRegistry := etcd.NewRegistry(c.EtcdHelper, nil)
87
+	manifestFactory := &pod.BasicManifestFactory{
88
+		ServiceRegistry: serviceRegistry,
89
+	}
86 90
 	m := &Master{
87
-		podRegistry:        etcd.NewRegistry(c.EtcdHelper),
88
-		controllerRegistry: etcd.NewRegistry(c.EtcdHelper),
89
-		serviceRegistry:    etcd.NewRegistry(c.EtcdHelper),
90
-		endpointRegistry:   etcd.NewRegistry(c.EtcdHelper),
91
-		bindingRegistry:    etcd.NewRegistry(c.EtcdHelper),
91
+		podRegistry:        etcd.NewRegistry(c.EtcdHelper, manifestFactory),
92
+		controllerRegistry: etcd.NewRegistry(c.EtcdHelper, nil),
93
+		serviceRegistry:    serviceRegistry,
94
+		endpointRegistry:   etcd.NewRegistry(c.EtcdHelper, nil),
95
+		bindingRegistry:    etcd.NewRegistry(c.EtcdHelper, manifestFactory),
92 96
 		minionRegistry:     minionRegistry,
93 97
 		client:             c.Client,
94 98
 	}
... ...
@@ -148,19 +152,19 @@ func (m *Master) init(cloud cloudprovider.Interface, podInfoGetter client.PodInf
148 148
 }
149 149
 
150 150
 // API_v1beta1 returns the resources and codec for API version v1beta1.
151
-func (m *Master) API_v1beta1() (map[string]apiserver.RESTStorage, runtime.Codec) {
151
+func (m *Master) API_v1beta1() (map[string]apiserver.RESTStorage, runtime.Codec, string, runtime.SelfLinker) {
152 152
 	storage := make(map[string]apiserver.RESTStorage)
153 153
 	for k, v := range m.storage {
154 154
 		storage[k] = v
155 155
 	}
156
-	return storage, v1beta1.Codec
156
+	return storage, v1beta1.Codec, "/api/v1beta1", latest.SelfLinker
157 157
 }
158 158
 
159 159
 // API_v1beta2 returns the resources and codec for API version v1beta2.
160
-func (m *Master) API_v1beta2() (map[string]apiserver.RESTStorage, runtime.Codec) {
160
+func (m *Master) API_v1beta2() (map[string]apiserver.RESTStorage, runtime.Codec, string, runtime.SelfLinker) {
161 161
 	storage := make(map[string]apiserver.RESTStorage)
162 162
 	for k, v := range m.storage {
163 163
 		storage[k] = v
164 164
 	}
165
-	return storage, v1beta2.Codec
165
+	return storage, v1beta2.Codec, "/api/v1beta1", latest.SelfLinker
166 166
 }
... ...
@@ -72,7 +72,8 @@ func (p *PodCache) updatePodInfo(host, id string) error {
72 72
 
73 73
 // UpdateAllContainers updates information about all containers.  Either called by Loop() below, or one-off.
74 74
 func (p *PodCache) UpdateAllContainers() {
75
-	pods, err := p.pods.ListPods(labels.Everything())
75
+	var ctx api.Context
76
+	pods, err := p.pods.ListPods(ctx, labels.Everything())
76 77
 	if err != nil {
77 78
 		glog.Errorf("Error synchronizing container list: %v", err)
78 79
 		return
... ...
@@ -41,7 +41,11 @@ func (f *FakePodInfoGetter) GetPodInfo(host, id string) (api.PodInfo, error) {
41 41
 func TestPodCacheGet(t *testing.T) {
42 42
 	cache := NewPodCache(nil, nil)
43 43
 
44
-	expected := api.PodInfo{"foo": docker.Container{ID: "foo"}}
44
+	expected := api.PodInfo{
45
+		"foo": api.ContainerStatus{
46
+			DetailInfo: docker.Container{ID: "foo"},
47
+		},
48
+	}
45 49
 	cache.podInfo["foo"] = expected
46 50
 
47 51
 	info, err := cache.GetPodInfo("host", "foo")
... ...
@@ -66,7 +70,11 @@ func TestPodCacheGetMissing(t *testing.T) {
66 66
 }
67 67
 
68 68
 func TestPodGetPodInfoGetter(t *testing.T) {
69
-	expected := api.PodInfo{"foo": docker.Container{ID: "foo"}}
69
+	expected := api.PodInfo{
70
+		"foo": api.ContainerStatus{
71
+			DetailInfo: docker.Container{ID: "foo"},
72
+		},
73
+	}
70 74
 	fake := FakePodInfoGetter{
71 75
 		data: expected,
72 76
 	}
... ...
@@ -98,7 +106,11 @@ func TestPodUpdateAllContainers(t *testing.T) {
98 98
 	pods := []api.Pod{pod}
99 99
 	mockRegistry := registrytest.NewPodRegistry(&api.PodList{Items: pods})
100 100
 
101
-	expected := api.PodInfo{"foo": docker.Container{ID: "foo"}}
101
+	expected := api.PodInfo{
102
+		"foo": api.ContainerStatus{
103
+			DetailInfo: docker.Container{ID: "foo"},
104
+		},
105
+	}
102 106
 	fake := FakePodInfoGetter{
103 107
 		data: expected,
104 108
 	}
... ...
@@ -29,10 +29,10 @@ import (
29 29
 
30 30
 // Watcher is the interface needed to receive changes to services and endpoints.
31 31
 type Watcher interface {
32
-	ListServices(label labels.Selector) (*api.ServiceList, error)
33
-	ListEndpoints(label labels.Selector) (*api.EndpointsList, error)
34
-	WatchServices(label, field labels.Selector, resourceVersion uint64) (watch.Interface, error)
35
-	WatchEndpoints(label, field labels.Selector, resourceVersion uint64) (watch.Interface, error)
32
+	ListServices(ctx api.Context, label labels.Selector) (*api.ServiceList, error)
33
+	ListEndpoints(ctx api.Context, label labels.Selector) (*api.EndpointsList, error)
34
+	WatchServices(ctx api.Context, label, field labels.Selector, resourceVersion uint64) (watch.Interface, error)
35
+	WatchEndpoints(ctx api.Context, label, field labels.Selector, resourceVersion uint64) (watch.Interface, error)
36 36
 }
37 37
 
38 38
 // SourceAPI implements a configuration source for services and endpoints that
... ...
@@ -72,8 +72,9 @@ func NewSourceAPI(client Watcher, period time.Duration, services chan<- ServiceU
72 72
 
73 73
 // runServices loops forever looking for changes to services.
74 74
 func (s *SourceAPI) runServices(resourceVersion *uint64) {
75
+	ctx := api.NewContext()
75 76
 	if *resourceVersion == 0 {
76
-		services, err := s.client.ListServices(labels.Everything())
77
+		services, err := s.client.ListServices(ctx, labels.Everything())
77 78
 		if err != nil {
78 79
 			glog.Errorf("Unable to load services: %v", err)
79 80
 			time.Sleep(wait.Jitter(s.waitDuration, 0.0))
... ...
@@ -83,7 +84,7 @@ func (s *SourceAPI) runServices(resourceVersion *uint64) {
83 83
 		s.services <- ServiceUpdate{Op: SET, Services: services.Items}
84 84
 	}
85 85
 
86
-	watcher, err := s.client.WatchServices(labels.Everything(), labels.Everything(), *resourceVersion)
86
+	watcher, err := s.client.WatchServices(ctx, labels.Everything(), labels.Everything(), *resourceVersion)
87 87
 	if err != nil {
88 88
 		glog.Errorf("Unable to watch for services changes: %v", err)
89 89
 		time.Sleep(wait.Jitter(s.waitDuration, 0.0))
... ...
@@ -101,7 +102,7 @@ func handleServicesWatch(resourceVersion *uint64, ch <-chan watch.Event, updates
101 101
 		select {
102 102
 		case event, ok := <-ch:
103 103
 			if !ok {
104
-				glog.V(2).Infof("WatchServices channel closed")
104
+				glog.V(4).Infof("WatchServices channel closed")
105 105
 				return
106 106
 			}
107 107
 
... ...
@@ -121,8 +122,9 @@ func handleServicesWatch(resourceVersion *uint64, ch <-chan watch.Event, updates
121 121
 
122 122
 // runEndpoints loops forever looking for changes to endpoints.
123 123
 func (s *SourceAPI) runEndpoints(resourceVersion *uint64) {
124
+	ctx := api.NewContext()
124 125
 	if *resourceVersion == 0 {
125
-		endpoints, err := s.client.ListEndpoints(labels.Everything())
126
+		endpoints, err := s.client.ListEndpoints(ctx, labels.Everything())
126 127
 		if err != nil {
127 128
 			glog.Errorf("Unable to load endpoints: %v", err)
128 129
 			time.Sleep(wait.Jitter(s.waitDuration, 0.0))
... ...
@@ -132,7 +134,7 @@ func (s *SourceAPI) runEndpoints(resourceVersion *uint64) {
132 132
 		s.endpoints <- EndpointsUpdate{Op: SET, Endpoints: endpoints.Items}
133 133
 	}
134 134
 
135
-	watcher, err := s.client.WatchEndpoints(labels.Everything(), labels.Everything(), *resourceVersion)
135
+	watcher, err := s.client.WatchEndpoints(ctx, labels.Everything(), labels.Everything(), *resourceVersion)
136 136
 	if err != nil {
137 137
 		glog.Errorf("Unable to watch for endpoints changes: %v", err)
138 138
 		time.Sleep(wait.Jitter(s.waitDuration, 0.0))
... ...
@@ -150,7 +152,7 @@ func handleEndpointsWatch(resourceVersion *uint64, ch <-chan watch.Event, update
150 150
 		select {
151 151
 		case event, ok := <-ch:
152 152
 			if !ok {
153
-				glog.V(2).Infof("WatchEndpoints channel closed")
153
+				glog.V(4).Infof("WatchEndpoints channel closed")
154 154
 				return
155 155
 			}
156 156
 
... ...
@@ -125,24 +125,24 @@ func (s *endpointsStore) Merge(source string, change interface{}) error {
125 125
 	update := change.(EndpointsUpdate)
126 126
 	switch update.Op {
127 127
 	case ADD:
128
-		glog.Infof("Adding new endpoint from source %s : %v", source, update.Endpoints)
128
+		glog.V(4).Infof("Adding new endpoint from source %s : %v", source, update.Endpoints)
129 129
 		for _, value := range update.Endpoints {
130 130
 			endpoints[value.ID] = value
131 131
 		}
132 132
 	case REMOVE:
133
-		glog.Infof("Removing an endpoint %v", update)
133
+		glog.V(4).Infof("Removing an endpoint %v", update)
134 134
 		for _, value := range update.Endpoints {
135 135
 			delete(endpoints, value.ID)
136 136
 		}
137 137
 	case SET:
138
-		glog.Infof("Setting endpoints %v", update)
138
+		glog.V(4).Infof("Setting endpoints %v", update)
139 139
 		// Clear the old map entries by just creating a new map
140 140
 		endpoints = make(map[string]api.Endpoints)
141 141
 		for _, value := range update.Endpoints {
142 142
 			endpoints[value.ID] = value
143 143
 		}
144 144
 	default:
145
-		glog.Infof("Received invalid update type: %v", update)
145
+		glog.V(4).Infof("Received invalid update type: %v", update)
146 146
 	}
147 147
 	s.endpoints[source] = endpoints
148 148
 	s.endpointLock.Unlock()
... ...
@@ -220,24 +220,24 @@ func (s *serviceStore) Merge(source string, change interface{}) error {
220 220
 	update := change.(ServiceUpdate)
221 221
 	switch update.Op {
222 222
 	case ADD:
223
-		glog.Infof("Adding new service from source %s : %v", source, update.Services)
223
+		glog.V(4).Infof("Adding new service from source %s : %v", source, update.Services)
224 224
 		for _, value := range update.Services {
225 225
 			services[value.ID] = value
226 226
 		}
227 227
 	case REMOVE:
228
-		glog.Infof("Removing a service %v", update)
228
+		glog.V(4).Infof("Removing a service %v", update)
229 229
 		for _, value := range update.Services {
230 230
 			delete(services, value.ID)
231 231
 		}
232 232
 	case SET:
233
-		glog.Infof("Setting services %v", update)
233
+		glog.V(4).Infof("Setting services %v", update)
234 234
 		// Clear the old map entries by just creating a new map
235 235
 		services = make(map[string]api.Service)
236 236
 		for _, value := range update.Services {
237 237
 			services[value.ID] = value
238 238
 		}
239 239
 	default:
240
-		glog.Infof("Received invalid update type: %v", update)
240
+		glog.V(4).Infof("Received invalid update type: %v", update)
241 241
 	}
242 242
 	s.services[source] = services
243 243
 	s.serviceLock.Unlock()
... ...
@@ -121,7 +121,7 @@ func (s ConfigSourceEtcd) GetServices() ([]api.Service, []api.Endpoints, error)
121 121
 	response, err := s.client.Get(registryRoot+"/specs", true, false)
122 122
 	if err != nil {
123 123
 		if tools.IsEtcdNotFound(err) {
124
-			glog.V(1).Infof("Failed to get the key %s: %v", registryRoot, err)
124
+			glog.V(4).Infof("Failed to get the key %s: %v", registryRoot, err)
125 125
 		} else {
126 126
 			glog.Errorf("Failed to contact etcd for key %s: %v", registryRoot, err)
127 127
 		}
... ...
@@ -144,12 +144,12 @@ func (s ConfigSourceEtcd) GetServices() ([]api.Service, []api.Endpoints, error)
144 144
 			endpoints, err := s.GetEndpoints(svc.ID)
145 145
 			if err != nil {
146 146
 				if tools.IsEtcdNotFound(err) {
147
-					glog.V(1).Infof("Unable to get endpoints for %s : %v", svc.ID, err)
147
+					glog.V(4).Infof("Unable to get endpoints for %s : %v", svc.ID, err)
148 148
 				}
149 149
 				glog.Errorf("Couldn't get endpoints for %s : %v skipping", svc.ID, err)
150 150
 				endpoints = api.Endpoints{}
151 151
 			} else {
152
-				glog.Infof("Got service: %s on localport %d mapping to: %s", svc.ID, svc.Port, endpoints)
152
+				glog.V(3).Infof("Got service: %s on localport %d mapping to: %s", svc.ID, svc.Port, endpoints)
153 153
 			}
154 154
 			retEndpoints[i] = endpoints
155 155
 		}
... ...
@@ -186,7 +186,7 @@ func etcdResponseToService(response *etcd.Response) (*api.Service, error) {
186 186
 }
187 187
 
188 188
 func (s ConfigSourceEtcd) WatchForChanges() {
189
-	glog.Info("Setting up a watch for new services")
189
+	glog.V(4).Info("Setting up a watch for new services")
190 190
 	watchChannel := make(chan *etcd.Response)
191 191
 	go s.client.Watch("/registry/services/", 0, true, watchChannel, nil)
192 192
 	for {
... ...
@@ -199,7 +199,7 @@ func (s ConfigSourceEtcd) WatchForChanges() {
199 199
 }
200 200
 
201 201
 func (s ConfigSourceEtcd) ProcessChange(response *etcd.Response) {
202
-	glog.Infof("Processing a change in service configuration... %s", *response)
202
+	glog.V(4).Infof("Processing a change in service configuration... %s", *response)
203 203
 
204 204
 	// If it's a new service being added (signified by a localport being added)
205 205
 	// then process it as such
... ...
@@ -212,7 +212,7 @@ func (s ConfigSourceEtcd) ProcessChange(response *etcd.Response) {
212 212
 			return
213 213
 		}
214 214
 
215
-		glog.Infof("New service added/updated: %#v", service)
215
+		glog.V(4).Infof("New service added/updated: %#v", service)
216 216
 		serviceUpdate := ServiceUpdate{Op: ADD, Services: []api.Service{*service}}
217 217
 		s.serviceChannel <- serviceUpdate
218 218
 		return
... ...
@@ -220,17 +220,17 @@ func (s ConfigSourceEtcd) ProcessChange(response *etcd.Response) {
220 220
 	if response.Action == "delete" {
221 221
 		parts := strings.Split(response.Node.Key[1:], "/")
222 222
 		if len(parts) == 4 {
223
-			glog.Infof("Deleting service: %s", parts[3])
223
+			glog.V(4).Infof("Deleting service: %s", parts[3])
224 224
 			serviceUpdate := ServiceUpdate{Op: REMOVE, Services: []api.Service{{JSONBase: api.JSONBase{ID: parts[3]}}}}
225 225
 			s.serviceChannel <- serviceUpdate
226 226
 			return
227 227
 		}
228
-		glog.Infof("Unknown service delete: %#v", parts)
228
+		glog.Warningf("Unknown service delete: %#v", parts)
229 229
 	}
230 230
 }
231 231
 
232 232
 func (s ConfigSourceEtcd) ProcessEndpointResponse(response *etcd.Response) {
233
-	glog.Infof("Processing a change in endpoint configuration... %s", *response)
233
+	glog.V(4).Infof("Processing a change in endpoint configuration... %s", *response)
234 234
 	var endpoints api.Endpoints
235 235
 	err := latest.Codec.DecodeInto([]byte(response.Node.Value), &endpoints)
236 236
 	if err != nil {
... ...
@@ -72,7 +72,7 @@ func NewConfigSourceFile(filename string, serviceChannel chan ServiceUpdate, end
72 72
 
73 73
 // Run begins watching the config file.
74 74
 func (s ConfigSourceFile) Run() {
75
-	glog.Infof("Watching file %s", s.filename)
75
+	glog.V(1).Infof("Watching file %s", s.filename)
76 76
 	var lastData []byte
77 77
 	var lastServices []api.Service
78 78
 	var lastEndpoints []api.Endpoints
... ...
@@ -32,7 +32,7 @@ import (
32 32
 
33 33
 type serviceInfo struct {
34 34
 	port     int
35
-	protocol string
35
+	protocol api.Protocol
36 36
 	socket   proxySocket
37 37
 	timeout  time.Duration
38 38
 	mu       sync.Mutex // protects active
... ...
@@ -90,14 +90,14 @@ func (tcp *tcpProxySocket) ProxyLoop(service string, proxier *Proxier) {
90 90
 			glog.Errorf("Accept failed: %v", err)
91 91
 			continue
92 92
 		}
93
-		glog.Infof("Accepted TCP connection from %v to %v", inConn.RemoteAddr(), inConn.LocalAddr())
93
+		glog.V(2).Infof("Accepted TCP connection from %v to %v", inConn.RemoteAddr(), inConn.LocalAddr())
94 94
 		endpoint, err := proxier.loadBalancer.NextEndpoint(service, inConn.RemoteAddr())
95 95
 		if err != nil {
96 96
 			glog.Errorf("Couldn't find an endpoint for %s %v", service, err)
97 97
 			inConn.Close()
98 98
 			continue
99 99
 		}
100
-		glog.Infof("Mapped service %s to endpoint %s", service, endpoint)
100
+		glog.V(3).Infof("Mapped service %s to endpoint %s", service, endpoint)
101 101
 		// TODO: This could spin up a new goroutine to make the outbound connection,
102 102
 		// and keep accepting inbound traffic.
103 103
 		outConn, err := net.DialTimeout("tcp", endpoint, endpointDialTimeout)
... ...
@@ -116,7 +116,7 @@ func (tcp *tcpProxySocket) ProxyLoop(service string, proxier *Proxier) {
116 116
 func proxyTCP(in, out *net.TCPConn) {
117 117
 	var wg sync.WaitGroup
118 118
 	wg.Add(2)
119
-	glog.Infof("Creating proxy between %v <-> %v <-> %v <-> %v",
119
+	glog.V(4).Infof("Creating proxy between %v <-> %v <-> %v <-> %v",
120 120
 		in.RemoteAddr(), in.LocalAddr(), out.LocalAddr(), out.RemoteAddr())
121 121
 	go copyBytes(in, out, &wg)
122 122
 	go copyBytes(out, in, &wg)
... ...
@@ -127,7 +127,7 @@ func proxyTCP(in, out *net.TCPConn) {
127 127
 
128 128
 func copyBytes(in, out *net.TCPConn, wg *sync.WaitGroup) {
129 129
 	defer wg.Done()
130
-	glog.Infof("Copying from %v <-> %v <-> %v <-> %v",
130
+	glog.V(4).Infof("Copying from %v <-> %v <-> %v <-> %v",
131 131
 		in.RemoteAddr(), in.LocalAddr(), out.LocalAddr(), out.RemoteAddr())
132 132
 	if _, err := io.Copy(in, out); err != nil {
133 133
 		glog.Errorf("I/O error: %v", err)
... ...
@@ -176,7 +176,7 @@ func (udp *udpProxySocket) ProxyLoop(service string, proxier *Proxier) {
176 176
 		if err != nil {
177 177
 			if e, ok := err.(net.Error); ok {
178 178
 				if e.Temporary() {
179
-					glog.Infof("ReadFrom had a temporary failure: %v", err)
179
+					glog.V(1).Infof("ReadFrom had a temporary failure: %v", err)
180 180
 					continue
181 181
 				}
182 182
 			}
... ...
@@ -214,13 +214,13 @@ func (udp *udpProxySocket) getBackendConn(activeClients *clientCache, cliAddr ne
214 214
 	if !found {
215 215
 		// TODO: This could spin up a new goroutine to make the outbound connection,
216 216
 		// and keep accepting inbound traffic.
217
-		glog.Infof("New UDP connection from %s", cliAddr)
217
+		glog.V(2).Infof("New UDP connection from %s", cliAddr)
218 218
 		endpoint, err := proxier.loadBalancer.NextEndpoint(service, cliAddr)
219 219
 		if err != nil {
220 220
 			glog.Errorf("Couldn't find an endpoint for %s %v", service, err)
221 221
 			return nil, err
222 222
 		}
223
-		glog.Infof("Mapped service %s to endpoint %s", service, endpoint)
223
+		glog.V(4).Infof("Mapped service %s to endpoint %s", service, endpoint)
224 224
 		svrConn, err = net.DialTimeout("udp", endpoint, endpointDialTimeout)
225 225
 		if err != nil {
226 226
 			// TODO: Try another endpoint?
... ...
@@ -269,15 +269,15 @@ func (udp *udpProxySocket) proxyClient(cliAddr net.Addr, svrConn net.Conn, activ
269 269
 func logTimeout(err error) bool {
270 270
 	if e, ok := err.(net.Error); ok {
271 271
 		if e.Timeout() {
272
-			glog.Infof("connection to endpoint closed due to inactivity")
272
+			glog.V(1).Infof("connection to endpoint closed due to inactivity")
273 273
 			return true
274 274
 		}
275 275
 	}
276 276
 	return false
277 277
 }
278 278
 
279
-func newProxySocket(protocol string, host string, port int) (proxySocket, error) {
280
-	switch strings.ToUpper(protocol) {
279
+func newProxySocket(protocol api.Protocol, host string, port int) (proxySocket, error) {
280
+	switch strings.ToUpper(string(protocol)) {
281 281
 	case "TCP":
282 282
 		listener, err := net.Listen("tcp", net.JoinHostPort(host, strconv.Itoa(port)))
283 283
 		if err != nil {
... ...
@@ -329,7 +329,7 @@ func (proxier *Proxier) stopProxyInternal(service string, info *serviceInfo) err
329 329
 	if !info.setActive(false) {
330 330
 		return nil
331 331
 	}
332
-	glog.Infof("Removing service: %s", service)
332
+	glog.V(3).Infof("Removing service: %s", service)
333 333
 	delete(proxier.serviceMap, service)
334 334
 	return info.socket.Close()
335 335
 }
... ...
@@ -350,7 +350,7 @@ func (proxier *Proxier) setServiceInfo(service string, info *serviceInfo) {
350 350
 // addServiceOnUnusedPort starts listening for a new service, returning the
351 351
 // port it's using.  For testing on a system with unknown ports used.  The timeout only applies to UDP
352 352
 // connections, for now.
353
-func (proxier *Proxier) addServiceOnUnusedPort(service, protocol string, timeout time.Duration) (string, error) {
353
+func (proxier *Proxier) addServiceOnUnusedPort(service string, protocol api.Protocol, timeout time.Duration) (string, error) {
354 354
 	sock, err := newProxySocket(protocol, proxier.address, 0)
355 355
 	if err != nil {
356 356
 		return "", err
... ...
@@ -375,7 +375,7 @@ func (proxier *Proxier) addServiceOnUnusedPort(service, protocol string, timeout
375 375
 }
376 376
 
377 377
 func (proxier *Proxier) startAccepting(service string, sock proxySocket) {
378
-	glog.Infof("Listening for %s on %s:%s", service, sock.Addr().Network(), sock.Addr().String())
378
+	glog.V(1).Infof("Listening for %s on %s:%s", service, sock.Addr().Network(), sock.Addr().String())
379 379
 	go func(service string, proxier *Proxier) {
380 380
 		defer util.HandleCrash()
381 381
 		sock.ProxyLoop(service, proxier)
... ...
@@ -389,7 +389,7 @@ const udpIdleTimeout = 1 * time.Minute
389 389
 // Active service proxies are reinitialized if found in the update set or
390 390
 // shutdown if missing from the update set.
391 391
 func (proxier *Proxier) OnUpdate(services []api.Service) {
392
-	glog.Infof("Received update notice: %+v", services)
392
+	glog.V(4).Infof("Received update notice: %+v", services)
393 393
 	activeServices := util.StringSet{}
394 394
 	for _, service := range services {
395 395
 		activeServices.Insert(service.ID)
... ...
@@ -404,7 +404,7 @@ func (proxier *Proxier) OnUpdate(services []api.Service) {
404 404
 				glog.Errorf("error stopping %s: %v", service.ID, err)
405 405
 			}
406 406
 		}
407
-		glog.Infof("Adding a new service %s on %s port %d", service.ID, service.Protocol, service.Port)
407
+		glog.V(3).Infof("Adding a new service %s on %s port %d", service.ID, service.Protocol, service.Port)
408 408
 		sock, err := newProxySocket(service.Protocol, proxier.address, service.Port)
409 409
 		if err != nil {
410 410
 			glog.Errorf("Failed to get a socket for %s: %+v", service.ID, err)
... ...
@@ -101,7 +101,7 @@ func (lb *LoadBalancerRR) OnUpdate(endpoints []api.Endpoints) {
101 101
 		existingEndpoints, exists := lb.endpointsMap[endpoint.ID]
102 102
 		validEndpoints := filterValidEndpoints(endpoint.Endpoints)
103 103
 		if !exists || !reflect.DeepEqual(existingEndpoints, validEndpoints) {
104
-			glog.Infof("LoadBalancerRR: Setting endpoints for %s to %+v", endpoint.ID, endpoint.Endpoints)
104
+			glog.V(3).Infof("LoadBalancerRR: Setting endpoints for %s to %+v", endpoint.ID, endpoint.Endpoints)
105 105
 			lb.endpointsMap[endpoint.ID] = validEndpoints
106 106
 			// Reset the round-robin index.
107 107
 			lb.rrIndex[endpoint.ID] = 0
... ...
@@ -111,7 +111,7 @@ func (lb *LoadBalancerRR) OnUpdate(endpoints []api.Endpoints) {
111 111
 	// Remove endpoints missing from the update.
112 112
 	for k, v := range lb.endpointsMap {
113 113
 		if _, exists := registeredEndpoints[k]; !exists {
114
-			glog.Infof("LoadBalancerRR: Removing endpoints for %s -> %+v", k, v)
114
+			glog.V(3).Infof("LoadBalancerRR: Removing endpoints for %s -> %+v", k, v)
115 115
 			delete(lb.endpointsMap, k)
116 116
 		}
117 117
 	}
... ...
@@ -25,6 +25,6 @@ type MockRegistry struct {
25 25
 	OnApplyBinding func(binding *api.Binding) error
26 26
 }
27 27
 
28
-func (mr MockRegistry) ApplyBinding(binding *api.Binding) error {
28
+func (mr MockRegistry) ApplyBinding(ctx api.Context, binding *api.Binding) error {
29 29
 	return mr.OnApplyBinding(binding)
30 30
 }
... ...
@@ -24,5 +24,5 @@ import (
24 24
 type Registry interface {
25 25
 	// ApplyBinding should apply the binding. That is, it should actually
26 26
 	// assign or place pod binding.PodID on machine binding.Host.
27
-	ApplyBinding(binding *api.Binding) error
27
+	ApplyBinding(ctx api.Context, binding *api.Binding) error
28 28
 }
... ...
@@ -41,17 +41,17 @@ func NewREST(bindingRegistry Registry) *REST {
41 41
 }
42 42
 
43 43
 // List returns an error because bindings are write-only objects.
44
-func (*REST) List(label, field labels.Selector) (runtime.Object, error) {
44
+func (*REST) List(ctx api.Context, label, field labels.Selector) (runtime.Object, error) {
45 45
 	return nil, errors.NewNotFound("binding", "list")
46 46
 }
47 47
 
48 48
 // Get returns an error because bindings are write-only objects.
49
-func (*REST) Get(id string) (runtime.Object, error) {
49
+func (*REST) Get(ctx api.Context, id string) (runtime.Object, error) {
50 50
 	return nil, errors.NewNotFound("binding", id)
51 51
 }
52 52
 
53 53
 // Delete returns an error because bindings are write-only objects.
54
-func (*REST) Delete(id string) (<-chan runtime.Object, error) {
54
+func (*REST) Delete(ctx api.Context, id string) (<-chan runtime.Object, error) {
55 55
 	return nil, errors.NewNotFound("binding", id)
56 56
 }
57 57
 
... ...
@@ -61,13 +61,13 @@ func (*REST) New() runtime.Object {
61 61
 }
62 62
 
63 63
 // Create attempts to make the assignment indicated by the binding it recieves.
64
-func (b *REST) Create(obj runtime.Object) (<-chan runtime.Object, error) {
64
+func (b *REST) Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) {
65 65
 	binding, ok := obj.(*api.Binding)
66 66
 	if !ok {
67 67
 		return nil, fmt.Errorf("incorrect type: %#v", obj)
68 68
 	}
69 69
 	return apiserver.MakeAsync(func() (runtime.Object, error) {
70
-		if err := b.registry.ApplyBinding(binding); err != nil {
70
+		if err := b.registry.ApplyBinding(ctx, binding); err != nil {
71 71
 			return nil, err
72 72
 		}
73 73
 		return &api.Status{Status: api.StatusSuccess}, nil
... ...
@@ -75,6 +75,6 @@ func (b *REST) Create(obj runtime.Object) (<-chan runtime.Object, error) {
75 75
 }
76 76
 
77 77
 // Update returns an error-- this object may not be updated.
78
-func (b *REST) Update(obj runtime.Object) (<-chan runtime.Object, error) {
78
+func (b *REST) Update(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) {
79 79
 	return nil, fmt.Errorf("Bindings may not be changed.")
80 80
 }
... ...
@@ -52,24 +52,25 @@ func TestNewREST(t *testing.T) {
52 52
 }
53 53
 
54 54
 func TestRESTUnsupported(t *testing.T) {
55
+	var ctx api.Context
55 56
 	mockRegistry := MockRegistry{
56 57
 		OnApplyBinding: func(b *api.Binding) error { return nil },
57 58
 	}
58 59
 	b := NewREST(mockRegistry)
59
-	if _, err := b.Delete("binding id"); err == nil {
60
+	if _, err := b.Delete(ctx, "binding id"); err == nil {
60 61
 		t.Errorf("unexpected non-error")
61 62
 	}
62
-	if _, err := b.Update(&api.Binding{PodID: "foo", Host: "new machine"}); err == nil {
63
+	if _, err := b.Update(ctx, &api.Binding{PodID: "foo", Host: "new machine"}); err == nil {
63 64
 		t.Errorf("unexpected non-error")
64 65
 	}
65
-	if _, err := b.Get("binding id"); err == nil {
66
+	if _, err := b.Get(ctx, "binding id"); err == nil {
66 67
 		t.Errorf("unexpected non-error")
67 68
 	}
68
-	if _, err := b.List(labels.Set{"name": "foo"}.AsSelector(), labels.Everything()); err == nil {
69
+	if _, err := b.List(ctx, labels.Set{"name": "foo"}.AsSelector(), labels.Everything()); err == nil {
69 70
 		t.Errorf("unexpected non-error")
70 71
 	}
71 72
 	// Try sending wrong object just to get 100% coverage
72
-	if _, err := b.Create(&api.Pod{}); err == nil {
73
+	if _, err := b.Create(ctx, &api.Pod{}); err == nil {
73 74
 		t.Errorf("unexpected non-error")
74 75
 	}
75 76
 }
... ...
@@ -93,8 +94,9 @@ func TestRESTPost(t *testing.T) {
93 93
 				return item.err
94 94
 			},
95 95
 		}
96
+		ctx := api.NewContext()
96 97
 		b := NewREST(mockRegistry)
97
-		resultChan, err := b.Create(item.b)
98
+		resultChan, err := b.Create(ctx, item.b)
98 99
 		if err != nil {
99 100
 			t.Errorf("Unexpected error %v", err)
100 101
 			continue
... ...
@@ -23,10 +23,10 @@ import (
23 23
 
24 24
 // Registry is an interface for things that know how to store ReplicationControllers.
25 25
 type Registry interface {
26
-	ListControllers() (*api.ReplicationControllerList, error)
27
-	WatchControllers(resourceVersion uint64) (watch.Interface, error)
28
-	GetController(controllerID string) (*api.ReplicationController, error)
29
-	CreateController(controller *api.ReplicationController) error
30
-	UpdateController(controller *api.ReplicationController) error
31
-	DeleteController(controllerID string) error
26
+	ListControllers(ctx api.Context) (*api.ReplicationControllerList, error)
27
+	WatchControllers(ctx api.Context, resourceVersion uint64) (watch.Interface, error)
28
+	GetController(ctx api.Context, controllerID string) (*api.ReplicationController, error)
29
+	CreateController(ctx api.Context, controller *api.ReplicationController) error
30
+	UpdateController(ctx api.Context, controller *api.ReplicationController) error
31
+	DeleteController(ctx api.Context, controllerID string) error
32 32
 }
... ...
@@ -34,7 +34,7 @@ import (
34 34
 
35 35
 // PodLister is anything that knows how to list pods.
36 36
 type PodLister interface {
37
-	ListPods(labels.Selector) (*api.PodList, error)
37
+	ListPods(ctx api.Context, labels labels.Selector) (*api.PodList, error)
38 38
 }
39 39
 
40 40
 // REST implements apiserver.RESTStorage for the replication controller service.
... ...
@@ -54,11 +54,15 @@ func NewREST(registry Registry, podLister PodLister) *REST {
54 54
 }
55 55
 
56 56
 // Create registers the given ReplicationController.
57
-func (rs *REST) Create(obj runtime.Object) (<-chan runtime.Object, error) {
57
+func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) {
58 58
 	controller, ok := obj.(*api.ReplicationController)
59 59
 	if !ok {
60 60
 		return nil, fmt.Errorf("not a replication controller: %#v", obj)
61 61
 	}
62
+	if !api.ValidNamespace(ctx, &controller.JSONBase) {
63
+		return nil, errors.NewConflict("controller", controller.Namespace, fmt.Errorf("Controller.Namespace does not match the provided context"))
64
+	}
65
+
62 66
 	if len(controller.ID) == 0 {
63 67
 		controller.ID = uuid.NewUUID().String()
64 68
 	}
... ...
@@ -71,44 +75,44 @@ func (rs *REST) Create(obj runtime.Object) (<-chan runtime.Object, error) {
71 71
 	controller.CreationTimestamp = util.Now()
72 72
 
73 73
 	return apiserver.MakeAsync(func() (runtime.Object, error) {
74
-		err := rs.registry.CreateController(controller)
74
+		err := rs.registry.CreateController(ctx, controller)
75 75
 		if err != nil {
76 76
 			return nil, err
77 77
 		}
78
-		return rs.registry.GetController(controller.ID)
78
+		return rs.registry.GetController(ctx, controller.ID)
79 79
 	}), nil
80 80
 }
81 81
 
82 82
 // Delete asynchronously deletes the ReplicationController specified by its id.
83
-func (rs *REST) Delete(id string) (<-chan runtime.Object, error) {
83
+func (rs *REST) Delete(ctx api.Context, id string) (<-chan runtime.Object, error) {
84 84
 	return apiserver.MakeAsync(func() (runtime.Object, error) {
85
-		return &api.Status{Status: api.StatusSuccess}, rs.registry.DeleteController(id)
85
+		return &api.Status{Status: api.StatusSuccess}, rs.registry.DeleteController(ctx, id)
86 86
 	}), nil
87 87
 }
88 88
 
89 89
 // Get obtains the ReplicationController specified by its id.
90
-func (rs *REST) Get(id string) (runtime.Object, error) {
91
-	controller, err := rs.registry.GetController(id)
90
+func (rs *REST) Get(ctx api.Context, id string) (runtime.Object, error) {
91
+	controller, err := rs.registry.GetController(ctx, id)
92 92
 	if err != nil {
93 93
 		return nil, err
94 94
 	}
95
-	rs.fillCurrentState(controller)
95
+	rs.fillCurrentState(ctx, controller)
96 96
 	return controller, err
97 97
 }
98 98
 
99 99
 // List obtains a list of ReplicationControllers that match selector.
100
-func (rs *REST) List(label, field labels.Selector) (runtime.Object, error) {
100
+func (rs *REST) List(ctx api.Context, label, field labels.Selector) (runtime.Object, error) {
101 101
 	if !field.Empty() {
102 102
 		return nil, fmt.Errorf("field selector not supported yet")
103 103
 	}
104
-	controllers, err := rs.registry.ListControllers()
104
+	controllers, err := rs.registry.ListControllers(ctx)
105 105
 	if err != nil {
106 106
 		return nil, err
107 107
 	}
108 108
 	filtered := []api.ReplicationController{}
109 109
 	for _, controller := range controllers.Items {
110 110
 		if label.Matches(labels.Set(controller.Labels)) {
111
-			rs.fillCurrentState(&controller)
111
+			rs.fillCurrentState(ctx, &controller)
112 112
 			filtered = append(filtered, controller)
113 113
 		}
114 114
 	}
... ...
@@ -123,46 +127,55 @@ func (*REST) New() runtime.Object {
123 123
 
124 124
 // Update replaces a given ReplicationController instance with an existing
125 125
 // instance in storage.registry.
126
-func (rs *REST) Update(obj runtime.Object) (<-chan runtime.Object, error) {
126
+func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) {
127 127
 	controller, ok := obj.(*api.ReplicationController)
128 128
 	if !ok {
129 129
 		return nil, fmt.Errorf("not a replication controller: %#v", obj)
130 130
 	}
131
+	if !api.ValidNamespace(ctx, &controller.JSONBase) {
132
+		return nil, errors.NewConflict("controller", controller.Namespace, fmt.Errorf("Controller.Namespace does not match the provided context"))
133
+	}
131 134
 	if errs := validation.ValidateReplicationController(controller); len(errs) > 0 {
132 135
 		return nil, errors.NewInvalid("replicationController", controller.ID, errs)
133 136
 	}
134 137
 	return apiserver.MakeAsync(func() (runtime.Object, error) {
135
-		err := rs.registry.UpdateController(controller)
138
+		err := rs.registry.UpdateController(ctx, controller)
136 139
 		if err != nil {
137 140
 			return nil, err
138 141
 		}
139
-		return rs.registry.GetController(controller.ID)
142
+		return rs.registry.GetController(ctx, controller.ID)
140 143
 	}), nil
141 144
 }
142 145
 
143 146
 // Watch returns ReplicationController events via a watch.Interface.
144 147
 // It implements apiserver.ResourceWatcher.
145
-func (rs *REST) Watch(label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
148
+func (rs *REST) Watch(ctx api.Context, label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
146 149
 	if !field.Empty() {
147 150
 		return nil, fmt.Errorf("no field selector implemented for controllers")
148 151
 	}
149
-	incoming, err := rs.registry.WatchControllers(resourceVersion)
152
+	incoming, err := rs.registry.WatchControllers(ctx, resourceVersion)
150 153
 	if err != nil {
151 154
 		return nil, err
152 155
 	}
156
+	// TODO(lavalamp): remove watch.Filter, which is broken. Implement consistent way of filtering.
157
+	// TODO(lavalamp): this watch method needs a test.
153 158
 	return watch.Filter(incoming, func(e watch.Event) (watch.Event, bool) {
154
-		repController := e.Object.(*api.ReplicationController)
159
+		repController, ok := e.Object.(*api.ReplicationController)
160
+		if !ok {
161
+			// must be an error event-- pass it on
162
+			return e, true
163
+		}
155 164
 		match := label.Matches(labels.Set(repController.Labels))
156 165
 		if match {
157
-			rs.fillCurrentState(repController)
166
+			rs.fillCurrentState(ctx, repController)
158 167
 		}
159 168
 		return e, match
160 169
 	}), nil
161 170
 }
162 171
 
163
-func (rs *REST) waitForController(ctrl *api.ReplicationController) (runtime.Object, error) {
172
+func (rs *REST) waitForController(ctx api.Context, ctrl *api.ReplicationController) (runtime.Object, error) {
164 173
 	for {
165
-		pods, err := rs.podLister.ListPods(labels.Set(ctrl.DesiredState.ReplicaSelector).AsSelector())
174
+		pods, err := rs.podLister.ListPods(ctx, labels.Set(ctrl.DesiredState.ReplicaSelector).AsSelector())
166 175
 		if err != nil {
167 176
 			return ctrl, err
168 177
 		}
... ...
@@ -174,11 +187,11 @@ func (rs *REST) waitForController(ctrl *api.ReplicationController) (runtime.Obje
174 174
 	return ctrl, nil
175 175
 }
176 176
 
177
-func (rs *REST) fillCurrentState(ctrl *api.ReplicationController) error {
177
+func (rs *REST) fillCurrentState(ctx api.Context, ctrl *api.ReplicationController) error {
178 178
 	if rs.podLister == nil {
179 179
 		return nil
180 180
 	}
181
-	list, err := rs.podLister.ListPods(labels.Set(ctrl.DesiredState.ReplicaSelector).AsSelector())
181
+	list, err := rs.podLister.ListPods(ctx, labels.Set(ctrl.DesiredState.ReplicaSelector).AsSelector())
182 182
 	if err != nil {
183 183
 		return err
184 184
 	}
... ...
@@ -39,7 +39,8 @@ func TestListControllersError(t *testing.T) {
39 39
 	storage := REST{
40 40
 		registry: &mockRegistry,
41 41
 	}
42
-	controllers, err := storage.List(labels.Everything(), labels.Everything())
42
+	ctx := api.NewContext()
43
+	controllers, err := storage.List(ctx, labels.Everything(), labels.Everything())
43 44
 	if err != mockRegistry.Err {
44 45
 		t.Errorf("Expected %#v, Got %#v", mockRegistry.Err, err)
45 46
 	}
... ...
@@ -53,7 +54,8 @@ func TestListEmptyControllerList(t *testing.T) {
53 53
 	storage := REST{
54 54
 		registry: &mockRegistry,
55 55
 	}
56
-	controllers, err := storage.List(labels.Everything(), labels.Everything())
56
+	ctx := api.NewContext()
57
+	controllers, err := storage.List(ctx, labels.Everything(), labels.Everything())
57 58
 	if err != nil {
58 59
 		t.Errorf("unexpected error: %v", err)
59 60
 	}
... ...
@@ -86,7 +88,8 @@ func TestListControllerList(t *testing.T) {
86 86
 	storage := REST{
87 87
 		registry: &mockRegistry,
88 88
 	}
89
-	controllersObj, err := storage.List(labels.Everything(), labels.Everything())
89
+	ctx := api.NewContext()
90
+	controllersObj, err := storage.List(ctx, labels.Everything(), labels.Everything())
90 91
 	controllers := controllersObj.(*api.ReplicationControllerList)
91 92
 	if err != nil {
92 93
 		t.Errorf("unexpected error: %v", err)
... ...
@@ -240,7 +243,8 @@ func TestCreateController(t *testing.T) {
240 240
 			PodTemplate:     validPodTemplate,
241 241
 		},
242 242
 	}
243
-	channel, err := storage.Create(controller)
243
+	ctx := api.NewDefaultContext()
244
+	channel, err := storage.Create(ctx, controller)
244 245
 	if err != nil {
245 246
 		t.Fatalf("Unexpected error: %v", err)
246 247
 	}
... ...
@@ -263,7 +267,6 @@ func TestControllerStorageValidatesCreate(t *testing.T) {
263 263
 		podLister:  nil,
264 264
 		pollPeriod: time.Millisecond * 1,
265 265
 	}
266
-
267 266
 	failureCases := map[string]api.ReplicationController{
268 267
 		"empty ID": {
269 268
 			JSONBase: api.JSONBase{ID: ""},
... ...
@@ -276,8 +279,9 @@ func TestControllerStorageValidatesCreate(t *testing.T) {
276 276
 			DesiredState: api.ReplicationControllerState{},
277 277
 		},
278 278
 	}
279
+	ctx := api.NewDefaultContext()
279 280
 	for _, failureCase := range failureCases {
280
-		c, err := storage.Create(&failureCase)
281
+		c, err := storage.Create(ctx, &failureCase)
281 282
 		if c != nil {
282 283
 			t.Errorf("Expected nil channel")
283 284
 		}
... ...
@@ -306,8 +310,9 @@ func TestControllerStorageValidatesUpdate(t *testing.T) {
306 306
 			DesiredState: api.ReplicationControllerState{},
307 307
 		},
308 308
 	}
309
+	ctx := api.NewDefaultContext()
309 310
 	for _, failureCase := range failureCases {
310
-		c, err := storage.Update(&failureCase)
311
+		c, err := storage.Update(ctx, &failureCase)
311 312
 		if c != nil {
312 313
 			t.Errorf("Expected nil channel")
313 314
 		}
... ...
@@ -323,7 +328,7 @@ type fakePodLister struct {
323 323
 	s labels.Selector
324 324
 }
325 325
 
326
-func (f *fakePodLister) ListPods(s labels.Selector) (*api.PodList, error) {
326
+func (f *fakePodLister) ListPods(ctx api.Context, s labels.Selector) (*api.PodList, error) {
327 327
 	f.s = s
328 328
 	return &f.l, f.e
329 329
 }
... ...
@@ -349,7 +354,8 @@ func TestFillCurrentState(t *testing.T) {
349 349
 			},
350 350
 		},
351 351
 	}
352
-	storage.fillCurrentState(&controller)
352
+	ctx := api.NewContext()
353
+	storage.fillCurrentState(ctx, &controller)
353 354
 	if controller.CurrentState.Replicas != 2 {
354 355
 		t.Errorf("expected 2, got: %d", controller.CurrentState.Replicas)
355 356
 	}
... ...
@@ -24,8 +24,8 @@ import (
24 24
 
25 25
 // Registry is an interface for things that know how to store endpoints.
26 26
 type Registry interface {
27
-	ListEndpoints() (*api.EndpointsList, error)
28
-	GetEndpoints(name string) (*api.Endpoints, error)
29
-	WatchEndpoints(labels, fields labels.Selector, resourceVersion uint64) (watch.Interface, error)
30
-	UpdateEndpoints(e *api.Endpoints) error
27
+	ListEndpoints(ctx api.Context) (*api.EndpointsList, error)
28
+	GetEndpoints(ctx api.Context, name string) (*api.Endpoints, error)
29
+	WatchEndpoints(ctx api.Context, labels, fields labels.Selector, resourceVersion uint64) (watch.Interface, error)
30
+	UpdateEndpoints(ctx api.Context, e *api.Endpoints) error
31 31
 }
... ...
@@ -18,10 +18,13 @@ package endpoint
18 18
 
19 19
 import (
20 20
 	"errors"
21
+	"fmt"
21 22
 
22 23
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
24
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
23 25
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
24 26
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
27
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
25 28
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
26 29
 )
27 30
 
... ...
@@ -38,36 +41,60 @@ func NewREST(registry Registry) *REST {
38 38
 }
39 39
 
40 40
 // Get satisfies the RESTStorage interface.
41
-func (rs *REST) Get(id string) (runtime.Object, error) {
42
-	return rs.registry.GetEndpoints(id)
41
+func (rs *REST) Get(ctx api.Context, id string) (runtime.Object, error) {
42
+	return rs.registry.GetEndpoints(ctx, id)
43 43
 }
44 44
 
45 45
 // List satisfies the RESTStorage interface.
46
-func (rs *REST) List(label, field labels.Selector) (runtime.Object, error) {
46
+func (rs *REST) List(ctx api.Context, label, field labels.Selector) (runtime.Object, error) {
47 47
 	if !label.Empty() || !field.Empty() {
48 48
 		return nil, errors.New("label/field selectors are not supported on endpoints")
49 49
 	}
50
-	return rs.registry.ListEndpoints()
50
+	return rs.registry.ListEndpoints(ctx)
51 51
 }
52 52
 
53 53
 // Watch returns Endpoint events via a watch.Interface.
54 54
 // It implements apiserver.ResourceWatcher.
55
-func (rs *REST) Watch(label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
56
-	return rs.registry.WatchEndpoints(label, field, resourceVersion)
55
+func (rs *REST) Watch(ctx api.Context, label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
56
+	return rs.registry.WatchEndpoints(ctx, label, field, resourceVersion)
57 57
 }
58 58
 
59
-// Create satisfies the RESTStorage interface but is unimplemented.
60
-func (rs *REST) Create(obj runtime.Object) (<-chan runtime.Object, error) {
61
-	return nil, errors.New("unimplemented")
59
+// Create satisfies the RESTStorage interface.
60
+func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) {
61
+	endpoints, ok := obj.(*api.Endpoints)
62
+	if !ok {
63
+		return nil, fmt.Errorf("not an endpoints: %#v", obj)
64
+	}
65
+	if len(endpoints.ID) == 0 {
66
+		return nil, fmt.Errorf("id is required: %#v", obj)
67
+	}
68
+	endpoints.CreationTimestamp = util.Now()
69
+	return apiserver.MakeAsync(func() (runtime.Object, error) {
70
+		err := rs.registry.UpdateEndpoints(ctx, endpoints)
71
+		if err != nil {
72
+			return nil, err
73
+		}
74
+		return rs.registry.GetEndpoints(ctx, endpoints.ID)
75
+	}), nil
62 76
 }
63 77
 
64
-// Update satisfies the RESTStorage interface but is unimplemented.
65
-func (rs *REST) Update(obj runtime.Object) (<-chan runtime.Object, error) {
66
-	return nil, errors.New("unimplemented")
78
+// Update satisfies the RESTStorage interface.
79
+func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) {
80
+	endpoints, ok := obj.(*api.Endpoints)
81
+	if !ok {
82
+		return nil, fmt.Errorf("not an endpoints: %#v", obj)
83
+	}
84
+	return apiserver.MakeAsync(func() (runtime.Object, error) {
85
+		err := rs.registry.UpdateEndpoints(ctx, endpoints)
86
+		if err != nil {
87
+			return nil, err
88
+		}
89
+		return rs.registry.GetEndpoints(ctx, endpoints.ID)
90
+	}), nil
67 91
 }
68 92
 
69 93
 // Delete satisfies the RESTStorage interface but is unimplemented.
70
-func (rs *REST) Delete(id string) (<-chan runtime.Object, error) {
94
+func (rs *REST) Delete(ctx api.Context, id string) (<-chan runtime.Object, error) {
71 95
 	return nil, errors.New("unimplemented")
72 96
 }
73 97
 
... ...
@@ -34,7 +34,8 @@ func TestGetEndpoints(t *testing.T) {
34 34
 		},
35 35
 	}
36 36
 	storage := NewREST(registry)
37
-	obj, err := storage.Get("foo")
37
+	ctx := api.NewContext()
38
+	obj, err := storage.Get(ctx, "foo")
38 39
 	if err != nil {
39 40
 		t.Fatalf("unexpected error: %#v", err)
40 41
 	}
... ...
@@ -48,9 +49,9 @@ func TestGetEndpointsMissingService(t *testing.T) {
48 48
 		Err: errors.NewNotFound("service", "foo"),
49 49
 	}
50 50
 	storage := NewREST(registry)
51
-
51
+	ctx := api.NewContext()
52 52
 	// returns service not found
53
-	_, err := storage.Get("foo")
53
+	_, err := storage.Get(ctx, "foo")
54 54
 	if !errors.IsNotFound(err) || !reflect.DeepEqual(err, errors.NewNotFound("service", "foo")) {
55 55
 		t.Errorf("expected NotFound error, got %#v", err)
56 56
 	}
... ...
@@ -60,7 +61,7 @@ func TestGetEndpointsMissingService(t *testing.T) {
60 60
 	registry.Service = &api.Service{
61 61
 		JSONBase: api.JSONBase{ID: "foo"},
62 62
 	}
63
-	obj, err := storage.Get("foo")
63
+	obj, err := storage.Get(ctx, "foo")
64 64
 	if err != nil {
65 65
 		t.Fatalf("unexpected error: %v", err)
66 66
 	}
... ...
@@ -79,7 +80,8 @@ func TestEndpointsRegistryList(t *testing.T) {
79 79
 			{JSONBase: api.JSONBase{ID: "bar"}},
80 80
 		},
81 81
 	}
82
-	s, _ := storage.List(labels.Everything(), labels.Everything())
82
+	ctx := api.NewContext()
83
+	s, _ := storage.List(ctx, labels.Everything(), labels.Everything())
83 84
 	sl := s.(*api.EndpointsList)
84 85
 	if len(sl.Items) != 2 {
85 86
 		t.Fatalf("Expected 2 endpoints, but got %v", len(sl.Items))
... ...
@@ -23,6 +23,7 @@ import (
23 23
 	etcderr "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors/etcd"
24 24
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/constraint"
25 25
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
26
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod"
26 27
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
27 28
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
28 29
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
... ...
@@ -37,17 +38,15 @@ import (
37 37
 // with backed by etcd.
38 38
 type Registry struct {
39 39
 	tools.EtcdHelper
40
-	manifestFactory ManifestFactory
40
+	manifestFactory pod.ManifestFactory
41 41
 }
42 42
 
43 43
 // NewRegistry creates an etcd registry.
44
-func NewRegistry(helper tools.EtcdHelper) *Registry {
44
+func NewRegistry(helper tools.EtcdHelper, manifestFactory pod.ManifestFactory) *Registry {
45 45
 	registry := &Registry{
46 46
 		EtcdHelper: helper,
47 47
 	}
48
-	registry.manifestFactory = &BasicManifestFactory{
49
-		serviceRegistry: registry,
50
-	}
48
+	registry.manifestFactory = manifestFactory
51 49
 	return registry
52 50
 }
53 51
 
... ...
@@ -56,14 +55,14 @@ func makePodKey(podID string) string {
56 56
 }
57 57
 
58 58
 // ListPods obtains a list of pods with labels that match selector.
59
-func (r *Registry) ListPods(selector labels.Selector) (*api.PodList, error) {
60
-	return r.ListPodsPredicate(func(pod *api.Pod) bool {
59
+func (r *Registry) ListPods(ctx api.Context, selector labels.Selector) (*api.PodList, error) {
60
+	return r.ListPodsPredicate(ctx, func(pod *api.Pod) bool {
61 61
 		return selector.Matches(labels.Set(pod.Labels))
62 62
 	})
63 63
 }
64 64
 
65 65
 // ListPodsPredicate obtains a list of pods that match filter.
66
-func (r *Registry) ListPodsPredicate(filter func(*api.Pod) bool) (*api.PodList, error) {
66
+func (r *Registry) ListPodsPredicate(ctx api.Context, filter func(*api.Pod) bool) (*api.PodList, error) {
67 67
 	allPods := api.PodList{}
68 68
 	err := r.ExtractList("/registry/pods", &allPods.Items, &allPods.ResourceVersion)
69 69
 	if err != nil {
... ...
@@ -84,19 +83,20 @@ func (r *Registry) ListPodsPredicate(filter func(*api.Pod) bool) (*api.PodList,
84 84
 }
85 85
 
86 86
 // WatchPods begins watching for new, changed, or deleted pods.
87
-func (r *Registry) WatchPods(resourceVersion uint64, filter func(*api.Pod) bool) (watch.Interface, error) {
87
+func (r *Registry) WatchPods(ctx api.Context, resourceVersion uint64, filter func(*api.Pod) bool) (watch.Interface, error) {
88 88
 	return r.WatchList("/registry/pods", resourceVersion, func(obj runtime.Object) bool {
89
-		pod, ok := obj.(*api.Pod)
90
-		if !ok {
91
-			glog.Errorf("Unexpected object during pod watch: %#v", obj)
92
-			return false
89
+		switch t := obj.(type) {
90
+		case *api.Pod:
91
+			return filter(t)
92
+		default:
93
+			// Must be an error
94
+			return true
93 95
 		}
94
-		return filter(pod)
95 96
 	})
96 97
 }
97 98
 
98 99
 // GetPod gets a specific pod specified by its ID.
99
-func (r *Registry) GetPod(podID string) (*api.Pod, error) {
100
+func (r *Registry) GetPod(ctx api.Context, podID string) (*api.Pod, error) {
100 101
 	var pod api.Pod
101 102
 	if err := r.ExtractObj(makePodKey(podID), &pod, false); err != nil {
102 103
 		return nil, etcderr.InterpretGetError(err, "pod", podID)
... ...
@@ -113,19 +113,19 @@ func makeContainerKey(machine string) string {
113 113
 }
114 114
 
115 115
 // CreatePod creates a pod based on a specification.
116
-func (r *Registry) CreatePod(pod *api.Pod) error {
116
+func (r *Registry) CreatePod(ctx api.Context, pod *api.Pod) error {
117 117
 	// Set current status to "Waiting".
118 118
 	pod.CurrentState.Status = api.PodWaiting
119 119
 	pod.CurrentState.Host = ""
120 120
 	// DesiredState.Host == "" is a signal to the scheduler that this pod needs scheduling.
121 121
 	pod.DesiredState.Status = api.PodRunning
122 122
 	pod.DesiredState.Host = ""
123
-	err := r.CreateObj(makePodKey(pod.ID), pod)
123
+	err := r.CreateObj(makePodKey(pod.ID), pod, 0)
124 124
 	return etcderr.InterpretCreateError(err, "pod", pod.ID)
125 125
 }
126 126
 
127 127
 // ApplyBinding implements binding's registry
128
-func (r *Registry) ApplyBinding(binding *api.Binding) error {
128
+func (r *Registry) ApplyBinding(ctx api.Context, binding *api.Binding) error {
129 129
 	return etcderr.InterpretCreateError(r.assignPod(binding.PodID, binding.Host), "binding", "")
130 130
 }
131 131
 
... ...
@@ -178,12 +178,12 @@ func (r *Registry) assignPod(podID string, machine string) error {
178 178
 	return err
179 179
 }
180 180
 
181
-func (r *Registry) UpdatePod(pod *api.Pod) error {
181
+func (r *Registry) UpdatePod(ctx api.Context, pod *api.Pod) error {
182 182
 	return fmt.Errorf("unimplemented!")
183 183
 }
184 184
 
185 185
 // DeletePod deletes an existing pod specified by its ID.
186
-func (r *Registry) DeletePod(podID string) error {
186
+func (r *Registry) DeletePod(ctx api.Context, podID string) error {
187 187
 	var pod api.Pod
188 188
 	podKey := makePodKey(podID)
189 189
 	err := r.ExtractObj(podKey, &pod, false)
... ...
@@ -218,7 +218,7 @@ func (r *Registry) DeletePod(podID string) error {
218 218
 			// This really shouldn't happen, it indicates something is broken, and likely
219 219
 			// there is a lost pod somewhere.
220 220
 			// However it is "deleted" so log it and move on
221
-			glog.Infof("Couldn't find: %s in %#v", podID, manifests)
221
+			glog.Warningf("Couldn't find: %s in %#v", podID, manifests)
222 222
 		}
223 223
 		manifests.Items = newManifests
224 224
 		return manifests, nil
... ...
@@ -226,14 +226,14 @@ func (r *Registry) DeletePod(podID string) error {
226 226
 }
227 227
 
228 228
 // ListControllers obtains a list of ReplicationControllers.
229
-func (r *Registry) ListControllers() (*api.ReplicationControllerList, error) {
229
+func (r *Registry) ListControllers(ctx api.Context) (*api.ReplicationControllerList, error) {
230 230
 	controllers := &api.ReplicationControllerList{}
231 231
 	err := r.ExtractList("/registry/controllers", &controllers.Items, &controllers.ResourceVersion)
232 232
 	return controllers, err
233 233
 }
234 234
 
235 235
 // WatchControllers begins watching for new, changed, or deleted controllers.
236
-func (r *Registry) WatchControllers(resourceVersion uint64) (watch.Interface, error) {
236
+func (r *Registry) WatchControllers(ctx api.Context, resourceVersion uint64) (watch.Interface, error) {
237 237
 	return r.WatchList("/registry/controllers", resourceVersion, tools.Everything)
238 238
 }
239 239
 
... ...
@@ -242,7 +242,7 @@ func makeControllerKey(id string) string {
242 242
 }
243 243
 
244 244
 // GetController gets a specific ReplicationController specified by its ID.
245
-func (r *Registry) GetController(controllerID string) (*api.ReplicationController, error) {
245
+func (r *Registry) GetController(ctx api.Context, controllerID string) (*api.ReplicationController, error) {
246 246
 	var controller api.ReplicationController
247 247
 	key := makeControllerKey(controllerID)
248 248
 	err := r.ExtractObj(key, &controller, false)
... ...
@@ -253,19 +253,19 @@ func (r *Registry) GetController(controllerID string) (*api.ReplicationControlle
253 253
 }
254 254
 
255 255
 // CreateController creates a new ReplicationController.
256
-func (r *Registry) CreateController(controller *api.ReplicationController) error {
257
-	err := r.CreateObj(makeControllerKey(controller.ID), controller)
256
+func (r *Registry) CreateController(ctx api.Context, controller *api.ReplicationController) error {
257
+	err := r.CreateObj(makeControllerKey(controller.ID), controller, 0)
258 258
 	return etcderr.InterpretCreateError(err, "replicationController", controller.ID)
259 259
 }
260 260
 
261 261
 // UpdateController replaces an existing ReplicationController.
262
-func (r *Registry) UpdateController(controller *api.ReplicationController) error {
262
+func (r *Registry) UpdateController(ctx api.Context, controller *api.ReplicationController) error {
263 263
 	err := r.SetObj(makeControllerKey(controller.ID), controller)
264 264
 	return etcderr.InterpretUpdateError(err, "replicationController", controller.ID)
265 265
 }
266 266
 
267 267
 // DeleteController deletes a ReplicationController specified by its ID.
268
-func (r *Registry) DeleteController(controllerID string) error {
268
+func (r *Registry) DeleteController(ctx api.Context, controllerID string) error {
269 269
 	key := makeControllerKey(controllerID)
270 270
 	err := r.Delete(key, false)
271 271
 	return etcderr.InterpretDeleteError(err, "replicationController", controllerID)
... ...
@@ -276,20 +276,20 @@ func makeServiceKey(name string) string {
276 276
 }
277 277
 
278 278
 // ListServices obtains a list of Services.
279
-func (r *Registry) ListServices() (*api.ServiceList, error) {
279
+func (r *Registry) ListServices(ctx api.Context) (*api.ServiceList, error) {
280 280
 	list := &api.ServiceList{}
281 281
 	err := r.ExtractList("/registry/services/specs", &list.Items, &list.ResourceVersion)
282 282
 	return list, err
283 283
 }
284 284
 
285 285
 // CreateService creates a new Service.
286
-func (r *Registry) CreateService(svc *api.Service) error {
287
-	err := r.CreateObj(makeServiceKey(svc.ID), svc)
286
+func (r *Registry) CreateService(ctx api.Context, svc *api.Service) error {
287
+	err := r.CreateObj(makeServiceKey(svc.ID), svc, 0)
288 288
 	return etcderr.InterpretCreateError(err, "service", svc.ID)
289 289
 }
290 290
 
291 291
 // GetService obtains a Service specified by its name.
292
-func (r *Registry) GetService(name string) (*api.Service, error) {
292
+func (r *Registry) GetService(ctx api.Context, name string) (*api.Service, error) {
293 293
 	key := makeServiceKey(name)
294 294
 	var svc api.Service
295 295
 	err := r.ExtractObj(key, &svc, false)
... ...
@@ -300,7 +300,7 @@ func (r *Registry) GetService(name string) (*api.Service, error) {
300 300
 }
301 301
 
302 302
 // GetEndpoints obtains the endpoints for the service identified by 'name'.
303
-func (r *Registry) GetEndpoints(name string) (*api.Endpoints, error) {
303
+func (r *Registry) GetEndpoints(ctx api.Context, name string) (*api.Endpoints, error) {
304 304
 	key := makeServiceEndpointsKey(name)
305 305
 	var endpoints api.Endpoints
306 306
 	err := r.ExtractObj(key, &endpoints, false)
... ...
@@ -315,7 +315,7 @@ func makeServiceEndpointsKey(name string) string {
315 315
 }
316 316
 
317 317
 // DeleteService deletes a Service specified by its name.
318
-func (r *Registry) DeleteService(name string) error {
318
+func (r *Registry) DeleteService(ctx api.Context, name string) error {
319 319
 	key := makeServiceKey(name)
320 320
 	err := r.Delete(key, true)
321 321
 	if err != nil {
... ...
@@ -332,18 +332,18 @@ func (r *Registry) DeleteService(name string) error {
332 332
 }
333 333
 
334 334
 // UpdateService replaces an existing Service.
335
-func (r *Registry) UpdateService(svc *api.Service) error {
335
+func (r *Registry) UpdateService(ctx api.Context, svc *api.Service) error {
336 336
 	err := r.SetObj(makeServiceKey(svc.ID), svc)
337 337
 	return etcderr.InterpretUpdateError(err, "service", svc.ID)
338 338
 }
339 339
 
340 340
 // WatchServices begins watching for new, changed, or deleted service configurations.
341
-func (r *Registry) WatchServices(label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
341
+func (r *Registry) WatchServices(ctx api.Context, label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
342 342
 	if !label.Empty() {
343 343
 		return nil, fmt.Errorf("label selectors are not supported on services")
344 344
 	}
345 345
 	if value, found := field.RequiresExactMatch("ID"); found {
346
-		return r.Watch(makeServiceKey(value), resourceVersion)
346
+		return r.Watch(makeServiceKey(value), resourceVersion), nil
347 347
 	}
348 348
 	if field.Empty() {
349 349
 		return r.WatchList("/registry/services/specs", resourceVersion, tools.Everything)
... ...
@@ -352,14 +352,14 @@ func (r *Registry) WatchServices(label, field labels.Selector, resourceVersion u
352 352
 }
353 353
 
354 354
 // ListEndpoints obtains a list of Services.
355
-func (r *Registry) ListEndpoints() (*api.EndpointsList, error) {
355
+func (r *Registry) ListEndpoints(ctx api.Context) (*api.EndpointsList, error) {
356 356
 	list := &api.EndpointsList{}
357 357
 	err := r.ExtractList("/registry/services/endpoints", &list.Items, &list.ResourceVersion)
358 358
 	return list, err
359 359
 }
360 360
 
361 361
 // UpdateEndpoints update Endpoints of a Service.
362
-func (r *Registry) UpdateEndpoints(e *api.Endpoints) error {
362
+func (r *Registry) UpdateEndpoints(ctx api.Context, e *api.Endpoints) error {
363 363
 	// TODO: this is a really bad misuse of AtomicUpdate, need to compute a diff inside the loop.
364 364
 	err := r.AtomicUpdate(makeServiceEndpointsKey(e.ID), &api.Endpoints{},
365 365
 		func(input runtime.Object) (runtime.Object, error) {
... ...
@@ -370,12 +370,12 @@ func (r *Registry) UpdateEndpoints(e *api.Endpoints) error {
370 370
 }
371 371
 
372 372
 // WatchEndpoints begins watching for new, changed, or deleted endpoint configurations.
373
-func (r *Registry) WatchEndpoints(label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
373
+func (r *Registry) WatchEndpoints(ctx api.Context, label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
374 374
 	if !label.Empty() {
375 375
 		return nil, fmt.Errorf("label selectors are not supported on endpoints")
376 376
 	}
377 377
 	if value, found := field.RequiresExactMatch("ID"); found {
378
-		return r.Watch(makeServiceEndpointsKey(value), resourceVersion)
378
+		return r.Watch(makeServiceEndpointsKey(value), resourceVersion), nil
379 379
 	}
380 380
 	if field.Empty() {
381 381
 		return r.WatchList("/registry/services/endpoints", resourceVersion, tools.Everything)
... ...
@@ -24,6 +24,7 @@ import (
24 24
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
25 25
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
26 26
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
27
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod"
27 28
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest"
28 29
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
29 30
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
... ...
@@ -32,18 +33,19 @@ import (
32 32
 )
33 33
 
34 34
 func NewTestEtcdRegistry(client tools.EtcdClient) *Registry {
35
-	registry := NewRegistry(tools.EtcdHelper{client, latest.Codec, latest.ResourceVersioner})
36
-	registry.manifestFactory = &BasicManifestFactory{
37
-		serviceRegistry: &registrytest.ServiceRegistry{},
38
-	}
35
+	registry := NewRegistry(tools.EtcdHelper{client, latest.Codec, latest.ResourceVersioner},
36
+		&pod.BasicManifestFactory{
37
+			ServiceRegistry: &registrytest.ServiceRegistry{},
38
+		})
39 39
 	return registry
40 40
 }
41 41
 
42 42
 func TestEtcdGetPod(t *testing.T) {
43
+	ctx := api.NewContext()
43 44
 	fakeClient := tools.NewFakeEtcdClient(t)
44 45
 	fakeClient.Set("/registry/pods/foo", runtime.EncodeOrDie(latest.Codec, &api.Pod{JSONBase: api.JSONBase{ID: "foo"}}), 0)
45 46
 	registry := NewTestEtcdRegistry(fakeClient)
46
-	pod, err := registry.GetPod("foo")
47
+	pod, err := registry.GetPod(ctx, "foo")
47 48
 	if err != nil {
48 49
 		t.Errorf("unexpected error: %v", err)
49 50
 	}
... ...
@@ -54,6 +56,7 @@ func TestEtcdGetPod(t *testing.T) {
54 54
 }
55 55
 
56 56
 func TestEtcdGetPodNotFound(t *testing.T) {
57
+	ctx := api.NewContext()
57 58
 	fakeClient := tools.NewFakeEtcdClient(t)
58 59
 	fakeClient.Data["/registry/pods/foo"] = tools.EtcdResponseWithError{
59 60
 		R: &etcd.Response{
... ...
@@ -62,13 +65,14 @@ func TestEtcdGetPodNotFound(t *testing.T) {
62 62
 		E: tools.EtcdErrorNotFound,
63 63
 	}
64 64
 	registry := NewTestEtcdRegistry(fakeClient)
65
-	_, err := registry.GetPod("foo")
65
+	_, err := registry.GetPod(ctx, "foo")
66 66
 	if !errors.IsNotFound(err) {
67 67
 		t.Errorf("Unexpected error returned: %#v", err)
68 68
 	}
69 69
 }
70 70
 
71 71
 func TestEtcdCreatePod(t *testing.T) {
72
+	ctx := api.NewContext()
72 73
 	fakeClient := tools.NewFakeEtcdClient(t)
73 74
 	fakeClient.TestIndex = true
74 75
 	fakeClient.Data["/registry/pods/foo"] = tools.EtcdResponseWithError{
... ...
@@ -79,7 +83,7 @@ func TestEtcdCreatePod(t *testing.T) {
79 79
 	}
80 80
 	fakeClient.Set("/registry/hosts/machine/kubelet", runtime.EncodeOrDie(latest.Codec, &api.ContainerManifestList{}), 0)
81 81
 	registry := NewTestEtcdRegistry(fakeClient)
82
-	err := registry.CreatePod(&api.Pod{
82
+	err := registry.CreatePod(ctx, &api.Pod{
83 83
 		JSONBase: api.JSONBase{
84 84
 			ID: "foo",
85 85
 		},
... ...
@@ -98,7 +102,7 @@ func TestEtcdCreatePod(t *testing.T) {
98 98
 	}
99 99
 
100 100
 	// Suddenly, a wild scheduler appears:
101
-	err = registry.ApplyBinding(&api.Binding{PodID: "foo", Host: "machine"})
101
+	err = registry.ApplyBinding(ctx, &api.Binding{PodID: "foo", Host: "machine"})
102 102
 	if err != nil {
103 103
 		t.Fatalf("unexpected error: %v", err)
104 104
 	}
... ...
@@ -129,6 +133,7 @@ func TestEtcdCreatePod(t *testing.T) {
129 129
 }
130 130
 
131 131
 func TestEtcdCreatePodAlreadyExisting(t *testing.T) {
132
+	ctx := api.NewContext()
132 133
 	fakeClient := tools.NewFakeEtcdClient(t)
133 134
 	fakeClient.Data["/registry/pods/foo"] = tools.EtcdResponseWithError{
134 135
 		R: &etcd.Response{
... ...
@@ -139,7 +144,7 @@ func TestEtcdCreatePodAlreadyExisting(t *testing.T) {
139 139
 		E: nil,
140 140
 	}
141 141
 	registry := NewTestEtcdRegistry(fakeClient)
142
-	err := registry.CreatePod(&api.Pod{
142
+	err := registry.CreatePod(ctx, &api.Pod{
143 143
 		JSONBase: api.JSONBase{
144 144
 			ID: "foo",
145 145
 		},
... ...
@@ -150,6 +155,7 @@ func TestEtcdCreatePodAlreadyExisting(t *testing.T) {
150 150
 }
151 151
 
152 152
 func TestEtcdCreatePodWithContainersError(t *testing.T) {
153
+	ctx := api.NewContext()
153 154
 	fakeClient := tools.NewFakeEtcdClient(t)
154 155
 	fakeClient.TestIndex = true
155 156
 	fakeClient.Data["/registry/pods/foo"] = tools.EtcdResponseWithError{
... ...
@@ -165,7 +171,7 @@ func TestEtcdCreatePodWithContainersError(t *testing.T) {
165 165
 		E: tools.EtcdErrorNodeExist, // validate that ApplyBinding is translating Create errors
166 166
 	}
167 167
 	registry := NewTestEtcdRegistry(fakeClient)
168
-	err := registry.CreatePod(&api.Pod{
168
+	err := registry.CreatePod(ctx, &api.Pod{
169 169
 		JSONBase: api.JSONBase{
170 170
 			ID: "foo",
171 171
 		},
... ...
@@ -175,12 +181,12 @@ func TestEtcdCreatePodWithContainersError(t *testing.T) {
175 175
 	}
176 176
 
177 177
 	// Suddenly, a wild scheduler appears:
178
-	err = registry.ApplyBinding(&api.Binding{PodID: "foo", Host: "machine"})
178
+	err = registry.ApplyBinding(ctx, &api.Binding{PodID: "foo", Host: "machine"})
179 179
 	if !errors.IsAlreadyExists(err) {
180 180
 		t.Fatalf("Unexpected error returned: %#v", err)
181 181
 	}
182 182
 
183
-	existingPod, err := registry.GetPod("foo")
183
+	existingPod, err := registry.GetPod(ctx, "foo")
184 184
 	if err != nil {
185 185
 		t.Fatalf("Unexpected error: %v", err)
186 186
 	}
... ...
@@ -190,6 +196,7 @@ func TestEtcdCreatePodWithContainersError(t *testing.T) {
190 190
 }
191 191
 
192 192
 func TestEtcdCreatePodWithContainersNotFound(t *testing.T) {
193
+	ctx := api.NewContext()
193 194
 	fakeClient := tools.NewFakeEtcdClient(t)
194 195
 	fakeClient.TestIndex = true
195 196
 	fakeClient.Data["/registry/pods/foo"] = tools.EtcdResponseWithError{
... ...
@@ -205,7 +212,7 @@ func TestEtcdCreatePodWithContainersNotFound(t *testing.T) {
205 205
 		E: tools.EtcdErrorNotFound,
206 206
 	}
207 207
 	registry := NewTestEtcdRegistry(fakeClient)
208
-	err := registry.CreatePod(&api.Pod{
208
+	err := registry.CreatePod(ctx, &api.Pod{
209 209
 		JSONBase: api.JSONBase{
210 210
 			ID: "foo",
211 211
 		},
... ...
@@ -225,7 +232,7 @@ func TestEtcdCreatePodWithContainersNotFound(t *testing.T) {
225 225
 	}
226 226
 
227 227
 	// Suddenly, a wild scheduler appears:
228
-	err = registry.ApplyBinding(&api.Binding{PodID: "foo", Host: "machine"})
228
+	err = registry.ApplyBinding(ctx, &api.Binding{PodID: "foo", Host: "machine"})
229 229
 	if err != nil {
230 230
 		t.Fatalf("unexpected error: %v", err)
231 231
 	}
... ...
@@ -256,6 +263,7 @@ func TestEtcdCreatePodWithContainersNotFound(t *testing.T) {
256 256
 }
257 257
 
258 258
 func TestEtcdCreatePodWithExistingContainers(t *testing.T) {
259
+	ctx := api.NewContext()
259 260
 	fakeClient := tools.NewFakeEtcdClient(t)
260 261
 	fakeClient.TestIndex = true
261 262
 	fakeClient.Data["/registry/pods/foo"] = tools.EtcdResponseWithError{
... ...
@@ -270,7 +278,7 @@ func TestEtcdCreatePodWithExistingContainers(t *testing.T) {
270 270
 		},
271 271
 	}), 0)
272 272
 	registry := NewTestEtcdRegistry(fakeClient)
273
-	err := registry.CreatePod(&api.Pod{
273
+	err := registry.CreatePod(ctx, &api.Pod{
274 274
 		JSONBase: api.JSONBase{
275 275
 			ID: "foo",
276 276
 		},
... ...
@@ -290,7 +298,7 @@ func TestEtcdCreatePodWithExistingContainers(t *testing.T) {
290 290
 	}
291 291
 
292 292
 	// Suddenly, a wild scheduler appears:
293
-	err = registry.ApplyBinding(&api.Binding{PodID: "foo", Host: "machine"})
293
+	err = registry.ApplyBinding(ctx, &api.Binding{PodID: "foo", Host: "machine"})
294 294
 	if err != nil {
295 295
 		t.Fatalf("unexpected error: %v", err)
296 296
 	}
... ...
@@ -321,6 +329,7 @@ func TestEtcdCreatePodWithExistingContainers(t *testing.T) {
321 321
 }
322 322
 
323 323
 func TestEtcdDeletePod(t *testing.T) {
324
+	ctx := api.NewContext()
324 325
 	fakeClient := tools.NewFakeEtcdClient(t)
325 326
 	fakeClient.TestIndex = true
326 327
 
... ...
@@ -335,7 +344,7 @@ func TestEtcdDeletePod(t *testing.T) {
335 335
 		},
336 336
 	}), 0)
337 337
 	registry := NewTestEtcdRegistry(fakeClient)
338
-	err := registry.DeletePod("foo")
338
+	err := registry.DeletePod(ctx, "foo")
339 339
 	if err != nil {
340 340
 		t.Errorf("unexpected error: %v", err)
341 341
 	}
... ...
@@ -357,6 +366,7 @@ func TestEtcdDeletePod(t *testing.T) {
357 357
 }
358 358
 
359 359
 func TestEtcdDeletePodMultipleContainers(t *testing.T) {
360
+	ctx := api.NewContext()
360 361
 	fakeClient := tools.NewFakeEtcdClient(t)
361 362
 	fakeClient.TestIndex = true
362 363
 
... ...
@@ -372,7 +382,7 @@ func TestEtcdDeletePodMultipleContainers(t *testing.T) {
372 372
 		},
373 373
 	}), 0)
374 374
 	registry := NewTestEtcdRegistry(fakeClient)
375
-	err := registry.DeletePod("foo")
375
+	err := registry.DeletePod(ctx, "foo")
376 376
 	if err != nil {
377 377
 		t.Errorf("unexpected error: %v", err)
378 378
 	}
... ...
@@ -409,7 +419,8 @@ func TestEtcdEmptyListPods(t *testing.T) {
409 409
 		E: nil,
410 410
 	}
411 411
 	registry := NewTestEtcdRegistry(fakeClient)
412
-	pods, err := registry.ListPods(labels.Everything())
412
+	ctx := api.NewContext()
413
+	pods, err := registry.ListPods(ctx, labels.Everything())
413 414
 	if err != nil {
414 415
 		t.Errorf("unexpected error: %v", err)
415 416
 	}
... ...
@@ -427,7 +438,8 @@ func TestEtcdListPodsNotFound(t *testing.T) {
427 427
 		E: tools.EtcdErrorNotFound,
428 428
 	}
429 429
 	registry := NewTestEtcdRegistry(fakeClient)
430
-	pods, err := registry.ListPods(labels.Everything())
430
+	ctx := api.NewContext()
431
+	pods, err := registry.ListPods(ctx, labels.Everything())
431 432
 	if err != nil {
432 433
 		t.Errorf("unexpected error: %v", err)
433 434
 	}
... ...
@@ -462,7 +474,8 @@ func TestEtcdListPods(t *testing.T) {
462 462
 		E: nil,
463 463
 	}
464 464
 	registry := NewTestEtcdRegistry(fakeClient)
465
-	pods, err := registry.ListPods(labels.Everything())
465
+	ctx := api.NewContext()
466
+	pods, err := registry.ListPods(ctx, labels.Everything())
466 467
 	if err != nil {
467 468
 		t.Errorf("unexpected error: %v", err)
468 469
 	}
... ...
@@ -477,6 +490,7 @@ func TestEtcdListPods(t *testing.T) {
477 477
 }
478 478
 
479 479
 func TestEtcdListControllersNotFound(t *testing.T) {
480
+	ctx := api.NewContext()
480 481
 	fakeClient := tools.NewFakeEtcdClient(t)
481 482
 	key := "/registry/controllers"
482 483
 	fakeClient.Data[key] = tools.EtcdResponseWithError{
... ...
@@ -484,7 +498,7 @@ func TestEtcdListControllersNotFound(t *testing.T) {
484 484
 		E: tools.EtcdErrorNotFound,
485 485
 	}
486 486
 	registry := NewTestEtcdRegistry(fakeClient)
487
-	controllers, err := registry.ListControllers()
487
+	controllers, err := registry.ListControllers(ctx)
488 488
 	if err != nil {
489 489
 		t.Errorf("unexpected error: %v", err)
490 490
 	}
... ...
@@ -495,6 +509,7 @@ func TestEtcdListControllersNotFound(t *testing.T) {
495 495
 }
496 496
 
497 497
 func TestEtcdListServicesNotFound(t *testing.T) {
498
+	ctx := api.NewContext()
498 499
 	fakeClient := tools.NewFakeEtcdClient(t)
499 500
 	key := "/registry/services/specs"
500 501
 	fakeClient.Data[key] = tools.EtcdResponseWithError{
... ...
@@ -502,7 +517,7 @@ func TestEtcdListServicesNotFound(t *testing.T) {
502 502
 		E: tools.EtcdErrorNotFound,
503 503
 	}
504 504
 	registry := NewTestEtcdRegistry(fakeClient)
505
-	services, err := registry.ListServices()
505
+	services, err := registry.ListServices(ctx)
506 506
 	if err != nil {
507 507
 		t.Errorf("unexpected error: %v", err)
508 508
 	}
... ...
@@ -513,6 +528,7 @@ func TestEtcdListServicesNotFound(t *testing.T) {
513 513
 }
514 514
 
515 515
 func TestEtcdListControllers(t *testing.T) {
516
+	ctx := api.NewContext()
516 517
 	fakeClient := tools.NewFakeEtcdClient(t)
517 518
 	key := "/registry/controllers"
518 519
 	fakeClient.Data[key] = tools.EtcdResponseWithError{
... ...
@@ -531,7 +547,7 @@ func TestEtcdListControllers(t *testing.T) {
531 531
 		E: nil,
532 532
 	}
533 533
 	registry := NewTestEtcdRegistry(fakeClient)
534
-	controllers, err := registry.ListControllers()
534
+	controllers, err := registry.ListControllers(ctx)
535 535
 	if err != nil {
536 536
 		t.Errorf("unexpected error: %v", err)
537 537
 	}
... ...
@@ -542,10 +558,11 @@ func TestEtcdListControllers(t *testing.T) {
542 542
 }
543 543
 
544 544
 func TestEtcdGetController(t *testing.T) {
545
+	ctx := api.NewContext()
545 546
 	fakeClient := tools.NewFakeEtcdClient(t)
546 547
 	fakeClient.Set("/registry/controllers/foo", runtime.EncodeOrDie(latest.Codec, &api.ReplicationController{JSONBase: api.JSONBase{ID: "foo"}}), 0)
547 548
 	registry := NewTestEtcdRegistry(fakeClient)
548
-	ctrl, err := registry.GetController("foo")
549
+	ctrl, err := registry.GetController(ctx, "foo")
549 550
 	if err != nil {
550 551
 		t.Errorf("unexpected error: %v", err)
551 552
 	}
... ...
@@ -556,6 +573,7 @@ func TestEtcdGetController(t *testing.T) {
556 556
 }
557 557
 
558 558
 func TestEtcdGetControllerNotFound(t *testing.T) {
559
+	ctx := api.NewContext()
559 560
 	fakeClient := tools.NewFakeEtcdClient(t)
560 561
 	fakeClient.Data["/registry/controllers/foo"] = tools.EtcdResponseWithError{
561 562
 		R: &etcd.Response{
... ...
@@ -564,7 +582,7 @@ func TestEtcdGetControllerNotFound(t *testing.T) {
564 564
 		E: tools.EtcdErrorNotFound,
565 565
 	}
566 566
 	registry := NewTestEtcdRegistry(fakeClient)
567
-	ctrl, err := registry.GetController("foo")
567
+	ctrl, err := registry.GetController(ctx, "foo")
568 568
 	if ctrl != nil {
569 569
 		t.Errorf("Unexpected non-nil controller: %#v", ctrl)
570 570
 	}
... ...
@@ -574,9 +592,10 @@ func TestEtcdGetControllerNotFound(t *testing.T) {
574 574
 }
575 575
 
576 576
 func TestEtcdDeleteController(t *testing.T) {
577
+	ctx := api.NewContext()
577 578
 	fakeClient := tools.NewFakeEtcdClient(t)
578 579
 	registry := NewTestEtcdRegistry(fakeClient)
579
-	err := registry.DeleteController("foo")
580
+	err := registry.DeleteController(ctx, "foo")
580 581
 	if err != nil {
581 582
 		t.Errorf("unexpected error: %v", err)
582 583
 	}
... ...
@@ -591,9 +610,10 @@ func TestEtcdDeleteController(t *testing.T) {
591 591
 }
592 592
 
593 593
 func TestEtcdCreateController(t *testing.T) {
594
+	ctx := api.NewContext()
594 595
 	fakeClient := tools.NewFakeEtcdClient(t)
595 596
 	registry := NewTestEtcdRegistry(fakeClient)
596
-	err := registry.CreateController(&api.ReplicationController{
597
+	err := registry.CreateController(ctx, &api.ReplicationController{
597 598
 		JSONBase: api.JSONBase{
598 599
 			ID: "foo",
599 600
 		},
... ...
@@ -618,11 +638,12 @@ func TestEtcdCreateController(t *testing.T) {
618 618
 }
619 619
 
620 620
 func TestEtcdCreateControllerAlreadyExisting(t *testing.T) {
621
+	ctx := api.NewContext()
621 622
 	fakeClient := tools.NewFakeEtcdClient(t)
622 623
 	fakeClient.Set("/registry/controllers/foo", runtime.EncodeOrDie(latest.Codec, &api.ReplicationController{JSONBase: api.JSONBase{ID: "foo"}}), 0)
623 624
 
624 625
 	registry := NewTestEtcdRegistry(fakeClient)
625
-	err := registry.CreateController(&api.ReplicationController{
626
+	err := registry.CreateController(ctx, &api.ReplicationController{
626 627
 		JSONBase: api.JSONBase{
627 628
 			ID: "foo",
628 629
 		},
... ...
@@ -633,12 +654,13 @@ func TestEtcdCreateControllerAlreadyExisting(t *testing.T) {
633 633
 }
634 634
 
635 635
 func TestEtcdUpdateController(t *testing.T) {
636
+	ctx := api.NewContext()
636 637
 	fakeClient := tools.NewFakeEtcdClient(t)
637 638
 	fakeClient.TestIndex = true
638 639
 
639 640
 	resp, _ := fakeClient.Set("/registry/controllers/foo", runtime.EncodeOrDie(latest.Codec, &api.ReplicationController{JSONBase: api.JSONBase{ID: "foo"}}), 0)
640 641
 	registry := NewTestEtcdRegistry(fakeClient)
641
-	err := registry.UpdateController(&api.ReplicationController{
642
+	err := registry.UpdateController(ctx, &api.ReplicationController{
642 643
 		JSONBase: api.JSONBase{ID: "foo", ResourceVersion: resp.Node.ModifiedIndex},
643 644
 		DesiredState: api.ReplicationControllerState{
644 645
 			Replicas: 2,
... ...
@@ -648,13 +670,14 @@ func TestEtcdUpdateController(t *testing.T) {
648 648
 		t.Errorf("unexpected error: %v", err)
649 649
 	}
650 650
 
651
-	ctrl, err := registry.GetController("foo")
651
+	ctrl, err := registry.GetController(ctx, "foo")
652 652
 	if ctrl.DesiredState.Replicas != 2 {
653 653
 		t.Errorf("Unexpected controller: %#v", ctrl)
654 654
 	}
655 655
 }
656 656
 
657 657
 func TestEtcdListServices(t *testing.T) {
658
+	ctx := api.NewContext()
658 659
 	fakeClient := tools.NewFakeEtcdClient(t)
659 660
 	key := "/registry/services/specs"
660 661
 	fakeClient.Data[key] = tools.EtcdResponseWithError{
... ...
@@ -673,7 +696,7 @@ func TestEtcdListServices(t *testing.T) {
673 673
 		E: nil,
674 674
 	}
675 675
 	registry := NewTestEtcdRegistry(fakeClient)
676
-	services, err := registry.ListServices()
676
+	services, err := registry.ListServices(ctx)
677 677
 	if err != nil {
678 678
 		t.Errorf("unexpected error: %v", err)
679 679
 	}
... ...
@@ -684,9 +707,10 @@ func TestEtcdListServices(t *testing.T) {
684 684
 }
685 685
 
686 686
 func TestEtcdCreateService(t *testing.T) {
687
+	ctx := api.NewContext()
687 688
 	fakeClient := tools.NewFakeEtcdClient(t)
688 689
 	registry := NewTestEtcdRegistry(fakeClient)
689
-	err := registry.CreateService(&api.Service{
690
+	err := registry.CreateService(ctx, &api.Service{
690 691
 		JSONBase: api.JSONBase{ID: "foo"},
691 692
 	})
692 693
 	if err != nil {
... ...
@@ -710,10 +734,11 @@ func TestEtcdCreateService(t *testing.T) {
710 710
 }
711 711
 
712 712
 func TestEtcdCreateServiceAlreadyExisting(t *testing.T) {
713
+	ctx := api.NewContext()
713 714
 	fakeClient := tools.NewFakeEtcdClient(t)
714 715
 	fakeClient.Set("/registry/services/specs/foo", runtime.EncodeOrDie(latest.Codec, &api.Service{JSONBase: api.JSONBase{ID: "foo"}}), 0)
715 716
 	registry := NewTestEtcdRegistry(fakeClient)
716
-	err := registry.CreateService(&api.Service{
717
+	err := registry.CreateService(ctx, &api.Service{
717 718
 		JSONBase: api.JSONBase{ID: "foo"},
718 719
 	})
719 720
 	if !errors.IsAlreadyExists(err) {
... ...
@@ -722,10 +747,11 @@ func TestEtcdCreateServiceAlreadyExisting(t *testing.T) {
722 722
 }
723 723
 
724 724
 func TestEtcdGetService(t *testing.T) {
725
+	ctx := api.NewContext()
725 726
 	fakeClient := tools.NewFakeEtcdClient(t)
726 727
 	fakeClient.Set("/registry/services/specs/foo", runtime.EncodeOrDie(latest.Codec, &api.Service{JSONBase: api.JSONBase{ID: "foo"}}), 0)
727 728
 	registry := NewTestEtcdRegistry(fakeClient)
728
-	service, err := registry.GetService("foo")
729
+	service, err := registry.GetService(ctx, "foo")
729 730
 	if err != nil {
730 731
 		t.Errorf("unexpected error: %v", err)
731 732
 	}
... ...
@@ -736,6 +762,7 @@ func TestEtcdGetService(t *testing.T) {
736 736
 }
737 737
 
738 738
 func TestEtcdGetServiceNotFound(t *testing.T) {
739
+	ctx := api.NewContext()
739 740
 	fakeClient := tools.NewFakeEtcdClient(t)
740 741
 	fakeClient.Data["/registry/services/specs/foo"] = tools.EtcdResponseWithError{
741 742
 		R: &etcd.Response{
... ...
@@ -744,16 +771,17 @@ func TestEtcdGetServiceNotFound(t *testing.T) {
744 744
 		E: tools.EtcdErrorNotFound,
745 745
 	}
746 746
 	registry := NewTestEtcdRegistry(fakeClient)
747
-	_, err := registry.GetService("foo")
747
+	_, err := registry.GetService(ctx, "foo")
748 748
 	if !errors.IsNotFound(err) {
749 749
 		t.Errorf("Unexpected error returned: %#v", err)
750 750
 	}
751 751
 }
752 752
 
753 753
 func TestEtcdDeleteService(t *testing.T) {
754
+	ctx := api.NewContext()
754 755
 	fakeClient := tools.NewFakeEtcdClient(t)
755 756
 	registry := NewTestEtcdRegistry(fakeClient)
756
-	err := registry.DeleteService("foo")
757
+	err := registry.DeleteService(ctx, "foo")
757 758
 	if err != nil {
758 759
 		t.Errorf("unexpected error: %v", err)
759 760
 	}
... ...
@@ -772,6 +800,7 @@ func TestEtcdDeleteService(t *testing.T) {
772 772
 }
773 773
 
774 774
 func TestEtcdUpdateService(t *testing.T) {
775
+	ctx := api.NewContext()
775 776
 	fakeClient := tools.NewFakeEtcdClient(t)
776 777
 	fakeClient.TestIndex = true
777 778
 
... ...
@@ -786,12 +815,12 @@ func TestEtcdUpdateService(t *testing.T) {
786 786
 			"baz": "bar",
787 787
 		},
788 788
 	}
789
-	err := registry.UpdateService(&testService)
789
+	err := registry.UpdateService(ctx, &testService)
790 790
 	if err != nil {
791 791
 		t.Errorf("unexpected error: %v", err)
792 792
 	}
793 793
 
794
-	svc, err := registry.GetService("foo")
794
+	svc, err := registry.GetService(ctx, "foo")
795 795
 	if err != nil {
796 796
 		t.Errorf("unexpected error: %v", err)
797 797
 	}
... ...
@@ -805,6 +834,7 @@ func TestEtcdUpdateService(t *testing.T) {
805 805
 }
806 806
 
807 807
 func TestEtcdListEndpoints(t *testing.T) {
808
+	ctx := api.NewContext()
808 809
 	fakeClient := tools.NewFakeEtcdClient(t)
809 810
 	key := "/registry/services/endpoints"
810 811
 	fakeClient.Data[key] = tools.EtcdResponseWithError{
... ...
@@ -823,7 +853,7 @@ func TestEtcdListEndpoints(t *testing.T) {
823 823
 		E: nil,
824 824
 	}
825 825
 	registry := NewTestEtcdRegistry(fakeClient)
826
-	services, err := registry.ListEndpoints()
826
+	services, err := registry.ListEndpoints(ctx)
827 827
 	if err != nil {
828 828
 		t.Errorf("unexpected error: %v", err)
829 829
 	}
... ...
@@ -834,6 +864,7 @@ func TestEtcdListEndpoints(t *testing.T) {
834 834
 }
835 835
 
836 836
 func TestEtcdGetEndpoints(t *testing.T) {
837
+	ctx := api.NewContext()
837 838
 	fakeClient := tools.NewFakeEtcdClient(t)
838 839
 	registry := NewTestEtcdRegistry(fakeClient)
839 840
 	endpoints := &api.Endpoints{
... ...
@@ -843,7 +874,7 @@ func TestEtcdGetEndpoints(t *testing.T) {
843 843
 
844 844
 	fakeClient.Set("/registry/services/endpoints/foo", runtime.EncodeOrDie(latest.Codec, endpoints), 0)
845 845
 
846
-	got, err := registry.GetEndpoints("foo")
846
+	got, err := registry.GetEndpoints(ctx, "foo")
847 847
 	if err != nil {
848 848
 		t.Errorf("unexpected error: %v", err)
849 849
 	}
... ...
@@ -854,6 +885,7 @@ func TestEtcdGetEndpoints(t *testing.T) {
854 854
 }
855 855
 
856 856
 func TestEtcdUpdateEndpoints(t *testing.T) {
857
+	ctx := api.NewContext()
857 858
 	fakeClient := tools.NewFakeEtcdClient(t)
858 859
 	fakeClient.TestIndex = true
859 860
 	registry := NewTestEtcdRegistry(fakeClient)
... ...
@@ -864,7 +896,7 @@ func TestEtcdUpdateEndpoints(t *testing.T) {
864 864
 
865 865
 	fakeClient.Set("/registry/services/endpoints/foo", runtime.EncodeOrDie(latest.Codec, &api.Endpoints{}), 0)
866 866
 
867
-	err := registry.UpdateEndpoints(&endpoints)
867
+	err := registry.UpdateEndpoints(ctx, &endpoints)
868 868
 	if err != nil {
869 869
 		t.Errorf("unexpected error: %v", err)
870 870
 	}
... ...
@@ -881,9 +913,10 @@ func TestEtcdUpdateEndpoints(t *testing.T) {
881 881
 }
882 882
 
883 883
 func TestEtcdWatchServices(t *testing.T) {
884
+	ctx := api.NewContext()
884 885
 	fakeClient := tools.NewFakeEtcdClient(t)
885 886
 	registry := NewTestEtcdRegistry(fakeClient)
886
-	watching, err := registry.WatchServices(
887
+	watching, err := registry.WatchServices(ctx,
887 888
 		labels.Everything(),
888 889
 		labels.SelectorFromSet(labels.Set{"ID": "foo"}),
889 890
 		1,
... ...
@@ -908,9 +941,11 @@ func TestEtcdWatchServices(t *testing.T) {
908 908
 }
909 909
 
910 910
 func TestEtcdWatchServicesBadSelector(t *testing.T) {
911
+	ctx := api.NewContext()
911 912
 	fakeClient := tools.NewFakeEtcdClient(t)
912 913
 	registry := NewTestEtcdRegistry(fakeClient)
913 914
 	_, err := registry.WatchServices(
915
+		ctx,
914 916
 		labels.Everything(),
915 917
 		labels.SelectorFromSet(labels.Set{"Field.Selector": "foo"}),
916 918
 		0,
... ...
@@ -920,6 +955,7 @@ func TestEtcdWatchServicesBadSelector(t *testing.T) {
920 920
 	}
921 921
 
922 922
 	_, err = registry.WatchServices(
923
+		ctx,
923 924
 		labels.SelectorFromSet(labels.Set{"Label.Selector": "foo"}),
924 925
 		labels.Everything(),
925 926
 		0,
... ...
@@ -930,9 +966,11 @@ func TestEtcdWatchServicesBadSelector(t *testing.T) {
930 930
 }
931 931
 
932 932
 func TestEtcdWatchEndpoints(t *testing.T) {
933
+	ctx := api.NewContext()
933 934
 	fakeClient := tools.NewFakeEtcdClient(t)
934 935
 	registry := NewTestEtcdRegistry(fakeClient)
935 936
 	watching, err := registry.WatchEndpoints(
937
+		ctx,
936 938
 		labels.Everything(),
937 939
 		labels.SelectorFromSet(labels.Set{"ID": "foo"}),
938 940
 		1,
... ...
@@ -957,9 +995,11 @@ func TestEtcdWatchEndpoints(t *testing.T) {
957 957
 }
958 958
 
959 959
 func TestEtcdWatchEndpointsBadSelector(t *testing.T) {
960
+	ctx := api.NewContext()
960 961
 	fakeClient := tools.NewFakeEtcdClient(t)
961 962
 	registry := NewTestEtcdRegistry(fakeClient)
962 963
 	_, err := registry.WatchEndpoints(
964
+		ctx,
963 965
 		labels.Everything(),
964 966
 		labels.SelectorFromSet(labels.Set{"Field.Selector": "foo"}),
965 967
 		0,
... ...
@@ -969,6 +1009,7 @@ func TestEtcdWatchEndpointsBadSelector(t *testing.T) {
969 969
 	}
970 970
 
971 971
 	_, err = registry.WatchEndpoints(
972
+		ctx,
972 973
 		labels.SelectorFromSet(labels.Set{"Label.Selector": "foo"}),
973 974
 		labels.Everything(),
974 975
 		0,
975 976
deleted file mode 100644
... ...
@@ -1,43 +0,0 @@
1
-/*
2
-Copyright 2014 Google Inc. All rights reserved.
3
-
4
-Licensed under the Apache License, Version 2.0 (the "License");
5
-you may not use this file except in compliance with the License.
6
-You may obtain a copy of the License at
7
-
8
-    http://www.apache.org/licenses/LICENSE-2.0
9
-
10
-Unless required by applicable law or agreed to in writing, software
11
-distributed under the License is distributed on an "AS IS" BASIS,
12
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
-See the License for the specific language governing permissions and
14
-limitations under the License.
15
-*/
16
-
17
-package etcd
18
-
19
-import (
20
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
21
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/service"
22
-)
23
-
24
-type ManifestFactory interface {
25
-	// Make a container object for a given pod, given the machine that the pod is running on.
26
-	MakeManifest(machine string, pod api.Pod) (api.ContainerManifest, error)
27
-}
28
-
29
-type BasicManifestFactory struct {
30
-	serviceRegistry service.Registry
31
-}
32
-
33
-func (b *BasicManifestFactory) MakeManifest(machine string, pod api.Pod) (api.ContainerManifest, error) {
34
-	envVars, err := service.GetServiceEnvironmentVariables(b.serviceRegistry, machine)
35
-	if err != nil {
36
-		return api.ContainerManifest{}, err
37
-	}
38
-	for ix, container := range pod.DesiredState.Manifest.Containers {
39
-		pod.DesiredState.Manifest.ID = pod.ID
40
-		pod.DesiredState.Manifest.Containers[ix].Env = append(container.Env, envVars...)
41
-	}
42
-	return pod.DesiredState.Manifest, nil
43
-}
44 1
deleted file mode 100644
... ...
@@ -1,221 +0,0 @@
1
-/*
2
-Copyright 2014 Google Inc. All rights reserved.
3
-
4
-Licensed under the Apache License, Version 2.0 (the "License");
5
-you may not use this file except in compliance with the License.
6
-You may obtain a copy of the License at
7
-
8
-    http://www.apache.org/licenses/LICENSE-2.0
9
-
10
-Unless required by applicable law or agreed to in writing, software
11
-distributed under the License is distributed on an "AS IS" BASIS,
12
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
-See the License for the specific language governing permissions and
14
-limitations under the License.
15
-*/
16
-
17
-package etcd
18
-
19
-import (
20
-	"reflect"
21
-	"testing"
22
-
23
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
24
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest"
25
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
26
-)
27
-
28
-func TestMakeManifestNoServices(t *testing.T) {
29
-	registry := registrytest.ServiceRegistry{}
30
-	factory := &BasicManifestFactory{
31
-		serviceRegistry: &registry,
32
-	}
33
-
34
-	manifest, err := factory.MakeManifest("machine", api.Pod{
35
-		JSONBase: api.JSONBase{ID: "foobar"},
36
-		DesiredState: api.PodState{
37
-			Manifest: api.ContainerManifest{
38
-				Containers: []api.Container{
39
-					{
40
-						Name: "foo",
41
-					},
42
-				},
43
-			},
44
-		},
45
-	})
46
-	if err != nil {
47
-		t.Errorf("unexpected error: %v", err)
48
-	}
49
-
50
-	container := manifest.Containers[0]
51
-	if len(container.Env) != 1 ||
52
-		container.Env[0].Name != "SERVICE_HOST" ||
53
-		container.Env[0].Value != "machine" {
54
-		t.Errorf("Expected one env vars, got: %#v", manifest)
55
-	}
56
-	if manifest.ID != "foobar" {
57
-		t.Errorf("Failed to assign ID to manifest: %#v", manifest.ID)
58
-	}
59
-}
60
-
61
-func TestMakeManifestServices(t *testing.T) {
62
-	registry := registrytest.ServiceRegistry{
63
-		List: api.ServiceList{
64
-			Items: []api.Service{
65
-				{
66
-					JSONBase: api.JSONBase{ID: "test"},
67
-					Port:     8080,
68
-					ContainerPort: util.IntOrString{
69
-						Kind:   util.IntstrInt,
70
-						IntVal: 900,
71
-					},
72
-				},
73
-			},
74
-		},
75
-	}
76
-	factory := &BasicManifestFactory{
77
-		serviceRegistry: &registry,
78
-	}
79
-
80
-	manifest, err := factory.MakeManifest("machine", api.Pod{
81
-		DesiredState: api.PodState{
82
-			Manifest: api.ContainerManifest{
83
-				Containers: []api.Container{
84
-					{
85
-						Name: "foo",
86
-					},
87
-				},
88
-			},
89
-		},
90
-	})
91
-	if err != nil {
92
-		t.Errorf("unexpected error: %v", err)
93
-	}
94
-
95
-	container := manifest.Containers[0]
96
-	envs := []api.EnvVar{
97
-		{
98
-			Name:  "TEST_SERVICE_PORT",
99
-			Value: "8080",
100
-		},
101
-		{
102
-			Name:  "TEST_PORT",
103
-			Value: "tcp://machine:8080",
104
-		},
105
-		{
106
-			Name:  "TEST_PORT_900_TCP",
107
-			Value: "tcp://machine:8080",
108
-		},
109
-		{
110
-			Name:  "TEST_PORT_900_TCP_PROTO",
111
-			Value: "tcp",
112
-		},
113
-		{
114
-			Name:  "TEST_PORT_900_TCP_PORT",
115
-			Value: "8080",
116
-		},
117
-		{
118
-			Name:  "TEST_PORT_900_TCP_ADDR",
119
-			Value: "machine",
120
-		},
121
-		{
122
-			Name:  "SERVICE_HOST",
123
-			Value: "machine",
124
-		},
125
-	}
126
-	if len(container.Env) != 7 {
127
-		t.Errorf("Expected 7 env vars, got %d: %#v", len(container.Env), manifest)
128
-		return
129
-	}
130
-	for ix := range container.Env {
131
-		if !reflect.DeepEqual(envs[ix], container.Env[ix]) {
132
-			t.Errorf("expected %#v, got %#v", envs[ix], container.Env[ix])
133
-		}
134
-	}
135
-}
136
-
137
-func TestMakeManifestServicesExistingEnvVar(t *testing.T) {
138
-	registry := registrytest.ServiceRegistry{
139
-		List: api.ServiceList{
140
-			Items: []api.Service{
141
-				{
142
-					JSONBase: api.JSONBase{ID: "test"},
143
-					Port:     8080,
144
-					ContainerPort: util.IntOrString{
145
-						Kind:   util.IntstrInt,
146
-						IntVal: 900,
147
-					},
148
-				},
149
-			},
150
-		},
151
-	}
152
-	factory := &BasicManifestFactory{
153
-		serviceRegistry: &registry,
154
-	}
155
-
156
-	manifest, err := factory.MakeManifest("machine", api.Pod{
157
-		DesiredState: api.PodState{
158
-			Manifest: api.ContainerManifest{
159
-				Containers: []api.Container{
160
-					{
161
-						Env: []api.EnvVar{
162
-							{
163
-								Name:  "foo",
164
-								Value: "bar",
165
-							},
166
-						},
167
-					},
168
-				},
169
-			},
170
-		},
171
-	})
172
-	if err != nil {
173
-		t.Errorf("unexpected error: %v", err)
174
-	}
175
-
176
-	container := manifest.Containers[0]
177
-
178
-	envs := []api.EnvVar{
179
-		{
180
-			Name:  "foo",
181
-			Value: "bar",
182
-		},
183
-		{
184
-			Name:  "TEST_SERVICE_PORT",
185
-			Value: "8080",
186
-		},
187
-		{
188
-			Name:  "TEST_PORT",
189
-			Value: "tcp://machine:8080",
190
-		},
191
-		{
192
-			Name:  "TEST_PORT_900_TCP",
193
-			Value: "tcp://machine:8080",
194
-		},
195
-		{
196
-			Name:  "TEST_PORT_900_TCP_PROTO",
197
-			Value: "tcp",
198
-		},
199
-		{
200
-			Name:  "TEST_PORT_900_TCP_PORT",
201
-			Value: "8080",
202
-		},
203
-		{
204
-			Name:  "TEST_PORT_900_TCP_ADDR",
205
-			Value: "machine",
206
-		},
207
-		{
208
-			Name:  "SERVICE_HOST",
209
-			Value: "machine",
210
-		},
211
-	}
212
-	if len(container.Env) != 8 {
213
-		t.Errorf("Expected 8 env vars, got: %#v", manifest)
214
-		return
215
-	}
216
-	for ix := range container.Env {
217
-		if !reflect.DeepEqual(envs[ix], container.Env[ix]) {
218
-			t.Errorf("expected %#v, got %#v", envs[ix], container.Env[ix])
219
-		}
220
-	}
221
-}
... ...
@@ -38,7 +38,7 @@ func NewREST(m Registry) *REST {
38 38
 	}
39 39
 }
40 40
 
41
-func (rs *REST) Create(obj runtime.Object) (<-chan runtime.Object, error) {
41
+func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) {
42 42
 	minion, ok := obj.(*api.Minion)
43 43
 	if !ok {
44 44
 		return nil, fmt.Errorf("not a minion: %#v", obj)
... ...
@@ -65,7 +65,7 @@ func (rs *REST) Create(obj runtime.Object) (<-chan runtime.Object, error) {
65 65
 	}), nil
66 66
 }
67 67
 
68
-func (rs *REST) Delete(id string) (<-chan runtime.Object, error) {
68
+func (rs *REST) Delete(ctx api.Context, id string) (<-chan runtime.Object, error) {
69 69
 	exists, err := rs.registry.Contains(id)
70 70
 	if !exists {
71 71
 		return nil, ErrDoesNotExist
... ...
@@ -78,7 +78,7 @@ func (rs *REST) Delete(id string) (<-chan runtime.Object, error) {
78 78
 	}), nil
79 79
 }
80 80
 
81
-func (rs *REST) Get(id string) (runtime.Object, error) {
81
+func (rs *REST) Get(ctx api.Context, id string) (runtime.Object, error) {
82 82
 	exists, err := rs.registry.Contains(id)
83 83
 	if !exists {
84 84
 		return nil, ErrDoesNotExist
... ...
@@ -86,7 +86,7 @@ func (rs *REST) Get(id string) (runtime.Object, error) {
86 86
 	return rs.toApiMinion(id), err
87 87
 }
88 88
 
89
-func (rs *REST) List(label, field labels.Selector) (runtime.Object, error) {
89
+func (rs *REST) List(ctx api.Context, label, field labels.Selector) (runtime.Object, error) {
90 90
 	nameList, err := rs.registry.List()
91 91
 	if err != nil {
92 92
 		return nil, err
... ...
@@ -102,7 +102,7 @@ func (*REST) New() runtime.Object {
102 102
 	return &api.Minion{}
103 103
 }
104 104
 
105
-func (rs *REST) Update(minion runtime.Object) (<-chan runtime.Object, error) {
105
+func (rs *REST) Update(ctx api.Context, minion runtime.Object) (<-chan runtime.Object, error) {
106 106
 	return nil, fmt.Errorf("Minions can only be created (inserted) and deleted.")
107 107
 }
108 108
 
... ...
@@ -27,18 +27,18 @@ import (
27 27
 func TestMinionREST(t *testing.T) {
28 28
 	m := NewRegistry([]string{"foo", "bar"})
29 29
 	ms := NewREST(m)
30
-
31
-	if obj, err := ms.Get("foo"); err != nil || obj.(*api.Minion).ID != "foo" {
30
+	ctx := api.NewContext()
31
+	if obj, err := ms.Get(ctx, "foo"); err != nil || obj.(*api.Minion).ID != "foo" {
32 32
 		t.Errorf("missing expected object")
33 33
 	}
34
-	if obj, err := ms.Get("bar"); err != nil || obj.(*api.Minion).ID != "bar" {
34
+	if obj, err := ms.Get(ctx, "bar"); err != nil || obj.(*api.Minion).ID != "bar" {
35 35
 		t.Errorf("missing expected object")
36 36
 	}
37
-	if _, err := ms.Get("baz"); err != ErrDoesNotExist {
37
+	if _, err := ms.Get(ctx, "baz"); err != ErrDoesNotExist {
38 38
 		t.Errorf("has unexpected object")
39 39
 	}
40 40
 
41
-	c, err := ms.Create(&api.Minion{JSONBase: api.JSONBase{ID: "baz"}})
41
+	c, err := ms.Create(ctx, &api.Minion{JSONBase: api.JSONBase{ID: "baz"}})
42 42
 	if err != nil {
43 43
 		t.Errorf("insert failed")
44 44
 	}
... ...
@@ -46,11 +46,11 @@ func TestMinionREST(t *testing.T) {
46 46
 	if m, ok := obj.(*api.Minion); !ok || m.ID != "baz" {
47 47
 		t.Errorf("insert return value was weird: %#v", obj)
48 48
 	}
49
-	if obj, err := ms.Get("baz"); err != nil || obj.(*api.Minion).ID != "baz" {
49
+	if obj, err := ms.Get(ctx, "baz"); err != nil || obj.(*api.Minion).ID != "baz" {
50 50
 		t.Errorf("insert didn't actually insert")
51 51
 	}
52 52
 
53
-	c, err = ms.Delete("bar")
53
+	c, err = ms.Delete(ctx, "bar")
54 54
 	if err != nil {
55 55
 		t.Errorf("delete failed")
56 56
 	}
... ...
@@ -58,16 +58,16 @@ func TestMinionREST(t *testing.T) {
58 58
 	if s, ok := obj.(*api.Status); !ok || s.Status != api.StatusSuccess {
59 59
 		t.Errorf("delete return value was weird: %#v", obj)
60 60
 	}
61
-	if _, err := ms.Get("bar"); err != ErrDoesNotExist {
61
+	if _, err := ms.Get(ctx, "bar"); err != ErrDoesNotExist {
62 62
 		t.Errorf("delete didn't actually delete")
63 63
 	}
64 64
 
65
-	_, err = ms.Delete("bar")
65
+	_, err = ms.Delete(ctx, "bar")
66 66
 	if err != ErrDoesNotExist {
67 67
 		t.Errorf("delete returned wrong error")
68 68
 	}
69 69
 
70
-	list, err := ms.List(labels.Everything(), labels.Everything())
70
+	list, err := ms.List(ctx, labels.Everything(), labels.Everything())
71 71
 	if err != nil {
72 72
 		t.Errorf("got error calling List")
73 73
 	}
74 74
new file mode 100644
... ...
@@ -0,0 +1,44 @@
0
+/*
1
+Copyright 2014 Google Inc. All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package pod
17
+
18
+import (
19
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
20
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/service"
21
+)
22
+
23
+type ManifestFactory interface {
24
+	// Make a container object for a given pod, given the machine that the pod is running on.
25
+	MakeManifest(machine string, pod api.Pod) (api.ContainerManifest, error)
26
+}
27
+
28
+type BasicManifestFactory struct {
29
+	// TODO: this should really point at the API rather than a registry
30
+	ServiceRegistry service.Registry
31
+}
32
+
33
+func (b *BasicManifestFactory) MakeManifest(machine string, pod api.Pod) (api.ContainerManifest, error) {
34
+	envVars, err := service.GetServiceEnvironmentVariables(api.NewContext(), b.ServiceRegistry, machine)
35
+	if err != nil {
36
+		return api.ContainerManifest{}, err
37
+	}
38
+	for ix, container := range pod.DesiredState.Manifest.Containers {
39
+		pod.DesiredState.Manifest.ID = pod.ID
40
+		pod.DesiredState.Manifest.Containers[ix].Env = append(container.Env, envVars...)
41
+	}
42
+	return pod.DesiredState.Manifest, nil
43
+}
0 44
new file mode 100644
... ...
@@ -0,0 +1,229 @@
0
+/*
1
+Copyright 2014 Google Inc. All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package pod
17
+
18
+import (
19
+	"reflect"
20
+	"testing"
21
+
22
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
23
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest"
24
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
25
+)
26
+
27
+func TestMakeManifestNoServices(t *testing.T) {
28
+	registry := registrytest.ServiceRegistry{}
29
+	factory := &BasicManifestFactory{
30
+		ServiceRegistry: &registry,
31
+	}
32
+
33
+	manifest, err := factory.MakeManifest("machine", api.Pod{
34
+		JSONBase: api.JSONBase{ID: "foobar"},
35
+		DesiredState: api.PodState{
36
+			Manifest: api.ContainerManifest{
37
+				Containers: []api.Container{
38
+					{
39
+						Name: "foo",
40
+					},
41
+				},
42
+			},
43
+		},
44
+	})
45
+	if err != nil {
46
+		t.Errorf("unexpected error: %v", err)
47
+	}
48
+
49
+	container := manifest.Containers[0]
50
+	if len(container.Env) != 1 ||
51
+		container.Env[0].Name != "SERVICE_HOST" ||
52
+		container.Env[0].Value != "machine" {
53
+		t.Errorf("Expected one env vars, got: %#v", manifest)
54
+	}
55
+	if manifest.ID != "foobar" {
56
+		t.Errorf("Failed to assign ID to manifest: %#v", manifest.ID)
57
+	}
58
+}
59
+
60
+func TestMakeManifestServices(t *testing.T) {
61
+	registry := registrytest.ServiceRegistry{
62
+		List: api.ServiceList{
63
+			Items: []api.Service{
64
+				{
65
+					JSONBase: api.JSONBase{ID: "test"},
66
+					Port:     8080,
67
+					ContainerPort: util.IntOrString{
68
+						Kind:   util.IntstrInt,
69
+						IntVal: 900,
70
+					},
71
+				},
72
+			},
73
+		},
74
+	}
75
+	factory := &BasicManifestFactory{
76
+		ServiceRegistry: &registry,
77
+	}
78
+
79
+	manifest, err := factory.MakeManifest("machine", api.Pod{
80
+		DesiredState: api.PodState{
81
+			Manifest: api.ContainerManifest{
82
+				Containers: []api.Container{
83
+					{
84
+						Name: "foo",
85
+					},
86
+				},
87
+			},
88
+		},
89
+	})
90
+	if err != nil {
91
+		t.Errorf("unexpected error: %v", err)
92
+	}
93
+
94
+	container := manifest.Containers[0]
95
+	envs := []api.EnvVar{
96
+		{
97
+			Name:  "TEST_SERVICE_HOST",
98
+			Value: "machine",
99
+		},
100
+		{
101
+			Name:  "TEST_SERVICE_PORT",
102
+			Value: "8080",
103
+		},
104
+		{
105
+			Name:  "TEST_PORT",
106
+			Value: "tcp://machine:8080",
107
+		},
108
+		{
109
+			Name:  "TEST_PORT_8080_TCP",
110
+			Value: "tcp://machine:8080",
111
+		},
112
+		{
113
+			Name:  "TEST_PORT_8080_TCP_PROTO",
114
+			Value: "tcp",
115
+		},
116
+		{
117
+			Name:  "TEST_PORT_8080_TCP_PORT",
118
+			Value: "8080",
119
+		},
120
+		{
121
+			Name:  "TEST_PORT_8080_TCP_ADDR",
122
+			Value: "machine",
123
+		},
124
+		{
125
+			Name:  "SERVICE_HOST",
126
+			Value: "machine",
127
+		},
128
+	}
129
+	if len(container.Env) != len(envs) {
130
+		t.Errorf("Expected %d env vars, got %d: %#v", len(envs), len(container.Env), manifest)
131
+		return
132
+	}
133
+	for ix := range container.Env {
134
+		if !reflect.DeepEqual(envs[ix], container.Env[ix]) {
135
+			t.Errorf("expected %#v, got %#v", envs[ix], container.Env[ix])
136
+		}
137
+	}
138
+}
139
+
140
+func TestMakeManifestServicesExistingEnvVar(t *testing.T) {
141
+	registry := registrytest.ServiceRegistry{
142
+		List: api.ServiceList{
143
+			Items: []api.Service{
144
+				{
145
+					JSONBase: api.JSONBase{ID: "test"},
146
+					Port:     8080,
147
+					ContainerPort: util.IntOrString{
148
+						Kind:   util.IntstrInt,
149
+						IntVal: 900,
150
+					},
151
+				},
152
+			},
153
+		},
154
+	}
155
+	factory := &BasicManifestFactory{
156
+		ServiceRegistry: &registry,
157
+	}
158
+
159
+	manifest, err := factory.MakeManifest("machine", api.Pod{
160
+		DesiredState: api.PodState{
161
+			Manifest: api.ContainerManifest{
162
+				Containers: []api.Container{
163
+					{
164
+						Env: []api.EnvVar{
165
+							{
166
+								Name:  "foo",
167
+								Value: "bar",
168
+							},
169
+						},
170
+					},
171
+				},
172
+			},
173
+		},
174
+	})
175
+	if err != nil {
176
+		t.Errorf("unexpected error: %v", err)
177
+	}
178
+
179
+	container := manifest.Containers[0]
180
+
181
+	envs := []api.EnvVar{
182
+		{
183
+			Name:  "foo",
184
+			Value: "bar",
185
+		},
186
+		{
187
+			Name:  "TEST_SERVICE_HOST",
188
+			Value: "machine",
189
+		},
190
+		{
191
+			Name:  "TEST_SERVICE_PORT",
192
+			Value: "8080",
193
+		},
194
+		{
195
+			Name:  "TEST_PORT",
196
+			Value: "tcp://machine:8080",
197
+		},
198
+		{
199
+			Name:  "TEST_PORT_8080_TCP",
200
+			Value: "tcp://machine:8080",
201
+		},
202
+		{
203
+			Name:  "TEST_PORT_8080_TCP_PROTO",
204
+			Value: "tcp",
205
+		},
206
+		{
207
+			Name:  "TEST_PORT_8080_TCP_PORT",
208
+			Value: "8080",
209
+		},
210
+		{
211
+			Name:  "TEST_PORT_8080_TCP_ADDR",
212
+			Value: "machine",
213
+		},
214
+		{
215
+			Name:  "SERVICE_HOST",
216
+			Value: "machine",
217
+		},
218
+	}
219
+	if len(container.Env) != len(envs) {
220
+		t.Errorf("Expected %d env vars, got: %#v", len(envs), manifest)
221
+		return
222
+	}
223
+	for ix := range container.Env {
224
+		if !reflect.DeepEqual(envs[ix], container.Env[ix]) {
225
+			t.Errorf("expected %#v, got %#v", envs[ix], container.Env[ix])
226
+		}
227
+	}
228
+}
... ...
@@ -25,17 +25,17 @@ import (
25 25
 // Registry is an interface implemented by things that know how to store Pod objects.
26 26
 type Registry interface {
27 27
 	// ListPods obtains a list of pods having labels which match selector.
28
-	ListPods(selector labels.Selector) (*api.PodList, error)
28
+	ListPods(ctx api.Context, selector labels.Selector) (*api.PodList, error)
29 29
 	// ListPodsPredicate obtains a list of pods for which filter returns true.
30
-	ListPodsPredicate(filter func(*api.Pod) bool) (*api.PodList, error)
30
+	ListPodsPredicate(ctx api.Context, filter func(*api.Pod) bool) (*api.PodList, error)
31 31
 	// Watch for new/changed/deleted pods
32
-	WatchPods(resourceVersion uint64, filter func(*api.Pod) bool) (watch.Interface, error)
32
+	WatchPods(ctx api.Context, resourceVersion uint64, filter func(*api.Pod) bool) (watch.Interface, error)
33 33
 	// Get a specific pod
34
-	GetPod(podID string) (*api.Pod, error)
34
+	GetPod(ctx api.Context, podID string) (*api.Pod, error)
35 35
 	// Create a pod based on a specification.
36
-	CreatePod(pod *api.Pod) error
36
+	CreatePod(ctx api.Context, pod *api.Pod) error
37 37
 	// Update an existing pod
38
-	UpdatePod(pod *api.Pod) error
38
+	UpdatePod(ctx api.Context, pod *api.Pod) error
39 39
 	// Delete an existing pod
40
-	DeletePod(podID string) error
40
+	DeletePod(ctx api.Context, podID string) error
41 41
 }
... ...
@@ -67,8 +67,11 @@ func NewREST(config *RESTConfig) *REST {
67 67
 	}
68 68
 }
69 69
 
70
-func (rs *REST) Create(obj runtime.Object) (<-chan runtime.Object, error) {
70
+func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) {
71 71
 	pod := obj.(*api.Pod)
72
+	if !api.ValidNamespace(ctx, &pod.JSONBase) {
73
+		return nil, errors.NewConflict("pod", pod.Namespace, fmt.Errorf("Pod.Namespace does not match the provided context"))
74
+	}
72 75
 	pod.DesiredState.Manifest.UUID = uuid.NewUUID().String()
73 76
 	if len(pod.ID) == 0 {
74 77
 		pod.ID = pod.DesiredState.Manifest.UUID
... ...
@@ -77,25 +80,24 @@ func (rs *REST) Create(obj runtime.Object) (<-chan runtime.Object, error) {
77 77
 	if errs := validation.ValidatePod(pod); len(errs) > 0 {
78 78
 		return nil, errors.NewInvalid("pod", pod.ID, errs)
79 79
 	}
80
-
81 80
 	pod.CreationTimestamp = util.Now()
82 81
 
83 82
 	return apiserver.MakeAsync(func() (runtime.Object, error) {
84
-		if err := rs.registry.CreatePod(pod); err != nil {
83
+		if err := rs.registry.CreatePod(ctx, pod); err != nil {
85 84
 			return nil, err
86 85
 		}
87
-		return rs.registry.GetPod(pod.ID)
86
+		return rs.registry.GetPod(ctx, pod.ID)
88 87
 	}), nil
89 88
 }
90 89
 
91
-func (rs *REST) Delete(id string) (<-chan runtime.Object, error) {
90
+func (rs *REST) Delete(ctx api.Context, id string) (<-chan runtime.Object, error) {
92 91
 	return apiserver.MakeAsync(func() (runtime.Object, error) {
93
-		return &api.Status{Status: api.StatusSuccess}, rs.registry.DeletePod(id)
92
+		return &api.Status{Status: api.StatusSuccess}, rs.registry.DeletePod(ctx, id)
94 93
 	}), nil
95 94
 }
96 95
 
97
-func (rs *REST) Get(id string) (runtime.Object, error) {
98
-	pod, err := rs.registry.GetPod(id)
96
+func (rs *REST) Get(ctx api.Context, id string) (runtime.Object, error) {
97
+	pod, err := rs.registry.GetPod(ctx, id)
99 98
 	if err != nil {
100 99
 		return pod, err
101 100
 	}
... ...
@@ -131,8 +133,8 @@ func (rs *REST) filterFunc(label, field labels.Selector) func(*api.Pod) bool {
131 131
 	}
132 132
 }
133 133
 
134
-func (rs *REST) List(label, field labels.Selector) (runtime.Object, error) {
135
-	pods, err := rs.registry.ListPodsPredicate(rs.filterFunc(label, field))
134
+func (rs *REST) List(ctx api.Context, label, field labels.Selector) (runtime.Object, error) {
135
+	pods, err := rs.registry.ListPodsPredicate(ctx, rs.filterFunc(label, field))
136 136
 	if err == nil {
137 137
 		for i := range pods.Items {
138 138
 			pod := &pods.Items[i]
... ...
@@ -149,24 +151,27 @@ func (rs *REST) List(label, field labels.Selector) (runtime.Object, error) {
149 149
 }
150 150
 
151 151
 // Watch begins watching for new, changed, or deleted pods.
152
-func (rs *REST) Watch(label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
153
-	return rs.registry.WatchPods(resourceVersion, rs.filterFunc(label, field))
152
+func (rs *REST) Watch(ctx api.Context, label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
153
+	return rs.registry.WatchPods(ctx, resourceVersion, rs.filterFunc(label, field))
154 154
 }
155 155
 
156 156
 func (*REST) New() runtime.Object {
157 157
 	return &api.Pod{}
158 158
 }
159 159
 
160
-func (rs *REST) Update(obj runtime.Object) (<-chan runtime.Object, error) {
160
+func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) {
161 161
 	pod := obj.(*api.Pod)
162
+	if !api.ValidNamespace(ctx, &pod.JSONBase) {
163
+		return nil, errors.NewConflict("pod", pod.Namespace, fmt.Errorf("Pod.Namespace does not match the provided context"))
164
+	}
162 165
 	if errs := validation.ValidatePod(pod); len(errs) > 0 {
163 166
 		return nil, errors.NewInvalid("pod", pod.ID, errs)
164 167
 	}
165 168
 	return apiserver.MakeAsync(func() (runtime.Object, error) {
166
-		if err := rs.registry.UpdatePod(pod); err != nil {
169
+		if err := rs.registry.UpdatePod(ctx, pod); err != nil {
167 170
 			return nil, err
168 171
 		}
169
-		return rs.registry.GetPod(pod.ID)
172
+		return rs.registry.GetPod(ctx, pod.ID)
170 173
 	}), nil
171 174
 }
172 175
 
... ...
@@ -196,8 +201,8 @@ func (rs *REST) fillPodInfo(pod *api.Pod) {
196 196
 		pod.CurrentState.Info = info
197 197
 		netContainerInfo, ok := info["net"]
198 198
 		if ok {
199
-			if netContainerInfo.NetworkSettings != nil {
200
-				pod.CurrentState.PodIP = netContainerInfo.NetworkSettings.IPAddress
199
+			if netContainerInfo.DetailInfo.NetworkSettings != nil {
200
+				pod.CurrentState.PodIP = netContainerInfo.DetailInfo.NetworkSettings.IPAddress
201 201
 			} else {
202 202
 				glog.Warningf("No network settings: %#v", netContainerInfo)
203 203
 			}
... ...
@@ -253,11 +258,13 @@ func getPodStatus(pod *api.Pod, minions client.MinionInterface) (api.PodStatus,
253 253
 	stopped := 0
254 254
 	unknown := 0
255 255
 	for _, container := range pod.DesiredState.Manifest.Containers {
256
-		if info, ok := pod.CurrentState.Info[container.Name]; ok {
257
-			if info.State.Running {
256
+		if containerStatus, ok := pod.CurrentState.Info[container.Name]; ok {
257
+			if containerStatus.State.Running != nil {
258 258
 				running++
259
-			} else {
259
+			} else if containerStatus.State.Termination != nil {
260 260
 				stopped++
261
+			} else {
262
+				unknown++
261 263
 			}
262 264
 		} else {
263 265
 			unknown++
... ...
@@ -275,9 +282,9 @@ func getPodStatus(pod *api.Pod, minions client.MinionInterface) (api.PodStatus,
275 275
 	}
276 276
 }
277 277
 
278
-func (rs *REST) waitForPodRunning(pod *api.Pod) (runtime.Object, error) {
278
+func (rs *REST) waitForPodRunning(ctx api.Context, pod *api.Pod) (runtime.Object, error) {
279 279
 	for {
280
-		podObj, err := rs.Get(pod.ID)
280
+		podObj, err := rs.Get(ctx, pod.ID)
281 281
 		if err != nil || podObj == nil {
282 282
 			return nil, err
283 283
 		}
... ...
@@ -69,7 +69,8 @@ func TestCreatePodRegistryError(t *testing.T) {
69 69
 		},
70 70
 	}
71 71
 	pod := &api.Pod{DesiredState: desiredState}
72
-	ch, err := storage.Create(pod)
72
+	ctx := api.NewDefaultContext()
73
+	ch, err := storage.Create(ctx, pod)
73 74
 	if err != nil {
74 75
 		t.Errorf("Expected %#v, Got %#v", nil, err)
75 76
 	}
... ...
@@ -88,7 +89,8 @@ func TestCreatePodSetsIds(t *testing.T) {
88 88
 		},
89 89
 	}
90 90
 	pod := &api.Pod{DesiredState: desiredState}
91
-	ch, err := storage.Create(pod)
91
+	ctx := api.NewDefaultContext()
92
+	ch, err := storage.Create(ctx, pod)
92 93
 	if err != nil {
93 94
 		t.Errorf("Expected %#v, Got %#v", nil, err)
94 95
 	}
... ...
@@ -114,7 +116,8 @@ func TestCreatePodSetsUUIDs(t *testing.T) {
114 114
 		},
115 115
 	}
116 116
 	pod := &api.Pod{DesiredState: desiredState}
117
-	ch, err := storage.Create(pod)
117
+	ctx := api.NewDefaultContext()
118
+	ch, err := storage.Create(ctx, pod)
118 119
 	if err != nil {
119 120
 		t.Errorf("Expected %#v, Got %#v", nil, err)
120 121
 	}
... ...
@@ -131,7 +134,8 @@ func TestListPodsError(t *testing.T) {
131 131
 	storage := REST{
132 132
 		registry: podRegistry,
133 133
 	}
134
-	pods, err := storage.List(labels.Everything(), labels.Everything())
134
+	ctx := api.NewContext()
135
+	pods, err := storage.List(ctx, labels.Everything(), labels.Everything())
135 136
 	if err != podRegistry.Err {
136 137
 		t.Errorf("Expected %#v, Got %#v", podRegistry.Err, err)
137 138
 	}
... ...
@@ -145,7 +149,8 @@ func TestListEmptyPodList(t *testing.T) {
145 145
 	storage := REST{
146 146
 		registry: podRegistry,
147 147
 	}
148
-	pods, err := storage.List(labels.Everything(), labels.Everything())
148
+	ctx := api.NewContext()
149
+	pods, err := storage.List(ctx, labels.Everything(), labels.Everything())
149 150
 	if err != nil {
150 151
 		t.Errorf("unexpected error: %v", err)
151 152
 	}
... ...
@@ -177,7 +182,8 @@ func TestListPodList(t *testing.T) {
177 177
 	storage := REST{
178 178
 		registry: podRegistry,
179 179
 	}
180
-	podsObj, err := storage.List(labels.Everything(), labels.Everything())
180
+	ctx := api.NewContext()
181
+	podsObj, err := storage.List(ctx, labels.Everything(), labels.Everything())
181 182
 	pods := podsObj.(*api.PodList)
182 183
 	if err != nil {
183 184
 		t.Errorf("unexpected error: %v", err)
... ...
@@ -217,6 +223,7 @@ func TestListPodListSelection(t *testing.T) {
217 217
 	storage := REST{
218 218
 		registry: podRegistry,
219 219
 	}
220
+	ctx := api.NewContext()
220 221
 
221 222
 	table := []struct {
222 223
 		label, field string
... ...
@@ -256,7 +263,7 @@ func TestListPodListSelection(t *testing.T) {
256 256
 			t.Errorf("unexpected error: %v", err)
257 257
 			continue
258 258
 		}
259
-		podsObj, err := storage.List(label, field)
259
+		podsObj, err := storage.List(ctx, label, field)
260 260
 		if err != nil {
261 261
 			t.Errorf("unexpected error: %v", err)
262 262
 		}
... ...
@@ -305,7 +312,8 @@ func TestGetPod(t *testing.T) {
305 305
 	storage := REST{
306 306
 		registry: podRegistry,
307 307
 	}
308
-	obj, err := storage.Get("foo")
308
+	ctx := api.NewContext()
309
+	obj, err := storage.Get(ctx, "foo")
309 310
 	pod := obj.(*api.Pod)
310 311
 	if err != nil {
311 312
 		t.Errorf("unexpected error: %v", err)
... ...
@@ -324,7 +332,8 @@ func TestGetPodCloud(t *testing.T) {
324 324
 		registry:      podRegistry,
325 325
 		cloudProvider: fakeCloud,
326 326
 	}
327
-	obj, err := storage.Get("foo")
327
+	ctx := api.NewContext()
328
+	obj, err := storage.Get(ctx, "foo")
328 329
 	pod := obj.(*api.Pod)
329 330
 	if err != nil {
330 331
 		t.Errorf("unexpected error: %v", err)
... ...
@@ -360,14 +369,14 @@ func TestMakePodStatus(t *testing.T) {
360 360
 	currentState := api.PodState{
361 361
 		Host: "machine",
362 362
 	}
363
-	runningState := docker.Container{
364
-		State: docker.State{
365
-			Running: true,
363
+	runningState := api.ContainerStatus{
364
+		State: api.ContainerState{
365
+			Running: &api.ContainerStateRunning{},
366 366
 		},
367 367
 	}
368
-	stoppedState := docker.Container{
369
-		State: docker.State{
370
-			Running: false,
368
+	stoppedState := api.ContainerStatus{
369
+		State: api.ContainerState{
370
+			Termination: &api.ContainerStateTerminated{},
371 371
 		},
372 372
 	}
373 373
 
... ...
@@ -376,14 +385,7 @@ func TestMakePodStatus(t *testing.T) {
376 376
 		status api.PodStatus
377 377
 		test   string
378 378
 	}{
379
-		{
380
-			&api.Pod{
381
-				DesiredState: desiredState,
382
-				CurrentState: currentState,
383
-			},
384
-			api.PodWaiting,
385
-			"waiting",
386
-		},
379
+		{&api.Pod{DesiredState: desiredState, CurrentState: currentState}, api.PodWaiting, "waiting"},
387 380
 		{
388 381
 			&api.Pod{
389 382
 				DesiredState: desiredState,
... ...
@@ -398,7 +400,7 @@ func TestMakePodStatus(t *testing.T) {
398 398
 			&api.Pod{
399 399
 				DesiredState: desiredState,
400 400
 				CurrentState: api.PodState{
401
-					Info: map[string]docker.Container{
401
+					Info: map[string]api.ContainerStatus{
402 402
 						"containerA": runningState,
403 403
 						"containerB": runningState,
404 404
 					},
... ...
@@ -412,7 +414,7 @@ func TestMakePodStatus(t *testing.T) {
412 412
 			&api.Pod{
413 413
 				DesiredState: desiredState,
414 414
 				CurrentState: api.PodState{
415
-					Info: map[string]docker.Container{
415
+					Info: map[string]api.ContainerStatus{
416 416
 						"containerA": runningState,
417 417
 						"containerB": runningState,
418 418
 					},
... ...
@@ -426,7 +428,7 @@ func TestMakePodStatus(t *testing.T) {
426 426
 			&api.Pod{
427 427
 				DesiredState: desiredState,
428 428
 				CurrentState: api.PodState{
429
-					Info: map[string]docker.Container{
429
+					Info: map[string]api.ContainerStatus{
430 430
 						"containerA": stoppedState,
431 431
 						"containerB": stoppedState,
432 432
 					},
... ...
@@ -440,7 +442,7 @@ func TestMakePodStatus(t *testing.T) {
440 440
 			&api.Pod{
441 441
 				DesiredState: desiredState,
442 442
 				CurrentState: api.PodState{
443
-					Info: map[string]docker.Container{
443
+					Info: map[string]api.ContainerStatus{
444 444
 						"containerA": stoppedState,
445 445
 						"containerB": stoppedState,
446 446
 					},
... ...
@@ -454,7 +456,7 @@ func TestMakePodStatus(t *testing.T) {
454 454
 			&api.Pod{
455 455
 				DesiredState: desiredState,
456 456
 				CurrentState: api.PodState{
457
-					Info: map[string]docker.Container{
457
+					Info: map[string]api.ContainerStatus{
458 458
 						"containerA": runningState,
459 459
 						"containerB": stoppedState,
460 460
 					},
... ...
@@ -468,7 +470,7 @@ func TestMakePodStatus(t *testing.T) {
468 468
 			&api.Pod{
469 469
 				DesiredState: desiredState,
470 470
 				CurrentState: api.PodState{
471
-					Info: map[string]docker.Container{
471
+					Info: map[string]api.ContainerStatus{
472 472
 						"containerA": runningState,
473 473
 					},
474 474
 					Host: "machine",
... ...
@@ -494,8 +496,9 @@ func TestPodStorageValidatesCreate(t *testing.T) {
494 494
 	storage := REST{
495 495
 		registry: podRegistry,
496 496
 	}
497
+	ctx := api.NewDefaultContext()
497 498
 	pod := &api.Pod{}
498
-	c, err := storage.Create(pod)
499
+	c, err := storage.Create(ctx, pod)
499 500
 	if c != nil {
500 501
 		t.Errorf("Expected nil channel")
501 502
 	}
... ...
@@ -510,8 +513,9 @@ func TestPodStorageValidatesUpdate(t *testing.T) {
510 510
 	storage := REST{
511 511
 		registry: podRegistry,
512 512
 	}
513
+	ctx := api.NewDefaultContext()
513 514
 	pod := &api.Pod{}
514
-	c, err := storage.Update(pod)
515
+	c, err := storage.Update(ctx, pod)
515 516
 	if c != nil {
516 517
 		t.Errorf("Expected nil channel")
517 518
 	}
... ...
@@ -541,7 +545,8 @@ func TestCreatePod(t *testing.T) {
541 541
 		JSONBase:     api.JSONBase{ID: "foo"},
542 542
 		DesiredState: desiredState,
543 543
 	}
544
-	channel, err := storage.Create(pod)
544
+	ctx := api.NewDefaultContext()
545
+	channel, err := storage.Create(ctx, pod)
545 546
 	if err != nil {
546 547
 		t.Errorf("unexpected error: %v", err)
547 548
 	}
... ...
@@ -566,12 +571,14 @@ func (f *FakePodInfoGetter) GetPodInfo(host, podID string) (api.PodInfo, error)
566 566
 func TestFillPodInfo(t *testing.T) {
567 567
 	expectedIP := "1.2.3.4"
568 568
 	fakeGetter := FakePodInfoGetter{
569
-		info: map[string]docker.Container{
569
+		info: map[string]api.ContainerStatus{
570 570
 			"net": {
571
-				ID:   "foobar",
572
-				Path: "bin/run.sh",
573
-				NetworkSettings: &docker.NetworkSettings{
574
-					IPAddress: expectedIP,
571
+				DetailInfo: docker.Container{
572
+					ID:   "foobar",
573
+					Path: "bin/run.sh",
574
+					NetworkSettings: &docker.NetworkSettings{
575
+						IPAddress: expectedIP,
576
+					},
575 577
 				},
576 578
 			},
577 579
 		},
... ...
@@ -592,10 +599,12 @@ func TestFillPodInfo(t *testing.T) {
592 592
 func TestFillPodInfoNoData(t *testing.T) {
593 593
 	expectedIP := ""
594 594
 	fakeGetter := FakePodInfoGetter{
595
-		info: map[string]docker.Container{
595
+		info: map[string]api.ContainerStatus{
596 596
 			"net": {
597
-				ID:   "foobar",
598
-				Path: "bin/run.sh",
597
+				DetailInfo: docker.Container{
598
+					ID:   "foobar",
599
+					Path: "bin/run.sh",
600
+				},
599 601
 			},
600 602
 		},
601 603
 	}
... ...
@@ -25,12 +25,12 @@ import (
25 25
 
26 26
 // Registry is an interface for things that know how to store services.
27 27
 type Registry interface {
28
-	ListServices() (*api.ServiceList, error)
29
-	CreateService(svc *api.Service) error
30
-	GetService(name string) (*api.Service, error)
31
-	DeleteService(name string) error
32
-	UpdateService(svc *api.Service) error
33
-	WatchServices(labels, fields labels.Selector, resourceVersion uint64) (watch.Interface, error)
28
+	ListServices(ctx api.Context) (*api.ServiceList, error)
29
+	CreateService(ctx api.Context, svc *api.Service) error
30
+	GetService(ctx api.Context, name string) (*api.Service, error)
31
+	DeleteService(ctx api.Context, name string) error
32
+	UpdateService(ctx api.Context, svc *api.Service) error
33
+	WatchServices(ctx api.Context, labels, fields labels.Selector, resourceVersion uint64) (watch.Interface, error)
34 34
 
35 35
 	// TODO: endpoints and their implementation should be separated, setting endpoints should be
36 36
 	// supported via the API, and the endpoints-controller should use the API to update endpoints.
... ...
@@ -50,8 +50,11 @@ func NewREST(registry Registry, cloud cloudprovider.Interface, machines minion.R
50 50
 	}
51 51
 }
52 52
 
53
-func (rs *REST) Create(obj runtime.Object) (<-chan runtime.Object, error) {
53
+func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) {
54 54
 	srv := obj.(*api.Service)
55
+	if !api.ValidNamespace(ctx, &srv.JSONBase) {
56
+		return nil, errors.NewConflict("service", srv.Namespace, fmt.Errorf("Service.Namespace does not match the provided context"))
57
+	}
55 58
 	if errs := validation.ValidateService(srv); len(errs) > 0 {
56 59
 		return nil, errors.NewInvalid("service", srv.ID, errs)
57 60
 	}
... ...
@@ -86,27 +89,27 @@ func (rs *REST) Create(obj runtime.Object) (<-chan runtime.Object, error) {
86 86
 				return nil, err
87 87
 			}
88 88
 		}
89
-		err := rs.registry.CreateService(srv)
89
+		err := rs.registry.CreateService(ctx, srv)
90 90
 		if err != nil {
91 91
 			return nil, err
92 92
 		}
93
-		return rs.registry.GetService(srv.ID)
93
+		return rs.registry.GetService(ctx, srv.ID)
94 94
 	}), nil
95 95
 }
96 96
 
97
-func (rs *REST) Delete(id string) (<-chan runtime.Object, error) {
98
-	service, err := rs.registry.GetService(id)
97
+func (rs *REST) Delete(ctx api.Context, id string) (<-chan runtime.Object, error) {
98
+	service, err := rs.registry.GetService(ctx, id)
99 99
 	if err != nil {
100 100
 		return nil, err
101 101
 	}
102 102
 	return apiserver.MakeAsync(func() (runtime.Object, error) {
103 103
 		rs.deleteExternalLoadBalancer(service)
104
-		return &api.Status{Status: api.StatusSuccess}, rs.registry.DeleteService(id)
104
+		return &api.Status{Status: api.StatusSuccess}, rs.registry.DeleteService(ctx, id)
105 105
 	}), nil
106 106
 }
107 107
 
108
-func (rs *REST) Get(id string) (runtime.Object, error) {
109
-	s, err := rs.registry.GetService(id)
108
+func (rs *REST) Get(ctx api.Context, id string) (runtime.Object, error) {
109
+	s, err := rs.registry.GetService(ctx, id)
110 110
 	if err != nil {
111 111
 		return nil, err
112 112
 	}
... ...
@@ -114,8 +117,8 @@ func (rs *REST) Get(id string) (runtime.Object, error) {
114 114
 }
115 115
 
116 116
 // TODO: implement field selector?
117
-func (rs *REST) List(label, field labels.Selector) (runtime.Object, error) {
118
-	list, err := rs.registry.ListServices()
117
+func (rs *REST) List(ctx api.Context, label, field labels.Selector) (runtime.Object, error) {
118
+	list, err := rs.registry.ListServices(ctx)
119 119
 	if err != nil {
120 120
 		return nil, err
121 121
 	}
... ...
@@ -131,8 +134,8 @@ func (rs *REST) List(label, field labels.Selector) (runtime.Object, error) {
131 131
 
132 132
 // Watch returns Services events via a watch.Interface.
133 133
 // It implements apiserver.ResourceWatcher.
134
-func (rs *REST) Watch(label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
135
-	return rs.registry.WatchServices(label, field, resourceVersion)
134
+func (rs *REST) Watch(ctx api.Context, label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
135
+	return rs.registry.WatchServices(ctx, label, field, resourceVersion)
136 136
 }
137 137
 
138 138
 func (*REST) New() runtime.Object {
... ...
@@ -141,40 +144,49 @@ func (*REST) New() runtime.Object {
141 141
 
142 142
 // GetServiceEnvironmentVariables populates a list of environment variables that are use
143 143
 // in the container environment to get access to services.
144
-func GetServiceEnvironmentVariables(registry Registry, machine string) ([]api.EnvVar, error) {
144
+func GetServiceEnvironmentVariables(ctx api.Context, registry Registry, machine string) ([]api.EnvVar, error) {
145 145
 	var result []api.EnvVar
146
-	services, err := registry.ListServices()
146
+	services, err := registry.ListServices(ctx)
147 147
 	if err != nil {
148 148
 		return result, err
149 149
 	}
150 150
 	for _, service := range services.Items {
151
-		name := makeEnvVariableName(service.ID) + "_SERVICE_PORT"
152
-		value := strconv.Itoa(service.Port)
153
-		result = append(result, api.EnvVar{Name: name, Value: value})
151
+		// Host
152
+		name := makeEnvVariableName(service.ID) + "_SERVICE_HOST"
153
+		result = append(result, api.EnvVar{Name: name, Value: machine})
154
+		// Port
155
+		name = makeEnvVariableName(service.ID) + "_SERVICE_PORT"
156
+		result = append(result, api.EnvVar{Name: name, Value: strconv.Itoa(service.Port)})
157
+		// Docker-compatible vars.
154 158
 		result = append(result, makeLinkVariables(service, machine)...)
155 159
 	}
160
+	// The 'SERVICE_HOST' variable is deprecated.
161
+	// TODO(thockin): get rid of it once ip-per-service is in and "deployed".
156 162
 	result = append(result, api.EnvVar{Name: "SERVICE_HOST", Value: machine})
157 163
 	return result, nil
158 164
 }
159 165
 
160
-func (rs *REST) Update(obj runtime.Object) (<-chan runtime.Object, error) {
166
+func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) {
161 167
 	srv := obj.(*api.Service)
168
+	if !api.ValidNamespace(ctx, &srv.JSONBase) {
169
+		return nil, errors.NewConflict("service", srv.Namespace, fmt.Errorf("Service.Namespace does not match the provided context"))
170
+	}
162 171
 	if errs := validation.ValidateService(srv); len(errs) > 0 {
163 172
 		return nil, errors.NewInvalid("service", srv.ID, errs)
164 173
 	}
165 174
 	return apiserver.MakeAsync(func() (runtime.Object, error) {
166 175
 		// TODO: check to see if external load balancer status changed
167
-		err := rs.registry.UpdateService(srv)
176
+		err := rs.registry.UpdateService(ctx, srv)
168 177
 		if err != nil {
169 178
 			return nil, err
170 179
 		}
171
-		return rs.registry.GetService(srv.ID)
180
+		return rs.registry.GetService(ctx, srv.ID)
172 181
 	}), nil
173 182
 }
174 183
 
175 184
 // ResourceLocation returns a URL to which one can send traffic for the specified service.
176
-func (rs *REST) ResourceLocation(id string) (string, error) {
177
-	e, err := rs.registry.GetEndpoints(id)
185
+func (rs *REST) ResourceLocation(ctx api.Context, id string) (string, error) {
186
+	e, err := rs.registry.GetEndpoints(ctx, id)
178 187
 	if err != nil {
179 188
 		return "", err
180 189
 	}
... ...
@@ -216,25 +228,23 @@ func makeEnvVariableName(str string) string {
216 216
 
217 217
 func makeLinkVariables(service api.Service, machine string) []api.EnvVar {
218 218
 	prefix := makeEnvVariableName(service.ID)
219
-	var port string
220
-	if service.ContainerPort.Kind == util.IntstrString {
221
-		port = service.ContainerPort.StrVal
222
-	} else {
223
-		port = strconv.Itoa(service.ContainerPort.IntVal)
219
+	protocol := string(api.ProtocolTCP)
220
+	if service.Protocol != "" {
221
+		protocol = string(service.Protocol)
224 222
 	}
225
-	portPrefix := prefix + "_PORT_" + makeEnvVariableName(port) + "_TCP"
223
+	portPrefix := fmt.Sprintf("%s_PORT_%d_%s", prefix, service.Port, strings.ToUpper(protocol))
226 224
 	return []api.EnvVar{
227 225
 		{
228 226
 			Name:  prefix + "_PORT",
229
-			Value: fmt.Sprintf("tcp://%s:%d", machine, service.Port),
227
+			Value: fmt.Sprintf("%s://%s:%d", strings.ToLower(protocol), machine, service.Port),
230 228
 		},
231 229
 		{
232 230
 			Name:  portPrefix,
233
-			Value: fmt.Sprintf("tcp://%s:%d", machine, service.Port),
231
+			Value: fmt.Sprintf("%s://%s:%d", strings.ToLower(protocol), machine, service.Port),
234 232
 		},
235 233
 		{
236 234
 			Name:  portPrefix + "_PROTO",
237
-			Value: "tcp",
235
+			Value: strings.ToLower(protocol),
238 236
 		},
239 237
 		{
240 238
 			Name:  portPrefix + "_PORT",
... ...
@@ -18,6 +18,7 @@ package service
18 18
 
19 19
 import (
20 20
 	"fmt"
21
+	"reflect"
21 22
 	"testing"
22 23
 
23 24
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
... ...
@@ -27,7 +28,6 @@ import (
27 27
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
28 28
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/minion"
29 29
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest"
30
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
31 30
 )
32 31
 
33 32
 func TestServiceRegistryCreate(t *testing.T) {
... ...
@@ -40,7 +40,8 @@ func TestServiceRegistryCreate(t *testing.T) {
40 40
 		JSONBase: api.JSONBase{ID: "foo"},
41 41
 		Selector: map[string]string{"bar": "baz"},
42 42
 	}
43
-	c, _ := storage.Create(svc)
43
+	ctx := api.NewDefaultContext()
44
+	c, _ := storage.Create(ctx, svc)
44 45
 	created_svc := <-c
45 46
 	created_service := created_svc.(*api.Service)
46 47
 	if created_service.ID != "foo" {
... ...
@@ -52,7 +53,7 @@ func TestServiceRegistryCreate(t *testing.T) {
52 52
 	if len(fakeCloud.Calls) != 0 {
53 53
 		t.Errorf("Unexpected call(s): %#v", fakeCloud.Calls)
54 54
 	}
55
-	srv, err := registry.GetService(svc.ID)
55
+	srv, err := registry.GetService(ctx, svc.ID)
56 56
 	if err != nil {
57 57
 		t.Errorf("unexpected error: %v", err)
58 58
 	}
... ...
@@ -75,8 +76,9 @@ func TestServiceStorageValidatesCreate(t *testing.T) {
75 75
 			Selector: map[string]string{},
76 76
 		},
77 77
 	}
78
+	ctx := api.NewDefaultContext()
78 79
 	for _, failureCase := range failureCases {
79
-		c, err := storage.Create(&failureCase)
80
+		c, err := storage.Create(ctx, &failureCase)
80 81
 		if c != nil {
81 82
 			t.Errorf("Expected nil channel")
82 83
 		}
... ...
@@ -88,14 +90,15 @@ func TestServiceStorageValidatesCreate(t *testing.T) {
88 88
 }
89 89
 
90 90
 func TestServiceRegistryUpdate(t *testing.T) {
91
+	ctx := api.NewDefaultContext()
91 92
 	registry := registrytest.NewServiceRegistry()
92
-	registry.CreateService(&api.Service{
93
+	registry.CreateService(ctx, &api.Service{
93 94
 		Port:     6502,
94 95
 		JSONBase: api.JSONBase{ID: "foo"},
95 96
 		Selector: map[string]string{"bar": "baz1"},
96 97
 	})
97 98
 	storage := NewREST(registry, nil, nil)
98
-	c, err := storage.Update(&api.Service{
99
+	c, err := storage.Update(ctx, &api.Service{
99 100
 		Port:     6502,
100 101
 		JSONBase: api.JSONBase{ID: "foo"},
101 102
 		Selector: map[string]string{"bar": "baz2"},
... ...
@@ -117,8 +120,9 @@ func TestServiceRegistryUpdate(t *testing.T) {
117 117
 }
118 118
 
119 119
 func TestServiceStorageValidatesUpdate(t *testing.T) {
120
+	ctx := api.NewDefaultContext()
120 121
 	registry := registrytest.NewServiceRegistry()
121
-	registry.CreateService(&api.Service{
122
+	registry.CreateService(ctx, &api.Service{
122 123
 		Port:     6502,
123 124
 		JSONBase: api.JSONBase{ID: "foo"},
124 125
 		Selector: map[string]string{"bar": "baz"},
... ...
@@ -137,7 +141,7 @@ func TestServiceStorageValidatesUpdate(t *testing.T) {
137 137
 		},
138 138
 	}
139 139
 	for _, failureCase := range failureCases {
140
-		c, err := storage.Update(&failureCase)
140
+		c, err := storage.Update(ctx, &failureCase)
141 141
 		if c != nil {
142 142
 			t.Errorf("Expected nil channel")
143 143
 		}
... ...
@@ -148,6 +152,7 @@ func TestServiceStorageValidatesUpdate(t *testing.T) {
148 148
 }
149 149
 
150 150
 func TestServiceRegistryExternalService(t *testing.T) {
151
+	ctx := api.NewDefaultContext()
151 152
 	registry := registrytest.NewServiceRegistry()
152 153
 	fakeCloud := &cloud.FakeCloud{}
153 154
 	machines := []string{"foo", "bar", "baz"}
... ...
@@ -158,12 +163,12 @@ func TestServiceRegistryExternalService(t *testing.T) {
158 158
 		Selector:                   map[string]string{"bar": "baz"},
159 159
 		CreateExternalLoadBalancer: true,
160 160
 	}
161
-	c, _ := storage.Create(svc)
161
+	c, _ := storage.Create(ctx, svc)
162 162
 	<-c
163 163
 	if len(fakeCloud.Calls) != 2 || fakeCloud.Calls[0] != "get-zone" || fakeCloud.Calls[1] != "create" {
164 164
 		t.Errorf("Unexpected call(s): %#v", fakeCloud.Calls)
165 165
 	}
166
-	srv, err := registry.GetService(svc.ID)
166
+	srv, err := registry.GetService(ctx, svc.ID)
167 167
 	if err != nil {
168 168
 		t.Errorf("Unexpected error: %v", err)
169 169
 	}
... ...
@@ -185,7 +190,8 @@ func TestServiceRegistryExternalServiceError(t *testing.T) {
185 185
 		Selector:                   map[string]string{"bar": "baz"},
186 186
 		CreateExternalLoadBalancer: true,
187 187
 	}
188
-	c, _ := storage.Create(svc)
188
+	ctx := api.NewDefaultContext()
189
+	c, _ := storage.Create(ctx, svc)
189 190
 	<-c
190 191
 	if len(fakeCloud.Calls) != 1 || fakeCloud.Calls[0] != "get-zone" {
191 192
 		t.Errorf("Unexpected call(s): %#v", fakeCloud.Calls)
... ...
@@ -196,6 +202,7 @@ func TestServiceRegistryExternalServiceError(t *testing.T) {
196 196
 }
197 197
 
198 198
 func TestServiceRegistryDelete(t *testing.T) {
199
+	ctx := api.NewDefaultContext()
199 200
 	registry := registrytest.NewServiceRegistry()
200 201
 	fakeCloud := &cloud.FakeCloud{}
201 202
 	machines := []string{"foo", "bar", "baz"}
... ...
@@ -204,8 +211,8 @@ func TestServiceRegistryDelete(t *testing.T) {
204 204
 		JSONBase: api.JSONBase{ID: "foo"},
205 205
 		Selector: map[string]string{"bar": "baz"},
206 206
 	}
207
-	registry.CreateService(svc)
208
-	c, _ := storage.Delete(svc.ID)
207
+	registry.CreateService(ctx, svc)
208
+	c, _ := storage.Delete(ctx, svc.ID)
209 209
 	<-c
210 210
 	if len(fakeCloud.Calls) != 0 {
211 211
 		t.Errorf("Unexpected call(s): %#v", fakeCloud.Calls)
... ...
@@ -216,6 +223,7 @@ func TestServiceRegistryDelete(t *testing.T) {
216 216
 }
217 217
 
218 218
 func TestServiceRegistryDeleteExternal(t *testing.T) {
219
+	ctx := api.NewDefaultContext()
219 220
 	registry := registrytest.NewServiceRegistry()
220 221
 	fakeCloud := &cloud.FakeCloud{}
221 222
 	machines := []string{"foo", "bar", "baz"}
... ...
@@ -225,8 +233,8 @@ func TestServiceRegistryDeleteExternal(t *testing.T) {
225 225
 		Selector:                   map[string]string{"bar": "baz"},
226 226
 		CreateExternalLoadBalancer: true,
227 227
 	}
228
-	registry.CreateService(svc)
229
-	c, _ := storage.Delete(svc.ID)
228
+	registry.CreateService(ctx, svc)
229
+	c, _ := storage.Delete(ctx, svc.ID)
230 230
 	<-c
231 231
 	if len(fakeCloud.Calls) != 2 || fakeCloud.Calls[0] != "get-zone" || fakeCloud.Calls[1] != "delete" {
232 232
 		t.Errorf("Unexpected call(s): %#v", fakeCloud.Calls)
... ...
@@ -237,37 +245,81 @@ func TestServiceRegistryDeleteExternal(t *testing.T) {
237 237
 }
238 238
 
239 239
 func TestServiceRegistryMakeLinkVariables(t *testing.T) {
240
-	service := api.Service{
241
-		JSONBase:      api.JSONBase{ID: "foo-bar"},
242
-		Selector:      map[string]string{"bar": "baz"},
243
-		ContainerPort: util.IntOrString{Kind: util.IntstrString, StrVal: "a-b-c"},
244
-	}
240
+	ctx := api.NewDefaultContext()
245 241
 	registry := registrytest.NewServiceRegistry()
246 242
 	registry.List = api.ServiceList{
247
-		Items: []api.Service{service},
243
+		Items: []api.Service{
244
+			{
245
+				JSONBase: api.JSONBase{ID: "foo-bar"},
246
+				Selector: map[string]string{"bar": "baz"},
247
+				Port:     8080,
248
+				Protocol: "TCP",
249
+			},
250
+			{
251
+				JSONBase: api.JSONBase{ID: "abc-123"},
252
+				Selector: map[string]string{"bar": "baz"},
253
+				Port:     8081,
254
+				Protocol: "UDP",
255
+			},
256
+			{
257
+				JSONBase: api.JSONBase{ID: "q-u-u-x"},
258
+				Selector: map[string]string{"bar": "baz"},
259
+				Port:     8082,
260
+				Protocol: "",
261
+			},
262
+		},
248 263
 	}
249 264
 	machine := "machine"
250
-	vars, err := GetServiceEnvironmentVariables(registry, machine)
265
+	vars, err := GetServiceEnvironmentVariables(ctx, registry, machine)
251 266
 	if err != nil {
252 267
 		t.Errorf("Unexpected err: %v", err)
253 268
 	}
254
-	for _, v := range vars {
255
-		if !util.IsCIdentifier(v.Name) {
256
-			t.Errorf("Environment variable name is not valid: %v", v.Name)
269
+	expected := []api.EnvVar{
270
+		{Name: "FOO_BAR_SERVICE_HOST", Value: "machine"},
271
+		{Name: "FOO_BAR_SERVICE_PORT", Value: "8080"},
272
+		{Name: "FOO_BAR_PORT", Value: "tcp://machine:8080"},
273
+		{Name: "FOO_BAR_PORT_8080_TCP", Value: "tcp://machine:8080"},
274
+		{Name: "FOO_BAR_PORT_8080_TCP_PROTO", Value: "tcp"},
275
+		{Name: "FOO_BAR_PORT_8080_TCP_PORT", Value: "8080"},
276
+		{Name: "FOO_BAR_PORT_8080_TCP_ADDR", Value: "machine"},
277
+		{Name: "ABC_123_SERVICE_HOST", Value: "machine"},
278
+		{Name: "ABC_123_SERVICE_PORT", Value: "8081"},
279
+		{Name: "ABC_123_PORT", Value: "udp://machine:8081"},
280
+		{Name: "ABC_123_PORT_8081_UDP", Value: "udp://machine:8081"},
281
+		{Name: "ABC_123_PORT_8081_UDP_PROTO", Value: "udp"},
282
+		{Name: "ABC_123_PORT_8081_UDP_PORT", Value: "8081"},
283
+		{Name: "ABC_123_PORT_8081_UDP_ADDR", Value: "machine"},
284
+		{Name: "Q_U_U_X_SERVICE_HOST", Value: "machine"},
285
+		{Name: "Q_U_U_X_SERVICE_PORT", Value: "8082"},
286
+		{Name: "Q_U_U_X_PORT", Value: "tcp://machine:8082"},
287
+		{Name: "Q_U_U_X_PORT_8082_TCP", Value: "tcp://machine:8082"},
288
+		{Name: "Q_U_U_X_PORT_8082_TCP_PROTO", Value: "tcp"},
289
+		{Name: "Q_U_U_X_PORT_8082_TCP_PORT", Value: "8082"},
290
+		{Name: "Q_U_U_X_PORT_8082_TCP_ADDR", Value: "machine"},
291
+		{Name: "SERVICE_HOST", Value: "machine"},
292
+	}
293
+	if len(vars) != len(expected) {
294
+		t.Errorf("Expected %d env vars, got: %+v", len(expected), vars)
295
+		return
296
+	}
297
+	for i := range expected {
298
+		if !reflect.DeepEqual(vars[i], expected[i]) {
299
+			t.Errorf("expected %#v, got %#v", vars[i], expected[i])
257 300
 		}
258 301
 	}
259 302
 }
260 303
 
261 304
 func TestServiceRegistryGet(t *testing.T) {
305
+	ctx := api.NewDefaultContext()
262 306
 	registry := registrytest.NewServiceRegistry()
263 307
 	fakeCloud := &cloud.FakeCloud{}
264 308
 	machines := []string{"foo", "bar", "baz"}
265 309
 	storage := NewREST(registry, fakeCloud, minion.NewRegistry(machines))
266
-	registry.CreateService(&api.Service{
310
+	registry.CreateService(ctx, &api.Service{
267 311
 		JSONBase: api.JSONBase{ID: "foo"},
268 312
 		Selector: map[string]string{"bar": "baz"},
269 313
 	})
270
-	storage.Get("foo")
314
+	storage.Get(ctx, "foo")
271 315
 	if len(fakeCloud.Calls) != 0 {
272 316
 		t.Errorf("Unexpected call(s): %#v", fakeCloud.Calls)
273 317
 	}
... ...
@@ -277,17 +329,18 @@ func TestServiceRegistryGet(t *testing.T) {
277 277
 }
278 278
 
279 279
 func TestServiceRegistryResourceLocation(t *testing.T) {
280
+	ctx := api.NewDefaultContext()
280 281
 	registry := registrytest.NewServiceRegistry()
281 282
 	registry.Endpoints = api.Endpoints{Endpoints: []string{"foo:80"}}
282 283
 	fakeCloud := &cloud.FakeCloud{}
283 284
 	machines := []string{"foo", "bar", "baz"}
284 285
 	storage := NewREST(registry, fakeCloud, minion.NewRegistry(machines))
285
-	registry.CreateService(&api.Service{
286
+	registry.CreateService(ctx, &api.Service{
286 287
 		JSONBase: api.JSONBase{ID: "foo"},
287 288
 		Selector: map[string]string{"bar": "baz"},
288 289
 	})
289 290
 	redirector := apiserver.Redirector(storage)
290
-	location, err := redirector.ResourceLocation("foo")
291
+	location, err := redirector.ResourceLocation(ctx, "foo")
291 292
 	if err != nil {
292 293
 		t.Errorf("Unexpected error: %v", err)
293 294
 	}
... ...
@@ -300,26 +353,27 @@ func TestServiceRegistryResourceLocation(t *testing.T) {
300 300
 
301 301
 	// Test error path
302 302
 	registry.Err = fmt.Errorf("fake error")
303
-	if _, err = redirector.ResourceLocation("foo"); err == nil {
303
+	if _, err = redirector.ResourceLocation(ctx, "foo"); err == nil {
304 304
 		t.Errorf("unexpected nil error")
305 305
 	}
306 306
 }
307 307
 
308 308
 func TestServiceRegistryList(t *testing.T) {
309
+	ctx := api.NewDefaultContext()
309 310
 	registry := registrytest.NewServiceRegistry()
310 311
 	fakeCloud := &cloud.FakeCloud{}
311 312
 	machines := []string{"foo", "bar", "baz"}
312 313
 	storage := NewREST(registry, fakeCloud, minion.NewRegistry(machines))
313
-	registry.CreateService(&api.Service{
314
+	registry.CreateService(ctx, &api.Service{
314 315
 		JSONBase: api.JSONBase{ID: "foo"},
315 316
 		Selector: map[string]string{"bar": "baz"},
316 317
 	})
317
-	registry.CreateService(&api.Service{
318
+	registry.CreateService(ctx, &api.Service{
318 319
 		JSONBase: api.JSONBase{ID: "foo2"},
319 320
 		Selector: map[string]string{"bar2": "baz2"},
320 321
 	})
321 322
 	registry.List.ResourceVersion = 1
322
-	s, _ := storage.List(labels.Everything(), labels.Everything())
323
+	s, _ := storage.List(ctx, labels.Everything(), labels.Everything())
323 324
 	sl := s.(*api.ServiceList)
324 325
 	if len(fakeCloud.Calls) != 0 {
325 326
 		t.Errorf("Unexpected call(s): %#v", fakeCloud.Calls)
326 327
new file mode 100644
... ...
@@ -0,0 +1,18 @@
0
+/*
1
+Copyright 2014 Google Inc. All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+// package resources has constants and utilities for dealing with resources
17
+package resources
0 18
new file mode 100644
... ...
@@ -0,0 +1,75 @@
0
+/*
1
+Copyright 2014 Google Inc. All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package resources
17
+
18
+import (
19
+	"strconv"
20
+
21
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
22
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
23
+	"github.com/golang/glog"
24
+)
25
+
26
+const (
27
+	CPU    api.ResourceName = "cpu"
28
+	Memory api.ResourceName = "memory"
29
+)
30
+
31
+// TODO: None of these currently handle SI units
32
+
33
+func GetFloatResource(resources api.ResourceList, name api.ResourceName, def float64) float64 {
34
+	value, found := resources[name]
35
+	if !found {
36
+		return def
37
+	}
38
+	if value.Kind == util.IntstrInt {
39
+		return float64(value.IntVal)
40
+	}
41
+	result, err := strconv.ParseFloat(value.StrVal, 64)
42
+	if err != nil {
43
+		glog.Errorf("parsing failed for %s: %s", name, value.StrVal)
44
+		return def
45
+	}
46
+	return result
47
+}
48
+
49
+func GetIntegerResource(resources api.ResourceList, name api.ResourceName, def int) int {
50
+	value, found := resources[name]
51
+	if !found {
52
+		return def
53
+	}
54
+	if value.Kind == util.IntstrInt {
55
+		return value.IntVal
56
+	}
57
+	result, err := strconv.Atoi(value.StrVal)
58
+	if err != nil {
59
+		glog.Errorf("parsing failed for %s: %s", name, value.StrVal)
60
+		return def
61
+	}
62
+	return result
63
+}
64
+
65
+func GetStringResource(resources api.ResourceList, name api.ResourceName, def string) string {
66
+	value, found := resources[name]
67
+	if !found {
68
+		return def
69
+	}
70
+	if value.Kind == util.IntstrInt {
71
+		return strconv.Itoa(value.IntVal)
72
+	}
73
+	return value.StrVal
74
+}
0 75
new file mode 100644
... ...
@@ -0,0 +1,169 @@
0
+/*
1
+Copyright 2014 Google Inc. All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package resources
17
+
18
+import (
19
+	"testing"
20
+
21
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
22
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
23
+)
24
+
25
+func TestGetInteger(t *testing.T) {
26
+	tests := []struct {
27
+		res      api.ResourceList
28
+		name     api.ResourceName
29
+		expected int
30
+		def      int
31
+		test     string
32
+	}{
33
+		{
34
+			res:      api.ResourceList{},
35
+			name:     CPU,
36
+			expected: 1,
37
+			def:      1,
38
+			test:     "nothing present",
39
+		},
40
+		{
41
+			res: api.ResourceList{
42
+				CPU: util.NewIntOrStringFromInt(2),
43
+			},
44
+			name:     CPU,
45
+			expected: 2,
46
+			def:      1,
47
+			test:     "present",
48
+		},
49
+		{
50
+			res: api.ResourceList{
51
+				Memory: util.NewIntOrStringFromInt(2),
52
+			},
53
+			name:     CPU,
54
+			expected: 1,
55
+			def:      1,
56
+			test:     "not-present",
57
+		},
58
+		{
59
+			res: api.ResourceList{
60
+				CPU: util.NewIntOrStringFromString("2"),
61
+			},
62
+			name:     CPU,
63
+			expected: 2,
64
+			def:      1,
65
+			test:     "present-string",
66
+		},
67
+		{
68
+			res: api.ResourceList{
69
+				CPU: util.NewIntOrStringFromString("foo"),
70
+			},
71
+			name:     CPU,
72
+			expected: 1,
73
+			def:      1,
74
+			test:     "present-invalid",
75
+		},
76
+	}
77
+
78
+	for _, test := range tests {
79
+		val := GetIntegerResource(test.res, test.name, test.def)
80
+		if val != test.expected {
81
+			t.Errorf("%s: expected: %d found %d", test.expected, val)
82
+		}
83
+	}
84
+}
85
+func TestGetFloat(t *testing.T) {
86
+	tests := []struct {
87
+		res      api.ResourceList
88
+		name     api.ResourceName
89
+		expected float64
90
+		def      float64
91
+		test     string
92
+	}{
93
+		{
94
+			res:      api.ResourceList{},
95
+			name:     CPU,
96
+			expected: 1.5,
97
+			def:      1.5,
98
+			test:     "nothing present",
99
+		},
100
+		{
101
+			res: api.ResourceList{
102
+				CPU: util.NewIntOrStringFromInt(2),
103
+			},
104
+			name:     CPU,
105
+			expected: 2.0,
106
+			def:      1.5,
107
+			test:     "present",
108
+		},
109
+		{
110
+			res: api.ResourceList{
111
+				CPU: util.NewIntOrStringFromString("2.5"),
112
+			},
113
+			name:     CPU,
114
+			expected: 2.5,
115
+			def:      1,
116
+			test:     "present-string",
117
+		},
118
+		{
119
+			res: api.ResourceList{
120
+				CPU: util.NewIntOrStringFromString("foo"),
121
+			},
122
+			name:     CPU,
123
+			expected: 1,
124
+			def:      1,
125
+			test:     "present-invalid",
126
+		},
127
+	}
128
+
129
+	for _, test := range tests {
130
+		val := GetFloatResource(test.res, test.name, test.def)
131
+		if val != test.expected {
132
+			t.Errorf("%s: expected: %d found %d", test.expected, val)
133
+		}
134
+	}
135
+}
136
+func TestGetString(t *testing.T) {
137
+	tests := []struct {
138
+		res      api.ResourceList
139
+		name     api.ResourceName
140
+		expected string
141
+		def      string
142
+		test     string
143
+	}{
144
+		{
145
+			res:      api.ResourceList{},
146
+			name:     CPU,
147
+			expected: "foo",
148
+			def:      "foo",
149
+			test:     "nothing present",
150
+		},
151
+		{
152
+			res: api.ResourceList{
153
+				CPU: util.NewIntOrStringFromString("bar"),
154
+			},
155
+			name:     CPU,
156
+			expected: "bar",
157
+			def:      "foo",
158
+			test:     "present",
159
+		},
160
+	}
161
+
162
+	for _, test := range tests {
163
+		val := GetStringResource(test.res, test.name, test.def)
164
+		if val != test.expected {
165
+			t.Errorf("%s: expected: %d found %d", test.expected, val)
166
+		}
167
+	}
168
+}
0 169
new file mode 100644
... ...
@@ -0,0 +1,91 @@
0
+/*
1
+Copyright 2014 Google Inc. All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package runtime
17
+
18
+import (
19
+	"fmt"
20
+	"reflect"
21
+)
22
+
23
+// GetItemsPtr returns a pointer to the list object's Items member.
24
+// If 'list' doesn't have an Items member, it's not really a list type
25
+// and an error will be returned.
26
+// This function will either return a pointer to a slice, or an error, but not both.
27
+func GetItemsPtr(list Object) (interface{}, error) {
28
+	v := reflect.ValueOf(list)
29
+	if !v.IsValid() {
30
+		return nil, fmt.Errorf("nil list object")
31
+	}
32
+	items := v.Elem().FieldByName("Items")
33
+	if !items.IsValid() {
34
+		return nil, fmt.Errorf("no Items field in %#v", list)
35
+	}
36
+	if items.Kind() != reflect.Slice {
37
+		return nil, fmt.Errorf("Items field is not a slice")
38
+	}
39
+	return items.Addr().Interface(), nil
40
+}
41
+
42
+// ExtractList returns obj's Items element as an array of runtime.Objects.
43
+// Returns an error if obj is not a List type (does not have an Items member).
44
+func ExtractList(obj Object) ([]Object, error) {
45
+	itemsPtr, err := GetItemsPtr(obj)
46
+	if err != nil {
47
+		return nil, err
48
+	}
49
+	items := reflect.ValueOf(itemsPtr).Elem()
50
+	list := make([]Object, items.Len())
51
+	for i := range list {
52
+		raw := items.Index(i)
53
+		item, ok := raw.Addr().Interface().(Object)
54
+		if !ok {
55
+			return nil, fmt.Errorf("item in index %v isn't an object: %#v", i, raw.Interface())
56
+		}
57
+		list[i] = item
58
+	}
59
+	return list, nil
60
+}
61
+
62
+// SetList sets the given list object's Items member have the elements given in
63
+// objects.
64
+// Returns an error if list is not a List type (does not have an Items member),
65
+// or if any of the objects are not of the right type.
66
+func SetList(list Object, objects []Object) error {
67
+	itemsPtr, err := GetItemsPtr(list)
68
+	if err != nil {
69
+		return err
70
+	}
71
+	items := reflect.ValueOf(itemsPtr).Elem()
72
+	slice := reflect.MakeSlice(items.Type(), len(objects), len(objects))
73
+	for i := range objects {
74
+		dest := slice.Index(i)
75
+		src := reflect.ValueOf(objects[i])
76
+		if !src.IsValid() || src.IsNil() {
77
+			return fmt.Errorf("an object was nil")
78
+		}
79
+		src = src.Elem() // Object is a pointer, but the items in slice are not.
80
+		if src.Type().AssignableTo(dest.Type()) {
81
+			dest.Set(src)
82
+		} else if src.Type().ConvertibleTo(dest.Type()) {
83
+			dest.Set(src.Convert(dest.Type()))
84
+		} else {
85
+			return fmt.Errorf("wrong type: need %v, got %v", dest.Type(), src.Type())
86
+		}
87
+	}
88
+	items.Set(slice)
89
+	return nil
90
+}
0 91
new file mode 100644
... ...
@@ -0,0 +1,93 @@
0
+/*
1
+Copyright 2014 Google Inc. All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package runtime_test
17
+
18
+import (
19
+	"reflect"
20
+	"testing"
21
+
22
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
23
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
24
+
25
+	"github.com/google/gofuzz"
26
+)
27
+
28
+func TestExtractList(t *testing.T) {
29
+	pl := &api.PodList{
30
+		Items: []api.Pod{
31
+			{JSONBase: api.JSONBase{ID: "1"}},
32
+			{JSONBase: api.JSONBase{ID: "2"}},
33
+			{JSONBase: api.JSONBase{ID: "3"}},
34
+		},
35
+	}
36
+	list, err := runtime.ExtractList(pl)
37
+	if err != nil {
38
+		t.Fatalf("Unexpected error %v", err)
39
+	}
40
+	if e, a := len(list), len(pl.Items); e != a {
41
+		t.Fatalf("Expected %v, got %v", e, a)
42
+	}
43
+	for i := range list {
44
+		if e, a := list[i].(*api.Pod).ID, pl.Items[i].ID; e != a {
45
+			t.Fatalf("Expected %v, got %v", e, a)
46
+		}
47
+	}
48
+}
49
+
50
+func TestSetList(t *testing.T) {
51
+	pl := &api.PodList{}
52
+	list := []runtime.Object{
53
+		&api.Pod{JSONBase: api.JSONBase{ID: "1"}},
54
+		&api.Pod{JSONBase: api.JSONBase{ID: "2"}},
55
+		&api.Pod{JSONBase: api.JSONBase{ID: "3"}},
56
+	}
57
+	err := runtime.SetList(pl, list)
58
+	if err != nil {
59
+		t.Fatalf("Unexpected error %v", err)
60
+	}
61
+	if e, a := len(list), len(pl.Items); e != a {
62
+		t.Fatalf("Expected %v, got %v", e, a)
63
+	}
64
+	for i := range list {
65
+		if e, a := list[i].(*api.Pod).ID, pl.Items[i].ID; e != a {
66
+			t.Fatalf("Expected %v, got %v", e, a)
67
+		}
68
+	}
69
+}
70
+
71
+func TestSetExtractListRoundTrip(t *testing.T) {
72
+	fuzzer := fuzz.New().NilChance(0).NumElements(1, 5)
73
+	for i := 0; i < 5; i++ {
74
+		start := &api.PodList{}
75
+		fuzzer.Fuzz(&start.Items)
76
+
77
+		list, err := runtime.ExtractList(start)
78
+		if err != nil {
79
+			t.Errorf("Unexpected error %v", err)
80
+			continue
81
+		}
82
+		got := &api.PodList{}
83
+		err = runtime.SetList(got, list)
84
+		if err != nil {
85
+			t.Errorf("Unexpected error %v", err)
86
+			continue
87
+		}
88
+		if e, a := start, got; !reflect.DeepEqual(e, a) {
89
+			t.Fatalf("Expected %#v, got %#v", e, a)
90
+		}
91
+	}
92
+}
... ...
@@ -40,6 +40,15 @@ type ResourceVersioner interface {
40 40
 	ResourceVersion(obj Object) (uint64, error)
41 41
 }
42 42
 
43
+// SelfLinker provides methods for setting and retrieving the SelfLink field of an API object.
44
+type SelfLinker interface {
45
+	SetSelfLink(obj Object, selfLink string) error
46
+	SelfLink(obj Object) (string, error)
47
+
48
+	// Knowing ID is sometimes necssary to use a SelfLinker.
49
+	ID(obj Object) (string, error)
50
+}
51
+
43 52
 // All api types must support the Object interface. It's deliberately tiny so that this is not an onerous
44 53
 // burden. Implement it with a pointer reciever; this will allow us to use the go compiler to check the
45 54
 // one thing about our objects that it's capable of checking for us.
... ...
@@ -21,15 +21,16 @@ import (
21 21
 	"reflect"
22 22
 )
23 23
 
24
-// NewJSONBaseResourceVersioner returns a resourceVersioner that can set or
24
+// NewJSONBaseResourceVersioner returns a ResourceVersioner that can set or
25 25
 // retrieve ResourceVersion on objects derived from JSONBase.
26 26
 func NewJSONBaseResourceVersioner() ResourceVersioner {
27
-	return &jsonBaseResourceVersioner{}
27
+	return jsonBaseModifier{}
28 28
 }
29 29
 
30
-type jsonBaseResourceVersioner struct{}
30
+// jsonBaseModifier implements ResourceVersioner and SelfLinker.
31
+type jsonBaseModifier struct{}
31 32
 
32
-func (v jsonBaseResourceVersioner) ResourceVersion(obj Object) (uint64, error) {
33
+func (v jsonBaseModifier) ResourceVersion(obj Object) (uint64, error) {
33 34
 	json, err := FindJSONBase(obj)
34 35
 	if err != nil {
35 36
 		return 0, err
... ...
@@ -37,7 +38,7 @@ func (v jsonBaseResourceVersioner) ResourceVersion(obj Object) (uint64, error) {
37 37
 	return json.ResourceVersion(), nil
38 38
 }
39 39
 
40
-func (v jsonBaseResourceVersioner) SetResourceVersion(obj Object, version uint64) error {
40
+func (v jsonBaseModifier) SetResourceVersion(obj Object, version uint64) error {
41 41
 	json, err := FindJSONBase(obj)
42 42
 	if err != nil {
43 43
 		return err
... ...
@@ -46,6 +47,36 @@ func (v jsonBaseResourceVersioner) SetResourceVersion(obj Object, version uint64
46 46
 	return nil
47 47
 }
48 48
 
49
+func (v jsonBaseModifier) ID(obj Object) (string, error) {
50
+	json, err := FindJSONBase(obj)
51
+	if err != nil {
52
+		return "", err
53
+	}
54
+	return json.ID(), nil
55
+}
56
+
57
+func (v jsonBaseModifier) SelfLink(obj Object) (string, error) {
58
+	json, err := FindJSONBase(obj)
59
+	if err != nil {
60
+		return "", err
61
+	}
62
+	return json.SelfLink(), nil
63
+}
64
+
65
+func (v jsonBaseModifier) SetSelfLink(obj Object, selfLink string) error {
66
+	json, err := FindJSONBase(obj)
67
+	if err != nil {
68
+		return err
69
+	}
70
+	json.SetSelfLink(selfLink)
71
+	return nil
72
+}
73
+
74
+// NewJSONBaseSelfLinker returns a SelfLinker that works on all JSONBase SelfLink fields.
75
+func NewJSONBaseSelfLinker() SelfLinker {
76
+	return jsonBaseModifier{}
77
+}
78
+
49 79
 // JSONBaseInterface lets you work with a JSONBase from any of the versioned or
50 80
 // internal APIObjects.
51 81
 type JSONBaseInterface interface {
... ...
@@ -57,6 +88,8 @@ type JSONBaseInterface interface {
57 57
 	SetKind(kind string)
58 58
 	ResourceVersion() uint64
59 59
 	SetResourceVersion(version uint64)
60
+	SelfLink() string
61
+	SetSelfLink(selfLink string)
60 62
 }
61 63
 
62 64
 type genericJSONBase struct {
... ...
@@ -64,6 +97,7 @@ type genericJSONBase struct {
64 64
 	apiVersion      *string
65 65
 	kind            *string
66 66
 	resourceVersion *uint64
67
+	selfLink        *string
67 68
 }
68 69
 
69 70
 func (g genericJSONBase) ID() string {
... ...
@@ -98,6 +132,14 @@ func (g genericJSONBase) SetResourceVersion(version uint64) {
98 98
 	*g.resourceVersion = version
99 99
 }
100 100
 
101
+func (g genericJSONBase) SelfLink() string {
102
+	return *g.selfLink
103
+}
104
+
105
+func (g genericJSONBase) SetSelfLink(selfLink string) {
106
+	*g.selfLink = selfLink
107
+}
108
+
101 109
 // fieldPtr puts the address of fieldName, which must be a member of v,
102 110
 // into dest, which must be an address of a variable to which this field's
103 111
 // address can be assigned.
... ...
@@ -140,5 +182,8 @@ func newGenericJSONBase(v reflect.Value) (genericJSONBase, error) {
140 140
 	if err := fieldPtr(v, "ResourceVersion", &g.resourceVersion); err != nil {
141 141
 		return g, err
142 142
 	}
143
+	if err := fieldPtr(v, "SelfLink", &g.selfLink); err != nil {
144
+		return g, err
145
+	}
143 146
 	return g, nil
144 147
 }
... ...
@@ -37,6 +37,7 @@ func TestGenericJSONBase(t *testing.T) {
37 37
 		APIVersion:      "a",
38 38
 		Kind:            "b",
39 39
 		ResourceVersion: 1,
40
+		SelfLink:        "some/place/only/we/know",
40 41
 	}
41 42
 	g, err := newGenericJSONBase(reflect.ValueOf(&j).Elem())
42 43
 	if err != nil {
... ...
@@ -56,11 +57,15 @@ func TestGenericJSONBase(t *testing.T) {
56 56
 	if e, a := uint64(1), jbi.ResourceVersion(); e != a {
57 57
 		t.Errorf("expected %v, got %v", e, a)
58 58
 	}
59
+	if e, a := "some/place/only/we/know", jbi.SelfLink(); e != a {
60
+		t.Errorf("expected %v, got %v", e, a)
61
+	}
59 62
 
60 63
 	jbi.SetID("bar")
61 64
 	jbi.SetAPIVersion("c")
62 65
 	jbi.SetKind("d")
63 66
 	jbi.SetResourceVersion(2)
67
+	jbi.SetSelfLink("google.com")
64 68
 
65 69
 	// Prove that jbi changes the original object.
66 70
 	if e, a := "bar", j.ID; e != a {
... ...
@@ -75,6 +80,9 @@ func TestGenericJSONBase(t *testing.T) {
75 75
 	if e, a := uint64(2), j.ResourceVersion; e != a {
76 76
 		t.Errorf("expected %v, got %v", e, a)
77 77
 	}
78
+	if e, a := "google.com", j.SelfLink; e != a {
79
+		t.Errorf("expected %v, got %v", e, a)
80
+	}
78 81
 }
79 82
 
80 83
 type MyAPIObject struct {
... ...
@@ -141,3 +149,48 @@ func TestResourceVersionerOfAPI(t *testing.T) {
141 141
 		}
142 142
 	}
143 143
 }
144
+
145
+func TestJSONBaseSelfLinker(t *testing.T) {
146
+	table := map[string]struct {
147
+		obj     Object
148
+		expect  string
149
+		try     string
150
+		succeed bool
151
+	}{
152
+		"normal": {
153
+			obj:     &MyAPIObject{JSONBase: JSONBase{SelfLink: "foobar"}},
154
+			expect:  "foobar",
155
+			try:     "newbar",
156
+			succeed: true,
157
+		},
158
+		"fail": {
159
+			obj:     &MyIncorrectlyMarkedAsAPIObject{},
160
+			succeed: false,
161
+		},
162
+	}
163
+
164
+	linker := NewJSONBaseSelfLinker()
165
+	for name, item := range table {
166
+		got, err := linker.SelfLink(item.obj)
167
+		if e, a := item.succeed, err == nil; e != a {
168
+			t.Errorf("%v: expected %v, got %v", name, e, a)
169
+		}
170
+		if e, a := item.expect, got; item.succeed && e != a {
171
+			t.Errorf("%v: expected %v, got %v", name, e, a)
172
+		}
173
+
174
+		err = linker.SetSelfLink(item.obj, item.try)
175
+		if e, a := item.succeed, err == nil; e != a {
176
+			t.Errorf("%v: expected %v, got %v", name, e, a)
177
+		}
178
+		if item.succeed {
179
+			got, err := linker.SelfLink(item.obj)
180
+			if err != nil {
181
+				t.Errorf("%v: expected no err, got %v", name, err)
182
+			}
183
+			if e, a := item.try, got; e != a {
184
+				t.Errorf("%v: expected %v, got %v", name, e, a)
185
+			}
186
+		}
187
+	}
188
+}
... ...
@@ -198,10 +198,17 @@ func (s *Scheme) AddKnownTypeWithName(version, kind string, obj Object) {
198 198
 	s.raw.AddKnownTypeWithName(version, kind, obj)
199 199
 }
200 200
 
201
+// KnownTypes returns the types known for the given version.
202
+// Return value must be treated as read-only.
201 203
 func (s *Scheme) KnownTypes(version string) map[string]reflect.Type {
202 204
 	return s.raw.KnownTypes(version)
203 205
 }
204 206
 
207
+// ObjectVersionAndKind returns the version and kind of the given Object.
208
+func (s *Scheme) ObjectVersionAndKind(obj Object) (version, kind string, err error) {
209
+	return s.raw.ObjectVersionAndKind(obj)
210
+}
211
+
205 212
 // New returns a new API object of the given version ("" for internal
206 213
 // representation) and name, or an error if it hasn't been registered.
207 214
 func (s *Scheme) New(versionName, typeName string) (Object, error) {
... ...
@@ -400,28 +407,3 @@ func (metaInsertion) Interpret(in interface{}) (version, kind string) {
400 400
 	m := in.(*metaInsertion)
401 401
 	return m.JSONBase.APIVersion, m.JSONBase.Kind
402 402
 }
403
-
404
-// Extract list returns obj's Items element as an array of runtime.Objects.
405
-// Returns an error if obj is not a List type (does not have an Items member).
406
-func ExtractList(obj Object) ([]Object, error) {
407
-	v := reflect.ValueOf(obj)
408
-	if !v.IsValid() {
409
-		return nil, fmt.Errorf("nil object")
410
-	}
411
-	items := v.Elem().FieldByName("Items")
412
-	if !items.IsValid() {
413
-		return nil, fmt.Errorf("no Items field")
414
-	}
415
-	if items.Kind() != reflect.Slice {
416
-		return nil, fmt.Errorf("Items field is not a slice")
417
-	}
418
-	list := make([]Object, items.Len())
419
-	for i := range list {
420
-		item, ok := items.Index(i).Addr().Interface().(Object)
421
-		if !ok {
422
-			return nil, fmt.Errorf("item in index %v isn't an object", i)
423
-		}
424
-		list[i] = item
425
-	}
426
-	return list, nil
427
-}
428 403
new file mode 100644
... ...
@@ -0,0 +1,128 @@
0
+/*
1
+Copyright 2014 Google Inc. All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package scheduler
17
+
18
+import (
19
+	"fmt"
20
+	"math/rand"
21
+	"sort"
22
+	"sync"
23
+
24
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
25
+)
26
+
27
+type genericScheduler struct {
28
+	predicates  []FitPredicate
29
+	prioritizer PriorityFunction
30
+	pods        PodLister
31
+	random      *rand.Rand
32
+	randomLock  sync.Mutex
33
+}
34
+
35
+func (g *genericScheduler) Schedule(pod api.Pod, minionLister MinionLister) (string, error) {
36
+	minions, err := minionLister.List()
37
+	if err != nil {
38
+		return "", err
39
+	}
40
+	filteredNodes, err := findNodesThatFit(pod, g.pods, g.predicates, minions)
41
+	if err != nil {
42
+		return "", err
43
+	}
44
+	priorityList, err := g.prioritizer(pod, g.pods, FakeMinionLister(filteredNodes))
45
+	if err != nil {
46
+		return "", err
47
+	}
48
+	if len(priorityList) == 0 {
49
+		return "", fmt.Errorf("failed to find a fit for pod: %v", pod)
50
+	}
51
+	return g.selectHost(priorityList)
52
+}
53
+
54
+func (g *genericScheduler) selectHost(priorityList HostPriorityList) (string, error) {
55
+	sort.Sort(priorityList)
56
+
57
+	hosts := getMinHosts(priorityList)
58
+	g.randomLock.Lock()
59
+	defer g.randomLock.Unlock()
60
+
61
+	ix := g.random.Int() % len(hosts)
62
+	return hosts[ix], nil
63
+}
64
+
65
+func findNodesThatFit(pod api.Pod, podLister PodLister, predicates []FitPredicate, nodes []string) ([]string, error) {
66
+	filtered := []string{}
67
+	machineToPods, err := MapPodsToMachines(podLister)
68
+	if err != nil {
69
+		return nil, err
70
+	}
71
+	for _, node := range nodes {
72
+		fits := true
73
+		for _, predicate := range predicates {
74
+			fit, err := predicate(pod, machineToPods[node], node)
75
+			if err != nil {
76
+				return nil, err
77
+			}
78
+			if !fit {
79
+				fits = false
80
+				break
81
+			}
82
+		}
83
+		if fits {
84
+			filtered = append(filtered, node)
85
+		}
86
+	}
87
+	return filtered, nil
88
+}
89
+
90
+func getMinHosts(list HostPriorityList) []string {
91
+	result := []string{}
92
+	for _, hostEntry := range list {
93
+		if hostEntry.score == list[0].score {
94
+			result = append(result, hostEntry.host)
95
+		} else {
96
+			break
97
+		}
98
+	}
99
+	return result
100
+}
101
+
102
+// EqualPriority is a prioritizer function that gives an equal weight of one to all nodes
103
+func EqualPriority(pod api.Pod, podLister PodLister, minionLister MinionLister) (HostPriorityList, error) {
104
+	nodes, err := minionLister.List()
105
+	result := []HostPriority{}
106
+
107
+	if err != nil {
108
+		fmt.Errorf("failed to list nodes: %v", err)
109
+		return []HostPriority{}, err
110
+	}
111
+	for _, minion := range nodes {
112
+		result = append(result, HostPriority{
113
+			host:  minion,
114
+			score: 1,
115
+		})
116
+	}
117
+	return result, nil
118
+}
119
+
120
+func NewGenericScheduler(predicates []FitPredicate, prioritizer PriorityFunction, pods PodLister, random *rand.Rand) Scheduler {
121
+	return &genericScheduler{
122
+		predicates:  predicates,
123
+		prioritizer: prioritizer,
124
+		pods:        pods,
125
+		random:      random,
126
+	}
127
+}
0 128
new file mode 100644
... ...
@@ -0,0 +1,129 @@
0
+/*
1
+Copyright 2014 Google Inc. All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package scheduler
17
+
18
+import (
19
+	"fmt"
20
+	"math/rand"
21
+	"strconv"
22
+	"testing"
23
+
24
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
25
+)
26
+
27
+func falsePredicate(pod api.Pod, existingPods []api.Pod, node string) (bool, error) {
28
+	return false, nil
29
+}
30
+
31
+func truePredicate(pod api.Pod, existingPods []api.Pod, node string) (bool, error) {
32
+	return true, nil
33
+}
34
+
35
+func matchesPredicate(pod api.Pod, existingPods []api.Pod, node string) (bool, error) {
36
+	return pod.ID == node, nil
37
+}
38
+
39
+func numericPriority(pod api.Pod, podLister PodLister, minionLister MinionLister) (HostPriorityList, error) {
40
+	nodes, err := minionLister.List()
41
+	result := []HostPriority{}
42
+
43
+	if err != nil {
44
+		fmt.Errorf("failed to list nodes: %v", err)
45
+		return nil, err
46
+	}
47
+	for _, minion := range nodes {
48
+		score, err := strconv.Atoi(minion)
49
+		if err != nil {
50
+			return nil, err
51
+		}
52
+		result = append(result, HostPriority{
53
+			host:  minion,
54
+			score: score,
55
+		})
56
+	}
57
+	return result, nil
58
+}
59
+
60
+func TestGenericScheduler(t *testing.T) {
61
+	tests := []struct {
62
+		predicates   []FitPredicate
63
+		prioritizer  PriorityFunction
64
+		nodes        []string
65
+		pod          api.Pod
66
+		expectedHost string
67
+		expectsErr   bool
68
+	}{
69
+		{
70
+			predicates:  []FitPredicate{falsePredicate},
71
+			prioritizer: EqualPriority,
72
+			nodes:       []string{"machine1", "machine2"},
73
+			expectsErr:  true,
74
+		},
75
+		{
76
+			predicates:  []FitPredicate{truePredicate},
77
+			prioritizer: EqualPriority,
78
+			nodes:       []string{"machine1", "machine2"},
79
+			// Random choice between both, the rand seeded above with zero, chooses "machine2"
80
+			expectedHost: "machine2",
81
+		},
82
+		{
83
+			// Fits on a machine where the pod ID matches the machine name
84
+			predicates:   []FitPredicate{matchesPredicate},
85
+			prioritizer:  EqualPriority,
86
+			nodes:        []string{"machine1", "machine2"},
87
+			pod:          api.Pod{JSONBase: api.JSONBase{ID: "machine2"}},
88
+			expectedHost: "machine2",
89
+		},
90
+		{
91
+			predicates:   []FitPredicate{truePredicate},
92
+			prioritizer:  numericPriority,
93
+			nodes:        []string{"3", "2", "1"},
94
+			expectedHost: "1",
95
+		},
96
+		{
97
+			predicates:   []FitPredicate{matchesPredicate},
98
+			prioritizer:  numericPriority,
99
+			nodes:        []string{"3", "2", "1"},
100
+			pod:          api.Pod{JSONBase: api.JSONBase{ID: "2"}},
101
+			expectedHost: "2",
102
+		},
103
+		{
104
+			predicates:  []FitPredicate{truePredicate, falsePredicate},
105
+			prioritizer: numericPriority,
106
+			nodes:       []string{"3", "2", "1"},
107
+			expectsErr:  true,
108
+		},
109
+	}
110
+
111
+	for _, test := range tests {
112
+		random := rand.New(rand.NewSource(0))
113
+		scheduler := NewGenericScheduler(test.predicates, test.prioritizer, FakePodLister([]api.Pod{}), random)
114
+		machine, err := scheduler.Schedule(test.pod, FakeMinionLister(test.nodes))
115
+		if test.expectsErr {
116
+			if err == nil {
117
+				t.Error("Unexpected non-error")
118
+			}
119
+		} else {
120
+			if err != nil {
121
+				t.Errorf("Unexpected error: %v", err)
122
+			}
123
+			if test.expectedHost != machine {
124
+				t.Errorf("Expected: %s, Saw: %s", test.expectedHost, machine)
125
+			}
126
+		}
127
+	}
128
+}
0 129
new file mode 100644
... ...
@@ -0,0 +1,119 @@
0
+/*
1
+Copyright 2014 Google Inc. All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package scheduler
17
+
18
+import (
19
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
20
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
21
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/resources"
22
+	"github.com/golang/glog"
23
+)
24
+
25
+type NodeInfo interface {
26
+	GetNodeInfo(nodeName string) (api.Minion, error)
27
+}
28
+
29
+type ResourceFit struct {
30
+	info NodeInfo
31
+}
32
+
33
+type resourceRequest struct {
34
+	milliCPU int
35
+	memory   int
36
+}
37
+
38
+func getResourceRequest(pod *api.Pod) resourceRequest {
39
+	result := resourceRequest{}
40
+	for ix := range pod.DesiredState.Manifest.Containers {
41
+		result.memory += pod.DesiredState.Manifest.Containers[ix].Memory
42
+		result.milliCPU += pod.DesiredState.Manifest.Containers[ix].CPU
43
+	}
44
+	return result
45
+}
46
+
47
+// PodFitsResources calculates fit based on requested, rather than used resources
48
+func (r *ResourceFit) PodFitsResources(pod api.Pod, existingPods []api.Pod, node string) (bool, error) {
49
+	podRequest := getResourceRequest(&pod)
50
+	if podRequest.milliCPU == 0 && podRequest.memory == 0 {
51
+		// no resources requested always fits.
52
+		return true, nil
53
+	}
54
+	info, err := r.info.GetNodeInfo(node)
55
+	if err != nil {
56
+		return false, err
57
+	}
58
+	milliCPURequested := 0
59
+	memoryRequested := 0
60
+	for ix := range existingPods {
61
+		existingRequest := getResourceRequest(&existingPods[ix])
62
+		milliCPURequested += existingRequest.milliCPU
63
+		memoryRequested += existingRequest.memory
64
+	}
65
+
66
+	// TODO: convert to general purpose resource matching, when pods ask for resources
67
+	totalMilliCPU := int(resources.GetFloatResource(info.NodeResources.Capacity, resources.CPU, 0) * 1000)
68
+	totalMemory := resources.GetIntegerResource(info.NodeResources.Capacity, resources.Memory, 0)
69
+
70
+	fitsCPU := totalMilliCPU == 0 || (totalMilliCPU-milliCPURequested) >= podRequest.milliCPU
71
+	fitsMemory := totalMemory == 0 || (totalMemory-memoryRequested) >= podRequest.memory
72
+	glog.V(3).Infof("Calculated fit: cpu: %s, memory %s", fitsCPU, fitsMemory)
73
+
74
+	return fitsCPU && fitsMemory, nil
75
+}
76
+
77
+func PodFitsPorts(pod api.Pod, existingPods []api.Pod, node string) (bool, error) {
78
+	for _, scheduledPod := range existingPods {
79
+		for _, container := range pod.DesiredState.Manifest.Containers {
80
+			for _, port := range container.Ports {
81
+				if port.HostPort == 0 {
82
+					continue
83
+				}
84
+				if containsPort(scheduledPod, port) {
85
+					return false, nil
86
+				}
87
+			}
88
+		}
89
+	}
90
+	return true, nil
91
+}
92
+
93
+func containsPort(pod api.Pod, port api.Port) bool {
94
+	for _, container := range pod.DesiredState.Manifest.Containers {
95
+		for _, podPort := range container.Ports {
96
+			if podPort.HostPort == port.HostPort {
97
+				return true
98
+			}
99
+		}
100
+	}
101
+	return false
102
+}
103
+
104
+// MapPodsToMachines obtains a list of pods and pivots that list into a map where the keys are host names
105
+// and the values are the list of pods running on that host.
106
+func MapPodsToMachines(lister PodLister) (map[string][]api.Pod, error) {
107
+	machineToPods := map[string][]api.Pod{}
108
+	// TODO: perform more targeted query...
109
+	pods, err := lister.ListPods(labels.Everything())
110
+	if err != nil {
111
+		return map[string][]api.Pod{}, err
112
+	}
113
+	for _, scheduledPod := range pods {
114
+		host := scheduledPod.CurrentState.Host
115
+		machineToPods[host] = append(machineToPods[host], scheduledPod)
116
+	}
117
+	return machineToPods, nil
118
+}
0 119
new file mode 100644
... ...
@@ -0,0 +1,182 @@
0
+/*
1
+Copyright 2014 Google Inc. All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package scheduler
17
+
18
+import (
19
+	"testing"
20
+
21
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
22
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/resources"
23
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
24
+)
25
+
26
+type FakeNodeInfo api.Minion
27
+
28
+func (n FakeNodeInfo) GetNodeInfo(nodeName string) (api.Minion, error) {
29
+	return api.Minion(n), nil
30
+}
31
+
32
+func makeResources(milliCPU int, memory int) api.NodeResources {
33
+	return api.NodeResources{
34
+		Capacity: api.ResourceList{
35
+			resources.CPU: util.IntOrString{
36
+				IntVal: milliCPU,
37
+				Kind:   util.IntstrInt,
38
+			},
39
+			resources.Memory: util.IntOrString{
40
+				IntVal: memory,
41
+				Kind:   util.IntstrInt,
42
+			},
43
+		},
44
+	}
45
+}
46
+
47
+func newResourcePod(usage ...resourceRequest) api.Pod {
48
+	containers := []api.Container{}
49
+	for _, req := range usage {
50
+		containers = append(containers, api.Container{
51
+			Memory: req.memory,
52
+			CPU:    req.milliCPU,
53
+		})
54
+	}
55
+	return api.Pod{
56
+		DesiredState: api.PodState{
57
+			Manifest: api.ContainerManifest{
58
+				Containers: containers,
59
+			},
60
+		},
61
+	}
62
+}
63
+
64
+func TestPodFitsResources(t *testing.T) {
65
+	tests := []struct {
66
+		pod          api.Pod
67
+		existingPods []api.Pod
68
+		fits         bool
69
+		test         string
70
+	}{
71
+		{
72
+			pod: api.Pod{},
73
+			existingPods: []api.Pod{
74
+				newResourcePod(resourceRequest{milliCPU: 10, memory: 20}),
75
+			},
76
+			fits: true,
77
+			test: "no resources requested always fits",
78
+		},
79
+		{
80
+			pod: newResourcePod(resourceRequest{milliCPU: 1, memory: 1}),
81
+			existingPods: []api.Pod{
82
+				newResourcePod(resourceRequest{milliCPU: 10, memory: 20}),
83
+			},
84
+			fits: false,
85
+			test: "too many resources fails",
86
+		},
87
+		{
88
+			pod: newResourcePod(resourceRequest{milliCPU: 1, memory: 1}),
89
+			existingPods: []api.Pod{
90
+				newResourcePod(resourceRequest{milliCPU: 5, memory: 5}),
91
+			},
92
+			fits: true,
93
+			test: "both resources fit",
94
+		},
95
+		{
96
+			pod: newResourcePod(resourceRequest{milliCPU: 1, memory: 2}),
97
+			existingPods: []api.Pod{
98
+				newResourcePod(resourceRequest{milliCPU: 5, memory: 19}),
99
+			},
100
+			fits: false,
101
+			test: "one resources fits",
102
+		},
103
+		{
104
+			pod: newResourcePod(resourceRequest{milliCPU: 5, memory: 1}),
105
+			existingPods: []api.Pod{
106
+				newResourcePod(resourceRequest{milliCPU: 5, memory: 19}),
107
+			},
108
+			fits: true,
109
+			test: "equal edge case",
110
+		},
111
+	}
112
+	for _, test := range tests {
113
+		node := api.Minion{NodeResources: makeResources(10, 20)}
114
+
115
+		fit := ResourceFit{FakeNodeInfo(node)}
116
+		fits, err := fit.PodFitsResources(test.pod, test.existingPods, "machine")
117
+		if err != nil {
118
+			t.Errorf("unexpected error: %v", err)
119
+		}
120
+		if fits != test.fits {
121
+			t.Errorf("%s: expected: %v got %v", test.test, test.fits, fits)
122
+		}
123
+	}
124
+}
125
+
126
+func TestPodFitsPorts(t *testing.T) {
127
+	tests := []struct {
128
+		pod          api.Pod
129
+		existingPods []api.Pod
130
+		fits         bool
131
+		test         string
132
+	}{
133
+		{
134
+			pod:          api.Pod{},
135
+			existingPods: []api.Pod{},
136
+			fits:         true,
137
+			test:         "nothing running",
138
+		},
139
+		{
140
+			pod: newPod("m1", 8080),
141
+			existingPods: []api.Pod{
142
+				newPod("m1", 9090),
143
+			},
144
+			fits: true,
145
+			test: "other port",
146
+		},
147
+		{
148
+			pod: newPod("m1", 8080),
149
+			existingPods: []api.Pod{
150
+				newPod("m1", 8080),
151
+			},
152
+			fits: false,
153
+			test: "same port",
154
+		},
155
+		{
156
+			pod: newPod("m1", 8000, 8080),
157
+			existingPods: []api.Pod{
158
+				newPod("m1", 8080),
159
+			},
160
+			fits: false,
161
+			test: "second port",
162
+		},
163
+		{
164
+			pod: newPod("m1", 8000, 8080),
165
+			existingPods: []api.Pod{
166
+				newPod("m1", 8001, 8080),
167
+			},
168
+			fits: false,
169
+			test: "second port",
170
+		},
171
+	}
172
+	for _, test := range tests {
173
+		fits, err := PodFitsPorts(test.pod, test.existingPods, "machine")
174
+		if err != nil {
175
+			t.Errorf("unexpected error: %v")
176
+		}
177
+		if test.fits != fits {
178
+			t.Errorf("%s: expected %v, saw %v", test.test, test.fits, fits)
179
+		}
180
+	}
181
+}
0 182
deleted file mode 100644
... ...
@@ -1,48 +0,0 @@
1
-/*
2
-Copyright 2014 Google Inc. All rights reserved.
3
-
4
-Licensed under the Apache License, Version 2.0 (the "License");
5
-you may not use this file except in compliance with the License.
6
-You may obtain a copy of the License at
7
-
8
-    http://www.apache.org/licenses/LICENSE-2.0
9
-
10
-Unless required by applicable law or agreed to in writing, software
11
-distributed under the License is distributed on an "AS IS" BASIS,
12
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
-See the License for the specific language governing permissions and
14
-limitations under the License.
15
-*/
16
-
17
-package scheduler
18
-
19
-import (
20
-	"math/rand"
21
-	"sync"
22
-
23
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
24
-)
25
-
26
-// RandomScheduler chooses machines uniformly at random.
27
-type RandomScheduler struct {
28
-	random     *rand.Rand
29
-	randomLock sync.Mutex
30
-}
31
-
32
-func NewRandomScheduler(random *rand.Rand) Scheduler {
33
-	return &RandomScheduler{
34
-		random: random,
35
-	}
36
-}
37
-
38
-// Schedule schedules a given pod to a random machine.
39
-func (s *RandomScheduler) Schedule(pod api.Pod, minionLister MinionLister) (string, error) {
40
-	machines, err := minionLister.List()
41
-	if err != nil {
42
-		return "", err
43
-	}
44
-
45
-	s.randomLock.Lock()
46
-	defer s.randomLock.Unlock()
47
-	return machines[s.random.Int()%len(machines)], nil
48
-}
49 1
deleted file mode 100644
... ...
@@ -1,34 +0,0 @@
1
-/*
2
-Copyright 2014 Google Inc. All rights reserved.
3
-
4
-Licensed under the Apache License, Version 2.0 (the "License");
5
-you may not use this file except in compliance with the License.
6
-You may obtain a copy of the License at
7
-
8
-    http://www.apache.org/licenses/LICENSE-2.0
9
-
10
-Unless required by applicable law or agreed to in writing, software
11
-distributed under the License is distributed on an "AS IS" BASIS,
12
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
-See the License for the specific language governing permissions and
14
-limitations under the License.
15
-*/
16
-
17
-package scheduler
18
-
19
-import (
20
-	"math/rand"
21
-	"testing"
22
-
23
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
24
-)
25
-
26
-func TestRandomScheduler(t *testing.T) {
27
-	random := rand.New(rand.NewSource(0))
28
-	st := schedulerTester{
29
-		t:            t,
30
-		scheduler:    NewRandomScheduler(random),
31
-		minionLister: FakeMinionLister{"m1", "m2", "m3", "m4"},
32
-	}
33
-	st.expectSuccess(api.Pod{})
34
-}
35 1
deleted file mode 100644
... ...
@@ -1,94 +0,0 @@
1
-/*
2
-Copyright 2014 Google Inc. All rights reserved.
3
-
4
-Licensed under the Apache License, Version 2.0 (the "License");
5
-you may not use this file except in compliance with the License.
6
-You may obtain a copy of the License at
7
-
8
-    http://www.apache.org/licenses/LICENSE-2.0
9
-
10
-Unless required by applicable law or agreed to in writing, software
11
-distributed under the License is distributed on an "AS IS" BASIS,
12
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
-See the License for the specific language governing permissions and
14
-limitations under the License.
15
-*/
16
-
17
-package scheduler
18
-
19
-import (
20
-	"fmt"
21
-	"math/rand"
22
-	"sync"
23
-
24
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
25
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
26
-)
27
-
28
-// RandomFitScheduler is a Scheduler which schedules a Pod on a random machine which matches its requirement.
29
-type RandomFitScheduler struct {
30
-	podLister  PodLister
31
-	random     *rand.Rand
32
-	randomLock sync.Mutex
33
-}
34
-
35
-func NewRandomFitScheduler(podLister PodLister, random *rand.Rand) Scheduler {
36
-	return &RandomFitScheduler{
37
-		podLister: podLister,
38
-		random:    random,
39
-	}
40
-}
41
-
42
-func (s *RandomFitScheduler) containsPort(pod api.Pod, port api.Port) bool {
43
-	for _, container := range pod.DesiredState.Manifest.Containers {
44
-		for _, podPort := range container.Ports {
45
-			if podPort.HostPort == port.HostPort {
46
-				return true
47
-			}
48
-		}
49
-	}
50
-	return false
51
-}
52
-
53
-// Schedule schedules a pod on a random machine which matches its requirement.
54
-func (s *RandomFitScheduler) Schedule(pod api.Pod, minionLister MinionLister) (string, error) {
55
-	machines, err := minionLister.List()
56
-	if err != nil {
57
-		return "", err
58
-	}
59
-	machineToPods := map[string][]api.Pod{}
60
-	// TODO: perform more targeted query...
61
-	pods, err := s.podLister.ListPods(labels.Everything())
62
-	if err != nil {
63
-		return "", err
64
-	}
65
-	for _, scheduledPod := range pods {
66
-		host := scheduledPod.CurrentState.Host
67
-		machineToPods[host] = append(machineToPods[host], scheduledPod)
68
-	}
69
-	var machineOptions []string
70
-	for _, machine := range machines {
71
-		podFits := true
72
-		for _, scheduledPod := range machineToPods[machine] {
73
-			for _, container := range pod.DesiredState.Manifest.Containers {
74
-				for _, port := range container.Ports {
75
-					if port.HostPort == 0 {
76
-						continue
77
-					}
78
-					if s.containsPort(scheduledPod, port) {
79
-						podFits = false
80
-					}
81
-				}
82
-			}
83
-		}
84
-		if podFits {
85
-			machineOptions = append(machineOptions, machine)
86
-		}
87
-	}
88
-	if len(machineOptions) == 0 {
89
-		return "", fmt.Errorf("failed to find fit for %#v", pod)
90
-	}
91
-	s.randomLock.Lock()
92
-	defer s.randomLock.Unlock()
93
-	return machineOptions[s.random.Int()%len(machineOptions)], nil
94
-}
95 1
deleted file mode 100644
... ...
@@ -1,78 +0,0 @@
1
-/*
2
-Copyright 2014 Google Inc. All rights reserved.
3
-
4
-Licensed under the Apache License, Version 2.0 (the "License");
5
-you may not use this file except in compliance with the License.
6
-You may obtain a copy of the License at
7
-
8
-    http://www.apache.org/licenses/LICENSE-2.0
9
-
10
-Unless required by applicable law or agreed to in writing, software
11
-distributed under the License is distributed on an "AS IS" BASIS,
12
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
-See the License for the specific language governing permissions and
14
-limitations under the License.
15
-*/
16
-
17
-package scheduler
18
-
19
-import (
20
-	"math/rand"
21
-	"testing"
22
-
23
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
24
-)
25
-
26
-func TestRandomFitSchedulerNothingScheduled(t *testing.T) {
27
-	fakeRegistry := FakePodLister{}
28
-	r := rand.New(rand.NewSource(0))
29
-	st := schedulerTester{
30
-		t:            t,
31
-		scheduler:    NewRandomFitScheduler(&fakeRegistry, r),
32
-		minionLister: FakeMinionLister{"m1", "m2", "m3"},
33
-	}
34
-	st.expectSchedule(api.Pod{}, "m3")
35
-}
36
-
37
-func TestRandomFitSchedulerFirstScheduled(t *testing.T) {
38
-	fakeRegistry := FakePodLister{
39
-		newPod("m1", 8080),
40
-	}
41
-	r := rand.New(rand.NewSource(0))
42
-	st := schedulerTester{
43
-		t:            t,
44
-		scheduler:    NewRandomFitScheduler(fakeRegistry, r),
45
-		minionLister: FakeMinionLister{"m1", "m2", "m3"},
46
-	}
47
-	st.expectSchedule(newPod("", 8080), "m3")
48
-}
49
-
50
-func TestRandomFitSchedulerFirstScheduledComplicated(t *testing.T) {
51
-	fakeRegistry := FakePodLister{
52
-		newPod("m1", 80, 8080),
53
-		newPod("m2", 8081, 8082, 8083),
54
-		newPod("m3", 80, 443, 8085),
55
-	}
56
-	r := rand.New(rand.NewSource(0))
57
-	st := schedulerTester{
58
-		t:            t,
59
-		scheduler:    NewRandomFitScheduler(fakeRegistry, r),
60
-		minionLister: FakeMinionLister{"m1", "m2", "m3"},
61
-	}
62
-	st.expectSchedule(newPod("", 8080, 8081), "m3")
63
-}
64
-
65
-func TestRandomFitSchedulerFirstScheduledImpossible(t *testing.T) {
66
-	fakeRegistry := FakePodLister{
67
-		newPod("m1", 8080),
68
-		newPod("m2", 8081),
69
-		newPod("m3", 8080),
70
-	}
71
-	r := rand.New(rand.NewSource(0))
72
-	st := schedulerTester{
73
-		t:            t,
74
-		scheduler:    NewRandomFitScheduler(fakeRegistry, r),
75
-		minionLister: FakeMinionLister{"m1", "m2", "m3"},
76
-	}
77
-	st.expectFailure(newPod("", 8080, 8081))
78
-}
79 1
deleted file mode 100644
... ...
@@ -1,43 +0,0 @@
1
-/*
2
-Copyright 2014 Google Inc. All rights reserved.
3
-
4
-Licensed under the Apache License, Version 2.0 (the "License");
5
-you may not use this file except in compliance with the License.
6
-You may obtain a copy of the License at
7
-
8
-    http://www.apache.org/licenses/LICENSE-2.0
9
-
10
-Unless required by applicable law or agreed to in writing, software
11
-distributed under the License is distributed on an "AS IS" BASIS,
12
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
-See the License for the specific language governing permissions and
14
-limitations under the License.
15
-*/
16
-
17
-package scheduler
18
-
19
-import (
20
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
21
-)
22
-
23
-// RoundRobinScheduler chooses machines in order.
24
-type RoundRobinScheduler struct {
25
-	currentIndex int
26
-}
27
-
28
-func NewRoundRobinScheduler() Scheduler {
29
-	return &RoundRobinScheduler{
30
-		currentIndex: -1,
31
-	}
32
-}
33
-
34
-// Schedule schedules a pod on the machine next to the last scheduled machine.
35
-func (s *RoundRobinScheduler) Schedule(pod api.Pod, minionLister MinionLister) (string, error) {
36
-	machines, err := minionLister.List()
37
-	if err != nil {
38
-		return "", err
39
-	}
40
-	s.currentIndex = (s.currentIndex + 1) % len(machines)
41
-	result := machines[s.currentIndex]
42
-	return result, nil
43
-}
44 1
deleted file mode 100644
... ...
@@ -1,35 +0,0 @@
1
-/*
2
-Copyright 2014 Google Inc. All rights reserved.
3
-
4
-Licensed under the Apache License, Version 2.0 (the "License");
5
-you may not use this file except in compliance with the License.
6
-You may obtain a copy of the License at
7
-
8
-    http://www.apache.org/licenses/LICENSE-2.0
9
-
10
-Unless required by applicable law or agreed to in writing, software
11
-distributed under the License is distributed on an "AS IS" BASIS,
12
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
-See the License for the specific language governing permissions and
14
-limitations under the License.
15
-*/
16
-
17
-package scheduler
18
-
19
-import (
20
-	"testing"
21
-
22
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
23
-)
24
-
25
-func TestRoundRobinScheduler(t *testing.T) {
26
-	st := schedulerTester{
27
-		t:            t,
28
-		scheduler:    NewRoundRobinScheduler(),
29
-		minionLister: FakeMinionLister{"m1", "m2", "m3", "m4"},
30
-	}
31
-	st.expectSchedule(api.Pod{}, "m1")
32
-	st.expectSchedule(api.Pod{}, "m2")
33
-	st.expectSchedule(api.Pod{}, "m3")
34
-	st.expectSchedule(api.Pod{}, "m4")
35
-}
36 1
new file mode 100644
... ...
@@ -0,0 +1,54 @@
0
+/*
1
+Copyright 2014 Google Inc. All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package scheduler
17
+
18
+import (
19
+	"math/rand"
20
+
21
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
22
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
23
+)
24
+
25
+// CalculateSpreadPriority spreads pods by minimizing the number of pods on the same machine with the same labels.
26
+// Importantly, if there are services in the system that span multiple heterogenous sets of pods, this spreading priority
27
+// may not provide optimal spreading for the members of that Service.
28
+// TODO: consider if we want to include Service label sets in the scheduling priority.
29
+func CalculateSpreadPriority(pod api.Pod, podLister PodLister, minionLister MinionLister) (HostPriorityList, error) {
30
+	pods, err := podLister.ListPods(labels.SelectorFromSet(pod.Labels))
31
+	if err != nil {
32
+		return nil, err
33
+	}
34
+	minions, err := minionLister.List()
35
+	if err != nil {
36
+		return nil, err
37
+	}
38
+
39
+	counts := map[string]int{}
40
+	for _, pod := range pods {
41
+		counts[pod.CurrentState.Host]++
42
+	}
43
+
44
+	result := []HostPriority{}
45
+	for _, minion := range minions {
46
+		result = append(result, HostPriority{host: minion, score: counts[minion]})
47
+	}
48
+	return result, nil
49
+}
50
+
51
+func NewSpreadingScheduler(podLister PodLister, minionLister MinionLister, predicates []FitPredicate, random *rand.Rand) Scheduler {
52
+	return NewGenericScheduler(predicates, CalculateSpreadPriority, podLister, random)
53
+}
0 54
new file mode 100644
... ...
@@ -0,0 +1,111 @@
0
+/*
1
+Copyright 2014 Google Inc. All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package scheduler
17
+
18
+import (
19
+	"reflect"
20
+	"testing"
21
+
22
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
23
+)
24
+
25
+func TestSpreadPriority(t *testing.T) {
26
+	labels1 := map[string]string{
27
+		"foo": "bar",
28
+		"baz": "blah",
29
+	}
30
+	labels2 := map[string]string{
31
+		"bar": "foo",
32
+		"baz": "blah",
33
+	}
34
+	machine1State := api.PodState{
35
+		Host: "machine1",
36
+	}
37
+	machine2State := api.PodState{
38
+		Host: "machine2",
39
+	}
40
+	tests := []struct {
41
+		pod          api.Pod
42
+		pods         []api.Pod
43
+		nodes        []string
44
+		expectedList HostPriorityList
45
+		test         string
46
+	}{
47
+		{
48
+			nodes:        []string{"machine1", "machine2"},
49
+			expectedList: []HostPriority{{"machine1", 0}, {"machine2", 0}},
50
+			test:         "nothing scheduled",
51
+		},
52
+		{
53
+			pod:          api.Pod{Labels: labels1},
54
+			pods:         []api.Pod{{CurrentState: machine1State}},
55
+			nodes:        []string{"machine1", "machine2"},
56
+			expectedList: []HostPriority{{"machine1", 0}, {"machine2", 0}},
57
+			test:         "no labels",
58
+		},
59
+		{
60
+			pod:          api.Pod{Labels: labels1},
61
+			pods:         []api.Pod{{CurrentState: machine1State, Labels: labels2}},
62
+			nodes:        []string{"machine1", "machine2"},
63
+			expectedList: []HostPriority{{"machine1", 0}, {"machine2", 0}},
64
+			test:         "different labels",
65
+		},
66
+		{
67
+			pod: api.Pod{Labels: labels1},
68
+			pods: []api.Pod{
69
+				{CurrentState: machine1State, Labels: labels2},
70
+				{CurrentState: machine2State, Labels: labels1},
71
+			},
72
+			nodes:        []string{"machine1", "machine2"},
73
+			expectedList: []HostPriority{{"machine1", 0}, {"machine2", 1}},
74
+			test:         "one label match",
75
+		},
76
+		{
77
+			pod: api.Pod{Labels: labels1},
78
+			pods: []api.Pod{
79
+				{CurrentState: machine1State, Labels: labels2},
80
+				{CurrentState: machine1State, Labels: labels1},
81
+				{CurrentState: machine2State, Labels: labels1},
82
+			},
83
+			nodes:        []string{"machine1", "machine2"},
84
+			expectedList: []HostPriority{{"machine1", 1}, {"machine2", 1}},
85
+			test:         "two label matches on different machines",
86
+		},
87
+		{
88
+			pod: api.Pod{Labels: labels1},
89
+			pods: []api.Pod{
90
+				{CurrentState: machine1State, Labels: labels2},
91
+				{CurrentState: machine1State, Labels: labels1},
92
+				{CurrentState: machine2State, Labels: labels1},
93
+				{CurrentState: machine2State, Labels: labels1},
94
+			},
95
+			nodes:        []string{"machine1", "machine2"},
96
+			expectedList: []HostPriority{{"machine1", 1}, {"machine2", 2}},
97
+			test:         "three label matches",
98
+		},
99
+	}
100
+
101
+	for _, test := range tests {
102
+		list, err := CalculateSpreadPriority(test.pod, FakePodLister(test.pods), FakeMinionLister(test.nodes))
103
+		if err != nil {
104
+			t.Errorf("unexpected error: %v", err)
105
+		}
106
+		if !reflect.DeepEqual(test.expectedList, list) {
107
+			t.Errorf("%s: expected %#v, got %#v", test.test, test.expectedList, list)
108
+		}
109
+	}
110
+}
0 111
new file mode 100644
... ...
@@ -0,0 +1,49 @@
0
+/*
1
+Copyright 2014 Google Inc. All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package scheduler
17
+
18
+import (
19
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
20
+)
21
+
22
+// FitPredicate is a function that indicates if a pod fits into an existing node.
23
+type FitPredicate func(pod api.Pod, existingPods []api.Pod, node string) (bool, error)
24
+
25
+// HostPriority represents the priority of scheduling to a particular host, lower priority is better.
26
+type HostPriority struct {
27
+	host  string
28
+	score int
29
+}
30
+
31
+type HostPriorityList []HostPriority
32
+
33
+func (h HostPriorityList) Len() int {
34
+	return len(h)
35
+}
36
+
37
+func (h HostPriorityList) Less(i, j int) bool {
38
+	if h[i].score == h[j].score {
39
+		return h[i].host < h[j].host
40
+	}
41
+	return h[i].score < h[j].score
42
+}
43
+
44
+func (h HostPriorityList) Swap(i, j int) {
45
+	h[i], h[j] = h[j], h[i]
46
+}
47
+
48
+type PriorityFunction func(pod api.Pod, podLister PodLister, minionLister MinionLister) (HostPriorityList, error)
... ...
@@ -20,6 +20,7 @@ import (
20 20
 	"fmt"
21 21
 	"net"
22 22
 	"strconv"
23
+	"strings"
23 24
 
24 25
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
25 26
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
... ...
@@ -46,14 +47,16 @@ func NewEndpointController(serviceRegistry service.Registry, client *client.Clie
46 46
 
47 47
 // SyncServiceEndpoints syncs service endpoints.
48 48
 func (e *EndpointController) SyncServiceEndpoints() error {
49
-	services, err := e.client.ListServices(labels.Everything())
49
+	ctx := api.NewContext()
50
+	services, err := e.client.ListServices(ctx, labels.Everything())
50 51
 	if err != nil {
51 52
 		glog.Errorf("Failed to list services: %v", err)
52 53
 		return err
53 54
 	}
54 55
 	var resultErr error
55 56
 	for _, service := range services.Items {
56
-		pods, err := e.client.ListPods(labels.Set(service.Selector).AsSelector())
57
+		nsCtx := api.WithNamespace(ctx, service.Namespace)
58
+		pods, err := e.client.ListPods(nsCtx, labels.Set(service.Selector).AsSelector())
57 59
 		if err != nil {
58 60
 			glog.Errorf("Error syncing service: %#v, skipping.", service)
59 61
 			resultErr = err
... ...
@@ -72,11 +75,35 @@ func (e *EndpointController) SyncServiceEndpoints() error {
72 72
 			}
73 73
 			endpoints[ix] = net.JoinHostPort(pod.CurrentState.PodIP, strconv.Itoa(port))
74 74
 		}
75
-		// TODO: this is totally broken, we need to compute this and store inside an AtomicUpdate loop.
76
-		err = e.serviceRegistry.UpdateEndpoints(&api.Endpoints{
77
-			JSONBase:  api.JSONBase{ID: service.ID},
78
-			Endpoints: endpoints,
79
-		})
75
+		currentEndpoints, err := e.client.GetEndpoints(nsCtx, service.ID)
76
+		if err != nil {
77
+			// TODO this is brittle as all get out, refactor the client libraries to return a structured error.
78
+			if strings.Contains(err.Error(), "not found") {
79
+				currentEndpoints = &api.Endpoints{
80
+					JSONBase: api.JSONBase{
81
+						ID: service.ID,
82
+					},
83
+				}
84
+			} else {
85
+				glog.Errorf("Error getting endpoints: %#v", err)
86
+				continue
87
+			}
88
+		}
89
+		newEndpoints := &api.Endpoints{}
90
+		*newEndpoints = *currentEndpoints
91
+		newEndpoints.Endpoints = endpoints
92
+
93
+		if currentEndpoints.ResourceVersion == 0 {
94
+			// No previous endpoints, create them
95
+			_, err = e.client.CreateEndpoints(nsCtx, newEndpoints)
96
+		} else {
97
+			// Pre-existing
98
+			if endpointsEqual(currentEndpoints, endpoints) {
99
+				glog.V(2).Infof("endpoints are equal for %s, skipping update", service.ID)
100
+				continue
101
+			}
102
+			_, err = e.client.UpdateEndpoints(nsCtx, newEndpoints)
103
+		}
80 104
 		if err != nil {
81 105
 			glog.Errorf("Error updating endpoints: %#v", err)
82 106
 			continue
... ...
@@ -85,6 +112,30 @@ func (e *EndpointController) SyncServiceEndpoints() error {
85 85
 	return resultErr
86 86
 }
87 87
 
88
+func containsEndpoint(endpoints *api.Endpoints, endpoint string) bool {
89
+	if endpoints == nil {
90
+		return false
91
+	}
92
+	for ix := range endpoints.Endpoints {
93
+		if endpoints.Endpoints[ix] == endpoint {
94
+			return true
95
+		}
96
+	}
97
+	return false
98
+}
99
+
100
+func endpointsEqual(e *api.Endpoints, endpoints []string) bool {
101
+	if len(e.Endpoints) != len(endpoints) {
102
+		return false
103
+	}
104
+	for _, endpoint := range endpoints {
105
+		if !containsEndpoint(e, endpoint) {
106
+			return false
107
+		}
108
+	}
109
+	return true
110
+}
111
+
88 112
 // findPort locates the container port for the given manifest and portName.
89 113
 func findPort(manifest *api.ContainerManifest, portName util.IntOrString) (int, error) {
90 114
 	if ((portName.Kind == util.IntstrString && len(portName.StrVal) == 0) ||
... ...
@@ -24,8 +24,10 @@ import (
24 24
 
25 25
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
26 26
 	_ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
27
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
27 28
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
28 29
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest"
30
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
29 31
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
30 32
 )
31 33
 
... ...
@@ -35,7 +37,7 @@ func newPodList(count int) api.PodList {
35 35
 		pods = append(pods, api.Pod{
36 36
 			JSONBase: api.JSONBase{
37 37
 				ID:         fmt.Sprintf("pod%d", i),
38
-				APIVersion: "v1beta1",
38
+				APIVersion: testapi.Version(),
39 39
 			},
40 40
 			DesiredState: api.PodState{
41 41
 				Manifest: api.ContainerManifest{
... ...
@@ -56,7 +58,7 @@ func newPodList(count int) api.PodList {
56 56
 		})
57 57
 	}
58 58
 	return api.PodList{
59
-		JSONBase: api.JSONBase{APIVersion: "v1beta1", Kind: "PodList"},
59
+		JSONBase: api.JSONBase{APIVersion: testapi.Version(), Kind: "PodList"},
60 60
 		Items:    pods,
61 61
 	}
62 62
 }
... ...
@@ -127,7 +129,7 @@ type serverResponse struct {
127 127
 	obj        interface{}
128 128
 }
129 129
 
130
-func makeTestServer(t *testing.T, podResponse serverResponse, serviceResponse serverResponse) *httptest.Server {
130
+func makeTestServer(t *testing.T, podResponse serverResponse, serviceResponse serverResponse, endpointsResponse serverResponse) (*httptest.Server, *util.FakeHandler) {
131 131
 	fakePodHandler := util.FakeHandler{
132 132
 		StatusCode:   podResponse.statusCode,
133 133
 		ResponseBody: util.EncodeJSON(podResponse.obj),
... ...
@@ -136,21 +138,28 @@ func makeTestServer(t *testing.T, podResponse serverResponse, serviceResponse se
136 136
 		StatusCode:   serviceResponse.statusCode,
137 137
 		ResponseBody: util.EncodeJSON(serviceResponse.obj),
138 138
 	}
139
+	fakeEndpointsHandler := util.FakeHandler{
140
+		StatusCode:   endpointsResponse.statusCode,
141
+		ResponseBody: util.EncodeJSON(endpointsResponse.obj),
142
+	}
139 143
 	mux := http.NewServeMux()
140
-	mux.Handle("/api/v1beta1/pods", &fakePodHandler)
141
-	mux.Handle("/api/v1beta1/services", &fakeServiceHandler)
144
+	mux.Handle("/api/"+testapi.Version()+"/pods", &fakePodHandler)
145
+	mux.Handle("/api/"+testapi.Version()+"/services", &fakeServiceHandler)
146
+	mux.Handle("/api/"+testapi.Version()+"/endpoints", &fakeEndpointsHandler)
147
+	mux.Handle("/api/"+testapi.Version()+"/endpoints/", &fakeEndpointsHandler)
142 148
 	mux.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) {
143 149
 		t.Errorf("unexpected request: %v", req.RequestURI)
144 150
 		res.WriteHeader(http.StatusNotFound)
145 151
 	})
146
-	return httptest.NewServer(mux)
152
+	return httptest.NewServer(mux), &fakeEndpointsHandler
147 153
 }
148 154
 
149 155
 func TestSyncEndpointsEmpty(t *testing.T) {
150
-	testServer := makeTestServer(t,
156
+	testServer, _ := makeTestServer(t,
151 157
 		serverResponse{http.StatusOK, newPodList(0)},
152
-		serverResponse{http.StatusOK, api.ServiceList{}})
153
-	client := client.NewOrDie(testServer.URL, "v1beta1", nil)
158
+		serverResponse{http.StatusOK, api.ServiceList{}},
159
+		serverResponse{http.StatusOK, api.Endpoints{}})
160
+	client := client.NewOrDie(&client.Config{Host: testServer.URL, Version: testapi.Version()})
154 161
 	serviceRegistry := registrytest.ServiceRegistry{}
155 162
 	endpoints := NewEndpointController(&serviceRegistry, client)
156 163
 	if err := endpoints.SyncServiceEndpoints(); err != nil {
... ...
@@ -159,10 +168,11 @@ func TestSyncEndpointsEmpty(t *testing.T) {
159 159
 }
160 160
 
161 161
 func TestSyncEndpointsError(t *testing.T) {
162
-	testServer := makeTestServer(t,
162
+	testServer, _ := makeTestServer(t,
163 163
 		serverResponse{http.StatusOK, newPodList(0)},
164
-		serverResponse{http.StatusInternalServerError, api.ServiceList{}})
165
-	client := client.NewOrDie(testServer.URL, "v1beta1", nil)
164
+		serverResponse{http.StatusInternalServerError, api.ServiceList{}},
165
+		serverResponse{http.StatusOK, api.Endpoints{}})
166
+	client := client.NewOrDie(&client.Config{Host: testServer.URL, Version: testapi.Version()})
166 167
 	serviceRegistry := registrytest.ServiceRegistry{
167 168
 		Err: fmt.Errorf("test error"),
168 169
 	}
... ...
@@ -172,29 +182,100 @@ func TestSyncEndpointsError(t *testing.T) {
172 172
 	}
173 173
 }
174 174
 
175
-func TestSyncEndpointsItems(t *testing.T) {
175
+func TestSyncEndpointsItemsPreexisting(t *testing.T) {
176
+	serviceList := api.ServiceList{
177
+		Items: []api.Service{
178
+			{
179
+				JSONBase: api.JSONBase{ID: "foo"},
180
+				Selector: map[string]string{
181
+					"foo": "bar",
182
+				},
183
+			},
184
+		},
185
+	}
186
+	testServer, endpointsHandler := makeTestServer(t,
187
+		serverResponse{http.StatusOK, newPodList(1)},
188
+		serverResponse{http.StatusOK, serviceList},
189
+		serverResponse{http.StatusOK, api.Endpoints{
190
+			JSONBase: api.JSONBase{
191
+				ID:              "foo",
192
+				ResourceVersion: 1,
193
+			},
194
+			Endpoints: []string{"6.7.8.9:1000"},
195
+		}})
196
+	client := client.NewOrDie(&client.Config{Host: testServer.URL, Version: testapi.Version()})
197
+	serviceRegistry := registrytest.ServiceRegistry{}
198
+	endpoints := NewEndpointController(&serviceRegistry, client)
199
+	if err := endpoints.SyncServiceEndpoints(); err != nil {
200
+		t.Errorf("unexpected error: %v", err)
201
+	}
202
+	data := runtime.EncodeOrDie(testapi.CodecForVersionOrDie(), &api.Endpoints{
203
+		JSONBase: api.JSONBase{
204
+			ID:              "foo",
205
+			ResourceVersion: 1,
206
+		},
207
+		Endpoints: []string{"1.2.3.4:8080"},
208
+	})
209
+	endpointsHandler.ValidateRequest(t, "/api/"+testapi.Version()+"/endpoints/foo", "PUT", &data)
210
+}
211
+
212
+func TestSyncEndpointsItemsPreexistingIdentical(t *testing.T) {
176 213
 	serviceList := api.ServiceList{
177 214
 		Items: []api.Service{
178 215
 			{
216
+				JSONBase: api.JSONBase{ID: "foo"},
179 217
 				Selector: map[string]string{
180 218
 					"foo": "bar",
181 219
 				},
182 220
 			},
183 221
 		},
184 222
 	}
185
-	testServer := makeTestServer(t,
223
+	testServer, endpointsHandler := makeTestServer(t,
186 224
 		serverResponse{http.StatusOK, newPodList(1)},
187
-		serverResponse{http.StatusOK, serviceList})
188
-	client := client.NewOrDie(testServer.URL, "v1beta1", nil)
225
+		serverResponse{http.StatusOK, serviceList},
226
+		serverResponse{http.StatusOK, api.Endpoints{
227
+			JSONBase: api.JSONBase{
228
+				ResourceVersion: 1,
229
+			},
230
+			Endpoints: []string{"1.2.3.4:8080"},
231
+		}})
232
+	client := client.NewOrDie(&client.Config{Host: testServer.URL, Version: testapi.Version()})
189 233
 	serviceRegistry := registrytest.ServiceRegistry{}
190 234
 	endpoints := NewEndpointController(&serviceRegistry, client)
191 235
 	if err := endpoints.SyncServiceEndpoints(); err != nil {
192 236
 		t.Errorf("unexpected error: %v", err)
193 237
 	}
194
-	if len(serviceRegistry.Endpoints.Endpoints) != 1 ||
195
-		serviceRegistry.Endpoints.Endpoints[0] != "1.2.3.4:8080" {
196
-		t.Errorf("Unexpected endpoints update: %#v", serviceRegistry.Endpoints)
238
+	endpointsHandler.ValidateRequest(t, "/api/"+testapi.Version()+"/endpoints/foo", "GET", nil)
239
+}
240
+
241
+func TestSyncEndpointsItems(t *testing.T) {
242
+	serviceList := api.ServiceList{
243
+		Items: []api.Service{
244
+			{
245
+				JSONBase: api.JSONBase{ID: "foo"},
246
+				Selector: map[string]string{
247
+					"foo": "bar",
248
+				},
249
+			},
250
+		},
251
+	}
252
+	testServer, endpointsHandler := makeTestServer(t,
253
+		serverResponse{http.StatusOK, newPodList(1)},
254
+		serverResponse{http.StatusOK, serviceList},
255
+		serverResponse{http.StatusOK, api.Endpoints{}})
256
+	client := client.NewOrDie(&client.Config{Host: testServer.URL, Version: testapi.Version()})
257
+	serviceRegistry := registrytest.ServiceRegistry{}
258
+	endpoints := NewEndpointController(&serviceRegistry, client)
259
+	if err := endpoints.SyncServiceEndpoints(); err != nil {
260
+		t.Errorf("unexpected error: %v", err)
197 261
 	}
262
+	data := runtime.EncodeOrDie(testapi.CodecForVersionOrDie(), &api.Endpoints{
263
+		JSONBase: api.JSONBase{
264
+			ResourceVersion: 0,
265
+		},
266
+		Endpoints: []string{"1.2.3.4:8080"},
267
+	})
268
+	endpointsHandler.ValidateRequest(t, "/api/"+testapi.Version()+"/endpoints", "POST", &data)
198 269
 }
199 270
 
200 271
 func TestSyncEndpointsPodError(t *testing.T) {
... ...
@@ -207,10 +288,11 @@ func TestSyncEndpointsPodError(t *testing.T) {
207 207
 			},
208 208
 		},
209 209
 	}
210
-	testServer := makeTestServer(t,
210
+	testServer, _ := makeTestServer(t,
211 211
 		serverResponse{http.StatusInternalServerError, api.PodList{}},
212
-		serverResponse{http.StatusOK, serviceList})
213
-	client := client.NewOrDie(testServer.URL, "v1beta1", nil)
212
+		serverResponse{http.StatusOK, serviceList},
213
+		serverResponse{http.StatusOK, api.Endpoints{}})
214
+	client := client.NewOrDie(&client.Config{Host: testServer.URL, Version: testapi.Version()})
214 215
 	serviceRegistry := registrytest.ServiceRegistry{
215 216
 		List: api.ServiceList{
216 217
 			Items: []api.Service{
... ...
@@ -123,6 +123,7 @@ func (h *EtcdHelper) listEtcdNode(key string) ([]*etcd.Node, uint64, error) {
123 123
 }
124 124
 
125 125
 // ExtractList extracts a go object per etcd node into a slice with the resource version.
126
+// DEPRECATED: Use ExtractToList instead, it's more convenient.
126 127
 func (h *EtcdHelper) ExtractList(key string, slicePtr interface{}, resourceVersion *uint64) error {
127 128
 	nodes, index, err := h.listEtcdNode(key)
128 129
 	if resourceVersion != nil {
... ...
@@ -152,6 +153,27 @@ func (h *EtcdHelper) ExtractList(key string, slicePtr interface{}, resourceVersi
152 152
 	return nil
153 153
 }
154 154
 
155
+// ExtractToList is just like ExtractList, but it works on a ThingyList api object.
156
+// extracts a go object per etcd node into a slice with the resource version.
157
+func (h *EtcdHelper) ExtractToList(key string, listObj runtime.Object) error {
158
+	var resourceVersion uint64
159
+	listPtr, err := runtime.GetItemsPtr(listObj)
160
+	if err != nil {
161
+		return err
162
+	}
163
+	err = h.ExtractList(key, listPtr, &resourceVersion)
164
+	if err != nil {
165
+		return err
166
+	}
167
+	if h.ResourceVersioner != nil {
168
+		err = h.ResourceVersioner.SetResourceVersion(listObj, resourceVersion)
169
+		if err != nil {
170
+			return err
171
+		}
172
+	}
173
+	return nil
174
+}
175
+
155 176
 // ExtractObj unmarshals json found at key into objPtr. On a not found error, will either return
156 177
 // a zero object of the requested type, or an error, depending on ignoreNotFound. Treats
157 178
 // empty responses and nil response nodes exactly like a not found error.
... ...
@@ -185,8 +207,9 @@ func (h *EtcdHelper) bodyAndExtractObj(key string, objPtr runtime.Object, ignore
185 185
 	return body, response.Node.ModifiedIndex, err
186 186
 }
187 187
 
188
-// CreateObj adds a new object at a key unless it already exists.
189
-func (h *EtcdHelper) CreateObj(key string, obj runtime.Object) error {
188
+// CreateObj adds a new object at a key unless it already exists. 'ttl' is time-to-live in seconds,
189
+// and 0 means forever.
190
+func (h *EtcdHelper) CreateObj(key string, obj runtime.Object, ttl uint64) error {
190 191
 	data, err := h.Codec.Encode(obj)
191 192
 	if err != nil {
192 193
 		return err
... ...
@@ -197,7 +220,7 @@ func (h *EtcdHelper) CreateObj(key string, obj runtime.Object) error {
197 197
 		}
198 198
 	}
199 199
 
200
-	_, err = h.Client.Create(key, string(data), 0)
200
+	_, err = h.Client.Create(key, string(data), ttl)
201 201
 	return err
202 202
 }
203 203
 
... ...
@@ -65,7 +65,7 @@ func TestIsEtcdNotFound(t *testing.T) {
65 65
 	try(fmt.Errorf("some other kind of error"), false)
66 66
 }
67 67
 
68
-func TestExtractList(t *testing.T) {
68
+func TestExtractToList(t *testing.T) {
69 69
 	fakeClient := NewFakeEtcdClient(t)
70 70
 	fakeClient.Data["/some/key"] = EtcdResponseWithError{
71 71
 		R: &etcd.Response{
... ...
@@ -88,27 +88,23 @@ func TestExtractList(t *testing.T) {
88 88
 			},
89 89
 		},
90 90
 	}
91
-	expect := []api.Pod{
92
-		{JSONBase: api.JSONBase{ID: "foo", ResourceVersion: 1}},
93
-		{JSONBase: api.JSONBase{ID: "bar", ResourceVersion: 2}},
94
-		{JSONBase: api.JSONBase{ID: "baz", ResourceVersion: 3}},
91
+	expect := api.PodList{
92
+		JSONBase: api.JSONBase{ResourceVersion: 10},
93
+		Items: []api.Pod{
94
+			{JSONBase: api.JSONBase{ID: "foo", ResourceVersion: 1}},
95
+			{JSONBase: api.JSONBase{ID: "bar", ResourceVersion: 2}},
96
+			{JSONBase: api.JSONBase{ID: "baz", ResourceVersion: 3}},
97
+		},
95 98
 	}
96 99
 
97
-	var got []api.Pod
100
+	var got api.PodList
98 101
 	helper := EtcdHelper{fakeClient, latest.Codec, versioner}
99
-	resourceVersion := uint64(0)
100
-	err := helper.ExtractList("/some/key", &got, &resourceVersion)
102
+	err := helper.ExtractToList("/some/key", &got)
101 103
 	if err != nil {
102
-		t.Errorf("Unexpected error %#v", err)
103
-	}
104
-	if resourceVersion != 10 {
105
-		t.Errorf("Unexpected resource version %d", resourceVersion)
104
+		t.Errorf("Unexpected error %v", err)
106 105
 	}
107
-
108
-	for i := 0; i < len(expect); i++ {
109
-		if !reflect.DeepEqual(got[i], expect[i]) {
110
-			t.Errorf("\nWanted:\n%#v\nGot:\n%#v\n", expect[i], got[i])
111
-		}
106
+	if e, a := expect, got; !reflect.DeepEqual(e, a) {
107
+		t.Errorf("Expected %#v, got %#v", e, a)
112 108
 	}
113 109
 }
114 110
 
... ...
@@ -167,6 +163,27 @@ func TestExtractObjNotFoundErr(t *testing.T) {
167 167
 	try("/some/key3")
168 168
 }
169 169
 
170
+func TestCreateObj(t *testing.T) {
171
+	obj := &api.Pod{JSONBase: api.JSONBase{ID: "foo"}}
172
+	fakeClient := NewFakeEtcdClient(t)
173
+	helper := EtcdHelper{fakeClient, latest.Codec, versioner}
174
+	err := helper.CreateObj("/some/key", obj, 5)
175
+	if err != nil {
176
+		t.Errorf("Unexpected error %#v", err)
177
+	}
178
+	data, err := latest.Codec.Encode(obj)
179
+	if err != nil {
180
+		t.Errorf("Unexpected error %#v", err)
181
+	}
182
+	node := fakeClient.Data["/some/key"].R.Node
183
+	if e, a := string(data), node.Value; e != a {
184
+		t.Errorf("Wanted %v, got %v", e, a)
185
+	}
186
+	if e, a := uint64(5), fakeClient.LastSetTTL; e != a {
187
+		t.Errorf("Wanted %v, got %v", e, a)
188
+	}
189
+}
190
+
170 191
 func TestSetObj(t *testing.T) {
171 192
 	obj := &api.Pod{JSONBase: api.JSONBase{ID: "foo"}}
172 193
 	fakeClient := NewFakeEtcdClient(t)
... ...
@@ -20,6 +20,7 @@ import (
20 20
 	"sync"
21 21
 	"time"
22 22
 
23
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
23 24
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
24 25
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
25 26
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
... ...
@@ -48,7 +49,8 @@ func (h *EtcdHelper) WatchList(key string, resourceVersion uint64, filter Filter
48 48
 
49 49
 // Watch begins watching the specified key. Events are decoded into
50 50
 // API objects and sent down the returned watch.Interface.
51
-func (h *EtcdHelper) Watch(key string, resourceVersion uint64) (watch.Interface, error) {
51
+// Errors will be sent down the channel.
52
+func (h *EtcdHelper) Watch(key string, resourceVersion uint64) watch.Interface {
52 53
 	return h.WatchAndTransform(key, resourceVersion, nil)
53 54
 }
54 55
 
... ...
@@ -67,10 +69,11 @@ func (h *EtcdHelper) Watch(key string, resourceVersion uint64) (watch.Interface,
67 67
 //     return value, nil
68 68
 //   })
69 69
 //
70
-func (h *EtcdHelper) WatchAndTransform(key string, resourceVersion uint64, transform TransformFunc) (watch.Interface, error) {
70
+// Errors will be sent down the channel.
71
+func (h *EtcdHelper) WatchAndTransform(key string, resourceVersion uint64, transform TransformFunc) watch.Interface {
71 72
 	w := newEtcdWatcher(false, Everything, h.Codec, h.ResourceVersioner, transform)
72 73
 	go w.etcdWatch(h.Client, key, resourceVersion)
73
-	return w, <-w.immediateError
74
+	return w
74 75
 }
75 76
 
76 77
 // TransformFunc attempts to convert an object to another object for use with a watcher.
... ...
@@ -86,14 +89,10 @@ type etcdWatcher struct {
86 86
 	filter FilterFunc
87 87
 
88 88
 	etcdIncoming  chan *etcd.Response
89
+	etcdError     chan error
89 90
 	etcdStop      chan bool
90 91
 	etcdCallEnded chan struct{}
91 92
 
92
-	// etcdWatch will send an error down this channel if the Watch fails.
93
-	// Otherwise, a nil will be sent down this channel watchWaitDuration
94
-	// after the watch starts.
95
-	immediateError chan error
96
-
97 93
 	outgoing chan watch.Event
98 94
 	userStop chan struct{}
99 95
 	stopped  bool
... ...
@@ -110,17 +109,16 @@ const watchWaitDuration = 100 * time.Millisecond
110 110
 // and a versioner, the versioner must be able to handle the objects that transform creates.
111 111
 func newEtcdWatcher(list bool, filter FilterFunc, encoding runtime.Codec, versioner runtime.ResourceVersioner, transform TransformFunc) *etcdWatcher {
112 112
 	w := &etcdWatcher{
113
-		encoding:       encoding,
114
-		versioner:      versioner,
115
-		transform:      transform,
116
-		list:           list,
117
-		filter:         filter,
118
-		etcdIncoming:   make(chan *etcd.Response),
119
-		etcdStop:       make(chan bool),
120
-		etcdCallEnded:  make(chan struct{}),
121
-		immediateError: make(chan error),
122
-		outgoing:       make(chan watch.Event),
123
-		userStop:       make(chan struct{}),
113
+		encoding:     encoding,
114
+		versioner:    versioner,
115
+		transform:    transform,
116
+		list:         list,
117
+		filter:       filter,
118
+		etcdIncoming: make(chan *etcd.Response),
119
+		etcdError:    make(chan error, 1),
120
+		etcdStop:     make(chan bool),
121
+		outgoing:     make(chan watch.Event),
122
+		userStop:     make(chan struct{}),
124 123
 	}
125 124
 	w.emit = func(e watch.Event) { w.outgoing <- e }
126 125
 	go w.translate()
... ...
@@ -128,46 +126,36 @@ func newEtcdWatcher(list bool, filter FilterFunc, encoding runtime.Codec, versio
128 128
 }
129 129
 
130 130
 // etcdWatch calls etcd's Watch function, and handles any errors. Meant to be called
131
-// as a goroutine. Will either send an error over w.immediateError if Watch fails, or in 100ms will
131
+// as a goroutine.
132 132
 func (w *etcdWatcher) etcdWatch(client EtcdGetSet, key string, resourceVersion uint64) {
133 133
 	defer util.HandleCrash()
134
-	defer close(w.etcdCallEnded)
135
-	go func() {
136
-		// This is racy; assume that Watch will fail within 100ms if it is going to fail.
137
-		// It's still more useful than blocking until the first result shows up.
138
-		// Trying to detect the 401: watch window expired error.
139
-		<-time.After(watchWaitDuration)
140
-		w.immediateError <- nil
141
-	}()
134
+	defer close(w.etcdError)
142 135
 	if resourceVersion == 0 {
143
-		latest, ok := etcdGetInitialWatchState(client, key, w.list, w.etcdIncoming)
144
-		if !ok {
136
+		latest, err := etcdGetInitialWatchState(client, key, w.list, w.etcdIncoming)
137
+		if err != nil {
138
+			w.etcdError <- err
145 139
 			return
146 140
 		}
147 141
 		resourceVersion = latest + 1
148 142
 	}
149 143
 	_, err := client.Watch(key, resourceVersion, w.list, w.etcdIncoming, w.etcdStop)
150
-	if err != etcd.ErrWatchStoppedByUser {
151
-		glog.Errorf("etcd.Watch stopped unexpectedly: %v (%#v)", err, key)
152
-		w.immediateError <- err
144
+	if err != nil && err != etcd.ErrWatchStoppedByUser {
145
+		w.etcdError <- err
153 146
 	}
154 147
 }
155 148
 
156 149
 // etcdGetInitialWatchState turns an etcd Get request into a watch equivalent
157
-func etcdGetInitialWatchState(client EtcdGetSet, key string, recursive bool, incoming chan<- *etcd.Response) (resourceVersion uint64, success bool) {
158
-	success = true
159
-
150
+func etcdGetInitialWatchState(client EtcdGetSet, key string, recursive bool, incoming chan<- *etcd.Response) (resourceVersion uint64, err error) {
160 151
 	resp, err := client.Get(key, false, recursive)
161 152
 	if err != nil {
162 153
 		if !IsEtcdNotFound(err) {
163 154
 			glog.Errorf("watch was unable to retrieve the current index for the provided key: %v (%#v)", err, key)
164
-			success = false
165
-			return
155
+			return resourceVersion, err
166 156
 		}
167 157
 		if index, ok := etcdErrorIndex(err); ok {
168 158
 			resourceVersion = index
169 159
 		}
170
-		return
160
+		return resourceVersion, nil
171 161
 	}
172 162
 	resourceVersion = resp.EtcdIndex
173 163
 	convertRecursiveResponse(resp.Node, resp, incoming)
... ...
@@ -189,7 +177,7 @@ func convertRecursiveResponse(node *etcd.Node, response *etcd.Response, incoming
189 189
 	incoming <- &copied
190 190
 }
191 191
 
192
-// translate pulls stuff from etcd, convert, and push out the outgoing channel. Meant to be
192
+// translate pulls stuff from etcd, converts, and pushes out the outgoing channel. Meant to be
193 193
 // called as a goroutine.
194 194
 func (w *etcdWatcher) translate() {
195 195
 	defer close(w.outgoing)
... ...
@@ -197,16 +185,26 @@ func (w *etcdWatcher) translate() {
197 197
 
198 198
 	for {
199 199
 		select {
200
-		case <-w.etcdCallEnded:
200
+		case err := <-w.etcdError:
201
+			if err != nil {
202
+				w.emit(watch.Event{
203
+					watch.Error,
204
+					&api.Status{
205
+						Status:  api.StatusFailure,
206
+						Message: err.Error(),
207
+					},
208
+				})
209
+			}
201 210
 			return
202 211
 		case <-w.userStop:
203 212
 			w.etcdStop <- true
204 213
 			return
205 214
 		case res, ok := <-w.etcdIncoming:
206
-			if !ok {
207
-				return
215
+			if ok {
216
+				w.sendResult(res)
208 217
 			}
209
-			w.sendResult(res)
218
+			// If !ok, don't return here-- must wait for etcdError channel
219
+			// to give an error or be closed.
210 220
 		}
211 221
 	}
212 222
 }
... ...
@@ -205,10 +205,20 @@ func TestWatchEtcdError(t *testing.T) {
205 205
 	fakeClient.WatchImmediateError = fmt.Errorf("immediate error")
206 206
 	h := EtcdHelper{fakeClient, codec, versioner}
207 207
 
208
-	_, err := h.Watch("/some/key", 0)
209
-	if err == nil {
208
+	got := <-h.Watch("/some/key", 4).ResultChan()
209
+	if got.Type != watch.Error {
210 210
 		t.Fatalf("Unexpected non-error")
211 211
 	}
212
+	status, ok := got.Object.(*api.Status)
213
+	if !ok {
214
+		t.Fatalf("Unexpected non-error object type")
215
+	}
216
+	if status.Message != "immediate error" {
217
+		t.Errorf("Unexpected wrong error")
218
+	}
219
+	if status.Status != api.StatusFailure {
220
+		t.Errorf("Unexpected wrong error status")
221
+	}
212 222
 }
213 223
 
214 224
 func TestWatch(t *testing.T) {
... ...
@@ -217,10 +227,7 @@ func TestWatch(t *testing.T) {
217 217
 	fakeClient.expectNotFoundGetSet["/some/key"] = struct{}{}
218 218
 	h := EtcdHelper{fakeClient, codec, versioner}
219 219
 
220
-	watching, err := h.Watch("/some/key", 0)
221
-	if err != nil {
222
-		t.Fatalf("Unexpected error: %v", err)
223
-	}
220
+	watching := h.Watch("/some/key", 0)
224 221
 
225 222
 	fakeClient.WaitForWatchCompletion()
226 223
 	// when server returns not found, the watch index starts at the next value (1)
... ...
@@ -249,6 +256,17 @@ func TestWatch(t *testing.T) {
249 249
 	// Test error case
250 250
 	fakeClient.WatchInjectError <- fmt.Errorf("Injected error")
251 251
 
252
+	if errEvent, ok := <-watching.ResultChan(); !ok {
253
+		t.Errorf("no error result?")
254
+	} else {
255
+		if e, a := watch.Error, errEvent.Type; e != a {
256
+			t.Errorf("Expected %v, got %v", e, a)
257
+		}
258
+		if e, a := "Injected error", errEvent.Object.(*api.Status).Message; e != a {
259
+			t.Errorf("Expected %v, got %v", e, a)
260
+		}
261
+	}
262
+
252 263
 	// Did everything shut down?
253 264
 	if _, open := <-fakeClient.WatchResponse; open {
254 265
 		t.Errorf("An injected error did not cause a graceful shutdown")
... ...
@@ -349,11 +367,7 @@ func TestWatchEtcdState(t *testing.T) {
349 349
 			fakeClient.Data[key] = value
350 350
 		}
351 351
 		h := EtcdHelper{fakeClient, codec, versioner}
352
-		watching, err := h.Watch("/somekey/foo", testCase.From)
353
-		if err != nil {
354
-			t.Errorf("%s: unexpected error: %v", k, err)
355
-			continue
356
-		}
352
+		watching := h.Watch("/somekey/foo", testCase.From)
357 353
 		fakeClient.WaitForWatchCompletion()
358 354
 
359 355
 		t.Logf("Testing %v", k)
... ...
@@ -421,10 +435,7 @@ func TestWatchFromZeroIndex(t *testing.T) {
421 421
 		fakeClient.Data["/some/key"] = testCase.Response
422 422
 		h := EtcdHelper{fakeClient, codec, versioner}
423 423
 
424
-		watching, err := h.Watch("/some/key", 0)
425
-		if err != nil {
426
-			t.Fatalf("%s: unexpected error: %v", k, err)
427
-		}
424
+		watching := h.Watch("/some/key", 0)
428 425
 
429 426
 		fakeClient.WaitForWatchCompletion()
430 427
 		if e, a := testCase.Response.R.EtcdIndex+1, fakeClient.WatchIndex; e != a {
... ...
@@ -525,10 +536,7 @@ func TestWatchFromNotFound(t *testing.T) {
525 525
 	}
526 526
 	h := EtcdHelper{fakeClient, codec, versioner}
527 527
 
528
-	watching, err := h.Watch("/some/key", 0)
529
-	if err != nil {
530
-		t.Fatalf("Unexpected error: %v", err)
531
-	}
528
+	watching := h.Watch("/some/key", 0)
532 529
 
533 530
 	fakeClient.WaitForWatchCompletion()
534 531
 	if fakeClient.WatchIndex != 3 {
... ...
@@ -551,9 +559,14 @@ func TestWatchFromOtherError(t *testing.T) {
551 551
 	}
552 552
 	h := EtcdHelper{fakeClient, codec, versioner}
553 553
 
554
-	watching, err := h.Watch("/some/key", 0)
555
-	if err != nil {
556
-		t.Fatalf("Unexpected error: %v", err)
554
+	watching := h.Watch("/some/key", 0)
555
+
556
+	errEvent := <-watching.ResultChan()
557
+	if e, a := watch.Error, errEvent.Type; e != a {
558
+		t.Errorf("Expected %v, got %v", e, a)
559
+	}
560
+	if e, a := "101:  () [2]", errEvent.Object.(*api.Status).Message; e != a {
561
+		t.Errorf("Expected %v, got %v", e, a)
557 562
 	}
558 563
 
559 564
 	select {
... ...
@@ -576,10 +589,7 @@ func TestWatchPurposefulShutdown(t *testing.T) {
576 576
 	fakeClient.expectNotFoundGetSet["/some/key"] = struct{}{}
577 577
 
578 578
 	// Test purposeful shutdown
579
-	watching, err := h.Watch("/some/key", 0)
580
-	if err != nil {
581
-		t.Fatalf("Unexpected error: %v", err)
582
-	}
579
+	watching := h.Watch("/some/key", 0)
583 580
 	fakeClient.WaitForWatchCompletion()
584 581
 	watching.Stop()
585 582
 
... ...
@@ -49,6 +49,7 @@ type FakeEtcdClient struct {
49 49
 	Ix          int
50 50
 	TestIndex   bool
51 51
 	ChangeIndex uint64
52
+	LastSetTTL  uint64
52 53
 
53 54
 	// Will become valid after Watch is called; tester may write to it. Tester may
54 55
 	// also read from it to verify that it's closed after injecting an error.
... ...
@@ -135,6 +136,7 @@ func (f *FakeEtcdClient) nodeExists(key string) bool {
135 135
 }
136 136
 
137 137
 func (f *FakeEtcdClient) setLocked(key, value string, ttl uint64) (*etcd.Response, error) {
138
+	f.LastSetTTL = ttl
138 139
 	if f.Err != nil {
139 140
 		return nil, f.Err
140 141
 	}
... ...
@@ -65,6 +65,10 @@ func (f FakeHandler) ValidateRequest(t TestInterface, expectedPath, expectedMeth
65 65
 	if err != nil {
66 66
 		t.Errorf("Couldn't parse %v as a URL.", expectedPath)
67 67
 	}
68
+	if f.RequestReceived == nil {
69
+		t.Errorf("Unexpected nil request received for %s", expectedPath)
70
+		return
71
+	}
68 72
 	if f.RequestReceived.URL.Path != expectURL.Path {
69 73
 		t.Errorf("Unexpected request path for request %#v, received: %q, expected: %q", f.RequestReceived, f.RequestReceived.URL.Path, expectURL.Path)
70 74
 	}
71 75
new file mode 100644
... ...
@@ -0,0 +1,104 @@
0
+/*
1
+Copyright 2014 Google Inc. All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package util
17
+
18
+import (
19
+	"sync"
20
+	"time"
21
+)
22
+
23
+type RateLimiter interface {
24
+	// CanAccept returns true if the rate is below the limit, false otherwise
25
+	CanAccept() bool
26
+	// Stop stops the rate limiter, subsequent calls to CanAccept will return false
27
+	Stop()
28
+}
29
+
30
+type tickRateLimiter struct {
31
+	lock   sync.Mutex
32
+	tokens chan bool
33
+	ticker <-chan time.Time
34
+	stop   chan bool
35
+}
36
+
37
+// NewTokenBucketRateLimiter creates a rate limiter which implements a token bucket approach.
38
+// The rate limiter allows bursts of up to 'burst' to exceed the QPS, while still maintaining a
39
+// smoothed qps rate of 'qps'.
40
+// The bucket is initially filled with 'burst' tokens, the rate limiter spawns a go routine
41
+// which refills the bucket with one token at a rate of 'qps'.  The maximum number of tokens in
42
+// the bucket is capped at 'burst'.
43
+// When done with the limiter, Stop() must be called to halt the associated goroutine.
44
+func NewTokenBucketRateLimiter(qps float32, burst int) RateLimiter {
45
+	ticker := time.Tick(time.Duration(float32(time.Second) / qps))
46
+	rate := newTokenBucketRateLimiterFromTicker(ticker, burst)
47
+	go rate.run()
48
+	return rate
49
+}
50
+
51
+func newTokenBucketRateLimiterFromTicker(ticker <-chan time.Time, burst int) *tickRateLimiter {
52
+	if burst < 1 {
53
+		panic("burst must be a positive integer")
54
+	}
55
+	rate := &tickRateLimiter{
56
+		tokens: make(chan bool, burst),
57
+		ticker: ticker,
58
+		stop:   make(chan bool),
59
+	}
60
+	for i := 0; i < burst; i++ {
61
+		rate.tokens <- true
62
+	}
63
+	return rate
64
+}
65
+
66
+func (t *tickRateLimiter) CanAccept() bool {
67
+	select {
68
+	case <-t.tokens:
69
+		return true
70
+	default:
71
+		return false
72
+	}
73
+}
74
+
75
+func (t *tickRateLimiter) Stop() {
76
+	close(t.stop)
77
+}
78
+
79
+func (r *tickRateLimiter) run() {
80
+	for {
81
+		if !r.step() {
82
+			break
83
+		}
84
+	}
85
+}
86
+
87
+func (r *tickRateLimiter) step() bool {
88
+	select {
89
+	case <-r.ticker:
90
+		r.increment()
91
+		return true
92
+	case <-r.stop:
93
+		return false
94
+	}
95
+}
96
+
97
+func (t *tickRateLimiter) increment() {
98
+	// non-blocking send
99
+	select {
100
+	case t.tokens <- true:
101
+	default:
102
+	}
103
+}
0 104
new file mode 100644
... ...
@@ -0,0 +1,62 @@
0
+/*
1
+Copyright 2014 Google Inc. All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package util
17
+
18
+import (
19
+	"testing"
20
+	"time"
21
+)
22
+
23
+func TestBasicThrottle(t *testing.T) {
24
+	ticker := make(chan time.Time, 1)
25
+	r := newTokenBucketRateLimiterFromTicker(ticker, 3)
26
+	for i := 0; i < 3; i++ {
27
+		if !r.CanAccept() {
28
+			t.Error("unexpected false accept")
29
+		}
30
+	}
31
+	if r.CanAccept() {
32
+		t.Error("unexpected true accept")
33
+	}
34
+}
35
+
36
+func TestIncrementThrottle(t *testing.T) {
37
+	ticker := make(chan time.Time, 1)
38
+	r := newTokenBucketRateLimiterFromTicker(ticker, 1)
39
+	if !r.CanAccept() {
40
+		t.Error("unexpected false accept")
41
+	}
42
+	if r.CanAccept() {
43
+		t.Error("unexpected true accept")
44
+	}
45
+	ticker <- time.Now()
46
+	r.step()
47
+
48
+	if !r.CanAccept() {
49
+		t.Error("unexpected false accept")
50
+	}
51
+}
52
+
53
+func TestOverBurst(t *testing.T) {
54
+	ticker := make(chan time.Time, 1)
55
+	r := newTokenBucketRateLimiterFromTicker(ticker, 3)
56
+
57
+	for i := 0; i < 4; i++ {
58
+		ticker <- time.Now()
59
+		r.step()
60
+	}
61
+}
... ...
@@ -49,32 +49,32 @@ type Cleaner interface {
49 49
 	TearDown() error
50 50
 }
51 51
 
52
-// HostDirectory volumes represent a bare host directory mount.
52
+// HostDir volumes represent a bare host directory mount.
53 53
 // The directory in Path will be directly exposed to the container.
54
-type HostDirectory struct {
54
+type HostDir struct {
55 55
 	Path string
56 56
 }
57 57
 
58 58
 // SetUp implements interface definitions, even though host directory
59 59
 // mounts don't require any setup or cleanup.
60
-func (hostVol *HostDirectory) SetUp() error {
60
+func (hostVol *HostDir) SetUp() error {
61 61
 	return nil
62 62
 }
63 63
 
64
-func (hostVol *HostDirectory) GetPath() string {
64
+func (hostVol *HostDir) GetPath() string {
65 65
 	return hostVol.Path
66 66
 }
67 67
 
68
-// EmptyDirectory volumes are temporary directories exposed to the pod.
68
+// EmptyDir volumes are temporary directories exposed to the pod.
69 69
 // These do not persist beyond the lifetime of a pod.
70
-type EmptyDirectory struct {
70
+type EmptyDir struct {
71 71
 	Name    string
72 72
 	PodID   string
73 73
 	RootDir string
74 74
 }
75 75
 
76 76
 // SetUp creates new directory.
77
-func (emptyDir *EmptyDirectory) SetUp() error {
77
+func (emptyDir *EmptyDir) SetUp() error {
78 78
 	path := emptyDir.GetPath()
79 79
 	err := os.MkdirAll(path, 0750)
80 80
 	if err != nil {
... ...
@@ -83,11 +83,11 @@ func (emptyDir *EmptyDirectory) SetUp() error {
83 83
 	return nil
84 84
 }
85 85
 
86
-func (emptyDir *EmptyDirectory) GetPath() string {
86
+func (emptyDir *EmptyDir) GetPath() string {
87 87
 	return path.Join(emptyDir.RootDir, emptyDir.PodID, "volumes", "empty", emptyDir.Name)
88 88
 }
89 89
 
90
-func (emptyDir *EmptyDirectory) renameDirectory() (string, error) {
90
+func (emptyDir *EmptyDir) renameDirectory() (string, error) {
91 91
 	oldPath := emptyDir.GetPath()
92 92
 	newPath, err := ioutil.TempDir(path.Dir(oldPath), emptyDir.Name+".deleting~")
93 93
 	if err != nil {
... ...
@@ -101,7 +101,7 @@ func (emptyDir *EmptyDirectory) renameDirectory() (string, error) {
101 101
 }
102 102
 
103 103
 // TearDown simply deletes everything in the directory.
104
-func (emptyDir *EmptyDirectory) TearDown() error {
104
+func (emptyDir *EmptyDir) TearDown() error {
105 105
 	tmpDir, err := emptyDir.renameDirectory()
106 106
 	if err != nil {
107 107
 		return err
... ...
@@ -113,14 +113,14 @@ func (emptyDir *EmptyDirectory) TearDown() error {
113 113
 	return nil
114 114
 }
115 115
 
116
-// createHostDirectory interprets API volume as a HostDirectory.
117
-func createHostDirectory(volume *api.Volume) *HostDirectory {
118
-	return &HostDirectory{volume.Source.HostDirectory.Path}
116
+// createHostDir interprets API volume as a HostDir.
117
+func createHostDir(volume *api.Volume) *HostDir {
118
+	return &HostDir{volume.Source.HostDir.Path}
119 119
 }
120 120
 
121
-// createEmptyDirectory interprets API volume as an EmptyDirectory.
122
-func createEmptyDirectory(volume *api.Volume, podID string, rootDir string) *EmptyDirectory {
123
-	return &EmptyDirectory{volume.Name, podID, rootDir}
121
+// createEmptyDir interprets API volume as an EmptyDir.
122
+func createEmptyDir(volume *api.Volume, podID string, rootDir string) *EmptyDir {
123
+	return &EmptyDir{volume.Name, podID, rootDir}
124 124
 }
125 125
 
126 126
 // CreateVolumeBuilder returns a Builder capable of mounting a volume described by an
... ...
@@ -135,10 +135,10 @@ func CreateVolumeBuilder(volume *api.Volume, podID string, rootDir string) (Buil
135 135
 	var vol Builder
136 136
 	// TODO(jonesdl) We should probably not check every pointer and directly
137 137
 	// resolve these types instead.
138
-	if source.HostDirectory != nil {
139
-		vol = createHostDirectory(volume)
140
-	} else if source.EmptyDirectory != nil {
141
-		vol = createEmptyDirectory(volume, podID, rootDir)
138
+	if source.HostDir != nil {
139
+		vol = createHostDir(volume)
140
+	} else if source.EmptyDir != nil {
141
+		vol = createEmptyDir(volume, podID, rootDir)
142 142
 	} else {
143 143
 		return nil, ErrUnsupportedVolumeType
144 144
 	}
... ...
@@ -149,7 +149,7 @@ func CreateVolumeBuilder(volume *api.Volume, podID string, rootDir string) (Buil
149 149
 func CreateVolumeCleaner(kind string, name string, podID string, rootDir string) (Cleaner, error) {
150 150
 	switch kind {
151 151
 	case "empty":
152
-		return &EmptyDirectory{name, podID, rootDir}, nil
152
+		return &EmptyDir{name, podID, rootDir}, nil
153 153
 	default:
154 154
 		return nil, ErrUnsupportedVolumeType
155 155
 	}
... ...
@@ -41,7 +41,7 @@ func TestCreateVolumeBuilders(t *testing.T) {
41 41
 			api.Volume{
42 42
 				Name: "host-dir",
43 43
 				Source: &api.VolumeSource{
44
-					HostDirectory: &api.HostDirectory{"/dir/path"},
44
+					HostDir: &api.HostDir{"/dir/path"},
45 45
 				},
46 46
 			},
47 47
 			"/dir/path",
... ...
@@ -52,7 +52,7 @@ func TestCreateVolumeBuilders(t *testing.T) {
52 52
 			api.Volume{
53 53
 				Name: "empty-dir",
54 54
 				Source: &api.VolumeSource{
55
-					EmptyDirectory: &api.EmptyDirectory{},
55
+					EmptyDir: &api.EmptyDir{},
56 56
 				},
57 57
 			},
58 58
 			path.Join(tempDir, "/my-id/volumes/empty/empty-dir"),
... ...
@@ -79,7 +79,7 @@ func TestCreateVolumeBuilders(t *testing.T) {
79 79
 			}
80 80
 			continue
81 81
 		}
82
-		if tt.volume.Source.HostDirectory == nil && tt.volume.Source.EmptyDirectory == nil {
82
+		if tt.volume.Source.HostDir == nil && tt.volume.Source.EmptyDir == nil {
83 83
 			if err != ErrUnsupportedVolumeType {
84 84
 				t.Errorf("Unexpected error: %v", err)
85 85
 			}
... ...
@@ -33,6 +33,7 @@ type watchEvent struct {
33 33
 
34 34
 	// For added or modified objects, this is the new object; for deleted objects,
35 35
 	// it's the state of the object immediately prior to its deletion.
36
+	// For errors, it's an api.Status.
36 37
 	Object runtime.RawExtension `json:"object,omitempty" yaml:"object,omitempty"`
37 38
 }
38 39
 
... ...
@@ -41,14 +41,18 @@ const (
41 41
 	Added    EventType = "ADDED"
42 42
 	Modified EventType = "MODIFIED"
43 43
 	Deleted  EventType = "DELETED"
44
+	Error    EventType = "ERROR"
44 45
 )
45 46
 
46 47
 // Event represents a single event to a watched resource.
47 48
 type Event struct {
48 49
 	Type EventType
49 50
 
50
-	// If Type == Deleted, then this is the state of the object
51
-	// immediately before deletion.
51
+	// Object is:
52
+	//  * If Type is Added or Modified: the new state of the object.
53
+	//  * If Type is Deleted: the state of the object immediately before deletion.
54
+	//  * If Type is Error: *api.Status is recommended; other types may make sense
55
+	//    depending on context.
52 56
 	Object runtime.Object
53 57
 }
54 58
 
... ...
@@ -94,6 +98,11 @@ func (f *FakeWatcher) Delete(lastValue runtime.Object) {
94 94
 	f.result <- Event{Deleted, lastValue}
95 95
 }
96 96
 
97
+// Error sends an Error event.
98
+func (f *FakeWatcher) Error(errValue runtime.Object) {
99
+	f.result <- Event{Error, errValue}
100
+}
101
+
97 102
 // Action sends an event of the requested type, for table-based testing.
98 103
 func (f *FakeWatcher) Action(action EventType, obj runtime.Object) {
99 104
 	f.result <- Event{action, obj}
... ...
@@ -35,6 +35,7 @@ func TestFake(t *testing.T) {
35 35
 		{Modified, testType("qux")},
36 36
 		{Modified, testType("bar")},
37 37
 		{Deleted, testType("bar")},
38
+		{Error, testType("error: blah")},
38 39
 	}
39 40
 
40 41
 	// Prove that f implements Interface by phrasing this as a function.
... ...
@@ -62,6 +63,7 @@ func TestFake(t *testing.T) {
62 62
 		f.Action(Modified, testType("qux"))
63 63
 		f.Modify(testType("bar"))
64 64
 		f.Delete(testType("bar"))
65
+		f.Error(testType("error: blah"))
65 66
 		f.Stop()
66 67
 	}
67 68
 
... ...
@@ -62,7 +62,11 @@ func (factory *ConfigFactory) Create() *scheduler.Config {
62 62
 	}
63 63
 
64 64
 	r := rand.New(rand.NewSource(time.Now().UnixNano()))
65
-	algo := algorithm.NewRandomFitScheduler(
65
+	algo := algorithm.NewGenericScheduler(
66
+		// Fit is defined based on the absence of port conflicts.
67
+		[]algorithm.FitPredicate{algorithm.PodFitsPorts},
68
+		// All nodes where things fit are equally likely (Random)
69
+		algorithm.EqualPriority,
66 70
 		&storeToPodLister{podCache}, r)
67 71
 
68 72
 	return &scheduler.Config{
... ...
@@ -71,9 +75,7 @@ func (factory *ConfigFactory) Create() *scheduler.Config {
71 71
 		Binder:       &binder{factory.Client},
72 72
 		NextPod: func() *api.Pod {
73 73
 			pod := podQueue.Pop().(*api.Pod)
74
-			// TODO: Remove or reduce verbosity by sep 6th, 2014. Leave until then to
75
-			// make it easy to find scheduling problems.
76
-			glog.Infof("About to try and schedule pod %v\n"+
74
+			glog.V(2).Infof("About to try and schedule pod %v\n"+
77 75
 				"\tknown minions: %v\n"+
78 76
 				"\tknown scheduled pods: %v\n",
79 77
 				pod.ID, minionCache.Contains(), podCache.Contains())
... ...
@@ -229,8 +231,6 @@ type binder struct {
229 229
 
230 230
 // Bind just does a POST binding RPC.
231 231
 func (b *binder) Bind(binding *api.Binding) error {
232
-	// TODO: Remove or reduce verbosity by sep 6th, 2014. Leave until then to
233
-	// make it easy to find scheduling problems.
234
-	glog.Infof("Attempting to bind %v to %v", binding.PodID, binding.Host)
232
+	glog.V(2).Infof("Attempting to bind %v to %v", binding.PodID, binding.Host)
235 233
 	return b.Post().Path("bindings").Body(binding).Do().Error()
236 234
 }
... ...
@@ -25,6 +25,7 @@ import (
25 25
 
26 26
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
27 27
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
28
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
28 29
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
29 30
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache"
30 31
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
... ...
@@ -39,7 +40,7 @@ func TestCreate(t *testing.T) {
39 39
 		T:            t,
40 40
 	}
41 41
 	server := httptest.NewServer(&handler)
42
-	client := client.NewOrDie(server.URL, "", nil)
42
+	client := client.NewOrDie(&client.Config{Host: server.URL, Version: testapi.Version()})
43 43
 	factory := ConfigFactory{client}
44 44
 	factory.Create()
45 45
 }
... ...
@@ -52,17 +53,17 @@ func TestCreateLists(t *testing.T) {
52 52
 	}{
53 53
 		// Minion
54 54
 		{
55
-			location: "/api/v1beta1/minions?fields=",
55
+			location: "/api/" + testapi.Version() + "/minions?fields=",
56 56
 			factory:  factory.createMinionLW,
57 57
 		},
58 58
 		// Assigned pod
59 59
 		{
60
-			location: "/api/v1beta1/pods?fields=DesiredState.Host!%3D",
60
+			location: "/api/" + testapi.Version() + "/pods?fields=DesiredState.Host!%3D",
61 61
 			factory:  factory.createAssignedPodLW,
62 62
 		},
63 63
 		// Unassigned pod
64 64
 		{
65
-			location: "/api/v1beta1/pods?fields=DesiredState.Host%3D",
65
+			location: "/api/" + testapi.Version() + "/pods?fields=DesiredState.Host%3D",
66 66
 			factory:  factory.createUnassignedPodLW,
67 67
 		},
68 68
 	}
... ...
@@ -74,7 +75,7 @@ func TestCreateLists(t *testing.T) {
74 74
 			T:            t,
75 75
 		}
76 76
 		server := httptest.NewServer(&handler)
77
-		factory.Client = client.NewOrDie(server.URL, latest.OldestVersion, nil)
77
+		factory.Client = client.NewOrDie(&client.Config{Host: server.URL, Version: testapi.Version()})
78 78
 		// This test merely tests that the correct request is made.
79 79
 		item.factory().List()
80 80
 		handler.ValidateRequest(t, item.location, "GET", nil)
... ...
@@ -91,31 +92,31 @@ func TestCreateWatches(t *testing.T) {
91 91
 		// Minion watch
92 92
 		{
93 93
 			rv:       0,
94
-			location: "/api/v1beta1/watch/minions?fields=&resourceVersion=0",
94
+			location: "/api/" + testapi.Version() + "/watch/minions?fields=&resourceVersion=0",
95 95
 			factory:  factory.createMinionLW,
96 96
 		}, {
97 97
 			rv:       42,
98
-			location: "/api/v1beta1/watch/minions?fields=&resourceVersion=42",
98
+			location: "/api/" + testapi.Version() + "/watch/minions?fields=&resourceVersion=42",
99 99
 			factory:  factory.createMinionLW,
100 100
 		},
101 101
 		// Assigned pod watches
102 102
 		{
103 103
 			rv:       0,
104
-			location: "/api/v1beta1/watch/pods?fields=DesiredState.Host!%3D&resourceVersion=0",
104
+			location: "/api/" + testapi.Version() + "/watch/pods?fields=DesiredState.Host!%3D&resourceVersion=0",
105 105
 			factory:  factory.createAssignedPodLW,
106 106
 		}, {
107 107
 			rv:       42,
108
-			location: "/api/v1beta1/watch/pods?fields=DesiredState.Host!%3D&resourceVersion=42",
108
+			location: "/api/" + testapi.Version() + "/watch/pods?fields=DesiredState.Host!%3D&resourceVersion=42",
109 109
 			factory:  factory.createAssignedPodLW,
110 110
 		},
111 111
 		// Unassigned pod watches
112 112
 		{
113 113
 			rv:       0,
114
-			location: "/api/v1beta1/watch/pods?fields=DesiredState.Host%3D&resourceVersion=0",
114
+			location: "/api/" + testapi.Version() + "/watch/pods?fields=DesiredState.Host%3D&resourceVersion=0",
115 115
 			factory:  factory.createUnassignedPodLW,
116 116
 		}, {
117 117
 			rv:       42,
118
-			location: "/api/v1beta1/watch/pods?fields=DesiredState.Host%3D&resourceVersion=42",
118
+			location: "/api/" + testapi.Version() + "/watch/pods?fields=DesiredState.Host%3D&resourceVersion=42",
119 119
 			factory:  factory.createUnassignedPodLW,
120 120
 		},
121 121
 	}
... ...
@@ -127,7 +128,7 @@ func TestCreateWatches(t *testing.T) {
127 127
 			T:            t,
128 128
 		}
129 129
 		server := httptest.NewServer(&handler)
130
-		factory.Client = client.NewOrDie(server.URL, "v1beta1", nil)
130
+		factory.Client = client.NewOrDie(&client.Config{Host: server.URL, Version: testapi.Version()})
131 131
 		// This test merely tests that the correct request is made.
132 132
 		item.factory().Watch(item.rv)
133 133
 		handler.ValidateRequest(t, item.location, "GET", nil)
... ...
@@ -155,9 +156,9 @@ func TestPollMinions(t *testing.T) {
155 155
 		}
156 156
 		mux := http.NewServeMux()
157 157
 		// FakeHandler musn't be sent requests other than the one you want to test.
158
-		mux.Handle("/api/v1beta1/minions", &handler)
158
+		mux.Handle("/api/"+testapi.Version()+"/minions", &handler)
159 159
 		server := httptest.NewServer(mux)
160
-		client := client.NewOrDie(server.URL, "v1beta1", nil)
160
+		client := client.NewOrDie(&client.Config{Host: server.URL, Version: testapi.Version()})
161 161
 		cf := ConfigFactory{client}
162 162
 
163 163
 		ce, err := cf.pollMinions()
... ...
@@ -165,7 +166,7 @@ func TestPollMinions(t *testing.T) {
165 165
 			t.Errorf("Unexpected error: %v", err)
166 166
 			continue
167 167
 		}
168
-		handler.ValidateRequest(t, "/api/v1beta1/minions", "GET", nil)
168
+		handler.ValidateRequest(t, "/api/"+testapi.Version()+"/minions", "GET", nil)
169 169
 
170 170
 		if e, a := len(item.minions), ce.Len(); e != a {
171 171
 			t.Errorf("Expected %v, got %v", e, a)
... ...
@@ -182,9 +183,9 @@ func TestDefaultErrorFunc(t *testing.T) {
182 182
 	}
183 183
 	mux := http.NewServeMux()
184 184
 	// FakeHandler musn't be sent requests other than the one you want to test.
185
-	mux.Handle("/api/v1beta1/pods/foo", &handler)
185
+	mux.Handle("/api/"+testapi.Version()+"/pods/foo", &handler)
186 186
 	server := httptest.NewServer(mux)
187
-	factory := ConfigFactory{client.NewOrDie(server.URL, "", nil)}
187
+	factory := ConfigFactory{client.NewOrDie(&client.Config{Host: server.URL, Version: testapi.Version()})}
188 188
 	queue := cache.NewFIFO()
189 189
 	errFunc := factory.makeDefaultErrorFunc(queue)
190 190
 
... ...
@@ -198,7 +199,7 @@ func TestDefaultErrorFunc(t *testing.T) {
198 198
 		if !exists {
199 199
 			continue
200 200
 		}
201
-		handler.ValidateRequest(t, "/api/v1beta1/pods/foo", "GET", nil)
201
+		handler.ValidateRequest(t, "/api/"+testapi.Version()+"/pods/foo", "GET", nil)
202 202
 		if e, a := testPod, got; !reflect.DeepEqual(e, a) {
203 203
 			t.Errorf("Expected %v, got %v", e, a)
204 204
 		}
... ...
@@ -289,14 +290,14 @@ func TestBind(t *testing.T) {
289 289
 			T:            t,
290 290
 		}
291 291
 		server := httptest.NewServer(&handler)
292
-		client := client.NewOrDie(server.URL, "", nil)
292
+		client := client.NewOrDie(&client.Config{Host: server.URL, Version: testapi.Version()})
293 293
 		b := binder{client}
294 294
 
295 295
 		if err := b.Bind(item.binding); err != nil {
296 296
 			t.Errorf("Unexpected error: %v", err)
297 297
 			continue
298 298
 		}
299
-		expectedBody := runtime.EncodeOrDie(latest.Codec, item.binding)
300
-		handler.ValidateRequest(t, "/api/v1beta1/bindings", "POST", &expectedBody)
299
+		expectedBody := runtime.EncodeOrDie(testapi.CodecForVersionOrDie(), item.binding)
300
+		handler.ValidateRequest(t, "/api/"+testapi.Version()+"/bindings", "POST", &expectedBody)
301 301
 	}
302 302
 }