Commit 085ca402 authored by derekwaynecarr's avatar derekwaynecarr

Enforce unique constraint at namespace boundary in etcd, make client and server namespace aware

parent b63974bd
......@@ -61,6 +61,8 @@ var (
imageName = flag.String("image", "", "Image used when updating a replicationController. Will apply to the first container in the pod template.")
clientConfig = &client.Config{}
openBrowser = flag.Bool("open_browser", true, "If true, and -proxy is specified, open a browser pointed at the Kubernetes UX. Default true.")
ns = flag.String("ns", "", "If present, the namespace scope for this request.")
nsFile = flag.String("ns_file", os.Getenv("HOME")+"/.kubernetes_ns", "Path to the namespace file")
)
func init() {
......@@ -97,6 +99,9 @@ on the given image:
kubecfg [OPTIONS] [-p <port spec>] run <image> <replicas> <controller>
Manage namespace:
kubecfg [OPTIONS] ns [<namespace>]
Options:
`, prettyWireStorage())
flag.PrintDefaults()
......@@ -178,8 +183,18 @@ func main() {
clientConfig.Host = os.Getenv("KUBERNETES_MASTER")
}
// TODO: get the namespace context when kubecfg ns is completed
ctx := api.NewContext()
// Load namespace information for requests
// Check if the namespace was overriden by the -ns argument
ctx := api.NewDefaultContext()
if len(*ns) > 0 {
ctx = api.WithNamespace(ctx, *ns)
} else {
nsInfo, err := kubecfg.LoadNamespaceInfo(*nsFile)
if err != nil {
glog.Fatalf("Error loading current namespace: %v", err)
}
ctx = api.WithNamespace(ctx, nsInfo.Namespace)
}
if clientConfig.Host == "" {
// TODO: eventually apiserver should start on 443 and be secure by default
......@@ -255,7 +270,7 @@ func main() {
}
method := flag.Arg(0)
matchFound := executeAPIRequest(ctx, method, kubeClient) || executeControllerRequest(ctx, method, kubeClient)
matchFound := executeAPIRequest(ctx, method, kubeClient) || executeControllerRequest(ctx, method, kubeClient) || executeNamespaceRequest(method, kubeClient)
if matchFound == false {
glog.Fatalf("Unknown command %s", method)
}
......@@ -347,7 +362,7 @@ func executeAPIRequest(ctx api.Context, method string, c *client.Client) bool {
glog.Fatalf("usage: kubecfg [OPTIONS] %s <%s>", method, prettyWireStorage())
}
case "update":
obj, err := c.Verb("GET").Path(path).Do().Get()
obj, err := c.Verb("GET").Namespace(api.Namespace(ctx)).Path(path).Do().Get()
if err != nil {
glog.Fatalf("error obtaining resource version for update: %v", err)
}
......@@ -373,7 +388,7 @@ func executeAPIRequest(ctx api.Context, method string, c *client.Client) bool {
return false
}
r := c.Verb(verb).Path(path)
r := c.Verb(verb).Namespace(api.Namespace(ctx)).Path(path)
if len(*selector) > 0 {
r.ParseSelectorParam("labels", *selector)
}
......@@ -464,6 +479,32 @@ func executeControllerRequest(ctx api.Context, method string, c *client.Client)
return true
}
// executeNamespaceRequest handles client operations for namespaces
func executeNamespaceRequest(method string, c *client.Client) bool {
var err error
var ns *kubecfg.NamespaceInfo
switch method {
case "ns":
args := flag.Args()
switch len(args) {
case 1:
ns, err = kubecfg.LoadNamespaceInfo(*nsFile)
case 2:
ns = &kubecfg.NamespaceInfo{Namespace: args[1]}
err = kubecfg.SaveNamespaceInfo(*nsFile, ns)
default:
glog.Fatalf("usage: kubecfg ns [<namespace>]")
}
default:
return false
}
if err != nil {
glog.Fatalf("Error: %v", err)
}
fmt.Printf("Using namespace %s\n", ns.Namespace)
return true
}
func humanReadablePrinter() *kubecfg.HumanReadablePrinter {
printer := kubecfg.NewHumanReadablePrinter()
// Add Handler calls here to support additional types
......
......@@ -63,6 +63,12 @@ func NamespaceFrom(ctx Context) (string, bool) {
return namespace, ok
}
// Namespace returns the value of the namespace key on the ctx, or the empty string if none
func Namespace(ctx Context) string {
namespace, _ := NamespaceFrom(ctx)
return namespace
}
// 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.
func ValidNamespace(ctx Context, resource *TypeMeta) bool {
ns, ok := NamespaceFrom(ctx)
......@@ -71,3 +77,12 @@ func ValidNamespace(ctx Context, resource *TypeMeta) bool {
}
return ns == resource.Namespace && ok
}
// WithNamespaceDefaultIfNone returns a context whose namespace is the default if and only if the parent context has no namespace value
func WithNamespaceDefaultIfNone(parent Context) Context {
namespace, ok := NamespaceFrom(parent)
if !ok || len(namespace) == 0 {
return WithNamespace(parent, NamespaceDefault)
}
return parent
}
......@@ -59,4 +59,10 @@ func TestValidNamespace(t *testing.T) {
if api.ValidNamespace(ctx, &resource.TypeMeta) {
t.Errorf("Expected error that resource and context errors do not match since context has no namespace")
}
ctx = api.NewContext()
ns := api.Namespace(ctx)
if ns != "" {
t.Errorf("Expected the empty string")
}
}
......@@ -100,10 +100,17 @@ func curry(f func(runtime.Object, *http.Request) error, req *http.Request) func(
// timeout=<duration> Timeout for synchronous requests, only applies if sync=true
// labels=<label-selector> Used for filtering list operations
func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w http.ResponseWriter, storage RESTStorage) {
// TODO for now, we perform all operations in the default namespace
ctx := api.NewDefaultContext()
ctx := api.NewContext()
sync := req.URL.Query().Get("sync") == "true"
timeout := parseTimeout(req.URL.Query().Get("timeout"))
// TODO for now, we pull namespace from query parameter, but according to spec, it must go in resource path in future PR
// if a namespace if specified, it's always used.
// for list/watch operations, a namespace is not required if omitted.
// for all other operations, if namespace is omitted, we will default to default namespace.
namespace := req.URL.Query().Get("namespace")
if len(namespace) > 0 {
ctx = api.WithNamespace(ctx, namespace)
}
switch req.Method {
case "GET":
switch len(parts) {
......@@ -129,7 +136,7 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt
}
writeJSON(http.StatusOK, h.codec, list, w)
case 2:
item, err := storage.Get(ctx, parts[1])
item, err := storage.Get(api.WithNamespaceDefaultIfNone(ctx), parts[1])
if err != nil {
errorJSON(err, h.codec, w)
return
......@@ -159,7 +166,7 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt
errorJSON(err, h.codec, w)
return
}
out, err := storage.Create(ctx, obj)
out, err := storage.Create(api.WithNamespaceDefaultIfNone(ctx), obj)
if err != nil {
errorJSON(err, h.codec, w)
return
......@@ -172,7 +179,7 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt
notFound(w, req)
return
}
out, err := storage.Delete(ctx, parts[1])
out, err := storage.Delete(api.WithNamespaceDefaultIfNone(ctx), parts[1])
if err != nil {
errorJSON(err, h.codec, w)
return
......@@ -196,7 +203,7 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt
errorJSON(err, h.codec, w)
return
}
out, err := storage.Update(ctx, obj)
out, err := storage.Update(api.WithNamespaceDefaultIfNone(ctx), obj)
if err != nil {
errorJSON(err, h.codec, w)
return
......
......@@ -104,26 +104,26 @@ type Client struct {
// ListPods takes a selector, and returns the list of pods that match that selector.
func (c *Client) ListPods(ctx api.Context, selector labels.Selector) (result *api.PodList, err error) {
result = &api.PodList{}
err = c.Get().Path("pods").SelectorParam("labels", selector).Do().Into(result)
err = c.Get().Namespace(api.Namespace(ctx)).Path("pods").SelectorParam("labels", selector).Do().Into(result)
return
}
// GetPod takes the id of the pod, and returns the corresponding Pod object, and an error if it occurs
func (c *Client) GetPod(ctx api.Context, id string) (result *api.Pod, err error) {
result = &api.Pod{}
err = c.Get().Path("pods").Path(id).Do().Into(result)
err = c.Get().Namespace(api.Namespace(ctx)).Path("pods").Path(id).Do().Into(result)
return
}
// DeletePod takes the id of the pod, and returns an error if one occurs
func (c *Client) DeletePod(ctx api.Context, id string) error {
return c.Delete().Path("pods").Path(id).Do().Error()
return c.Delete().Namespace(api.Namespace(ctx)).Path("pods").Path(id).Do().Error()
}
// CreatePod takes the representation of a pod. Returns the server's representation of the pod, and an error, if it occurs.
func (c *Client) CreatePod(ctx api.Context, pod *api.Pod) (result *api.Pod, err error) {
result = &api.Pod{}
err = c.Post().Path("pods").Body(pod).Do().Into(result)
err = c.Post().Namespace(api.Namespace(ctx)).Path("pods").Body(pod).Do().Into(result)
return
}
......@@ -134,28 +134,28 @@ func (c *Client) UpdatePod(ctx api.Context, pod *api.Pod) (result *api.Pod, err
err = fmt.Errorf("invalid update object, missing resource version: %v", pod)
return
}
err = c.Put().Path("pods").Path(pod.ID).Body(pod).Do().Into(result)
err = c.Put().Namespace(api.Namespace(ctx)).Path("pods").Path(pod.ID).Body(pod).Do().Into(result)
return
}
// ListReplicationControllers takes a selector, and returns the list of replication controllers that match that selector.
func (c *Client) ListReplicationControllers(ctx api.Context, selector labels.Selector) (result *api.ReplicationControllerList, err error) {
result = &api.ReplicationControllerList{}
err = c.Get().Path("replicationControllers").SelectorParam("labels", selector).Do().Into(result)
err = c.Get().Namespace(api.Namespace(ctx)).Path("replicationControllers").SelectorParam("labels", selector).Do().Into(result)
return
}
// GetReplicationController returns information about a particular replication controller.
func (c *Client) GetReplicationController(ctx api.Context, id string) (result *api.ReplicationController, err error) {
result = &api.ReplicationController{}
err = c.Get().Path("replicationControllers").Path(id).Do().Into(result)
err = c.Get().Namespace(api.Namespace(ctx)).Path("replicationControllers").Path(id).Do().Into(result)
return
}
// CreateReplicationController creates a new replication controller.
func (c *Client) CreateReplicationController(ctx api.Context, controller *api.ReplicationController) (result *api.ReplicationController, err error) {
result = &api.ReplicationController{}
err = c.Post().Path("replicationControllers").Body(controller).Do().Into(result)
err = c.Post().Namespace(api.Namespace(ctx)).Path("replicationControllers").Body(controller).Do().Into(result)
return
}
......@@ -166,18 +166,19 @@ func (c *Client) UpdateReplicationController(ctx api.Context, controller *api.Re
err = fmt.Errorf("invalid update object, missing resource version: %v", controller)
return
}
err = c.Put().Path("replicationControllers").Path(controller.ID).Body(controller).Do().Into(result)
err = c.Put().Namespace(api.Namespace(ctx)).Path("replicationControllers").Path(controller.ID).Body(controller).Do().Into(result)
return
}
// DeleteReplicationController deletes an existing replication controller.
func (c *Client) DeleteReplicationController(ctx api.Context, id string) error {
return c.Delete().Path("replicationControllers").Path(id).Do().Error()
return c.Delete().Namespace(api.Namespace(ctx)).Path("replicationControllers").Path(id).Do().Error()
}
// WatchReplicationControllers returns a watch.Interface that watches the requested controllers.
func (c *Client) WatchReplicationControllers(ctx api.Context, label, field labels.Selector, resourceVersion string) (watch.Interface, error) {
return c.Get().
Namespace(api.Namespace(ctx)).
Path("watch").
Path("replicationControllers").
Param("resourceVersion", resourceVersion).
......@@ -189,21 +190,21 @@ func (c *Client) WatchReplicationControllers(ctx api.Context, label, field label
// ListServices takes a selector, and returns the list of services that match that selector
func (c *Client) ListServices(ctx api.Context, selector labels.Selector) (result *api.ServiceList, err error) {
result = &api.ServiceList{}
err = c.Get().Path("services").SelectorParam("labels", selector).Do().Into(result)
err = c.Get().Namespace(api.Namespace(ctx)).Path("services").SelectorParam("labels", selector).Do().Into(result)
return
}
// GetService returns information about a particular service.
func (c *Client) GetService(ctx api.Context, id string) (result *api.Service, err error) {
result = &api.Service{}
err = c.Get().Path("services").Path(id).Do().Into(result)
err = c.Get().Namespace(api.Namespace(ctx)).Path("services").Path(id).Do().Into(result)
return
}
// CreateService creates a new service.
func (c *Client) CreateService(ctx api.Context, svc *api.Service) (result *api.Service, err error) {
result = &api.Service{}
err = c.Post().Path("services").Body(svc).Do().Into(result)
err = c.Post().Namespace(api.Namespace(ctx)).Path("services").Body(svc).Do().Into(result)
return
}
......@@ -214,18 +215,19 @@ func (c *Client) UpdateService(ctx api.Context, svc *api.Service) (result *api.S
err = fmt.Errorf("invalid update object, missing resource version: %v", svc)
return
}
err = c.Put().Path("services").Path(svc.ID).Body(svc).Do().Into(result)
err = c.Put().Namespace(api.Namespace(ctx)).Path("services").Path(svc.ID).Body(svc).Do().Into(result)
return
}
// DeleteService deletes an existing service.
func (c *Client) DeleteService(ctx api.Context, id string) error {
return c.Delete().Path("services").Path(id).Do().Error()
return c.Delete().Namespace(api.Namespace(ctx)).Path("services").Path(id).Do().Error()
}
// WatchServices returns a watch.Interface that watches the requested services.
func (c *Client) WatchServices(ctx api.Context, label, field labels.Selector, resourceVersion string) (watch.Interface, error) {
return c.Get().
Namespace(api.Namespace(ctx)).
Path("watch").
Path("services").
Param("resourceVersion", resourceVersion).
......@@ -237,20 +239,21 @@ func (c *Client) WatchServices(ctx api.Context, label, field labels.Selector, re
// ListEndpoints takes a selector, and returns the list of endpoints that match that selector
func (c *Client) ListEndpoints(ctx api.Context, selector labels.Selector) (result *api.EndpointsList, err error) {
result = &api.EndpointsList{}
err = c.Get().Path("endpoints").SelectorParam("labels", selector).Do().Into(result)
err = c.Get().Namespace(api.Namespace(ctx)).Path("endpoints").SelectorParam("labels", selector).Do().Into(result)
return
}
// GetEndpoints returns information about the endpoints for a particular service.
func (c *Client) GetEndpoints(ctx api.Context, id string) (result *api.Endpoints, err error) {
result = &api.Endpoints{}
err = c.Get().Path("endpoints").Path(id).Do().Into(result)
err = c.Get().Namespace(api.Namespace(ctx)).Path("endpoints").Path(id).Do().Into(result)
return
}
// WatchEndpoints returns a watch.Interface that watches the requested endpoints for a service.
func (c *Client) WatchEndpoints(ctx api.Context, label, field labels.Selector, resourceVersion string) (watch.Interface, error) {
return c.Get().
Namespace(api.Namespace(ctx)).
Path("watch").
Path("endpoints").
Param("resourceVersion", resourceVersion).
......@@ -261,7 +264,7 @@ func (c *Client) WatchEndpoints(ctx api.Context, label, field labels.Selector, r
func (c *Client) CreateEndpoints(ctx api.Context, endpoints *api.Endpoints) (*api.Endpoints, error) {
result := &api.Endpoints{}
err := c.Post().Path("endpoints").Body(endpoints).Do().Into(result)
err := c.Post().Namespace(api.Namespace(ctx)).Path("endpoints").Body(endpoints).Do().Into(result)
return result, err
}
......@@ -271,6 +274,7 @@ func (c *Client) UpdateEndpoints(ctx api.Context, endpoints *api.Endpoints) (*ap
return nil, fmt.Errorf("invalid update object, missing resource version: %v", endpoints)
}
err := c.Put().
Namespace(api.Namespace(ctx)).
Path("endpoints").
Path(endpoints.ID).
Body(endpoints).
......
......@@ -74,6 +74,14 @@ func (r *Request) Sync(sync bool) *Request {
return r
}
// Namespace applies the namespace scope to a request
func (r *Request) Namespace(namespace string) *Request {
if len(namespace) > 0 {
return r.setParam("namespace", namespace)
}
return r
}
// AbsPath overwrites an existing path with the path parameter.
func (r *Request) AbsPath(path string) *Request {
if r.err != nil {
......@@ -196,6 +204,7 @@ func (r *Request) finalURL() string {
for key, value := range r.params {
query.Add(key, value)
}
// sync and timeout are handled specially here, to allow setting them
// in any order.
if r.sync {
......
......@@ -215,7 +215,7 @@ func TestCreateReplica(t *testing.T) {
Labels: controllerSpec.DesiredState.PodTemplate.Labels,
DesiredState: controllerSpec.DesiredState.PodTemplate.DesiredState,
}
fakeHandler.ValidateRequest(t, makeURL("/pods"), "POST", nil)
fakeHandler.ValidateRequest(t, makeURL("/pods?namespace=default"), "POST", nil)
actualPod := api.Pod{}
if err := json.Unmarshal([]byte(fakeHandler.RequestBody), &actualPod); err != nil {
t.Errorf("Unexpected error: %#v", err)
......
......@@ -60,6 +60,10 @@ type AuthInfo struct {
Insecure *bool
}
type NamespaceInfo struct {
Namespace string
}
// LoadAuthInfo parses an AuthInfo object from a file path. It prompts user and creates file if it doesn't exist.
func LoadAuthInfo(path string, r io.Reader) (*AuthInfo, error) {
var auth AuthInfo
......@@ -84,6 +88,35 @@ func LoadAuthInfo(path string, r io.Reader) (*AuthInfo, error) {
return &auth, err
}
// LoadNamespaceInfo parses a NamespaceInfo object from a file path. It creates a file at the specified path if it doesn't exist with the default namespace.
func LoadNamespaceInfo(path string) (*NamespaceInfo, error) {
var ns NamespaceInfo
if _, err := os.Stat(path); os.IsNotExist(err) {
ns.Namespace = api.NamespaceDefault
err = SaveNamespaceInfo(path, &ns)
return &ns, err
}
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
err = json.Unmarshal(data, &ns)
if err != nil {
return nil, err
}
return &ns, err
}
// SaveNamespaceInfo saves a NamespaceInfo object at the specified file path.
func SaveNamespaceInfo(path string, ns *NamespaceInfo) error {
if !util.IsDNSLabel(ns.Namespace) {
return fmt.Errorf("Namespace %s is not a valid DNS Label", ns.Namespace)
}
data, err := json.Marshal(ns)
err = ioutil.WriteFile(path, data, 0600)
return err
}
// Update performs a rolling update of a collection of pods.
// 'name' points to a replication controller.
// 'client' is used for updating pods.
......
......@@ -240,6 +240,58 @@ func TestCloudCfgDeleteControllerWithReplicas(t *testing.T) {
}
}
func TestLoadNamespaceInfo(t *testing.T) {
loadNamespaceInfoTests := []struct {
nsData string
nsInfo *NamespaceInfo
}{
{
`{"Namespace":"test"}`,
&NamespaceInfo{Namespace: "test"},
},
{
"", nil,
},
{
"missing",
&NamespaceInfo{Namespace: "default"},
},
}
for _, loadNamespaceInfoTest := range loadNamespaceInfoTests {
tt := loadNamespaceInfoTest
nsfile, err := ioutil.TempFile("", "testNamespaceInfo")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if tt.nsData != "missing" {
defer os.Remove(nsfile.Name())
defer nsfile.Close()
_, err := nsfile.WriteString(tt.nsData)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
} else {
nsfile.Close()
os.Remove(nsfile.Name())
}
nsInfo, err := LoadNamespaceInfo(nsfile.Name())
if len(tt.nsData) == 0 && tt.nsData != "missing" {
if err == nil {
t.Error("LoadNamespaceInfo didn't fail on an empty file")
}
continue
}
if tt.nsData != "missing" {
if err != nil {
t.Errorf("Unexpected error: %v, %v", tt.nsData, err)
}
if !reflect.DeepEqual(nsInfo, tt.nsInfo) {
t.Errorf("Expected %v, got %v", tt.nsInfo, nsInfo)
}
}
}
}
func TestLoadAuthInfo(t *testing.T) {
loadAuthInfoTests := []struct {
authData string
......
......@@ -179,18 +179,18 @@ func (h *EtcdHelper) decodeNodeList(nodes []*etcd.Node, slicePtr interface{}) er
for _, node := range nodes {
if node.Dir {
h.decodeNodeList(node.Nodes, slicePtr)
} else {
obj := reflect.New(v.Type().Elem())
err := h.Codec.DecodeInto([]byte(node.Value), obj.Interface().(runtime.Object))
if h.ResourceVersioner != nil {
_ = h.ResourceVersioner.SetResourceVersion(obj.Interface().(runtime.Object), node.ModifiedIndex)
// being unable to set the version does not prevent the object from being extracted
}
if err != nil {
return err
}
v.Set(reflect.Append(v, obj.Elem()))
continue
}
obj := reflect.New(v.Type().Elem())
err := h.Codec.DecodeInto([]byte(node.Value), obj.Interface().(runtime.Object))
if h.ResourceVersioner != nil {
_ = h.ResourceVersioner.SetResourceVersion(obj.Interface().(runtime.Object), node.ModifiedIndex)
// being unable to set the version does not prevent the object from being extracted
}
if err != nil {
return err
}
v.Set(reflect.Append(v, obj.Elem()))
}
return nil
}
......
......@@ -141,10 +141,10 @@ func TestExtractToListAcrossDirectories(t *testing.T) {
},
}
expect := api.PodList{
JSONBase: api.JSONBase{ResourceVersion: 10},
TypeMeta: api.TypeMeta{ResourceVersion: "10"},
Items: []api.Pod{
{JSONBase: api.JSONBase{ID: "foo", ResourceVersion: 1}},
{JSONBase: api.JSONBase{ID: "bar", ResourceVersion: 2}},
{TypeMeta: api.TypeMeta{ID: "foo", ResourceVersion: "1"}},
{TypeMeta: api.TypeMeta{ID: "bar", ResourceVersion: "2"}},
},
}
......@@ -187,11 +187,11 @@ func TestExtractToListExcludesDirectories(t *testing.T) {
},
}
expect := api.PodList{
JSONBase: api.JSONBase{ResourceVersion: 10},
TypeMeta: api.TypeMeta{ResourceVersion: "10"},
Items: []api.Pod{
{JSONBase: api.JSONBase{ID: "foo", ResourceVersion: 1}},
{JSONBase: api.JSONBase{ID: "bar", ResourceVersion: 2}},
{JSONBase: api.JSONBase{ID: "baz", ResourceVersion: 3}},
{TypeMeta: api.TypeMeta{ID: "foo", ResourceVersion: "1"}},
{TypeMeta: api.TypeMeta{ID: "bar", ResourceVersion: "2"}},
{TypeMeta: api.TypeMeta{ID: "baz", ResourceVersion: "3"}},
},
}
......
......@@ -184,7 +184,8 @@ func (factory *ConfigFactory) makeDefaultErrorFunc(backoff *podBackoff, podQueue
backoff.wait(podID)
// Get the pod again; it may have changed/been scheduled already.
pod = &api.Pod{}
err := factory.Client.Get().Path("pods").Path(podID).Do().Into(pod)
ctx := api.WithNamespace(api.NewContext(), pod.Namespace)
err := factory.Client.Get().Namespace(api.Namespace(ctx)).Path("pods").Path(podID).Do().Into(pod)
if err != nil {
glog.Errorf("Error getting pod %v for retry: %v; abandoning", podID, err)
return
......@@ -256,7 +257,8 @@ type binder struct {
// Bind just does a POST binding RPC.
func (b *binder) Bind(binding *api.Binding) error {
glog.V(2).Infof("Attempting to bind %v to %v", binding.PodID, binding.Host)
return b.Post().Path("bindings").Body(binding).Do().Error()
ctx := api.WithNamespace(api.NewContext(), binding.Namespace)
return b.Post().Namespace(api.Namespace(ctx)).Path("bindings").Body(binding).Do().Error()
}
type clock interface {
......
......@@ -73,8 +73,9 @@ func (s *Scheduler) scheduleOne() {
return
}
b := &api.Binding{
PodID: pod.ID,
Host: dest,
TypeMeta: api.TypeMeta{Namespace: pod.Namespace},
PodID: pod.ID,
Host: dest,
}
if err := s.config.Binder.Bind(b); err != nil {
record.Eventf(pod, "", string(api.PodWaiting), "failedScheduling", "Binding rejected: %v", err)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment