mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-02 16:29:21 +00:00
Merge pull request #3613 from derekwaynecarr/namespace_as_kind
Namespace as kind
This commit is contained in:
commit
dce4cd8b1d
@ -75,7 +75,7 @@ grains:
|
|||||||
cloud_provider: vagrant
|
cloud_provider: vagrant
|
||||||
roles:
|
roles:
|
||||||
- kubernetes-master
|
- kubernetes-master
|
||||||
admission_control: AlwaysAdmit
|
admission_control: NamespaceExists,AlwaysAdmit
|
||||||
runtime_config: '$(echo "$RUNTIME_CONFIG" | sed -e "s/'/''/g")'
|
runtime_config: '$(echo "$RUNTIME_CONFIG" | sed -e "s/'/''/g")'
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
@ -379,7 +379,7 @@ func executeAPIRequest(ctx api.Context, method string, c *client.Client) bool {
|
|||||||
glog.Fatalf("usage: kubecfg [OPTIONS] %s <%s>", method, prettyWireStorage())
|
glog.Fatalf("usage: kubecfg [OPTIONS] %s <%s>", method, prettyWireStorage())
|
||||||
}
|
}
|
||||||
case "update":
|
case "update":
|
||||||
obj, err := c.Verb("GET").Namespace(api.Namespace(ctx)).Suffix(path).Do().Get()
|
obj, err := c.Verb("GET").Namespace(api.NamespaceValue(ctx)).Suffix(path).Do().Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Fatalf("error obtaining resource version for update: %v", err)
|
glog.Fatalf("error obtaining resource version for update: %v", err)
|
||||||
}
|
}
|
||||||
@ -405,7 +405,7 @@ func executeAPIRequest(ctx api.Context, method string, c *client.Client) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
r := c.Verb(verb).Namespace(api.Namespace(ctx)).Suffix(path)
|
r := c.Verb(verb).Namespace(api.NamespaceValue(ctx)).Suffix(path)
|
||||||
if len(*selector) > 0 {
|
if len(*selector) > 0 {
|
||||||
r.ParseSelectorParam("labels", *selector)
|
r.ParseSelectorParam("labels", *selector)
|
||||||
}
|
}
|
||||||
|
191
examples/kubernetes-namespaces/README.md
Normal file
191
examples/kubernetes-namespaces/README.md
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
## Kubernetes Namespaces
|
||||||
|
|
||||||
|
Kubernetes Namespaces help different projects, teams, or customers to share a Kubernetes cluster.
|
||||||
|
|
||||||
|
It does this by providing the following:
|
||||||
|
|
||||||
|
1. A scope for [Names](identifiers.md).
|
||||||
|
2. A mechanism to attach authorization and policy to a subsection of the cluster.
|
||||||
|
|
||||||
|
Use of multiple namespaces is optional.
|
||||||
|
|
||||||
|
This example demonstrates how to use Kubernetes namespaces to subdivide your cluster.
|
||||||
|
|
||||||
|
### Step Zero: Prerequisites
|
||||||
|
|
||||||
|
This example assumes the following:
|
||||||
|
|
||||||
|
1. You have an existing Kubernetes cluster.
|
||||||
|
2. You have a basic understanding of Kubernetes pods, services, and replication controllers.
|
||||||
|
|
||||||
|
### Step One: Understand the default namespace
|
||||||
|
|
||||||
|
By default, a Kubernetes cluster will instantiate a default namespace when provisioning the cluster to hold the default set of pods,
|
||||||
|
services, and replication controllers used by the cluster.
|
||||||
|
|
||||||
|
Assuming you have a fresh cluster, you can introspect the available namespace's by doing the following:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ cluster/kubectl.sh get namespaces
|
||||||
|
NAME LABELS
|
||||||
|
default <none>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step Two: Create new namespaces
|
||||||
|
|
||||||
|
For this exercise, we will create two additional Kubernetes namespaces to hold our content.
|
||||||
|
|
||||||
|
Let's imagine a scenario where an organization is using a shared Kubernetes cluster for development and production use cases.
|
||||||
|
|
||||||
|
The development team would like to maintain a space in the cluster where they can get a view on the list of pods, services, and replication-controllers
|
||||||
|
they use to build and run their application. In this space, Kubernetes resources come and go, and the restrictions on who can or cannot modify resources
|
||||||
|
are relaxed to enable agile development.
|
||||||
|
|
||||||
|
The operations team would like to maintain a space in the cluster where they can enforce strict procedures on who can or cannot manipulate the set of
|
||||||
|
pods, services, and replication controllers that run the production site.
|
||||||
|
|
||||||
|
One pattern this organization could follow is to partition the Kubernetes cluster into two namespaces: development and production.
|
||||||
|
|
||||||
|
Let's create two new namespaces to hold our work.
|
||||||
|
|
||||||
|
Use the file `examples/kubernetes-namespaces/namespace-dev.json` which describes a development namespace:
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
"kind": "Namespace",
|
||||||
|
"apiVersion":"v1beta1",
|
||||||
|
"id": "development",
|
||||||
|
"spec": {},
|
||||||
|
"status": {},
|
||||||
|
"labels": {
|
||||||
|
"name": "development"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Create the development namespace using kubectl.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ cluster/kubectl.sh create -f examples/kubernetes-namespaces/namespace-dev.json
|
||||||
|
```
|
||||||
|
|
||||||
|
And then lets create the production namespace using kubectl.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ cluster/kubectl.sh create -f examples/kubernetes-namespaces/namespace-prod.json
|
||||||
|
```
|
||||||
|
|
||||||
|
To be sure things are right, let's list all of the namespaces in our cluster.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ cluster/kubectl.sh get namespaces
|
||||||
|
NAME LABELS
|
||||||
|
default <none>
|
||||||
|
development name=development
|
||||||
|
production name=production
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step Three: Create pods in each namespace
|
||||||
|
|
||||||
|
A Kubernetes namespace provides the scope for pods, services, and replication controllers in the cluster.
|
||||||
|
|
||||||
|
Users interacting with one namespace do not see the content in another namespace.
|
||||||
|
|
||||||
|
To demonstrate this, let's spin up a simple replication controller and pod in the development namespace.
|
||||||
|
|
||||||
|
The first step is to define a context for the kubectl client to work in each namespace.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ cluster/kubectl.sh config set-context dev --namespace=development
|
||||||
|
$ cluster/kubectl.sh config set-context prod --namespace=production
|
||||||
|
```
|
||||||
|
|
||||||
|
The above commands provided two request contexts you can alternate against depending on what namespace you
|
||||||
|
wish to work against.
|
||||||
|
|
||||||
|
Let's switch to operate in the development namespace.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ cluster/kubectl.sh config use-context dev
|
||||||
|
```
|
||||||
|
|
||||||
|
You can verify your current context by doing the following:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ cluster/kubectl.sh config view
|
||||||
|
clusters: {}
|
||||||
|
contexts:
|
||||||
|
dev:
|
||||||
|
cluster: ""
|
||||||
|
namespace: development
|
||||||
|
user: ""
|
||||||
|
prod:
|
||||||
|
cluster: ""
|
||||||
|
namespace: production
|
||||||
|
user: ""
|
||||||
|
current-context: dev
|
||||||
|
preferences: {}
|
||||||
|
users: {}
|
||||||
|
```
|
||||||
|
|
||||||
|
At this point, all requests we make to the Kubernetes cluster from the command line are scoped to the development namespace.
|
||||||
|
|
||||||
|
Let's create some content.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ cluster/kubectl.sh run-container snowflake --image=kubernetes/serve_hostname --replicas=2
|
||||||
|
```
|
||||||
|
|
||||||
|
We have just created a replication controller whose replica size is 2 that is running the pod called snowflake with a basic container that just serves the hostname.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cluster/kubectl.sh get rc
|
||||||
|
CONTROLLER CONTAINER(S) IMAGE(S) SELECTOR REPLICAS
|
||||||
|
snowflake snowflake kubernetes/serve_hostname run-container=snowflake 2
|
||||||
|
|
||||||
|
$ cluster/kubectl.sh get pods
|
||||||
|
POD IP CONTAINER(S) IMAGE(S) HOST LABELS STATUS
|
||||||
|
snowflake-fplln 10.246.0.5 snowflake kubernetes/serve_hostname 10.245.1.3/10.245.1.3 run-container=snowflake Running
|
||||||
|
snowflake-gziey 10.246.0.4 snowflake kubernetes/serve_hostname 10.245.1.3/10.245.1.3 run-container=snowflake Running
|
||||||
|
```
|
||||||
|
|
||||||
|
And this is great, developers are able to do what they want, and they do not have to worry about affecting content in the production namespace.
|
||||||
|
|
||||||
|
Let's switch to the production namespace and show how resources in one namespace are hidden from the other.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ cluster/kubectl.sh config use-context prod
|
||||||
|
```
|
||||||
|
|
||||||
|
The production namespace should be empty.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ cluster/kubectl.sh get rc
|
||||||
|
CONTROLLER CONTAINER(S) IMAGE(S) SELECTOR REPLICAS
|
||||||
|
|
||||||
|
$ cluster/kubectl.sh get pods
|
||||||
|
POD IP CONTAINER(S) IMAGE(S) HOST LABELS STATUS
|
||||||
|
```
|
||||||
|
|
||||||
|
Production likes to run cattle, so let's create some cattle pods.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ cluster/kubectl.sh run-container cattle --image=kubernetes/serve_hostname --replicas=5
|
||||||
|
|
||||||
|
$ cluster/kubectl.sh get rc
|
||||||
|
CONTROLLER CONTAINER(S) IMAGE(S) SELECTOR REPLICAS
|
||||||
|
cattle cattle kubernetes/serve_hostname run-container=cattle 5
|
||||||
|
|
||||||
|
$ cluster/kubectl.sh get pods
|
||||||
|
POD IP CONTAINER(S) IMAGE(S) HOST LABELS STATUS
|
||||||
|
cattle-0133o 10.246.0.7 cattle kubernetes/serve_hostname 10.245.1.3/10.245.1.3 run-container=cattle Running
|
||||||
|
cattle-hh2gd 10.246.0.10 cattle kubernetes/serve_hostname 10.245.1.3/10.245.1.3 run-container=cattle Running
|
||||||
|
cattle-ls6k1 10.246.0.9 cattle kubernetes/serve_hostname 10.245.1.3/10.245.1.3 run-container=cattle Running
|
||||||
|
cattle-nyxxv 10.246.0.8 cattle kubernetes/serve_hostname 10.245.1.3/10.245.1.3 run-container=cattle Running
|
||||||
|
cattle-oh43e 10.246.0.6 cattle kubernetes/serve_hostname 10.245.1.3/10.245.1.3 run-container=cattle Running
|
||||||
|
```
|
||||||
|
|
||||||
|
At this point, it should be clear that the resources users create in one namespace are hidden from the other namespace.
|
||||||
|
|
||||||
|
As the policy support in Kubernetes evolves, we will extend this scenario to show how you can provide different
|
||||||
|
authorization rules for each namespace.
|
10
examples/kubernetes-namespaces/namespace-dev.json
Normal file
10
examples/kubernetes-namespaces/namespace-dev.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"kind": "Namespace",
|
||||||
|
"apiVersion":"v1beta1",
|
||||||
|
"id": "development",
|
||||||
|
"spec": {},
|
||||||
|
"status": {},
|
||||||
|
"labels": {
|
||||||
|
"name": "development"
|
||||||
|
},
|
||||||
|
}
|
10
examples/kubernetes-namespaces/namespace-prod.json
Normal file
10
examples/kubernetes-namespaces/namespace-prod.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"kind": "Namespace",
|
||||||
|
"apiVersion":"v1beta1",
|
||||||
|
"id": "production",
|
||||||
|
"spec": {},
|
||||||
|
"status": {},
|
||||||
|
"labels": {
|
||||||
|
"name": "production"
|
||||||
|
},
|
||||||
|
}
|
@ -63,8 +63,8 @@ func NamespaceFrom(ctx Context) (string, bool) {
|
|||||||
return namespace, ok
|
return namespace, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// Namespace returns the value of the namespace key on the ctx, or the empty string if none
|
// NamespaceValue returns the value of the namespace key on the ctx, or the empty string if none
|
||||||
func Namespace(ctx Context) string {
|
func NamespaceValue(ctx Context) string {
|
||||||
namespace, _ := NamespaceFrom(ctx)
|
namespace, _ := NamespaceFrom(ctx)
|
||||||
return namespace
|
return namespace
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ func TestValidNamespace(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx = api.NewContext()
|
ctx = api.NewContext()
|
||||||
ns := api.Namespace(ctx)
|
ns := api.NamespaceValue(ctx)
|
||||||
if ns != "" {
|
if ns != "" {
|
||||||
t.Errorf("Expected the empty string")
|
t.Errorf("Expected the empty string")
|
||||||
}
|
}
|
||||||
|
@ -122,8 +122,9 @@ func init() {
|
|||||||
// the list of kinds that are scoped at the root of the api hierarchy
|
// the list of kinds that are scoped at the root of the api hierarchy
|
||||||
// if a kind is not enumerated here, it is assumed to have a namespace scope
|
// if a kind is not enumerated here, it is assumed to have a namespace scope
|
||||||
kindToRootScope := map[string]bool{
|
kindToRootScope := map[string]bool{
|
||||||
"Node": true,
|
"Node": true,
|
||||||
"Minion": true,
|
"Minion": true,
|
||||||
|
"Namespace": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// enumerate all supported versions, get the kinds, and register with the mapper how to address our resources
|
// enumerate all supported versions, get the kinds, and register with the mapper how to address our resources
|
||||||
|
@ -51,7 +51,7 @@ var RESTScopeNamespaceLegacy = &restScope{
|
|||||||
|
|
||||||
var RESTScopeNamespace = &restScope{
|
var RESTScopeNamespace = &restScope{
|
||||||
name: RESTScopeNameNamespace,
|
name: RESTScopeNameNamespace,
|
||||||
paramName: "ns",
|
paramName: "namespaces",
|
||||||
paramPath: true,
|
paramPath: true,
|
||||||
paramDescription: "object name and auth scope, such as for teams and projects",
|
paramDescription: "object name and auth scope, such as for teams and projects",
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,8 @@ func init() {
|
|||||||
&ResourceQuota{},
|
&ResourceQuota{},
|
||||||
&ResourceQuotaList{},
|
&ResourceQuotaList{},
|
||||||
&ResourceQuotaUsage{},
|
&ResourceQuotaUsage{},
|
||||||
|
&Namespace{},
|
||||||
|
&NamespaceList{},
|
||||||
)
|
)
|
||||||
// Legacy names are supported
|
// Legacy names are supported
|
||||||
Scheme.AddKnownTypeWithName("", "Minion", &Node{})
|
Scheme.AddKnownTypeWithName("", "Minion", &Node{})
|
||||||
@ -81,3 +83,5 @@ func (*LimitRangeList) IsAnAPIObject() {}
|
|||||||
func (*ResourceQuota) IsAnAPIObject() {}
|
func (*ResourceQuota) IsAnAPIObject() {}
|
||||||
func (*ResourceQuotaList) IsAnAPIObject() {}
|
func (*ResourceQuotaList) IsAnAPIObject() {}
|
||||||
func (*ResourceQuotaUsage) IsAnAPIObject() {}
|
func (*ResourceQuotaUsage) IsAnAPIObject() {}
|
||||||
|
func (*Namespace) IsAnAPIObject() {}
|
||||||
|
func (*NamespaceList) IsAnAPIObject() {}
|
||||||
|
@ -118,7 +118,7 @@ type nodeStrategy struct {
|
|||||||
// objects.
|
// objects.
|
||||||
var Nodes RESTCreateStrategy = nodeStrategy{api.Scheme, api.SimpleNameGenerator}
|
var Nodes RESTCreateStrategy = nodeStrategy{api.Scheme, api.SimpleNameGenerator}
|
||||||
|
|
||||||
// NamespaceScoped is false for services.
|
// NamespaceScoped is false for nodes.
|
||||||
func (nodeStrategy) NamespaceScoped() bool {
|
func (nodeStrategy) NamespaceScoped() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -134,3 +134,30 @@ func (nodeStrategy) Validate(obj runtime.Object) errors.ValidationErrorList {
|
|||||||
node := obj.(*api.Node)
|
node := obj.(*api.Node)
|
||||||
return validation.ValidateMinion(node)
|
return validation.ValidateMinion(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// namespaceStrategy implements behavior for nodes
|
||||||
|
type namespaceStrategy struct {
|
||||||
|
runtime.ObjectTyper
|
||||||
|
api.NameGenerator
|
||||||
|
}
|
||||||
|
|
||||||
|
// Namespaces is the default logic that applies when creating and updating Namespace
|
||||||
|
// objects.
|
||||||
|
var Namespaces RESTCreateStrategy = namespaceStrategy{api.Scheme, api.SimpleNameGenerator}
|
||||||
|
|
||||||
|
// NamespaceScoped is false for namespaces.
|
||||||
|
func (namespaceStrategy) NamespaceScoped() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetBeforeCreate clears fields that are not allowed to be set by end users on creation.
|
||||||
|
func (namespaceStrategy) ResetBeforeCreate(obj runtime.Object) {
|
||||||
|
_ = obj.(*api.Namespace)
|
||||||
|
// Namespace allow *all* fields, including status, to be set.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates a new namespace.
|
||||||
|
func (namespaceStrategy) Validate(obj runtime.Object) errors.ValidationErrorList {
|
||||||
|
namespace := obj.(*api.Namespace)
|
||||||
|
return validation.ValidateNamespace(namespace)
|
||||||
|
}
|
||||||
|
@ -851,6 +851,35 @@ type NodeList struct {
|
|||||||
Items []Node `json:"items"`
|
Items []Node `json:"items"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NamespaceSpec describes the attributes on a Namespace
|
||||||
|
type NamespaceSpec struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamespaceStatus is information about the current status of a Namespace.
|
||||||
|
type NamespaceStatus struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// A namespace provides a scope for Names.
|
||||||
|
// Use of multiple namespaces is optional
|
||||||
|
type Namespace struct {
|
||||||
|
TypeMeta `json:",inline"`
|
||||||
|
ObjectMeta `json:"metadata,omitempty"`
|
||||||
|
|
||||||
|
// Spec defines the behavior of the Namespace.
|
||||||
|
Spec NamespaceSpec `json:"spec,omitempty"`
|
||||||
|
|
||||||
|
// Status describes the current status of a Namespace
|
||||||
|
Status NamespaceStatus `json:"status,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamespaceList is a list of Namespaces.
|
||||||
|
type NamespaceList struct {
|
||||||
|
TypeMeta `json:",inline"`
|
||||||
|
ListMeta `json:"metadata,omitempty"`
|
||||||
|
|
||||||
|
Items []Namespace `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
// Binding is written by a scheduler to cause a pod to be bound to a host.
|
// Binding is written by a scheduler to cause a pod to be bound to a host.
|
||||||
type Binding struct {
|
type Binding struct {
|
||||||
TypeMeta `json:",inline"`
|
TypeMeta `json:",inline"`
|
||||||
|
@ -743,6 +743,21 @@ func init() {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
func(in *Namespace, out *newer.Namespace, s conversion.Scope) error {
|
||||||
|
if err := s.Convert(&in.TypeMeta, &out.TypeMeta, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.TypeMeta, &out.ObjectMeta, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Spec, &out.Spec, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Labels, &out.ObjectMeta.Labels, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
func(in *newer.LimitRangeSpec, out *LimitRangeSpec, s conversion.Scope) error {
|
func(in *newer.LimitRangeSpec, out *LimitRangeSpec, s conversion.Scope) error {
|
||||||
*out = LimitRangeSpec{}
|
*out = LimitRangeSpec{}
|
||||||
out.Limits = make([]LimitRangeItem, len(in.Limits), len(in.Limits))
|
out.Limits = make([]LimitRangeItem, len(in.Limits), len(in.Limits))
|
||||||
|
@ -51,6 +51,8 @@ func init() {
|
|||||||
&ResourceQuota{},
|
&ResourceQuota{},
|
||||||
&ResourceQuotaList{},
|
&ResourceQuotaList{},
|
||||||
&ResourceQuotaUsage{},
|
&ResourceQuotaUsage{},
|
||||||
|
&Namespace{},
|
||||||
|
&NamespaceList{},
|
||||||
)
|
)
|
||||||
// Future names are supported
|
// Future names are supported
|
||||||
api.Scheme.AddKnownTypeWithName("v1beta1", "Node", &Minion{})
|
api.Scheme.AddKnownTypeWithName("v1beta1", "Node", &Minion{})
|
||||||
@ -82,3 +84,5 @@ func (*LimitRangeList) IsAnAPIObject() {}
|
|||||||
func (*ResourceQuota) IsAnAPIObject() {}
|
func (*ResourceQuota) IsAnAPIObject() {}
|
||||||
func (*ResourceQuotaList) IsAnAPIObject() {}
|
func (*ResourceQuotaList) IsAnAPIObject() {}
|
||||||
func (*ResourceQuotaUsage) IsAnAPIObject() {}
|
func (*ResourceQuotaUsage) IsAnAPIObject() {}
|
||||||
|
func (*Namespace) IsAnAPIObject() {}
|
||||||
|
func (*NamespaceList) IsAnAPIObject() {}
|
||||||
|
@ -684,6 +684,36 @@ type MinionList struct {
|
|||||||
Items []Minion `json:"items" description:"list of nodes"`
|
Items []Minion `json:"items" description:"list of nodes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NamespaceSpec describes the attributes on a Namespace
|
||||||
|
type NamespaceSpec struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamespaceStatus is information about the current status of a Namespace.
|
||||||
|
type NamespaceStatus struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// A namespace provides a scope for Names.
|
||||||
|
// Use of multiple namespaces is optional
|
||||||
|
type Namespace struct {
|
||||||
|
TypeMeta `json:",inline"`
|
||||||
|
|
||||||
|
// Labels
|
||||||
|
Labels map[string]string `json:"labels,omitempty" description:"map of string keys and values that can be used to organize and categorize namespaces"`
|
||||||
|
|
||||||
|
// Spec defines the behavior of the Namespace.
|
||||||
|
Spec NamespaceSpec `json:"spec,omitempty"`
|
||||||
|
|
||||||
|
// Status describes the current status of a Namespace
|
||||||
|
Status NamespaceStatus `json:"status,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamespaceList is a list of Namespaces.
|
||||||
|
type NamespaceList struct {
|
||||||
|
TypeMeta `json:",inline"`
|
||||||
|
|
||||||
|
Items []Namespace `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
// Binding is written by a scheduler to cause a pod to be bound to a host.
|
// Binding is written by a scheduler to cause a pod to be bound to a host.
|
||||||
type Binding struct {
|
type Binding struct {
|
||||||
TypeMeta `json:",inline"`
|
TypeMeta `json:",inline"`
|
||||||
|
@ -663,6 +663,21 @@ func init() {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
func(in *Namespace, out *newer.Namespace, s conversion.Scope) error {
|
||||||
|
if err := s.Convert(&in.TypeMeta, &out.TypeMeta, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.TypeMeta, &out.ObjectMeta, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Spec, &out.Spec, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.Labels, &out.ObjectMeta.Labels, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
func(in *newer.LimitRangeSpec, out *LimitRangeSpec, s conversion.Scope) error {
|
func(in *newer.LimitRangeSpec, out *LimitRangeSpec, s conversion.Scope) error {
|
||||||
*out = LimitRangeSpec{}
|
*out = LimitRangeSpec{}
|
||||||
out.Limits = make([]LimitRangeItem, len(in.Limits), len(in.Limits))
|
out.Limits = make([]LimitRangeItem, len(in.Limits), len(in.Limits))
|
||||||
|
@ -51,6 +51,8 @@ func init() {
|
|||||||
&ResourceQuota{},
|
&ResourceQuota{},
|
||||||
&ResourceQuotaList{},
|
&ResourceQuotaList{},
|
||||||
&ResourceQuotaUsage{},
|
&ResourceQuotaUsage{},
|
||||||
|
&Namespace{},
|
||||||
|
&NamespaceList{},
|
||||||
)
|
)
|
||||||
// Future names are supported
|
// Future names are supported
|
||||||
api.Scheme.AddKnownTypeWithName("v1beta2", "Node", &Minion{})
|
api.Scheme.AddKnownTypeWithName("v1beta2", "Node", &Minion{})
|
||||||
@ -82,3 +84,5 @@ func (*LimitRangeList) IsAnAPIObject() {}
|
|||||||
func (*ResourceQuota) IsAnAPIObject() {}
|
func (*ResourceQuota) IsAnAPIObject() {}
|
||||||
func (*ResourceQuotaList) IsAnAPIObject() {}
|
func (*ResourceQuotaList) IsAnAPIObject() {}
|
||||||
func (*ResourceQuotaUsage) IsAnAPIObject() {}
|
func (*ResourceQuotaUsage) IsAnAPIObject() {}
|
||||||
|
func (*Namespace) IsAnAPIObject() {}
|
||||||
|
func (*NamespaceList) IsAnAPIObject() {}
|
||||||
|
@ -645,6 +645,36 @@ type MinionList struct {
|
|||||||
Items []Minion `json:"items" description:"list of nodes"`
|
Items []Minion `json:"items" description:"list of nodes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NamespaceSpec describes the attributes on a Namespace
|
||||||
|
type NamespaceSpec struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamespaceStatus is information about the current status of a Namespace.
|
||||||
|
type NamespaceStatus struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// A namespace provides a scope for Names.
|
||||||
|
// Use of multiple namespaces is optional
|
||||||
|
type Namespace struct {
|
||||||
|
TypeMeta `json:",inline"`
|
||||||
|
|
||||||
|
// Labels
|
||||||
|
Labels map[string]string `json:"labels,omitempty" description:"map of string keys and values that can be used to organize and categorize namespaces"`
|
||||||
|
|
||||||
|
// Spec defines the behavior of the Namespace.
|
||||||
|
Spec NamespaceSpec `json:"spec,omitempty"`
|
||||||
|
|
||||||
|
// Status describes the current status of a Namespace
|
||||||
|
Status NamespaceStatus `json:"status,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamespaceList is a list of Namespaces.
|
||||||
|
type NamespaceList struct {
|
||||||
|
TypeMeta `json:",inline"`
|
||||||
|
|
||||||
|
Items []Namespace `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
// Binding is written by a scheduler to cause a pod to be bound to a host.
|
// Binding is written by a scheduler to cause a pod to be bound to a host.
|
||||||
type Binding struct {
|
type Binding struct {
|
||||||
TypeMeta `json:",inline"`
|
TypeMeta `json:",inline"`
|
||||||
|
@ -51,6 +51,8 @@ func init() {
|
|||||||
&ResourceQuota{},
|
&ResourceQuota{},
|
||||||
&ResourceQuotaList{},
|
&ResourceQuotaList{},
|
||||||
&ResourceQuotaUsage{},
|
&ResourceQuotaUsage{},
|
||||||
|
&Namespace{},
|
||||||
|
&NamespaceList{},
|
||||||
)
|
)
|
||||||
// Legacy names are supported
|
// Legacy names are supported
|
||||||
api.Scheme.AddKnownTypeWithName("v1beta3", "Minion", &Node{})
|
api.Scheme.AddKnownTypeWithName("v1beta3", "Minion", &Node{})
|
||||||
@ -82,3 +84,5 @@ func (*LimitRangeList) IsAnAPIObject() {}
|
|||||||
func (*ResourceQuota) IsAnAPIObject() {}
|
func (*ResourceQuota) IsAnAPIObject() {}
|
||||||
func (*ResourceQuotaList) IsAnAPIObject() {}
|
func (*ResourceQuotaList) IsAnAPIObject() {}
|
||||||
func (*ResourceQuotaUsage) IsAnAPIObject() {}
|
func (*ResourceQuotaUsage) IsAnAPIObject() {}
|
||||||
|
func (*Namespace) IsAnAPIObject() {}
|
||||||
|
func (*NamespaceList) IsAnAPIObject() {}
|
||||||
|
@ -866,6 +866,35 @@ type NodeList struct {
|
|||||||
Items []Node `json:"items"`
|
Items []Node `json:"items"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NamespaceSpec describes the attributes on a Namespace
|
||||||
|
type NamespaceSpec struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamespaceStatus is information about the current status of a Namespace.
|
||||||
|
type NamespaceStatus struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// A namespace provides a scope for Names.
|
||||||
|
// Use of multiple namespaces is optional
|
||||||
|
type Namespace struct {
|
||||||
|
TypeMeta `json:",inline"`
|
||||||
|
ObjectMeta `json:"metadata,omitempty"`
|
||||||
|
|
||||||
|
// Spec defines the behavior of the Namespace.
|
||||||
|
Spec NamespaceSpec `json:"spec,omitempty"`
|
||||||
|
|
||||||
|
// Status describes the current status of a Namespace
|
||||||
|
Status NamespaceStatus `json:"status,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamespaceList is a list of Namespaces.
|
||||||
|
type NamespaceList struct {
|
||||||
|
TypeMeta `json:",inline"`
|
||||||
|
ListMeta `json:"metadata,omitempty"`
|
||||||
|
|
||||||
|
Items []Namespace `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
// Binding is written by a scheduler to cause a pod to be bound to a node. Name is not
|
// Binding is written by a scheduler to cause a pod to be bound to a node. Name is not
|
||||||
// required for Bindings.
|
// required for Bindings.
|
||||||
type Binding struct {
|
type Binding struct {
|
||||||
|
@ -109,6 +109,13 @@ func ValidateNodeName(name string, prefix bool) (bool, string) {
|
|||||||
return nameIsDNSSubdomain(name, prefix)
|
return nameIsDNSSubdomain(name, prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidateNamespaceName can be used to check whether the given namespace name is valid.
|
||||||
|
// Prefix indicates this name will be used as part of generation, in which case
|
||||||
|
// trailing dashes are allowed.
|
||||||
|
func ValidateNamespaceName(name string, prefix bool) (bool, string) {
|
||||||
|
return nameIsDNSSubdomain(name, prefix)
|
||||||
|
}
|
||||||
|
|
||||||
// nameIsDNSSubdomain is a ValidateNameFunc for names that must be a DNS subdomain.
|
// nameIsDNSSubdomain is a ValidateNameFunc for names that must be a DNS subdomain.
|
||||||
func nameIsDNSSubdomain(name string, prefix bool) (bool, string) {
|
func nameIsDNSSubdomain(name string, prefix bool) (bool, string) {
|
||||||
if prefix {
|
if prefix {
|
||||||
@ -839,3 +846,27 @@ func ValidateResourceQuota(resourceQuota *api.ResourceQuota) errs.ValidationErro
|
|||||||
}
|
}
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidateNamespace tests if required fields are set.
|
||||||
|
func ValidateNamespace(namespace *api.Namespace) errs.ValidationErrorList {
|
||||||
|
allErrs := errs.ValidationErrorList{}
|
||||||
|
allErrs = append(allErrs, ValidateObjectMeta(&namespace.ObjectMeta, false, ValidateNamespaceName).Prefix("metadata")...)
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateNamespaceUpdate tests to make sure a mamespace update can be applied. Modifies oldNamespace.
|
||||||
|
func ValidateNamespaceUpdate(oldNamespace *api.Namespace, namespace *api.Namespace) errs.ValidationErrorList {
|
||||||
|
allErrs := errs.ValidationErrorList{}
|
||||||
|
allErrs = append(allErrs, ValidateObjectMetaUpdate(&oldNamespace.ObjectMeta, &namespace.ObjectMeta).Prefix("metadata")...)
|
||||||
|
|
||||||
|
// TODO: move reset function to its own location
|
||||||
|
// Ignore metadata changes now that they have been tested
|
||||||
|
oldNamespace.ObjectMeta = namespace.ObjectMeta
|
||||||
|
|
||||||
|
// TODO: Add a 'real' ValidationError type for this error and provide print actual diffs.
|
||||||
|
if !api.Semantic.DeepEqual(oldNamespace, namespace) {
|
||||||
|
glog.V(4).Infof("Update failed validation %#v vs %#v", oldNamespace, namespace)
|
||||||
|
allErrs = append(allErrs, fmt.Errorf("update contains more than labels or annotation changes"))
|
||||||
|
}
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
@ -2353,3 +2353,114 @@ func TestValidateResourceQuota(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidateNamespace(t *testing.T) {
|
||||||
|
validLabels := map[string]string{"a": "b"}
|
||||||
|
invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
|
||||||
|
successCases := []api.Namespace{
|
||||||
|
{
|
||||||
|
ObjectMeta: api.ObjectMeta{Name: "abc", Labels: validLabels},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: api.ObjectMeta{Name: "abc-123"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, successCase := range successCases {
|
||||||
|
if errs := ValidateNamespace(&successCase); len(errs) != 0 {
|
||||||
|
t.Errorf("expected success: %v", errs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
errorCases := map[string]struct {
|
||||||
|
R api.Namespace
|
||||||
|
D string
|
||||||
|
}{
|
||||||
|
"zero-length name": {
|
||||||
|
api.Namespace{ObjectMeta: api.ObjectMeta{Name: ""}},
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
"defined-namespace": {
|
||||||
|
api.Namespace{ObjectMeta: api.ObjectMeta{Name: "abc-123", Namespace: "makesnosense"}},
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
"invalid-labels": {
|
||||||
|
api.Namespace{ObjectMeta: api.ObjectMeta{Name: "abc", Labels: invalidLabels}},
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for k, v := range errorCases {
|
||||||
|
errs := ValidateNamespace(&v.R)
|
||||||
|
if len(errs) == 0 {
|
||||||
|
t.Errorf("expected failure for %s", k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateNamespaceUpdate(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
oldNamespace api.Namespace
|
||||||
|
namespace api.Namespace
|
||||||
|
valid bool
|
||||||
|
}{
|
||||||
|
{api.Namespace{}, api.Namespace{}, true},
|
||||||
|
{api.Namespace{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "foo"}},
|
||||||
|
api.Namespace{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "bar"},
|
||||||
|
}, false},
|
||||||
|
{api.Namespace{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
Labels: map[string]string{"foo": "bar"},
|
||||||
|
},
|
||||||
|
}, api.Namespace{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
Labels: map[string]string{"foo": "baz"},
|
||||||
|
},
|
||||||
|
}, true},
|
||||||
|
{api.Namespace{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
},
|
||||||
|
}, api.Namespace{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
Labels: map[string]string{"foo": "baz"},
|
||||||
|
},
|
||||||
|
}, true},
|
||||||
|
{api.Namespace{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
Labels: map[string]string{"bar": "foo"},
|
||||||
|
},
|
||||||
|
}, api.Namespace{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
Labels: map[string]string{"foo": "baz"},
|
||||||
|
},
|
||||||
|
}, true},
|
||||||
|
{api.Namespace{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
Labels: map[string]string{"foo": "baz"},
|
||||||
|
},
|
||||||
|
}, api.Namespace{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
Labels: map[string]string{"Foo": "baz"},
|
||||||
|
},
|
||||||
|
}, false},
|
||||||
|
}
|
||||||
|
for i, test := range tests {
|
||||||
|
errs := ValidateNamespaceUpdate(&test.oldNamespace, &test.namespace)
|
||||||
|
if test.valid && len(errs) > 0 {
|
||||||
|
t.Errorf("%d: Unexpected error: %v", i, errs)
|
||||||
|
t.Logf("%#v vs %#v", test.oldNamespace.ObjectMeta, test.namespace.ObjectMeta)
|
||||||
|
}
|
||||||
|
if !test.valid && len(errs) == 0 {
|
||||||
|
t.Errorf("%d: Unexpected non-error", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -232,7 +232,7 @@ func (storage *SimpleRESTStorage) Watch(ctx api.Context, label, field labels.Sel
|
|||||||
storage.requestedLabelSelector = label
|
storage.requestedLabelSelector = label
|
||||||
storage.requestedFieldSelector = field
|
storage.requestedFieldSelector = field
|
||||||
storage.requestedResourceVersion = resourceVersion
|
storage.requestedResourceVersion = resourceVersion
|
||||||
storage.requestedResourceNamespace = api.Namespace(ctx)
|
storage.requestedResourceNamespace = api.NamespaceValue(ctx)
|
||||||
if err := storage.errors["watch"]; err != nil {
|
if err := storage.errors["watch"]; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -243,7 +243,7 @@ func (storage *SimpleRESTStorage) Watch(ctx api.Context, label, field labels.Sel
|
|||||||
// Implement Redirector.
|
// Implement Redirector.
|
||||||
func (storage *SimpleRESTStorage) ResourceLocation(ctx api.Context, id string) (string, error) {
|
func (storage *SimpleRESTStorage) ResourceLocation(ctx api.Context, id string) (string, error) {
|
||||||
// validate that the namespace context on the request matches the expected input
|
// validate that the namespace context on the request matches the expected input
|
||||||
storage.requestedResourceNamespace = api.Namespace(ctx)
|
storage.requestedResourceNamespace = api.NamespaceValue(ctx)
|
||||||
if storage.expectedResourceNamespace != storage.requestedResourceNamespace {
|
if storage.expectedResourceNamespace != storage.requestedResourceNamespace {
|
||||||
return "", fmt.Errorf("Expected request namespace %s, but got namespace %s", storage.expectedResourceNamespace, storage.requestedResourceNamespace)
|
return "", fmt.Errorf("Expected request namespace %s, but got namespace %s", storage.expectedResourceNamespace, storage.requestedResourceNamespace)
|
||||||
}
|
}
|
||||||
|
@ -223,8 +223,10 @@ type APIRequestInfoResolver struct {
|
|||||||
// GetAPIRequestInfo returns the information from the http request. If error is not nil, APIRequestInfo holds the information as best it is known before the failure
|
// GetAPIRequestInfo returns the information from the http request. If error is not nil, APIRequestInfo holds the information as best it is known before the failure
|
||||||
// Valid Inputs:
|
// Valid Inputs:
|
||||||
// Storage paths
|
// Storage paths
|
||||||
// /ns/{namespace}/{resource}
|
// /namespaces
|
||||||
// /ns/{namespace}/{resource}/{resourceName}
|
// /namespaces/{namespace}
|
||||||
|
// /namespaces/{namespace}/{resource}
|
||||||
|
// /namespaces/{namespace}/{resource}/{resourceName}
|
||||||
// /{resource}
|
// /{resource}
|
||||||
// /{resource}/{resourceName}
|
// /{resource}/{resourceName}
|
||||||
// /{resource}/{resourceName}?namespace={namespace}
|
// /{resource}/{resourceName}?namespace={namespace}
|
||||||
@ -287,15 +289,16 @@ func (r *APIRequestInfoResolver) GetAPIRequestInfo(req *http.Request) (APIReques
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// URL forms: /ns/{namespace}/{resource}/*, where parts are adjusted to be relative to kind
|
// URL forms: /namespaces/{namespace}/{kind}/*, where parts are adjusted to be relative to kind
|
||||||
if currentParts[0] == "ns" {
|
if currentParts[0] == "namespaces" {
|
||||||
if len(currentParts) < 3 {
|
if len(currentParts) < 3 {
|
||||||
return requestInfo, fmt.Errorf("ResourceTypeAndNamespace expects a path of form /ns/{namespace}/*")
|
requestInfo.Namespace = ""
|
||||||
|
requestInfo.Resource = "namespaces"
|
||||||
|
} else {
|
||||||
|
requestInfo.Resource = currentParts[2]
|
||||||
|
requestInfo.Namespace = currentParts[1]
|
||||||
|
currentParts = currentParts[2:]
|
||||||
}
|
}
|
||||||
requestInfo.Resource = currentParts[2]
|
|
||||||
requestInfo.Namespace = currentParts[1]
|
|
||||||
currentParts = currentParts[2:]
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// URL forms: /{resource}/*
|
// URL forms: /{resource}/*
|
||||||
// URL forms: POST /{resource} is a legacy API convention to create in "default" namespace
|
// URL forms: POST /{resource} is a legacy API convention to create in "default" namespace
|
||||||
|
@ -77,9 +77,13 @@ func TestGetAPIRequestInfo(t *testing.T) {
|
|||||||
expectedName string
|
expectedName string
|
||||||
expectedParts []string
|
expectedParts []string
|
||||||
}{
|
}{
|
||||||
|
|
||||||
// resource paths
|
// resource paths
|
||||||
{"GET", "/ns/other/pods", "list", "", "other", "pods", "Pod", "", []string{"pods"}},
|
{"GET", "/namespaces", "list", "", "", "namespaces", "Namespace", "", []string{"namespaces"}},
|
||||||
{"GET", "/ns/other/pods/foo", "get", "", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
|
{"GET", "/namespaces/other", "get", "", "", "namespaces", "Namespace", "other", []string{"namespaces", "other"}},
|
||||||
|
|
||||||
|
{"GET", "/namespaces/other/pods", "list", "", "other", "pods", "Pod", "", []string{"pods"}},
|
||||||
|
{"GET", "/namespaces/other/pods/foo", "get", "", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||||
{"GET", "/pods", "list", "", api.NamespaceAll, "pods", "Pod", "", []string{"pods"}},
|
{"GET", "/pods", "list", "", api.NamespaceAll, "pods", "Pod", "", []string{"pods"}},
|
||||||
{"POST", "/pods", "create", "", api.NamespaceDefault, "pods", "Pod", "", []string{"pods"}},
|
{"POST", "/pods", "create", "", api.NamespaceDefault, "pods", "Pod", "", []string{"pods"}},
|
||||||
{"GET", "/pods/foo", "get", "", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
|
{"GET", "/pods/foo", "get", "", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||||
@ -87,16 +91,16 @@ func TestGetAPIRequestInfo(t *testing.T) {
|
|||||||
{"GET", "/pods?namespace=other", "list", "", "other", "pods", "Pod", "", []string{"pods"}},
|
{"GET", "/pods?namespace=other", "list", "", "other", "pods", "Pod", "", []string{"pods"}},
|
||||||
|
|
||||||
// special verbs
|
// special verbs
|
||||||
{"GET", "/proxy/ns/other/pods/foo", "proxy", "", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
|
{"GET", "/proxy/namespaces/other/pods/foo", "proxy", "", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||||
{"GET", "/proxy/pods/foo", "proxy", "", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
|
{"GET", "/proxy/pods/foo", "proxy", "", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||||
{"GET", "/redirect/ns/other/pods/foo", "redirect", "", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
|
{"GET", "/redirect/namespaces/other/pods/foo", "redirect", "", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||||
{"GET", "/redirect/pods/foo", "redirect", "", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
|
{"GET", "/redirect/pods/foo", "redirect", "", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||||
{"GET", "/watch/pods", "watch", "", api.NamespaceAll, "pods", "Pod", "", []string{"pods"}},
|
{"GET", "/watch/pods", "watch", "", api.NamespaceAll, "pods", "Pod", "", []string{"pods"}},
|
||||||
{"GET", "/watch/ns/other/pods", "watch", "", "other", "pods", "Pod", "", []string{"pods"}},
|
{"GET", "/watch/namespaces/other/pods", "watch", "", "other", "pods", "Pod", "", []string{"pods"}},
|
||||||
|
|
||||||
// fully-qualified paths
|
// fully-qualified paths
|
||||||
{"GET", "/api/v1beta1/ns/other/pods", "list", "v1beta1", "other", "pods", "Pod", "", []string{"pods"}},
|
{"GET", "/api/v1beta1/namespaces/other/pods", "list", "v1beta1", "other", "pods", "Pod", "", []string{"pods"}},
|
||||||
{"GET", "/api/v1beta1/ns/other/pods/foo", "get", "v1beta1", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
|
{"GET", "/api/v1beta1/namespaces/other/pods/foo", "get", "v1beta1", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||||
{"GET", "/api/v1beta1/pods", "list", "v1beta1", api.NamespaceAll, "pods", "Pod", "", []string{"pods"}},
|
{"GET", "/api/v1beta1/pods", "list", "v1beta1", api.NamespaceAll, "pods", "Pod", "", []string{"pods"}},
|
||||||
{"POST", "/api/v1beta1/pods", "create", "v1beta1", api.NamespaceDefault, "pods", "Pod", "", []string{"pods"}},
|
{"POST", "/api/v1beta1/pods", "create", "v1beta1", api.NamespaceDefault, "pods", "Pod", "", []string{"pods"}},
|
||||||
{"GET", "/api/v1beta1/pods/foo", "get", "v1beta1", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
|
{"GET", "/api/v1beta1/pods/foo", "get", "v1beta1", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||||
@ -105,7 +109,7 @@ func TestGetAPIRequestInfo(t *testing.T) {
|
|||||||
{"GET", "/api/v1beta1/proxy/pods/foo", "proxy", "v1beta1", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
|
{"GET", "/api/v1beta1/proxy/pods/foo", "proxy", "v1beta1", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||||
{"GET", "/api/v1beta1/redirect/pods/foo", "redirect", "v1beta1", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
|
{"GET", "/api/v1beta1/redirect/pods/foo", "redirect", "v1beta1", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||||
{"GET", "/api/v1beta1/watch/pods", "watch", "v1beta1", api.NamespaceAll, "pods", "Pod", "", []string{"pods"}},
|
{"GET", "/api/v1beta1/watch/pods", "watch", "v1beta1", api.NamespaceAll, "pods", "Pod", "", []string{"pods"}},
|
||||||
{"GET", "/api/v1beta1/watch/ns/other/pods", "watch", "v1beta1", "other", "pods", "Pod", "", []string{"pods"}},
|
{"GET", "/api/v1beta1/watch/namespaces/other/pods", "watch", "v1beta1", "other", "pods", "Pod", "", []string{"pods"}},
|
||||||
}
|
}
|
||||||
|
|
||||||
apiRequestInfoResolver := &APIRequestInfoResolver{util.NewStringSet("api"), latest.RESTMapper}
|
apiRequestInfoResolver := &APIRequestInfoResolver{util.NewStringSet("api"), latest.RESTMapper}
|
||||||
@ -141,11 +145,9 @@ func TestGetAPIRequestInfo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
errorCases := map[string]string{
|
errorCases := map[string]string{
|
||||||
"no resource path": "/",
|
"no resource path": "/",
|
||||||
"missing resource type": "/ns/other",
|
"just apiversion": "/api/v1beta1/",
|
||||||
"just apiversion": "/api/v1beta1/",
|
"apiversion with no resource": "/api/v1beta1/",
|
||||||
"apiversion with no resource": "/api/v1beta1/",
|
|
||||||
"apiversion with just namespace": "/api/v1beta1/ns/other",
|
|
||||||
}
|
}
|
||||||
for k, v := range errorCases {
|
for k, v := range errorCases {
|
||||||
req, err := http.NewRequest("GET", v, nil)
|
req, err := http.NewRequest("GET", v, nil)
|
||||||
|
@ -294,7 +294,7 @@ func TestProxy(t *testing.T) {
|
|||||||
server *httptest.Server
|
server *httptest.Server
|
||||||
proxyTestPattern string
|
proxyTestPattern string
|
||||||
}{
|
}{
|
||||||
{namespaceServer, "/prefix/version/proxy/ns/" + item.reqNamespace + "/foo/id" + item.path},
|
{namespaceServer, "/prefix/version/proxy/namespaces/" + item.reqNamespace + "/foo/id" + item.path},
|
||||||
{legacyNamespaceServer, "/prefix/version/proxy/foo/id" + item.path + "?namespace=" + item.reqNamespace},
|
{legacyNamespaceServer, "/prefix/version/proxy/foo/id" + item.path + "?namespace=" + item.reqNamespace},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +107,7 @@ func TestRedirectWithNamespaces(t *testing.T) {
|
|||||||
for _, item := range table {
|
for _, item := range table {
|
||||||
simpleStorage.errors["resourceLocation"] = item.err
|
simpleStorage.errors["resourceLocation"] = item.err
|
||||||
simpleStorage.resourceLocation = item.id
|
simpleStorage.resourceLocation = item.id
|
||||||
resp, err := client.Get(server.URL + "/prefix/version/redirect/ns/other/foo/" + item.id)
|
resp, err := client.Get(server.URL + "/prefix/version/redirect/namespaces/other/foo/" + item.id)
|
||||||
if resp == nil {
|
if resp == nil {
|
||||||
t.Fatalf("Unexpected nil resp")
|
t.Fatalf("Unexpected nil resp")
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@ type RESTHandler struct {
|
|||||||
func (h *RESTHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
func (h *RESTHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
requestInfo, err := h.apiRequestInfoResolver.GetAPIRequestInfo(req)
|
requestInfo, err := h.apiRequestInfoResolver.GetAPIRequestInfo(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
glog.Errorf("Unable to handle request %s %s %v", requestInfo.Namespace, requestInfo.Kind, err)
|
||||||
notFound(w, req)
|
notFound(w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@ type Interface interface {
|
|||||||
LimitRangesNamespacer
|
LimitRangesNamespacer
|
||||||
ResourceQuotasNamespacer
|
ResourceQuotasNamespacer
|
||||||
ResourceQuotaUsagesNamespacer
|
ResourceQuotaUsagesNamespacer
|
||||||
|
NamespacesInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) ReplicationControllers(namespace string) ReplicationControllerInterface {
|
func (c *Client) ReplicationControllers(namespace string) ReplicationControllerInterface {
|
||||||
@ -78,6 +79,10 @@ func (c *Client) ResourceQuotaUsages(namespace string) ResourceQuotaUsageInterfa
|
|||||||
return newResourceQuotaUsages(c, namespace)
|
return newResourceQuotaUsages(c, namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) Namespaces() NamespaceInterface {
|
||||||
|
return newNamespaces(c)
|
||||||
|
}
|
||||||
|
|
||||||
// VersionInterface has a method to retrieve the server version.
|
// VersionInterface has a method to retrieve the server version.
|
||||||
type VersionInterface interface {
|
type VersionInterface interface {
|
||||||
ServerVersion() (*version.Info, error)
|
ServerVersion() (*version.Info, error)
|
||||||
|
@ -44,6 +44,7 @@ type Fake struct {
|
|||||||
EventsList api.EventList
|
EventsList api.EventList
|
||||||
LimitRangesList api.LimitRangeList
|
LimitRangesList api.LimitRangeList
|
||||||
ResourceQuotasList api.ResourceQuotaList
|
ResourceQuotasList api.ResourceQuotaList
|
||||||
|
NamespacesList api.NamespaceList
|
||||||
Err error
|
Err error
|
||||||
Watch watch.Interface
|
Watch watch.Interface
|
||||||
}
|
}
|
||||||
@ -84,6 +85,10 @@ func (c *Fake) Services(namespace string) ServiceInterface {
|
|||||||
return &FakeServices{Fake: c, Namespace: namespace}
|
return &FakeServices{Fake: c, Namespace: namespace}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Fake) Namespaces() NamespaceInterface {
|
||||||
|
return &FakeNamespaces{Fake: c}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Fake) ServerVersion() (*version.Info, error) {
|
func (c *Fake) ServerVersion() (*version.Info, error) {
|
||||||
c.Actions = append(c.Actions, FakeAction{Action: "get-version", Value: nil})
|
c.Actions = append(c.Actions, FakeAction{Action: "get-version", Value: nil})
|
||||||
versionInfo := version.Get()
|
versionInfo := version.Get()
|
||||||
|
53
pkg/client/fake_namespaces.go
Normal file
53
pkg/client/fake_namespaces.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FakeNamespaces implements NamespacesInterface. Meant to be embedded into a struct to get a default
|
||||||
|
// implementation. This makes faking out just the methods you want to test easier.
|
||||||
|
type FakeNamespaces struct {
|
||||||
|
Fake *Fake
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *FakeNamespaces) List(selector labels.Selector) (*api.NamespaceList, error) {
|
||||||
|
c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "list-namespaces"})
|
||||||
|
return api.Scheme.CopyOrDie(&c.Fake.NamespacesList).(*api.NamespaceList), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *FakeNamespaces) Get(name string) (*api.Namespace, error) {
|
||||||
|
c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "get-namespace", Value: name})
|
||||||
|
return &api.Namespace{ObjectMeta: api.ObjectMeta{Name: name}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *FakeNamespaces) Delete(name string) error {
|
||||||
|
c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "delete-namespace", Value: name})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *FakeNamespaces) Create(namespace *api.Namespace) (*api.Namespace, error) {
|
||||||
|
c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "create-namespace"})
|
||||||
|
return &api.Namespace{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *FakeNamespaces) Update(namespace *api.Namespace) (*api.Namespace, error) {
|
||||||
|
c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "update-namespace", Value: namespace.Name})
|
||||||
|
return &api.Namespace{}, nil
|
||||||
|
}
|
88
pkg/client/namespaces.go
Normal file
88
pkg/client/namespaces.go
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NamespacesInterface interface {
|
||||||
|
Namespaces() NamespaceInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
type NamespaceInterface interface {
|
||||||
|
Create(item *api.Namespace) (*api.Namespace, error)
|
||||||
|
Get(name string) (result *api.Namespace, err error)
|
||||||
|
List(selector labels.Selector) (*api.NamespaceList, error)
|
||||||
|
Delete(name string) error
|
||||||
|
Update(item *api.Namespace) (*api.Namespace, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// namespaces implements NamespacesInterface
|
||||||
|
type namespaces struct {
|
||||||
|
r *Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// newNamespaces returns a namespaces object.
|
||||||
|
func newNamespaces(c *Client) *namespaces {
|
||||||
|
return &namespaces{r: c}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create creates a new namespace.
|
||||||
|
func (c *namespaces) Create(namespace *api.Namespace) (*api.Namespace, error) {
|
||||||
|
result := &api.Namespace{}
|
||||||
|
err := c.r.Post().Resource("namespaces").Body(namespace).Do().Into(result)
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// List lists all the namespaces in the cluster.
|
||||||
|
func (c *namespaces) List(selector labels.Selector) (*api.NamespaceList, error) {
|
||||||
|
result := &api.NamespaceList{}
|
||||||
|
err := c.r.Get().Resource("namespaces").SelectorParam("labels", selector).Do().Into(result)
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update takes the representation of a namespace to update. Returns the server's representation of the namespace, and an error, if it occurs.
|
||||||
|
func (c *namespaces) Update(namespace *api.Namespace) (result *api.Namespace, err error) {
|
||||||
|
result = &api.Namespace{}
|
||||||
|
if len(namespace.ResourceVersion) == 0 {
|
||||||
|
err = fmt.Errorf("invalid update object, missing resource version: %v", namespace)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = c.r.Put().Resource("namespaces").Name(namespace.Name).Body(namespace).Do().Into(result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get gets an existing namespace
|
||||||
|
func (c *namespaces) Get(name string) (*api.Namespace, error) {
|
||||||
|
if len(name) == 0 {
|
||||||
|
return nil, errors.New("name is required parameter to Get")
|
||||||
|
}
|
||||||
|
|
||||||
|
result := &api.Namespace{}
|
||||||
|
err := c.r.Get().Resource("namespaces").Name(name).Do().Into(result)
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes an existing namespace.
|
||||||
|
func (c *namespaces) Delete(name string) error {
|
||||||
|
return c.r.Delete().Resource("namespaces").Name(name).Do().Error()
|
||||||
|
}
|
134
pkg/client/namespaces_test.go
Normal file
134
pkg/client/namespaces_test.go
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNamespaceCreate(t *testing.T) {
|
||||||
|
// we create a namespace relative to another namespace
|
||||||
|
namespace := &api.Namespace{
|
||||||
|
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||||
|
}
|
||||||
|
c := &testClient{
|
||||||
|
Request: testRequest{
|
||||||
|
Method: "POST",
|
||||||
|
Path: "/namespaces",
|
||||||
|
Body: namespace,
|
||||||
|
},
|
||||||
|
Response: Response{StatusCode: 200, Body: namespace},
|
||||||
|
}
|
||||||
|
|
||||||
|
// from the source ns, provision a new global namespace "foo"
|
||||||
|
response, err := c.Setup().Namespaces().Create(namespace)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%#v should be nil.", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, a := response.Name, namespace.Name; e != a {
|
||||||
|
t.Errorf("%#v != %#v.", e, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNamespaceGet(t *testing.T) {
|
||||||
|
namespace := &api.Namespace{
|
||||||
|
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||||
|
}
|
||||||
|
c := &testClient{
|
||||||
|
Request: testRequest{
|
||||||
|
Method: "GET",
|
||||||
|
Path: "/namespaces/foo",
|
||||||
|
Body: nil,
|
||||||
|
},
|
||||||
|
Response: Response{StatusCode: 200, Body: namespace},
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := c.Setup().Namespaces().Get("foo")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%#v should be nil.", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, r := response.Name, namespace.Name; e != r {
|
||||||
|
t.Errorf("%#v != %#v.", e, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNamespaceList(t *testing.T) {
|
||||||
|
namespaceList := &api.NamespaceList{
|
||||||
|
Items: []api.Namespace{
|
||||||
|
{
|
||||||
|
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c := &testClient{
|
||||||
|
Request: testRequest{
|
||||||
|
Method: "GET",
|
||||||
|
Path: "/ns",
|
||||||
|
Body: nil,
|
||||||
|
},
|
||||||
|
Response: Response{StatusCode: 200, Body: namespaceList},
|
||||||
|
}
|
||||||
|
response, err := c.Setup().Namespaces().List(labels.Everything())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%#v should be nil.", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(response.Items) != 1 {
|
||||||
|
t.Errorf("%#v response.Items should have len 1.", response.Items)
|
||||||
|
}
|
||||||
|
|
||||||
|
responseNamespace := response.Items[0]
|
||||||
|
if e, r := responseNamespace.Name, "foo"; e != r {
|
||||||
|
t.Errorf("%#v != %#v.", e, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNamespaceUpdate(t *testing.T) {
|
||||||
|
requestNamespace := &api.Namespace{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
ResourceVersion: "1",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"name": "baz",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c := &testClient{
|
||||||
|
Request: testRequest{Method: "PUT", Path: "/namespaces/foo"},
|
||||||
|
Response: Response{StatusCode: 200, Body: requestNamespace},
|
||||||
|
}
|
||||||
|
receivedNamespace, err := c.Setup().Namespaces().Update(requestNamespace)
|
||||||
|
c.Validate(t, receivedNamespace, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNamespaceDelete(t *testing.T) {
|
||||||
|
c := &testClient{
|
||||||
|
Request: testRequest{Method: "DELETE", Path: "/namespaces/foo"},
|
||||||
|
Response: Response{StatusCode: 200},
|
||||||
|
}
|
||||||
|
err := c.Setup().Namespaces().Delete("foo")
|
||||||
|
c.Validate(t, nil, err)
|
||||||
|
}
|
@ -298,7 +298,7 @@ func (r *Request) Body(obj interface{}) *Request {
|
|||||||
func (r *Request) finalURL() string {
|
func (r *Request) finalURL() string {
|
||||||
p := r.path
|
p := r.path
|
||||||
if r.namespaceSet && !r.namespaceInQuery && len(r.namespace) > 0 {
|
if r.namespaceSet && !r.namespaceInQuery && len(r.namespace) > 0 {
|
||||||
p = path.Join(p, "ns", r.namespace)
|
p = path.Join(p, "namespaces", r.namespace)
|
||||||
}
|
}
|
||||||
if len(r.resource) != 0 {
|
if len(r.resource) != 0 {
|
||||||
resource := r.resource
|
resource := r.resource
|
||||||
|
@ -112,7 +112,7 @@ func TestRequestSetsNamespace(t *testing.T) {
|
|||||||
Path: "/",
|
Path: "/",
|
||||||
},
|
},
|
||||||
}).Namespace("foo")
|
}).Namespace("foo")
|
||||||
if s := r.finalURL(); s != "ns/foo" {
|
if s := r.finalURL(); s != "namespaces/foo" {
|
||||||
t.Errorf("namespace should be in path: %s", s)
|
t.Errorf("namespace should be in path: %s", s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,7 +122,7 @@ func TestRequestOrdersNamespaceInPath(t *testing.T) {
|
|||||||
baseURL: &url.URL{},
|
baseURL: &url.URL{},
|
||||||
path: "/test/",
|
path: "/test/",
|
||||||
}).Name("bar").Resource("baz").Namespace("foo")
|
}).Name("bar").Resource("baz").Namespace("foo")
|
||||||
if s := r.finalURL(); s != "/test/ns/foo/baz/bar" {
|
if s := r.finalURL(); s != "/test/namespaces/foo/baz/bar" {
|
||||||
t.Errorf("namespace should be in order in path: %s", s)
|
t.Errorf("namespace should be in order in path: %s", s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,7 +123,7 @@ var (
|
|||||||
// update of the image is performed.
|
// update of the image is performed.
|
||||||
func Update(ctx api.Context, name string, client client.Interface, updatePeriod time.Duration, imageName string) error {
|
func Update(ctx api.Context, name string, client client.Interface, updatePeriod time.Duration, imageName string) error {
|
||||||
// TODO ctx is not needed as input to this function, should just be 'namespace'
|
// TODO ctx is not needed as input to this function, should just be 'namespace'
|
||||||
controller, err := client.ReplicationControllers(api.Namespace(ctx)).Get(name)
|
controller, err := client.ReplicationControllers(api.NamespaceValue(ctx)).Get(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -138,7 +138,7 @@ func Update(ctx api.Context, name string, client client.Interface, updatePeriod
|
|||||||
|
|
||||||
s := labels.Set(controller.Spec.Selector).AsSelector()
|
s := labels.Set(controller.Spec.Selector).AsSelector()
|
||||||
|
|
||||||
podList, err := client.Pods(api.Namespace(ctx)).List(s)
|
podList, err := client.Pods(api.NamespaceValue(ctx)).List(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -156,7 +156,7 @@ func Update(ctx api.Context, name string, client client.Interface, updatePeriod
|
|||||||
time.Sleep(updatePeriod)
|
time.Sleep(updatePeriod)
|
||||||
}
|
}
|
||||||
return wait.Poll(updatePollInterval, updatePollTimeout, func() (bool, error) {
|
return wait.Poll(updatePollInterval, updatePollTimeout, func() (bool, error) {
|
||||||
podList, err := client.Pods(api.Namespace(ctx)).List(s)
|
podList, err := client.Pods(api.NamespaceValue(ctx)).List(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@ -172,12 +172,12 @@ func StopController(ctx api.Context, name string, client client.Interface) error
|
|||||||
// ResizeController resizes a controller named 'name' by setting replicas to 'replicas'.
|
// ResizeController resizes a controller named 'name' by setting replicas to 'replicas'.
|
||||||
func ResizeController(ctx api.Context, name string, replicas int, client client.Interface) error {
|
func ResizeController(ctx api.Context, name string, replicas int, client client.Interface) error {
|
||||||
// TODO ctx is not needed, and should just be a namespace
|
// TODO ctx is not needed, and should just be a namespace
|
||||||
controller, err := client.ReplicationControllers(api.Namespace(ctx)).Get(name)
|
controller, err := client.ReplicationControllers(api.NamespaceValue(ctx)).Get(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
controller.Spec.Replicas = replicas
|
controller.Spec.Replicas = replicas
|
||||||
controllerOut, err := client.ReplicationControllers(api.Namespace(ctx)).Update(controller)
|
controllerOut, err := client.ReplicationControllers(api.NamespaceValue(ctx)).Update(controller)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -270,7 +270,7 @@ func RunController(ctx api.Context, image, name string, replicas int, client cli
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
controllerOut, err := client.ReplicationControllers(api.Namespace(ctx)).Create(controller)
|
controllerOut, err := client.ReplicationControllers(api.NamespaceValue(ctx)).Create(controller)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -310,7 +310,7 @@ func createService(ctx api.Context, name string, port int, client client.Interfa
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
svc, err := client.Services(api.Namespace(ctx)).Create(svc)
|
svc, err := client.Services(api.NamespaceValue(ctx)).Create(svc)
|
||||||
return svc, err
|
return svc, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,12 +318,12 @@ func createService(ctx api.Context, name string, port int, client client.Interfa
|
|||||||
// already be stopped.
|
// already be stopped.
|
||||||
func DeleteController(ctx api.Context, name string, client client.Interface) error {
|
func DeleteController(ctx api.Context, name string, client client.Interface) error {
|
||||||
// TODO remove ctx in favor of just namespace string
|
// TODO remove ctx in favor of just namespace string
|
||||||
controller, err := client.ReplicationControllers(api.Namespace(ctx)).Get(name)
|
controller, err := client.ReplicationControllers(api.NamespaceValue(ctx)).Get(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if controller.Spec.Replicas != 0 {
|
if controller.Spec.Replicas != 0 {
|
||||||
return fmt.Errorf("controller has non-zero replicas (%d), please stop it first", controller.Spec.Replicas)
|
return fmt.Errorf("controller has non-zero replicas (%d), please stop it first", controller.Spec.Replicas)
|
||||||
}
|
}
|
||||||
return client.ReplicationControllers(api.Namespace(ctx)).Delete(name)
|
return client.ReplicationControllers(api.NamespaceValue(ctx)).Delete(name)
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ func TestCreateObject(t *testing.T) {
|
|||||||
Codec: codec,
|
Codec: codec,
|
||||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
switch p, m := req.URL.Path, req.Method; {
|
switch p, m := req.URL.Path, req.Method; {
|
||||||
case p == "/ns/test/pods" && m == "POST":
|
case p == "/namespaces/test/pods" && m == "POST":
|
||||||
return &http.Response{StatusCode: 201, Body: objBody(codec, &pods.Items[0])}, nil
|
return &http.Response{StatusCode: 201, Body: objBody(codec, &pods.Items[0])}, nil
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
@ -64,9 +64,9 @@ func TestCreateMultipleObject(t *testing.T) {
|
|||||||
Codec: codec,
|
Codec: codec,
|
||||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
switch p, m := req.URL.Path, req.Method; {
|
switch p, m := req.URL.Path, req.Method; {
|
||||||
case p == "/ns/test/pods" && m == "POST":
|
case p == "/namespaces/test/pods" && m == "POST":
|
||||||
return &http.Response{StatusCode: 201, Body: objBody(codec, &pods.Items[0])}, nil
|
return &http.Response{StatusCode: 201, Body: objBody(codec, &pods.Items[0])}, nil
|
||||||
case p == "/ns/test/services" && m == "POST":
|
case p == "/namespaces/test/services" && m == "POST":
|
||||||
return &http.Response{StatusCode: 201, Body: objBody(codec, &svc.Items[0])}, nil
|
return &http.Response{StatusCode: 201, Body: objBody(codec, &svc.Items[0])}, nil
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
@ -98,11 +98,11 @@ func TestCreateDirectory(t *testing.T) {
|
|||||||
Codec: codec,
|
Codec: codec,
|
||||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
switch p, m := req.URL.Path, req.Method; {
|
switch p, m := req.URL.Path, req.Method; {
|
||||||
case p == "/ns/test/pods" && m == "POST":
|
case p == "/namespaces/test/pods" && m == "POST":
|
||||||
return &http.Response{StatusCode: 201, Body: objBody(codec, &pods.Items[0])}, nil
|
return &http.Response{StatusCode: 201, Body: objBody(codec, &pods.Items[0])}, nil
|
||||||
case p == "/ns/test/services" && m == "POST":
|
case p == "/namespaces/test/services" && m == "POST":
|
||||||
return &http.Response{StatusCode: 201, Body: objBody(codec, &svc.Items[0])}, nil
|
return &http.Response{StatusCode: 201, Body: objBody(codec, &svc.Items[0])}, nil
|
||||||
case p == "/ns/test/replicationcontrollers" && m == "POST":
|
case p == "/namespaces/test/replicationcontrollers" && m == "POST":
|
||||||
return &http.Response{StatusCode: 201, Body: objBody(codec, &svc.Items[0])}, nil
|
return &http.Response{StatusCode: 201, Body: objBody(codec, &svc.Items[0])}, nil
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
|
@ -35,7 +35,7 @@ func TestDeleteObject(t *testing.T) {
|
|||||||
Codec: codec,
|
Codec: codec,
|
||||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
switch p, m := req.URL.Path, req.Method; {
|
switch p, m := req.URL.Path, req.Method; {
|
||||||
case p == "/ns/test/pods/redis-master" && m == "DELETE":
|
case p == "/namespaces/test/pods/redis-master" && m == "DELETE":
|
||||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
|
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
@ -63,7 +63,7 @@ func TestDeleteObjectIgnoreNotFound(t *testing.T) {
|
|||||||
Codec: codec,
|
Codec: codec,
|
||||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
switch p, m := req.URL.Path, req.Method; {
|
switch p, m := req.URL.Path, req.Method; {
|
||||||
case p == "/ns/test/pods/redis-master" && m == "DELETE":
|
case p == "/namespaces/test/pods/redis-master" && m == "DELETE":
|
||||||
return &http.Response{StatusCode: 404, Body: stringBody("")}, nil
|
return &http.Response{StatusCode: 404, Body: stringBody("")}, nil
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
@ -90,7 +90,7 @@ func TestDeleteNoObjects(t *testing.T) {
|
|||||||
Codec: codec,
|
Codec: codec,
|
||||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
switch p, m := req.URL.Path, req.Method; {
|
switch p, m := req.URL.Path, req.Method; {
|
||||||
case p == "/ns/test/pods" && m == "GET":
|
case p == "/namespaces/test/pods" && m == "GET":
|
||||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &api.PodList{})}, nil
|
return &http.Response{StatusCode: 200, Body: objBody(codec, &api.PodList{})}, nil
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
@ -123,9 +123,9 @@ func TestDeleteMultipleObject(t *testing.T) {
|
|||||||
Codec: codec,
|
Codec: codec,
|
||||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
switch p, m := req.URL.Path, req.Method; {
|
switch p, m := req.URL.Path, req.Method; {
|
||||||
case p == "/ns/test/pods/redis-master" && m == "DELETE":
|
case p == "/namespaces/test/pods/redis-master" && m == "DELETE":
|
||||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
|
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
|
||||||
case p == "/ns/test/services/frontend" && m == "DELETE":
|
case p == "/namespaces/test/services/frontend" && m == "DELETE":
|
||||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil
|
return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
@ -155,9 +155,9 @@ func TestDeleteMultipleObjectIgnoreMissing(t *testing.T) {
|
|||||||
Codec: codec,
|
Codec: codec,
|
||||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
switch p, m := req.URL.Path, req.Method; {
|
switch p, m := req.URL.Path, req.Method; {
|
||||||
case p == "/ns/test/pods/redis-master" && m == "DELETE":
|
case p == "/namespaces/test/pods/redis-master" && m == "DELETE":
|
||||||
return &http.Response{StatusCode: 404, Body: stringBody("")}, nil
|
return &http.Response{StatusCode: 404, Body: stringBody("")}, nil
|
||||||
case p == "/ns/test/services/frontend" && m == "DELETE":
|
case p == "/namespaces/test/services/frontend" && m == "DELETE":
|
||||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil
|
return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
@ -187,11 +187,11 @@ func TestDeleteDirectory(t *testing.T) {
|
|||||||
Codec: codec,
|
Codec: codec,
|
||||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
switch p, m := req.URL.Path, req.Method; {
|
switch p, m := req.URL.Path, req.Method; {
|
||||||
case strings.HasPrefix(p, "/ns/test/pods/") && m == "DELETE":
|
case strings.HasPrefix(p, "/namespaces/test/pods/") && m == "DELETE":
|
||||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
|
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
|
||||||
case strings.HasPrefix(p, "/ns/test/services/") && m == "DELETE":
|
case strings.HasPrefix(p, "/namespaces/test/services/") && m == "DELETE":
|
||||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil
|
return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil
|
||||||
case strings.HasPrefix(p, "/ns/test/replicationcontrollers/") && m == "DELETE":
|
case strings.HasPrefix(p, "/namespaces/test/replicationcontrollers/") && m == "DELETE":
|
||||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil
|
return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
@ -220,19 +220,19 @@ func TestDeleteMultipleSelector(t *testing.T) {
|
|||||||
Codec: codec,
|
Codec: codec,
|
||||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
switch p, m := req.URL.Path, req.Method; {
|
switch p, m := req.URL.Path, req.Method; {
|
||||||
case p == "/ns/test/pods" && m == "GET":
|
case p == "/namespaces/test/pods" && m == "GET":
|
||||||
if req.URL.Query().Get("labels") != "a=b" {
|
if req.URL.Query().Get("labels") != "a=b" {
|
||||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
}
|
}
|
||||||
return &http.Response{StatusCode: 200, Body: objBody(codec, pods)}, nil
|
return &http.Response{StatusCode: 200, Body: objBody(codec, pods)}, nil
|
||||||
case p == "/ns/test/services" && m == "GET":
|
case p == "/namespaces/test/services" && m == "GET":
|
||||||
if req.URL.Query().Get("labels") != "a=b" {
|
if req.URL.Query().Get("labels") != "a=b" {
|
||||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
}
|
}
|
||||||
return &http.Response{StatusCode: 200, Body: objBody(codec, svc)}, nil
|
return &http.Response{StatusCode: 200, Body: objBody(codec, svc)}, nil
|
||||||
case strings.HasPrefix(p, "/ns/test/pods/") && m == "DELETE":
|
case strings.HasPrefix(p, "/namespaces/test/pods/") && m == "DELETE":
|
||||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
|
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
|
||||||
case strings.HasPrefix(p, "/ns/test/services/") && m == "DELETE":
|
case strings.HasPrefix(p, "/namespaces/test/services/") && m == "DELETE":
|
||||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil
|
return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
|
@ -183,9 +183,9 @@ func TestGetMultipleTypeObjects(t *testing.T) {
|
|||||||
Codec: codec,
|
Codec: codec,
|
||||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
switch req.URL.Path {
|
switch req.URL.Path {
|
||||||
case "/ns/test/pods":
|
case "/namespaces/test/pods":
|
||||||
return &http.Response{StatusCode: 200, Body: objBody(codec, pods)}, nil
|
return &http.Response{StatusCode: 200, Body: objBody(codec, pods)}, nil
|
||||||
case "/ns/test/services":
|
case "/namespaces/test/services":
|
||||||
return &http.Response{StatusCode: 200, Body: objBody(codec, svc)}, nil
|
return &http.Response{StatusCode: 200, Body: objBody(codec, svc)}, nil
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
@ -219,9 +219,9 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) {
|
|||||||
Codec: codec,
|
Codec: codec,
|
||||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
switch req.URL.Path {
|
switch req.URL.Path {
|
||||||
case "/ns/test/pods":
|
case "/namespaces/test/pods":
|
||||||
return &http.Response{StatusCode: 200, Body: objBody(codec, pods)}, nil
|
return &http.Response{StatusCode: 200, Body: objBody(codec, pods)}, nil
|
||||||
case "/ns/test/services":
|
case "/namespaces/test/services":
|
||||||
return &http.Response{StatusCode: 200, Body: objBody(codec, svc)}, nil
|
return &http.Response{StatusCode: 200, Body: objBody(codec, svc)}, nil
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
@ -271,9 +271,9 @@ func TestGetMultipleTypeObjectsWithSelector(t *testing.T) {
|
|||||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
}
|
}
|
||||||
switch req.URL.Path {
|
switch req.URL.Path {
|
||||||
case "/ns/test/pods":
|
case "/namespaces/test/pods":
|
||||||
return &http.Response{StatusCode: 200, Body: objBody(codec, pods)}, nil
|
return &http.Response{StatusCode: 200, Body: objBody(codec, pods)}, nil
|
||||||
case "/ns/test/services":
|
case "/namespaces/test/services":
|
||||||
return &http.Response{StatusCode: 200, Body: objBody(codec, svc)}, nil
|
return &http.Response{StatusCode: 200, Body: objBody(codec, svc)}, nil
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
@ -359,9 +359,9 @@ func TestWatchSelector(t *testing.T) {
|
|||||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
}
|
}
|
||||||
switch req.URL.Path {
|
switch req.URL.Path {
|
||||||
case "/ns/test/pods":
|
case "/namespaces/test/pods":
|
||||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &api.PodList{Items: pods})}, nil
|
return &http.Response{StatusCode: 200, Body: objBody(codec, &api.PodList{Items: pods})}, nil
|
||||||
case "/watch/ns/test/pods":
|
case "/watch/namespaces/test/pods":
|
||||||
return &http.Response{StatusCode: 200, Body: watchBody(codec, events)}, nil
|
return &http.Response{StatusCode: 200, Body: watchBody(codec, events)}, nil
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
@ -398,9 +398,9 @@ func TestWatchResource(t *testing.T) {
|
|||||||
Codec: codec,
|
Codec: codec,
|
||||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
switch req.URL.Path {
|
switch req.URL.Path {
|
||||||
case "/ns/test/pods/foo":
|
case "/namespaces/test/pods/foo":
|
||||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods[0])}, nil
|
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods[0])}, nil
|
||||||
case "/watch/ns/test/pods/foo":
|
case "/watch/namespaces/test/pods/foo":
|
||||||
return &http.Response{StatusCode: 200, Body: watchBody(codec, events)}, nil
|
return &http.Response{StatusCode: 200, Body: watchBody(codec, events)}, nil
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
@ -436,9 +436,9 @@ func TestWatchOnlyResource(t *testing.T) {
|
|||||||
Codec: codec,
|
Codec: codec,
|
||||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
switch req.URL.Path {
|
switch req.URL.Path {
|
||||||
case "/ns/test/pods/foo":
|
case "/namespaces/test/pods/foo":
|
||||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods[0])}, nil
|
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods[0])}, nil
|
||||||
case "/watch/ns/test/pods/foo":
|
case "/watch/namespaces/test/pods/foo":
|
||||||
return &http.Response{StatusCode: 200, Body: watchBody(codec, events)}, nil
|
return &http.Response{StatusCode: 200, Body: watchBody(codec, events)}, nil
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
|
@ -49,9 +49,9 @@ func TestUpdateObject(t *testing.T) {
|
|||||||
Codec: codec,
|
Codec: codec,
|
||||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
switch p, m := req.URL.Path, req.Method; {
|
switch p, m := req.URL.Path, req.Method; {
|
||||||
case p == "/ns/test/pods/redis-master" && m == "GET":
|
case p == "/namespaces/test/pods/redis-master" && m == "GET":
|
||||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
|
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
|
||||||
case p == "/ns/test/pods/redis-master" && m == "PUT":
|
case p == "/namespaces/test/pods/redis-master" && m == "PUT":
|
||||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
|
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
@ -81,14 +81,14 @@ func TestUpdateMultipleObject(t *testing.T) {
|
|||||||
Codec: codec,
|
Codec: codec,
|
||||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
switch p, m := req.URL.Path, req.Method; {
|
switch p, m := req.URL.Path, req.Method; {
|
||||||
case p == "/ns/test/pods/redis-master" && m == "GET":
|
case p == "/namespaces/test/pods/redis-master" && m == "GET":
|
||||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
|
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
|
||||||
case p == "/ns/test/pods/redis-master" && m == "PUT":
|
case p == "/namespaces/test/pods/redis-master" && m == "PUT":
|
||||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
|
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
|
||||||
|
|
||||||
case p == "/ns/test/services/frontend" && m == "GET":
|
case p == "/namespaces/test/services/frontend" && m == "GET":
|
||||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil
|
return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil
|
||||||
case p == "/ns/test/services/frontend" && m == "PUT":
|
case p == "/namespaces/test/services/frontend" && m == "PUT":
|
||||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil
|
return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
@ -119,11 +119,11 @@ func TestUpdateDirectory(t *testing.T) {
|
|||||||
Codec: codec,
|
Codec: codec,
|
||||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
switch p, m := req.URL.Path, req.Method; {
|
switch p, m := req.URL.Path, req.Method; {
|
||||||
case strings.HasPrefix(p, "/ns/test/pods/") && (m == "GET" || m == "PUT"):
|
case strings.HasPrefix(p, "/namespaces/test/pods/") && (m == "GET" || m == "PUT"):
|
||||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
|
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
|
||||||
case strings.HasPrefix(p, "/ns/test/services/") && (m == "GET" || m == "PUT"):
|
case strings.HasPrefix(p, "/namespaces/test/services/") && (m == "GET" || m == "PUT"):
|
||||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil
|
return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil
|
||||||
case strings.HasPrefix(p, "/ns/test/replicationcontrollers/") && (m == "GET" || m == "PUT"):
|
case strings.HasPrefix(p, "/namespaces/test/replicationcontrollers/") && (m == "GET" || m == "PUT"):
|
||||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &rc.Items[0])}, nil
|
return &http.Response{StatusCode: 200, Body: objBody(codec, &rc.Items[0])}, nil
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
|
@ -304,7 +304,7 @@ func TestURLBuilderRequireNamespace(t *testing.T) {
|
|||||||
func TestResourceByName(t *testing.T) {
|
func TestResourceByName(t *testing.T) {
|
||||||
pods, _ := testData()
|
pods, _ := testData()
|
||||||
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
|
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
|
||||||
"/ns/test/pods/foo": runtime.EncodeOrDie(latest.Codec, &pods.Items[0]),
|
"/namespaces/test/pods/foo": runtime.EncodeOrDie(latest.Codec, &pods.Items[0]),
|
||||||
})).
|
})).
|
||||||
NamespaceParam("test")
|
NamespaceParam("test")
|
||||||
|
|
||||||
@ -337,7 +337,7 @@ func TestResourceByName(t *testing.T) {
|
|||||||
func TestResourceByNameAndEmptySelector(t *testing.T) {
|
func TestResourceByNameAndEmptySelector(t *testing.T) {
|
||||||
pods, _ := testData()
|
pods, _ := testData()
|
||||||
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
|
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
|
||||||
"/ns/test/pods/foo": runtime.EncodeOrDie(latest.Codec, &pods.Items[0]),
|
"/namespaces/test/pods/foo": runtime.EncodeOrDie(latest.Codec, &pods.Items[0]),
|
||||||
})).
|
})).
|
||||||
NamespaceParam("test").
|
NamespaceParam("test").
|
||||||
SelectorParam("").
|
SelectorParam("").
|
||||||
@ -364,8 +364,8 @@ func TestResourceByNameAndEmptySelector(t *testing.T) {
|
|||||||
func TestSelector(t *testing.T) {
|
func TestSelector(t *testing.T) {
|
||||||
pods, svc := testData()
|
pods, svc := testData()
|
||||||
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
|
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
|
||||||
"/ns/test/pods?labels=a%3Db": runtime.EncodeOrDie(latest.Codec, pods),
|
"/namespaces/test/pods?labels=a%3Db": runtime.EncodeOrDie(latest.Codec, pods),
|
||||||
"/ns/test/services?labels=a%3Db": runtime.EncodeOrDie(latest.Codec, svc),
|
"/namespaces/test/services?labels=a%3Db": runtime.EncodeOrDie(latest.Codec, svc),
|
||||||
})).
|
})).
|
||||||
SelectorParam("a=b").
|
SelectorParam("a=b").
|
||||||
NamespaceParam("test").
|
NamespaceParam("test").
|
||||||
@ -494,7 +494,7 @@ func TestSingularObject(t *testing.T) {
|
|||||||
func TestListObject(t *testing.T) {
|
func TestListObject(t *testing.T) {
|
||||||
pods, _ := testData()
|
pods, _ := testData()
|
||||||
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
|
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
|
||||||
"/ns/test/pods?labels=a%3Db": runtime.EncodeOrDie(latest.Codec, pods),
|
"/namespaces/test/pods?labels=a%3Db": runtime.EncodeOrDie(latest.Codec, pods),
|
||||||
})).
|
})).
|
||||||
SelectorParam("a=b").
|
SelectorParam("a=b").
|
||||||
NamespaceParam("test").
|
NamespaceParam("test").
|
||||||
@ -526,8 +526,8 @@ func TestListObject(t *testing.T) {
|
|||||||
func TestListObjectWithDifferentVersions(t *testing.T) {
|
func TestListObjectWithDifferentVersions(t *testing.T) {
|
||||||
pods, svc := testData()
|
pods, svc := testData()
|
||||||
obj, err := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
|
obj, err := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
|
||||||
"/ns/test/pods?labels=a%3Db": runtime.EncodeOrDie(latest.Codec, pods),
|
"/namespaces/test/pods?labels=a%3Db": runtime.EncodeOrDie(latest.Codec, pods),
|
||||||
"/ns/test/services?labels=a%3Db": runtime.EncodeOrDie(latest.Codec, svc),
|
"/namespaces/test/services?labels=a%3Db": runtime.EncodeOrDie(latest.Codec, svc),
|
||||||
})).
|
})).
|
||||||
SelectorParam("a=b").
|
SelectorParam("a=b").
|
||||||
NamespaceParam("test").
|
NamespaceParam("test").
|
||||||
@ -552,7 +552,7 @@ func TestListObjectWithDifferentVersions(t *testing.T) {
|
|||||||
func TestWatch(t *testing.T) {
|
func TestWatch(t *testing.T) {
|
||||||
pods, _ := testData()
|
pods, _ := testData()
|
||||||
w, err := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
|
w, err := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
|
||||||
"/watch/ns/test/pods/redis-master?resourceVersion=10": watchBody(watch.Event{
|
"/watch/namespaces/test/pods/redis-master?resourceVersion=10": watchBody(watch.Event{
|
||||||
Type: watch.Added,
|
Type: watch.Added,
|
||||||
Object: &pods.Items[0],
|
Object: &pods.Items[0],
|
||||||
}),
|
}),
|
||||||
@ -607,9 +607,9 @@ func TestLatest(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
|
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
|
||||||
"/ns/test/pods/foo": runtime.EncodeOrDie(latest.Codec, newPod),
|
"/namespaces/test/pods/foo": runtime.EncodeOrDie(latest.Codec, newPod),
|
||||||
"/ns/test/pods/bar": runtime.EncodeOrDie(latest.Codec, newPod2),
|
"/namespaces/test/pods/bar": runtime.EncodeOrDie(latest.Codec, newPod2),
|
||||||
"/ns/test/services/baz": runtime.EncodeOrDie(latest.Codec, newSvc),
|
"/namespaces/test/services/baz": runtime.EncodeOrDie(latest.Codec, newSvc),
|
||||||
})).
|
})).
|
||||||
NamespaceParam("other").Stream(r, "STDIN").Flatten().Latest()
|
NamespaceParam("other").Stream(r, "STDIN").Flatten().Latest()
|
||||||
|
|
||||||
|
@ -318,7 +318,7 @@ func TestHelperList(t *testing.T) {
|
|||||||
t.Errorf("unexpected method: %#v", req)
|
t.Errorf("unexpected method: %#v", req)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if req.URL.Path != "/ns/bar" {
|
if req.URL.Path != "/namespaces/bar" {
|
||||||
t.Errorf("url doesn't contain name: %#v", req.URL)
|
t.Errorf("url doesn't contain name: %#v", req.URL)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -224,6 +224,7 @@ var statusColumns = []string{"STATUS"}
|
|||||||
var eventColumns = []string{"TIME", "NAME", "KIND", "SUBOBJECT", "REASON", "SOURCE", "MESSAGE"}
|
var eventColumns = []string{"TIME", "NAME", "KIND", "SUBOBJECT", "REASON", "SOURCE", "MESSAGE"}
|
||||||
var limitRangeColumns = []string{"NAME"}
|
var limitRangeColumns = []string{"NAME"}
|
||||||
var resourceQuotaColumns = []string{"NAME"}
|
var resourceQuotaColumns = []string{"NAME"}
|
||||||
|
var namespaceColumns = []string{"NAME", "LABELS"}
|
||||||
|
|
||||||
// addDefaultHandlers adds print handlers for default Kubernetes types.
|
// addDefaultHandlers adds print handlers for default Kubernetes types.
|
||||||
func (h *HumanReadablePrinter) addDefaultHandlers() {
|
func (h *HumanReadablePrinter) addDefaultHandlers() {
|
||||||
@ -243,6 +244,8 @@ func (h *HumanReadablePrinter) addDefaultHandlers() {
|
|||||||
h.Handler(limitRangeColumns, printLimitRangeList)
|
h.Handler(limitRangeColumns, printLimitRangeList)
|
||||||
h.Handler(resourceQuotaColumns, printResourceQuota)
|
h.Handler(resourceQuotaColumns, printResourceQuota)
|
||||||
h.Handler(resourceQuotaColumns, printResourceQuotaList)
|
h.Handler(resourceQuotaColumns, printResourceQuotaList)
|
||||||
|
h.Handler(namespaceColumns, printNamespace)
|
||||||
|
h.Handler(namespaceColumns, printNamespaceList)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HumanReadablePrinter) unknown(data []byte, w io.Writer) error {
|
func (h *HumanReadablePrinter) unknown(data []byte, w io.Writer) error {
|
||||||
@ -366,6 +369,20 @@ func printEndpoints(endpoint *api.Endpoints, w io.Writer) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func printNamespace(item *api.Namespace, w io.Writer) error {
|
||||||
|
_, err := fmt.Fprintf(w, "%s\t%s\n", item.Name, formatLabels(item.Labels))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func printNamespaceList(list *api.NamespaceList, w io.Writer) error {
|
||||||
|
for _, item := range list.Items {
|
||||||
|
if err := printNamespace(&item, w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func printMinion(minion *api.Node, w io.Writer) error {
|
func printMinion(minion *api.Node, w io.Writer) error {
|
||||||
conditionMap := make(map[api.NodeConditionKind]*api.NodeCondition)
|
conditionMap := make(map[api.NodeConditionKind]*api.NodeCondition)
|
||||||
NodeAllConditions := []api.NodeConditionKind{api.NodeReady, api.NodeReachable}
|
NodeAllConditions := []api.NodeConditionKind{api.NodeReady, api.NodeReachable}
|
||||||
|
@ -50,6 +50,7 @@ import (
|
|||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/limitrange"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/limitrange"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/minion"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/minion"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/namespace"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/resourcequota"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/resourcequota"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/resourcequotausage"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/resourcequotausage"
|
||||||
@ -122,6 +123,7 @@ type Master struct {
|
|||||||
eventRegistry generic.Registry
|
eventRegistry generic.Registry
|
||||||
limitRangeRegistry generic.Registry
|
limitRangeRegistry generic.Registry
|
||||||
resourceQuotaRegistry resourcequota.Registry
|
resourceQuotaRegistry resourcequota.Registry
|
||||||
|
namespaceRegistry generic.Registry
|
||||||
storage map[string]apiserver.RESTStorage
|
storage map[string]apiserver.RESTStorage
|
||||||
client *client.Client
|
client *client.Client
|
||||||
portalNet *net.IPNet
|
portalNet *net.IPNet
|
||||||
@ -279,6 +281,7 @@ func New(c *Config) *Master {
|
|||||||
endpointRegistry: etcd.NewRegistry(c.EtcdHelper, nil),
|
endpointRegistry: etcd.NewRegistry(c.EtcdHelper, nil),
|
||||||
bindingRegistry: etcd.NewRegistry(c.EtcdHelper, boundPodFactory),
|
bindingRegistry: etcd.NewRegistry(c.EtcdHelper, boundPodFactory),
|
||||||
eventRegistry: event.NewEtcdRegistry(c.EtcdHelper, uint64(c.EventTTL.Seconds())),
|
eventRegistry: event.NewEtcdRegistry(c.EtcdHelper, uint64(c.EventTTL.Seconds())),
|
||||||
|
namespaceRegistry: namespace.NewEtcdRegistry(c.EtcdHelper),
|
||||||
minionRegistry: minionRegistry,
|
minionRegistry: minionRegistry,
|
||||||
limitRangeRegistry: limitrange.NewEtcdRegistry(c.EtcdHelper),
|
limitRangeRegistry: limitrange.NewEtcdRegistry(c.EtcdHelper),
|
||||||
resourceQuotaRegistry: resourcequota.NewEtcdRegistry(c.EtcdHelper),
|
resourceQuotaRegistry: resourcequota.NewEtcdRegistry(c.EtcdHelper),
|
||||||
@ -400,6 +403,7 @@ func (m *Master) init(c *Config) {
|
|||||||
"limitRanges": limitrange.NewREST(m.limitRangeRegistry),
|
"limitRanges": limitrange.NewREST(m.limitRangeRegistry),
|
||||||
"resourceQuotas": resourcequota.NewREST(m.resourceQuotaRegistry),
|
"resourceQuotas": resourcequota.NewREST(m.resourceQuotaRegistry),
|
||||||
"resourceQuotaUsages": resourcequotausage.NewREST(m.resourceQuotaRegistry),
|
"resourceQuotaUsages": resourcequotausage.NewREST(m.resourceQuotaRegistry),
|
||||||
|
"namespaces": namespace.NewREST(m.namespaceRegistry),
|
||||||
}
|
}
|
||||||
|
|
||||||
apiVersions := []string{"v1beta1", "v1beta2"}
|
apiVersions := []string{"v1beta1", "v1beta2"}
|
||||||
|
@ -34,6 +34,9 @@ func (m *Master) serviceWriterLoop(stop chan struct{}) {
|
|||||||
// TODO: when it becomes possible to change this stuff,
|
// TODO: when it becomes possible to change this stuff,
|
||||||
// stop polling and start watching.
|
// stop polling and start watching.
|
||||||
// TODO: add endpoints of all replicas, not just the elected master.
|
// TODO: add endpoints of all replicas, not just the elected master.
|
||||||
|
if err := m.createMasterNamespaceIfNeeded(api.NamespaceDefault); err != nil {
|
||||||
|
glog.Errorf("Can't create master namespace: %v", err)
|
||||||
|
}
|
||||||
if m.serviceReadWriteIP != nil {
|
if m.serviceReadWriteIP != nil {
|
||||||
if err := m.createMasterServiceIfNeeded("kubernetes", m.serviceReadWriteIP, m.serviceReadWritePort); err != nil {
|
if err := m.createMasterServiceIfNeeded("kubernetes", m.serviceReadWriteIP, m.serviceReadWritePort); err != nil {
|
||||||
glog.Errorf("Can't create rw service: %v", err)
|
glog.Errorf("Can't create rw service: %v", err)
|
||||||
@ -56,6 +59,9 @@ func (m *Master) roServiceWriterLoop(stop chan struct{}) {
|
|||||||
// Update service & endpoint records.
|
// Update service & endpoint records.
|
||||||
// TODO: when it becomes possible to change this stuff,
|
// TODO: when it becomes possible to change this stuff,
|
||||||
// stop polling and start watching.
|
// stop polling and start watching.
|
||||||
|
if err := m.createMasterNamespaceIfNeeded(api.NamespaceDefault); err != nil {
|
||||||
|
glog.Errorf("Can't create master namespace: %v", err)
|
||||||
|
}
|
||||||
if m.serviceReadOnlyIP != nil {
|
if m.serviceReadOnlyIP != nil {
|
||||||
if err := m.createMasterServiceIfNeeded("kubernetes-ro", m.serviceReadOnlyIP, m.serviceReadOnlyPort); err != nil {
|
if err := m.createMasterServiceIfNeeded("kubernetes-ro", m.serviceReadOnlyIP, m.serviceReadOnlyPort); err != nil {
|
||||||
glog.Errorf("Can't create ro service: %v", err)
|
glog.Errorf("Can't create ro service: %v", err)
|
||||||
@ -73,6 +79,30 @@ func (m *Master) roServiceWriterLoop(stop chan struct{}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// createMasterNamespaceIfNeeded will create the namespace that contains the master services if it doesn't already exist
|
||||||
|
func (m *Master) createMasterNamespaceIfNeeded(ns string) error {
|
||||||
|
ctx := api.NewContext()
|
||||||
|
if _, err := m.namespaceRegistry.Get(ctx, api.NamespaceDefault); err == nil {
|
||||||
|
// the namespace already exists
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
namespace := &api.Namespace{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: ns,
|
||||||
|
Namespace: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c, err := m.storage["namespaces"].(apiserver.RESTCreater).Create(ctx, namespace)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
resp := <-c
|
||||||
|
if _, ok := resp.Object.(*api.Service); ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("unexpected response %#v", resp)
|
||||||
|
}
|
||||||
|
|
||||||
// createMasterServiceIfNeeded will create the specified service if it
|
// createMasterServiceIfNeeded will create the specified service if it
|
||||||
// doesn't already exist.
|
// doesn't already exist.
|
||||||
func (m *Master) createMasterServiceIfNeeded(serviceName string, serviceIP net.IP, servicePort int) error {
|
func (m *Master) createMasterServiceIfNeeded(serviceName string, serviceIP net.IP, servicePort int) error {
|
||||||
|
@ -31,6 +31,8 @@ import (
|
|||||||
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/admit"
|
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/admit"
|
||||||
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/deny"
|
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/deny"
|
||||||
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/limitranger"
|
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/limitranger"
|
||||||
|
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/namespace/autoprovision"
|
||||||
|
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/namespace/exists"
|
||||||
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/resourcedefaults"
|
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/resourcedefaults"
|
||||||
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/resourcequota"
|
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/resourcequota"
|
||||||
)
|
)
|
||||||
|
@ -47,7 +47,7 @@ func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan apiserver.RE
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("invalid object type")
|
return nil, fmt.Errorf("invalid object type")
|
||||||
}
|
}
|
||||||
if api.Namespace(ctx) != "" {
|
if api.NamespaceValue(ctx) != "" {
|
||||||
if !api.ValidNamespace(ctx, &event.ObjectMeta) {
|
if !api.ValidNamespace(ctx, &event.ObjectMeta) {
|
||||||
return nil, errors.NewConflict("event", event.Namespace, fmt.Errorf("event.namespace does not match the provided context"))
|
return nil, errors.NewConflict("event", event.Namespace, fmt.Errorf("event.namespace does not match the provided context"))
|
||||||
}
|
}
|
||||||
@ -72,7 +72,7 @@ func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RE
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("not an event object: %#v", obj)
|
return nil, fmt.Errorf("not an event object: %#v", obj)
|
||||||
}
|
}
|
||||||
if api.Namespace(ctx) != "" {
|
if api.NamespaceValue(ctx) != "" {
|
||||||
if !api.ValidNamespace(ctx, &event.ObjectMeta) {
|
if !api.ValidNamespace(ctx, &event.ObjectMeta) {
|
||||||
return nil, errors.NewConflict("event", event.Namespace, fmt.Errorf("event.namespace does not match the provided context"))
|
return nil, errors.NewConflict("event", event.Namespace, fmt.Errorf("event.namespace does not match the provided context"))
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,7 @@ func TestRESTCreate(t *testing.T) {
|
|||||||
c, err := rest.Create(item.ctx, item.event)
|
c, err := rest.Create(item.ctx, item.event)
|
||||||
if !item.valid {
|
if !item.valid {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
ctxNS := api.Namespace(item.ctx)
|
ctxNS := api.NamespaceValue(item.ctx)
|
||||||
t.Errorf("unexpected non-error for %v (%v, %v)", item.event.Name, ctxNS, item.event.Namespace)
|
t.Errorf("unexpected non-error for %v (%v, %v)", item.event.Name, ctxNS, item.event.Namespace)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
|
19
pkg/registry/namespace/doc.go
Normal file
19
pkg/registry/namespace/doc.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package namespace provides Registry interface and it's REST
|
||||||
|
// implementation for storing Namespace api objects.
|
||||||
|
package namespace
|
48
pkg/registry/namespace/registry.go
Normal file
48
pkg/registry/namespace/registry.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package namespace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
|
||||||
|
etcdgeneric "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic/etcd"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
// registry implements custom changes to generic.Etcd for Namespace storage
|
||||||
|
type registry struct {
|
||||||
|
*etcdgeneric.Etcd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEtcdRegistry returns a registry which will store Namespace objects in the given EtcdHelper.
|
||||||
|
func NewEtcdRegistry(h tools.EtcdHelper) generic.Registry {
|
||||||
|
return registry{
|
||||||
|
Etcd: &etcdgeneric.Etcd{
|
||||||
|
NewFunc: func() runtime.Object { return &api.Namespace{} },
|
||||||
|
NewListFunc: func() runtime.Object { return &api.NamespaceList{} },
|
||||||
|
EndpointName: "namespaces",
|
||||||
|
KeyRootFunc: func(ctx api.Context) string {
|
||||||
|
return "/registry/namespaces"
|
||||||
|
},
|
||||||
|
KeyFunc: func(ctx api.Context, id string) (string, error) {
|
||||||
|
return "/registry/namespaces/" + id, nil
|
||||||
|
},
|
||||||
|
Helper: h,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
17
pkg/registry/namespace/registry_test.go
Normal file
17
pkg/registry/namespace/registry_test.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package namespace
|
134
pkg/registry/namespace/rest.go
Normal file
134
pkg/registry/namespace/rest.go
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package namespace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
kerrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
||||||
|
)
|
||||||
|
|
||||||
|
// REST provides the RESTStorage access patterns to work with Namespace objects.
|
||||||
|
type REST struct {
|
||||||
|
registry generic.Registry
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewREST returns a new REST. You must use a registry created by
|
||||||
|
// NewEtcdRegistry unless you're testing.
|
||||||
|
func NewREST(registry generic.Registry) *REST {
|
||||||
|
return &REST{
|
||||||
|
registry: registry,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create creates a Namespace object
|
||||||
|
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
|
||||||
|
namespace := obj.(*api.Namespace)
|
||||||
|
if err := rest.BeforeCreate(rest.Namespaces, ctx, obj); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
||||||
|
if err := rs.registry.Create(ctx, namespace.Name, namespace); err != nil {
|
||||||
|
err = rest.CheckGeneratedNameError(rest.Namespaces, err, namespace)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return rs.registry.Get(ctx, namespace.Name)
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update updates a Namespace object.
|
||||||
|
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
|
||||||
|
namespace, ok := obj.(*api.Namespace)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("not a namespace: %#v", obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
oldObj, err := rs.registry.Get(ctx, namespace.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
oldNamespace := oldObj.(*api.Namespace)
|
||||||
|
if errs := validation.ValidateNamespaceUpdate(oldNamespace, namespace); len(errs) > 0 {
|
||||||
|
return nil, kerrors.NewInvalid("namespace", namespace.Name, errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
||||||
|
err := rs.registry.Update(ctx, oldNamespace.Name, oldNamespace)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return rs.registry.Get(ctx, oldNamespace.Name)
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes the Namespace with the specified name
|
||||||
|
func (rs *REST) Delete(ctx api.Context, id string) (<-chan apiserver.RESTResult, error) {
|
||||||
|
obj, err := rs.registry.Get(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, ok := obj.(*api.Namespace)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid object type")
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
||||||
|
return &api.Status{Status: api.StatusSuccess}, rs.registry.Delete(ctx, id)
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *REST) Get(ctx api.Context, id string) (runtime.Object, error) {
|
||||||
|
obj, err := rs.registry.Get(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
namespace, ok := obj.(*api.Namespace)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid object type")
|
||||||
|
}
|
||||||
|
return namespace, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *REST) getAttrs(obj runtime.Object) (objLabels, objFields labels.Set, err error) {
|
||||||
|
return labels.Set{}, labels.Set{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *REST) List(ctx api.Context, label, field labels.Selector) (runtime.Object, error) {
|
||||||
|
return rs.registry.List(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *REST) Watch(ctx api.Context, label, field labels.Selector, resourceVersion string) (watch.Interface, error) {
|
||||||
|
return rs.registry.Watch(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs}, resourceVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new api.Namespace
|
||||||
|
func (*REST) New() runtime.Object {
|
||||||
|
return &api.Namespace{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*REST) NewList() runtime.Object {
|
||||||
|
return &api.NamespaceList{}
|
||||||
|
}
|
188
pkg/registry/namespace/rest_test.go
Normal file
188
pkg/registry/namespace/rest_test.go
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package namespace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testRegistry struct {
|
||||||
|
*registrytest.GenericRegistry
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTestREST() (testRegistry, *REST) {
|
||||||
|
reg := testRegistry{registrytest.NewGeneric(nil)}
|
||||||
|
return reg, NewREST(reg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testNamespace(name string) *api.Namespace {
|
||||||
|
return &api.Namespace{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRESTCreate(t *testing.T) {
|
||||||
|
table := []struct {
|
||||||
|
ctx api.Context
|
||||||
|
namespace *api.Namespace
|
||||||
|
valid bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
ctx: api.NewContext(),
|
||||||
|
namespace: testNamespace("foo"),
|
||||||
|
valid: true,
|
||||||
|
}, {
|
||||||
|
ctx: api.NewContext(),
|
||||||
|
namespace: testNamespace("bar"),
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range table {
|
||||||
|
_, rest := NewTestREST()
|
||||||
|
c, err := rest.Create(item.ctx, item.namespace)
|
||||||
|
if !item.valid {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("unexpected non-error for %v", item.namespace.Name)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v: Unexpected error %v", item.namespace.Name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !api.HasObjectMetaSystemFieldValues(&item.namespace.ObjectMeta) {
|
||||||
|
t.Errorf("storage did not populate object meta field values")
|
||||||
|
}
|
||||||
|
if e, a := item.namespace, (<-c).Object; !reflect.DeepEqual(e, a) {
|
||||||
|
t.Errorf("diff: %s", util.ObjectDiff(e, a))
|
||||||
|
}
|
||||||
|
// Ensure we implement the interface
|
||||||
|
_ = apiserver.ResourceWatcher(rest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRESTUpdate(t *testing.T) {
|
||||||
|
_, rest := NewTestREST()
|
||||||
|
namespaceA := testNamespace("foo")
|
||||||
|
c, err := rest.Create(api.NewDefaultContext(), namespaceA)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error %v", err)
|
||||||
|
}
|
||||||
|
<-c
|
||||||
|
got, err := rest.Get(api.NewDefaultContext(), namespaceA.Name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error %v", err)
|
||||||
|
}
|
||||||
|
if e, a := namespaceA, got; !reflect.DeepEqual(e, a) {
|
||||||
|
t.Errorf("diff: %s", util.ObjectDiff(e, a))
|
||||||
|
}
|
||||||
|
namespaceB := testNamespace("foo")
|
||||||
|
u, err := rest.Update(api.NewDefaultContext(), namespaceB)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error %v", err)
|
||||||
|
}
|
||||||
|
<-u
|
||||||
|
got2, err := rest.Get(api.NewDefaultContext(), namespaceB.Name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error %v", err)
|
||||||
|
}
|
||||||
|
if e, a := namespaceB, got2; !reflect.DeepEqual(e, a) {
|
||||||
|
t.Errorf("diff: %s", util.ObjectDiff(e, a))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRESTDelete(t *testing.T) {
|
||||||
|
_, rest := NewTestREST()
|
||||||
|
namespaceA := testNamespace("foo")
|
||||||
|
c, err := rest.Create(api.NewContext(), namespaceA)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error %v", err)
|
||||||
|
}
|
||||||
|
<-c
|
||||||
|
c, err = rest.Delete(api.NewContext(), namespaceA.Name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error %v", err)
|
||||||
|
}
|
||||||
|
if stat := (<-c).Object.(*api.Status); stat.Status != api.StatusSuccess {
|
||||||
|
t.Errorf("unexpected status: %v", stat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRESTGet(t *testing.T) {
|
||||||
|
_, rest := NewTestREST()
|
||||||
|
namespaceA := testNamespace("foo")
|
||||||
|
c, err := rest.Create(api.NewContext(), namespaceA)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error %v", err)
|
||||||
|
}
|
||||||
|
<-c
|
||||||
|
got, err := rest.Get(api.NewContext(), namespaceA.Name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error %v", err)
|
||||||
|
}
|
||||||
|
if e, a := namespaceA, got; !reflect.DeepEqual(e, a) {
|
||||||
|
t.Errorf("diff: %s", util.ObjectDiff(e, a))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRESTList(t *testing.T) {
|
||||||
|
reg, rest := NewTestREST()
|
||||||
|
namespaceA := testNamespace("foo")
|
||||||
|
namespaceB := testNamespace("bar")
|
||||||
|
namespaceC := testNamespace("baz")
|
||||||
|
reg.ObjectList = &api.NamespaceList{
|
||||||
|
Items: []api.Namespace{*namespaceA, *namespaceB, *namespaceC},
|
||||||
|
}
|
||||||
|
got, err := rest.List(api.NewContext(), labels.Everything(), labels.Everything())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error %v", err)
|
||||||
|
}
|
||||||
|
expect := &api.NamespaceList{
|
||||||
|
Items: []api.Namespace{*namespaceA, *namespaceB, *namespaceC},
|
||||||
|
}
|
||||||
|
if e, a := expect, got; !reflect.DeepEqual(e, a) {
|
||||||
|
t.Errorf("diff: %s", util.ObjectDiff(e, a))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRESTWatch(t *testing.T) {
|
||||||
|
namespaceA := testNamespace("foo")
|
||||||
|
reg, rest := NewTestREST()
|
||||||
|
wi, err := rest.Watch(api.NewContext(), labels.Everything(), labels.Everything(), "0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error %v", err)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
reg.Broadcaster.Action(watch.Added, namespaceA)
|
||||||
|
}()
|
||||||
|
got := <-wi.ResultChan()
|
||||||
|
if e, a := namespaceA, got.Object; !reflect.DeepEqual(e, a) {
|
||||||
|
t.Errorf("diff: %s", util.ObjectDiff(e, a))
|
||||||
|
}
|
||||||
|
}
|
94
plugin/pkg/admission/namespace/autoprovision/admission.go
Normal file
94
plugin/pkg/admission/namespace/autoprovision/admission.go
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package autoprovision
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
admission.RegisterPlugin("NamespaceAutoProvision", func(client client.Interface, config io.Reader) (admission.Interface, error) {
|
||||||
|
return NewProvision(client), nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// provision is an implementation of admission.Interface.
|
||||||
|
// It looks at all incoming requests in a namespace context, and if the namespace does not exist, it creates one.
|
||||||
|
// It is useful in deployments that do not want to restrict creation of a namespace prior to its usage.
|
||||||
|
type provision struct {
|
||||||
|
client client.Interface
|
||||||
|
store cache.Store
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *provision) Admit(a admission.Attributes) (err error) {
|
||||||
|
defaultVersion, kind, err := latest.RESTMapper.VersionAndKindForResource(a.GetResource())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mapping, err := latest.RESTMapper.RESTMapping(kind, defaultVersion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
namespace := &api.Namespace{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: a.GetNamespace(),
|
||||||
|
Namespace: "",
|
||||||
|
},
|
||||||
|
Status: api.NamespaceStatus{},
|
||||||
|
}
|
||||||
|
_, exists, err := p.store.Get(namespace)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
_, err = p.client.Namespaces().Create(namespace)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProvision(c client.Interface) admission.Interface {
|
||||||
|
store := cache.NewStore(cache.MetaNamespaceKeyFunc)
|
||||||
|
reflector := cache.NewReflector(
|
||||||
|
&cache.ListWatch{
|
||||||
|
Client: c.(*client.Client),
|
||||||
|
FieldSelector: labels.Everything(),
|
||||||
|
Resource: "namespaces",
|
||||||
|
},
|
||||||
|
&api.Namespace{},
|
||||||
|
store,
|
||||||
|
)
|
||||||
|
reflector.Run()
|
||||||
|
return &provision{
|
||||||
|
client: c,
|
||||||
|
store: store,
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package autoprovision
|
98
plugin/pkg/admission/namespace/exists/admission.go
Normal file
98
plugin/pkg/admission/namespace/exists/admission.go
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package exists
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
apierrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
admission.RegisterPlugin("NamespaceExists", func(client client.Interface, config io.Reader) (admission.Interface, error) {
|
||||||
|
return NewExists(client), nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// exists is an implementation of admission.Interface.
|
||||||
|
// It rejects all incoming requests in a namespace context if the namespace does not exist.
|
||||||
|
// It is useful in deployments that want to enforce pre-declaration of a Namespace resource.
|
||||||
|
type exists struct {
|
||||||
|
client client.Interface
|
||||||
|
store cache.Store
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *exists) Admit(a admission.Attributes) (err error) {
|
||||||
|
defaultVersion, kind, err := latest.RESTMapper.VersionAndKindForResource(a.GetResource())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mapping, err := latest.RESTMapper.RESTMapping(kind, defaultVersion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
namespace := &api.Namespace{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: a.GetNamespace(),
|
||||||
|
Namespace: "",
|
||||||
|
},
|
||||||
|
Status: api.NamespaceStatus{},
|
||||||
|
}
|
||||||
|
_, exists, err := e.store.Get(namespace)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
obj := a.GetObject()
|
||||||
|
name := "Unknown"
|
||||||
|
if obj != nil {
|
||||||
|
name, _ = meta.NewAccessor().Name(obj)
|
||||||
|
}
|
||||||
|
return apierrors.NewForbidden(kind, name, fmt.Errorf("Namespace %s does not exist", a.GetNamespace()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExists(c client.Interface) admission.Interface {
|
||||||
|
store := cache.NewStore(cache.MetaNamespaceKeyFunc)
|
||||||
|
// TODO: look into a list/watch that can work with client.Interface, maybe pass it a ListFunc and a WatchFunc
|
||||||
|
reflector := cache.NewReflector(
|
||||||
|
&cache.ListWatch{
|
||||||
|
Client: c.(*client.Client),
|
||||||
|
FieldSelector: labels.Everything(),
|
||||||
|
Resource: "namespaces",
|
||||||
|
},
|
||||||
|
&api.Namespace{},
|
||||||
|
store,
|
||||||
|
)
|
||||||
|
reflector.Run()
|
||||||
|
return &exists{
|
||||||
|
client: c,
|
||||||
|
store: store,
|
||||||
|
}
|
||||||
|
}
|
17
plugin/pkg/admission/namespace/exists/admission_test.go
Normal file
17
plugin/pkg/admission/namespace/exists/admission_test.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package exists
|
@ -273,7 +273,7 @@ type binder struct {
|
|||||||
func (b *binder) Bind(binding *api.Binding) error {
|
func (b *binder) Bind(binding *api.Binding) error {
|
||||||
glog.V(2).Infof("Attempting to bind %v to %v", binding.PodID, binding.Host)
|
glog.V(2).Infof("Attempting to bind %v to %v", binding.PodID, binding.Host)
|
||||||
ctx := api.WithNamespace(api.NewContext(), binding.Namespace)
|
ctx := api.WithNamespace(api.NewContext(), binding.Namespace)
|
||||||
return b.Post().Namespace(api.Namespace(ctx)).Resource("bindings").Body(binding).Do().Error()
|
return b.Post().Namespace(api.NamespaceValue(ctx)).Resource("bindings").Body(binding).Do().Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
type clock interface {
|
type clock interface {
|
||||||
|
Loading…
Reference in New Issue
Block a user