refactor workspace controller
Signed-off-by: hongming <talonwan@yunify.com>
This commit is contained in:
@@ -20,240 +20,207 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/go-logr/logr"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
"k8s.io/klog"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/tools/record"
|
||||
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
|
||||
tenantv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
controllerutils "kubesphere.io/kubesphere/pkg/controller/utils/controller"
|
||||
"kubesphere.io/kubesphere/pkg/utils/k8sutil"
|
||||
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
|
||||
"reflect"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
"sigs.k8s.io/controller-runtime/pkg/source"
|
||||
)
|
||||
|
||||
// Add creates a new Namespace Controller and adds it to the Manager with default RBAC. The Manager will set fields on the Controller
|
||||
// and Start it when the Manager is Started.
|
||||
func Add(mgr manager.Manager) error {
|
||||
return add(mgr, newReconciler(mgr))
|
||||
}
|
||||
const (
|
||||
controllerName = "namespace-controller"
|
||||
)
|
||||
|
||||
// newReconciler returns a new reconcile.Reconciler
|
||||
func newReconciler(mgr manager.Manager) reconcile.Reconciler {
|
||||
return &ReconcileNamespace{
|
||||
Client: mgr.GetClient(),
|
||||
scheme: mgr.GetScheme(),
|
||||
}
|
||||
}
|
||||
|
||||
// add adds a new Controller to mgr with r as the reconcile.Reconciler
|
||||
func add(mgr manager.Manager, r reconcile.Reconciler) error {
|
||||
// Create a new controller
|
||||
c, err := controller.New("namespace-controller", mgr, controller.Options{Reconciler: r})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Watch for changes to Namespace
|
||||
err = c.Watch(&source.Kind{Type: &corev1.Namespace{}}, &handler.EnqueueRequestForObject{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ reconcile.Reconciler = &ReconcileNamespace{}
|
||||
|
||||
// ReconcileNamespace reconciles a Namespace object
|
||||
type ReconcileNamespace struct {
|
||||
// Reconciler reconciles a Namespace object
|
||||
type Reconciler struct {
|
||||
client.Client
|
||||
scheme *runtime.Scheme
|
||||
Logger logr.Logger
|
||||
Recorder record.EventRecorder
|
||||
MaxConcurrentReconciles int
|
||||
}
|
||||
|
||||
// Reconcile reads that state of the cluster for a Namespace object and makes changes based on the state read
|
||||
// and what is in the Namespace.Spec
|
||||
// +kubebuilder:rbac:groups=core.kubesphere.io,resources=namespaces,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=core.kubesphere.io,resources=namespaces/status,verbs=get;update;patch
|
||||
func (r *ReconcileNamespace) Reconcile(request reconcile.Request) (reconcile.Result, error) {
|
||||
// Fetch the Namespace instance
|
||||
instance := &corev1.Namespace{}
|
||||
err := r.Get(context.TODO(), request.NamespacedName, instance)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
// Object not found, return. Created objects are automatically garbage collected.
|
||||
// For additional cleanup logic use finalizers.
|
||||
// The object is being deleted
|
||||
// our finalizer is present, so lets handle our external dependency
|
||||
return reconcile.Result{}, nil
|
||||
}
|
||||
// Error reading the object - requeue the request.
|
||||
return reconcile.Result{}, err
|
||||
func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
if r.Client == nil {
|
||||
r.Client = mgr.GetClient()
|
||||
}
|
||||
if r.Logger == nil {
|
||||
r.Logger = ctrl.Log.WithName("controllers").WithName(controllerName)
|
||||
}
|
||||
if r.Recorder == nil {
|
||||
r.Recorder = mgr.GetEventRecorderFor(controllerName)
|
||||
}
|
||||
if r.MaxConcurrentReconciles <= 0 {
|
||||
r.MaxConcurrentReconciles = 1
|
||||
}
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
Named(controllerName).
|
||||
WithOptions(controller.Options{
|
||||
MaxConcurrentReconciles: r.MaxConcurrentReconciles,
|
||||
}).
|
||||
For(&corev1.Namespace{}).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
// +kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=tenant.kubesphere.io,resources=workspaces,verbs=get;list;watch
|
||||
// +kubebuilder:rbac:groups=iam.kubesphere.io,resources=rolebases,verbs=get;list;watch
|
||||
// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles,verbs=get;list;watch
|
||||
// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings,verbs=get;list;watch;create;update;patch;delete
|
||||
func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
||||
logger := r.Logger.WithValues("namespace", req.NamespacedName)
|
||||
rootCtx := context.Background()
|
||||
namespace := &corev1.Namespace{}
|
||||
if err := r.Get(rootCtx, req.NamespacedName, namespace); err != nil {
|
||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||
}
|
||||
// name of your custom finalizer
|
||||
finalizer := "finalizers.kubesphere.io/namespaces"
|
||||
|
||||
if instance.ObjectMeta.DeletionTimestamp.IsZero() {
|
||||
if namespace.ObjectMeta.DeletionTimestamp.IsZero() {
|
||||
// The object is not being deleted, so if it does not have our finalizer,
|
||||
// then lets add the finalizer and update the object.
|
||||
if !sliceutil.HasString(instance.ObjectMeta.Finalizers, finalizer) {
|
||||
instance.ObjectMeta.Finalizers = append(instance.ObjectMeta.Finalizers, finalizer)
|
||||
if instance.Labels == nil {
|
||||
instance.Labels = make(map[string]string)
|
||||
if !sliceutil.HasString(namespace.ObjectMeta.Finalizers, finalizer) {
|
||||
// create only once, ignore already exists error
|
||||
if err := r.initCreatorRoleBinding(rootCtx, logger, namespace); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
instance.Labels[constants.NamespaceLabelKey] = instance.Name
|
||||
if err := r.Update(context.Background(), instance); err != nil {
|
||||
return reconcile.Result{}, err
|
||||
namespace.ObjectMeta.Finalizers = append(namespace.ObjectMeta.Finalizers, finalizer)
|
||||
if namespace.Labels == nil {
|
||||
namespace.Labels = make(map[string]string)
|
||||
}
|
||||
// used for NetworkPolicyPeer.NamespaceSelector
|
||||
namespace.Labels[constants.NamespaceLabelKey] = namespace.Name
|
||||
if err := r.Update(rootCtx, namespace); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// The object is being deleted
|
||||
if sliceutil.HasString(instance.ObjectMeta.Finalizers, finalizer) {
|
||||
if err = r.deleteRouter(instance.Name); err != nil {
|
||||
return reconcile.Result{}, err
|
||||
if sliceutil.HasString(namespace.ObjectMeta.Finalizers, finalizer) {
|
||||
if err := r.deleteRouter(rootCtx, logger, namespace.Name); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// remove our finalizer from the list and update it.
|
||||
instance.ObjectMeta.Finalizers = sliceutil.RemoveString(instance.ObjectMeta.Finalizers, func(item string) bool {
|
||||
namespace.ObjectMeta.Finalizers = sliceutil.RemoveString(namespace.ObjectMeta.Finalizers, func(item string) bool {
|
||||
return item == finalizer
|
||||
})
|
||||
|
||||
if err := r.Update(context.Background(), instance); err != nil {
|
||||
return reconcile.Result{}, err
|
||||
if err := r.Update(rootCtx, namespace); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
// Our finalizer has finished, so the reconciler can do nothing.
|
||||
return reconcile.Result{}, nil
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// initialize subresource if created by kubesphere
|
||||
if workspace := instance.Labels[constants.WorkspaceLabelKey]; workspace != "" {
|
||||
if err = r.bindWorkspace(instance); err != nil {
|
||||
return reconcile.Result{}, err
|
||||
if workspace := namespace.Labels[tenantv1alpha1.WorkspaceLabel]; workspace != "" {
|
||||
if err := r.bindWorkspace(rootCtx, logger, namespace); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
if err = r.initRoles(instance); err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
if err = r.initCreatorRoleBinding(instance); err != nil {
|
||||
return reconcile.Result{}, err
|
||||
if err := r.initRoles(rootCtx, logger, namespace); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
} else {
|
||||
r.unbindWorkspace(instance)
|
||||
if err := r.unbindWorkspace(rootCtx, logger, namespace); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return reconcile.Result{}, nil
|
||||
r.Recorder.Event(namespace, corev1.EventTypeNormal, controllerutils.SuccessSynced, controllerutils.MessageResourceSynced)
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
func (r *ReconcileNamespace) bindWorkspace(namespace *corev1.Namespace) error {
|
||||
workspaceName := namespace.Labels[constants.WorkspaceLabelKey]
|
||||
func (r *Reconciler) bindWorkspace(ctx context.Context, logger logr.Logger, namespace *corev1.Namespace) error {
|
||||
workspace := &tenantv1alpha1.Workspace{}
|
||||
if err := r.Get(context.TODO(), types.NamespacedName{Name: workspaceName}, workspace); err != nil {
|
||||
if err := r.Get(ctx, types.NamespacedName{Name: namespace.Labels[constants.WorkspaceLabelKey]}, workspace); err != nil {
|
||||
// remove existed owner reference if workspace not found
|
||||
if errors.IsNotFound(err) && k8sutil.IsControlledBy(namespace.OwnerReferences, tenantv1alpha1.ResourceKindWorkspace, "") {
|
||||
return r.unbindWorkspace(ctx, logger, namespace)
|
||||
}
|
||||
// skip if workspace not found
|
||||
if errors.IsNotFound(err) {
|
||||
klog.Warning(err)
|
||||
return nil
|
||||
}
|
||||
klog.Error(err)
|
||||
return err
|
||||
return client.IgnoreNotFound(err)
|
||||
}
|
||||
|
||||
// owner reference not match workspace label
|
||||
if !metav1.IsControlledBy(namespace, workspace) {
|
||||
workspace.OwnerReferences = removeWorkspaceOwnerReference(workspace.OwnerReferences)
|
||||
if err := controllerutil.SetControllerReference(workspace, namespace, r.scheme); err != nil {
|
||||
klog.Error(err)
|
||||
namespace := namespace.DeepCopy()
|
||||
namespace.OwnerReferences = k8sutil.RemoveWorkspaceOwnerReference(namespace.OwnerReferences)
|
||||
if err := controllerutil.SetControllerReference(workspace, namespace, scheme.Scheme); err != nil {
|
||||
logger.Error(err, "set controller reference failed")
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.Update(context.TODO(), namespace); err != nil {
|
||||
klog.Error(err)
|
||||
logger.V(4).Info("update namespace owner reference", "workspace", workspace.Name)
|
||||
if err := r.Update(ctx, namespace); err != nil {
|
||||
logger.Error(err, "update namespace failed")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ReconcileNamespace) unbindWorkspace(namespace *corev1.Namespace) error {
|
||||
|
||||
func (r *Reconciler) unbindWorkspace(ctx context.Context, logger logr.Logger, namespace *corev1.Namespace) error {
|
||||
if k8sutil.IsControlledBy(namespace.OwnerReferences, tenantv1alpha1.ResourceKindWorkspace, "") {
|
||||
namespace := namespace.DeepCopy()
|
||||
namespace.OwnerReferences = removeWorkspaceOwnerReference(namespace.OwnerReferences)
|
||||
if err := r.Update(context.TODO(), namespace); err != nil {
|
||||
klog.Error(err)
|
||||
namespace.OwnerReferences = k8sutil.RemoveWorkspaceOwnerReference(namespace.OwnerReferences)
|
||||
logger.V(4).Info("remove owner reference", "workspace", namespace.Labels[constants.WorkspaceLabelKey])
|
||||
if err := r.Update(ctx, namespace); err != nil {
|
||||
logger.Error(err, "update owner reference failed")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove workspace kind owner reference of the namespace
|
||||
func removeWorkspaceOwnerReference(ownerReferences []metav1.OwnerReference) []metav1.OwnerReference {
|
||||
tmp := make([]metav1.OwnerReference, 0)
|
||||
for _, owner := range ownerReferences {
|
||||
if owner.Kind != tenantv1alpha1.ResourceKindWorkspace {
|
||||
tmp = append(tmp, owner)
|
||||
}
|
||||
}
|
||||
return tmp
|
||||
}
|
||||
|
||||
func (r *ReconcileNamespace) deleteRouter(namespace string) error {
|
||||
func (r *Reconciler) deleteRouter(ctx context.Context, logger logr.Logger, namespace string) error {
|
||||
routerName := constants.IngressControllerPrefix + namespace
|
||||
// delete service first
|
||||
found := corev1.Service{}
|
||||
err := r.Get(context.TODO(), types.NamespacedName{Namespace: constants.IngressControllerNamespace, Name: routerName}, &found)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
klog.Error(err)
|
||||
}
|
||||
|
||||
err = r.Delete(context.TODO(), &found)
|
||||
// delete service first
|
||||
service := corev1.Service{}
|
||||
err := r.Get(ctx, types.NamespacedName{Namespace: constants.IngressControllerNamespace, Name: routerName}, &service)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
return client.IgnoreNotFound(err)
|
||||
}
|
||||
logger.V(4).Info("delete router service", "namespace", service.Namespace, "service", service.Name)
|
||||
err = r.Delete(ctx, &service)
|
||||
if err != nil {
|
||||
return client.IgnoreNotFound(err)
|
||||
}
|
||||
|
||||
// delete deployment
|
||||
deploy := appsv1.Deployment{}
|
||||
err = r.Get(context.TODO(), types.NamespacedName{Namespace: constants.IngressControllerNamespace, Name: routerName}, &deploy)
|
||||
err = r.Get(ctx, types.NamespacedName{Namespace: constants.IngressControllerNamespace, Name: routerName}, &deploy)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
klog.Error(err)
|
||||
logger.Error(err, "delete router deployment failed")
|
||||
return err
|
||||
}
|
||||
|
||||
err = r.Delete(context.TODO(), &deploy)
|
||||
logger.V(4).Info("delete router deployment", "namespace", deploy.Namespace, "deployment", deploy.Name)
|
||||
err = r.Delete(ctx, &deploy)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
return client.IgnoreNotFound(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ReconcileNamespace) initRoles(namespace *corev1.Namespace) error {
|
||||
var roleBases iamv1alpha2.RoleBaseList
|
||||
|
||||
func (r *Reconciler) initRoles(ctx context.Context, logger logr.Logger, namespace *corev1.Namespace) error {
|
||||
var templates iamv1alpha2.RoleBaseList
|
||||
var labelKey string
|
||||
// filtering initial roles by label
|
||||
if namespace.Labels[constants.DevOpsProjectLabelKey] != "" {
|
||||
@@ -263,29 +230,26 @@ func (r *ReconcileNamespace) initRoles(namespace *corev1.Namespace) error {
|
||||
// scope.kubesphere.io/namespace: ""
|
||||
labelKey = fmt.Sprintf(iamv1alpha2.ScopeLabelFormat, iamv1alpha2.ScopeNamespace)
|
||||
}
|
||||
err := r.List(context.Background(), &roleBases, client.MatchingLabelsSelector{Selector: labels.SelectorFromSet(labels.Set{labelKey: ""})})
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
|
||||
if err := r.List(ctx, &templates, client.MatchingLabelsSelector{Selector: labels.SelectorFromSet(labels.Set{labelKey: ""})}); err != nil {
|
||||
logger.Error(err, "list role bases failed")
|
||||
return err
|
||||
}
|
||||
|
||||
for _, roleBase := range roleBases.Items {
|
||||
for _, template := range templates.Items {
|
||||
var role rbacv1.Role
|
||||
if err = yaml.NewYAMLOrJSONDecoder(bytes.NewBuffer(roleBase.Role.Raw), 1024).Decode(&role); err == nil && role.Kind == iamv1alpha2.ResourceKindRole {
|
||||
if err := yaml.NewYAMLOrJSONDecoder(bytes.NewBuffer(template.Role.Raw), 1024).Decode(&role); err == nil && role.Kind == iamv1alpha2.ResourceKindRole {
|
||||
var old rbacv1.Role
|
||||
err := r.Client.Get(context.Background(), types.NamespacedName{Namespace: namespace.Name, Name: role.Name}, &old)
|
||||
if err != nil {
|
||||
if err := r.Client.Get(ctx, types.NamespacedName{Namespace: namespace.Name, Name: role.Name}, &old); err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
role.Namespace = namespace.Name
|
||||
err = r.Client.Create(context.Background(), &role)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
logger.V(4).Info("init builtin role", "role", role.Name)
|
||||
if err := r.Client.Create(ctx, &role); err != nil {
|
||||
logger.Error(err, "create role failed")
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(role.Labels, old.Labels) ||
|
||||
!reflect.DeepEqual(role.Annotations, old.Annotations) ||
|
||||
!reflect.DeepEqual(role.Rules, old.Rules) {
|
||||
@@ -294,36 +258,46 @@ func (r *ReconcileNamespace) initRoles(namespace *corev1.Namespace) error {
|
||||
old.Annotations = role.Annotations
|
||||
old.Rules = role.Rules
|
||||
|
||||
if err := r.Update(context.Background(), &old); err != nil {
|
||||
klog.Error(err)
|
||||
logger.V(4).Info("update builtin role", "role", role.Name)
|
||||
if err := r.Update(ctx, &old); err != nil {
|
||||
logger.Error(err, "update role failed")
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if err != nil {
|
||||
logger.Error(fmt.Errorf("invalid role base found"), "init roles failed", "name", template.Name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ReconcileNamespace) initCreatorRoleBinding(namespace *corev1.Namespace) error {
|
||||
func (r *Reconciler) initCreatorRoleBinding(ctx context.Context, logger logr.Logger, namespace *corev1.Namespace) error {
|
||||
creator := namespace.Annotations[constants.CreatorAnnotationKey]
|
||||
if creator == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var user iamv1alpha2.User
|
||||
if err := r.Get(context.Background(), types.NamespacedName{Name: creator}, &user); err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
if err := r.Get(ctx, types.NamespacedName{Name: creator}, &user); err != nil {
|
||||
return client.IgnoreNotFound(err)
|
||||
}
|
||||
creatorRoleBinding := newCreatorRoleBinding(creator, namespace.Name)
|
||||
logger.V(4).Info("init creator role binding", "creator", user.Name)
|
||||
if err := r.Client.Create(ctx, creatorRoleBinding); err != nil {
|
||||
if errors.IsAlreadyExists(err) {
|
||||
return nil
|
||||
}
|
||||
klog.Error(err)
|
||||
logger.Error(err, "create role binding failed")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
creatorRoleBinding := &rbacv1.RoleBinding{
|
||||
func newCreatorRoleBinding(creator string, namespace string) *rbacv1.RoleBinding {
|
||||
return &rbacv1.RoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("%s-%s", creator, iamv1alpha2.NamespaceAdmin),
|
||||
Labels: map[string]string{iamv1alpha2.UserReferenceLabel: creator},
|
||||
Namespace: namespace.Name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
RoleRef: rbacv1.RoleRef{
|
||||
APIGroup: rbacv1.GroupName,
|
||||
@@ -338,14 +312,4 @@ func (r *ReconcileNamespace) initCreatorRoleBinding(namespace *corev1.Namespace)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := r.Client.Create(context.Background(), creatorRoleBinding); err != nil {
|
||||
if errors.IsAlreadyExists(err) {
|
||||
return nil
|
||||
}
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -17,59 +17,84 @@ limitations under the License.
|
||||
package namespace
|
||||
|
||||
import (
|
||||
stdlog "log"
|
||||
"github.com/onsi/gomega/gexec"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/klog/klogr"
|
||||
"kubesphere.io/kubesphere/pkg/apis"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/onsi/gomega"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
"kubesphere.io/kubesphere/pkg/apis"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
|
||||
)
|
||||
|
||||
var cfg *rest.Config
|
||||
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
|
||||
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
t := &envtest.Environment{
|
||||
CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crds")},
|
||||
}
|
||||
apis.AddToScheme(scheme.Scheme)
|
||||
var k8sClient client.Client
|
||||
var k8sManager ctrl.Manager
|
||||
var testEnv *envtest.Environment
|
||||
|
||||
var err error
|
||||
if cfg, err = t.Start(); err != nil {
|
||||
stdlog.Fatal(err)
|
||||
}
|
||||
|
||||
code := m.Run()
|
||||
t.Stop()
|
||||
os.Exit(code)
|
||||
func TestNamespaceController(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecsWithDefaultAndCustomReporters(t,
|
||||
"Namespace Controller Test Suite",
|
||||
[]Reporter{printer.NewlineReporter{}})
|
||||
}
|
||||
|
||||
// SetupTestReconcile returns a reconcile.Reconcile implementation that delegates to inner and
|
||||
// writes the request to requests after Reconcile is finished.
|
||||
func SetupTestReconcile(inner reconcile.Reconciler) (reconcile.Reconciler, chan reconcile.Request) {
|
||||
requests := make(chan reconcile.Request)
|
||||
fn := reconcile.Func(func(req reconcile.Request) (reconcile.Result, error) {
|
||||
result, err := inner.Reconcile(req)
|
||||
requests <- req
|
||||
return result, err
|
||||
var _ = BeforeSuite(func(done Done) {
|
||||
logf.SetLogger(klogr.New())
|
||||
|
||||
By("bootstrapping test environment")
|
||||
t := true
|
||||
if os.Getenv("TEST_USE_EXISTING_CLUSTER") == "true" {
|
||||
testEnv = &envtest.Environment{
|
||||
UseExistingCluster: &t,
|
||||
}
|
||||
} else {
|
||||
testEnv = &envtest.Environment{
|
||||
CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crds")},
|
||||
AttachControlPlaneOutput: false,
|
||||
}
|
||||
}
|
||||
|
||||
cfg, err := testEnv.Start()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(cfg).ToNot(BeNil())
|
||||
|
||||
err = apis.AddToScheme(scheme.Scheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
k8sManager, err = ctrl.NewManager(cfg, ctrl.Options{
|
||||
Scheme: scheme.Scheme,
|
||||
MetricsBindAddress: "0",
|
||||
})
|
||||
return fn, requests
|
||||
}
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
err = (&Reconciler{}).SetupWithManager(k8sManager)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// StartTestManager adds recFn
|
||||
func StartTestManager(mgr manager.Manager, g *gomega.GomegaWithT) (chan struct{}, *sync.WaitGroup) {
|
||||
stop := make(chan struct{})
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
g.Expect(mgr.Start(stop)).NotTo(gomega.HaveOccurred())
|
||||
err = k8sManager.Start(ctrl.SetupSignalHandler())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}()
|
||||
return stop, wg
|
||||
}
|
||||
|
||||
k8sClient = k8sManager.GetClient()
|
||||
Expect(k8sClient).ToNot(BeNil())
|
||||
|
||||
close(done)
|
||||
}, 60)
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
By("tearing down the test environment")
|
||||
gexec.KillAndWait(5 * time.Second)
|
||||
err := testEnv.Stop()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
@@ -15,3 +15,88 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
package namespace
|
||||
|
||||
import (
|
||||
"context"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
tenantv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ = Describe("Namespace", func() {
|
||||
|
||||
const timeout = time.Second * 30
|
||||
const interval = time.Second * 1
|
||||
|
||||
workspace := &tenantv1alpha1.Workspace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-workspace",
|
||||
},
|
||||
}
|
||||
BeforeEach(func() {
|
||||
// Create workspace
|
||||
Expect(k8sClient.Create(context.Background(), workspace)).Should(Succeed())
|
||||
})
|
||||
|
||||
// Add Tests for OpenAPI validation (or additonal CRD features) specified in
|
||||
// your API definition.
|
||||
// Avoid adding tests for vanilla CRUD operations because they would
|
||||
// test Kubernetes API server, which isn't the goal here.
|
||||
Context("Namespace Controller", func() {
|
||||
It("Should create successfully", func() {
|
||||
namespace := &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-namespace",
|
||||
Labels: map[string]string{tenantv1alpha1.WorkspaceLabel: workspace.Name},
|
||||
},
|
||||
}
|
||||
|
||||
// Create namespace
|
||||
Expect(k8sClient.Create(context.Background(), namespace)).Should(Succeed())
|
||||
|
||||
By("Expecting to create namespace successfully")
|
||||
Eventually(func() bool {
|
||||
k8sClient.Get(context.Background(), types.NamespacedName{Name: namespace.Name}, namespace)
|
||||
return !namespace.CreationTimestamp.IsZero()
|
||||
}, timeout, interval).Should(BeTrue())
|
||||
|
||||
By("Expecting to set owner reference successfully")
|
||||
Eventually(func() bool {
|
||||
k8sClient.Get(context.Background(), types.NamespacedName{Name: namespace.Name}, namespace)
|
||||
return len(namespace.OwnerReferences) > 0
|
||||
}, timeout, interval).Should(BeTrue())
|
||||
|
||||
Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: workspace.Name}, workspace)).Should(Succeed())
|
||||
|
||||
controlled := true
|
||||
expectedOwnerReference := metav1.OwnerReference{
|
||||
Kind: workspace.Kind,
|
||||
APIVersion: workspace.APIVersion,
|
||||
UID: workspace.UID,
|
||||
Name: workspace.Name,
|
||||
Controller: &controlled,
|
||||
BlockOwnerDeletion: &controlled,
|
||||
}
|
||||
|
||||
By("Expecting to bind workspace successfully")
|
||||
Expect(namespace.OwnerReferences).To(ContainElement(expectedOwnerReference))
|
||||
Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: workspace.Name}, workspace)).Should(Succeed())
|
||||
|
||||
By("Expecting to update namespace successfully")
|
||||
updated := namespace.DeepCopy()
|
||||
updated.Labels[constants.WorkspaceLabelKey] = "workspace-not-exist"
|
||||
Expect(k8sClient.Update(context.Background(), updated)).Should(Succeed())
|
||||
|
||||
By("Expecting to unbind workspace successfully")
|
||||
Eventually(func() bool {
|
||||
k8sClient.Get(context.Background(), types.NamespacedName{Name: namespace.Name}, namespace)
|
||||
return len(namespace.OwnerReferences) == 0
|
||||
}, timeout, interval).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user