Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions pkg/cmd/server/kubernetes/master.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ import (
latestschedulerapi "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/scheduler/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/scheduler/factory"

// Namespace controller will be added
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/namespace"
"github.com/GoogleCloudPlatform/kubernetes/pkg/namespace"
)

const (
Expand Down Expand Up @@ -83,6 +82,12 @@ func (c *MasterConfig) InstallAPI(container *restful.Container) []string {
}
}

func (c *MasterConfig) RunNamespaceController() {
namespaceController := namespace.NewNamespaceManager(c.KubeClient)
namespaceController.Run(1 * time.Minute)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems slow. Does this mean that my namespace may not be cleaned up for a minute? During that minute, can I keep creating things?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means you will see a namespace appear in "Terminating" status for ~1 min. The value is the same default as upstream. You are unable to create content in a terminating namespace. There are two controllers that need to run in order to delete content (the k8s one, and the openshift one) so it will take ~1-2 minutes for something to truly be gone.

glog.Infof("Started Kubernetes Namespace Manager")
}

// RunReplicationController starts the Kubernetes replication controller sync loop
func (c *MasterConfig) RunReplicationController() {
controllerManager := controller.NewReplicationManager(c.KubeClient)
Expand Down
4 changes: 2 additions & 2 deletions pkg/cmd/server/origin/master.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ import (
clientauthorizationregistry "github.com/openshift/origin/pkg/oauth/registry/clientauthorization"
oauthetcd "github.com/openshift/origin/pkg/oauth/registry/etcd"
projectcontroller "github.com/openshift/origin/pkg/project/controller"
projectregistry "github.com/openshift/origin/pkg/project/registry/project"
projectproxy "github.com/openshift/origin/pkg/project/registry/project/proxy"
routeallocationcontroller "github.com/openshift/origin/pkg/route/controller/allocation"
routeetcd "github.com/openshift/origin/pkg/route/registry/etcd"
routeregistry "github.com/openshift/origin/pkg/route/registry/route"
Expand Down Expand Up @@ -224,7 +224,7 @@ func (c *MasterConfig) InstallProtectedAPI(container *restful.Container) []strin

"routes": routeregistry.NewREST(routeEtcd, routeAllocator),

"projects": projectregistry.NewREST(kclient.Namespaces(), c.ProjectAuthorizationCache),
"projects": projectproxy.NewREST(kclient.Namespaces(), c.ProjectAuthorizationCache),

"users": userStorage,
"identities": identityStorage,
Expand Down
1 change: 1 addition & 0 deletions pkg/cmd/server/start/start_master.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ func StartMaster(openshiftMasterConfig *configapi.MasterConfig) error {
kubeConfig.RunEndpointController()
kubeConfig.RunMinionController()
kubeConfig.RunResourceQuotaManager()
kubeConfig.RunNamespaceController()

} else {
_, kubeConfig, err := configapi.GetKubeClient(openshiftMasterConfig.MasterClients.KubernetesKubeConfig)
Expand Down
7 changes: 7 additions & 0 deletions pkg/project/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,15 @@ type ProjectList struct {
Items []Project
}

// These are internal finalizer values to Origin
const (
FinalizerProject kapi.FinalizerName = "openshift.com/project"
)

// ProjectSpec describes the attributes on a Project
type ProjectSpec struct {
// Finalizers is an opaque list of values that must be empty to permanently remove object from storage
Finalizers []kapi.FinalizerName
}

// ProjectStatus is information about the current status of a Project
Expand Down
7 changes: 7 additions & 0 deletions pkg/project/api/v1beta1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,15 @@ type ProjectList struct {
Items []Project `json:"items"`
}

// These are internal finalizer values to Origin
const (
FinalizerProject kapi.FinalizerName = "openshift.com/project"
)

// ProjectSpec describes the attributes on a Project
type ProjectSpec struct {
// Finalizers is an opaque list of values that must be empty to permanently remove object from storage
Finalizers []kapi.FinalizerName `json:"finalizers,omitempty" description:"an opaque list of values that must be empty to permanently remove object from storage"`
}

// ProjectStatus is information about the current status of a Project
Expand Down
10 changes: 10 additions & 0 deletions pkg/project/api/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package validation
import (
"strings"

kvalidation "github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/fielderrors"
"github.com/openshift/origin/pkg/project/api"
Expand All @@ -29,3 +30,12 @@ func ValidateProject(project *api.Project) fielderrors.ValidationErrorList {
func validateNoNewLineOrTab(s string) bool {
return !(strings.Contains(s, "\n") || strings.Contains(s, "\t"))
}

// ValidateProjectUpdate tests to make sure a project update can be applied. Modifies newProject with immutable fields.
func ValidateProjectUpdate(newProject *api.Project, oldProject *api.Project) fielderrors.ValidationErrorList {
allErrs := fielderrors.ValidationErrorList{}
allErrs = append(allErrs, kvalidation.ValidateObjectMetaUpdate(&oldProject.ObjectMeta, &newProject.ObjectMeta).Prefix("metadata")...)
newProject.Spec.Finalizers = oldProject.Spec.Finalizers
newProject.Status = oldProject.Status
return allErrs
}
54 changes: 50 additions & 4 deletions pkg/project/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import (
kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
osclient "github.com/openshift/origin/pkg/client"
"github.com/openshift/origin/pkg/project/api"
)

// NamespaceController is responsible for participating in Kubernetes Namespace termination
Expand All @@ -23,17 +25,61 @@ type fatalError string
func (e fatalError) Error() string { return "fatal error handling namespace: " + string(e) }

// Handle processes a namespace and deletes content in origin if its terminating
func (c *NamespaceController) Handle(namespace *kapi.Namespace) error {
// ignore namespaces that are not terminating
func (c *NamespaceController) Handle(namespace *kapi.Namespace) (err error) {
// if namespace is not terminating, ignore it
if namespace.Status.Phase != kapi.NamespaceTerminating {
return nil
}

deleteAllContent(c.Client, namespace.Name)
// TODO: finalize namespace (remove openshift.com/origin)
// if we already processed this namespace, ignore it
if finalized(namespace) {
return nil
}

// there may still be content for us to remove
err = deleteAllContent(c.Client, namespace.Name)
if err != nil {
return err
}

// we have removed content, so mark it finalized by us
err = finalize(c.KubeClient, namespace)
if err != nil {
return err
}

return nil
}

// finalized returns true if the spec.finalizers does not contain the project finalizer
func finalized(namespace *kapi.Namespace) bool {
for i := range namespace.Spec.Finalizers {
if api.FinalizerProject == namespace.Spec.Finalizers[i] {
return false
}
}
return true
}

// finalize will finalize the namespace for kubernetes
func finalize(kubeClient kclient.Interface, namespace *kapi.Namespace) error {
namespaceFinalize := kapi.Namespace{}
namespaceFinalize.ObjectMeta = namespace.ObjectMeta
namespaceFinalize.Spec = namespace.Spec
finalizerSet := util.NewStringSet()
for i := range namespace.Spec.Finalizers {
if namespace.Spec.Finalizers[i] != api.FinalizerProject {
finalizerSet.Insert(string(namespace.Spec.Finalizers[i]))
}
}
namespaceFinalize.Spec.Finalizers = make([]kapi.FinalizerName, 0, len(finalizerSet))
for _, value := range finalizerSet.List() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the order of finalizers matter? This effectively reorders the list.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Order does not matter.

I should change upstream to make sure the field cannot have duplicate values though.

Will make that change in Kube repo

Sent from my iPhone

On Apr 6, 2015, at 3:53 PM, David Eads notifications@github.com wrote:

In pkg/project/controller/controller.go:

  • return true
    +}

+// finalize will finalize the namespace for kubernetes
+func finalize(kubeClient kclient.Interface, namespace *kapi.Namespace) error {

  • namespaceFinalize := kapi.Namespace{}
  • namespaceFinalize.ObjectMeta = namespace.ObjectMeta
  • namespaceFinalize.Spec = namespace.Spec
  • finalizerSet := util.NewStringSet()
  • for i := range namespace.Spec.Finalizers {
  •   if namespace.Spec.Finalizers[i] != api.FinalizerProject {
    
  •       finalizerSet.Insert(string(namespace.Spec.Finalizers[i]))
    
  •   }
    
  • }
  • namespaceFinalize.Spec.Finalizers = make([]kapi.FinalizerName, 0, len(finalizerSet))
  • for _, value := range finalizerSet.List() {
    Does the order of finalizers matter? This effectively reorders the list.


Reply to this email directly or view it on GitHub.

namespaceFinalize.Spec.Finalizers = append(namespaceFinalize.Spec.Finalizers, kapi.FinalizerName(value))
}
_, err := kubeClient.Namespaces().Finalize(&namespaceFinalize)
return err
}

// deleteAllContent will purge all content in openshift in the specified namespace
func deleteAllContent(client osclient.Interface, namespace string) (err error) {
err = deleteBuildConfigs(client, namespace)
Expand Down
42 changes: 20 additions & 22 deletions pkg/project/controller/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ package controller
import (
"testing"

"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"

osclient "github.com/openshift/origin/pkg/client"
"github.com/openshift/origin/pkg/project/api"
)

func TestSyncNamespaceThatIsTerminating(t *testing.T) {
Expand All @@ -17,18 +17,18 @@ func TestSyncNamespaceThatIsTerminating(t *testing.T) {
KubeClient: mockKubeClient,
Client: mockOriginClient,
}
//now := util.Now()
testNamespace := &api.Namespace{
ObjectMeta: api.ObjectMeta{
Name: "test",
ResourceVersion: "1",
// DeletionTimestamp: &now,
now := util.Now()
testNamespace := &kapi.Namespace{
ObjectMeta: kapi.ObjectMeta{
Name: "test",
ResourceVersion: "1",
DeletionTimestamp: &now,
},
// Spec: api.NamespaceSpec{
// Finalizers: []api.FinalizerName{"kubernetes"},
// },
Status: api.NamespaceStatus{
Phase: api.NamespaceTerminating,
Spec: kapi.NamespaceSpec{
Finalizers: []kapi.FinalizerName{kapi.FinalizerKubernetes, api.FinalizerProject},
},
Status: kapi.NamespaceStatus{
Phase: kapi.NamespaceTerminating,
},
}
err := nm.Handle(testNamespace)
Expand Down Expand Up @@ -67,18 +67,16 @@ func TestSyncNamespaceThatIsActive(t *testing.T) {
KubeClient: mockKubeClient,
Client: mockOriginClient,
}
//now := util.Now()
testNamespace := &api.Namespace{
ObjectMeta: api.ObjectMeta{
testNamespace := &kapi.Namespace{
ObjectMeta: kapi.ObjectMeta{
Name: "test",
ResourceVersion: "1",
// DeletionTimestamp: &now,
},
// Spec: api.NamespaceSpec{
// Finalizers: []api.FinalizerName{"kubernetes"},
// },
Status: api.NamespaceStatus{
Phase: api.NamespaceActive,
Spec: kapi.NamespaceSpec{
Finalizers: []kapi.FinalizerName{kapi.FinalizerKubernetes, api.FinalizerProject},
},
Status: kapi.NamespaceStatus{
Phase: kapi.NamespaceActive,
},
}
err := nm.Handle(testNamespace)
Expand Down
135 changes: 135 additions & 0 deletions pkg/project/registry/project/proxy/proxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package proxy

import (
"fmt"

kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
kerrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"

"github.com/openshift/origin/pkg/project/api"
projectauth "github.com/openshift/origin/pkg/project/auth"
projectregistry "github.com/openshift/origin/pkg/project/registry/project"
)

type REST struct {
// client can modify Kubernetes namespaces
client kclient.NamespaceInterface
// lister can enumerate project lists that enforce policy
lister projectauth.Lister
// Allows extended behavior during creation, required
createStrategy rest.RESTCreateStrategy
// Allows extended behavior during updates, required
updateStrategy rest.RESTUpdateStrategy
}

// NewREST returns a RESTStorage object that will work against Project resources
func NewREST(client kclient.NamespaceInterface, lister projectauth.Lister) *REST {
return &REST{
client: client,
lister: lister,
createStrategy: projectregistry.Strategy,
updateStrategy: projectregistry.Strategy,
}
}

// New returns a new Project
func (s *REST) New() runtime.Object {
return &api.Project{}
}

// NewList returns a new ProjectList
func (*REST) NewList() runtime.Object {
return &api.ProjectList{}
}

// convertNamespace transforms a Namespace into a Project
func convertNamespace(namespace *kapi.Namespace) *api.Project {
displayName := namespace.Annotations["displayname"]
return &api.Project{
ObjectMeta: namespace.ObjectMeta,
DisplayName: displayName,
Spec: api.ProjectSpec{
Finalizers: namespace.Spec.Finalizers,
},
Status: api.ProjectStatus{
Phase: namespace.Status.Phase,
},
}
}

// convertProject transforms a Project into a Namespace
func convertProject(project *api.Project) *kapi.Namespace {
namespace := &kapi.Namespace{
ObjectMeta: project.ObjectMeta,
Spec: kapi.NamespaceSpec{
Finalizers: project.Spec.Finalizers,
},
Status: kapi.NamespaceStatus{
Phase: project.Status.Phase,
},
}
if namespace.Annotations == nil {
namespace.Annotations = map[string]string{}
}
namespace.Annotations["displayname"] = project.DisplayName
return namespace
}

// convertNamespaceList transforms a NamespaceList into a ProjectList
func convertNamespaceList(namespaceList *kapi.NamespaceList) *api.ProjectList {
projects := &api.ProjectList{}
for _, n := range namespaceList.Items {
projects.Items = append(projects.Items, *convertNamespace(&n))
}
return projects
}

// List retrieves a list of Projects that match label.
func (s *REST) List(ctx kapi.Context, label labels.Selector, field fields.Selector) (runtime.Object, error) {
user, ok := kapi.UserFrom(ctx)
if !ok {
return nil, kerrors.NewForbidden("Project", "", fmt.Errorf("Unable to list projects without a user on the context"))
}
namespaceList, err := s.lister.List(user)
if err != nil {
return nil, err
}
return convertNamespaceList(namespaceList), nil
}

// Get retrieves a Project by name
func (s *REST) Get(ctx kapi.Context, name string) (runtime.Object, error) {
namespace, err := s.client.Get(name)
if err != nil {
return nil, err
}
return convertNamespace(namespace), nil
}

// Create registers the given Project.
func (s *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) {
project, ok := obj.(*api.Project)
if !ok {
return nil, fmt.Errorf("not a project: %#v", obj)
}
kapi.FillObjectMetaSystemFields(ctx, &project.ObjectMeta)
s.createStrategy.PrepareForCreate(obj)
if errs := s.createStrategy.Validate(ctx, obj); len(errs) > 0 {
return nil, kerrors.NewInvalid("project", project.Name, errs)
}
namespace, err := s.client.Create(convertProject(project))
if err != nil {
return nil, err
}
return convertNamespace(namespace), nil
}

// Delete deletes a Project specified by its name
func (s *REST) Delete(ctx kapi.Context, name string) (runtime.Object, error) {
return &kapi.Status{Status: kapi.StatusSuccess}, s.client.Delete(name)
}
Loading