Skip to content

Commit 54d7039

Browse files
committed
feat: add remote cluster Liqo version detection
1 parent fd1d6f8 commit 54d7039

File tree

17 files changed

+1251
-10
lines changed

17 files changed

+1251
-10
lines changed

apis/authentication/v1beta1/tenant_types.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,14 @@ type TenantSpec struct {
7676
// +kubebuilder:validation:Enum=Active;Cordoned;Drained
7777
// +kubebuilder:default=Active
7878
TenantCondition TenantCondition `json:"tenantCondition,omitempty"`
79+
// ConsumerAPIServerURL is the URL of the consumer cluster's API server.
80+
// This is used by the provider cluster to fetch the consumer's Liqo version.
81+
// +kubebuilder:validation:Optional
82+
ConsumerAPIServerURL string `json:"consumerAPIServerURL,omitempty"`
83+
// ConsumerVersionReaderToken is the token used to authenticate when fetching the consumer's Liqo version.
84+
// This should be the token from the liqo-version-reader ServiceAccount on the consumer cluster.
85+
// +kubebuilder:validation:Optional
86+
ConsumerVersionReaderToken string `json:"consumerVersionReaderToken,omitempty"`
7987
}
8088

8189
// TenantCondition contains the conditions of the tenant.

apis/core/v1beta1/foreigncluster_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ type ForeignClusterStatus struct {
6767
// +kubebuilder:validation:Optional
6868
TenantNamespace TenantNamespaceType `json:"tenantNamespace"`
6969

70+
// RemoteVersion is the Liqo version running on the remote cluster.
71+
// +kubebuilder:validation:Optional
72+
RemoteVersion string `json:"remoteVersion,omitempty"`
73+
7074
// Generic conditions related to the foreign cluster.
7175
Conditions []Condition `json:"conditions,omitempty"`
7276
}

cmd/liqo-controller-manager/main.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import (
5252
foreignclustercontroller "github.com/liqotech/liqo/pkg/liqo-controller-manager/core/foreigncluster-controller"
5353
ipmapping "github.com/liqotech/liqo/pkg/liqo-controller-manager/ipmapping"
5454
quotacreatorcontroller "github.com/liqotech/liqo/pkg/liqo-controller-manager/quotacreator-controller"
55+
versionpkg "github.com/liqotech/liqo/pkg/liqo-controller-manager/version"
5556
virtualnodecreatorcontroller "github.com/liqotech/liqo/pkg/liqo-controller-manager/virtualnodecreator-controller"
5657
tenantnamespace "github.com/liqotech/liqo/pkg/tenantNamespace"
5758
dynamicutils "github.com/liqotech/liqo/pkg/utils/dynamic"
@@ -163,6 +164,13 @@ func run(cmd *cobra.Command, _ []string) error {
163164
return fmt.Errorf("unable to setup the indexer for the Pod nodeName field: %w", err)
164165
}
165166

167+
// Setup version resources (ConfigMap, Role, RoleBinding) for remote clusters to read the local Liqo version.
168+
// Read the version from the liqo-controller-manager deployment's image tag
169+
liqoVersion := versionpkg.GetVersionFromDeployment(cmd.Context(), clientset, opts.LiqoNamespace, "liqo-controller-manager")
170+
if err := versionpkg.SetupVersionResources(cmd.Context(), clientset, opts.LiqoNamespace, liqoVersion); err != nil {
171+
return fmt.Errorf("unable to setup version resources: %w", err)
172+
}
173+
166174
namespaceManager := tenantnamespace.NewCachedManager(cmd.Context(), clientset, scheme)
167175

168176
// Setup operators for each module:
@@ -285,6 +293,9 @@ func run(cmd *cobra.Command, _ []string) error {
285293
AuthenticationEnabled: opts.AuthenticationEnabled,
286294
OffloadingEnabled: opts.OffloadingEnabled,
287295

296+
IdentityManager: idManager,
297+
LiqoNamespace: opts.LiqoNamespace,
298+
288299
APIServerCheckers: foreignclustercontroller.NewAPIServerCheckers(idManager, opts.ForeignClusterPingInterval, opts.ForeignClusterPingTimeout),
289300
}
290301
if err = foreignClusterReconciler.SetupWithManager(mgr, opts.ForeignClusterWorkers); err != nil {

deployments/liqo/charts/liqo-crds/crds/authentication.liqo.io_tenants.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,16 @@ spec:
6666
x-kubernetes-validations:
6767
- message: ClusterID is immutable
6868
rule: self == oldSelf
69+
consumerAPIServerURL:
70+
description: |-
71+
ConsumerAPIServerURL is the URL of the consumer cluster's API server.
72+
This is used by the provider cluster to fetch the consumer's Liqo version.
73+
type: string
74+
consumerVersionReaderToken:
75+
description: |-
76+
ConsumerVersionReaderToken is the token used to authenticate when fetching the consumer's Liqo version.
77+
This should be the token from the liqo-version-reader ServiceAccount on the consumer cluster.
78+
type: string
6979
csr:
7080
description: CSR is the Certificate Signing Request of the tenant
7181
cluster.

deployments/liqo/charts/liqo-crds/crds/core.liqo.io_foreignclusters.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,10 @@ spec:
322322
- networking
323323
- offloading
324324
type: object
325+
remoteVersion:
326+
description: RemoteVersion is the Liqo version running on the remote
327+
cluster.
328+
type: string
325329
role:
326330
default: Unknown
327331
description: Role of the ForeignCluster.

deployments/liqo/files/liqo-controller-manager-ClusterRole.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
rules:
2+
- apiGroups:
3+
- ""
4+
resources:
5+
- configmaps
6+
verbs:
7+
- create
8+
- get
9+
- update
210
- apiGroups:
311
- ""
412
resources:
@@ -334,6 +342,14 @@ rules:
334342
- patch
335343
- update
336344
- watch
345+
- apiGroups:
346+
- rbac.authorization.k8s.io
347+
resources:
348+
- roles
349+
verbs:
350+
- create
351+
- get
352+
- update
337353
- apiGroups:
338354
- storage
339355
- storage.k8s.io

docs/usage/version-detection.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
## Summary
2+
Enables querying a remote Liqo cluster's version without establishing full peering, using only a minimal authentication token.
3+
4+
## Motivation
5+
Before initiating peering, administrators need to check version compatibility between clusters. This feature allows version queries without full peering setup.
6+
7+
## Changes
8+
9+
### Version Query Infrastructure
10+
- Added `QueryRemoteVersion()` function for standalone version queries
11+
- Created `liqo-version` ConfigMap to expose local cluster version
12+
- Set up `liqo-version-reader` ServiceAccount with minimal RBAC permissions
13+
- Added token-based authentication for reading version ConfigMap
14+
15+
### Helper Functions
16+
- `GetLocalVersion()`: Retrieve local cluster version from ConfigMap
17+
- `GetVersionReaderToken()`: Extract token from secret
18+
- `GetRemoteVersionWithToken()`: Query remote version with minimal auth
19+
20+
### Supporting Features
21+
- Version resources auto-created at liqo-controller-manager startup
22+
- ForeignCluster auto-creation for tenant consumers (enables bidirectional detection)
23+
- Comprehensive unit tests (21 test specs)
24+
25+
## Testing
26+
- ✅ All version package unit tests pass (17/17 specs)
27+
- ✅ Tenant controller tests added
28+
-`make generate` runs successfully
29+
- ✅ RBAC auto-generated correctly
30+
31+
## Usage Example
32+
```bash
33+
# Extract token from consumer cluster
34+
kubectl get secret -n liqo liqo-version-reader-token \
35+
-o jsonpath='{.data.token}' | base64 -d > token
36+
37+
# Query version from any cluster
38+
kubectl --server=https://consumer-cluster:6443 \
39+
--token="$(cat token)" \
40+
--insecure-skip-tls-verify \
41+
get configmap liqo-version -n liqo \
42+
-o jsonpath='{.data.version}'

pkg/liqo-controller-manager/authentication/forge/tenant.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ import (
2525

2626
// TenantForRemoteCluster forges a Tenant resource to be applied on a remote cluster.
2727
func TenantForRemoteCluster(localClusterID liqov1beta1.ClusterID,
28-
publicKey, csr, signature []byte, namespace, proxyURL *string) *authv1beta1.Tenant {
28+
publicKey, csr, signature []byte, namespace, proxyURL *string, apiServerURL, versionReaderToken string) *authv1beta1.Tenant {
2929
tenant := Tenant(localClusterID, namespace)
30-
MutateTenant(tenant, localClusterID, publicKey, csr, signature, proxyURL)
30+
MutateTenant(tenant, localClusterID, publicKey, csr, signature, proxyURL, apiServerURL, versionReaderToken)
3131

3232
return tenant
3333
}
@@ -48,7 +48,7 @@ func Tenant(remoteClusterID liqov1beta1.ClusterID, namespace *string) *authv1bet
4848

4949
// MutateTenant mutates a Tenant resource.
5050
func MutateTenant(tenant *authv1beta1.Tenant, remoteClusterID liqov1beta1.ClusterID,
51-
publicKey, csr, signature []byte, proxyURL *string) {
51+
publicKey, csr, signature []byte, proxyURL *string, apiServerURL, versionReaderToken string) {
5252
if tenant.Labels == nil {
5353
tenant.Labels = map[string]string{}
5454
}
@@ -60,10 +60,12 @@ func MutateTenant(tenant *authv1beta1.Tenant, remoteClusterID liqov1beta1.Cluste
6060
}
6161

6262
tenant.Spec = authv1beta1.TenantSpec{
63-
ClusterID: remoteClusterID,
64-
PublicKey: publicKey,
65-
CSR: csr,
66-
Signature: signature,
67-
ProxyURL: proxyURLPtr,
63+
ClusterID: remoteClusterID,
64+
PublicKey: publicKey,
65+
CSR: csr,
66+
Signature: signature,
67+
ProxyURL: proxyURLPtr,
68+
ConsumerAPIServerURL: apiServerURL,
69+
ConsumerVersionReaderToken: versionReaderToken,
6870
}
6971
}

pkg/liqo-controller-manager/authentication/tenant-controller/tenant_controller.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
corev1 "k8s.io/api/core/v1"
2222
rbacv1 "k8s.io/api/rbac/v1"
2323
apierrors "k8s.io/apimachinery/pkg/api/errors"
24+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2425
"k8s.io/apimachinery/pkg/runtime"
2526
"k8s.io/client-go/rest"
2627
"k8s.io/client-go/tools/record"
@@ -30,6 +31,7 @@ import (
3031
controllerutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3132

3233
authv1beta1 "github.com/liqotech/liqo/apis/authentication/v1beta1"
34+
liqov1beta1 "github.com/liqotech/liqo/apis/core/v1beta1"
3335
"github.com/liqotech/liqo/pkg/consts"
3436
identitymanager "github.com/liqotech/liqo/pkg/identityManager"
3537
"github.com/liqotech/liqo/pkg/liqo-controller-manager/authentication"
@@ -94,6 +96,7 @@ func NewTenantReconciler(cl client.Client, scheme *runtime.Scheme, config *rest.
9496
// cluster-role
9597
// +kubebuilder:rbac:groups=authentication.liqo.io,resources=tenants;tenants/status,verbs=get;list;watch;create;update;patch;delete
9698
// +kubebuilder:rbac:groups=authentication.liqo.io,resources=tenants;tenants/finalizers,verbs=update
99+
// +kubebuilder:rbac:groups=core.liqo.io,resources=foreignclusters,verbs=get;list;watch;create;update;patch
97100
// +kubebuilder:rbac:groups=core,resources=namespaces,verbs=get;list;watch;create;update;patch;delete
98101
// +kubebuilder:rbac:groups=core,resources=namespaces/finalizers,verbs=update
99102
// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings,verbs=get;list;watch;create;update;patch;deletecollection;delete
@@ -265,6 +268,12 @@ func (r *TenantReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res
265268
}
266269
}
267270

271+
// Ensure a ForeignCluster exists for the consumer cluster to enable bidirectional version detection
272+
if err := r.ensureForeignCluster(ctx, tenant); err != nil {
273+
klog.Errorf("Unable to ensure ForeignCluster for Tenant %q: %s", req.Name, err)
274+
// Don't fail the reconciliation if ForeignCluster creation fails, just log it
275+
}
276+
268277
return ctrl.Result{}, nil
269278
}
270279

@@ -375,6 +384,53 @@ func (r *TenantReconciler) handleTenantUncordoned(ctx context.Context, tenant *a
375384
return nil
376385
}
377386

387+
// ensureForeignCluster ensures a ForeignCluster exists for the consumer cluster.
388+
// This enables bidirectional version detection even in unidirectional peering scenarios.
389+
func (r *TenantReconciler) ensureForeignCluster(ctx context.Context, tenant *authv1beta1.Tenant) error {
390+
clusterID := tenant.Spec.ClusterID
391+
392+
// Check if a ForeignCluster already exists
393+
var existingFC liqov1beta1.ForeignCluster
394+
err := r.Get(ctx, client.ObjectKey{Name: string(clusterID)}, &existingFC)
395+
if err == nil {
396+
// ForeignCluster already exists, nothing to do
397+
klog.V(6).Infof("ForeignCluster for consumer cluster %q already exists", clusterID)
398+
return nil
399+
}
400+
401+
if !apierrors.IsNotFound(err) {
402+
return fmt.Errorf("failed to check for existing ForeignCluster: %w", err)
403+
}
404+
405+
// ForeignCluster doesn't exist, create it
406+
foreignCluster := &liqov1beta1.ForeignCluster{
407+
ObjectMeta: metav1.ObjectMeta{
408+
Name: string(clusterID),
409+
Labels: map[string]string{
410+
consts.RemoteClusterID: string(clusterID),
411+
},
412+
},
413+
Spec: liqov1beta1.ForeignClusterSpec{
414+
ClusterID: clusterID,
415+
},
416+
}
417+
418+
if err := r.Create(ctx, foreignCluster); err != nil {
419+
if apierrors.IsAlreadyExists(err) {
420+
// Race condition - another controller created it
421+
klog.V(6).Infof("ForeignCluster for consumer cluster %q was created concurrently", clusterID)
422+
return nil
423+
}
424+
return fmt.Errorf("failed to create ForeignCluster: %w", err)
425+
}
426+
427+
klog.Infof("Created ForeignCluster for consumer cluster %q", clusterID)
428+
r.EventRecorder.Event(tenant, corev1.EventTypeNormal, "ForeignClusterCreated",
429+
fmt.Sprintf("ForeignCluster created for consumer cluster %s", clusterID))
430+
431+
return nil
432+
}
433+
378434
func (r *TenantReconciler) handleTenantDrained(ctx context.Context, tenant *authv1beta1.Tenant) error {
379435
// Delete binding of cluster roles cluster wide
380436
if err := r.NamespaceManager.UnbindClusterRolesClusterWide(ctx, tenant.Spec.ClusterID,

0 commit comments

Comments
 (0)