mirror of
https://github.com/Gradiant/open5gs-legacy-operator.git
synced 2025-10-23 08:12:10 +00:00
252 lines
11 KiB
Go
252 lines
11 KiB
Go
package controller
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
netv1 "gradiant/open5gs-legacy-operator/api/v1"
|
|
"log"
|
|
"strings"
|
|
"time"
|
|
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
"go.mongodb.org/mongo-driver/mongo"
|
|
"go.mongodb.org/mongo-driver/mongo/options"
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/errors"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
ctrl "sigs.k8s.io/controller-runtime"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
)
|
|
|
|
// Core resources
|
|
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete
|
|
//+kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete
|
|
//+kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;create;update;patch;delete
|
|
//+kubebuilder:rbac:groups="",resources=serviceaccounts,verbs=get;list;watch;create;update;patch;delete
|
|
//+kubebuilder:rbac:groups="",resources=persistentvolumeclaims,verbs=get;list;watch;create;update;patch;delete
|
|
//+kubebuilder:rbac:groups="",resources=pods,verbs=get;list;watch;create;update;patch;delete
|
|
//+kubebuilder:rbac:groups="",resources=events,verbs=get;list;watch;create;update;patch
|
|
//+kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch
|
|
|
|
// net group resources
|
|
//+kubebuilder:rbac:groups=net.gradiant.org,resources=open5gs,verbs=get;list;watch;create;update;patch;delete
|
|
//+kubebuilder:rbac:groups=net.gradiant.org,resources=open5gs/status,verbs=get;update;patch
|
|
//+kubebuilder:rbac:groups=net.gradiant.org,resources=open5gs/finalizers,verbs=update
|
|
//+kubebuilder:rbac:groups=net.gradiant.org,resources=open5gsusers,verbs=get;list;watch;create;update;patch;delete
|
|
|
|
// Apps resources
|
|
//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
|
|
//+kubebuilder:rbac:groups=apps,resources=statefulsets,verbs=get;list;watch;create;update;patch;delete
|
|
|
|
// Batch resources
|
|
//+kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;patch;delete
|
|
//+kubebuilder:rbac:groups=batch,resources=cronjobs,verbs=get;list;watch;create;update;patch;delete
|
|
|
|
// RBAC resources
|
|
//+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles,verbs=get;list;watch;create;update;patch;delete
|
|
//+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings,verbs=get;list;watch;create;update;patch;delete
|
|
|
|
// Autoscaling resources
|
|
//+kubebuilder:rbac:groups=autoscaling,resources=horizontalpodautoscalers,verbs=get;list;watch;create;update;patch;delete
|
|
|
|
// Policy resources
|
|
//+kubebuilder:rbac:groups=policy,resources=poddisruptionbudgets,verbs=get;list;watch;create;update;patch;delete
|
|
|
|
// Network resources
|
|
//+kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=get;list;watch;create;update;patch;delete
|
|
//+kubebuilder:rbac:groups=networking.k8s.io,resources=networkpolicies,verbs=get;list;watch;create;update;patch;delete
|
|
|
|
// Monitoring resources
|
|
//+kubebuilder:rbac:groups=monitoring.coreos.com,resources=servicemonitors,verbs=get;list;watch;create;update;patch;delete
|
|
|
|
// Helm-specific resources
|
|
//+kubebuilder:rbac:groups=helm.toolkit.fluxcd.io,resources=helmreleases,verbs=get;list;watch;create;update;patch;delete
|
|
|
|
const (
|
|
Open5GSUserFinalizer = "finalizer.open5gsuser.net.gradiant.org/user"
|
|
lastAppliedConfig = "kubectl.kubernetes.io/last-applied-configuration"
|
|
)
|
|
|
|
type Open5GSUserReconciler struct {
|
|
client.Client
|
|
Scheme *runtime.Scheme
|
|
}
|
|
|
|
func (r *Open5GSUserReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
|
var Open5GSUser netv1.Open5GSUser
|
|
var ipService string
|
|
var err error
|
|
if err := r.Get(ctx, req.NamespacedName, &Open5GSUser); err != nil {
|
|
if errors.IsNotFound(err) {
|
|
return ctrl.Result{}, nil
|
|
}
|
|
log.Printf("Error retrieving Open5GSUser %s: %v", req.NamespacedName, err)
|
|
return ctrl.Result{}, err
|
|
}
|
|
serviceName := fmt.Sprintf("%s-mongodb", strings.ToLower(Open5GSUser.Spec.Open5GS.Name))
|
|
if Open5GSUser.Spec.Open5GS.Namespace == "" {
|
|
ipService, err = r.GetServiceIp(ctx, serviceName, Open5GSUser.Namespace)
|
|
} else {
|
|
ipService, err = r.GetServiceIp(ctx, serviceName, Open5GSUser.Spec.Open5GS.Namespace)
|
|
}
|
|
|
|
if err != nil {
|
|
log.Printf("Failed to get IP for service %s: %v", serviceName, err)
|
|
return ctrl.Result{}, err
|
|
}
|
|
|
|
mongoURI := "mongodb://" + ipService + ":27017"
|
|
|
|
if Open5GSUser.ObjectMeta.DeletionTimestamp.IsZero() {
|
|
// Resource is not being deleted
|
|
if !containsString(Open5GSUser.ObjectMeta.Finalizers, Open5GSUserFinalizer) {
|
|
log.Printf("Creating subscriber IMSI: %s", Open5GSUser.Spec.IMSI)
|
|
if Open5GSUser.Spec.SST != "" || Open5GSUser.Spec.SD != "" {
|
|
// SST or SD specified, call function handling slice.
|
|
if err := addSubscriberWithSlice(Open5GSUser, mongoURI); err != nil {
|
|
log.Printf("Failed to create subscriber with slice %s", err)
|
|
return ctrl.Result{}, err
|
|
}
|
|
log.Printf("Subscriber with slice created successfully IMSI: %s", Open5GSUser.Spec.IMSI)
|
|
Open5GSUser.ObjectMeta.Finalizers = append(Open5GSUser.ObjectMeta.Finalizers, Open5GSUserFinalizer)
|
|
if err := r.Update(ctx, &Open5GSUser); err != nil {
|
|
return ctrl.Result{}, err
|
|
}
|
|
} else if Open5GSUser.Spec.APN != "" {
|
|
// APN specified without SST and SD, use function for default APN.
|
|
if err := addSubscriberWithAPN(Open5GSUser, mongoURI); err != nil {
|
|
log.Printf("Failed to create subscriber with APN %s", err)
|
|
return ctrl.Result{}, err
|
|
}
|
|
log.Printf("Subscriber with APN created successfully IMSI: %s", Open5GSUser.Spec.IMSI)
|
|
Open5GSUser.ObjectMeta.Finalizers = append(Open5GSUser.ObjectMeta.Finalizers, Open5GSUserFinalizer)
|
|
if err := r.Update(ctx, &Open5GSUser); err != nil {
|
|
return ctrl.Result{}, err
|
|
}
|
|
} else {
|
|
// If SST, SD, and APN not specified but Key and OPC are, use defaults.
|
|
if Open5GSUser.Spec.Key != "" && Open5GSUser.Spec.OPC != "" {
|
|
if err := addSubscriberWithDefaults(Open5GSUser, mongoURI); err != nil {
|
|
log.Printf("Failed to create subscriber with defaults %s", err)
|
|
return ctrl.Result{}, err
|
|
}
|
|
log.Printf("Subscriber with defaults created successfully IMSI: %s", Open5GSUser.Spec.IMSI)
|
|
Open5GSUser.ObjectMeta.Finalizers = append(Open5GSUser.ObjectMeta.Finalizers, Open5GSUserFinalizer)
|
|
if err := r.Update(ctx, &Open5GSUser); err != nil {
|
|
return ctrl.Result{}, err
|
|
}
|
|
} else {
|
|
// Handle the case where necessary data is not provided.
|
|
log.Printf("Cannot create subscriber, missing key data IMSI: %s", Open5GSUser.Spec.IMSI)
|
|
return ctrl.Result{}, fmt.Errorf("insufficient data provided for subscriber creation")
|
|
}
|
|
}
|
|
} else {
|
|
// Resource already has the finalizer
|
|
var subscriber bson.M
|
|
clientOptions := options.Client().ApplyURI(mongoURI)
|
|
client, err := mongo.Connect(ctx, clientOptions)
|
|
if err != nil {
|
|
return ctrl.Result{}, fmt.Errorf("failed to connect to mongo: %v", err)
|
|
}
|
|
defer client.Disconnect(ctx)
|
|
collection := client.Database("open5gs").Collection("subscribers")
|
|
|
|
err = collection.FindOne(ctx, bson.M{"imsi": Open5GSUser.Spec.IMSI}).Decode(&subscriber)
|
|
if err != nil {
|
|
if err == mongo.ErrNoDocuments {
|
|
log.Printf("Subscriber %s not found in database, creating it", Open5GSUser.Spec.IMSI)
|
|
if Open5GSUser.Spec.SST != "" || Open5GSUser.Spec.SD != "" {
|
|
// SST or SD specified, call function handling slice.
|
|
if err := addSubscriberWithSlice(Open5GSUser, mongoURI); err != nil {
|
|
log.Printf("Failed to create subscriber with slice %s", err)
|
|
return ctrl.Result{}, err
|
|
}
|
|
log.Printf("Subscriber with slice created successfully IMSI: %s", Open5GSUser.Spec.IMSI)
|
|
} else if Open5GSUser.Spec.APN != "" {
|
|
// APN specified without SST and SD, use function for default APN.
|
|
if err := addSubscriberWithAPN(Open5GSUser, mongoURI); err != nil {
|
|
log.Printf("Failed to create subscriber with APN %s", err)
|
|
return ctrl.Result{}, err
|
|
}
|
|
log.Printf("Subscriber with APN created successfully IMSI: %s", Open5GSUser.Spec.IMSI)
|
|
} else {
|
|
// If SST, SD, and APN not specified but Key and OPC are, use defaults.
|
|
if Open5GSUser.Spec.Key != "" && Open5GSUser.Spec.OPC != "" {
|
|
if err := addSubscriberWithDefaults(Open5GSUser, mongoURI); err != nil {
|
|
log.Printf("Failed to create subscriber with defaults %s", err)
|
|
return ctrl.Result{}, err
|
|
}
|
|
log.Printf("Subscriber with defaults created successfully IMSI: %s", Open5GSUser.Spec.IMSI)
|
|
} else {
|
|
// Handle the case where necessary data is not provided.
|
|
log.Printf("Cannot create subscriber, missing key data IMSI: %s", Open5GSUser.Spec.IMSI)
|
|
return ctrl.Result{}, fmt.Errorf("insufficient data provided for subscriber creation")
|
|
}
|
|
}
|
|
} else {
|
|
log.Printf("Failed to find subscriber %s in database: %v", Open5GSUser.Spec.IMSI, err)
|
|
return ctrl.Result{}, err
|
|
}
|
|
} else {
|
|
if hasDrift(Open5GSUser, subscriber) {
|
|
log.Printf("Change detected for subscriber %s, updating it", Open5GSUser.Spec.IMSI)
|
|
if err := updateSubscriber(Open5GSUser, mongoURI); err != nil {
|
|
log.Printf("Failed to correct drift for subscriber %s: %v", Open5GSUser.Spec.IMSI, err)
|
|
return ctrl.Result{}, err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if containsString(Open5GSUser.ObjectMeta.Finalizers, Open5GSUserFinalizer) {
|
|
log.Printf("Deleting subscriber for Open5GSUser %s", Open5GSUser.Name)
|
|
if err := deleteSubscriber(Open5GSUser, mongoURI); err != nil {
|
|
log.Printf("Failed to delete subscriber for Open5GSUser %s: %v", Open5GSUser.Name, err)
|
|
return ctrl.Result{}, err
|
|
}
|
|
Open5GSUser.ObjectMeta.Finalizers = removeString(Open5GSUser.ObjectMeta.Finalizers, Open5GSUserFinalizer)
|
|
if err := r.Update(ctx, &Open5GSUser); err != nil {
|
|
log.Printf("Failed to remove finalizer for Open5GSUser %s: %v", Open5GSUser.Name, err)
|
|
return ctrl.Result{}, err
|
|
}
|
|
}
|
|
}
|
|
|
|
return ctrl.Result{RequeueAfter: 15 * time.Second}, nil // Requeue every 15 seconds
|
|
}
|
|
|
|
func (r *Open5GSUserReconciler) GetServiceIp(ctx context.Context, serviceName string, namespace string) (string, error) {
|
|
var service corev1.Service
|
|
namespacedName := client.ObjectKey{Name: serviceName, Namespace: namespace}
|
|
if err := r.Get(ctx, namespacedName, &service); err != nil {
|
|
log.Printf("Failed to get service %s: %v", serviceName, err)
|
|
return "", err
|
|
}
|
|
return service.Spec.ClusterIP, nil
|
|
}
|
|
|
|
func containsString(slice []string, s string) bool {
|
|
for _, item := range slice {
|
|
if item == s {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func removeString(slice []string, s string) (result []string) {
|
|
for _, item := range slice {
|
|
if item != s {
|
|
result = append(result, item)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (r *Open5GSUserReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
|
return ctrl.NewControllerManagedBy(mgr).
|
|
For(&netv1.Open5GSUser{}).
|
|
Complete(r)
|
|
}
|