diff --git a/api/v1beta1/slice_types.go b/api/v1beta1/slice_types.go index 846e0cd7a..c786d03c7 100644 --- a/api/v1beta1/slice_types.go +++ b/api/v1beta1/slice_types.go @@ -76,6 +76,8 @@ type SliceConfig struct { SliceGatewayProtocol string `json:"sliceGatewayProtocol,omitempty"` // Slice overlay network deployment mode: single-network, multi-network or no-network SliceOverlayNetworkDeploymentMode controllerv1alpha1.NetworkType `json:"sliceOverlayNetworkDeploymentMode,omitempty"` + // Topology configuration + TopologyConfig *controllerv1alpha1.TopologyConfig `json:"topologyConfig,omitempty"` } // NamespaceIsolationProfile defines the namespace isolation policy for the slice diff --git a/controllers/slicegateway/slicegateway.go b/controllers/slicegateway/slicegateway.go index c62b982a0..c331a5017 100644 --- a/controllers/slicegateway/slicegateway.go +++ b/controllers/slicegateway/slicegateway.go @@ -953,10 +953,20 @@ func (r *SliceGwReconciler) SendConnectionContextAndQosToGwPod(ctx context.Conte log.Info("Gw podIPs not available yet, requeuing") return ctrl.Result{RequeueAfter: 5 * time.Second}, nil, true } + + topologyType := "full-mesh" + if slice.Status.SliceConfig != nil && slice.Status.SliceConfig.TopologyConfig != nil { + topologyType = slice.Status.SliceConfig.TopologyConfig.TopologyType + } + connCtx := &gwsidecar.GwConnectionContext{ RemoteSliceGwVpnIP: slicegateway.Status.Config.SliceGatewayRemoteVpnIP, RemoteSliceGwNsmSubnet: slicegateway.Status.Config.SliceGatewayRemoteSubnet, + TopologyType: topologyType, } + + log.Info("Sending connection context to gateway pod", "topology", topologyType, "remoteCluster", slicegateway.Status.Config.SliceGatewayRemoteClusterID) + for i := range gwPodsInfo { sidecarGrpcAddress := gwPodsInfo[i].PodIP + ":5000" diff --git a/pkg/gwsidecar/gwsidecar.go b/pkg/gwsidecar/gwsidecar.go index a9ec5458a..db954ac2a 100644 --- a/pkg/gwsidecar/gwsidecar.go +++ b/pkg/gwsidecar/gwsidecar.go @@ -54,6 +54,7 @@ type GwStatus struct { type GwConnectionContext struct { RemoteSliceGwVpnIP string RemoteSliceGwNsmSubnet string + TopologyType string } type gwSidecarClient struct { @@ -149,6 +150,7 @@ func (worker gwSidecarClient) SendConnectionContext(ctx context.Context, serverA msg := &sidecar.SliceGwConnectionContext{ RemoteSliceGwVpnIP: gwConnCtx.RemoteSliceGwVpnIP, RemoteSliceGwNsmSubnet: gwConnCtx.RemoteSliceGwNsmSubnet, + TopologyType: gwConnCtx.TopologyType, } log.Info("SliceGwConnectionContext", "SliceGwConnectionContext", msg) diff --git a/pkg/hub/controllers/slice_controller.go b/pkg/hub/controllers/slice_controller.go index 8caef204b..c01ad841a 100644 --- a/pkg/hub/controllers/slice_controller.go +++ b/pkg/hub/controllers/slice_controller.go @@ -21,6 +21,7 @@ package controllers import ( "context" "fmt" + "reflect" "time" "github.com/go-logr/logr" @@ -328,6 +329,10 @@ func (r *SliceReconciler) updateSliceConfig(ctx context.Context, meshSlice *kube VPCServiceAccess: extGwCfg.VPCServiceAccess, } + if err := r.validateAndCopyTopology(ctx, meshSlice, spokeSlice); err != nil { + return err + } + return r.MeshClient.Status().Update(ctx, meshSlice) } @@ -661,3 +666,96 @@ func (r *SliceReconciler) UpdateSliceHealthMetrics(slice *spokev1alpha1.WorkerSl } } } + +func (r *SliceReconciler) validateAndCopyTopology(ctx context.Context, meshSlice *kubeslicev1beta1.Slice, spokeSlice *spokev1alpha1.WorkerSliceConfig) error { + log := logger.FromContext(ctx) + topologyValue := reflect.ValueOf(spokeSlice.Spec).FieldByName("TopologyConfig") + if !topologyValue.IsValid() || topologyValue.IsNil() { + log.V(1).Info("TopologyConfig field not present or nil, skipping topology copy") + return nil + } + topologyInterface := topologyValue.Interface() + if topologyInterface == nil { + return nil + } + topologyType := reflect.TypeOf(topologyInterface) + if topologyType.Kind() != reflect.Ptr { + return nil + } + topologyElem := topologyValue.Elem() + if !topologyElem.IsValid() { + return nil + } + topologyTypeField := topologyElem.FieldByName("TopologyType") + if !topologyTypeField.IsValid() { + return nil + } + connectivityMatrix := topologyElem.FieldByName("ConnectivityMatrix") + forbiddenEdges := topologyElem.FieldByName("ForbiddenEdges") + if !connectivityMatrix.IsValid() || !forbiddenEdges.IsValid() { + return nil + } + clustersValue := reflect.ValueOf(spokeSlice.Spec).FieldByName("Clusters") + if !clustersValue.IsValid() || clustersValue.Kind() != reflect.Slice { + return nil + } + clusterSet := make(map[string]struct{}) + for i := 0; i < clustersValue.Len(); i++ { + cluster := clustersValue.Index(i).String() + clusterSet[cluster] = struct{}{} + } + if connectivityMatrix.Len() > 0 { + for i := 0; i < connectivityMatrix.Len(); i++ { + entry := connectivityMatrix.Index(i) + sourceCluster := entry.FieldByName("SourceCluster").String() + if _, ok := clusterSet[sourceCluster]; !ok { + return fmt.Errorf("connectivityMatrix[%d]: sourceCluster %q not in clusters", i, sourceCluster) + } + targetClusters := entry.FieldByName("TargetClusters") + if targetClusters.IsValid() && targetClusters.Kind() == reflect.Slice { + for j := 0; j < targetClusters.Len(); j++ { + target := targetClusters.Index(j).String() + if _, ok := clusterSet[target]; !ok { + return fmt.Errorf("connectivityMatrix[%d].targetClusters[%d]: %q not in clusters", i, j, target) + } + } + } + } + } + if forbiddenEdges.Len() > 0 { + for i := 0; i < forbiddenEdges.Len(); i++ { + edge := forbiddenEdges.Index(i) + sourceCluster := edge.FieldByName("SourceCluster").String() + if _, ok := clusterSet[sourceCluster]; !ok { + return fmt.Errorf("forbiddenEdges[%d]: sourceCluster %q not in clusters", i, sourceCluster) + } + targetClusters := edge.FieldByName("TargetClusters") + if targetClusters.IsValid() && targetClusters.Kind() == reflect.Slice { + for j := 0; j < targetClusters.Len(); j++ { + target := targetClusters.Index(j).String() + if _, ok := clusterSet[target]; !ok { + return fmt.Errorf("forbiddenEdges[%d].targetClusters[%d]: %q not in clusters", i, j, target) + } + } + } + } + } + statusConfigValue := reflect.ValueOf(meshSlice.Status.SliceConfig).Elem() + topologyConfigField := statusConfigValue.FieldByName("TopologyConfig") + if !topologyConfigField.IsValid() || !topologyConfigField.CanSet() { + log.V(1).Info("TopologyConfig field not available in worker SliceConfig, skipping copy") + return nil + } + newTopology := reflect.New(topologyConfigField.Type().Elem()) + newTopologyElem := newTopology.Elem() + newTopologyElem.FieldByName("TopologyType").SetString(topologyTypeField.String()) + if connectivityMatrix.IsValid() && connectivityMatrix.Len() > 0 { + newTopologyElem.FieldByName("ConnectivityMatrix").Set(connectivityMatrix) + } + if forbiddenEdges.IsValid() && forbiddenEdges.Len() > 0 { + newTopologyElem.FieldByName("ForbiddenEdges").Set(forbiddenEdges) + } + topologyConfigField.Set(newTopology) + log.Info("Topology configuration copied to worker slice", "topologyType", topologyTypeField.String()) + return nil +}