diff --git a/apis/topology/v1alpha1/common.go b/apis/topology/v1alpha1/common.go index 6b8e116c..0b992da4 100644 --- a/apis/topology/v1alpha1/common.go +++ b/apis/topology/v1alpha1/common.go @@ -112,7 +112,7 @@ type LinkEndpoint struct { // Tunnel represents a VXLAN tunnel between clabernetes nodes (as configured by containerlab). type Tunnel struct { // ID is the VXLAN ID (vnid) for the tunnel. - ID int `json:"id"` + ID int `json:"id" yaml:"id"` // LocalNodeName is the name of the local node for this tunnel. LocalNodeName string `json:"localNodeName" yaml:"localNodeName"` // RemoteName is the name of the service to contact the remote end of the tunnel. diff --git a/controllers/topology/containerlab/config.go b/controllers/topology/containerlab/config.go index d7d4561e..aa201a36 100644 --- a/controllers/topology/containerlab/config.go +++ b/controllers/topology/containerlab/config.go @@ -6,15 +6,12 @@ import ( clabernetesconstants "github.com/srl-labs/clabernetes/constants" - clabernetescontrollerstopology "github.com/srl-labs/clabernetes/controllers/topology" - - clabernetesutilcontainerlab "github.com/srl-labs/clabernetes/util/containerlab" - - "gopkg.in/yaml.v3" - clabernetesapistopologyv1alpha1 "github.com/srl-labs/clabernetes/apis/topology/v1alpha1" + clabernetescontrollerstopologyreconciler "github.com/srl-labs/clabernetes/controllers/topology/reconciler" claberneteserrors "github.com/srl-labs/clabernetes/errors" clabernetesutil "github.com/srl-labs/clabernetes/util" + clabernetesutilcontainerlab "github.com/srl-labs/clabernetes/util/containerlab" + "gopkg.in/yaml.v3" ) func getDefaultPorts() []*clabernetesutilcontainerlab.TypedPort { @@ -276,8 +273,7 @@ func (c *Controller) processConfigForNode( nodeName string, nodeDefinition *clabernetesutilcontainerlab.NodeDefinition, defaultsYAML []byte, - clabernetesConfigs map[string]*clabernetesutilcontainerlab.Config, - clabernetesTunnels map[string][]*clabernetesapistopologyv1alpha1.Tunnel, + reconcileData *clabernetescontrollerstopologyreconciler.ReconcileData, ) error { deepCopiedDefaults := &clabernetesutilcontainerlab.NodeDefinition{} @@ -296,7 +292,7 @@ func (c *Controller) processConfigForNode( nodeDefinition.Ports = nodePorts } - clabernetesConfigs[nodeName] = &clabernetesutilcontainerlab.Config{ + reconcileData.PostReconcileConfigs[nodeName] = &clabernetesutilcontainerlab.Config{ Name: fmt.Sprintf("clabernetes-%s", nodeName), Topology: &clabernetesutilcontainerlab.Topology{ Defaults: deepCopiedDefaults, @@ -357,8 +353,8 @@ func (c *Controller) processConfigForNode( if endpointA.NodeName == nodeName && endpointB.NodeName == nodeName { // link loops back to ourselves, no need to do overlay things just append the link - clabernetesConfigs[nodeName].Topology.Links = append( - clabernetesConfigs[nodeName].Topology.Links, + reconcileData.PostReconcileConfigs[nodeName].Topology.Links = append( + reconcileData.PostReconcileConfigs[nodeName].Topology.Links, link, ) @@ -373,8 +369,8 @@ func (c *Controller) processConfigForNode( uninterestingEndpoint = endpointA } - clabernetesConfigs[nodeName].Topology.Links = append( - clabernetesConfigs[nodeName].Topology.Links, + reconcileData.PostReconcileConfigs[nodeName].Topology.Links = append( + reconcileData.PostReconcileConfigs[nodeName].Topology.Links, &clabernetesutilcontainerlab.LinkDefinition{ LinkConfig: clabernetesutilcontainerlab.LinkConfig{ Endpoints: []string{ @@ -393,8 +389,8 @@ func (c *Controller) processConfigForNode( }, ) - clabernetesTunnels[nodeName] = append( - clabernetesTunnels[nodeName], + reconcileData.PostReconcileTunnels[nodeName] = append( + reconcileData.PostReconcileTunnels[nodeName], &clabernetesapistopologyv1alpha1.Tunnel{ LocalNodeName: nodeName, RemoteNodeName: uninterestingEndpoint.NodeName, @@ -417,21 +413,15 @@ func (c *Controller) processConfigForNode( func (c *Controller) processConfig( clab *clabernetesapistopologyv1alpha1.Containerlab, clabTopo *clabernetesutilcontainerlab.Topology, + reconcileData *clabernetescontrollerstopologyreconciler.ReconcileData, ) ( - clabernetesConfigs map[string]*clabernetesutilcontainerlab.Config, - clabernetesTunnels map[string][]*clabernetesapistopologyv1alpha1.Tunnel, - shouldUpdate bool, err error, ) { - clabernetesConfigs = make(map[string]*clabernetesutilcontainerlab.Config) - - clabernetesTunnels = make(map[string][]*clabernetesapistopologyv1alpha1.Tunnel) - // we may have *different defaults per "sub-topology" so we do a cheater "deep copy" by just // marshalling/unmarshalling :) defaultsYAML, err := yaml.Marshal(clabTopo.Defaults) if err != nil { - return clabernetesConfigs, clabernetesTunnels, false, err + return err } for nodeName, nodeDefinition := range clabTopo.Nodes { @@ -441,47 +431,12 @@ func (c *Controller) processConfig( nodeName, nodeDefinition, defaultsYAML, - clabernetesConfigs, - clabernetesTunnels, + reconcileData, ) if err != nil { - return nil, nil, false, err + return err } } - clabernetesConfigsBytes, err := yaml.Marshal(clabernetesConfigs) - if err != nil { - return nil, nil, false, err - } - - tunnelsBytes, err := yaml.Marshal(clabernetesTunnels) - if err != nil { - return nil, nil, false, err - } - - newConfigsHash := clabernetesutil.HashBytes(clabernetesConfigsBytes) - - newTunnelsHash := clabernetesutil.HashBytes(tunnelsBytes) - - if clab.Status.ConfigsHash == newConfigsHash && clab.Status.TunnelsHash == newTunnelsHash { - // the configs hashes match, nothing to do, should reconcile is false, and no error - return clabernetesConfigs, clabernetesTunnels, false, nil - } - - // if we got here we know we need to re-reconcile as the hash has changed, set the config and - // config hash, and then return "true" (yes we should reconcile/update the object). before we - // can do that though, we need to handle setting tunnel ids. so first we go over and re-use - // all the existing tunnel ids by assigning matching node/interface pairs from the previous - // status to the new tunnels... when doing so we record the allocated ids... - clabernetescontrollerstopology.AllocateTunnelIDs( - clab.Status.TopologyStatus.Tunnels, - clabernetesTunnels, - ) - - clab.Status.Configs = string(clabernetesConfigsBytes) - clab.Status.ConfigsHash = newConfigsHash - clab.Status.Tunnels = clabernetesTunnels - clab.Status.TunnelsHash = newTunnelsHash - - return clabernetesConfigs, clabernetesTunnels, true, nil + return nil } diff --git a/controllers/topology/containerlab/get.go b/controllers/topology/containerlab/get.go index a5b842ef..ed678a30 100644 --- a/controllers/topology/containerlab/get.go +++ b/controllers/topology/containerlab/get.go @@ -8,12 +8,12 @@ import ( ctrlruntime "sigs.k8s.io/controller-runtime" ) -// getClabFromReq fetches the reconcile target Containerlab topology from the Request. -func (c *Controller) getClabFromReq( +// getContainerlabFromReq fetches the reconcile target Containerlab topology from the Request. +func (c *Controller) getContainerlabFromReq( ctx context.Context, req ctrlruntime.Request, ) (*clabernetesapistopologyv1alpha1.Containerlab, error) { - clab := &clabernetesapistopologyv1alpha1.Containerlab{} + containerlab := &clabernetesapistopologyv1alpha1.Containerlab{} err := c.BaseController.Client.Get( ctx, @@ -21,8 +21,8 @@ func (c *Controller) getClabFromReq( Namespace: req.Namespace, Name: req.Name, }, - clab, + containerlab, ) - return clab, err + return containerlab, err } diff --git a/controllers/topology/containerlab/reconcile.go b/controllers/topology/containerlab/reconcile.go index e23b7c29..28677594 100644 --- a/controllers/topology/containerlab/reconcile.go +++ b/controllers/topology/containerlab/reconcile.go @@ -3,12 +3,8 @@ package containerlab import ( "context" - clabernetesutil "github.com/srl-labs/clabernetes/util" - + clabernetescontrollerstopologyreconciler "github.com/srl-labs/clabernetes/controllers/topology/reconciler" clabernetesutilcontainerlab "github.com/srl-labs/clabernetes/util/containerlab" - - "gopkg.in/yaml.v3" - apimachineryerrors "k8s.io/apimachinery/pkg/api/errors" ctrlruntime "sigs.k8s.io/controller-runtime" ) @@ -20,7 +16,7 @@ func (c *Controller) Reconcile( ) (ctrlruntime.Result, error) { c.BaseController.LogReconcileStart(req) - clab, err := c.getClabFromReq(ctx, req) + containerlab, err := c.getContainerlabFromReq(ctx, req) if err != nil { if apimachineryerrors.IsNotFound(err) { // was deleted, nothing to do @@ -34,33 +30,31 @@ func (c *Controller) Reconcile( return ctrlruntime.Result{}, err } - if clab.DeletionTimestamp != nil { + if containerlab.DeletionTimestamp != nil { // deleting nothing to do, we have no finalizers or anything at this point return ctrlruntime.Result{}, nil } - preReconcileConfigs := make(map[string]*clabernetesutilcontainerlab.Config) - - if clab.Status.Configs != "" { - err = yaml.Unmarshal([]byte(clab.Status.Configs), &preReconcileConfigs) - if err != nil { - c.BaseController.Log.Criticalf( - "failed parsing unmarshalling previously stored config, error: %s", err, - ) + reconcileData, err := clabernetescontrollerstopologyreconciler.NewReconcileData(containerlab) + if err != nil { + c.BaseController.Log.Criticalf( + "failed processing previously stored containerlab resource, error: %s", err, + ) - return ctrlruntime.Result{}, err - } + return ctrlruntime.Result{}, err } - // load the containerlab topo to make sure its all good - containerlabTopo, err := clabernetesutilcontainerlab.LoadContainerlabTopology(clab.Spec.Config) + // load the containerlab topo from the CR to make sure its all good + containerlabTopo, err := clabernetesutilcontainerlab.LoadContainerlabTopology( + containerlab.Spec.Config, + ) if err != nil { c.BaseController.Log.Criticalf("failed parsing containerlab config, error: %s", err) return ctrlruntime.Result{}, err } - clabernetesConfigs, tunnels, configShouldUpdate, err := c.processConfig(clab, containerlabTopo) + err = c.processConfig(containerlab, containerlabTopo, reconcileData) if err != nil { c.BaseController.Log.Criticalf("failed processing containerlab config, error: %s", err) @@ -69,45 +63,46 @@ func (c *Controller) Reconcile( err = c.TopologyReconciler.ReconcileConfigMap( ctx, - clab, - clabernetesConfigs, - tunnels, + containerlab, + reconcileData, ) if err != nil { - c.BaseController.Log.Criticalf("failed reconciling clabernetes config map, error: %s", err) + c.BaseController.Log.Criticalf( + "failed reconciling clabernetes config map, error: %s", + err, + ) return ctrlruntime.Result{}, err } - err = c.TopologyReconciler.ReconcileServiceFabric(ctx, clab, clabernetesConfigs) + err = c.TopologyReconciler.ReconcileServiceFabric( + ctx, + containerlab, + reconcileData, + ) if err != nil { c.BaseController.Log.Criticalf("failed reconciling clabernetes services, error: %s", err) return ctrlruntime.Result{}, err } - var exposeServicesShouldUpdate bool - - if !clab.Spec.DisableExpose { - exposeServicesShouldUpdate, err = c.TopologyReconciler.ReconcileServicesExpose( - ctx, - clab, - clabernetesConfigs, + err = c.TopologyReconciler.ReconcileServicesExpose( + ctx, + containerlab, + reconcileData, + ) + if err != nil { + c.BaseController.Log.Criticalf( + "failed reconciling clabernetes expose services, error: %s", err, ) - if err != nil { - c.BaseController.Log.Criticalf( - "failed reconciling clabernetes expose services, error: %s", err, - ) - return ctrlruntime.Result{}, err - } + return ctrlruntime.Result{}, err } err = c.TopologyReconciler.ReconcileDeployments( ctx, - clab, - preReconcileConfigs, - clabernetesConfigs, + containerlab, + reconcileData, ) if err != nil { c.BaseController.Log.Criticalf("failed reconciling clabernetes deployments, error: %s", err) @@ -115,14 +110,20 @@ func (c *Controller) Reconcile( return ctrlruntime.Result{}, err } - if clabernetesutil.AnyBoolTrue(configShouldUpdate, exposeServicesShouldUpdate) { - // we should update because config hash or something changed, so push update to the object - err = c.BaseController.Client.Update(ctx, clab) + if reconcileData.ShouldUpdateResource { + // we should update because config hash or something changed, so snag the updated status + // data out of the reconcile data, put it in the resource, and push the update + containerlab.Status.Configs = string(reconcileData.PostReconcileConfigsBytes) + containerlab.Status.ConfigsHash = reconcileData.PostReconcileConfigsHash + containerlab.Status.Tunnels = reconcileData.PostReconcileTunnels + containerlab.Status.TunnelsHash = reconcileData.PostReconcileTunnelsHash + + err = c.BaseController.Client.Update(ctx, containerlab) if err != nil { c.BaseController.Log.Criticalf( "failed updating object '%s/%s' error: %s", - clab.Namespace, - clab.Name, + containerlab.Namespace, + containerlab.Name, err, ) diff --git a/controllers/topology/kne/config.go b/controllers/topology/kne/config.go index 9c0f5bf7..a78747d0 100644 --- a/controllers/topology/kne/config.go +++ b/controllers/topology/kne/config.go @@ -3,30 +3,24 @@ package kne import ( "fmt" + clabernetescontrollerstopologyreconciler "github.com/srl-labs/clabernetes/controllers/topology/reconciler" + clabernetesutilcontainerlab "github.com/srl-labs/clabernetes/util/containerlab" knetopologyproto "github.com/openconfig/kne/proto/topo" clabernetesapistopologyv1alpha1 "github.com/srl-labs/clabernetes/apis/topology/v1alpha1" - clabernetescontrollerstopology "github.com/srl-labs/clabernetes/controllers/topology" claberneteserrors "github.com/srl-labs/clabernetes/errors" clabernetesutil "github.com/srl-labs/clabernetes/util" clabernetesutilkne "github.com/srl-labs/clabernetes/util/kne" - "gopkg.in/yaml.v3" ) -func (c *Controller) processConfig( //nolint:funlen +func (c *Controller) processConfig( kne *clabernetesapistopologyv1alpha1.Kne, kneTopo *knetopologyproto.Topology, + reconcileData *clabernetescontrollerstopologyreconciler.ReconcileData, ) ( - clabernetesConfigs map[string]*clabernetesutilcontainerlab.Config, - clabernetesTunnels map[string][]*clabernetesapistopologyv1alpha1.Tunnel, - shouldUpdate bool, err error, ) { - clabernetesConfigs = make(map[string]*clabernetesutilcontainerlab.Config) - - tunnels := make(map[string][]*clabernetesapistopologyv1alpha1.Tunnel) - // making many assumptions that things that are pointers are not going to be nil... since // basically everything in the kne topology obj is pointers for _, nodeDefinition := range kneTopo.Nodes { @@ -45,7 +39,7 @@ func (c *Controller) processConfig( //nolint:funlen c.BaseController.Log.Critical(msg) - return nil, nil, false, fmt.Errorf( + return fmt.Errorf( "%w: %s", claberneteserrors.ErrParse, msg, ) } @@ -64,13 +58,13 @@ func (c *Controller) processConfig( //nolint:funlen c.BaseController.Log.Critical(msg) - return nil, nil, false, fmt.Errorf( + return fmt.Errorf( "%w: %s", claberneteserrors.ErrParse, msg, ) } } - clabernetesConfigs[nodeName] = &clabernetesutilcontainerlab.Config{ + reconcileData.PostReconcileConfigs[nodeName] = &clabernetesutilcontainerlab.Config{ Name: fmt.Sprintf("clabernetes-%s", nodeName), Topology: &clabernetesutilcontainerlab.Topology{ Nodes: map[string]*clabernetesutilcontainerlab.NodeDefinition{ @@ -86,7 +80,7 @@ func (c *Controller) processConfig( //nolint:funlen } if kneModel != "" { - clabernetesConfigs[nodeName].Topology.Nodes[nodeName].Type = kneModel + reconcileData.PostReconcileConfigs[nodeName].Topology.Nodes[nodeName].Type = kneModel } for _, link := range kneTopo.Links { @@ -107,8 +101,8 @@ func (c *Controller) processConfig( //nolint:funlen if endpointA.NodeName == nodeName && endpointB.NodeName == nodeName { // link loops back to ourselves, no need to do overlay things just create the normal // clab link setup here - clabernetesConfigs[nodeName].Topology.Links = append( - clabernetesConfigs[nodeName].Topology.Links, + reconcileData.PostReconcileConfigs[nodeName].Topology.Links = append( + reconcileData.PostReconcileConfigs[nodeName].Topology.Links, &clabernetesutilcontainerlab.LinkDefinition{ LinkConfig: clabernetesutilcontainerlab.LinkConfig{ Endpoints: []string{ @@ -130,8 +124,8 @@ func (c *Controller) processConfig( //nolint:funlen uninterestingEndpoint = endpointA } - clabernetesConfigs[nodeName].Topology.Links = append( - clabernetesConfigs[nodeName].Topology.Links, + reconcileData.PostReconcileConfigs[nodeName].Topology.Links = append( + reconcileData.PostReconcileConfigs[nodeName].Topology.Links, &clabernetesutilcontainerlab.LinkDefinition{ LinkConfig: clabernetesutilcontainerlab.LinkConfig{ Endpoints: []string{ @@ -150,8 +144,8 @@ func (c *Controller) processConfig( //nolint:funlen }, ) - tunnels[nodeName] = append( - tunnels[nodeName], + reconcileData.PostReconcileTunnels[nodeName] = append( + reconcileData.PostReconcileTunnels[nodeName], &clabernetesapistopologyv1alpha1.Tunnel{ LocalNodeName: nodeName, RemoteNodeName: uninterestingEndpoint.NodeName, @@ -169,28 +163,5 @@ func (c *Controller) processConfig( //nolint:funlen } } - clabernetesConfigsBytes, err := yaml.Marshal(clabernetesConfigs) - if err != nil { - return nil, nil, false, err - } - - newConfigsHash := clabernetesutil.HashBytes(clabernetesConfigsBytes) - - if kne.Status.ConfigsHash == newConfigsHash { - // the configs hash matches, nothing to do, should reconcile is false, and no error - return clabernetesConfigs, tunnels, false, nil - } - - // if we got here we know we need to re-reconcile as the hash has changed, set the config and - // config hash, and then return "true" (yes we should reconcile/update the object). before we - // can do that though, we need to handle setting tunnel ids. so first we go over and re-use - // all the existing tunnel ids by assigning matching node/interface pairs from the previous - // status to the new tunnels... when doing so we record the allocated ids... - clabernetescontrollerstopology.AllocateTunnelIDs(kne.Status.TopologyStatus.Tunnels, tunnels) - - kne.Status.Configs = string(clabernetesConfigsBytes) - kne.Status.ConfigsHash = newConfigsHash - kne.Status.Tunnels = tunnels - - return clabernetesConfigs, tunnels, true, nil + return nil } diff --git a/controllers/topology/kne/reconcile.go b/controllers/topology/kne/reconcile.go index d73dda7e..26c3a2ce 100644 --- a/controllers/topology/kne/reconcile.go +++ b/controllers/topology/kne/reconcile.go @@ -3,13 +3,8 @@ package kne import ( "context" - clabernetesutilcontainerlab "github.com/srl-labs/clabernetes/util/containerlab" - - clabernetesutil "github.com/srl-labs/clabernetes/util" - + clabernetescontrollerstopologyreconciler "github.com/srl-labs/clabernetes/controllers/topology/reconciler" clabernetesutilkne "github.com/srl-labs/clabernetes/util/kne" - "gopkg.in/yaml.v3" - apimachineryerrors "k8s.io/apimachinery/pkg/api/errors" ctrlruntime "sigs.k8s.io/controller-runtime" ) @@ -40,17 +35,13 @@ func (c *Controller) Reconcile( return ctrlruntime.Result{}, nil } - preReconcileConfigs := make(map[string]*clabernetesutilcontainerlab.Config) - - if kne.Status.Configs != "" { - err = yaml.Unmarshal([]byte(kne.Status.Configs), &preReconcileConfigs) - if err != nil { - c.BaseController.Log.Criticalf( - "failed parsing unmarshalling previously stored config, error: %s", err, - ) + reconcileData, err := clabernetescontrollerstopologyreconciler.NewReconcileData(kne) + if err != nil { + c.BaseController.Log.Criticalf( + "failed processing previously stored kne resource, error: %s", err, + ) - return ctrlruntime.Result{}, err - } + return ctrlruntime.Result{}, err } // load the kne topo to make sure its all good @@ -61,9 +52,9 @@ func (c *Controller) Reconcile( return ctrlruntime.Result{}, err } - clabernetesConfigs, tunnels, configShouldUpdate, err := c.processConfig(kne, kneTopo) + err = c.processConfig(kne, kneTopo, reconcileData) if err != nil { - c.BaseController.Log.Criticalf("failed processing kne topology, error: %s", err) + c.BaseController.Log.Criticalf("failed processing kne config, error: %s", err) return ctrlruntime.Result{}, err } @@ -71,44 +62,41 @@ func (c *Controller) Reconcile( err = c.TopologyReconciler.ReconcileConfigMap( ctx, kne, - clabernetesConfigs, - tunnels, + reconcileData, ) if err != nil { - c.BaseController.Log.Criticalf("failed reconciling clabernetes config map, error: %s", err) + c.BaseController.Log.Criticalf( + "failed reconciling clabernetes config map, error: %s", + err, + ) return ctrlruntime.Result{}, err } - err = c.TopologyReconciler.ReconcileServiceFabric(ctx, kne, clabernetesConfigs) + err = c.TopologyReconciler.ReconcileServiceFabric(ctx, kne, reconcileData) if err != nil { c.BaseController.Log.Criticalf("failed reconciling clabernetes services, error: %s", err) return ctrlruntime.Result{}, err } - var exposeServicesShouldUpdate bool - - if !kne.Spec.DisableExpose { - exposeServicesShouldUpdate, err = c.TopologyReconciler.ReconcileServicesExpose( - ctx, - kne, - clabernetesConfigs, + err = c.TopologyReconciler.ReconcileServicesExpose( + ctx, + kne, + reconcileData, + ) + if err != nil { + c.BaseController.Log.Criticalf( + "failed reconciling clabernetes expose services, error: %s", err, ) - if err != nil { - c.BaseController.Log.Criticalf( - "failed reconciling clabernetes expose services, error: %s", err, - ) - return ctrlruntime.Result{}, err - } + return ctrlruntime.Result{}, err } err = c.TopologyReconciler.ReconcileDeployments( ctx, kne, - preReconcileConfigs, - clabernetesConfigs, + reconcileData, ) if err != nil { c.BaseController.Log.Criticalf("failed reconciling clabernetes deployments, error: %s", err) @@ -116,8 +104,14 @@ func (c *Controller) Reconcile( return ctrlruntime.Result{}, err } - if clabernetesutil.AnyBoolTrue(configShouldUpdate, exposeServicesShouldUpdate) { - // we should update because config hash or something changed, so push update to the object + if reconcileData.ShouldUpdateResource { + // we should update because config hash or something changed, so snag the updated status + // data out of the reconcile data, put it in the resource, and push the update + kne.Status.Configs = string(reconcileData.PostReconcileConfigsBytes) + kne.Status.ConfigsHash = reconcileData.PostReconcileConfigsHash + kne.Status.Tunnels = reconcileData.PostReconcileTunnels + kne.Status.TunnelsHash = reconcileData.PostReconcileTunnelsHash + err = c.BaseController.Client.Update(ctx, kne) if err != nil { c.BaseController.Log.Criticalf( diff --git a/controllers/topology/reconciler/reconciler.go b/controllers/topology/reconciler/reconciler.go index 26dfcc2b..38c93141 100644 --- a/controllers/topology/reconciler/reconciler.go +++ b/controllers/topology/reconciler/reconciler.go @@ -6,6 +6,8 @@ import ( "slices" "time" + clabernetescontrollerstopology "github.com/srl-labs/clabernetes/controllers/topology" + clabernetesconstants "github.com/srl-labs/clabernetes/constants" k8sappsv1 "k8s.io/api/apps/v1" @@ -90,9 +92,47 @@ type Reconciler struct { func (r *Reconciler) ReconcileConfigMap( ctx context.Context, owningTopology clabernetesapistopologyv1alpha1.TopologyCommonObject, - clabernetesConfigs map[string]*clabernetesutilcontainerlab.Config, - tunnels map[string][]*clabernetesapistopologyv1alpha1.Tunnel, + reconcileData *ReconcileData, ) error { + var err error + + reconcileData.PostReconcileConfigsBytes, err = yaml.Marshal(reconcileData.PostReconcileConfigs) + if err != nil { + return err + } + + tunnelsBytes, err := yaml.Marshal(reconcileData.PostReconcileTunnels) + if err != nil { + return err + } + + reconcileData.PostReconcileConfigsHash = clabernetesutil.HashBytes( + reconcileData.PostReconcileConfigsBytes, + ) + + reconcileData.PostReconcileTunnelsHash = clabernetesutil.HashBytes(tunnelsBytes) + + if reconcileData.PreReconcileConfigsHash == reconcileData.PostReconcileConfigsHash && + reconcileData.PreReconcileTunnelsHash == reconcileData.PostReconcileTunnelsHash { + // the configs hashes match, nothing to do, should reconcile is false, and no error, *but* + // because the services may force us to update the cr we are reconciling, and we haven't + // processed the tunnel ids yet (because its slow and we are lazy), we need to copy the + // *previous* tunnel data into our "current" tunnel data so we make sure to not update the + // cr status with tunnel data with all zero for the tunnel ids + reconcileData.PostReconcileTunnels = reconcileData.PreReconcileTunnels + + return nil + } + + clabernetescontrollerstopology.AllocateTunnelIDs( + reconcileData.PreReconcileTunnels, + reconcileData.PostReconcileTunnels, + ) + + // we need to tell the controller to update the originating CR because obviously our hashes + // dont match which means we had some changes from the previous reconcile + reconcileData.ShouldUpdateResource = true + namespacedName := apimachinerytypes.NamespacedName{ Namespace: owningTopology.GetNamespace(), Name: owningTopology.GetName(), @@ -100,8 +140,8 @@ func (r *Reconciler) ReconcileConfigMap( renderedConfigMap, err := r.configMapReconciler.Render( namespacedName, - clabernetesConfigs, - tunnels, + reconcileData.PostReconcileConfigs, + reconcileData.PostReconcileTunnels, ) if err != nil { return err @@ -214,8 +254,7 @@ func (r *Reconciler) reconcileDeploymentsHandleRestarts( func (r *Reconciler) ReconcileDeployments( ctx context.Context, owningTopology clabernetesapistopologyv1alpha1.TopologyCommonObject, - previousClabernetesConfigs, - currentClabernetesConfigs map[string]*clabernetesutilcontainerlab.Config, + reconcileData *ReconcileData, ) error { deployments, err := reconcileResolve( ctx, @@ -224,7 +263,7 @@ func (r *Reconciler) ReconcileDeployments( &k8sappsv1.DeploymentList{}, clabernetesconstants.KubernetesDeployment, owningTopology, - currentClabernetesConfigs, + reconcileData.PostReconcileConfigs, r.deploymentReconciler.Resolve, ) if err != nil { @@ -244,7 +283,7 @@ func (r *Reconciler) ReconcileDeployments( renderedMissingDeployments := r.deploymentReconciler.RenderAll( owningTopology, - currentClabernetesConfigs, + reconcileData.PostReconcileConfigs, deployments.Missing, ) @@ -265,7 +304,7 @@ func (r *Reconciler) ReconcileDeployments( for existingCurrentDeploymentNodeName, existingCurrentDeployment := range deployments.Current { renderedCurrentDeployment := r.deploymentReconciler.Render( owningTopology, - currentClabernetesConfigs, + reconcileData.PostReconcileConfigs, existingCurrentDeploymentNodeName, ) @@ -297,8 +336,8 @@ func (r *Reconciler) ReconcileDeployments( return r.reconcileDeploymentsHandleRestarts( ctx, owningTopology, - previousClabernetesConfigs, - currentClabernetesConfigs, + reconcileData.PreReconcileConfigs, + reconcileData.PostReconcileConfigs, deployments, ) } @@ -307,7 +346,7 @@ func (r *Reconciler) ReconcileDeployments( func (r *Reconciler) ReconcileServiceFabric( ctx context.Context, owningTopology clabernetesapistopologyv1alpha1.TopologyCommonObject, - currentClabernetesConfigs map[string]*clabernetesutilcontainerlab.Config, + reconcileData *ReconcileData, ) error { serviceTypeName := fmt.Sprintf("fabric %s", clabernetesconstants.KubernetesService) @@ -318,8 +357,8 @@ func (r *Reconciler) ReconcileServiceFabric( &k8scorev1.ServiceList{}, serviceTypeName, owningTopology, - currentClabernetesConfigs, - r.serviceExposeReconciler.Resolve, + reconcileData.PostReconcileConfigs, + r.serviceFabricReconciler.Resolve, ) if err != nil { return err @@ -397,18 +436,18 @@ func (r *Reconciler) ReconcileServiceFabric( func (r *Reconciler) ReconcileServicesExpose( ctx context.Context, owningTopology clabernetesapistopologyv1alpha1.TopologyCommonObject, - currentClabernetesConfigs map[string]*clabernetesutilcontainerlab.Config, -) (bool, error) { + reconcileData *ReconcileData, +) error { serviceTypeName := fmt.Sprintf("expose %s", clabernetesconstants.KubernetesService) - var shouldUpdate bool - owningTopologyStatus := owningTopology.GetTopologyStatus() if owningTopologyStatus.NodeExposedPorts == nil { owningTopologyStatus.NodeExposedPorts = map[string]*clabernetesapistopologyv1alpha1.ExposedPorts{} //nolint:lll - shouldUpdate = true + // shouldUpdate is true because we didn't have any previously stored node exposed port + // status data + reconcileData.ShouldUpdateResource = true } services, err := reconcileResolve( @@ -418,11 +457,11 @@ func (r *Reconciler) ReconcileServicesExpose( &k8scorev1.ServiceList{}, serviceTypeName, owningTopology, - currentClabernetesConfigs, + reconcileData.PostReconcileConfigs, r.serviceExposeReconciler.Resolve, ) if err != nil { - return shouldUpdate, err + return err } r.Log.Info("pruning extraneous services") @@ -434,7 +473,7 @@ func (r *Reconciler) ReconcileServicesExpose( serviceTypeName, ) if err != nil { - return shouldUpdate, err + return err } } @@ -443,7 +482,7 @@ func (r *Reconciler) ReconcileServicesExpose( renderedMissingServices := r.serviceExposeReconciler.RenderAll( owningTopology, &owningTopologyStatus, - currentClabernetesConfigs, + reconcileData.PostReconcileConfigs, services.Missing, ) @@ -455,15 +494,17 @@ func (r *Reconciler) ReconcileServicesExpose( serviceTypeName, ) if err != nil { - return shouldUpdate, err + return err } } + r.Log.Info("enforcing desired state on expose services") + for existingCurrentServiceNodeName, existingCurrentService := range services.Current { renderedCurrentService := r.serviceExposeReconciler.Render( owningTopology, &owningTopologyStatus, - currentClabernetesConfigs, + reconcileData.PostReconcileConfigs, existingCurrentServiceNodeName, ) @@ -481,7 +522,7 @@ func (r *Reconciler) ReconcileServicesExpose( r.Client.Scheme(), ) if err != nil { - return shouldUpdate, err + return err } if !r.serviceExposeReconciler.Conforms( @@ -495,14 +536,14 @@ func (r *Reconciler) ReconcileServicesExpose( serviceTypeName, ) if err != nil { - return shouldUpdate, err + return err } } } nodeExposedPortsBytes, err := yaml.Marshal(owningTopologyStatus.NodeExposedPorts) if err != nil { - return shouldUpdate, err + return err } newNodeExposedPortsHash := clabernetesutil.HashBytes(nodeExposedPortsBytes) @@ -512,10 +553,11 @@ func (r *Reconciler) ReconcileServicesExpose( owningTopology.SetTopologyStatus(owningTopologyStatus) - shouldUpdate = true + // our exposed hash stuff changed, we need to update the cr status + reconcileData.ShouldUpdateResource = true } - return shouldUpdate, nil + return nil } // EnqueueForAll enqueues a reconcile for kinds the Reconciler represents. This is probably not very diff --git a/controllers/topology/reconciler/service.go b/controllers/topology/reconciler/service.go index 1de48e11..f966f944 100644 --- a/controllers/topology/reconciler/service.go +++ b/controllers/topology/reconciler/service.go @@ -27,6 +27,10 @@ func ServiceConforms( return false } + if len(renderedService.Spec.Ports) != len(existingService.Spec.Ports) { + return false + } + for _, expectedPort := range renderedService.Spec.Ports { var expectedPortExists bool diff --git a/controllers/topology/reconciler/serviceexpose.go b/controllers/topology/reconciler/serviceexpose.go index ed9d1db7..33d6798f 100644 --- a/controllers/topology/reconciler/serviceexpose.go +++ b/controllers/topology/reconciler/serviceexpose.go @@ -85,9 +85,17 @@ func (r *ServiceExposeReconciler) Resolve( exposedNodes := make([]string, 0) - disableAutoExpose := owningTopology.GetTopologyCommonSpec().DisableAutoExpose + commonSpec := owningTopology.GetTopologyCommonSpec() + disableExpose := commonSpec.DisableExpose + disableAutoExpose := commonSpec.DisableAutoExpose for nodeName, nodeData := range clabernetesConfigs { + // disable expose is set to true for the whole spec, nothing should be exposed, so skip + // every node + if disableExpose { + continue + } + // if disable auto expose is true *and* there are no ports defined for the node *and* // there are no default ports defined for the topology we can skip the node from an expose // perspective. diff --git a/controllers/topology/reconciler/types.go b/controllers/topology/reconciler/types.go new file mode 100644 index 00000000..da9516cc --- /dev/null +++ b/controllers/topology/reconciler/types.go @@ -0,0 +1,50 @@ +package reconciler + +import ( + clabernetesapistopologyv1alpha1 "github.com/srl-labs/clabernetes/apis/topology/v1alpha1" + clabernetesutilcontainerlab "github.com/srl-labs/clabernetes/util/containerlab" + "gopkg.in/yaml.v3" +) + +// ReconcileData is a struct that holds data that is common during a reconciliation process +// regardless of the type of clabernetes topology that is being reconciled. +type ReconcileData struct { + PreReconcileConfigsHash string + PreReconcileConfigs map[string]*clabernetesutilcontainerlab.Config + PostReconcileConfigs map[string]*clabernetesutilcontainerlab.Config + PostReconcileConfigsBytes []byte + PostReconcileConfigsHash string + + PreReconcileTunnelsHash string + PreReconcileTunnels map[string][]*clabernetesapistopologyv1alpha1.Tunnel + PostReconcileTunnels map[string][]*clabernetesapistopologyv1alpha1.Tunnel + PostReconcileTunnelsHash string + + ShouldUpdateResource bool +} + +// NewReconcileData accepts a TopologyCommonObject and returns a ReconcileData object. +func NewReconcileData( + owningTopology clabernetesapistopologyv1alpha1.TopologyCommonObject, +) (*ReconcileData, error) { + status := owningTopology.GetTopologyStatus() + + rd := &ReconcileData{ + PreReconcileConfigsHash: status.ConfigsHash, + PreReconcileConfigs: make(map[string]*clabernetesutilcontainerlab.Config), + PostReconcileConfigs: make(map[string]*clabernetesutilcontainerlab.Config), + + PreReconcileTunnelsHash: status.TunnelsHash, + PreReconcileTunnels: status.Tunnels, + PostReconcileTunnels: make(map[string][]*clabernetesapistopologyv1alpha1.Tunnel), + } + + if status.Configs != "" { + err := yaml.Unmarshal([]byte(status.Configs), &rd.PreReconcileConfigs) + if err != nil { + return nil, err + } + } + + return rd, nil +} diff --git a/launcher/containerlab.go b/launcher/containerlab.go index 57d9c3f7..c7afc8a3 100644 --- a/launcher/containerlab.go +++ b/launcher/containerlab.go @@ -105,7 +105,14 @@ func (c *clabernetes) runContainerlabVxlanTools( strconv.Itoa(clabernetesconstants.VXLANServicePort), ) - _, err = cmd.Output() + c.logger.Debugf( + "using following args for vxlan tunnel creation (via containerlab) '%s'", cmd.Args, + ) + + cmd.Stdout = c.containerlabLogger + cmd.Stderr = c.containerlabLogger + + err = cmd.Run() if err != nil { return err }