Skip to content
This repository was archived by the owner on Sep 9, 2026. It is now read-only.
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
27 changes: 24 additions & 3 deletions api/v1alpha1/tenant_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,23 @@ const (
TenantReasonMultipleDefaultsFound = "MultipleDefaultsFound"
)

// ResolvedStorageClass captures a single resolved StorageClass for a specific
// storage tier. The Tenant controller populates one entry per tier.
type ResolvedStorageClass struct {
// Name is the name of the resolved Kubernetes StorageClass.
// +kubebuilder:validation:Required
// +kubebuilder:validation:MinLength=1
Name string `json:"name"`

// Tier is the storage tier this StorageClass provides,
// taken from the osac.openshift.io/storage-tier label.
// +kubebuilder:validation:Required
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=63
// +kubebuilder:validation:Pattern=`^[a-z0-9]([a-z0-9._-]*[a-z0-9])?$`
Tier string `json:"tier"`
}

// TenantStatus defines the observed state of Tenant.
type TenantStatus struct {
// Phase is the phase of the tenant
Expand All @@ -64,8 +81,12 @@ type TenantStatus struct {
// Namespace is the namespace allocated to the tenant on the target cluster
Namespace string `json:"namespace,omitempty"`

// StorageClass is the StorageClass allocated to the tenant on the target cluster
StorageClass string `json:"storageClass,omitempty"`
// StorageClasses lists all resolved StorageClass mappings for the tenant,
// one per storage tier.
// +kubebuilder:validation:Optional
// +listType=map
// +listMapKey=tier
StorageClasses []ResolvedStorageClass `json:"storageClasses,omitempty"`
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Conditions holds an array of metav1.Condition that describe the state of the Tenant
// +kubebuilder:validation:Optional
Expand All @@ -75,7 +96,7 @@ type TenantStatus struct {
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Tenant Namespace",type=string,JSONPath=`.status.namespace`
// +kubebuilder:printcolumn:name="Storage Class",type=string,JSONPath=`.status.storageClass`
// +kubebuilder:printcolumn:name="Storage Tiers",type=string,JSONPath=`.status.storageClasses[*].tier`
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`

// Tenant is the Schema for the tenants API.
Expand Down
20 changes: 20 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 31 additions & 6 deletions config/crd/bases/osac.openshift.io_tenants.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ spec:
- jsonPath: .status.namespace
name: Tenant Namespace
type: string
- jsonPath: .status.storageClass
name: Storage Class
- jsonPath: .status.storageClasses[*].tier
name: Storage Tiers
type: string
- jsonPath: .status.phase
name: Phase
Expand Down Expand Up @@ -117,10 +117,35 @@ spec:
phase:
description: Phase is the phase of the tenant
type: string
storageClass:
description: StorageClass is the StorageClass allocated to the tenant
on the target cluster
type: string
storageClasses:
description: |-
StorageClasses lists all resolved StorageClass mappings for the tenant,
one per storage tier.
items:
description: |-
ResolvedStorageClass captures a single resolved StorageClass for a specific
storage tier. The Tenant controller populates one entry per tier.
properties:
name:
description: Name is the name of the resolved Kubernetes StorageClass.
minLength: 1
type: string
tier:
description: |-
Tier is the storage tier this StorageClass provides,
taken from the osac.openshift.io/storage-tier label.
maxLength: 63
minLength: 1
pattern: ^[a-z0-9]([a-z0-9._-]*[a-z0-9])?$
type: string
required:
- name
- tier
type: object
type: array
x-kubernetes-list-map-keys:
- tier
x-kubernetes-list-type: map
type: object
type: object
served: true
Expand Down
14 changes: 9 additions & 5 deletions internal/controller/tenant_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func (r *TenantReconciler) handleUpdate(ctx context.Context, req reconcile.Reque
// Ready. Any early return below leaves the status in a clean Progressing state.
instance.Status.Phase = v1alpha1.TenantPhaseProgressing
instance.Status.Namespace = ""
instance.Status.StorageClass = ""
instance.Status.StorageClasses = nil

// Get target cluster client where namespace, StorageClass, and UDN are reconciled
targetClient, err := r.getTargetClient(ctx)
Expand Down Expand Up @@ -166,7 +166,9 @@ func (r *TenantReconciler) handleUpdate(ctx context.Context, req reconcile.Reque
scResult.message)

instance.Status.Namespace = namespace.GetName()
instance.Status.StorageClass = scResult.name
instance.Status.StorageClasses = []v1alpha1.ResolvedStorageClass{
{Name: scResult.name, Tier: "default"},
}
instance.Status.Phase = v1alpha1.TenantPhaseReady

return ctrl.Result{}, nil
Expand Down Expand Up @@ -379,11 +381,13 @@ func (r *TenantReconciler) SetupWithManager(mgr mcmanager.Manager) error {
}

// storageClassTenantPredicate returns a predicate that passes only StorageClasses
// carrying the osac.openshift.io/tenant label (any value).
// carrying both the osac.openshift.io/tenant and osac.openshift.io/storage-tier labels.
func storageClassTenantPredicate() predicate.Predicate {
return predicate.NewPredicateFuncs(func(obj client.Object) bool {
_, exists := obj.GetLabels()[osacTenantAnnotation]
return exists
labels := obj.GetLabels()
_, hasTenant := labels[osacTenantAnnotation]
_, hasTier := labels[osacStorageTierLabel]
return hasTenant && hasTier
})
}
Comment on lines 385 to 392

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The predicate requires both labels, but getTenantStorageClass (line 206) still queries by tenant label only. This is intentionally ahead of the resolution logic until the full multi-tier work lands (EP #32). Consider adding a brief code comment noting that StorageClasses without the storage-tier label won't trigger reconciliation, so developers working on this area understand the mismatch is intentional while multi-tier resolution is in progress.


Expand Down
9 changes: 7 additions & 2 deletions internal/controller/tenant_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ var _ = Describe("Tenant Controller", func() {
// found) while still correctly setting status conditions.
reconcileAndAssertStatus := func(
expectedPhase v1alpha1.TenantPhaseType,
expectedSC string,
expectedSCName string,
expectedNSStatus metav1.ConditionStatus,
expectedNSReason string,
expectedSCStatus metav1.ConditionStatus,
Expand All @@ -105,7 +105,12 @@ var _ = Describe("Tenant Controller", func() {
_ = doReconcile()
g.Expect(k8sClient.Get(ctx, typeNamespacedName, tenant)).To(Succeed())
g.Expect(tenant.Status.Phase).To(Equal(expectedPhase))
g.Expect(tenant.Status.StorageClass).To(Equal(expectedSC))
if expectedSCName == "" {
g.Expect(tenant.Status.StorageClasses).To(BeNil())
} else {
g.Expect(tenant.Status.StorageClasses).To(HaveLen(1))
g.Expect(tenant.Status.StorageClasses[0].Name).To(Equal(expectedSCName))
}

nsCond := tenant.GetStatusCondition(v1alpha1.TenantConditionNamespaceReady)
g.Expect(nsCond).NotTo(BeNil())
Expand Down
3 changes: 3 additions & 0 deletions internal/controller/tenant_names.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,7 @@ var (

// osacTenantAnnotation is the annotation used to reference the tenant name
osacTenantAnnotation string = fmt.Sprintf("%s/tenant", osacPrefix)

// osacStorageTierLabel is the label key that identifies the storage tier of a StorageClass
osacStorageTierLabel string = fmt.Sprintf("%s/storage-tier", osacPrefix)
)
Loading