Repository: skupperproject/skupper
Pull request #1294 (2023-11-27 17:41:17)

Improves TestSiteHandler

fgiorgetti: This test is currently flaky as some containers are taking awhile to be in a running state.

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/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/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/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_link.go b/cmd/skupper/skupper_link.go
index fe1c1459..6b0e967a 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())
 			}
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/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 3af66872..4f025a3a 100644
--- a/pkg/domain/podman/site.go
+++ b/pkg/domain/podman/site.go
@@ -8,11 +8,13 @@ import (
 	"strings"
 	"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/config"
 	"github.com/skupperproject/skupper/pkg/domain"
+	"github.com/skupperproject/skupper/pkg/images"
 	"github.com/skupperproject/skupper/pkg/qdr"
 	"github.com/skupperproject/skupper/pkg/utils"
 	"github.com/skupperproject/skupper/pkg/version"
@@ -428,6 +430,11 @@ func (s *SiteHandler) canCreate(site *Site) error {
 		return fmt.Errorf("You cannot use a remote podman endpoint - %w", err)
 	}
 
+	// Validating router container runs without errors
+	err = s.runTempContainer()
+	if err != nil {
+		return fmt.Errorf("site cannot be created: %s", err)
+	}
 	return nil
 }
 
@@ -902,3 +909,41 @@ func (s *SiteHandler) createPrometheusConfigFiles(site *Site) error {
 	}
 	return nil
 }
+
+func (s *SiteHandler) runTempContainer() error {
+	cli := s.cli
+	err := cli.ImagePull(images.GetRouterImageName())
+	if err != nil {
+		return err
+	}
+	tempName := fmt.Sprintf("skupper-temp-%s", uuid.NewString()[:5])
+	err = cli.ContainerCreate(
+		&container.Container{
+			Name:          tempName,
+			Image:         images.GetRouterImageName(),
+			RestartPolicy: "no",
+			Command:       []string{"skrouterd", "-version"},
+		},
+	)
+	if err != nil {
+		return fmt.Errorf("unable to validate container creation: %s", err)
+	}
+	defer func() {
+		_ = cli.ContainerRemove(tempName)
+	}()
+	if err = cli.ContainerStart(tempName); err != nil {
+		return fmt.Errorf("error starting validation container: %s", err)
+	}
+	ci, err := cli.ContainerInspect(tempName)
+	if err != nil {
+		return fmt.Errorf("error inspecting validation container: %s", err)
+	}
+	if ci.ExitCode != 0 {
+		logs, err := cli.ContainerLogs(tempName)
+		if err != nil {
+			return fmt.Errorf("error executing validation container (unable to read logs) - exit code: %d", ci.ExitCode)
+		}
+		return fmt.Errorf("unable to run %s - reason: %s", ci.Image, logs)
+	}
+	return nil
+}
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..1d70ffe3 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,117 @@ 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() {
+		defer close(updates)
+		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 76fed167..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
 }
 
@@ -2436,18 +2444,24 @@ func (fc *FlowCollector) reconcileConnectorRecords() error {
 				}
 				for _, process := range fc.Processes {
 					if siteId == process.Parent {
-						if process.SourceHost != nil {
+						if process.SourceHost != nil && matchHost != nil {
 							if *matchHost == *process.SourceHost {
-								connector.ProcessId = &process.Identity
-								connector.Target = process.Name
-								process.connector = &connector.Identity
-								process.ProcessBinding = &Bound
-								fc.updateNetworkStatus()
-								log.Printf("COLLECTOR: Connector %s/%s associated to process %s\n", connector.Identity, *connector.Address, *process.Name)
-								delete(fc.connectorsToReconcile, connId)
 								found = true
-								break
 							}
+						} else if process.HostName != nil {
+							if *process.HostName == *connector.DestHost {
+								found = true
+							}
+						}
+						if found {
+							connector.ProcessId = &process.Identity
+							connector.Target = process.Name
+							process.connector = &connector.Identity
+							process.ProcessBinding = &Bound
+							fc.updateNetworkStatus()
+							log.Printf("COLLECTOR: Connector %s/%s associated to process %s\n", connector.Identity, *connector.Address, *process.Name)
+							delete(fc.connectorsToReconcile, connId)
+							break
 						}
 					}
 				}
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/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..fa688b2c 100644
--- a/cmd/skupper/skupper_mock_test.go
+++ b/cmd/skupper/skupper_mock_test.go
@@ -577,7 +577,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..a406387d 100644
--- a/pkg/domain/podman/site_test.go
+++ b/pkg/domain/podman/site_test.go
@@ -5,6 +5,7 @@ package podman
 
 import (
 	"testing"
+	"time"
 
 	"github.com/skupperproject/skupper/api/types"
 	"github.com/skupperproject/skupper/client/container"
@@ -101,9 +102,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)
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/flow/controller_test.go b/pkg/flow/controller_test.go
index 19dd9c88..9b3856c8 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,146 @@ 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)
+				},
+				func(t *testing.T, rec *SiteRecord, ok bool) {
+					assert.Equal(t, ok, false) // expect closed
+				},
+			},
+			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/flow/flow_mem_driver_test.go b/pkg/flow/flow_mem_driver_test.go
index a6eb2f6c..e6a32819 100644
--- a/pkg/flow/flow_mem_driver_test.go
+++ b/pkg/flow/flow_mem_driver_test.go
@@ -12,9 +12,12 @@ import (
 )
 
 func TestRecordGraphWithMetrics(t *testing.T) {
-	name := "skupper-site"
-	namespace := "skupper-public"
+	name := "skupper-site1"
+	namespace := "skupper-public1"
 	provider := "aws"
+	name2 := "skupper-site2"
+	namespace2 := "skupper-public2"
+	provider2 := "gke"
 	address := "tcp-go-echo"
 	address2 := "redis-cart"
 	address3 := "mongo"
@@ -31,6 +34,8 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 	connectorProcess1 := "process:1"
 	connectorProcess2 := "process:2"
 	processConnector := "connector:0"
+	destHost1 := "10.20.30.40"
+	destHost2 := "host.cloud.com"
 
 	sites := []SiteRecord{
 		{
@@ -43,6 +48,16 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			NameSpace: &namespace,
 			Provider:  &provider,
 		},
+		{
+			Base: Base{
+				RecType:   recordNames[Site],
+				Identity:  "site:1",
+				StartTime: uint64(time.Now().UnixNano()) / uint64(time.Microsecond),
+			},
+			Name:      &name2,
+			NameSpace: &namespace2,
+			Provider:  &provider2,
+		},
 	}
 	routers := []RouterRecord{
 		{
@@ -126,9 +141,10 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 				Parent:    "site:0",
 				StartTime: uint64(time.Now().UnixNano()) / uint64(time.Microsecond),
 			},
-			Name:      &processName2,
-			GroupName: &groupName1,
-			connector: &processConnector,
+			Name:       &processName2,
+			GroupName:  &groupName1,
+			connector:  &processConnector,
+			SourceHost: &destHost1,
 		},
 		{
 			Base: Base{
@@ -139,6 +155,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			},
 			Name:      &processName1,
 			GroupName: &groupName1,
+			HostName:  &destHost2,
 		},
 	}
 	listeners := []ListenerRecord{
@@ -149,8 +166,9 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 				Parent:    "router:0",
 				StartTime: uint64(time.Now().UnixNano()) / uint64(time.Microsecond),
 			},
-			Address:  &address,
-			Protocol: &protocol,
+			Address:   &address,
+			AddressId: &address,
+			Protocol:  &protocol,
 		},
 		{
 			Base: Base{
@@ -159,7 +177,9 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 				Parent:    "router:1",
 				StartTime: uint64(time.Now().UnixNano()) / uint64(time.Microsecond),
 			},
-			Protocol: &protocol,
+			Protocol:  &protocol,
+			Address:   &address,
+			AddressId: &address,
 		},
 		{
 			Base: Base{
@@ -183,6 +203,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			Address:   &address3,
 			Protocol:  &protocol,
 			ProcessId: &connectorProcess1,
+			DestHost:  &destHost1,
 		},
 		{
 			Base: Base{
@@ -203,6 +224,19 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			Protocol:  &protocol,
 			Address:   &address4,
 			ProcessId: &connectorProcess2,
+			DestHost:  &destHost2,
+		},
+		{
+			Base: Base{
+				RecType:   recordNames[Connector],
+				Identity:  "connector:2",
+				Parent:    "router:0",
+				StartTime: uint64(time.Now().UnixNano()) / uint64(time.Microsecond),
+			},
+			Protocol:  &protocol,
+			Address:   &address,
+			ProcessId: &connectorProcess1,
+			DestHost:  &destHost1,
 		},
 	}
 	flows := []FlowRecord{
@@ -227,6 +261,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			Trace:       &flowTrace,
 			ProcessName: &processName2,
 			Process:     &connectorProcess1,
+			SourceHost:  &destHost1,
 		},
 		{
 			Base: Base{
@@ -377,14 +412,29 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 		}
 		if f.Trace != nil {
 			trace := fc.annotateFlowTrace(&f)
-			assert.Equal(t, *trace, "skupper-site@skupper-site")
+			assert.Equal(t, *trace, "skupper-site1@skupper-site1")
 		}
 	}
 
 	fc.reconcileConnectorRecords()
 	fc.reconcileFlowRecords()
 
+	var addressId string
+	for _, address := range fc.VanAddresses {
+		if address.Name == "tcp-go-echo" {
+			addressId = address.Identity
+			break
+		}
+	}
+
+	var processGroupId string
+	for x, _ := range fc.ProcessGroups {
+		processGroupId = x
+		break
+	}
+
 	type test struct {
+		desc         string
 		recordType   int
 		method       string
 		url          string
@@ -396,6 +446,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 
 	testTable := []test{
 		{
+			desc:         "Get Site list",
 			recordType:   Site,
 			method:       "Get",
 			url:          "/",
@@ -405,6 +456,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: len(sites),
 		},
 		{
+			desc:         "Get Site item",
 			recordType:   Site,
 			method:       "Get",
 			url:          "/",
@@ -414,6 +466,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get Site routers",
 			recordType:   Site,
 			method:       "Get",
 			url:          "/",
@@ -423,6 +476,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: len(routers),
 		},
 		{
+			desc:         "Get Site links",
 			recordType:   Site,
 			method:       "Get",
 			url:          "/",
@@ -432,6 +486,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: len(links),
 		},
 		{
+			desc:         "Get Site hosts",
 			recordType:   Site,
 			method:       "Get",
 			url:          "/",
@@ -441,6 +496,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: len(hosts),
 		},
 		{
+			desc:         "Get Site processes",
 			recordType:   Site,
 			method:       "Get",
 			url:          "/",
@@ -450,6 +506,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: len(processes),
 		},
 		{
+			desc:         "Get Host list",
 			recordType:   Host,
 			method:       "Get",
 			url:          "/",
@@ -459,6 +516,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: len(hosts),
 		},
 		{
+			desc:         "Get Host item",
 			recordType:   Host,
 			method:       "Get",
 			url:          "/",
@@ -468,6 +526,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get Router list",
 			recordType:   Router,
 			method:       "Get",
 			url:          "/",
@@ -477,6 +536,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: len(routers),
 		},
 		{
+			desc:         "Get Router item",
 			recordType:   Router,
 			method:       "Get",
 			url:          "/",
@@ -486,6 +546,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get Router flows",
 			recordType:   Router,
 			method:       "Get",
 			url:          "/",
@@ -495,6 +556,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 6,
 		},
 		{
+			desc:         "Get Router links",
 			recordType:   Router,
 			method:       "Get",
 			url:          "/",
@@ -504,6 +566,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get Router listeners",
 			recordType:   Router,
 			method:       "Get",
 			url:          "/",
@@ -513,15 +576,17 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get Router connectors",
 			recordType:   Router,
 			method:       "Get",
 			url:          "/",
 			params:       map[string]string{"sortBy": "identity.asc"},
 			vars:         map[string]string{"id": "router:0"},
 			name:         "connectors",
-			responseSize: 1,
+			responseSize: 2,
 		},
 		{
+			desc:         "Get Link list",
 			recordType:   Link,
 			method:       "Get",
 			url:          "/",
@@ -531,6 +596,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: len(links),
 		},
 		{
+			desc:         "Get Link item",
 			recordType:   Link,
 			method:       "Get",
 			url:          "/",
@@ -540,6 +606,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get Listener list",
 			recordType:   Listener,
 			method:       "Get",
 			url:          "/",
@@ -549,6 +616,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: len(listeners),
 		},
 		{
+			desc:         "Get Listener item",
 			recordType:   Listener,
 			method:       "Get",
 			url:          "/",
@@ -558,6 +626,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get Listener flows",
 			recordType:   Listener,
 			method:       "Get",
 			url:          "/",
@@ -567,15 +636,17 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 3,
 		},
 		{
+			desc:         "Get Connector list",
 			recordType:   Connector,
 			method:       "Get",
 			url:          "/",
 			params:       map[string]string{"sortBy": "identity.asc"},
 			vars:         map[string]string{},
 			name:         "list",
-			responseSize: 2,
+			responseSize: 3,
 		},
 		{
+			desc:         "Get Connector item",
 			recordType:   Connector,
 			method:       "Get",
 			url:          "/",
@@ -585,6 +656,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get Connector flows",
 			recordType:   Connector,
 			method:       "Get",
 			url:          "/",
@@ -594,6 +666,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 3,
 		},
 		{
+			desc:         "Get Connector process",
 			recordType:   Connector,
 			method:       "Get",
 			url:          "/",
@@ -603,6 +676,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get Address list",
 			recordType:   Address,
 			method:       "Get",
 			url:          "/",
@@ -612,15 +686,57 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: len(fc.VanAddresses),
 		},
 		{
-			recordType:   Connector,
+			desc:         "Get Address item",
+			recordType:   Address,
 			method:       "Get",
 			url:          "/",
 			params:       map[string]string{"sortBy": "identity.asc"},
-			vars:         map[string]string{"id": "connector:0"},
+			vars:         map[string]string{"id": addressId},
 			name:         "item",
 			responseSize: 1,
 		},
 		{
+			desc:         "Get Address flows",
+			recordType:   Address,
+			method:       "Get",
+			url:          "/",
+			params:       map[string]string{"sortBy": "identity.asc"},
+			vars:         map[string]string{"id": addressId},
+			name:         "flows",
+			responseSize: 3,
+		},
+		{
+			desc:         "Get Address flowpairs",
+			recordType:   Address,
+			method:       "Get",
+			url:          "/",
+			params:       map[string]string{"sortBy": "identity.asc"},
+			vars:         map[string]string{"id": addressId},
+			name:         "flowpairs",
+			responseSize: 3,
+		},
+		{
+			desc:         "Get Address listeners",
+			recordType:   Address,
+			method:       "Get",
+			url:          "/",
+			params:       map[string]string{"sortBy": "identity.asc"},
+			vars:         map[string]string{"id": addressId},
+			name:         "listeners",
+			responseSize: 2,
+		},
+		{
+			desc:         "Get Address connectors",
+			recordType:   Address,
+			method:       "Get",
+			url:          "/",
+			params:       map[string]string{"sortBy": "identity.asc"},
+			vars:         map[string]string{"id": addressId},
+			name:         "connectors",
+			responseSize: 1,
+		},
+		{
+			desc:         "Get Flow list",
 			recordType:   Flow,
 			method:       "Get",
 			url:          "/",
@@ -630,6 +746,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: len(fc.Flows),
 		},
 		{
+			desc:         "Get Flow item",
 			recordType:   Flow,
 			method:       "Get",
 			url:          "/",
@@ -639,6 +756,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get Flow process",
 			recordType:   Flow,
 			method:       "Get",
 			url:          "/",
@@ -648,6 +766,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get Flowpair list",
 			recordType:   FlowPair,
 			method:       "Get",
 			url:          "/",
@@ -657,6 +776,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: len(fc.FlowPairs),
 		},
 		{
+			desc:         "Get Flowpair item",
 			recordType:   FlowPair,
 			method:       "Get",
 			url:          "/",
@@ -666,6 +786,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get Process list",
 			recordType:   Process,
 			method:       "Get",
 			url:          "/",
@@ -675,6 +796,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: len(processes),
 		},
 		{
+			desc:         "Get Process item",
 			recordType:   Process,
 			method:       "Get",
 			url:          "/",
@@ -684,6 +806,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get Process flows",
 			recordType:   Process,
 			method:       "Get",
 			url:          "/",
@@ -693,15 +816,17 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 3,
 		},
 		{
+			desc:         "Get Process addresses",
 			recordType:   Process,
 			method:       "Get",
 			url:          "/",
 			params:       map[string]string{"sortBy": "identity.asc"},
 			vars:         map[string]string{"id": "process:1"},
 			name:         "addresses",
-			responseSize: 1,
+			responseSize: 2,
 		},
 		{
+			desc:         "Get Process connector",
 			recordType:   Process,
 			method:       "Get",
 			url:          "/",
@@ -711,6 +836,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get ProcessGroup list",
 			recordType:   ProcessGroup,
 			method:       "Get",
 			url:          "/",
@@ -720,6 +846,27 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get ProcessGroup item",
+			recordType:   ProcessGroup,
+			method:       "Get",
+			url:          "/",
+			params:       map[string]string{"sortBy": "identity.asc"},
+			vars:         map[string]string{"id": processGroupId},
+			name:         "item",
+			responseSize: 1,
+		},
+		{
+			desc:         "Get ProcessGroup processes",
+			recordType:   ProcessGroup,
+			method:       "Get",
+			url:          "/",
+			params:       map[string]string{"sortBy": "identity.asc"},
+			vars:         map[string]string{"id": processGroupId},
+			name:         "processes",
+			responseSize: 3,
+		},
+		{
+			desc:         "Get SitePair list",
 			recordType:   SitePair,
 			method:       "Get",
 			url:          "/",
@@ -729,6 +876,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get SitePair item",
 			recordType:   SitePair,
 			method:       "Get",
 			url:          "/",
@@ -738,6 +886,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get ProcessGroupPair list",
 			recordType:   ProcessGroupPair,
 			method:       "Get",
 			url:          "/",
@@ -747,6 +896,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get ProcessPair list",
 			recordType:   ProcessPair,
 			method:       "Get",
 			url:          "/",
@@ -756,6 +906,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get ProcessPair item",
 			recordType:   ProcessPair,
 			method:       "Get",
 			url:          "/",
@@ -765,6 +916,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get Collector list",
 			recordType:   Collector,
 			method:       "Get",
 			url:          "/",
@@ -773,8 +925,21 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			name:         "list",
 			responseSize: 1,
 		},
+		{
+			desc:         "Get Collector item",
+			recordType:   Collector,
+			method:       "Get",
+			url:          "/",
+			params:       map[string]string{"sortBy": "identity.asc"},
+			vars:         map[string]string{},
+			name:         "item",
+			responseSize: 1,
+		},
 	}
 
+	fc.reconcileConnectorRecords()
+	fc.reconcileFlowRecords()
+
 	var payload Payload
 	for _, test := range testTable {
 		req, _ := http.NewRequest(test.method, test.url, nil)
@@ -788,7 +953,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 		assert.Assert(t, err)
 		err = json.Unmarshal([]byte(*resp), &payload)
 		assert.Assert(t, err)
-		assert.Equal(t, test.responseSize, payload.Count)
+		assert.Equal(t, test.responseSize, payload.Count, test.desc)
 	}
 
 	endTime := uint64(time.Now().UnixNano()) / uint64(time.Microsecond)
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/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..1f240e0a 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 {
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..1920f5c8 100644
--- a/test/integration/acceptance/custom/hello_policy/issues_test.go
+++ b/test/integration/acceptance/custom/hello_policy/issues_test.go
@@ -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/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