Repository: skupperproject/skupper
Pull request #1306 (2023-12-04 23:56:20)

Fix the output in status commands when headless services are exposed in skupper
Context: 
When a headless service is exposed, Skupper creates statefulsets with a pod of the router image that includes a listener or a connector, separated from the skupper-router. 

Fixed issues: 
- The service status command didn't show the targets of the headless services because the command was just checking the skupper-router.
- The link status command, when getting all the direct connections from the router,  was querying the first ready pod with the label "skupper.io/component=router". This could return sometimes the pods from the statefulsets that have the same label; and because they did not have the expected connectors (links), the command show incorrectly that the links where not connected. For the same reason, the skupper version command, some times was getting the wrong router component and it was not showing the versions of the router and the config-sync.
- The skupper status command, was getting information about the linked sites from a statefulset-router, for that reason the output was showing sites connected indirectly (and incorrectly).
- For the same reason as before, the skupper network status command was not showing always the linked sites.




Patch:
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 00000000..34a678b6
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,26 @@
+---
+name: Bug report
+about: Describe a bug or issue
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**How To Reproduce**
+Steps to reproduce the behavior:
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+
+**Environment details**
+ - Skupper CLI: [e.g. 1.5.0, 1.4.3]
+ - Skupper Operator (if applicable): [e.g. 1.5.0, 1.4.3]
+ - Platform: [e.g kubernetes, openshift]
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 00000000..bbcbbe7d
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/api/types/client.go b/api/types/client.go
index 30cace52..6dfea411 100644
--- a/api/types/client.go
+++ b/api/types/client.go
@@ -350,6 +350,5 @@ type VanClientInterface interface {
 	GetIngressDefault() string
 	RevokeAccess(ctx context.Context) error
 	NetworkStatus(ctx context.Context) (*network.NetworkStatusInfo, error)
-	GetRemoteLinks(ctx context.Context, siteConfig *SiteConfig) ([]*network.RemoteLinkInfo, error)
 	GetConsoleUrl(namespace string) (string, error)
 }
diff --git a/api/types/types.go b/api/types/types.go
index a99caa1e..28034631 100644
--- a/api/types/types.go
+++ b/api/types/types.go
@@ -198,7 +198,7 @@ var ClusterControllerExtendedPolicyRules = []rbacv1.PolicyRule{
 	},
 }
 
-// Certifcates/Secrets constants
+// Certificates/Secrets constants
 const (
 	LocalClientSecret        string = "skupper-local-client"
 	LocalServerSecret        string = "skupper-local-server"
diff --git a/client/connector_create.go b/client/connector_create.go
index fde22f53..6599cce6 100644
--- a/client/connector_create.go
+++ b/client/connector_create.go
@@ -3,8 +3,8 @@ package client
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
 	"net/url"
+	"os"
 	"reflect"
 	"strconv"
 
@@ -24,7 +24,7 @@ import (
 )
 
 func (cli *VanClient) ConnectorCreateFromFile(ctx context.Context, secretFile string, options types.ConnectorCreateOptions) (*corev1.Secret, error) {
-	yaml, err := ioutil.ReadFile(secretFile)
+	yaml, err := os.ReadFile(secretFile)
 	if err != nil {
 		fmt.Println("Could not read connection token", err.Error())
 		return nil, err
diff --git a/client/container/client.go b/client/container/client.go
index 334f8aae..3dccc3bb 100644
--- a/client/container/client.go
+++ b/client/container/client.go
@@ -3,7 +3,6 @@ package container
 import (
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path"
 	"strings"
@@ -170,7 +169,7 @@ func (v *Volume) ReadFile(name string) (string, error) {
 	if err != nil {
 		return "", err
 	}
-	data, err := ioutil.ReadFile(path.Join(vDir.Name(), name))
+	data, err := os.ReadFile(path.Join(vDir.Name(), name))
 	if err != nil {
 		return "", err
 	}
diff --git a/client/gateway.go b/client/gateway.go
index cfc0442f..c5b2a688 100644
--- a/client/gateway.go
+++ b/client/gateway.go
@@ -10,7 +10,6 @@ import (
 	"encoding/hex"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
 	"net"
 	"os"
 	"os/exec"
@@ -286,7 +285,7 @@ func getConfigHome() string {
 }
 
 func getGatewaySiteId(gatewayDir string) (string, error) {
-	siteId, err := ioutil.ReadFile(gatewayDir + "/config/siteid.txt")
+	siteId, err := os.ReadFile(gatewayDir + "/config/siteid.txt")
 	if err != nil {
 		return "", fmt.Errorf("Failed to read site id: %w", err)
 	}
@@ -294,7 +293,7 @@ func getGatewaySiteId(gatewayDir string) (string, error) {
 }
 
 func getRouterId(gatewayDir string) (string, error) {
-	routerId, err := ioutil.ReadFile(gatewayDir + "/config/routerid.txt")
+	routerId, err := os.ReadFile(gatewayDir + "/config/routerid.txt")
 	if err != nil {
 		return "", fmt.Errorf("Failed to read router id: %w", err)
 	}
@@ -302,7 +301,7 @@ func getRouterId(gatewayDir string) (string, error) {
 }
 
 func getRouterUrl(gatewayDir string) (string, error) {
-	url, err := ioutil.ReadFile(gatewayDir + "/config/url.txt")
+	url, err := os.ReadFile(gatewayDir + "/config/url.txt")
 	if err != nil {
 		return "", fmt.Errorf("Failed to read router url: %w", err)
 	}
@@ -333,9 +332,9 @@ func getRouterVersion(gatewayName string, gatewayType string) (string, error) {
 }
 
 func getMachineID() (string, error) {
-	id, err := ioutil.ReadFile("/var/lib/dbus/machine-id")
+	id, err := os.ReadFile("/var/lib/dbus/machine-id")
 	if err != nil {
-		id, err = ioutil.ReadFile("/etc/machine-id")
+		id, err = os.ReadFile("/etc/machine-id")
 	}
 	if err != nil {
 		return "", err
@@ -471,12 +470,12 @@ func setupLocalDir(localDir string) error {
 
 func startGatewayUserService(gatewayName, unitDir, localDir string) error {
 
-	unitFile, err := ioutil.ReadFile(localDir + "/user/" + gatewayName + ".service")
+	unitFile, err := os.ReadFile(localDir + "/user/" + gatewayName + ".service")
 	if err != nil {
 		return fmt.Errorf("Unable to read service file: %w", err)
 	}
 
-	err = ioutil.WriteFile(unitDir+"/"+gatewayName+".service", unitFile, 0644)
+	err = os.WriteFile(unitDir+"/"+gatewayName+".service", unitFile, 0644)
 	if err != nil {
 		return fmt.Errorf("Unable to write user unit file: %w", err)
 	}
@@ -548,7 +547,7 @@ func updateLocalGatewayConfig(gatewayDir string, gatewayType string, gatewayConf
 	qdrConfig := template.Must(template.New("qdrConfig").Parse(mc))
 	qdrConfig.Execute(&buf, instance)
 
-	err = ioutil.WriteFile(gatewayDir+"/config/skrouterd.json", buf.Bytes(), 0644)
+	err = os.WriteFile(gatewayDir+"/config/skrouterd.json", buf.Bytes(), 0644)
 	if err != nil {
 		return fmt.Errorf("Failed to write config file: %w", err)
 	}
@@ -575,7 +574,7 @@ func (cli *VanClient) setupGatewayConfig(ctx context.Context, gatewayName string
 	}
 
 	for _, cert := range certs {
-		err = ioutil.WriteFile(gatewayDir+"/skupper-router-certs/conn1-profile/"+cert, secret.Data[cert], 0644)
+		err = os.WriteFile(gatewayDir+"/skupper-router-certs/conn1-profile/"+cert, secret.Data[cert], 0644)
 		if err != nil {
 			return fmt.Errorf("Failed to write cert file: %w", err)
 		}
@@ -592,7 +591,7 @@ func (cli *VanClient) setupGatewayConfig(ctx context.Context, gatewayName string
 
 	amqpPort, err := GetFreePort()
 	if err != nil {
-		return fmt.Errorf("Could not aquire free port: %w", err)
+		return fmt.Errorf("Could not acquire free port: %w", err)
 	}
 	gatewayConfig.Listeners["amqp"] = qdr.Listener{
 		Name: "amqp",
@@ -602,14 +601,14 @@ func (cli *VanClient) setupGatewayConfig(ctx context.Context, gatewayName string
 
 	// store the url for instance queries
 	url := fmt.Sprintf("amqp://127.0.0.1:%s", strconv.Itoa(amqpPort))
-	err = ioutil.WriteFile(gatewayDir+"/config/url.txt", []byte(url), 0644)
+	err = os.WriteFile(gatewayDir+"/config/url.txt", []byte(url), 0644)
 	if err != nil {
 		return fmt.Errorf("Failed to write instance url file: %w", err)
 	}
 
 	// generate a router id and store it for subsequent template updates
 	routerId := newUUID()
-	err = ioutil.WriteFile(gatewayDir+"/config/routerid.txt", []byte(routerId), 0644)
+	err = os.WriteFile(gatewayDir+"/config/routerid.txt", []byte(routerId), 0644)
 	if err != nil {
 		return fmt.Errorf("Failed to write instance id file: %w", err)
 	}
@@ -620,7 +619,7 @@ func (cli *VanClient) setupGatewayConfig(ctx context.Context, gatewayName string
 		return fmt.Errorf("Failed to retrieve site id: %w", err)
 	}
 	if siteConfig != nil {
-		err = ioutil.WriteFile(gatewayDir+"/config/siteid.txt", []byte(siteConfig.Reference.UID), 0644)
+		err = os.WriteFile(gatewayDir+"/config/siteid.txt", []byte(siteConfig.Reference.UID), 0644)
 		if err != nil {
 			return fmt.Errorf("Failed to write site id file: %w", err)
 		}
@@ -664,7 +663,7 @@ func (cli *VanClient) setupGatewayConfig(ctx context.Context, gatewayName string
 	qdrConfig := template.Must(template.New("qdrConfig").Parse(mc))
 	qdrConfig.Execute(&buf, instance)
 
-	err = ioutil.WriteFile(gatewayDir+"/config/skrouterd.json", buf.Bytes(), 0644)
+	err = os.WriteFile(gatewayDir+"/config/skrouterd.json", buf.Bytes(), 0644)
 	if err != nil {
 		return fmt.Errorf("Failed to write config file: %w", err)
 	}
@@ -693,7 +692,7 @@ func (cli *VanClient) gatewayStartService(ctx context.Context, gatewayName strin
 		ConfigPath:      gatewayDir,
 		GatewayName:     gatewayName,
 	})
-	err = ioutil.WriteFile(gatewayDir+"/user/"+gatewayName+".service", []byte(qdrUserUnit), 0644)
+	err = os.WriteFile(gatewayDir+"/user/"+gatewayName+".service", []byte(qdrUserUnit), 0644)
 	if err != nil {
 		return fmt.Errorf("Failed to write unit file: %w", err)
 	}
@@ -926,7 +925,7 @@ func (cli *VanClient) newGateway(ctx context.Context, gatewayName string, gatewa
 
 	if configFile != "" {
 		// grab the bindings and forwards from the config file
-		yamlFile, err := ioutil.ReadFile(configFile)
+		yamlFile, err := os.ReadFile(configFile)
 		if err != nil {
 			return "", fmt.Errorf("Failed to read gateway config file: %w", err)
 		}
@@ -1138,7 +1137,7 @@ func (cli *VanClient) GatewayInit(ctx context.Context, gatewayName string, gatew
 	}
 
 	if configFile != "" && gatewayType != GatewayMockType {
-		yamlFile, err := ioutil.ReadFile(configFile)
+		yamlFile, err := os.ReadFile(configFile)
 		if err != nil {
 			return "", fmt.Errorf("Failed to read gateway config file: %w", err)
 		}
@@ -2055,7 +2054,7 @@ func (cli *VanClient) GatewayExportConfig(ctx context.Context, targetGatewayName
 		return exportFile, fmt.Errorf("Failed to marshal export config: %w", err)
 	}
 
-	err = ioutil.WriteFile(exportFile, mcData, 0644)
+	err = os.WriteFile(exportFile, mcData, 0644)
 	if err != nil {
 		return exportFile, fmt.Errorf("Failed to write export config file: %w", err)
 	}
@@ -2071,7 +2070,7 @@ func (cli *VanClient) GatewayGenerateBundle(ctx context.Context, configFile stri
 		return "", fmt.Errorf("Skupper is not enabled in namespace '%s'", cli.Namespace)
 	}
 
-	yamlFile, err := ioutil.ReadFile(configFile)
+	yamlFile, err := os.ReadFile(configFile)
 	if err != nil {
 		return "", fmt.Errorf("Failed to read gateway config file: %w", err)
 	}
diff --git a/client/network_status.go b/client/network_status.go
index cea132ea..b9ea34f0 100644
--- a/client/network_status.go
+++ b/client/network_status.go
@@ -4,7 +4,6 @@ import (
 	"context"
 	"fmt"
 	"github.com/skupperproject/skupper/api/types"
-	"github.com/skupperproject/skupper/pkg/domain/kube"
 	k8s "github.com/skupperproject/skupper/pkg/kube"
 	"github.com/skupperproject/skupper/pkg/network"
 )
@@ -29,12 +28,3 @@ func (cli *VanClient) NetworkStatus(ctx context.Context) (*network.NetworkStatus
 
 	return vanInfo, nil
 }
-
-func (cli *VanClient) GetRemoteLinks(ctx context.Context, siteConfig *types.SiteConfig) ([]*network.RemoteLinkInfo, error) {
-	cfg, err := cli.getRouterConfig(ctx, cli.Namespace)
-	if err != nil {
-		return nil, err
-	}
-	linkHander := kube.NewLinkHandlerKube(cli.Namespace, siteConfig, cfg, cli.KubeClient, cli.RestConfig)
-	return linkHander.RemoteLinks(ctx)
-}
diff --git a/client/podman/container.go b/client/podman/container.go
index 957d21a6..c26e5fa3 100644
--- a/client/podman/container.go
+++ b/client/podman/container.go
@@ -340,7 +340,7 @@ func (p *PodmanRestClient) ContainerExec(id string, command []string) (string, e
 	}
 	result, err := p.RestClient.Submit(execOp)
 	if err != nil {
-		return "", fmt.Errorf("error executing command: %v", err)
+		return "", fmt.Errorf("error executing command on %s: %v", id, err)
 	}
 	resp, ok := result.(*models.IDResponse)
 	if !ok {
diff --git a/client/podman/container_mock.go b/client/podman/container_mock.go
index 6ad4c2e8..b8943785 100644
--- a/client/podman/container_mock.go
+++ b/client/podman/container_mock.go
@@ -13,6 +13,7 @@ import (
 	"github.com/google/uuid"
 	"github.com/skupperproject/skupper/client/container"
 	"github.com/skupperproject/skupper/client/generated/libpod/client/containers"
+	"github.com/skupperproject/skupper/client/generated/libpod/client/networks"
 	"github.com/skupperproject/skupper/client/generated/libpod/client/volumes"
 	"github.com/skupperproject/skupper/client/generated/libpod/models"
 )
@@ -35,8 +36,9 @@ func NewPodmanClientMock(containers []*container.Container) *PodmanRestClient {
 // performed by the mock implementation.
 type RestClientMock struct {
 	Containers   []*container.Container
-	volumes      map[string]*container.Volume
-	volumesFiles map[string]map[string]string
+	networks     map[string]*container.Network
+	Volumes      map[string]*container.Volume
+	VolumesFiles map[string]map[string]string
 	volumesDir   string
 	ErrorHook    func(operation *runtime.ClientOperation) error
 }
@@ -47,8 +49,8 @@ func (r *RestClientMock) MockVolumeFiles(volumes map[string]*container.Volume, v
 	if err != nil {
 		return err
 	}
-	r.volumes = volumes
-	r.volumesFiles = volumesFiles
+	r.Volumes = volumes
+	r.VolumesFiles = volumesFiles
 
 	prepareVolumeDir := func(volumeName string, volume *container.Volume) error {
 		volumeDir := path.Join(r.volumesDir, volumeName)
@@ -59,20 +61,20 @@ func (r *RestClientMock) MockVolumeFiles(volumes map[string]*container.Volume, v
 		return nil
 	}
 	// creating temp directory for volume
-	for volumeName, volume := range r.volumes {
+	for volumeName, volume := range r.Volumes {
 		if err = prepareVolumeDir(volumeName, volume); err != nil {
 			return err
 		}
 	}
 	// creating volume files
-	for volumeName, volumeFiles := range r.volumesFiles {
-		volume, ok := r.volumes[volumeName]
+	for volumeName, volumeFiles := range r.VolumesFiles {
+		volume, ok := r.Volumes[volumeName]
 		// if volume not defined earlier, create it
 		if !ok {
 			v := &container.Volume{
 				Name: volumeName,
 			}
-			r.volumes[volumeName] = v
+			r.Volumes[volumeName] = v
 			if err = prepareVolumeDir(volumeName, v); err != nil {
 				return err
 			}
@@ -115,6 +117,10 @@ func (r *RestClientMock) Submit(operation *runtime.ClientOperation) (interface{}
 		res, err = r.HandleVolumeInspect(operation, r.ErrorHook)
 	case "VolumeListLibpod":
 		res, err = r.HandleVolumeList(operation, r.ErrorHook)
+	case "VolumeDeleteLibpod":
+		res, err = r.HandleVolumeDelete(operation, r.ErrorHook)
+	case "NetworkInspectLibpod":
+		res, err = r.HandleNetworkInspect(operation, r.ErrorHook)
 	}
 	return res, err
 }
@@ -446,7 +452,7 @@ func (r *RestClientMock) HandleVolumeInspect(operation *runtime.ClientOperation,
 		}
 	}
 	params := operation.Params.(*volumes.VolumeInspectLibpodParams)
-	v, ok := r.volumes[params.Name]
+	v, ok := r.Volumes[params.Name]
 	if !ok {
 		return res, fmt.Errorf("no volume with name %q", params.Name)
 	}
@@ -465,7 +471,7 @@ func (r *RestClientMock) HandleVolumeList(operation *runtime.ClientOperation, ho
 			return res, err
 		}
 	}
-	for vName, v := range r.volumes {
+	for vName, v := range r.Volumes {
 		res.Payload = append(res.Payload, &models.VolumeConfigResponse{
 			Labels:     v.Labels,
 			Mountpoint: v.Source,
@@ -474,3 +480,57 @@ func (r *RestClientMock) HandleVolumeList(operation *runtime.ClientOperation, ho
 	}
 	return res, nil
 }
+
+func (r *RestClientMock) HandleVolumeDelete(operation *runtime.ClientOperation, hook func(operation *runtime.ClientOperation) error) (interface{}, error) {
+	var res = &volumes.VolumeDeleteLibpodNoContent{}
+	if hook != nil {
+		if err := hook(operation); err != nil {
+			return res, err
+		}
+	}
+	params := operation.Params.(*volumes.VolumeDeleteLibpodParams)
+	v, ok := r.Volumes[params.Name]
+	if !ok {
+		return res, fmt.Errorf("no volume with name %q", params.Name)
+	}
+	delete(r.Volumes, v.Name)
+	return res, nil
+}
+
+func (r *RestClientMock) HandleNetworkInspect(operation *runtime.ClientOperation, hook func(operation *runtime.ClientOperation) error) (interface{}, error) {
+	var res = &networks.NetworkInspectLibpodOK{}
+	if hook != nil {
+		if err := hook(operation); err != nil {
+			return res, err
+		}
+	}
+	params := operation.Params.(*networks.NetworkInspectLibpodParams)
+	n, ok := r.networks[params.Name]
+	if !ok {
+		return res, fmt.Errorf("no network with name %q", params.Name)
+	}
+
+	subnets := []*models.Subnet{}
+	for _, sn := range n.Subnets {
+		subnets = append(subnets, &models.Subnet{
+			Gateway: sn.Gateway,
+			Subnet:  sn.Subnet,
+		})
+	}
+
+	created, _ := strfmt.ParseDateTime(n.CreatedAt)
+	res.Payload = &models.Network{
+		Created:     created,
+		DNSEnabled:  n.DNS,
+		Driver:      n.Driver,
+		ID:          n.ID,
+		IPV6Enabled: n.IPV6,
+		Internal:    n.Internal,
+		Labels:      n.Labels,
+		Name:        n.Name,
+		Options:     n.Options,
+		Subnets:     subnets,
+	}
+
+	return res, nil
+}
diff --git a/client/podman/rest.go b/client/podman/rest.go
index 821450b9..e73b595a 100644
--- a/client/podman/rest.go
+++ b/client/podman/rest.go
@@ -5,7 +5,6 @@ import (
 	"crypto/tls"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net"
 	"net/http"
 	"net/url"
@@ -201,7 +200,7 @@ func (r *responseReaderBody) Consume(reader io.Reader, i interface{}) error {
 	if !ok {
 		return fmt.Errorf("error parsing body")
 	}
-	data, err := ioutil.ReadAll(reader)
+	data, err := io.ReadAll(reader)
 	if err != nil {
 		return fmt.Errorf("error reading response body: %v", err)
 	}
diff --git a/client/podman/volume.go b/client/podman/volume.go
index c4dee92f..0a679406 100644
--- a/client/podman/volume.go
+++ b/client/podman/volume.go
@@ -52,6 +52,7 @@ func (p *PodmanRestClient) VolumeRemove(id string) error {
 	cli := volumes.New(p.RestClient, formats)
 	params := volumes.NewVolumeDeleteLibpodParams()
 	params.Name = id
+	params.Force = boolTrue()
 	_, err = cli.VolumeDeleteLibpod(params)
 	if err != nil {
 		return err
diff --git a/client/router_inspect.go b/client/router_inspect.go
index 35193d96..a1291407 100644
--- a/client/router_inspect.go
+++ b/client/router_inspect.go
@@ -162,7 +162,7 @@ func getSelf(sites []data.Site, siteId string) *data.Site {
 }
 
 func (cli *VanClient) exec(command []string, namespace string) (*bytes.Buffer, error) {
-	pod, err := kube.GetReadyPod(namespace, cli.KubeClient, "service-controller")
+	pod, err := kube.GetReadyPod(namespace, cli.KubeClient, "service-controller", "")
 	if err != nil {
 		return nil, err
 	}
diff --git a/client/router_update.go b/client/router_update.go
index 9a717d13..68240f85 100644
--- a/client/router_update.go
+++ b/client/router_update.go
@@ -4,7 +4,6 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
 	"log"
 	"net"
 	"os"
@@ -910,17 +909,17 @@ func updateGatewayMultiport(ctx context.Context, cli *VanClient) error {
 			}
 			// generate a router id and store it for subsequent template updates
 			routerId := newUUID()
-			err = ioutil.WriteFile(newGatewayDir+"/config/routerid.txt", []byte(routerId), 0644)
+			err = os.WriteFile(newGatewayDir+"/config/routerid.txt", []byte(routerId), 0644)
 			if err != nil {
 				return err
 			}
 			updateFileContent := func(fileName, oldPath, newPath string) error {
-				content, err := ioutil.ReadFile(fileName)
+				content, err := os.ReadFile(fileName)
 				if err != nil {
 					return err
 				}
 				updatedContent := strings.ReplaceAll(string(content), oldPath, newPath)
-				err = ioutil.WriteFile(fileName, []byte(updatedContent), 0)
+				err = os.WriteFile(fileName, []byte(updatedContent), 0)
 				if err != nil {
 					return err
 				}
diff --git a/cmd/config-sync/config_sync.go b/cmd/config-sync/config_sync.go
index 705d28df..b731eaa5 100644
--- a/cmd/config-sync/config_sync.go
+++ b/cmd/config-sync/config_sync.go
@@ -3,7 +3,6 @@ package main
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
 	"log"
 	"math"
 	"net/http"
@@ -88,7 +87,7 @@ func (c *ConfigSync) stop() {
 func (c *ConfigSync) runConfigSync() {
 	err := c.checkCertFiles(SHARED_TLS_DIRECTORY)
 	if err != nil {
-		log.Printf("An error has ocurred when checking certification files for the router: %s", err)
+		log.Printf("An error has occurred when checking certification files for the router: %s", err)
 	}
 	http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
 		w.WriteHeader(200)
@@ -403,7 +402,7 @@ func (c *ConfigSync) copyCertsFilesToPath(path string, profilename string, secre
 
 	_, err = os.Stat(certFile)
 	if secret.Data["tls.crt"] != nil && os.IsNotExist(err) {
-		err = ioutil.WriteFile(certFile, secret.Data["tls.crt"], 0777)
+		err = os.WriteFile(certFile, secret.Data["tls.crt"], 0777)
 		if err != nil {
 			return err
 		}
@@ -411,7 +410,7 @@ func (c *ConfigSync) copyCertsFilesToPath(path string, profilename string, secre
 
 	_, err = os.Stat(keyFile)
 	if secret.Data["tls.key"] != nil && os.IsNotExist(err) {
-		err = ioutil.WriteFile(keyFile, secret.Data["tls.key"], 0777)
+		err = os.WriteFile(keyFile, secret.Data["tls.key"], 0777)
 		if err != nil {
 			return err
 		}
@@ -419,7 +418,7 @@ func (c *ConfigSync) copyCertsFilesToPath(path string, profilename string, secre
 
 	_, err = os.Stat(caCertFile)
 	if secret.Data["ca.crt"] != nil && os.IsNotExist(err) {
-		err = ioutil.WriteFile(caCertFile, secret.Data["ca.crt"], 0777)
+		err = os.WriteFile(caCertFile, secret.Data["ca.crt"], 0777)
 		if err != nil {
 			return err
 		}
diff --git a/cmd/flow-collector/handlers.go b/cmd/flow-collector/handlers.go
index 0ebad711..0591d1d1 100644
--- a/cmd/flow-collector/handlers.go
+++ b/cmd/flow-collector/handlers.go
@@ -2,7 +2,7 @@ package main
 
 import (
 	"fmt"
-	"io/ioutil"
+	"io"
 	"log"
 	"net/http"
 
@@ -184,7 +184,7 @@ func (c *Controller) promqueryHandler(w http.ResponseWriter, r *http.Request) {
 		log.Printf("COLLECTOR: Prometheus query error: %s\n", err.Error())
 	} else {
 		w.WriteHeader(proxyResp.StatusCode)
-		data, _ := ioutil.ReadAll(proxyResp.Body)
+		data, _ := io.ReadAll(proxyResp.Body)
 		proxyResp.Body.Close()
 		fmt.Fprintf(w, "%s\n", data)
 	}
@@ -205,7 +205,7 @@ func (c *Controller) promqueryrangeHandler(w http.ResponseWriter, r *http.Reques
 	if err != nil {
 		log.Printf("COLLECTOR: Prometheus query_range error: %s\n", err.Error())
 	} else {
-		data, _ := ioutil.ReadAll(proxyResp.Body)
+		data, _ := io.ReadAll(proxyResp.Body)
 		proxyResp.Body.Close()
 		fmt.Fprintf(w, "%s\n", data)
 	}
diff --git a/cmd/flow-collector/main.go b/cmd/flow-collector/main.go
index 55eb117d..2c778b8e 100755
--- a/cmd/flow-collector/main.go
+++ b/cmd/flow-collector/main.go
@@ -7,9 +7,10 @@ import (
 	"errors"
 	"flag"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"log"
 	"net/http"
+	_ "net/http/pprof"
 	"os"
 	"os/signal"
 	"path"
@@ -59,7 +60,7 @@ var shutdownSignals = []os.Signal{os.Interrupt, syscall.SIGTERM}
 func getConnectInfo(file string) (connectJson, error) {
 	cj := connectJson{}
 
-	jsonFile, err := ioutil.ReadFile(file)
+	jsonFile, err := os.ReadFile(file)
 	if err != nil {
 		return cj, err
 	}
@@ -113,7 +114,7 @@ func authenticate(dir string, user string, password string) bool {
 	}
 	defer file.Close()
 
-	bytes, err := ioutil.ReadAll(file)
+	bytes, err := io.ReadAll(file)
 	if err != nil {
 		log.Printf("COLLECTOR: Failed to authenticate %s: %s", user, err)
 		return false
@@ -207,9 +208,12 @@ func internalLogout(w http.ResponseWriter, r *http.Request, validNonces map[stri
 }
 
 func main() {
+	flags := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
 	// if -version used, report and exit
-	isVersion := flag.Bool("version", false, "Report the version of the Skupper Flow Collector")
-	flag.Parse()
+	isVersion := flags.Bool("version", false, "Report the version of the Skupper Flow Collector")
+	isProf := flags.Bool("profile", false, "Exposes the runtime profiling facilities from net/http/pprof on http://localhost:9970")
+
+	flags.Parse(os.Args[1:])
 	if *isVersion {
 		fmt.Println(version.Version)
 		os.Exit(0)
@@ -383,18 +387,22 @@ func main() {
 	userApi.StrictSlash(true)
 	userApi.HandleFunc("/", authenticated(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		handler, exists := userMap[authMode]
-		statusCode := http.StatusNoContent
 
-		if exists {
-			userResponse := handler(r)
-			if response, err := json.Marshal(userResponse); err == nil {
-				statusCode = http.StatusOK
-				fmt.Fprintf(w, "%s", response)
-			}
+		if !exists {
+			w.WriteHeader(http.StatusNoContent)
+			return
+		}
+
+		response, err := json.Marshal(handler(r))
+
+		if err != nil {
+			log.Printf("Error /user response: %s", err.Error())
+			w.WriteHeader(http.StatusInternalServerError)
+			return
 		}
 
 		w.Header().Set("Content-Type", "application/json")
-		w.WriteHeader(statusCode)
+		w.Write(response)
 	})))
 
 	var userLogout = api1.PathPrefix("/logout").Subrouter()
@@ -585,6 +593,14 @@ func main() {
 			}
 		}
 	}()
+	if *isProf {
+		// serve only over localhost loopback
+		go func() {
+			if err := http.ListenAndServe("localhost:9970", nil); err != nil {
+				log.Fatalf("failure running default http server for net/http/pprof: %s", err)
+			}
+		}()
+	}
 
 	if err = c.Run(stopCh); err != nil {
 		log.Fatal("Error running Flow collector: ", err.Error())
diff --git a/cmd/service-controller/console_server.go b/cmd/service-controller/console_server.go
index b3bd2670..4befe7cc 100644
--- a/cmd/service-controller/console_server.go
+++ b/cmd/service-controller/console_server.go
@@ -4,7 +4,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"log"
 	"net/http"
 	"os"
@@ -65,7 +65,7 @@ func authenticate(dir string, user string, password string) bool {
 	}
 	defer file.Close()
 
-	bytes, err := ioutil.ReadAll(file)
+	bytes, err := io.ReadAll(file)
 	if err != nil {
 		event.Recordf(HttpAuthFailure, "Failed to authenticate %s: %s", user, err)
 		return false
diff --git a/cmd/service-controller/controller.go b/cmd/service-controller/controller.go
index c5bd9215..21e09371 100644
--- a/cmd/service-controller/controller.go
+++ b/cmd/service-controller/controller.go
@@ -217,7 +217,9 @@ func NewController(cli *client.VanClient, origin string, tlsConfig *certs.TlsCon
 
 	controller.serviceSync = service_sync.NewServiceSync(origin, ttl, version.Version, qdr.NewConnectionFactory("amqps://"+types.QualifiedServiceName(types.LocalTransportServiceName, cli.Namespace)+":5671", tlsConfig), handler, controller.eventHandler)
 
-	controller.flowController = flow.NewFlowController(origin, version.Version, siteCreationTime, qdr.NewConnectionFactory("amqps://"+types.QualifiedServiceName(types.LocalTransportServiceName, cli.Namespace)+":5671", tlsConfig))
+	controller.flowController = flow.NewFlowController(origin, version.Version, siteCreationTime,
+		qdr.NewConnectionFactory("amqps://"+types.QualifiedServiceName(types.LocalTransportServiceName, cli.Namespace)+":5671", tlsConfig),
+		client.NewClusterPolicyValidator(cli))
 	ipHandler := func(deleted bool, name string, process *flow.ProcessRecord) error {
 		return flow.UpdateProcess(controller.flowController, deleted, name, process)
 	}
@@ -624,7 +626,7 @@ func (c *Controller) updateBridgeConfig(name string) error {
 
 func (c *Controller) initialiseServiceBindingsMap() (map[string][]int, error) {
 	c.bindings = map[string]*service.ServiceBindings{}
-	// on first initiliasing the service bindings map, need to get any
+	// on first initialising the service bindings map, need to get any
 	// port allocations from bridge config
 	bridges, err := c.getInitialBridgeConfig()
 	if err != nil {
@@ -793,7 +795,7 @@ func (c *Controller) processNextEvent() bool {
 				c.updateServiceSync(cm)
 				var updated []*service.ServiceBindings
 				if len(cm.Data) > 0 {
-					// filter out any entires that have not changed since the last change event
+					// filter out any entries that have not changed since the last change event
 					entries := c.changedServiceDefinitions(cm.Data)
 					for k, v := range entries {
 						si := types.ServiceInterface{}
diff --git a/cmd/service-controller/links.go b/cmd/service-controller/links.go
index 47ef6ae4..625a9a0f 100644
--- a/cmd/service-controller/links.go
+++ b/cmd/service-controller/links.go
@@ -4,7 +4,7 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"strconv"
 	"time"
@@ -236,7 +236,7 @@ func serveLinks(m Links) http.Handler {
 				}
 			}
 		} else if r.Method == http.MethodPost {
-			body, err := ioutil.ReadAll(r.Body)
+			body, err := io.ReadAll(r.Body)
 			if err != nil {
 				http.Error(w, err.Error(), http.StatusInternalServerError)
 			}
diff --git a/cmd/service-controller/services.go b/cmd/service-controller/services.go
index f6901960..63665d6f 100644
--- a/cmd/service-controller/services.go
+++ b/cmd/service-controller/services.go
@@ -4,10 +4,11 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
-	"github.com/skupperproject/skupper/pkg/event"
-	"io/ioutil"
+	"io"
 	"net/http"
 
+	"github.com/skupperproject/skupper/pkg/event"
+
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
@@ -252,7 +253,7 @@ func (o *ServiceOptions) DeducePort() bool {
 
 func getServiceOptions(r *http.Request) (*ServiceOptions, error) {
 	options := &ServiceOptions{}
-	body, err := ioutil.ReadAll(r.Body)
+	body, err := io.ReadAll(r.Body)
 	if err != nil {
 		return options, err
 	}
diff --git a/cmd/skupper/skupper_kube_network.go b/cmd/skupper/skupper_kube_network.go
index cc048a8a..cc1a1b06 100644
--- a/cmd/skupper/skupper_kube_network.go
+++ b/cmd/skupper/skupper_kube_network.go
@@ -73,8 +73,12 @@ func (s *SkupperKubeNetwork) Status(cmd *cobra.Command, args []string) error {
 
 				if len(siteStatus.RouterStatus) > 0 {
 
-					//to get the generic information about the links of a site, we can get the first router, because in case of multiple routers the information will be the same.
-					mapSiteLink := statusManager.GetSiteLinkMapPerRouter(&siteStatus.RouterStatus[0], &siteStatus.Site)
+					err, index := statusManager.GetRouterIndex(&siteStatus)
+					if err != nil {
+						return err
+					}
+
+					mapSiteLink := statusManager.GetSiteLinkMapPerRouter(&siteStatus.RouterStatus[index], &siteStatus.Site)
 
 					if len(mapSiteLink) > 0 {
 						siteLinks := siteLevel.NewChild("Linked sites:")
@@ -87,26 +91,28 @@ func (s *SkupperKubeNetwork) Status(cmd *cobra.Command, args []string) error {
 						routers := siteLevel.NewChild("Routers:")
 						for _, routerStatus := range siteStatus.RouterStatus {
 							routerId := strings.Split(routerStatus.Router.Name, "/")
-							routerItem := fmt.Sprintf("name: %s\n", routerId[1])
-							detailsRouter := map[string]string{"image name": routerStatus.Router.ImageName, "image version": routerStatus.Router.ImageVersion}
 
-							routerLevel := routers.NewChildWithDetail(routerItem, detailsRouter)
+							// skip routers that belong to headless services
+							if len(routerId) > 1 && strings.HasPrefix(routerId[1], siteStatus.Site.Name) {
+								routerItem := fmt.Sprintf("name: %s\n", routerId[1])
+								detailsRouter := map[string]string{"image name": routerStatus.Router.ImageName, "image version": routerStatus.Router.ImageVersion}
 
-							printableLinks := statusManager.RemoveLinksFromSameSite(routerStatus, siteStatus.Site)
+								routerLevel := routers.NewChildWithDetail(routerItem, detailsRouter)
 
-							if len(printableLinks) > 0 {
-								links := routerLevel.NewChild("Links:")
-								for _, link := range printableLinks {
-									linkItem := fmt.Sprintf("name:  %s\n", link.Name)
-									detailsLink := map[string]string{"direction": link.Direction}
-									if link.LinkCost > 0 {
-										detailsLink["cost"] = strconv.FormatUint(link.LinkCost, 10)
-									}
-									links.NewChildWithDetail(linkItem, detailsLink)
+								printableLinks := statusManager.RemoveLinksFromSameSite(routerStatus, siteStatus.Site)
 
+								if len(printableLinks) > 0 {
+									links := routerLevel.NewChild("Links:")
+									for _, link := range printableLinks {
+										linkItem := fmt.Sprintf("name:  %s\n", link.Name)
+										detailsLink := map[string]string{"direction": link.Direction}
+										if link.LinkCost > 0 {
+											detailsLink["cost"] = strconv.FormatUint(link.LinkCost, 10)
+										}
+										links.NewChildWithDetail(linkItem, detailsLink)
+									}
 								}
 							}
-
 						}
 					}
 				}
diff --git a/cmd/skupper/skupper_kube_service.go b/cmd/skupper/skupper_kube_service.go
index 33a4b0c8..7e7f892c 100644
--- a/cmd/skupper/skupper_kube_service.go
+++ b/cmd/skupper/skupper_kube_service.go
@@ -62,7 +62,7 @@ func (s *SkupperKubeService) Status(cmd *cobra.Command, args []string) error {
 	}
 
 	mapServiceSites := statusManager.GetServiceSitesMap()
-	mapSiteTargets := statusManager.GetSiteTargetsMap()
+	mapSiteTarget := statusManager.GetSiteTargetMap()
 
 	var mapServiceLabels map[string]map[string]string
 	if err == nil {
@@ -74,10 +74,6 @@ func (s *SkupperKubeService) Status(cmd *cobra.Command, args []string) error {
 	} else {
 		l := formatter.NewList()
 		l.Item("Services exposed through Skupper:")
-		var addresses []string
-		for _, si := range currentNetworkStatus.Addresses {
-			addresses = append(addresses, si.Name)
-		}
 
 		for _, si := range currentNetworkStatus.Addresses {
 			svc := l.NewChild(fmt.Sprintf("%s (%s)", si.Name, si.Protocol))
@@ -91,7 +87,7 @@ func (s *SkupperKubeService) Status(cmd *cobra.Command, args []string) error {
 						theSite := sites.NewChildWithDetail(item, map[string]string{"policy": site.Site.Policy})
 
 						if si.ConnectorCount > 0 {
-							t := mapSiteTargets[site.Site.Identity][si.Name]
+							t := mapSiteTarget[site.Site.Identity][si.Name]
 
 							if len(t.Address) > 0 {
 								targets := theSite.NewChild("Targets:")
diff --git a/cmd/skupper/skupper_kube_site.go b/cmd/skupper/skupper_kube_site.go
index 79a2e72b..dc2e68c6 100644
--- a/cmd/skupper/skupper_kube_site.go
+++ b/cmd/skupper/skupper_kube_site.go
@@ -268,7 +268,12 @@ func (s *SkupperKubeSite) Status(cmd *cobra.Command, args []string) error {
 				policies:  currentSite.Site.Policy,
 			}
 
-			mapSiteLink := statusManager.GetSiteLinkMapPerRouter(&currentSite.RouterStatus[0], &currentSite.Site)
+			err, index := statusManager.GetRouterIndex(currentSite)
+			if err != nil {
+				return err
+			}
+
+			mapSiteLink := statusManager.GetSiteLinkMapPerRouter(&currentSite.RouterStatus[index], &currentSite.Site)
 
 			totalSites := len(currentStatus.SiteStatus)
 			// the current site does not count as a connection
diff --git a/cmd/skupper/skupper_link.go b/cmd/skupper/skupper_link.go
index fe1c1459..b19fa991 100644
--- a/cmd/skupper/skupper_link.go
+++ b/cmd/skupper/skupper_link.go
@@ -3,7 +3,7 @@ package main
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
+	"os"
 	"strconv"
 	"time"
 
@@ -42,7 +42,7 @@ func NewCmdLinkCreate(skupperClient SkupperLinkClient, flag string) *cobra.Comma
 		PreRun: skupperClient.NewClient,
 		RunE: func(cmd *cobra.Command, args []string) error {
 			// loading secret from file
-			yaml, err := ioutil.ReadFile(args[0])
+			yaml, err := os.ReadFile(args[0])
 			if err != nil {
 				return fmt.Errorf("Could not read connection token: %s", err.Error())
 			}
@@ -227,7 +227,7 @@ func NewCmdLinkStatus(skupperClient SkupperLinkClient) *cobra.Command {
 		},
 	}
 	cmd.Flags().IntVar(&waitFor, "wait", 0, "The number of seconds to wait for links to become connected")
-	cmd.Flags().BoolVar(&verboseLinkStatus, "verbose", false, "Show detailed information about a link")
+	cmd.Flags().BoolVarP(&verboseLinkStatus, "verbose", "v", false, "Show detailed information about a link")
 
 	return cmd
 
diff --git a/cmd/skupper/skupper_podman.go b/cmd/skupper/skupper_podman.go
index 74964a44..aefec2b3 100644
--- a/cmd/skupper/skupper_podman.go
+++ b/cmd/skupper/skupper_podman.go
@@ -7,7 +7,7 @@ import (
 	"github.com/skupperproject/skupper/api/types"
 	clientpodman "github.com/skupperproject/skupper/client/podman"
 	"github.com/skupperproject/skupper/pkg/domain/podman"
-	"github.com/skupperproject/skupper/test/utils"
+	"github.com/skupperproject/skupper/pkg/utils"
 	"github.com/spf13/cobra"
 )
 
@@ -107,7 +107,7 @@ func (s *SkupperPodman) NewClient(cmd *cobra.Command, args []string) {
 	if err != nil {
 		if exitOnError {
 			fmt.Printf("Podman endpoint is not available: %s",
-				utils.StrDefault(endpoint, clientpodman.GetDefaultPodmanEndpoint()))
+				utils.DefaultStr(endpoint, clientpodman.GetDefaultPodmanEndpoint()))
 			fmt.Println()
 			os.Exit(1)
 		}
@@ -131,11 +131,17 @@ func (s *SkupperPodman) NewClient(cmd *cobra.Command, args []string) {
 			fmt.Println()
 			os.Exit(0)
 		}
-	} else if cmd.Name() != "version" {
-		// All other commands, but version, must stop now
+	} else if !utils.StringSliceContains([]string{"version", "delete"}, cmd.Name()) {
+		// All other commands, but version and delete, must stop now
 		if err != nil {
 			fmt.Printf("Skupper is not enabled for user '%s'", podman.Username)
 			fmt.Println()
+			if siteHandler.AnyResourceLeft() {
+				fmt.Println("Reason:", err)
+				fmt.Println()
+				fmt.Println("There are podman resources missing or left from an earlier installation")
+				fmt.Println("To clean them up, run: skupper delete")
+			}
 			os.Exit(0)
 		}
 	}
diff --git a/cmd/skupper/skupper_podman_service.go b/cmd/skupper/skupper_podman_service.go
index bcf18845..f1dc2f70 100644
--- a/cmd/skupper/skupper_podman_service.go
+++ b/cmd/skupper/skupper_podman_service.go
@@ -349,7 +349,11 @@ func (s *SkupperPodmanService) ExposeArgs(cmd *cobra.Command, args []string) err
 
 func (s *SkupperPodmanService) ExposeFlags(cmd *cobra.Command) {
 	cmd.Use = "expose [host <hostname|ip>]"
+	cmd.Short = "Expose one or more network services"
 	cmd.Example = `
+        # exposing a service running on the local machine
+        skupper expose host host.containers.internal --address my-service --port 8080
+
         # exposing a local network IP
         skupper expose host 10.0.0.1 --address my-service --port 8080
 
@@ -397,7 +401,11 @@ func (s *SkupperPodmanService) Unexpose(cmd *cobra.Command, args []string) error
 
 func (s *SkupperPodmanService) UnexposeFlags(cmd *cobra.Command) error {
 	cmd.Use = "unexpose [host <hostname|ip>]"
+	cmd.Short = "Unexpose one or more network services"
 	cmd.Example = `
+        # unexposing a service running on the local machine
+        skupper unexpose host host.containers.internal --address my-service
+
         # unexposing a local network IP
         skupper unexpose host 10.0.0.1 --address my-service
 
diff --git a/cmd/skupper/skupper_podman_site.go b/cmd/skupper/skupper_podman_site.go
index 47b56756..84ef49b5 100644
--- a/cmd/skupper/skupper_podman_site.go
+++ b/cmd/skupper/skupper_podman_site.go
@@ -142,6 +142,7 @@ Recommendation:
 	}
 
 	// Initializing
+	cmd.SilenceUsage = true
 	err = siteHandler.Create(site)
 	if err != nil {
 		return fmt.Errorf("Error initializing Skupper - %w", err)
@@ -198,13 +199,15 @@ func (s *SkupperPodmanSite) CreateFlags(cmd *cobra.Command) {
 }
 
 func (s *SkupperPodmanSite) Delete(cmd *cobra.Command, args []string) error {
+	cmd.SilenceUsage = true
 	siteHandler, err := podman.NewSitePodmanHandler("")
 	if err != nil {
 		return fmt.Errorf("Unable to delete Skupper - %w", err)
 	}
-	curSite, err := siteHandler.Get()
-	if err != nil || curSite == nil {
-		return err
+	if s.podman.currentSite == nil && !siteHandler.AnyResourceLeft() {
+		fmt.Printf("Skupper is not enabled for user '%s'", podman.Username)
+		fmt.Println()
+		return nil
 	}
 	err = siteHandler.Delete()
 	if err != nil {
diff --git a/pkg/certs/certs.go b/pkg/certs/certs.go
index e0d81cab..64274a1d 100644
--- a/pkg/certs/certs.go
+++ b/pkg/certs/certs.go
@@ -22,7 +22,6 @@ import (
 	"crypto/x509/pkix"
 	"encoding/pem"
 	"fmt"
-	"io/ioutil"
 	"log"
 	"math/big"
 	"net"
@@ -309,7 +308,7 @@ func getTlsConfig(verify bool, cert, key, ca string) (*tls.Config, error) {
 	config.InsecureSkipVerify = true
 	if verify {
 		certPool := x509.NewCertPool()
-		file, err := ioutil.ReadFile(ca)
+		file, err := os.ReadFile(ca)
 		if err != nil {
 			return nil, err
 		}
diff --git a/pkg/config/local.go b/pkg/config/local.go
index 11476545..778a3820 100644
--- a/pkg/config/local.go
+++ b/pkg/config/local.go
@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"os"
 	"path"
 	"path/filepath"
@@ -56,7 +55,7 @@ func (l *ConfigFileHandlerCommon) Save() error {
 }
 
 func (l *ConfigFileHandlerCommon) Load() error {
-	data, err := ioutil.ReadFile(l.GetFilename())
+	data, err := os.ReadFile(l.GetFilename())
 	if err != nil && !os.IsNotExist(err) {
 		return fmt.Errorf("error loading %s: %v", l.GetFilename(), err)
 	}
@@ -116,7 +115,7 @@ func (p *PlatformInfo) Update(platform types.Platform) error {
 }
 
 func (p *PlatformInfo) Load() error {
-	data, err := ioutil.ReadFile(PlatformConfigFile)
+	data, err := os.ReadFile(PlatformConfigFile)
 	if err != nil && !os.IsNotExist(err) {
 		return fmt.Errorf("error loading %s: %v", PlatformConfigFile, err)
 	}
diff --git a/pkg/config/startup.go b/pkg/config/startup.go
index 6b2accfd..f752bc52 100644
--- a/pkg/config/startup.go
+++ b/pkg/config/startup.go
@@ -3,7 +3,6 @@ package config
 import (
 	_ "embed"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path"
 
@@ -38,12 +37,12 @@ func GetStartupScripts(platform types.Platform) *StartupScripts {
 
 func (s *StartupScripts) Create() error {
 	startFileName := path.Join(GetDataHome(), s.GetStartFileName())
-	err := ioutil.WriteFile(startFileName, []byte(s.StartScript), 0755)
+	err := os.WriteFile(startFileName, []byte(s.StartScript), 0755)
 	if err != nil {
 		return err
 	}
 	stopFileName := path.Join(GetDataHome(), s.GetStopFileName())
-	err = ioutil.WriteFile(stopFileName, []byte(s.StopScript), 0755)
+	err = os.WriteFile(stopFileName, []byte(s.StopScript), 0755)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/config/systemd.go b/pkg/config/systemd.go
index 409318c3..02c60d31 100644
--- a/pkg/config/systemd.go
+++ b/pkg/config/systemd.go
@@ -4,7 +4,6 @@ import (
 	"bytes"
 	_ "embed"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"os/exec"
 	"path"
@@ -55,7 +54,7 @@ func (s *systemdServiceInfo) Create() error {
 
 	// Saving systemd user service
 	serviceName := s.getServiceName()
-	err = ioutil.WriteFile(s.getServiceFile(), buf.Bytes(), 0644)
+	err = os.WriteFile(s.getServiceFile(), buf.Bytes(), 0644)
 	if err != nil {
 		return fmt.Errorf("Unable to write user unit file: %w", err)
 	}
diff --git a/pkg/domain/link.go b/pkg/domain/link.go
index a9fbccb6..5938692e 100644
--- a/pkg/domain/link.go
+++ b/pkg/domain/link.go
@@ -6,8 +6,7 @@ import (
 	"crypto/tls"
 	"crypto/x509"
 	"fmt"
-	"github.com/skupperproject/skupper/pkg/network"
-	"io/ioutil"
+	"io"
 	"log"
 	"net/http"
 	"regexp"
@@ -15,6 +14,8 @@ import (
 	"strings"
 	"time"
 
+	"github.com/skupperproject/skupper/pkg/network"
+
 	"github.com/skupperproject/skupper/api/types"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/runtime/serializer/json"
@@ -115,7 +116,7 @@ func (c *ClaimRedeemer) RedeemClaim(claim *corev1.Secret) error {
 		fmt.Println()
 		return c.handleError(claim, err.Error(), false)
 	}
-	body, err := ioutil.ReadAll(response.Body)
+	body, err := io.ReadAll(response.Body)
 	if err != nil {
 		fmt.Printf("Claim request failed on reading body: %s", err)
 		fmt.Println()
diff --git a/pkg/domain/podman/controller/controller.go b/pkg/domain/podman/controller/controller.go
index 00c61dcb..1c73cb89 100644
--- a/pkg/domain/podman/controller/controller.go
+++ b/pkg/domain/podman/controller/controller.go
@@ -76,7 +76,7 @@ func (c *ControllerPodman) Run(stopCh <-chan struct{}) error {
 	c.site = sitePodman
 
 	siteCreationTime := uint64(time.Now().UnixNano()) / uint64(time.Microsecond)
-	flowController := flow.NewFlowController(c.origin, version.Version, siteCreationTime, qdr.NewConnectionFactory("amqps://"+types.LocalTransportServiceName+":5671", c.tlsConfig))
+	flowController := flow.NewFlowController(c.origin, version.Version, siteCreationTime, qdr.NewConnectionFactory("amqps://"+types.LocalTransportServiceName+":5671", c.tlsConfig), flow.WithPolicyDisabled)
 	flowController.Start(stopCh)
 	log.Println("Started flow-controller")
 
diff --git a/pkg/domain/podman/credential_handler.go b/pkg/domain/podman/credential_handler.go
index 4a59f7a1..d01b5b31 100644
--- a/pkg/domain/podman/credential_handler.go
+++ b/pkg/domain/podman/credential_handler.go
@@ -3,7 +3,6 @@ package podman
 import (
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path"
 
@@ -171,7 +170,7 @@ func (p *CredentialHandler) LoadVolumeAsSecret(vol *container.Volume) (*corev1.S
 		if file.IsDir() {
 			continue
 		}
-		data, err := ioutil.ReadFile(path.Join(vol.Source, file.Name()))
+		data, err := os.ReadFile(path.Join(vol.Source, file.Name()))
 		if err != nil {
 			return nil, fmt.Errorf("error reading file %s for secret %s - %v", file.Name(), vol.Name, err)
 		}
diff --git a/pkg/domain/podman/site.go b/pkg/domain/podman/site.go
index 4f025a3a..e43e29d8 100644
--- a/pkg/domain/podman/site.go
+++ b/pkg/domain/podman/site.go
@@ -293,6 +293,8 @@ func (s *SiteHandler) Create(site domain.Site) error {
 			if err != nil {
 				return err
 			}
+		}
+		if OwnedBySkupper("volume", vol.Labels) == nil {
 			cleanupFns = append(cleanupFns, func() {
 				_ = s.cli.VolumeRemove(vol.Name)
 			})
@@ -363,8 +365,8 @@ func (s *SiteHandler) canCreate(site *Site) error {
 	// TODO improve on container and network already exists
 	// Validating any of the required deployment exists
 	for _, skupperDepl := range site.Deployments {
-		container, err := cli.ContainerInspect(skupperDepl.GetName())
-		if err == nil && container != nil {
+		containerInspect, err := cli.ContainerInspect(skupperDepl.GetName())
+		if err == nil && containerInspect != nil {
 			return fmt.Errorf("%s container already defined", skupperDepl.GetName())
 		}
 	}
@@ -463,16 +465,16 @@ func (s *SiteHandler) Get() (domain.Site, error) {
 
 	// getting router config
 	configHandler := NewRouterConfigHandlerPodman(s.cli)
-	config, err := configHandler.GetRouterConfig()
+	routerConfig, err := configHandler.GetRouterConfig()
 	if err != nil {
-		return nil, fmt.Errorf("Skupper is not enabled for user '%s'", Username)
+		return nil, err
 	}
 
 	// Setting basic site info
-	site.Name = config.Metadata.Id
-	site.Mode = string(config.Metadata.Mode)
-	site.Id = config.GetSiteMetadata().Id
-	site.Version = config.GetSiteMetadata().Version
+	site.Name = routerConfig.Metadata.Id
+	site.Mode = string(routerConfig.Metadata.Mode)
+	site.Id = routerConfig.GetSiteMetadata().Id
+	site.Version = routerConfig.GetSiteMetadata().Version
 	site.Platform = types.PlatformPodman
 
 	// Reading cert authorities
@@ -481,6 +483,9 @@ func (s *SiteHandler) Get() (domain.Site, error) {
 	if err != nil {
 		return nil, fmt.Errorf("error reading certificate authorities - %w", err)
 	}
+	if len(cas) == 0 {
+		return nil, fmt.Errorf("skupper certificate authorities not found")
+	}
 	site.CertAuthorities = cas
 
 	// Reading credentials
@@ -488,6 +493,9 @@ func (s *SiteHandler) Get() (domain.Site, error) {
 	if err != nil {
 		return nil, fmt.Errorf("error reading credentials - %w", err)
 	}
+	if len(creds) == 0 {
+		return nil, fmt.Errorf("skupper credentials not found")
+	}
 	site.Credentials = creds
 	for _, cred := range creds {
 		if cred.Name == types.SiteServerSecret {
@@ -501,8 +509,13 @@ func (s *SiteHandler) Get() (domain.Site, error) {
 	if err != nil {
 		return nil, fmt.Errorf("error retrieving deployments - %w", err)
 	}
+	if len(deps) == 0 {
+		return nil, fmt.Errorf("skupper containers not found")
+	}
 	site.Deployments = deps
 
+	routerFound := false
+	ctrlFound := false
 	for _, dep := range site.GetDeployments() {
 		for _, comp := range dep.GetComponents() {
 			depPodman := dep.(*SkupperDeployment)
@@ -519,7 +532,9 @@ func (s *SiteHandler) Get() (domain.Site, error) {
 			}
 			switch c := comp.(type) {
 			case *domain.Router:
-				site.RouterOpts.Logging = qdr.GetRouterLogging(config)
+				routerFound = true
+				c.GetSiteIngresses()
+				site.RouterOpts.Logging = qdr.GetRouterLogging(routerConfig)
 			case *domain.FlowCollector:
 				enableConsole, _ := strconv.ParseBool(c.Env["ENABLE_CONSOLE"])
 				consoleUsers, _ := c.Env["FLOW_USERS"]
@@ -538,6 +553,7 @@ func (s *SiteHandler) Get() (domain.Site, error) {
 				site.ConsoleUser = user
 				site.ConsolePassword = password
 			case *domain.Controller:
+				ctrlFound = true
 			case *domain.Prometheus:
 				site.PrometheusOpts, err = s.getPrometheusServerOptions()
 				if err != nil {
@@ -546,10 +562,16 @@ func (s *SiteHandler) Get() (domain.Site, error) {
 			}
 		}
 	}
+	if !routerFound {
+		return nil, fmt.Errorf("%s component not found", types.TransportDeploymentName)
+	}
+	if !ctrlFound {
+		return nil, fmt.Errorf("%s component not found", types.ControllerPodmanContainerName)
+	}
 
 	// Router options from router config
-	site.RouterOpts.MaxFrameSize = config.Listeners["interior-listener"].MaxFrameSize
-	site.RouterOpts.MaxSessionFrames = config.Listeners["interior-listener"].MaxSessionFrames
+	site.RouterOpts.MaxFrameSize = routerConfig.Listeners["interior-listener"].MaxFrameSize
+	site.RouterOpts.MaxSessionFrames = routerConfig.Listeners["interior-listener"].MaxSessionFrames
 
 	return site, nil
 }
@@ -557,7 +579,15 @@ func (s *SiteHandler) Get() (domain.Site, error) {
 func (s *SiteHandler) Delete() error {
 	site, err := s.Get()
 	if err != nil {
-		return err
+		// removing eventual resources from an incomplete initialization
+		if s.AnyResourceLeft() {
+			removeErr := s.removePodmanResources()
+			if removeErr != nil {
+				return fmt.Errorf("error cleaning up resources: %s - %s", removeErr, err)
+			}
+			return nil
+		}
+		return nil
 	}
 	podmanSite := site.(*Site)
 
@@ -566,10 +596,6 @@ func (s *SiteHandler) Delete() error {
 	if err != nil {
 		return fmt.Errorf("error retrieving deployments - %w", err)
 	}
-	volumeList, err := s.cli.VolumeList()
-	if err != nil {
-		return fmt.Errorf("error retrieving volume list - %w", err)
-	}
 
 	// Stopping and removing containers
 	for _, dep := range deploys {
@@ -578,6 +604,20 @@ func (s *SiteHandler) Delete() error {
 			return fmt.Errorf("error removing deployment %s - %w", dep.GetName(), err)
 		}
 	}
+
+	// Removing any eventual container/volume left
+	if err = s.removePodmanResources(); err != nil {
+		return err
+	}
+
+	// Removing networks
+	_ = s.cli.NetworkRemove(podmanSite.ContainerNetwork)
+
+	return nil
+}
+
+func (s *SiteHandler) removePodmanResources() error {
+	// removing containers owned by Skupper
 	containers, err := s.cli.ContainerList()
 	if err != nil {
 		return fmt.Errorf("error listing containers - %w", err)
@@ -585,17 +625,22 @@ func (s *SiteHandler) Delete() error {
 	for _, c := range containers {
 		if OwnedBySkupper("container", c.Labels) == nil {
 			_ = s.cli.ContainerStop(c.Name)
-			_ = s.cli.ContainerRemove(c.Name)
+			if err = s.cli.ContainerRemove(c.Name); err != nil {
+				return err
+			}
 		}
 	}
 
-	// Removing networks
-	_ = s.cli.NetworkRemove(podmanSite.ContainerNetwork)
-
 	// Removing volumes
+	volumeList, err := s.cli.VolumeList()
+	if err != nil {
+		return fmt.Errorf("error retrieving volume list - %w", err)
+	}
 	for _, v := range volumeList {
 		if app, ok := v.GetLabels()["application"]; ok && app == types.AppName {
-			_ = s.cli.VolumeRemove(v.Name)
+			if err = s.cli.VolumeRemove(v.Name); err != nil {
+				return err
+			}
 		}
 	}
 
@@ -606,9 +651,26 @@ func (s *SiteHandler) Delete() error {
 	if err = systemd.Remove(); err != nil {
 		fmt.Printf("Unable to remove systemd service - %v\n", err)
 	}
+
 	return nil
 }
 
+func (s *SiteHandler) AnyResourceLeft() bool {
+	containers, _ := s.cli.ContainerList()
+	for _, c := range containers {
+		if OwnedBySkupper("container", c.Labels) == nil {
+			return true
+		}
+	}
+	volumeList, _ := s.cli.VolumeList()
+	for _, v := range volumeList {
+		if OwnedBySkupper("volume", v.GetLabels()) == nil {
+			return true
+		}
+	}
+	return false
+}
+
 func (s *SiteHandler) Update() error {
 	return fmt.Errorf("not implemented")
 }
diff --git a/pkg/domain/token.go b/pkg/domain/token.go
index 2a80cebf..08c92314 100644
--- a/pkg/domain/token.go
+++ b/pkg/domain/token.go
@@ -27,7 +27,7 @@ func VerifyToken(secret *v1.Secret) error {
 		secret.ObjectMeta.Labels = map[string]string{}
 	}
 	if _, ok := secret.ObjectMeta.Labels[types.SkupperTypeQualifier]; !ok {
-		// deduce type from structire of secret
+		// deduce type from structure of secret
 		if _, ok = secret.Data["tls.crt"]; ok {
 			secret.ObjectMeta.Labels[types.SkupperTypeQualifier] = types.TypeToken
 		} else if secret.ObjectMeta.Annotations != nil && secret.ObjectMeta.Annotations[types.ClaimUrlAnnotationKey] != "" {
diff --git a/pkg/flow/collector.go b/pkg/flow/collector.go
index c52280c0..ea85d201 100644
--- a/pkg/flow/collector.go
+++ b/pkg/flow/collector.go
@@ -92,7 +92,7 @@ func (fc *FlowCollector) NewMetrics(reg prometheus.Registerer) *collectorMetrics
 		activeFlows: prometheus.NewGaugeVec(
 			prometheus.GaugeOpts{
 				Name: "active_flows",
-				Help: "Number of flows that are currently active, partititioned by source and destination",
+				Help: "Number of flows that are currently active, partitioned by source and destination",
 			},
 			[]string{"sourceSite", "destSite", "address", "protocol", "direction", "sourceProcess", "destProcess"}),
 		lastAccessed: prometheus.NewGaugeVec(
diff --git a/pkg/flow/controller.go b/pkg/flow/controller.go
index f7dde8f3..989647b2 100644
--- a/pkg/flow/controller.go
+++ b/pkg/flow/controller.go
@@ -4,10 +4,10 @@ import (
 	"log"
 	"os"
 	"strings"
+	"sync"
 	"time"
 
 	"github.com/skupperproject/skupper/api/types"
-	"github.com/skupperproject/skupper/client"
 	"github.com/skupperproject/skupper/pkg/config"
 	"github.com/skupperproject/skupper/pkg/messaging"
 )
@@ -17,36 +17,40 @@ const (
 )
 
 type FlowController struct {
-	origin            string
-	creationTime      uint64
-	version           string
-	connectionFactory messaging.ConnectionFactory
-	beaconOutgoing    chan interface{}
-	heartbeatOutgoing chan interface{}
-	recordOutgoing    chan interface{}
-	flushIncoming     chan []interface{}
-	processOutgoing   chan *ProcessRecord
-	processRecords    map[string]*ProcessRecord
-	hostRecords       map[string]*HostRecord
-	hostOutgoing      chan *HostRecord
-	startTime         int64
+	origin               string
+	connectionFactory    messaging.ConnectionFactory
+	beaconOutgoing       chan interface{}
+	heartbeatOutgoing    chan interface{}
+	recordOutgoing       chan interface{}
+	flushIncoming        chan []interface{}
+	processOutgoing      chan *ProcessRecord
+	processRecords       map[string]*ProcessRecord
+	hostRecords          map[string]*HostRecord
+	hostOutgoing         chan *HostRecord
+	siteRecordController siteRecordController
+	startTime            int64
 }
 
-func NewFlowController(origin string, version string, creationTime uint64, connectionFactory messaging.ConnectionFactory) *FlowController {
+type PolicyEvaluator interface {
+	Enabled() bool
+}
+
+const WithPolicyDisabled = policyEnabledConst(false)
+
+func NewFlowController(origin string, version string, creationTime uint64, connectionFactory messaging.ConnectionFactory, policyEvaluator PolicyEvaluator) *FlowController {
 	fc := &FlowController{
-		origin:            origin,
-		creationTime:      creationTime,
-		version:           version,
-		connectionFactory: connectionFactory,
-		beaconOutgoing:    make(chan interface{}, 10),
-		heartbeatOutgoing: make(chan interface{}, 10),
-		recordOutgoing:    make(chan interface{}, 10),
-		flushIncoming:     make(chan []interface{}, 10),
-		processOutgoing:   make(chan *ProcessRecord, 10),
-		processRecords:    make(map[string]*ProcessRecord),
-		hostRecords:       make(map[string]*HostRecord),
-		hostOutgoing:      make(chan *HostRecord, 10),
-		startTime:         time.Now().Unix(),
+		origin:               origin,
+		connectionFactory:    connectionFactory,
+		beaconOutgoing:       make(chan interface{}, 10),
+		heartbeatOutgoing:    make(chan interface{}, 10),
+		recordOutgoing:       make(chan interface{}, 10),
+		flushIncoming:        make(chan []interface{}, 10),
+		processOutgoing:      make(chan *ProcessRecord, 10),
+		processRecords:       make(map[string]*ProcessRecord),
+		hostRecords:          make(map[string]*HostRecord),
+		hostOutgoing:         make(chan *HostRecord, 10),
+		siteRecordController: newSiteRecordController(creationTime, version, policyEvaluator),
+		startTime:            time.Now().Unix(),
 	}
 	return fc
 }
@@ -159,46 +163,18 @@ func (c *FlowController) updateHeartbeats(stopCh <-chan struct{}) {
 	}
 }
 
-func (c *FlowController) updateRecords(stopCh <-chan struct{}) {
+func (c *FlowController) updateRecords(stopCh <-chan struct{}, siteRecordsIncoming <-chan *SiteRecord) {
 	tickerAge := time.NewTicker(10 * time.Minute)
 	defer tickerAge.Stop()
-
-	name := os.Getenv("SKUPPER_SITE_NAME")
-	nameSpace := os.Getenv("SKUPPER_NAMESPACE")
-	policy := Disabled
-	var platformStr string
-	platform := config.GetPlatform()
-	if platform == "" || platform == types.PlatformKubernetes {
-		platformStr = string(types.PlatformKubernetes)
-		cli, err := client.NewClient(nameSpace, "", "")
-		if err == nil {
-			cpv := client.NewClusterPolicyValidator(cli)
-			if cpv.Enabled() {
-				policy = Enabled
-			}
-		}
-	} else if platform == types.PlatformPodman {
-		platformStr = string(types.PlatformPodman)
-	}
-	site := &SiteRecord{
-		Base: Base{
-			RecType:   recordNames[Site],
-			Identity:  os.Getenv("SKUPPER_SITE_ID"),
-			StartTime: c.creationTime,
-		},
-		Name:      &name,
-		NameSpace: &nameSpace,
-		Platform:  &platformStr,
-		Version:   &c.version,
-		Policy:    &policy,
-	}
-
-	c.recordOutgoing <- site
-
 	for {
 		select {
 		case process := <-c.processOutgoing:
 			c.recordOutgoing <- process
+		case site, ok := <-siteRecordsIncoming:
+			if !ok {
+				continue
+			}
+			c.recordOutgoing <- site
 		case host := <-c.hostOutgoing:
 			c.recordOutgoing <- host
 		case flushUpdates := <-c.flushIncoming:
@@ -208,7 +184,7 @@ func (c *FlowController) updateRecords(stopCh <-chan struct{}) {
 					log.Println("Unable to convert interface to flush")
 				}
 			}
-			c.recordOutgoing <- site
+			c.recordOutgoing <- c.siteRecordController.Record()
 			for _, process := range c.processRecords {
 				c.recordOutgoing <- process
 			}
@@ -240,7 +216,7 @@ func (c *FlowController) run(stopCh <-chan struct{}) {
 
 	go c.updateBeacon(stopCh)
 	go c.updateHeartbeats(stopCh)
-	go c.updateRecords(stopCh)
+	go c.updateRecords(stopCh, c.siteRecordController.Start(stopCh))
 	<-stopCh
 
 	beaconSender.stop()
@@ -248,3 +224,116 @@ func (c *FlowController) run(stopCh <-chan struct{}) {
 	recordSender.stop()
 	flushReceiver.stop()
 }
+
+type siteRecordController struct {
+	mu            sync.Mutex
+	Identity      string
+	CreatedAt     uint64
+	Version       string
+	Name          string
+	Namespace     string
+	Platform      string
+	PolicyEnabled bool
+
+	policyEvaluator PolicyEvaluator
+	pollInterval    time.Duration
+}
+
+func newSiteRecordController(createdAt uint64, version string, policyEvaluator PolicyEvaluator) siteRecordController {
+	var policy bool
+	var platformStr string
+	platform := config.GetPlatform()
+	if platform == "" || platform == types.PlatformKubernetes {
+		platformStr = string(types.PlatformKubernetes)
+		policy = policyEvaluator.Enabled()
+	} else if platform == types.PlatformPodman {
+		platformStr = string(types.PlatformPodman)
+	}
+	return siteRecordController{
+		Identity:        os.Getenv("SKUPPER_SITE_ID"),
+		CreatedAt:       createdAt,
+		Version:         version,
+		Name:            os.Getenv("SKUPPER_SITE_NAME"),
+		Namespace:       os.Getenv("SKUPPER_NAMESPACE"),
+		Platform:        platformStr,
+		PolicyEnabled:   policy,
+		policyEvaluator: policyEvaluator,
+	}
+}
+
+func (c *siteRecordController) Start(stopCh <-chan struct{}) <-chan *SiteRecord {
+	updates := make(chan *SiteRecord, 1)
+
+	go func() {
+		updates <- c.Record()
+
+		if c.Platform != string(types.PlatformKubernetes) {
+			return
+		}
+		// watch for changes to  policy enabled
+		pollInterval := c.pollInterval
+		if pollInterval <= 0 {
+			pollInterval = time.Second * 30
+		}
+		ticker := time.NewTicker(pollInterval)
+		defer ticker.Stop()
+		for {
+			select {
+			case <-stopCh:
+				return
+			case <-ticker.C:
+				if update := c.updatePolicy(); update != nil {
+					updates <- update
+				}
+			}
+		}
+	}()
+	return updates
+}
+
+func (c *siteRecordController) updatePolicy() *SiteRecord {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+	enabled := c.policyEvaluator.Enabled()
+	if enabled == c.PolicyEnabled {
+		return nil
+	}
+	c.PolicyEnabled = enabled
+	policy := Disabled
+	if enabled {
+		policy = Enabled
+	}
+	return &SiteRecord{
+		Base: Base{
+			RecType:  recordNames[Site],
+			Identity: c.Identity,
+		},
+		Policy: &policy,
+	}
+
+}
+
+func (c *siteRecordController) Record() *SiteRecord {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+	policy := Disabled
+	if c.PolicyEnabled {
+		policy = Enabled
+	}
+	return &SiteRecord{
+		Base: Base{
+			RecType:   recordNames[Site],
+			Identity:  c.Identity,
+			StartTime: c.CreatedAt,
+		},
+		Name:      &c.Name,
+		NameSpace: &c.Namespace,
+		Platform:  &c.Platform,
+		Version:   &c.Version,
+		Policy:    &policy,
+	}
+}
+
+type policyEnabledConst bool
+
+func (c policyEnabledConst) Enabled() bool { return bool(c) }
diff --git a/pkg/flow/flow_mem_driver.go b/pkg/flow/flow_mem_driver.go
index dfd9aa88..94cc67f0 100644
--- a/pkg/flow/flow_mem_driver.go
+++ b/pkg/flow/flow_mem_driver.go
@@ -690,6 +690,7 @@ func (fc *FlowCollector) updateLastHeard(source string) error {
 }
 
 func (fc *FlowCollector) updateRecord(record interface{}) error {
+	var updatesNetworkStatus bool
 	switch record.(type) {
 	case HeartbeatRecord:
 		if heartbeat, ok := record.(HeartbeatRecord); ok {
@@ -721,7 +722,10 @@ func (fc *FlowCollector) updateRecord(record interface{}) error {
 					}
 					fc.deleteRecord(current)
 				} else {
-					*current = site
+					updatesNetworkStatus = true
+					if site.Policy != nil {
+						current.Policy = site.Policy
+					}
 				}
 			}
 			fc.updateLastHeard(site.Source)
@@ -1182,6 +1186,10 @@ func (fc *FlowCollector) updateRecord(record interface{}) error {
 	default:
 		return fmt.Errorf("Unrecognized record type %T", record)
 	}
+
+	if updatesNetworkStatus && fc.mode == RecordStatus {
+		fc.updateNetworkStatus()
+	}
 	return nil
 }
 
diff --git a/pkg/kube/claims/claim_verifier.go b/pkg/kube/claims/claim_verifier.go
index 54e379ff..8c8c621d 100644
--- a/pkg/kube/claims/claim_verifier.go
+++ b/pkg/kube/claims/claim_verifier.go
@@ -4,7 +4,7 @@ import (
 	"bytes"
 	"context"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"log"
 	"net/http"
 	"os"
@@ -143,7 +143,7 @@ func (server *ClaimVerifier) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 	name := strings.Join(strings.Split(r.URL.Path, "/"), "")
-	body, err := ioutil.ReadAll(r.Body)
+	body, err := io.ReadAll(r.Body)
 	if err != nil {
 		log.Printf("Error reading body: %s", err.Error())
 		http.Error(w, "Request body not valid", http.StatusBadRequest)
diff --git a/pkg/kube/configmaps.go b/pkg/kube/configmaps.go
index e6442db0..0f803af3 100644
--- a/pkg/kube/configmaps.go
+++ b/pkg/kube/configmaps.go
@@ -98,7 +98,7 @@ func UpdateSkupperServices(changed []types.ServiceInterface, deleted []string, o
 			return fmt.Errorf("Failed to update skupper-services config map: %s", err)
 		}
 	} else {
-		return fmt.Errorf("Could not retrive service definitions from configmap 'skupper-services', Error: %v", err)
+		return fmt.Errorf("Could not retrieve service definitions from configmap 'skupper-services', Error: %v", err)
 	}
 
 	return nil
diff --git a/pkg/kube/namespaces.go b/pkg/kube/namespaces.go
index 5dd62adb..86f9568b 100644
--- a/pkg/kube/namespaces.go
+++ b/pkg/kube/namespaces.go
@@ -3,7 +3,6 @@ package kube
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
 	"os"
 
 	corev1 "k8s.io/api/core/v1"
@@ -30,9 +29,9 @@ func DeleteNamespace(name string, cli kubernetes.Interface) error {
 	if err == nil {
 		return err
 	} else if errors.IsNotFound(err) {
-		return fmt.Errorf("Namepace %s does not exist.", name)
+		return fmt.Errorf("Namespace %s does not exist.", name)
 	} else {
-		return fmt.Errorf("Failed to delete namesspace: %w", err)
+		return fmt.Errorf("Failed to delete namespace: %w", err)
 	}
 }
 
@@ -45,7 +44,7 @@ func CurrentNamespace() (string, error) {
 	}
 	_, err := os.Stat(NamespaceFile)
 	if err == nil {
-		raw, err := ioutil.ReadFile(NamespaceFile)
+		raw, err := os.ReadFile(NamespaceFile)
 		if err != nil {
 			return namespace, err
 		}
diff --git a/pkg/kube/pods.go b/pkg/kube/pods.go
index 82f5c11d..bffc7447 100644
--- a/pkg/kube/pods.go
+++ b/pkg/kube/pods.go
@@ -20,6 +20,7 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"k8s.io/apimachinery/pkg/labels"
 	"strings"
 	"time"
 
@@ -65,9 +66,17 @@ func FirstReadyPod(list []corev1.Pod) *corev1.Pod {
 	return nil
 }
 
-func GetReadyPod(namespace string, clientset kubernetes.Interface, component string) (*corev1.Pod, error) {
-	selector := "skupper.io/component=" + component
-	pods, err := clientset.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: selector})
+func GetReadyPod(namespace string, clientset kubernetes.Interface, component string, application string) (*corev1.Pod, error) {
+	matchLabels := map[string]string{"skupper.io/component": component}
+
+	if application != "" {
+		matchLabels["application"] = application
+	}
+
+	labelSelector := metav1.LabelSelector{MatchLabels: matchLabels}
+	listOptions := metav1.ListOptions{LabelSelector: labels.Set(labelSelector.MatchLabels).String()}
+
+	pods, err := clientset.CoreV1().Pods(namespace).List(context.TODO(), listOptions)
 	if err != nil {
 		return nil, err
 	} else if len(pods.Items) == 0 {
@@ -96,7 +105,11 @@ func GetImageVersion(pod *corev1.Pod, container string) string {
 }
 
 func GetComponentVersion(namespace string, clientset kubernetes.Interface, component string, container string) string {
-	pod, err := GetReadyPod(namespace, clientset, component)
+	application := ""
+	if component == "router" {
+		application = "skupper-router"
+	}
+	pod, err := GetReadyPod(namespace, clientset, component, application)
 	if err == nil {
 		return GetImageVersion(pod, container)
 	} else {
diff --git a/pkg/kube/qdr/mgmt.go b/pkg/kube/qdr/mgmt.go
index 95698269..a4902872 100644
--- a/pkg/kube/qdr/mgmt.go
+++ b/pkg/kube/qdr/mgmt.go
@@ -290,7 +290,7 @@ func getLocalRouterId(namespace string, clientset kubernetes.Interface, config *
 }
 
 func router_exec(command []string, namespace string, clientset kubernetes.Interface, config *restclient.Config) (*bytes.Buffer, error) {
-	pod, err := kube.GetReadyPod(namespace, clientset, "router")
+	pod, err := kube.GetReadyPod(namespace, clientset, "router", "skupper-router")
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/network/network.go b/pkg/network/network.go
index 07a230ae..0b32a563 100644
--- a/pkg/network/network.go
+++ b/pkg/network/network.go
@@ -17,39 +17,49 @@ func (s *SkupperStatus) GetServiceSitesMap() map[string][]SiteStatusInfo {
 	for _, site := range s.NetworkStatus.SiteStatus {
 
 		if len(site.RouterStatus) > 0 {
-			for _, listener := range site.RouterStatus[0].Listeners {
-				if mapServiceSites[listener.Name] != nil {
-					serviceSites := mapServiceSites[listener.Name]
-
-					serviceSites = append(serviceSites, site)
-					mapServiceSites[listener.Name] = serviceSites
-				} else {
-					mapServiceSites[listener.Name] = []SiteStatusInfo{site}
+			for _, router := range site.RouterStatus {
+				for _, listener := range router.Listeners {
+					if len(mapServiceSites[listener.Address]) == 0 || !sliceContainsSite(mapServiceSites[listener.Address], site) {
+						mapServiceSites[listener.Address] = append(mapServiceSites[listener.Address], site)
+					}
+				}
+
+				/* Checking for headless services:
+				   the service can be available in the same site where the headless service was exposed, but in that site there is no
+				   listeners for the statefulstet.
+				*/
+				for _, connector := range router.Connectors {
+					if len(mapServiceSites[connector.Address]) == 0 || !sliceContainsSite(mapServiceSites[connector.Address], site) {
+						mapServiceSites[connector.Address] = append(mapServiceSites[connector.Address], site)
+					}
 				}
 			}
+
 		}
 	}
 
 	return mapServiceSites
 }
 
-func (s *SkupperStatus) GetSiteTargetsMap() map[string]map[string]ConnectorInfo {
+func (s *SkupperStatus) GetSiteTargetMap() map[string]map[string]ConnectorInfo {
 
-	mapSiteTargets := make(map[string]map[string]ConnectorInfo)
+	mapSiteTarget := make(map[string]map[string]ConnectorInfo)
 
 	for _, site := range s.NetworkStatus.SiteStatus {
 
 		if len(site.RouterStatus) > 0 {
-			for _, connector := range site.RouterStatus[0].Connectors {
-				if mapSiteTargets[site.Site.Identity] == nil {
-					mapSiteTargets[site.Site.Identity] = make(map[string]ConnectorInfo)
+			for _, router := range site.RouterStatus {
+				for _, connector := range router.Connectors {
+					if mapSiteTarget[site.Site.Identity] == nil {
+						mapSiteTarget[site.Site.Identity] = make(map[string]ConnectorInfo)
+					}
+					mapSiteTarget[site.Site.Identity][connector.Address] = connector
 				}
-				mapSiteTargets[site.Site.Identity][connector.Address] = connector
 			}
 		}
 	}
 
-	return mapSiteTargets
+	return mapSiteTarget
 }
 
 func (s *SkupperStatus) GetRouterSiteMap() map[string]SiteStatusInfo {
@@ -59,7 +69,11 @@ func (s *SkupperStatus) GetRouterSiteMap() map[string]SiteStatusInfo {
 			for _, routerStatus := range siteStatus.RouterStatus {
 				// the name of the router has a "0/" as a prefix that it is needed to remove
 				routerName := strings.Split(routerStatus.Router.Name, "/")
-				mapRouterSite[routerName[1]] = siteStatus
+
+				// Remove routers that belong to statefulsets for headless services
+				if len(routerName) > 1 && strings.HasPrefix(routerName[1], siteStatus.Site.Name) {
+					mapRouterSite[routerName[1]] = siteStatus
+				}
 			}
 		}
 	}
@@ -79,7 +93,6 @@ func (s *SkupperStatus) GetSiteById(siteId string) *SiteStatusInfo {
 }
 
 func (s *SkupperStatus) GetSiteLinkMapPerRouter(router *RouterStatusInfo, site *SiteInfo) map[string]LinkInfo {
-
 	routerSiteMap := s.GetRouterSiteMap()
 	siteLinkMap := make(map[string]LinkInfo)
 	if len(router.Links) > 0 {
@@ -104,6 +117,21 @@ func (s *SkupperStatus) LinkBelongsToSameSite(linkName string, siteId string, ro
 
 }
 
+func (s *SkupperStatus) GetRouterIndex(site *SiteStatusInfo) (error, int) {
+
+	for index, router := range site.RouterStatus {
+		// Ignore routers that belong to statefulsets for headless services and any other router
+		routerId := strings.Split(router.Router.Name, "/")
+
+		if len(routerId) > 1 && strings.HasPrefix(routerId[1], site.Site.Name) {
+			return nil, index
+
+		}
+	}
+
+	return fmt.Errorf("not valid router found"), -1
+}
+
 func (s *SkupperStatus) RemoveLinksFromSameSite(router RouterStatusInfo, site SiteInfo) []LinkInfo {
 	routerSiteMap := s.GetRouterSiteMap()
 	var filteredLinks []LinkInfo
@@ -129,3 +157,13 @@ func UnmarshalSkupperStatus(data map[string]string) (*NetworkStatusInfo, error)
 
 	return networkStatusInfo, nil
 }
+
+func sliceContainsSite(sites []SiteStatusInfo, site SiteStatusInfo) bool {
+	for _, s := range sites {
+		if site.Site.Identity == s.Site.Identity {
+			return true
+		}
+	}
+
+	return false
+}
diff --git a/pkg/qdr/amqp_mgmt.go b/pkg/qdr/amqp_mgmt.go
index 71cb8ab4..f4024523 100644
--- a/pkg/qdr/amqp_mgmt.go
+++ b/pkg/qdr/amqp_mgmt.go
@@ -360,7 +360,7 @@ func (a *Agent) request(operation string, typename string, name string, attribut
 	response, err := a.receiver.Receive(ctx)
 	if err != nil {
 		a.Close()
-		return fmt.Errorf("Failed to receive reponse: %s", err)
+		return fmt.Errorf("Failed to receive response: %s", err)
 	}
 	response.Accept()
 	if status, ok := AsInt(response.ApplicationProperties["statusCode"]); !ok && !isOk(status) {
@@ -475,7 +475,7 @@ func (a *Agent) QueryByAgentAddress(typename string, attributes []string, agent
 	response, err := a.receiver.Receive(ctx)
 	if err != nil {
 		a.Close()
-		return nil, fmt.Errorf("Failed to receive reponse: %s", err)
+		return nil, fmt.Errorf("Failed to receive response: %s", err)
 	}
 	response.Accept()
 	if status, ok := AsInt(response.ApplicationProperties["statusCode"]); ok && isOk(status) {
@@ -573,7 +573,7 @@ func (a *Agent) BatchQuery(queries []Query) ([][]Record, error) {
 		response, err := a.receiver.Receive(ctx)
 		if err != nil {
 			a.Close()
-			return nil, fmt.Errorf("Failed to receive reponse: %s", err)
+			return nil, fmt.Errorf("Failed to receive response: %s", err)
 		}
 		response.Accept()
 		responseIndex, ok := response.Properties.CorrelationID.(uint64)
@@ -1369,7 +1369,7 @@ func (a *Agent) Request(request *Request) (*Response, error) {
 	responseMsg, err := a.receiver.Receive(ctx)
 	if err != nil {
 		a.Close()
-		return nil, fmt.Errorf("Failed to receive reponse: %s", err)
+		return nil, fmt.Errorf("Failed to receive response: %s", err)
 	}
 	responseMsg.Accept()
 
diff --git a/pkg/utils/retry.go b/pkg/utils/retry.go
index d3a0188b..ba247186 100644
--- a/pkg/utils/retry.go
+++ b/pkg/utils/retry.go
@@ -30,7 +30,7 @@ type CheckedFunc func() error
 // For example, if interval is 3s, f takes 1s, another f will be called 2s later.
 // However, if f takes longer than interval, it will be delayed.
 //
-// If an error is received from f, fail immediatelly and return that error (no
+// If an error is received from f, fail immediately and return that error (no
 // further retries).
 //
 // Keep in mind that the second argument is for max _retries_.  So, with a value
@@ -57,8 +57,8 @@ func Retry(interval time.Duration, maxRetries int, f ConditionFunc) error {
 	}
 }
 
-// This is similar to Retry(), but it will not fail immediatelly on errors, and
-// if the retries are exausted and f() still failing, it will return f()'s error
+// This is similar to Retry(), but it will not fail immediately on errors, and
+// if the retries are exhausted and f() still failing, it will return f()'s error
 func RetryError(interval time.Duration, maxRetries int, f CheckedFunc) error {
 	if maxRetries <= 0 {
 		return fmt.Errorf("maxRetries (%d) should be > 0", maxRetries)
diff --git a/scripts/swagger-v4.0.3.yaml b/scripts/swagger-v4.0.3.yaml
index 7e62a5b7..3b643c3c 100644
--- a/scripts/swagger-v4.0.3.yaml
+++ b/scripts/swagger-v4.0.3.yaml
@@ -2870,7 +2870,7 @@ definitions:
         type: array
       ExtraHosts:
         description: |-
-          ExtraHosts contains hosts that will be aded to the container's
+          ExtraHosts contains hosts that will be added to the container's
           etc/hosts.
         items:
           type: string


Test patch:
diff --git a/client/serviceinterface_create_test.go b/client/serviceinterface_create_test.go
index 8bf70bac..fe929593 100644
--- a/client/serviceinterface_create_test.go
+++ b/client/serviceinterface_create_test.go
@@ -34,7 +34,7 @@ func check_result(t *testing.T, name string, timeoutSeconds float64, resultType
 	}
 	// Sometimes it requires a little time for the requested entities to be
 	// created and for the informers to tell us about them.
-	// So -- count down by tenths of a second until the alotted timeout expires,
+	// So -- count down by tenths of a second until the allotted timeout expires,
 	// or until we have at least the correct number of results.
 	for {
 		if timeoutSeconds <= 0 {
diff --git a/client/token_claim_create_test.go b/client/token_claim_create_test.go
index e38bfae4..767eaf4c 100644
--- a/client/token_claim_create_test.go
+++ b/client/token_claim_create_test.go
@@ -3,7 +3,6 @@ package client
 import (
 	"bytes"
 	"context"
-	"io/ioutil"
 	"net/url"
 	"os"
 	"strings"
@@ -19,7 +18,7 @@ import (
 )
 
 func readSecretFromFile(filename string) (*corev1.Secret, error) {
-	yaml, err := ioutil.ReadFile(filename)
+	yaml, err := os.ReadFile(filename)
 	if err != nil {
 		return nil, err
 	}
diff --git a/cmd/service-controller/claim_handler_test.go b/cmd/service-controller/claim_handler_test.go
index de2e93af..26c3d2e2 100644
--- a/cmd/service-controller/claim_handler_test.go
+++ b/cmd/service-controller/claim_handler_test.go
@@ -4,7 +4,7 @@ import (
 	"bytes"
 	"context"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"net/http/httptest"
 	"testing"
@@ -53,7 +53,7 @@ func (server *MockVerifier) addFailedResult(code int, message string) {
 
 func (server *MockVerifier) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	if len(server.Results) > server.Current {
-		body, err := ioutil.ReadAll(r.Body)
+		body, err := io.ReadAll(r.Body)
 		if err != nil {
 			http.Error(w, err.Error(), http.StatusInternalServerError)
 		} else {
diff --git a/cmd/service-controller/services_test.go b/cmd/service-controller/services_test.go
index 470e708e..01e95bad 100644
--- a/cmd/service-controller/services_test.go
+++ b/cmd/service-controller/services_test.go
@@ -6,7 +6,6 @@ import (
 	"encoding/json"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
 	"reflect"
@@ -447,7 +446,7 @@ func TestServeServices(t *testing.T) {
 		res := httptest.NewRecorder()
 
 		router.ServeHTTP(res, req)
-		responseBody, _ := ioutil.ReadAll(res.Body)
+		responseBody, _ := io.ReadAll(res.Body)
 		assert.Equal(t, res.Code, test.expectedCode, name+" "+string(responseBody))
 		if test.checker != nil {
 			assert.Check(t, test.checker.Check(responseBody), name)
@@ -638,7 +637,7 @@ func TestServeServiceTargets(t *testing.T) {
 		res := httptest.NewRecorder()
 
 		router.ServeHTTP(res, req)
-		responseBody, _ := ioutil.ReadAll(res.Body)
+		responseBody, _ := io.ReadAll(res.Body)
 		assert.Equal(t, res.Code, test.expectedCode, name+" "+string(responseBody))
 		if test.checker != nil {
 			assert.Check(t, test.checker.Check(responseBody), name)
diff --git a/cmd/service-controller/tokens_test.go b/cmd/service-controller/tokens_test.go
index 634217c8..032b9a5f 100644
--- a/cmd/service-controller/tokens_test.go
+++ b/cmd/service-controller/tokens_test.go
@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"context"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
 	"strconv"
@@ -262,7 +261,7 @@ func TestDownloadClaim(t *testing.T) {
 		router.ServeHTTP(res, req)
 		assert.Equal(t, res.Code, test.expectedCode, name)
 		if res.Code == http.StatusOK {
-			body, err := ioutil.ReadAll(res.Body)
+			body, err := io.ReadAll(res.Body)
 			assert.Check(t, err, testname)
 			s := json.NewSerializerWithOptions(json.DefaultMetaFactory, scheme.Scheme, scheme.Scheme, json.SerializerOptions{})
 			var token corev1.Secret
diff --git a/cmd/skupper/skupper_cluster_test.go b/cmd/skupper/skupper_cluster_test.go
index 4d3fcf4f..6340ebdd 100644
--- a/cmd/skupper/skupper_cluster_test.go
+++ b/cmd/skupper/skupper_cluster_test.go
@@ -4,7 +4,7 @@ import (
 	"bytes"
 	"context"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/kubernetes"
 	k8stesting "k8s.io/client-go/testing"
@@ -150,7 +150,7 @@ func testCommand(t *testing.T, cmd *cobra.Command, testName string, expectedErro
 	cmdOutput, err := executeCommand(cmd, args...)
 
 	w.Close()
-	stdOutput, _ := ioutil.ReadAll(r)
+	stdOutput, _ := io.ReadAll(r)
 	os.Stdout = rescueStdout
 
 	lines := strings.Split(string(stdOutput), "\n")
diff --git a/cmd/skupper/skupper_mock_test.go b/cmd/skupper/skupper_mock_test.go
index 380ed7a1..1f0918f6 100644
--- a/cmd/skupper/skupper_mock_test.go
+++ b/cmd/skupper/skupper_mock_test.go
@@ -319,10 +319,6 @@ func (v *vanClientMock) NetworkStatus(ctx context.Context) (*network.NetworkStat
 	return &result, nil
 }
 
-func (v *vanClientMock) GetRemoteLinks(ctx context.Context, siteConfig *types.SiteConfig) ([]*network.RemoteLinkInfo, error) {
-	return nil, nil
-}
-
 func (v *vanClientMock) GetConsoleUrl(namespace string) (string, error) {
 	return "", nil
 }
@@ -577,7 +573,7 @@ func TestExpose_Binding(t *testing.T) {
 
 		})
 
-	t.Run("service exists and Bind is successfull",
+	t.Run("service exists and Bind is successful",
 		func(t *testing.T) {
 			cli := &vanClientMock{}
 			aService := &types.ServiceInterface{
diff --git a/pkg/config/local_test.go b/pkg/config/local_test.go
index 2b2379b6..9309b9e3 100644
--- a/pkg/config/local_test.go
+++ b/pkg/config/local_test.go
@@ -3,7 +3,6 @@ package config
 import (
 	"bytes"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"reflect"
 	"testing"
@@ -365,7 +364,7 @@ func TestPlatformInfo_Update(t *testing.T) {
 
 			// loading file
 			if tt.want != nil {
-				data, _ := ioutil.ReadFile(tempFile)
+				data, _ := os.ReadFile(tempFile)
 				decoder := yaml.NewDecoder(bytes.NewReader(data))
 				current := &PlatformInfo{}
 				_ = decoder.Decode(current)
diff --git a/pkg/domain/podman/site_test.go b/pkg/domain/podman/site_test.go
index 61e61c8a..b278a67f 100644
--- a/pkg/domain/podman/site_test.go
+++ b/pkg/domain/podman/site_test.go
@@ -4,15 +4,28 @@
 package podman
 
 import (
+	_ "embed"
+	"strings"
 	"testing"
+	"time"
 
+	"github.com/google/uuid"
 	"github.com/skupperproject/skupper/api/types"
 	"github.com/skupperproject/skupper/client/container"
+	"github.com/skupperproject/skupper/client/podman"
 	"github.com/skupperproject/skupper/pkg/domain"
+	"github.com/skupperproject/skupper/pkg/images"
 	"github.com/skupperproject/skupper/pkg/utils"
 	"gotest.tools/assert"
 )
 
+var (
+	//go:embed update/skrouterd.json
+	skrouterdJson string
+	//go:embed update/skupper-services.json
+	skupperServicesJson string
+)
+
 func TestSiteHandler(t *testing.T) {
 	siteHandler, err := NewSitePodmanHandler(getEndpoint())
 	assert.Assert(t, err)
@@ -101,9 +114,17 @@ func TestSiteHandler(t *testing.T) {
 			for _, dep := range podmanSite.GetDeployments() {
 				assert.Assert(t, len(dep.GetComponents()) > 0, "no components found for %s", dep.GetName())
 				for _, cmp := range dep.GetComponents() {
-					cmpContainer, err := siteHandler.cli.ContainerInspect(cmp.Name())
+					var cmpContainer *container.Container
+					err = utils.Retry(time.Second*6, 10, func() (bool, error) {
+						cmpContainer, err = siteHandler.cli.ContainerInspect(cmp.Name())
+						if err != nil {
+							return true, err
+						}
+						return cmpContainer.Running, nil
+					})
 					assert.Assert(t, err, "error retrieving container info")
-					assert.Assert(t, cmpContainer.Running)
+					assert.Assert(t, cmpContainer.Running, "component %s is not running - exit code: %d "+
+						"- restarts: %d", cmpContainer.Name, cmpContainer.ExitCode, cmpContainer.RestartCount)
 				}
 			}
 			assert.Assert(t, len(podmanSite.Credentials) > 0)
@@ -115,3 +136,273 @@ func TestSiteHandler(t *testing.T) {
 		})
 	}
 }
+
+func TestSiteHandlerDeleteBrokenSite(t *testing.T) {
+	siteHandler, err := NewSitePodmanHandler(getEndpoint())
+	assert.Assert(t, err)
+
+	// counting containers and volumes
+	countContainersAndVolumes := func() (int, int) {
+		// Saving number of containers and volumes before initializing skupper
+		// Asserting number of container and volumes are > 1
+		containers, err := siteHandler.cli.ContainerList()
+		containerCount := len(containers)
+		assert.Assert(t, err)
+		volumes, err := siteHandler.cli.VolumeList()
+		volumeCount := len(volumes)
+		assert.Assert(t, err)
+		return containerCount, volumeCount
+	}
+
+	// retrieve totals before site creation
+	containersBefore, volumesBefore := countContainersAndVolumes()
+
+	// Initializing skupper
+	site := &Site{
+		SiteCommon: &domain.SiteCommon{
+			Name: "site-podman-fc-ingress",
+		},
+		IngressHosts:        []string{"127.0.0.1"},
+		EnableFlowCollector: true,
+		EnableConsole:       true,
+		AuthMode:            "internal",
+		ConsoleUser:         "internal",
+		ConsolePassword:     "internal",
+		PrometheusOpts: types.PrometheusServerOptions{
+			ExternalServer: "http://10.0.0.1:8080/v1",
+			AuthMode:       "internal",
+			User:           "admin",
+			Password:       "admin",
+		},
+	}
+
+	// Create a podman site
+	err = siteHandler.Create(site)
+	assert.Assert(t, err)
+
+	// remove site
+	defer func() {
+		_ = siteHandler.Delete()
+	}()
+
+	// Validating container and volume counts after creation
+	containersAfterCreate, volumesAfterCreate := countContainersAndVolumes()
+	assert.Equal(t, containersAfterCreate, containersBefore+4)
+	assert.Equal(t, volumesAfterCreate, volumesBefore+14)
+
+	//
+	// Removing mandatory volume
+	err = siteHandler.cli.VolumeRemove(types.TransportConfigMapName)
+	assert.Assert(t, err)
+
+	// Forcing site to be in a bad state
+	err = siteHandler.Delete()
+	assert.Assert(t, err)
+
+	// Asserting number of containers and volumes are back to original state
+	containersAfterDelete, volumesAfterDelete := countContainersAndVolumes()
+	assert.Equal(t, containersBefore, containersAfterDelete)
+	assert.Equal(t, volumesBefore, volumesAfterDelete)
+}
+
+func TestSiteHandlerDeleteBrokenSiteMock(t *testing.T) {
+	cli := podman.NewPodmanClientMock(mockContainers())
+	mock := cli.RestClient.(*podman.RestClientMock)
+	assert.Assert(t, mock.MockVolumeFiles(mockVolumes()))
+	defer func() {
+		_ = mock.CleanupMockVolumeDir()
+	}()
+
+	// must NOT be removed after SiteHandler.Delete() is called
+	mock.Volumes["my-volume"] = &container.Volume{
+		Name: "my-volume",
+		Labels: map[string]string{
+			"my-label": "my-value",
+		},
+	}
+
+	sh := NewSitePodmanHandlerFromCli(cli)
+	site, err := sh.Get()
+	assert.Assert(t, err)
+	assert.Assert(t, site != nil)
+
+	// verify mock site is in good state
+	assert.Assert(t, err)
+
+	// validating number of containers and volumes before removal
+	containers, err := cli.ContainerList()
+	assert.Assert(t, err)
+	assert.Equal(t, len(containers), 5)
+	volumes, err := cli.VolumeList()
+	assert.Assert(t, err)
+	assert.Equal(t, len(volumes), 13)
+
+	// force a site get to be in a bad state
+	delete(mock.Volumes, types.TransportConfigMapName)
+	site, err = sh.Get()
+	assert.Assert(t, err != nil)
+	assert.Assert(t, site == nil)
+
+	// validating delete removed remaining resources
+	err = sh.Delete()
+	assert.Assert(t, err)
+
+	// assert container not owned by skupper remains
+	containers, err = cli.ContainerList()
+	assert.Assert(t, err)
+	assert.Equal(t, len(containers), 1)
+
+	// assert volume not owned by skupper remains
+	volumes, err = cli.VolumeList()
+	assert.Assert(t, err)
+	assert.Equal(t, len(volumes), 1)
+
+}
+
+func mockContainers() []*container.Container {
+	return []*container.Container{
+		{
+			ID:    strings.Replace(uuid.New().String(), "-", "", -1),
+			Name:  "skupper-router",
+			Image: images.GetRouterImageName(),
+			Labels: map[string]string{
+				"application":          "skupper",
+				"skupper.io/component": "skupper-router",
+			},
+			Networks: map[string]container.ContainerNetworkInfo{
+				"skupper": {
+					ID:        "skupper",
+					IPAddress: "172.17.0.10",
+					Gateway:   "172.17.0.1",
+					Aliases:   []string{"skupper-router"},
+					//Aliases:   []string{"skupper", "service-controller"},
+				},
+			},
+			Ports: []container.Port{
+				{Host: "55671", HostIP: "", Target: "55671", Protocol: "tcp"},
+				{Host: "45671", HostIP: "", Target: "45671", Protocol: "tcp"},
+			},
+			Running:   true,
+			CreatedAt: time.Now(),
+			StartedAt: time.Now(),
+		},
+		{
+			ID:    strings.Replace(uuid.New().String(), "-", "", -1),
+			Name:  "skupper-controller-podman",
+			Image: images.GetControllerPodmanImageName(),
+			Labels: map[string]string{
+				"application":          "skupper",
+				"skupper.io/component": "skupper-controller-podman",
+			},
+			Networks: map[string]container.ContainerNetworkInfo{
+				"skupper": {
+					ID:        "skupper",
+					IPAddress: "172.17.0.11",
+					Gateway:   "172.17.0.1",
+					Aliases:   []string{"skupper", "service-controller-podman"},
+				},
+			},
+			Running:   true,
+			CreatedAt: time.Now(),
+			StartedAt: time.Now(),
+		}, {
+			ID:    strings.Replace(uuid.New().String(), "-", "", -1),
+			Name:  "flow-collector",
+			Image: images.GetFlowCollectorImageName(),
+			Labels: map[string]string{
+				"application":          "skupper",
+				"skupper.io/component": "flow-collector",
+			},
+			Networks: map[string]container.ContainerNetworkInfo{
+				"skupper": {
+					ID:        "skupper",
+					IPAddress: "172.17.0.12",
+					Gateway:   "172.17.0.1",
+					Aliases:   []string{"flow-collector"},
+				},
+			},
+			Ports: []container.Port{
+				{Host: "8010", HostIP: "", Target: "8010", Protocol: "tcp"},
+			},
+			Running:   true,
+			CreatedAt: time.Now(),
+			StartedAt: time.Now(),
+		}, {
+			ID:    strings.Replace(uuid.New().String(), "-", "", -1),
+			Name:  "nginx-service",
+			Image: images.GetRouterImageName(),
+			Labels: map[string]string{
+				"application":        "skupper",
+				"skupper.io/address": "nginx",
+			},
+			Networks: map[string]container.ContainerNetworkInfo{
+				"skupper": {
+					ID:        "skupper",
+					IPAddress: "172.17.0.13",
+					Gateway:   "172.17.0.1",
+					Aliases:   []string{"nginx-service"},
+				},
+			},
+			Ports: []container.Port{
+				{Host: "8080", HostIP: "", Target: "8080", Protocol: "tcp"},
+			},
+			Running:   true,
+			CreatedAt: time.Now(),
+			StartedAt: time.Now(),
+		}, {
+			ID:     strings.Replace(uuid.New().String(), "-", "", -1),
+			Name:   "nginx",
+			Image:  "docker.io/nginxinc/nginx-unprivileged:stable-alpine",
+			Labels: map[string]string{},
+			Networks: map[string]container.ContainerNetworkInfo{
+				"skupper": {
+					ID:        "skupper",
+					IPAddress: "172.17.0.14",
+					Gateway:   "172.17.0.1",
+					Aliases:   []string{"nginx"},
+				},
+			},
+			Running:   true,
+			CreatedAt: time.Now(),
+			StartedAt: time.Now(),
+		},
+	}
+}
+
+func mockVolumes() (map[string]*container.Volume, map[string]map[string]string) {
+	var volumes = map[string]*container.Volume{}
+	var volumesFiles = map[string]map[string]string{}
+	addSkupperVolume := func(name string, typeLabel ...string) {
+		labels := map[string]string{"application": "skupper"}
+		if len(typeLabel) == 1 {
+			labels[types.SkupperTypeQualifier] = typeLabel[0]
+		}
+		volumes[name] = &container.Volume{Name: name, Labels: labels}
+	}
+	addSkupperVolume("skupper-console-certs", "Credential")
+	addSkupperVolume("skupper-console-users")
+	addSkupperVolume("skupper-internal")
+	addSkupperVolume("skupper-local-ca", "CertAuthority")
+	addSkupperVolume("skupper-local-client", "Credential")
+	addSkupperVolume("skupper-local-server", "Credential")
+	addSkupperVolume("skupper-router-certs")
+	addSkupperVolume("skupper-service-ca", "CertAuthority")
+	addSkupperVolume("skupper-service-client", "Credential")
+	addSkupperVolume("skupper-services")
+	addSkupperVolume("skupper-site-ca", "CertAuthority")
+	addSkupperVolume("skupper-site-server", "Credential")
+
+	// volumes content
+	volumesFiles["skupper-internal"] = map[string]string{
+		"skrouterd.json": skrouterdJson,
+	}
+	volumesFiles["skupper-console-users"] = map[string]string{
+		"admin": "admin",
+	}
+	volumesFiles["skupper-services"] = map[string]string{
+		"skupper-services.json": skupperServicesJson,
+	}
+
+	// defining skupper-services configmap
+	return volumes, volumesFiles
+}
diff --git a/pkg/domain/podman/token_test.go b/pkg/domain/podman/token_test.go
index 7a5286e2..1a75cd3a 100644
--- a/pkg/domain/podman/token_test.go
+++ b/pkg/domain/podman/token_test.go
@@ -4,7 +4,6 @@
 package podman
 
 import (
-	"io/ioutil"
 	"os"
 	"testing"
 
@@ -48,7 +47,7 @@ func TestPodmanTokenCertHandler(t *testing.T) {
 	assert.Assert(t, err)
 
 	// loading secret from file
-	yaml, err := ioutil.ReadFile(tokenFile.Name())
+	yaml, err := os.ReadFile(tokenFile.Name())
 	assert.Assert(t, err)
 
 	// reading yaml as secret
diff --git a/pkg/domain/podman/update/common_test.go b/pkg/domain/podman/update/common_test.go
index b7503fd9..0f81e88d 100644
--- a/pkg/domain/podman/update/common_test.go
+++ b/pkg/domain/podman/update/common_test.go
@@ -13,6 +13,7 @@ import (
 	"time"
 
 	"github.com/google/uuid"
+	"github.com/skupperproject/skupper/api/types"
 	"github.com/skupperproject/skupper/client/container"
 	"github.com/skupperproject/skupper/client/podman"
 	domainpodman "github.com/skupperproject/skupper/pkg/domain/podman"
@@ -273,21 +274,25 @@ func mockContainers() []*container.Container {
 func mockVolumes() (map[string]*container.Volume, map[string]map[string]string) {
 	var volumes = map[string]*container.Volume{}
 	var volumesFiles = map[string]map[string]string{}
-	addSkupperVolume := func(name string) {
-		volumes[name] = &container.Volume{Name: name, Labels: map[string]string{"application": "skupper"}}
+	addSkupperVolume := func(name string, typeLabel ...string) {
+		labels := map[string]string{"application": "skupper"}
+		if len(typeLabel) == 1 {
+			labels[types.SkupperTypeQualifier] = typeLabel[0]
+		}
+		volumes[name] = &container.Volume{Name: name, Labels: labels}
 	}
-	addSkupperVolume("skupper-console-certs")
+	addSkupperVolume("skupper-console-certs", "Credential")
 	addSkupperVolume("skupper-console-users")
 	addSkupperVolume("skupper-internal")
-	addSkupperVolume("skupper-local-ca")
-	addSkupperVolume("skupper-local-client")
-	addSkupperVolume("skupper-local-server")
+	addSkupperVolume("skupper-local-ca", "CertAuthority")
+	addSkupperVolume("skupper-local-client", "Credential")
+	addSkupperVolume("skupper-local-server", "Credential")
 	addSkupperVolume("skupper-router-certs")
-	addSkupperVolume("skupper-service-ca")
-	addSkupperVolume("skupper-service-client")
+	addSkupperVolume("skupper-service-ca", "CertAuthority")
+	addSkupperVolume("skupper-service-client", "Credential")
 	addSkupperVolume("skupper-services")
-	addSkupperVolume("skupper-site-ca")
-	addSkupperVolume("skupper-site-server")
+	addSkupperVolume("skupper-site-ca", "CertAuthority")
+	addSkupperVolume("skupper-site-server", "Credential")
 
 	// volumes content
 	volumesFiles["skupper-internal"] = map[string]string{
diff --git a/pkg/flow/controller_test.go b/pkg/flow/controller_test.go
index 19dd9c88..90e136f5 100644
--- a/pkg/flow/controller_test.go
+++ b/pkg/flow/controller_test.go
@@ -5,11 +5,12 @@ import (
 	"testing"
 	"time"
 
+	"github.com/skupperproject/skupper/api/types"
 	"gotest.tools/assert"
 )
 
 func TestUpdateProcess(t *testing.T) {
-	fc := NewFlowController("mysite", "X.Y.Z", uint64(time.Now().UnixNano())/uint64(time.Microsecond), nil)
+	fc := NewFlowController("mysite", "X.Y.Z", uint64(time.Now().UnixNano())/uint64(time.Microsecond), nil, WithPolicyDisabled)
 	assert.Assert(t, fc != nil)
 	procName := "tcp-go-echo"
 	sourceIP := "10.0.0.1"
@@ -37,7 +38,7 @@ func TestUpdateProcess(t *testing.T) {
 }
 
 func TestUpdateHost(t *testing.T) {
-	fc := NewFlowController("mysite", "X.Y.Z", uint64(time.Now().UnixNano())/uint64(time.Microsecond), nil)
+	fc := NewFlowController("mysite", "X.Y.Z", uint64(time.Now().UnixNano())/uint64(time.Microsecond), nil, WithPolicyDisabled)
 	assert.Assert(t, fc != nil)
 	hostName := "bastion-server"
 	host := &HostRecord{
@@ -61,7 +62,7 @@ func TestUpdateHost(t *testing.T) {
 
 func TestUpdateBeaconAndHeartbeats(t *testing.T) {
 	_ = os.Setenv("SKUPPER_SITE_ID", "mySite")
-	fc := NewFlowController("mySite", "X.Y.Z", uint64(time.Now().UnixNano())/uint64(time.Microsecond), nil)
+	fc := NewFlowController("mySite", "X.Y.Z", uint64(time.Now().UnixNano())/uint64(time.Microsecond), nil, WithPolicyDisabled)
 	assert.Assert(t, fc != nil)
 	stopCh := make(chan struct{})
 	go fc.updateBeacon(stopCh)
@@ -90,10 +91,11 @@ func TestUpdateBeaconAndHeartbeats(t *testing.T) {
 func TestUpdateRecords(t *testing.T) {
 	_ = os.Setenv("SKUPPER_SITE_ID", "mySite")
 	_ = os.Setenv("SKUPPER_NAMESPACE", "myNamespace")
-	fc := NewFlowController("mySite", "X.Y.Z", uint64(time.Now().UnixNano())/uint64(time.Microsecond), nil)
+	fc := NewFlowController("mySite", "X.Y.Z", uint64(time.Now().UnixNano())/uint64(time.Microsecond), nil, WithPolicyDisabled)
 	assert.Assert(t, fc != nil)
 	stopCh := make(chan struct{})
-	go fc.updateRecords(stopCh)
+
+	go fc.updateRecords(stopCh, fc.siteRecordController.Start(stopCh))
 
 	recordUpdate := <-fc.recordOutgoing
 	assert.Assert(t, recordUpdate != nil)
@@ -148,3 +150,143 @@ func TestUpdateRecords(t *testing.T) {
 	close(stopCh)
 
 }
+
+func TestSiteController(t *testing.T) {
+	alwaysOnEval := policyEnabledConst(true)
+	flipFlopEval := stubPolicyEvaluator{
+		Next: make(chan bool, 8),
+	}
+	testCases := []struct {
+		controller        *siteRecordController
+		expectedOut       []func(t *testing.T, record *SiteRecord, ok bool)
+		waitTimeout       time.Duration
+		expectWaitTimeout bool
+	}{
+		{
+			controller: &siteRecordController{
+				Identity:        "basic-podman",
+				Name:            "basic",
+				Platform:        string(types.PlatformPodman),
+				CreatedAt:       1_111_111,
+				policyEvaluator: WithPolicyDisabled,
+			},
+			expectedOut: []func(*testing.T, *SiteRecord, bool){
+				func(t *testing.T, rec *SiteRecord, ok bool) {
+					assert.Equal(t, rec.Identity, "basic-podman")
+					assert.Equal(t, *rec.Name, "basic")
+					assert.Equal(t, *rec.Platform, string(types.PlatformPodman))
+					assert.Equal(t, rec.StartTime, uint64(1_111_111))
+					assert.Equal(t, *rec.Policy, Disabled)
+				},
+			},
+			waitTimeout: time.Millisecond * 500,
+		},
+		{
+			controller: &siteRecordController{
+				Identity:        "basic-kube",
+				Name:            "basic",
+				Platform:        string(types.PlatformKubernetes),
+				CreatedAt:       1_111_111,
+				PolicyEnabled:   true,
+				policyEvaluator: alwaysOnEval,
+				pollInterval:    time.Millisecond * 5,
+			},
+			expectedOut: []func(*testing.T, *SiteRecord, bool){
+				func(t *testing.T, rec *SiteRecord, ok bool) {
+					assert.Equal(t, rec.Identity, "basic-kube")
+					assert.Equal(t, *rec.Name, "basic")
+					assert.Equal(t, *rec.Platform, string(types.PlatformKubernetes))
+					assert.Equal(t, rec.StartTime, uint64(1_111_111))
+					assert.Equal(t, *rec.Policy, Enabled)
+				},
+			},
+			waitTimeout:       time.Millisecond * 250,
+			expectWaitTimeout: true,
+		},
+		{
+			controller: &siteRecordController{
+				Identity:        "policy-updates-kube",
+				Name:            "basic",
+				Platform:        string(types.PlatformKubernetes),
+				CreatedAt:       1_111_111,
+				policyEvaluator: flipFlopEval,
+				pollInterval:    time.Millisecond * 5,
+			},
+			expectedOut: []func(*testing.T, *SiteRecord, bool){
+				func(t *testing.T, rec *SiteRecord, ok bool) {
+					assert.Equal(t, rec.Identity, "policy-updates-kube")
+					assert.Equal(t, *rec.Name, "basic")
+					assert.Equal(t, *rec.Platform, string(types.PlatformKubernetes))
+					assert.Equal(t, rec.StartTime, uint64(1_111_111))
+					assert.Equal(t, *rec.Policy, Disabled)
+					// queue two updates to policy enabled
+					flipFlopEval.Next <- false
+					flipFlopEval.Next <- true
+					flipFlopEval.Next <- false
+				},
+				func(t *testing.T, rec *SiteRecord, ok bool) { // closed
+					assert.DeepEqual(t, *rec, SiteRecord{
+						Base: Base{
+							RecType:  recordNames[Site],
+							Identity: "policy-updates-kube",
+						},
+						Policy: &Enabled,
+					})
+				},
+				func(t *testing.T, rec *SiteRecord, ok bool) { // closed
+					assert.DeepEqual(t, rec, &SiteRecord{
+						Base: Base{
+							RecType:  recordNames[Site],
+							Identity: "policy-updates-kube",
+						},
+						Policy: &Disabled,
+					})
+				},
+			},
+			waitTimeout: time.Millisecond * 500,
+		},
+	}
+
+	for _, testCase := range testCases {
+		tc := testCase
+		t.Run(tc.controller.Identity, func(t *testing.T) {
+			t.Parallel()
+			end := make(chan struct{})
+			defer close(end)
+
+			out := tc.controller.Start(end)
+			for i, expect := range tc.expectedOut {
+				select {
+				case actual, ok := <-out:
+					expect(t, actual, ok)
+				case <-time.After(tc.waitTimeout):
+					t.Fatalf("test case timed out waiting for output %d", i)
+				}
+			}
+			if tc.expectWaitTimeout {
+				select {
+				case actual := <-out:
+					t.Errorf("unexpected update at end of test: %v", actual)
+				case <-time.After(tc.waitTimeout):
+					// okay
+				}
+			}
+		})
+	}
+}
+
+type stubPolicyEvaluator struct {
+	IsEnabled bool
+	Next      chan bool
+}
+
+func (s stubPolicyEvaluator) Enabled() bool {
+	select {
+	case next, ok := <-s.Next:
+		if ok {
+			s.IsEnabled = next
+		}
+	default:
+	}
+	return s.IsEnabled
+}
diff --git a/pkg/kube/configmaps_test.go b/pkg/kube/configmaps_test.go
index d1cd984d..96487a1b 100644
--- a/pkg/kube/configmaps_test.go
+++ b/pkg/kube/configmaps_test.go
@@ -216,7 +216,7 @@ func TestUpdateSkupperServices(t *testing.T) {
 		{
 			name:               "skupper-services-not-found",
 			hasSkupperServices: false,
-			expectedErr:        fmt.Errorf("Could not retrive service definitions from configmap 'skupper-services', Error: "),
+			expectedErr:        fmt.Errorf("Could not retrieve service definitions from configmap 'skupper-services', Error: "),
 		},
 		// data is empty and no change
 		{
diff --git a/pkg/network/network_test.go b/pkg/network/network_test.go
index 93a443e1..1727f7be 100644
--- a/pkg/network/network_test.go
+++ b/pkg/network/network_test.go
@@ -53,7 +53,7 @@ func TestGetSiteTargetsMap(t *testing.T) {
 	expectedSite2 := "429c2780-003d-44cc-9a91-4139885c7d20"
 	expectedService := "backend:8080"
 
-	result := skupperStatus.GetSiteTargetsMap()
+	result := skupperStatus.GetSiteTargetMap()
 
 	assert.Check(t, result[expectedSite2] == nil)
 	assert.Check(t, result[expectedSite1][expectedService].Address == expectedService)
diff --git a/pkg/utils/retry_test.go b/pkg/utils/retry_test.go
index 057256ab..f827f586 100644
--- a/pkg/utils/retry_test.go
+++ b/pkg/utils/retry_test.go
@@ -23,7 +23,7 @@ import (
 //
 // Or:
 //
-// - If function produces an error, fail immediatelly with that error
+// - If function produces an error, fail immediately with that error
 // - Else, if ok is true, return nil and succeed
 // - Otherwise:
 //   - if before maximum retry: retry
diff --git a/test/images/Makefile b/test/images/Makefile
index acc5dfbb..6ef71996 100644
--- a/test/images/Makefile
+++ b/test/images/Makefile
@@ -33,7 +33,7 @@
 # may still consume Dockerhub pull requests in the process.
 #
 # As with any Makefile, you can configure the execution by setting variables
-# on the make invokation:
+# on the make invocation:
 #
 #   make MAIN_REPO=quay.io/dhashimo docker.io/ssorj/quiver
 #
diff --git a/test/integration/acceptance/custom/basic/basic.go b/test/integration/acceptance/custom/basic/basic.go
index cda54b18..43824398 100644
--- a/test/integration/acceptance/custom/basic/basic.go
+++ b/test/integration/acceptance/custom/basic/basic.go
@@ -21,7 +21,7 @@ import (
 
 const (
 	SkipReasonIngressNone           = "this test only runs against a single cluster (ingress=none)"
-	SkipReasonNodePortNoIngressHost = "this test can only be executed if PUBLIC_1_INGRESS_HOST environment varialbe is set (ingress=nodeport)"
+	SkipReasonNodePortNoIngressHost = "this test can only be executed if PUBLIC_1_INGRESS_HOST environment variable is set (ingress=nodeport)"
 )
 
 type BasicTestRunner struct {
@@ -128,7 +128,7 @@ func (r *BasicTestRunner) Setup(ctx context.Context, createOptsPublic types.Site
 			"StartTimeAfter=", podStartTimeAfter, "Router component restarted - POD status:", string(podStatus))
 
 		// Check if the Volume is shared by both containers
-		podContainers, _ := kube.GetReadyPod(prv1Cluster.Namespace, prv1Cluster.VanClient.KubeClient, "router")
+		podContainers, _ := kube.GetReadyPod(prv1Cluster.Namespace, prv1Cluster.VanClient.KubeClient, "router", "skupper-router")
 		for _, container := range podContainers.Spec.Containers {
 			foundCertVol := false
 			for _, contVolume := range container.VolumeMounts {
diff --git a/test/integration/acceptance/custom/hello_policy/README.md b/test/integration/acceptance/custom/hello_policy/README.md
index 72d1924b..0b3120b9 100644
--- a/test/integration/acceptance/custom/hello_policy/README.md
+++ b/test/integration/acceptance/custom/hello_policy/README.md
@@ -129,7 +129,7 @@ For some existing resources, when they are disallowed, they're removed for good.
 Of others, however, they're only disabled.  Check that behavior by allowing and
 disallowing the policy items a few times.
 
-## The assynchronous nature of the policy engine
+## The asynchronous nature of the policy engine
 
 The policy engine works in a declarative manner: the service controllers in
 each namespace monitor for policy changes and recalculate the local policy when
@@ -266,7 +266,7 @@ Describe features of the product that may help writing test cases.
 * `get events`
 * `get policies`
 
-## service controler pod logs
+## service controller pod logs
 
 ## service sync
 
diff --git a/test/integration/acceptance/custom/hello_policy/features/incoming_links.feature b/test/integration/acceptance/custom/hello_policy/features/incoming_links.feature
index 90b8c796..7299448d 100644
--- a/test/integration/acceptance/custom/hello_policy/features/incoming_links.feature
+++ b/test/integration/acceptance/custom/hello_policy/features/incoming_links.feature
@@ -25,7 +25,7 @@ Feature: AllowIncomingLink
       and a policy that allows all outgoing hosts for links on the private namespace
      When creating a token
       and using the token to create a link
-     Then the creation works successfuly
+     Then the creation works successfully
       and GET allowIncomingLinks == true
      When removing the policy that allows IncomingLinks on public namespace
      Then the link goes down
@@ -42,7 +42,7 @@ Feature: AllowIncomingLink
       and creating a token
      When removing the policy that allows IncomingLinks on public namespace
       and using the token to create a link
-     Then the link creation suceeds
+     Then the link creation succeeds
       But the link is shown as inactive
       and GET allowIncomingLinks == false
       and link removal works fine
diff --git a/test/integration/acceptance/custom/hello_policy/features/services.feature b/test/integration/acceptance/custom/hello_policy/features/services.feature
index 283d29cf..129ca46c 100644
--- a/test/integration/acceptance/custom/hello_policy/features/services.feature
+++ b/test/integration/acceptance/custom/hello_policy/features/services.feature
@@ -33,7 +33,7 @@ Feature: Skupper service creation and binding
      and removing the policy that allowed 'hello-world-.*'
     Then the services are unaffected
     # make-policies-specific-to-namespace
-    When changing these policies to apply only to the namespaces where the services are respectivelly deployed
+    When changing these policies to apply only to the namespaces where the services are respectively deployed
     Then the services are unaffected, but they disappear from the list on the other namespace
     # policies-list-both-services
     When adding both specific services (hello-world-{backend,fronted}) to the list on both namespace-specific policies
diff --git a/test/integration/acceptance/custom/hello_policy/hostnames_test.go b/test/integration/acceptance/custom/hello_policy/hostnames_test.go
index d1eddbdd..337aab1b 100644
--- a/test/integration/acceptance/custom/hello_policy/hostnames_test.go
+++ b/test/integration/acceptance/custom/hello_policy/hostnames_test.go
@@ -346,7 +346,7 @@ func testHostnamesPolicy(t *testing.T, pub, prv *base.ClusterContext) {
 		linkStatusTestScenario(prv, "", "hostnames", false),
 	}
 
-	// status testing, tied to router hostname (link being establised)
+	// status testing, tied to router hostname (link being established)
 	statusTestTable := []policyTestCase{}
 
 	for _, t := range tests {
diff --git a/test/integration/acceptance/custom/hello_policy/issues_test.go b/test/integration/acceptance/custom/hello_policy/issues_test.go
index dbf84d6c..c6523676 100644
--- a/test/integration/acceptance/custom/hello_policy/issues_test.go
+++ b/test/integration/acceptance/custom/hello_policy/issues_test.go
@@ -26,7 +26,7 @@ import (
 //
 // The pod name comes from kube.GetReadyPod for service-controller
 func seekServiceControllerAndDelete(ctx *base.ClusterContext) {
-	pod, err := kube.GetReadyPod(ctx.Namespace, ctx.VanClient.KubeClient, "service-controller")
+	pod, err := kube.GetReadyPod(ctx.Namespace, ctx.VanClient.KubeClient, "service-controller", "")
 	if err != nil {
 		log.Printf("Ignoring pod listing error '%v'", err)
 		return
@@ -105,7 +105,7 @@ func testLinkIssue753(t *testing.T, pub, prv *base.ClusterContext) {
 								// originally, we looked only for "command terminated with exit code".  However, the fix for
 								// #753 on #782 just added a better explanation of what's going on ("skupper-service-controller not ready"),
 								// while keeping the actual error with exit code for helping on investigations.  So, now we check that,
-								// if a policy error ocurred with a command exit code, we don't report just that: we expect that
+								// if a policy error occurred with a command exit code, we don't report just that: we expect that
 								// something else be given as an explanation to the error, like #782
 								if strings.Contains(err.Error(), "Policy validation error: command terminated with exit code") {
 									return fmt.Errorf("Error matched: '%w'", err)
@@ -257,7 +257,7 @@ func testLinkIssue789(t *testing.T, pub, prv *base.ClusterContext) {
 
 }
 
-// If a service is up and running, then we remove all policies and immediatelly reallow them: will
+// If a service is up and running, then we remove all policies and immediately reallow them: will
 // the services be removed?
 //
 // The result may depend on cluster load and other factors, so we run it multiple times (runs+1)
@@ -328,7 +328,7 @@ func removeReallowRemove(pub, prv *base.ClusterContext, runs int) (allTestSteps
 	return
 }
 
-// If a service is up and running, then we remove all policies and immediatelly reallow them: will
+// If a service is up and running, then we remove all policies and immediately reallow them: will
 // the services be removed?
 //
 // The result may depend on cluster load and other factors, so we run it multiple times (runs+1)
@@ -406,7 +406,7 @@ func removeReallowKeep(pub, prv *base.ClusterContext, runs int) (allTestSteps []
 	return
 }
 
-// Can a service be created immediatelly after a policy allowing it?  If not,
+// Can a service be created immediately after a policy allowing it?  If not,
 // do we get a proper error message for that?
 //
 // This is testing for https://github.com/skupperproject/skupper/issues/728
diff --git a/test/integration/acceptance/custom/hello_policy/resources_test.go b/test/integration/acceptance/custom/hello_policy/resources_test.go
index f6e4c273..2db25d2f 100644
--- a/test/integration/acceptance/custom/hello_policy/resources_test.go
+++ b/test/integration/acceptance/custom/hello_policy/resources_test.go
@@ -37,7 +37,7 @@ type resourceDetails struct {
 	testAllowed             []string
 	testDisallowed          []string
 	survivors               []string // these should come from the previous test untouched
-	zombies                 []string // they were gone somehow; they musn't come back
+	zombies                 []string // they were gone somehow; they mustn't come back
 	namespaces              []string // if empty,  uses the context's namespace
 }
 
diff --git a/test/integration/acceptance/custom/hello_policy/runner_test.go b/test/integration/acceptance/custom/hello_policy/runner_test.go
index 86fb023a..a62e4f3b 100644
--- a/test/integration/acceptance/custom/hello_policy/runner_test.go
+++ b/test/integration/acceptance/custom/hello_policy/runner_test.go
@@ -106,7 +106,7 @@ func (r policyTestRunner) run(t *testing.T, pub, prv *base.ClusterContext) {
 	}
 }
 
-// A function to programatically control the skipping of policyTestCase
+// A function to programmatically control the skipping of policyTestCase
 // and policyTestStep.  An empty response means no skip; a non-empty
 // response is used as the reason for skipping.
 type skipFunction func() string
@@ -174,10 +174,10 @@ type hookFunction func(map[string]string) error
 // When you have more than one policy and you're not updating all, it may be
 // good to document it on the struct.  Something like this:
 //
-//   pubPolicy: []skupperv1.SkupperClusterPolicySpec{
-//     allowIncomingLinkPolicy(pub.Namespace, true),
-//     // second policy is not being changed on this test
-//  },
+// pubPolicy: []skupperv1.SkupperClusterPolicySpec{
+//   allowIncomingLinkPolicy(pub.Namespace, true),
+//   // second policy is not being changed on this test
+// },
 //
 // To remove a policy, set it as having a sole namespace named REMOVE.  To keep
 // a policy while updating or removing another one that follows it, set it with
@@ -219,7 +219,7 @@ type policyTestStep struct {
 
 	// if provided, skipFunction will be run and its result checked.  If not empty,
 	// the test will be skipped with the return string as the input to t.Skip().
-	// This allows to programatically skip some of the steps, based on environmental
+	// This allows to programmatically skip some of the steps, based on environmental
 	// information.
 	skip skipFunction
 }
diff --git a/test/integration/acceptance/custom/hello_policy/service_test.go b/test/integration/acceptance/custom/hello_policy/service_test.go
index a50c9d6c..d2c01996 100644
--- a/test/integration/acceptance/custom/hello_policy/service_test.go
+++ b/test/integration/acceptance/custom/hello_policy/service_test.go
@@ -158,7 +158,7 @@ func serviceCheckTestScenario(pub *base.ClusterContext, prefix string, unboundSe
 // skupper service status + skupper status
 //
 // All services that are to be exposed on the VAN need to be declared on either
-// boundServices or unauthServices, as their lenghths are summed in order to provide
+// boundServices or unauthServices, as their lengths are summed in order to provide
 // the StatusTester with its ExposedServices field.
 //
 // TODO: On callers, replace this single call by two calls?  This way, these could be
@@ -202,7 +202,7 @@ func serviceCheckFrontStatusTestScenario(pub *base.ClusterContext, prefix string
 // skupper service status + skupper status
 //
 // All services that are to be exposed on the VAN need to be declared on either
-// boundServices or unauthServices, as their lenghths are summed in order to provide
+// boundServices or unauthServices, as their lengths are summed in order to provide
 // the StatusTester with its ExposedServices field.
 //
 // TODO: On callers, replace this single call by two calls?  This way, these could be
diff --git a/test/integration/acceptance/gateway_test.go b/test/integration/acceptance/gateway_test.go
index b6483e14..02adf1a5 100644
--- a/test/integration/acceptance/gateway_test.go
+++ b/test/integration/acceptance/gateway_test.go
@@ -9,7 +9,6 @@ import (
 	"compress/gzip"
 	"context"
 	"io"
-	"io/ioutil"
 	"log"
 	"os"
 	"os/exec"
@@ -43,14 +42,12 @@ func TestGateway(t *testing.T) {
 	t.Run("local-gateway-podman", testLocalGatewayPodman)
 }
 
-//
 // testLocalGateway uses localhost to run a TCP Echo server
 // bound to a dynamic port and expose it through a local
 // gateway into the skupper network, against two connected
 // clusters. It also forwards local requests to a cluster
 // port reaching out tcp-echo-cluster service using a
 // dynamic port
-//
 func testLocalGatewayService(t *testing.T) {
 	testLocalGateway(t, "")
 }
@@ -210,7 +207,7 @@ func installGateway(tarball string) error {
 	tarReader := tar.NewReader(tarStream)
 
 	// uncompress all files under temp directory
-	dir, err := ioutil.TempDir("", "gateway")
+	dir, err := os.MkdirTemp("", "gateway")
 	if err != nil {
 		return err
 	}
diff --git a/test/integration/examples/bookinfo/job/bookinfojob_test.go b/test/integration/examples/bookinfo/job/bookinfojob_test.go
index 13352ee6..033cc1f8 100644
--- a/test/integration/examples/bookinfo/job/bookinfojob_test.go
+++ b/test/integration/examples/bookinfo/job/bookinfojob_test.go
@@ -4,7 +4,7 @@ package job
 
 import (
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"strings"
 	"testing"
@@ -38,10 +38,9 @@ func tryProductPage() ([]byte, error) {
 		return nil, fmt.Errorf("unexpedted http response status: %v", resp.Status)
 	}
 	defer resp.Body.Close()
-	body, err := ioutil.ReadAll(resp.Body)
+	body, err := io.ReadAll(resp.Body)
 	if err != nil {
 		return nil, err
 	}
 	return body, nil
 }
-
diff --git a/test/integration/examples/custom/hipstershop/hipstershop.go b/test/integration/examples/custom/hipstershop/hipstershop.go
index 4fdce60a..e63af859 100644
--- a/test/integration/examples/custom/hipstershop/hipstershop.go
+++ b/test/integration/examples/custom/hipstershop/hipstershop.go
@@ -3,8 +3,8 @@ package hipstershop
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
 	"log"
+	"os"
 	"strings"
 	"testing"
 
@@ -101,7 +101,7 @@ func CreateVAN(t *testing.T, testRunner base.ClusterTestRunner) {
 	assert.Assert(t, err, "error creating router on private1 cluster")
 
 	// Creating a local directory for storing the token
-	tokenPath, err := ioutil.TempDir("", "token")
+	tokenPath, err := os.MkdirTemp("", "token")
 	assert.Assert(t, err, "error creating temporary directory for tokens")
 	cleanUpDirs = append(cleanUpDirs, tokenPath)
 
diff --git a/test/integration/examples/custom/hipstershop/job/grpcclient_test.go b/test/integration/examples/custom/hipstershop/job/grpcclient_test.go
index d3b244dd..34402558 100644
--- a/test/integration/examples/custom/hipstershop/job/grpcclient_test.go
+++ b/test/integration/examples/custom/hipstershop/job/grpcclient_test.go
@@ -1,3 +1,4 @@
+//go:build job
 // +build job
 
 package job
diff --git a/test/integration/examples/http/job/httpjob_test.go b/test/integration/examples/http/job/httpjob_test.go
index b6607074..061a5db4 100644
--- a/test/integration/examples/http/job/httpjob_test.go
+++ b/test/integration/examples/http/job/httpjob_test.go
@@ -6,15 +6,17 @@ package job
 import (
 	"crypto/tls"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"log"
 	"net"
 	"net/http"
+	"os"
 	"strings"
 	"testing"
 	"time"
 
 	"crypto/x509"
+
 	"github.com/davecgh/go-spew/spew"
 	"github.com/tsenart/vegeta/v12/lib"
 	"golang.org/x/net/http2"
@@ -26,7 +28,7 @@ func TestHttpJob(t *testing.T) {
 }
 
 func TestHttp2Job(t *testing.T) {
-	//TODO: enable this if we add suport for "Upgrade" in skupper http2
+	//TODO: enable this if we add support for "Upgrade" in skupper http2
 	//testHttpJob(t, "http://nghttp2:8443/")
 
 	//https://www.mailgun.com/blog/http-2-cleartext-h2c-client-example-go/
@@ -49,7 +51,7 @@ func TestHttp2Job(t *testing.T) {
 	fmt.Println("Client Header:", resp.Header)
 
 	defer resp.Body.Close()
-	_body, err := ioutil.ReadAll(resp.Body)
+	_body, err := io.ReadAll(resp.Body)
 	assert.Assert(t, err)
 
 	body := string(_body)
@@ -60,7 +62,7 @@ func TestHttp2Job(t *testing.T) {
 func TestHttp2TlsJob(t *testing.T) {
 
 	//Load CA cert
-	caCert, err := ioutil.ReadFile("/tmp/certs/skupper-service-client/ca.crt")
+	caCert, err := os.ReadFile("/tmp/certs/skupper-service-client/ca.crt")
 	if err != nil {
 		log.Fatal(err)
 	}
@@ -90,7 +92,7 @@ func TestHttp2TlsJob(t *testing.T) {
 	fmt.Println("Client Header:", resp.Header)
 
 	defer resp.Body.Close()
-	_body, err := ioutil.ReadAll(resp.Body)
+	_body, err := io.ReadAll(resp.Body)
 	assert.Assert(t, err)
 
 	body := string(_body)
@@ -102,7 +104,7 @@ func TestHttp2TlsJob(t *testing.T) {
 func TestHttp2TcpTlsJob(t *testing.T) {
 
 	//Load CA cert
-	caCert, err := ioutil.ReadFile("/tmp/certs/skupper-service-client/ca.crt")
+	caCert, err := os.ReadFile("/tmp/certs/skupper-service-client/ca.crt")
 	assert.Assert(t, err)
 
 	caCertPool := x509.NewCertPool()
@@ -131,7 +133,7 @@ func TestHttp2TcpTlsJob(t *testing.T) {
 	fmt.Println("Client Header:", resp.Header)
 
 	defer resp.Body.Close()
-	_body, err := ioutil.ReadAll(resp.Body)
+	_body, err := io.ReadAll(resp.Body)
 	assert.Assert(t, err)
 
 	body := string(_body)
@@ -143,7 +145,7 @@ func TestHttp2TcpTlsJob(t *testing.T) {
 func TestHttp1TlsJob(t *testing.T) {
 
 	//Load CA cert
-	caCert, err := ioutil.ReadFile("/tmp/certs/skupper-service-client/ca.crt")
+	caCert, err := os.ReadFile("/tmp/certs/skupper-service-client/ca.crt")
 	assert.Assert(t, err)
 
 	caCertPool := x509.NewCertPool()
@@ -171,7 +173,7 @@ func TestHttp1TlsJob(t *testing.T) {
 	fmt.Println("Client Header:", resp.Header)
 
 	defer resp.Body.Close()
-	_body, err := ioutil.ReadAll(resp.Body)
+	_body, err := io.ReadAll(resp.Body)
 	assert.Assert(t, err)
 
 	body := string(_body)
@@ -197,7 +199,7 @@ func testHttpJob(t *testing.T, url string) {
 	}
 	metrics.Close()
 
-	//this is too verbose, anyway mantaining for now until we add more
+	//this is too verbose, anyway maintaining for now until we add more
 	//assertions
 	spew.Dump(metrics)
 
diff --git a/test/integration/performance/common/setup.go b/test/integration/performance/common/setup.go
index 5d60977e..67817196 100644
--- a/test/integration/performance/common/setup.go
+++ b/test/integration/performance/common/setup.go
@@ -404,7 +404,7 @@ func tailRouterLogs(ctx context.Context, saveLogs *bool) *sync.WaitGroup {
 		var err error
 		log.Printf("Waiting for router pod to be running")
 		err = pkgutils.RetryWithContext(ctx, time.Second*5, func() (bool, error) {
-			pod, err = kube.GetReadyPod(cli.Namespace, cli.KubeClient, "router")
+			pod, err = kube.GetReadyPod(cli.Namespace, cli.KubeClient, "router", "skupper-router")
 			if pod != nil && err == nil {
 				return true, nil
 			}
diff --git a/test/utils/base/env.go b/test/utils/base/env.go
index b2a1fe45..6bda62ab 100644
--- a/test/utils/base/env.go
+++ b/test/utils/base/env.go
@@ -121,7 +121,7 @@ func PostPolicyChangeSleep() {
 }
 
 // This checks whether the current attempt sent as an argument
-// is greather than the environment variable ENV_MAX_STATUS_ATTEMPTS
+// is greater than the environment variable ENV_MAX_STATUS_ATTEMPTS
 //
 // If the variable is not set or is malformed, this will always
 // return false (meaning that the status commands will only fail once
diff --git a/test/utils/k8s/yaml.go b/test/utils/k8s/yaml.go
index 8d3bb9da..c6fd41fd 100644
--- a/test/utils/k8s/yaml.go
+++ b/test/utils/k8s/yaml.go
@@ -6,8 +6,8 @@ import (
 	"context"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
+	"os"
 	"regexp"
 
 	"github.com/skupperproject/skupper/client"
@@ -36,7 +36,7 @@ func CreateResourcesFromYAML(vanClient *client.VanClient, fileOrUrl string) erro
 		}
 	} else {
 		// Read YAML file
-		yamlData, err = ioutil.ReadFile(fileOrUrl)
+		yamlData, err = os.ReadFile(fileOrUrl)
 		if err != nil {
 			return fmt.Errorf("error reading yaml file: %s", err)
 		}
@@ -118,7 +118,7 @@ func readYAMLFromUrl(url string) ([]byte, error) {
 		return nil, fmt.Errorf("error loading yaml from url [%s]: %s", url, err)
 	}
 	defer resp.Body.Close()
-	yamlData, err = ioutil.ReadAll(resp.Body)
+	yamlData, err = io.ReadAll(resp.Body)
 	if err != nil {
 		return nil, fmt.Errorf("error reading yaml from url [%s]: %s", url, err)
 	}
diff --git a/test/utils/skupper/cli/init.go b/test/utils/skupper/cli/init.go
index d64353eb..ae0c38d4 100644
--- a/test/utils/skupper/cli/init.go
+++ b/test/utils/skupper/cli/init.go
@@ -26,7 +26,7 @@ import (
 )
 
 // InitTester runs `skupper init` and validates output, console,
-// as well as skupper resources that should be availble in the cluster.
+// as well as skupper resources that should be available in the cluster.
 type InitTester struct {
 	ConsoleAuth           string
 	ConsoleUser           string
@@ -297,7 +297,7 @@ func (s *InitTester) ValidateConsoleKube(cluster *base.ClusterContext) error {
 			return fmt.Errorf("error retrieving route: %s - %v", types.ControllerServiceName, err)
 		}
 		if route.Spec.To.Kind != "Service" || route.Spec.To.Name != types.ControllerServiceName {
-			return fmt.Errorf("console route is not targetting the correct service - expected: Service/%s - found: %s/%s",
+			return fmt.Errorf("console route is not targeting the correct service - expected: Service/%s - found: %s/%s",
 				types.ControllerServiceName, route.Spec.To.Kind, route.Spec.To.Name)
 		}
 	}
@@ -422,11 +422,11 @@ func (s *InitTester) validateRouterMode(cluster *base.ClusterContext) error {
 
 			// Verify routes
 			if route.Spec.To.Kind != "Service" || route.Spec.To.Name != types.TransportServiceName {
-				return fmt.Errorf("controller route is not targetting the correct service - expected: Service/%s - found: %s/%s",
+				return fmt.Errorf("controller route is not targeting the correct service - expected: Service/%s - found: %s/%s",
 					types.TransportServiceName, route.Spec.To.Kind, route.Spec.To.Name)
 			}
 			if route.Spec.Port.String() != targetPortName {
-				return fmt.Errorf("controller route is not targetting the correct service - expected: Service/%s - found: %s/%s",
+				return fmt.Errorf("controller route is not targeting the correct service - expected: Service/%s - found: %s/%s",
 					types.TransportServiceName, route.Spec.To.Kind, route.Spec.To.Name)
 			}
 		}
diff --git a/test/utils/skupper/cli/tester.go b/test/utils/skupper/cli/tester.go
index 1faa1a49..3bd3de0a 100644
--- a/test/utils/skupper/cli/tester.go
+++ b/test/utils/skupper/cli/tester.go
@@ -169,7 +169,7 @@ func runScenarios(t *testing.T, scenarios []TestScenario, parallel bool) {
 
 	if parallel {
 		// Group parallel scenarios within a subtest named 'parallel'
-		// This helps better undestand the logs and is required for the
+		// This helps better understand the logs and is required for the
 		// scenario set to finish only once all parallel scenarios have.
 		t.Run("parallel", work)
 	} else {
@@ -236,7 +236,7 @@ func SkupperCommonOptions(platform types.Platform, cluster *base.ClusterContext)
 // expressions you're looking for.
 //
 // StdOutReNot and StdErrReNot behave like the previous ones, but ensure
-// that the patters are not there in the checked string
+// that the patterns are not there in the checked string
 type Expect struct {
 	StdOut      []string
 	StdErr      []string
diff --git a/test/utils/skupper/cli/token/create.go b/test/utils/skupper/cli/token/create.go
index 67697351..1cf6423f 100644
--- a/test/utils/skupper/cli/token/create.go
+++ b/test/utils/skupper/cli/token/create.go
@@ -3,7 +3,6 @@ package token
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
 	"log"
 	"net/url"
 	"os"
@@ -114,7 +113,7 @@ func (t *CreateTester) Run(platform types.Platform, cluster *base.ClusterContext
 	// Loading secret
 	var secret v1.Secret
 	var secretFile []byte
-	secretFile, err = ioutil.ReadFile(t.FileName)
+	secretFile, err = os.ReadFile(t.FileName)
 	if err != nil {
 		err = fmt.Errorf("unable to read token file - %v", err)
 		return