Skip to content

Commit

Permalink
operator: Add pool-namespaced concept
Browse files Browse the repository at this point in the history
  • Loading branch information
wpjunior authored and cezarsa committed Jun 11, 2021
1 parent 8d84b93 commit f57084c
Show file tree
Hide file tree
Showing 12 changed files with 238 additions and 11 deletions.
4 changes: 4 additions & 0 deletions api/v1alpha1/rpaasinstance_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ type RpaasInstanceSpec struct {
// +optional
PlanName string `json:"planName"`

// PlanNamespace is the namespace of target plan and their flavors, when empty uses the same namespace of instance.
// +optional
PlanNamespace string `json:"planNamespace"`

// Flavors are references to RpaasFlavors resources. When provided, each flavor
// merges its instance template spec with this instance spec.
// +optional
Expand Down
4 changes: 4 additions & 0 deletions config/crd/bases/extensions.tsuru.io_rpaasflavors.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,10 @@ spec:
planName:
description: PlanName is the name of the rpaasplan instance.
type: string
planNamespace:
description: PlanNamespace is the namespace of target plan and their
flavors, when empty uses the same namespace of instance.
type: string
planTemplate:
description: PlanTemplate allow overriding fields in the specified
plan.
Expand Down
4 changes: 4 additions & 0 deletions config/crd/bases/extensions.tsuru.io_rpaasinstances.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,10 @@ spec:
planName:
description: PlanName is the name of the rpaasplan instance.
type: string
planNamespace:
description: PlanNamespace is the namespace of target plan and their
flavors, when empty uses the same namespace of instance.
type: string
planTemplate:
description: PlanTemplate allow overriding fields in the specified plan.
properties:
Expand Down
4 changes: 4 additions & 0 deletions controllers/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,10 @@ func (r *RpaasInstanceReconciler) mergeInstanceWithFlavors(ctx context.Context,
Namespace: instance.Namespace,
}

if instance.Spec.PlanNamespace != "" {
flavorObjectKey.Namespace = instance.Spec.PlanNamespace
}

var flavor extensionsv1alpha1.RpaasFlavor
if err := r.Client.Get(ctx, flavorObjectKey, &flavor); err != nil {
return nil, err
Expand Down
93 changes: 92 additions & 1 deletion controllers/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,11 @@ func TestReconcileRpaasInstance_getRpaasInstance(t *testing.T) {
instance6.Name = "instance6"
instance6.Spec.Flavors = []string{"raspberry"}

instance7 := newEmptyRpaasInstance()
instance7.Name = "instance7"
instance7.Spec.Flavors = []string{"beer"}
instance7.Spec.PlanNamespace = "rpaasv2-system"

mintFlavor := newRpaasFlavor()
mintFlavor.Name = "mint"
mintFlavor.Spec.InstanceTemplate = &v1alpha1.RpaasInstanceSpec{
Expand Down Expand Up @@ -298,6 +303,13 @@ func TestReconcileRpaasInstance_getRpaasInstance(t *testing.T) {
AllocateContainerPorts: v1alpha1.Bool(false),
}

poolNameSpacedFlavor := newRpaasFlavor()
poolNameSpacedFlavor.Name = "beer"
poolNameSpacedFlavor.Namespace = "rpaasv2-system"
poolNameSpacedFlavor.Spec.InstanceTemplate = &v1alpha1.RpaasInstanceSpec{
AllocateContainerPorts: v1alpha1.Bool(false),
}

defaultFlavor := newRpaasFlavor()
defaultFlavor.Name = "default"
defaultFlavor.Spec.Default = true
Expand Down Expand Up @@ -336,7 +348,10 @@ func TestReconcileRpaasInstance_getRpaasInstance(t *testing.T) {
},
}

resources := []runtime.Object{instance1, instance2, instance3, instance4, instance5, instance6, mintFlavor, mangoFlavor, bananaFlavor, defaultFlavor, raspberryFlavor}
resources := []runtime.Object{
instance1, instance2, instance3, instance4, instance5, instance6, instance7,
mintFlavor, mangoFlavor, bananaFlavor, defaultFlavor, poolNameSpacedFlavor, raspberryFlavor,
}

tests := []struct {
name string
Expand Down Expand Up @@ -637,6 +652,49 @@ func TestReconcileRpaasInstance_getRpaasInstance(t *testing.T) {
},
},
},
{
name: "when flavor has another namespace",
objectKey: types.NamespacedName{Name: instance7.Name, Namespace: instance7.Namespace},
expected: v1alpha1.RpaasInstance{
TypeMeta: metav1.TypeMeta{
APIVersion: "extensions.tsuru.io/v1alpha1",
Kind: "RpaasInstance",
},
ObjectMeta: metav1.ObjectMeta{
Name: instance7.Name,
Namespace: instance7.Namespace,
ResourceVersion: "999",
},
Spec: v1alpha1.RpaasInstanceSpec{
PlanName: "my-plan",
PlanNamespace: "rpaasv2-system",
Flavors: []string{"beer"},
AllocateContainerPorts: v1alpha1.Bool(false),
Service: &nginxv1alpha1.NginxService{
Annotations: map[string]string{
"default-service-annotation": "default",
},
Labels: map[string]string{
"default-service-label": "default",
"flavored-service-label": "default",
},
},
PodTemplate: nginxv1alpha1.NginxPodTemplateSpec{
Annotations: map[string]string{
"default-pod-annotation": "default",
},
Labels: map[string]string{
"mango-pod-label": "not-a-mango",
"default-pod-label": "default",
},
},
DNS: &v1alpha1.DNSConfig{
Zone: "test-zone",
TTL: func() *int32 { ttl := int32(30); return &ttl }(),
},
},
},
},
}

for _, tt := range tests {
Expand Down Expand Up @@ -1753,6 +1811,39 @@ func TestReconcile(t *testing.T) {

}

func TestReconcilePoolNamespaced(t *testing.T) {
rpaas := &v1alpha1.RpaasInstance{
ObjectMeta: metav1.ObjectMeta{
Name: "my-instance",
Namespace: "rpaasv2-my-pool",
},
Spec: v1alpha1.RpaasInstanceSpec{
PlanName: "my-plan",
PlanNamespace: "default",
},
}
plan := &v1alpha1.RpaasPlan{
ObjectMeta: metav1.ObjectMeta{
Name: "my-plan",
Namespace: "default",
},
Spec: v1alpha1.RpaasPlanSpec{
Image: "tsuru:pool-namespaces-image:test",
},
}
reconciler := newRpaasInstanceReconciler(rpaas, plan)
result, err := reconciler.Reconcile(context.TODO(), reconcile.Request{NamespacedName: types.NamespacedName{Namespace: "rpaasv2-my-pool", Name: "my-instance"}})
require.NoError(t, err)

assert.Equal(t, result, reconcile.Result{})

nginx := &nginxv1alpha1.Nginx{}
err = reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: rpaas.Name, Namespace: rpaas.Namespace}, nginx)
require.NoError(t, err)

assert.Equal(t, "tsuru:pool-namespaces-image:test", nginx.Spec.Image)
}

func resourceMustParsePtr(fmt string) *resource.Quantity {
qty := resource.MustParse(fmt)
return &qty
Expand Down
4 changes: 4 additions & 0 deletions controllers/rpaasinstance_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ func (r *RpaasInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Reques
Name: instance.Spec.PlanName,
Namespace: instance.Namespace,
}
if instance.Spec.PlanNamespace != "" {
planName.Namespace = instance.Spec.PlanNamespace
}

plan := &extensionsv1alpha1.RpaasPlan{}
err = r.Client.Get(ctx, planName, plan)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type RpaasConfig struct {
WebSocketAllowedOrigins []string `json:"websocket-allowed-origins"`
SuppressPrivateKeyOnCertificatesList bool `json:"suppress-private-key-on-certificates-list"`
MultiCluster bool `json:"multi-cluster"`
NamespacedInstances bool `json:"namespaced-instances"`
Clusters []ClusterConfig `json:"clusters"`
ConfigDenyPatterns []regexp.Regexp `json:"config-deny-patterns"`
}
Expand Down
5 changes: 5 additions & 0 deletions internal/pkg/rpaas/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@
package rpaas

import (
"github.com/pkg/errors"
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
)

var (
ErrNoPoolDefined = errors.New("No pool defined")
)

type ValidationError struct {
Msg string
}
Expand Down
48 changes: 45 additions & 3 deletions internal/pkg/rpaas/k8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,16 @@ type k8sRpaasManager struct {
restConfig *rest.Config
kcs *kubernetes.Clientset
clusterName string
poolName string
}

func NewK8S(cfg *rest.Config, k8sClient client.Client, clusterName string) (RpaasManager, error) {
func NewK8S(cfg *rest.Config, k8sClient client.Client, clusterName string, poolName string) (RpaasManager, error) {
m := &k8sRpaasManager{
cli: k8sClient,
cacheManager: nginxManager.NewNginxManager(),
restConfig: cfg,
clusterName: clusterName,
poolName: poolName,
}

if cfg == nil {
Expand Down Expand Up @@ -244,6 +246,10 @@ func (m *k8sRpaasManager) CreateInstance(ctx context.Context, args CreateArgs) e
RolloutNginxOnce: true,
}

if config.Get().NamespacedInstances {
instance.Spec.PlanNamespace = getServiceName()
}

setDescription(instance, args.Description)
instance.SetTeamOwner(args.Team)
if m.clusterName != "" {
Expand Down Expand Up @@ -298,7 +304,17 @@ func (m *k8sRpaasManager) UpdateInstance(ctx context.Context, instanceName strin
}

func (m *k8sRpaasManager) ensureNamespaceExists(ctx context.Context) (string, error) {
nsName := getServiceName()
var nsName string
poolNamespace, err := m.poolNamespace()
if err != nil {
return "", err
}
if poolNamespace != "" {
nsName = poolNamespace
} else {
nsName = getServiceName()
}

ns := newNamespace(nsName)
if err := m.cli.Create(ctx, &ns); err != nil && !k8sErrors.IsAlreadyExists(err) {
return "", err
Expand Down Expand Up @@ -710,7 +726,22 @@ func (m *k8sRpaasManager) GetInstanceAddress(ctx context.Context, name string) (

func (m *k8sRpaasManager) GetInstance(ctx context.Context, name string) (*v1alpha1.RpaasInstance, error) {
var instance v1alpha1.RpaasInstance
err := m.cli.Get(ctx, types.NamespacedName{Name: name, Namespace: namespaceName()}, &instance)

poolNamespace, err := m.poolNamespace()
if err != nil {
return nil, err
}
if poolNamespace != "" {
err = m.cli.Get(ctx, types.NamespacedName{Name: name, Namespace: poolNamespace}, &instance)
if err != nil && !k8sErrors.IsNotFound(err) {
return nil, err
}

if err == nil {
return &instance, nil
}
}
err = m.cli.Get(ctx, types.NamespacedName{Name: name, Namespace: namespaceName()}, &instance)
if err != nil && k8sErrors.IsNotFound(err) {
return nil, NotFoundError{Msg: fmt.Sprintf("rpaas instance %q not found", name)}
}
Expand Down Expand Up @@ -779,6 +810,17 @@ func (m *k8sRpaasManager) GetFlavors(ctx context.Context) ([]Flavor, error) {
return result, nil
}

func (m *k8sRpaasManager) poolNamespace() (string, error) {
if config.Get().NamespacedInstances {
if m.poolName == "" {
return "", ErrNoPoolDefined
}
return fmt.Sprintf("%s-%s", defaultNamespace, m.poolName), nil
}

return "", nil
}

func (m *k8sRpaasManager) getFlavors(ctx context.Context) ([]v1alpha1.RpaasFlavor, error) {
flavorList := &v1alpha1.RpaasFlavorList{}
if err := m.cli.List(ctx, flavorList, client.InNamespace(namespaceName())); err != nil {
Expand Down
67 changes: 66 additions & 1 deletion internal/pkg/rpaas/k8s_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3033,6 +3033,7 @@ func Test_k8sRpaasManager_CreateInstance(t *testing.T) {
expectedError string
extraConfig config.RpaasConfig
clusterName string // to simulate a multi-cluster environment
poolName string // to simulate a pool-namespaced environment
}{
{
name: "without name",
Expand Down Expand Up @@ -3181,6 +3182,67 @@ func Test_k8sRpaasManager_CreateInstance(t *testing.T) {
},
},
},
{
name: "pool-namespaced",
args: CreateArgs{Name: "r1", Team: "t1"},
clusterName: "cluster-01",
poolName: "my-pool",
extraConfig: config.RpaasConfig{
NamespacedInstances: true,
},
expected: v1alpha1.RpaasInstance{
TypeMeta: metav1.TypeMeta{
Kind: "RpaasInstance",
APIVersion: "extensions.tsuru.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "r1",
Namespace: "rpaasv2-my-pool",
ResourceVersion: "1",
Annotations: map[string]string{
"rpaas.extensions.tsuru.io/description": "",
"rpaas.extensions.tsuru.io/tags": "",
"rpaas.extensions.tsuru.io/team-owner": "t1",
"rpaas.extensions.tsuru.io/cluster-name": "cluster-01",
},
Labels: map[string]string{
"rpaas.extensions.tsuru.io/service-name": "rpaasv2",
"rpaas.extensions.tsuru.io/instance-name": "r1",
"rpaas.extensions.tsuru.io/team-owner": "t1",
"rpaas.extensions.tsuru.io/cluster-name": "cluster-01",
"rpaas_service": "rpaasv2",
"rpaas_instance": "r1",
},
},
Spec: v1alpha1.RpaasInstanceSpec{
Replicas: &one,
PlanName: "plan1",
PlanNamespace: "rpaasv2",
Service: &nginxv1alpha1.NginxService{
Type: corev1.ServiceTypeLoadBalancer,
Labels: map[string]string{
"rpaas.extensions.tsuru.io/service-name": "rpaasv2",
"rpaas.extensions.tsuru.io/instance-name": "r1",
"rpaas.extensions.tsuru.io/team-owner": "t1",
"rpaas.extensions.tsuru.io/cluster-name": "cluster-01",
"rpaas_service": "rpaasv2",
"rpaas_instance": "r1",
},
},
PodTemplate: nginxv1alpha1.NginxPodTemplateSpec{
Labels: map[string]string{
"rpaas.extensions.tsuru.io/service-name": "rpaasv2",
"rpaas.extensions.tsuru.io/instance-name": "r1",
"rpaas.extensions.tsuru.io/team-owner": "t1",
"rpaas.extensions.tsuru.io/cluster-name": "cluster-01",
"rpaas_service": "rpaasv2",
"rpaas_instance": "r1",
},
},
RolloutNginxOnce: true,
},
},
},
{
name: "with override",
args: CreateArgs{Name: "r1", Team: "t1", Tags: []string{`plan-override={"config": {"cacheEnabled": false}}`}},
Expand Down Expand Up @@ -3441,7 +3503,10 @@ func Test_k8sRpaasManager_CreateInstance(t *testing.T) {
config.Set(baseConfig)
defer config.Set(config.RpaasConfig{})
scheme := newScheme()
manager := &k8sRpaasManager{cli: fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(resources...).Build(), clusterName: tt.clusterName}
manager := &k8sRpaasManager{
cli: fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(resources...).Build(), clusterName: tt.clusterName,
poolName: tt.poolName,
}
err := manager.CreateInstance(context.Background(), tt.args)
if tt.expectedError != "" {
assert.EqualError(t, err, tt.expectedError)
Expand Down
2 changes: 1 addition & 1 deletion internal/web/target/local-cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func NewKubeConfigFactory() (Factory, error) {
return nil, err
}

manager, err := rpaas.NewK8S(restConfig, k8sClient, "")
manager, err := rpaas.NewK8S(restConfig, k8sClient, "", "")
if err != nil {
return nil, err
}
Expand Down
Loading

0 comments on commit f57084c

Please sign in to comment.