Index: /branches/main/array-ingress-controller/admission/Dockerfile
===================================================================
--- /branches/main/array-ingress-controller/admission/Dockerfile	(nonexistent)
+++ /branches/main/array-ingress-controller/admission/Dockerfile	(working copy)
@@ -0,0 +1,34 @@
+# Build stage
+FROM golang:1.23 AS builder
+
+WORKDIR /app
+COPY . .
+
+# Disable CGO and statically link the binary
+ENV CGO_ENABLED=0 GOOS=linux GOARCH=amd64
+RUN go mod tidy
+RUN go build -o admission-controller .
+
+# Set the correct permissions for the schema file
+RUN chmod 644 /app/config/templates/ratelimit-schema.json
+
+# Production stage
+FROM alpine:latest
+
+# Install necessary packages (like bash)
+RUN apk add --no-cache bash
+
+RUN mkdir -p /var/log && touch /var/log/arraycontroller.log && chmod 666 /var/log/arraycontroller.log
+
+# Copy built binary and TLS certificates
+COPY --from=builder /app/admission-controller /
+COPY --from=builder /app/tls /tls/
+
+# Copy all template files
+COPY --from=builder /app/config/templates /config/templates
+
+# Set executable permissions (if needed)
+RUN chmod +x /admission-controller
+
+# Start the controller
+CMD ["/admission-controller"]
Index: /branches/main/array-ingress-controller/admission/README.md
===================================================================
--- /branches/main/array-ingress-controller/admission/README.md	(nonexistent)
+++ /branches/main/array-ingress-controller/admission/README.md	(working copy)
@@ -0,0 +1,184 @@
+## Admission Controller
+
+## Prerequisites
+
+- Create a `tls/` directory inside the `admission/` folder:
+  ```sh
+  mkdir -p admission/tls
+  ```
+- Modify `csr.conf` with the appropriate values for your webhook service:
+  ```ini
+  [req]
+  req_extensions = v3_req
+  distinguished_name = req_distinguished_name
+  
+  [req_distinguished_name]
+  # The Common Name (CN) should match your webhook service name
+  CN = <service-name>.<namespace>.svc
+  
+  [v3_req]
+  keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+  extendedKeyUsage = serverAuth
+  subjectAltName = @alt_names
+  
+  [alt_names]
+  # These DNS names must match your service name and namespace in Kubernetes
+  DNS.1 = <service-name>.<namespace>.svc
+  DNS.2 = <service-name>.<namespace>.svc.cluster.local
+  ```
+- Generate a TLS key and certificate using the CSR template:
+  ```sh
+  openssl req -new -nodes -out admission/tls/tls.csr -newkey rsa:2048 -keyout admission/tls/tls.key -config admission/csr.conf
+  ```
+- Sign the CSR to create `tls.crt`:
+  ```sh
+  openssl x509 -req -in admission/tls/tls.csr -signkey admission/tls/tls.key -out admission/tls/tls.crt -days 365 -extfile admission/csr.conf -extensions v3_req
+  ```
+- Move the generated key and certificate to the `tls/` folder:
+  ```sh
+  mv admission/tls/tls.key admission/tls/tls.crt admission/tls/
+  ```
+
+- Docker installed and running
+- Kubernetes cluster with `kubectl` configured
+- Access to a container registry (e.g., Docker Hub, AWS ECR, GCR)
+
+## Build, Push, and Deploy Admission Controller
+
+1. **Build and Push Docker Image**
+   ```sh
+   docker build -t <your-docker-registry>/admission-controller:latest .
+   docker push <your-docker-registry>/admission-controller:latest
+   ```
+
+2. **Create the TLS Secret**
+   Encode `tls.crt` and `tls.key` in base64 and update `secret.yaml`:
+   ```yaml
+   apiVersion: v1
+   kind: Secret
+   metadata:
+     name: <name>
+     namespace: <namespace>
+   type: kubernetes.io/tls
+   data:
+     tls.crt: <base64-encoded-tls.crt>
+     tls.key: <base64-encoded-tls.key>
+   ```
+   Apply the secret:
+   ```sh
+   kubectl apply -f secret.yaml
+   ```
+
+3. **Modify Service Configuration**
+   Edit the `service.yaml` file to ensure it matches the service details:
+   ```yaml
+   apiVersion: v1
+   kind: Service
+   metadata:
+     name: <service-name>
+     namespace: an-val
+     labels:
+       app: <app-name>
+   spec:
+     selector:
+       app: <app-name>
+     ports:
+       - protocol: TCP
+         port: 443
+         targetPort: 8443
+   ```
+   Apply the changes:
+   ```sh
+   kubectl apply -f service.yaml
+   ```
+
+4. **Update and Deploy the Admission Controller**
+   Edit the `deployment.yaml` file to use the new image:
+   ```yaml
+   spec:
+     containers:
+       - name: admission-controller
+         image: <your-docker-registry>/admission-controller:latest
+   ```
+   Apply the deployment:
+   ```sh
+   kubectl apply -f deployment.yaml
+   ```
+
+5. **Create a ValidatingWebhookConfiguration**
+   Edit `validating-webhook.yaml` to include the CA bundle:
+   ```yaml
+   apiVersion: admissionregistration.k8s.io/v1
+   kind: ValidatingWebhookConfiguration
+   metadata:
+     name: annotation-validator-webhook
+   webhooks:
+     - name: validate.annotations.arraynetworks.com
+       clientConfig:
+         service:
+           name: annotation-validator
+           namespace: an-val
+           path: "/validate"
+         caBundle: "<base64-encoded-tls.crt>"
+       admissionReviewVersions: ["v1"]
+       sideEffects: None
+       rules:
+         - operations: ["CREATE", "UPDATE"]
+           apiGroups: ["route.openshift.io"]
+           apiVersions: ["v1"]
+           resources: ["routes"]
+   ```
+   Apply the webhook configuration:
+   ```sh
+   kubectl apply -f validating-webhook.yaml
+   ```
+
+6. **Verify Deployment**
+   ```sh
+   kubectl get pods -n <namespace>
+   ```
+
+## Admission Controller Validation Rules
+
+### Valid HealthCheck Annotation
+```yaml
+apv.arraynetworks.com/healthcheck: '{"type" : "http", "interval": 10, "timeout": 10,"retries":10}'
+```
+
+### Invalid HealthCheck Annotation
+```yaml
+apv.arraynetworks.com/healthcheck: '{"type" : "random", "interval": 10, "timeout": 10,"retries":10}'
+```
+
+### Valid RateLimit Annotation
+```yaml
+apv.arraynetworks.com/ratelimit: '{"max_cps": 5000, "soft_bandwidth": 1000, "hard_bandwidth": 500000}'
+```
+
+### Invalid RateLimit Annotation
+```yaml
+apv.arraynetworks.com/ratelimit: '{"ingress_name": "my-ingress", "soft_bandwidth": 100000}'
+```
+
+### Required Fields
+- **RateLimit:** `max_cps`, `soft_bandwidth`, `hard_bandwidth`
+- **HealthCheck:** `type`, `interval`, `timeout`, `retries`
+
+### Allowed Fields
+#### HealthCheck
+- `hc_name`
+- `service_name`
+- `type` (Allowed: `"http"`, `"tcp"`)
+- `hc_up`
+- `hc_down`
+- `ip.ipv4`
+- `port`
+- `interval`
+- `timeout`
+- `retries`
+
+#### RateLimit
+- `max_cps`
+- `soft_bandwidth`
+- `hard_bandwidth`
+
Index: /branches/main/array-ingress-controller/admission/config/templates/healthcheck-schema.json
===================================================================
--- /branches/main/array-ingress-controller/admission/config/templates/healthcheck-schema.json	(nonexistent)
+++ /branches/main/array-ingress-controller/admission/config/templates/healthcheck-schema.json	(working copy)
@@ -0,0 +1,50 @@
+{
+    "$schema": "http://json-schema.org/draft-07/schema",
+    "type": "object",
+    "required": [ "type", "interval", "timeout","retries"],
+    "properties": {
+      "ingress_name": {
+        "type": "string"
+      },
+      "hc_name": {
+        "type": "string"
+      },
+      "service_name": {
+        "type": "string"
+      },
+      "type": {
+        "type": "string",
+        "enum": ["http", "tcp"]
+      },
+      "hc_up": {
+        "type": "integer",
+        "minimum": 1
+      },
+      "hc_down": {
+        "type": "integer",
+        "minimum": 1
+      },
+      "ip.ipv4": {
+        "type": "string",
+        "pattern": "^((25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)$"
+      },
+      "port": {
+        "type": "integer",
+        "minimum": 1,
+        "maximum": 65535
+      },
+      "interval": {
+        "type": "integer",
+        "minimum": 1
+      },
+      "timeout": {
+        "type": "integer",
+        "minimum": 1
+      },
+      "retries": {
+        "type": "integer",
+        "minimum": 1
+      }
+    }
+  }
+  
\ No newline at end of file
Index: /branches/main/array-ingress-controller/admission/config/templates/ratelimit-schema.json
===================================================================
--- /branches/main/array-ingress-controller/admission/config/templates/ratelimit-schema.json	(nonexistent)
+++ /branches/main/array-ingress-controller/admission/config/templates/ratelimit-schema.json	(working copy)
@@ -0,0 +1,26 @@
+{
+    "$schema": "http://json-schema.org/draft-07/schema",
+    "type": "object",
+    "required": ["max_cps", "soft_bandwidth", "hard_bandwidth"],
+    "properties": {
+      "ingress_name": {
+        "type": "string"
+      },
+      "max_cps": {
+        "type": "integer",
+        "minimum": 1,
+        "maximum": 10000
+      },
+      "soft_bandwidth": {
+        "type": "integer",
+        "minimum": 1,
+        "maximum": 1000000
+      },
+      "hard_bandwidth": {
+        "type": "integer",
+        "minimum": 1,
+        "maximum": 1000000
+      }
+    }
+  }
+  
\ No newline at end of file
Index: /branches/main/array-ingress-controller/admission/controllers/admission/admission.go
===================================================================
--- /branches/main/array-ingress-controller/admission/controllers/admission/admission.go	(nonexistent)
+++ /branches/main/array-ingress-controller/admission/controllers/admission/admission.go	(working copy)
@@ -0,0 +1,114 @@
+package admission
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+
+	"arraynetworks.com/admission-controller/controllers"
+	"arraynetworks.com/admission-controller/controllers/admission/validation"
+	"arraynetworks.com/admission-controller/logger"
+	v1 "k8s.io/api/admission/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var log = logger.GetLogger()
+
+// HandleAdmissionRequest processes the admission request.
+func HandleAdmissionRequest(w http.ResponseWriter, r *http.Request) {
+	log.Infof("Received admission request")
+
+	if r.Method != http.MethodPost {
+		log.Warnf("Invalid request method: %s", r.Method)
+		http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
+		log.Infof("Admission response sent successfully")
+		return
+	}
+
+	var admissionReviewReq v1.AdmissionReview
+	if err := json.NewDecoder(r.Body).Decode(&admissionReviewReq); err != nil {
+		log.Errorf("Failed to decode admission review request: %v", err)
+		http.Error(w, fmt.Sprintf("Could not decode request: %v", err), http.StatusBadRequest)
+		return
+	}
+
+	reviewResponse := v1.AdmissionResponse{
+		UID:     admissionReviewReq.Request.UID,
+		Allowed: true,
+	}
+
+	if admissionReviewReq.Request.Kind.Kind == "Route" {
+		log.Infof("Processing Route kind request...")
+		annotations := admissionReviewReq.Request.Object.Raw
+		annotationsMap, class, err := extractAnnotations(annotations)
+		if err != nil {
+			log.Errorf("Failed to extract annotations: %v", err)
+			reviewResponse.Allowed = false
+			reviewResponse.Result = &metav1.Status{
+				Message: fmt.Sprintf("Invalid annotation format: %v", err),
+			}
+		} else {
+			log.Infof("Extracted annotations: %v", annotationsMap)
+			if class == "" {
+				log.Infof("Class annotation missing, allowing request by default")
+			} else {
+				log.Infof("Validating annotations for class: %s", class)
+				if err := validation.ValidateAnnotations(class, annotationsMap); err != nil {
+					log.Errorf("Validation failed: %v", err)
+					reviewResponse.Allowed = false
+					reviewResponse.Result = &metav1.Status{
+						Message: fmt.Sprintf("Validation failed: %v", err),
+					}
+				}
+			}
+		}
+	}
+
+	admissionReviewResp := v1.AdmissionReview{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "AdmissionReview",
+			APIVersion: "admission.k8s.io/v1",
+		},
+		Response: &reviewResponse,
+	}
+
+	respBytes, err := json.Marshal(admissionReviewResp)
+	if err != nil {
+		log.Errorf("Failed to encode response: %v", err)
+		http.Error(w, "Failed to encode response", http.StatusInternalServerError)
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+	w.WriteHeader(http.StatusOK)
+	w.Write(respBytes)
+	log.Infof("Admission response sent successfully")
+}
+
+// extractAnnotations extracts the annotations from the raw object.
+func extractAnnotations(raw []byte) (map[string]string, string, error) {
+	var obj map[string]interface{}
+	if err := json.Unmarshal(raw, &obj); err != nil {
+		return nil, "", fmt.Errorf("failed to unmarshal object: %v", err)
+	}
+
+	metadata, ok := obj["metadata"].(map[string]interface{})
+	if !ok {
+		return nil, "", fmt.Errorf("no metadata found in object")
+	}
+
+	annotations, ok := metadata["annotations"].(map[string]interface{})
+	if !ok {
+		return nil, "", fmt.Errorf("no annotations found in metadata")
+	}
+
+	annotationsMap := make(map[string]string)
+	for key, value := range annotations {
+		if strValue, ok := value.(string); ok {
+			annotationsMap[key] = strValue
+		}
+	}
+
+	class, _ := annotationsMap[controllers.AnnotationClass]
+	return annotationsMap, class, nil
+}
Index: /branches/main/array-ingress-controller/admission/controllers/admission/annotations/healthcheck_validate.go
===================================================================
--- /branches/main/array-ingress-controller/admission/controllers/admission/annotations/healthcheck_validate.go	(nonexistent)
+++ /branches/main/array-ingress-controller/admission/controllers/admission/annotations/healthcheck_validate.go	(working copy)
@@ -0,0 +1,112 @@
+package annotations
+
+import (
+	"encoding/json"
+	"fmt"
+	"regexp"
+
+	"arraynetworks.com/admission-controller/logger"
+
+	"github.com/xeipuuv/gojsonschema"
+)
+
+// HealthCheck struct to hold parsed health check data.
+type HealthCheck struct {
+	IngressName   string `json:"ingress_name"`
+	HCName        string `json:"hc_name"`
+	ServiceName   string `json:"service_name"`
+	Type          string `json:"type"`
+	HCUp          int    `json:"hc_up"`
+	HCDown        int    `json:"hc_down"`
+	IPV4          string `json:"ip.ipv4"`
+	Port          int    `json:"port"`
+	SendInterval  int    `json:"interval"`
+	ServerTimeout int    `json:"timeout"`
+	retries       int    `json:"retries"`
+}
+
+// ValidateHealthCheck validates the health check annotation against a JSON schema.
+var log = logger.GetLogger()
+
+// ValidateHealthCheck validates the health check annotation against a JSON schema.
+func ValidateHealthCheck(rawData string) error {
+	log.Infof("Starting HealthCheck validation...")
+
+	// Load the schema for validation
+	schemaLoader := gojsonschema.NewReferenceLoader("file://./config/templates/healthcheck-schema.json")
+	documentLoader := gojsonschema.NewStringLoader(rawData)
+
+	log.Debugf("Loading HealthCheck schema...")
+	result, err := gojsonschema.Validate(schemaLoader, documentLoader)
+	if err != nil {
+		log.Errorf("Schema validation error: %v", err)
+		return fmt.Errorf("schema validation error: %v", err)
+	}
+
+	if !result.Valid() {
+		log.Warnf("HealthCheck data is invalid:")
+		for _, desc := range result.Errors() {
+			log.Errorf("Validation error: %s", desc)
+		}
+		return fmt.Errorf("invalid health check data: %v", result.Errors())
+	}
+
+	var healthCheckMap map[string]interface{}
+	if err := json.Unmarshal([]byte(rawData), &healthCheckMap); err != nil {
+		log.Errorf("Failed to parse HealthCheck: %v", err)
+		return fmt.Errorf("failed to parse health check: %v", err)
+	}
+
+	// Optional field validations (validate only if present)
+	validatePositiveNumber(healthCheckMap, "hc_up")
+	validatePositiveNumber(healthCheckMap, "hc_down")
+	validatePort(healthCheckMap, "port")
+	validateIPv4(healthCheckMap, "ip.ipv4")
+	validatePositiveNumber(healthCheckMap, "interval")
+	validatePositiveNumber(healthCheckMap, "timeout")
+	validatePositiveNumber(healthCheckMap, "retries")
+
+	log.Infof("HealthCheck is valid: %+v", healthCheckMap)
+	return nil
+}
+
+func validatePositiveNumber(data map[string]interface{}, key string) error {
+	if val, ok := data[key].(float64); ok {
+		if val <= 0 {
+			log.Errorf("Invalid %s value: %d", key, int(val))
+			return fmt.Errorf("invalid %s value: %d", key, int(val))
+		}
+	} else if data[key] != nil {
+		log.Errorf("Invalid %s value: %v", key, data[key])
+		return fmt.Errorf("invalid %s value: %v", key, data[key])
+	}
+	return nil
+}
+
+func validatePort(data map[string]interface{}, key string) error {
+	if val, ok := data[key].(float64); ok {
+		if val <= 0 || val > 65535 {
+			log.Errorf("Invalid %s value: %v", key, val)
+			return fmt.Errorf("invalid %s value: %v", key, val)
+		}
+	} else if data[key] != nil {
+		log.Errorf("Invalid %s value: %v", key, data[key])
+		return fmt.Errorf("invalid %s value: %v", key, data[key])
+	}
+	return nil
+}
+
+func validateIPv4(data map[string]interface{}, key string) error {
+	if ipv4, ok := data[key].(string); ok {
+		ipPattern := `^((25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)$`
+		matched, _ := regexp.MatchString(ipPattern, ipv4)
+		if !matched {
+			log.Errorf("Invalid %s value: %s", key, ipv4)
+			return fmt.Errorf("invalid %s value: %s", key, ipv4)
+		}
+	} else if data[key] != nil {
+		log.Errorf("Invalid %s value: %v", key, data[key])
+		return fmt.Errorf("invalid %s value: %v", key, data[key])
+	}
+	return nil
+}
Index: /branches/main/array-ingress-controller/admission/controllers/admission/annotations/ratelimit_validate.go
===================================================================
--- /branches/main/array-ingress-controller/admission/controllers/admission/annotations/ratelimit_validate.go	(nonexistent)
+++ /branches/main/array-ingress-controller/admission/controllers/admission/annotations/ratelimit_validate.go	(working copy)
@@ -0,0 +1,86 @@
+package annotations
+
+import (
+	"encoding/json"
+	"fmt"
+
+	"github.com/xeipuuv/gojsonschema"
+)
+
+// RateLimit struct to hold parsed rate limit data.
+type RateLimit struct {
+	IngressName   string `json:"ingress_name"`
+	MaxCPS        int    `json:"max_cps"`
+	SoftBandwidth int    `json:"soft_bandwidth"`
+	HardBandwidth int    `json:"hard_bandwidth"`
+}
+
+// ValidateRateLimit validates the rate limit annotation against a JSON schema.
+func ValidateRateLimit(rawData string) error {
+	log.Infof("Starting RateLimit validation...")
+
+	// Load the schema for validation
+	schemaLoader := gojsonschema.NewReferenceLoader("file://./config/templates/ratelimit-schema.json")
+	documentLoader := gojsonschema.NewStringLoader(rawData)
+
+	// Log schema loading
+	log.Debugf("Loading RateLimit schema...")
+	result, err := gojsonschema.Validate(schemaLoader, documentLoader)
+	if err != nil {
+		log.Errorf("Schema validation error: %v", err)
+		return fmt.Errorf("schema validation error: %v", err)
+	}
+
+	// Check if the schema is valid or not
+	if !result.Valid() {
+		log.Warnf("RateLimit data is invalid:")
+		for _, desc := range result.Errors() {
+			log.Errorf("Validation error: %s", desc)
+		}
+		return fmt.Errorf("invalid rate limit data: %v", result.Errors())
+	}
+
+	// Parse the raw data into a generic map to ensure correct types
+	var rateLimitMap map[string]interface{}
+	if err := json.Unmarshal([]byte(rawData), &rateLimitMap); err != nil {
+		log.Errorf("Failed to parse RateLimit: %v", err)
+		return fmt.Errorf("failed to parse rate limit: %v", err)
+	}
+
+	// Validate MaxCPS if present
+	if maxCPS, ok := rateLimitMap["max_cps"].(float64); ok {
+		if maxCPS != float64(int(maxCPS)) {
+			log.Errorf("MaxCPS is not an integer: %v", maxCPS)
+			return fmt.Errorf("max_cps must be an integer, got: %v", maxCPS)
+		}
+	} else if rateLimitMap["max_cps"] != nil {
+		log.Errorf("Invalid MaxCPS value: %v", rateLimitMap["max_cps"])
+		return fmt.Errorf("invalid max_cps value: %v", rateLimitMap["max_cps"])
+	}
+
+	// Validate SoftBandwidth if present
+	if softBandwidth, ok := rateLimitMap["soft_bandwidth"].(float64); ok {
+		if softBandwidth != float64(int(softBandwidth)) {
+			log.Errorf("SoftBandwidth is not an integer: %v", softBandwidth)
+			return fmt.Errorf("soft_bandwidth must be an integer, got: %v", softBandwidth)
+		}
+	} else if rateLimitMap["soft_bandwidth"] != nil {
+		log.Errorf("Invalid SoftBandwidth value: %v", rateLimitMap["soft_bandwidth"])
+		return fmt.Errorf("invalid soft_bandwidth value: %v", rateLimitMap["soft_bandwidth"])
+	}
+
+	// Validate HardBandwidth if present
+	if hardBandwidth, ok := rateLimitMap["hard_bandwidth"].(float64); ok {
+		if hardBandwidth != float64(int(hardBandwidth)) {
+			log.Errorf("HardBandwidth is not an integer: %v", hardBandwidth)
+			return fmt.Errorf("hard_bandwidth must be an integer, got: %v", hardBandwidth)
+		}
+	} else if rateLimitMap["hard_bandwidth"] != nil {
+		log.Errorf("Invalid HardBandwidth value: %v", rateLimitMap["hard_bandwidth"])
+		return fmt.Errorf("invalid hard_bandwidth value: %v", rateLimitMap["hard_bandwidth"])
+	}
+
+	// Log successful validation
+	log.Infof("RateLimit is valid and all values are correct.")
+	return nil
+}
Index: /branches/main/array-ingress-controller/admission/controllers/admission/server.go
===================================================================
--- /branches/main/array-ingress-controller/admission/controllers/admission/server.go	(nonexistent)
+++ /branches/main/array-ingress-controller/admission/controllers/admission/server.go	(working copy)
@@ -0,0 +1,55 @@
+package admission
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"os"
+	"os/signal"
+	"syscall"
+
+	"arraynetworks.com/admission-controller/controllers"
+)
+
+
+
+// StartWebhookServer starts the webhook server.
+func StartWebhookServer(ctx context.Context) {
+	mux := http.NewServeMux()
+
+	// Add logging for the `/validate` endpoint
+	mux.HandleFunc("/validate", func(w http.ResponseWriter, r *http.Request) {
+		log.Infof("Request received at /validate endpoint: Method=%s, RemoteAddr=%s", r.Method, r.RemoteAddr)
+		HandleAdmissionRequest(w, r)
+	})
+
+	// Create the server with enhanced logging
+	server := &http.Server{
+		Addr:    fmt.Sprintf(":%d", controllers.Port),
+		Handler: mux,
+	}
+
+	// Start server in a separate goroutine with logging
+	go func() {
+		log.Infof("Starting webhook server on port %d", controllers.Port)
+		if err := server.ListenAndServeTLS("/tls/tls.crt", "/tls/tls.key"); err != nil && err != http.ErrServerClosed {
+			log.Fatalf("Failed to start server: %v", err)
+		}
+		log.Infof("Webhook server is now running and ready to receive requests")
+	}()
+
+	// Handle graceful shutdown with logging
+	stopCh := make(chan os.Signal, 1)
+	signal.Notify(stopCh, os.Interrupt, syscall.SIGTERM)
+	<-stopCh
+
+	log.Infof("Shutdown signal received, stopping server...")
+	shutdownCtx, cancel := context.WithTimeout(ctx, controllers.ShutdownPeriod)
+	defer cancel()
+
+	if err := server.Shutdown(shutdownCtx); err != nil {
+		log.Errorf("Error during server shutdown: %v", err)
+	} else {
+		log.Infof("Webhook server stopped gracefully")
+	}
+}
\ No newline at end of file
Index: /branches/main/array-ingress-controller/admission/controllers/admission/validation/validate.go
===================================================================
--- /branches/main/array-ingress-controller/admission/controllers/admission/validation/validate.go	(nonexistent)
+++ /branches/main/array-ingress-controller/admission/controllers/admission/validation/validate.go	(working copy)
@@ -0,0 +1,124 @@
+package validation
+
+import (
+	"fmt"
+	"strings"
+
+	"arraynetworks.com/admission-controller/controllers/admission/annotations"
+	"arraynetworks.com/admission-controller/logger"
+)
+
+// allowedAPVAnnotations - Allowed keys for APV class
+var allowedAPVAnnotations = map[string]bool{
+	"apv.arraynetworks.com/healthcheck": true,
+	"apv.arraynetworks.com/ratelimit":   true,
+}
+
+// allowedASFAnnotations - Allowed keys for ASF class
+var allowedASFAnnotations = map[string]bool{
+	"asf.arraynetworks.com/healthcheck": true,
+	"asf.arraynetworks.com/ratelimit":   true,
+}
+
+var log = logger.GetLogger()
+
+// ValidateAPV validates annotations for ARRAY-APV class.
+func ValidateAPV(annotationsMap map[string]string) error {
+	log.Infof("Validating APV annotations...")
+
+	for key := range annotationsMap {
+		// Skip class annotation
+		if key == "arraynetworks.com/class" {
+			continue
+		}
+		// Reject anything under arraynetworks.com/ except healthcheck and ratelimit
+		if strings.HasPrefix(key, "arraynetworks.com/") {
+			if !allowedAPVAnnotations[key] {
+				log.Errorf("Unsupported annotation for ARRAY-APV: %s", key)
+				return fmt.Errorf("unsupported annotation for ARRAY-APV: %s", key)
+			}
+		}
+	}
+
+	// Validate HealthCheck Annotation
+	if healthCheck, ok := annotationsMap["apv.arraynetworks.com/healthcheck"]; ok {
+		log.Infof("Validating APV healthcheck annotation...")
+		if err := annotations.ValidateHealthCheck(healthCheck); err != nil {
+			log.Errorf("Invalid APV healthcheck annotation: %v", err)
+			return fmt.Errorf("invalid APV healthcheck annotation: %v", err)
+		}
+	}
+
+	// Validate RateLimit Annotation
+	if rateLimit, ok := annotationsMap["apv.arraynetworks.com/ratelimit"]; ok {
+		log.Infof("Validating APV ratelimit annotation...")
+		if err := annotations.ValidateRateLimit(rateLimit); err != nil {
+			log.Errorf("Invalid APV ratelimit annotation: %v", err)
+			return fmt.Errorf("invalid APV ratelimit annotation: %v", err)
+		}
+	}
+
+	log.Infof("APV annotations validated successfully.")
+	return nil
+}
+
+// ValidateASF validates annotations for ARRAY-ASF class.
+func ValidateASF(annotationsMap map[string]string) error {
+	log.Infof("Validating ASF annotations...")
+
+	for key := range annotationsMap {
+		// Skip class annotation
+		if key == "arraynetworks.com/class" {
+			continue
+		}
+		// Reject anything under arraynetworks.com/ except healthcheck and ratelimit
+		if strings.HasPrefix(key, "arraynetworks.com/") {
+			if !allowedASFAnnotations[key] {
+				log.Errorf("Unsupported annotation for ARRAY-ASF: %s", key)
+				return fmt.Errorf("unsupported annotation for ARRAY-ASF: %s", key)
+			}
+		}
+	}
+
+	// Validate HealthCheck Annotation
+	if healthCheck, ok := annotationsMap["asf.arraynetworks.com/healthcheck"]; ok {
+		log.Infof("Validating ASF healthcheck annotation...")
+		if err := annotations.ValidateHealthCheck(healthCheck); err != nil {
+			log.Errorf("Invalid ASF healthcheck annotation: %v", err)
+			return fmt.Errorf("invalid ASF healthcheck annotation: %v", err)
+		}
+	}
+
+	// Validate RateLimit Annotation
+	if rateLimit, ok := annotationsMap["asf.arraynetworks.com/ratelimit"]; ok {
+		log.Infof("Validating ASF ratelimit annotation...")
+		if err := annotations.ValidateRateLimit(rateLimit); err != nil {
+			log.Errorf("Invalid ASF ratelimit annotation: %v", err)
+			return fmt.Errorf("invalid ASF ratelimit annotation: %v", err)
+		}
+	}
+
+	log.Infof("ASF annotations validated successfully.")
+	return nil
+}
+
+// ValidateAnnotations validates annotations based on class type.
+func ValidateAnnotations(class string, annotationsMap map[string]string) error {
+	// Skip validation if class annotation is not present
+	if class == "" {
+		log.Infof("No class annotation found, skipping validation.")
+		return nil
+	}
+
+	log.Infof("Validating class: %s", class)
+
+	switch class {
+	case "ARRAY-APV":
+		return ValidateAPV(annotationsMap)
+	case "ARRAY-ASF":
+		return ValidateASF(annotationsMap)
+	default:
+		log.Errorf("Unsupported class type: %s", class)
+		return fmt.Errorf("unsupported class type: %s", class)
+	}
+}
Index: /branches/main/array-ingress-controller/admission/controllers/consts.go
===================================================================
--- /branches/main/array-ingress-controller/admission/controllers/consts.go	(nonexistent)
+++ /branches/main/array-ingress-controller/admission/controllers/consts.go	(working copy)
@@ -0,0 +1,10 @@
+package controllers
+
+import (
+	"time"
+)
+const (
+	AnnotationClass = "arraynetworks.com/class"
+	Port           = 8443
+	ShutdownPeriod = 5 * time.Second	
+)
\ No newline at end of file
Index: /branches/main/array-ingress-controller/admission/csr.conf
===================================================================
--- /branches/main/array-ingress-controller/admission/csr.conf	(nonexistent)
+++ /branches/main/array-ingress-controller/admission/csr.conf	(working copy)
@@ -0,0 +1,17 @@
+[req]
+req_extensions = v3_req
+distinguished_name = req_distinguished_name
+
+[req_distinguished_name]
+# The Common Name (CN) should match your webhook service name
+CN = <service-name>.<namespace>.svc
+
+[v3_req]
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+extendedKeyUsage = serverAuth
+subjectAltName = @alt_names
+
+[alt_names]
+# These DNS names must match your service name and namespace in Kubernetes
+DNS.1 = <service-name>.<namespace>.svc
+DNS.2 = <service-name>.<namespace>.svc.cluster.local
Index: /branches/main/array-ingress-controller/admission/deployment.yaml
===================================================================
--- /branches/main/array-ingress-controller/admission/deployment.yaml	(nonexistent)
+++ /branches/main/array-ingress-controller/admission/deployment.yaml	(working copy)
@@ -0,0 +1,51 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: <name>
+  namespace: <namespace>
+  labels:
+    app: annotation-validator
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: annotation-validator
+  template:
+    metadata:
+      labels:
+        app: annotation-validator
+    spec:
+      containers:
+        - name: validator
+          image:  <image-registry>/annotation-validator:latest
+          imagePullPolicy: Always
+          securityContext:
+            runAsNonRoot: true
+            allowPrivilegeEscalation: false
+            capabilities:
+              drop:
+                - ALL
+            seccompProfile:
+              type: RuntimeDefault
+          ports:
+            - containerPort: 8443
+          volumeMounts:
+            - name: tls-cert
+              mountPath: /tls
+              readOnly: false
+          env:
+            - name: TLS_CERT_FILE
+              value: /tls/tls.crt
+            - name: TLS_KEY_FILE
+              value: /tls/tls.key
+          resources:
+            requests:
+              memory: "128Mi"
+              cpu: "500m"
+            limits:
+              memory: "256Mi"
+              cpu: 1
+      volumes:
+        - name: tls-cert
+          secret:
+            secretName: <secret-name>
Index: /branches/main/array-ingress-controller/admission/go.mod
===================================================================
--- /branches/main/array-ingress-controller/admission/go.mod	(nonexistent)
+++ /branches/main/array-ingress-controller/admission/go.mod	(working copy)
@@ -0,0 +1,35 @@
+module arraynetworks.com/admission-controller
+
+go 1.23.0
+
+replace admission-controller => ./
+
+require (
+	github.com/xeipuuv/gojsonschema v1.2.0
+	go.uber.org/zap v1.27.0
+	gopkg.in/natefinch/lumberjack.v2 v2.2.1
+	k8s.io/api v0.32.3
+	k8s.io/apimachinery v0.32.3
+)
+
+require (
+	github.com/fxamacker/cbor/v2 v2.7.0 // indirect
+	github.com/go-logr/logr v1.4.2 // indirect
+	github.com/gogo/protobuf v1.3.2 // indirect
+	github.com/google/gofuzz v1.2.0 // indirect
+	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/x448/float16 v0.8.4 // indirect
+	github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
+	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
+	go.uber.org/multierr v1.10.0 // indirect
+	golang.org/x/net v0.30.0 // indirect
+	golang.org/x/text v0.19.0 // indirect
+	gopkg.in/inf.v0 v0.9.1 // indirect
+	k8s.io/klog/v2 v2.130.1 // indirect
+	k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
+	sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
+	sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
+	sigs.k8s.io/yaml v1.4.0 // indirect
+)
Index: /branches/main/array-ingress-controller/admission/go.sum
===================================================================
--- /branches/main/array-ingress-controller/admission/go.sum	(nonexistent)
+++ /branches/main/array-ingress-controller/admission/go.sum	(working copy)
@@ -0,0 +1,108 @@
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
+github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
+github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
+github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
+github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
+github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
+github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
+github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
+github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
+go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
+go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
+golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
+golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls=
+k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k=
+k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U=
+k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
+k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
+k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
+k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
+k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
+sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
+sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA=
+sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
+sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
+sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
Index: /branches/main/array-ingress-controller/admission/logger/consts.go
===================================================================
--- /branches/main/array-ingress-controller/admission/logger/consts.go	(nonexistent)
+++ /branches/main/array-ingress-controller/admission/logger/consts.go	(working copy)
@@ -0,0 +1,20 @@
+package logger
+
+const(
+	// DebugLevel has verbose message
+	DebugLevel = "debug"
+	// InfoLevel is default log level
+	InfoLevel = "info"
+	// WarnLevel is for logging messages about possible issues
+	WarnLevel = "warn"
+	// ErrorLevel is for logging errors
+	ErrorLevel = "error"
+	// FatalLevel is for logging fatal messages.
+	FatalLevel = "fatal"
+
+	InstanceZapLogger int = iota
+
+	TimestampFormat    = "15:04:05.999 02/01/2006 (MST)"
+	DEFAULTLOGFILEPATH = "/var/log/arraycontroller.log"
+	DEFAULTLOGLEVEL    = InfoLevel
+)
\ No newline at end of file
Index: /branches/main/array-ingress-controller/admission/logger/logger.go
===================================================================
--- /branches/main/array-ingress-controller/admission/logger/logger.go	(nonexistent)
+++ /branches/main/array-ingress-controller/admission/logger/logger.go	(working copy)
@@ -0,0 +1,216 @@
+/*
+	Copyright 2025 Array Networks
+*/
+
+package logger
+
+import (
+	"errors"
+	//"io/ioutil"
+	"sync"
+	//"gopkg.in/yaml.v3"
+)
+
+var log Logger
+var once sync.Once
+var config Configuration
+
+// Fields Type to pass when we want to call WithFields for structured logging
+type Fields map[string]interface{}
+
+var (
+	errInvalidLoggerInstance = errors.New("Invalid logger instance")
+)
+
+// Logger is our contract for the logger
+type Logger interface {
+	Print(...interface{})
+
+	Printf(string, ...interface{})
+
+	Println(...interface{})
+
+	Debugf(format string, args ...interface{})
+
+	Infof(format string, args ...interface{})
+
+	Warnf(format string, args ...interface{})
+
+	Errorf(format string, args ...interface{})
+
+	Fatalf(format string, args ...interface{})
+
+	Panicf(format string, args ...interface{})
+
+	Debug(args ...interface{})
+
+	Info(args ...interface{})
+
+	Warn(args ...interface{})
+
+	Error(args ...interface{})
+
+	Fatal(args ...interface{})
+
+	Panic(args ...interface{})
+
+	WithFields(keyValues Fields) Logger
+}
+
+// Configuration stores the config for the logger
+// For some loggers there can only be one level across writers,
+// for such the level of Console is picked by default
+type Configuration struct {
+	EnableConsole     bool
+	ConsoleJSONFormat bool
+	ConsoleLevel      string
+	EnableFile        bool
+	FileJSONFormat    bool
+	FileLevel         string
+	FileLocation      string
+}
+
+// NewLogger returns an instance of logger
+func New(config Configuration, loggerInstance int) error {
+	switch loggerInstance {
+	case InstanceZapLogger:
+		logger, err := newZapLogger(config)
+		if err != nil {
+			Panic(err)
+			return err
+		}
+		log = logger
+		return nil
+	default:
+		return errInvalidLoggerInstance
+	}
+}
+
+// Printf to satisfy std logger
+func Printf(format string, args ...interface{}) {
+	log.Debugf(format, args...)
+}
+
+// Print to satisfy std logger
+func Print(args ...interface{}) {
+	log.Debug(args...)
+}
+
+// Println to satisfy std logger
+func Println(args ...interface{}) {
+	log.Debug(args...)
+}
+
+// Debugf ...
+func Debugf(format string, args ...interface{}) {
+	log.Debugf(format, args...)
+}
+
+// Infof ...
+func Infof(format string, args ...interface{}) {
+	log.Infof(format, args...)
+}
+
+// Warnf ...
+func Warnf(format string, args ...interface{}) {
+	log.Warnf(format, args...)
+}
+
+// Errorf ...
+func Errorf(format string, args ...interface{}) {
+	log.Errorf(format, args...)
+}
+
+// Fatalf ...
+func Fatalf(format string, args ...interface{}) {
+	log.Fatalf(format, args...)
+}
+
+// Panicf ...
+func Panicf(format string, args ...interface{}) {
+	log.Panicf(format, args...)
+}
+
+// Debug ...
+func Debug(args ...interface{}) {
+	log.Debug(args...)
+}
+
+// Info ...
+func Info(args ...interface{}) {
+	log.Info(args...)
+}
+
+// Warn ...
+func Warn(args ...interface{}) {
+	log.Warn(args...)
+}
+
+// Error ...
+func Error(args ...interface{}) {
+	log.Error(args...)
+}
+
+// Fatal ...
+func Fatal(args ...interface{}) {
+	log.Fatal(args...)
+}
+
+// Panic ...
+func Panic(args ...interface{}) {
+	log.Panic(args...)
+}
+
+// WithFields ...
+func WithFields(keyValues Fields) Logger {
+	return log.WithFields(keyValues)
+}
+
+// DefaultLoggerConfig is used if default config is not provided
+func DefaultLoggerConfig() Configuration {
+	return Configuration{
+		EnableConsole:     false,
+		ConsoleLevel:      DEFAULTLOGLEVEL,
+		ConsoleJSONFormat: false,
+		EnableFile:        true,
+		FileLevel:         DEFAULTLOGLEVEL,
+		FileJSONFormat:    false,
+		FileLocation:      DEFAULTLOGFILEPATH,
+	}
+}
+
+// LoadLogConfig read log config from file
+func LoadLogConfig(filepath string) (Configuration, error) {
+
+	logconfig := Configuration{
+		EnableConsole:     true,
+		ConsoleLevel:      DEFAULTLOGLEVEL,
+		ConsoleJSONFormat: false,
+		EnableFile:        true,
+		FileLevel:         DEFAULTLOGLEVEL,
+		FileJSONFormat:    false,
+		FileLocation:      filepath,
+	}
+
+	return logconfig, nil
+}
+
+// GetLogger return logger instance
+// initialize global logger only onces
+func GetLogger() Logger {
+	once.Do(
+		func() {
+			var err error
+			config, err = LoadLogConfig(DEFAULTLOGFILEPATH)
+			if err != nil {
+				config = DefaultLoggerConfig()
+			}
+			// fmt.Printf("log config: %+v\n", config)
+			err = New(config, InstanceZapLogger)
+			if err != nil {
+				Panic("Could not instantiate log %s", err.Error())
+			}
+		},
+	)
+	return log
+}
Index: /branches/main/array-ingress-controller/admission/logger/zap.go
===================================================================
--- /branches/main/array-ingress-controller/admission/logger/zap.go	(nonexistent)
+++ /branches/main/array-ingress-controller/admission/logger/zap.go	(working copy)
@@ -0,0 +1,168 @@
+/*
+	Copyright 2025 Array Networks
+*/
+
+package logger
+
+import (
+	"os"
+	"time"
+
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
+	lumberjack "gopkg.in/natefinch/lumberjack.v2"
+)
+
+type zapLogger struct {
+	sugaredLogger *zap.SugaredLogger
+}
+
+func customTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
+	enc.AppendString(t.Format(TimestampFormat))
+}
+
+func getEncoder(isJSON bool) zapcore.Encoder {
+	var encoder zapcore.Encoder
+	if isJSON {
+		encoderConfig := zap.NewProductionEncoderConfig()
+		encoderConfig.EncodeTime = customTimeEncoder
+		encoder = zapcore.NewJSONEncoder(encoderConfig)
+	} else {
+		encoderConfig := zap.NewProductionEncoderConfig()
+		encoderConfig.EncodeTime = customTimeEncoder
+		// encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
+		encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
+		encoder = zapcore.NewConsoleEncoder(encoderConfig)
+	}
+	return encoder
+}
+
+func getZapLevel(level string) zapcore.Level {
+	switch level {
+	case InfoLevel:
+		return zapcore.InfoLevel
+	case WarnLevel:
+		return zapcore.WarnLevel
+	case DebugLevel:
+		return zapcore.DebugLevel
+	case ErrorLevel:
+		return zapcore.ErrorLevel
+	case FatalLevel:
+		return zapcore.FatalLevel
+	default:
+		return zapcore.InfoLevel
+	}
+}
+
+func newZapLogger(config Configuration) (Logger, error) {
+	cores := []zapcore.Core{}
+
+	if config.EnableConsole {
+		level := getZapLevel(config.ConsoleLevel)
+		writer := zapcore.Lock(os.Stdout)
+		core := zapcore.NewCore(getEncoder(config.ConsoleJSONFormat), writer, level)
+		cores = append(cores, core)
+	}
+
+	if config.EnableFile {
+		level := getZapLevel(config.FileLevel)
+		writer := zapcore.AddSync(&lumberjack.Logger{
+			Filename:   config.FileLocation,
+			MaxSize:    20,
+			Compress:   true,
+			MaxAge:     28,
+			MaxBackups: 5,
+		})
+		core := zapcore.NewCore(getEncoder(config.FileJSONFormat), writer, level)
+		cores = append(cores, core)
+	}
+
+	combinedCore := zapcore.NewTee(cores...)
+
+	// AddCallerSkip skips 1 number of callers, this is important else the file that gets
+	// logged will always be the wrapped file. In our case zap.go
+	logger := zap.New(combinedCore,
+		zap.AddCallerSkip(1),
+		zap.AddCaller(),
+	).Sugar()
+	// defer func() {
+	// 	err := logger.Sync()
+	// 	if err != nil {
+	// 		Panic(err)
+	// 	}
+	// }()
+
+	return &zapLogger{
+		sugaredLogger: logger,
+	}, nil
+}
+
+func (l *zapLogger) Printf(format string, args ...interface{}) {
+	l.sugaredLogger.Debugf(format, args...)
+}
+
+func (l *zapLogger) Print(args ...interface{}) {
+	l.sugaredLogger.Debug(args...)
+}
+
+func (l *zapLogger) Println(args ...interface{}) {
+	l.sugaredLogger.Debug(args...)
+}
+
+func (l *zapLogger) Debugf(format string, args ...interface{}) {
+	l.sugaredLogger.Debugf(format, args...)
+}
+
+func (l *zapLogger) Infof(format string, args ...interface{}) {
+	l.sugaredLogger.Infof(format, args...)
+}
+
+func (l *zapLogger) Warnf(format string, args ...interface{}) {
+	l.sugaredLogger.Warnf(format, args...)
+}
+
+func (l *zapLogger) Errorf(format string, args ...interface{}) {
+	l.sugaredLogger.Errorf(format, args...)
+}
+
+func (l *zapLogger) Fatalf(format string, args ...interface{}) {
+	l.sugaredLogger.Fatalf(format, args...)
+}
+
+func (l *zapLogger) Panicf(format string, args ...interface{}) {
+	l.sugaredLogger.Fatalf(format, args...)
+}
+
+func (l *zapLogger) Debug(args ...interface{}) {
+	l.sugaredLogger.Debug(args...)
+}
+
+func (l *zapLogger) Info(args ...interface{}) {
+	l.sugaredLogger.Info(args...)
+}
+
+func (l *zapLogger) Warn(args ...interface{}) {
+	l.sugaredLogger.Warn(args...)
+}
+
+func (l *zapLogger) Error(args ...interface{}) {
+	l.sugaredLogger.Error(args...)
+}
+
+func (l *zapLogger) Fatal(args ...interface{}) {
+	l.sugaredLogger.Fatal(args...)
+}
+
+func (l *zapLogger) Panic(args ...interface{}) {
+	l.sugaredLogger.Fatal(args...)
+}
+
+func (l *zapLogger) WithFields(fields Fields) Logger {
+	var f = make([]interface{}, 0)
+	for k, v := range fields {
+		f = append(f, k)
+		f = append(f, v)
+	}
+	newLogger := l.sugaredLogger.With(f...)
+	return &zapLogger{newLogger}
+}
Index: /branches/main/array-ingress-controller/admission/main.go
===================================================================
--- /branches/main/array-ingress-controller/admission/main.go	(nonexistent)
+++ /branches/main/array-ingress-controller/admission/main.go	(working copy)
@@ -0,0 +1,52 @@
+package main
+
+import (
+	"context"
+	"os"
+	"os/signal"
+	"syscall"
+	"time"
+
+	"arraynetworks.com/admission-controller/controllers/admission"
+	"arraynetworks.com/admission-controller/logger"
+)
+
+func main() {
+	log := logger.GetLogger()
+
+	log.Infof("Starting Admission Controller...")
+
+	// Create context with cancellation
+	ctx, cancel := context.WithCancel(context.Background())
+
+	// Start the webhook server
+	go admission.StartWebhookServer(ctx)
+
+	// Handle termination signals
+	stopCh := make(chan os.Signal, 1)
+	signal.Notify(stopCh, os.Interrupt, syscall.SIGTERM)
+
+	<-stopCh
+	log.Infof("Termination signal received, shutting down...")
+
+	// Cancel context to trigger shutdown
+	cancel()
+
+	// Graceful shutdown with timeout
+	shutdownTimeout := 5 * time.Second
+	shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), shutdownTimeout)
+	defer shutdownCancel()
+
+	log.Infof("Waiting for webhook server to shut down...")
+
+	select {
+	case <-shutdownCtx.Done():
+		if shutdownCtx.Err() == context.DeadlineExceeded {
+			log.Warnf("Shutdown timeout exceeded, forcing exit")
+		}
+	default:
+		log.Infof("Webhook server shut down cleanly")
+	}
+
+	log.Infof("Admission Controller stopped")
+}
Index: /branches/main/array-ingress-controller/admission/route/cerrors/cerrors.go
===================================================================
--- /branches/main/array-ingress-controller/admission/route/cerrors/cerrors.go	(nonexistent)
+++ /branches/main/array-ingress-controller/admission/route/cerrors/cerrors.go	(working copy)
@@ -0,0 +1,140 @@
+package cerrors
+
+import (
+	"errors"
+	"fmt"
+)
+
+var (
+	// ErrMissingAnnotations the ingress rule does not contain annotations
+	// This is an error only when annotations are being parsed
+	ErrMissingAnnotations = errors.New("ingress rule without annotations")
+
+	// ErrInvalidAnnotationName the ingress rule does contains an invalid
+	// annotation name
+	ErrInvalidAnnotationName = errors.New("invalid annotation name")
+)
+
+// NewInvalidAnnotationConfiguration returns a new InvalidConfiguration error for use when
+// annotations are not correctly configured
+func NewInvalidAnnotationConfiguration(name, reason string) error {
+	return InvalidConfigurationError{
+		Name: fmt.Sprintf("the annotation %v does not contain a valid configuration: %v", name, reason),
+	}
+}
+
+// NewInvalidAnnotationContent returns a new InvalidContent error
+func NewInvalidAnnotationContent(name string, val interface{}) error {
+	return InvalidContentError{
+		Name: fmt.Sprintf("the annotation %v does not contain a valid value (%v)", name, val),
+	}
+}
+
+// NewLocationDenied returns a new LocationDenied error
+func NewLocationDenied(reason string) error {
+	return LocationDeniedError{
+		Reason: fmt.Errorf("location denied, reason: %v", reason),
+	}
+}
+
+// InvalidConfigurationError
+type InvalidConfigurationError struct {
+	Name string
+}
+
+func (e InvalidConfigurationError) Error() string {
+	return e.Name
+}
+
+// InvalidContentError
+type InvalidContentError struct {
+	Name string
+}
+
+func (e InvalidContentError) Error() string {
+	return e.Name
+}
+
+// LocationDeniedError
+type LocationDeniedError struct {
+	Reason error
+}
+
+func (e LocationDeniedError) Error() string {
+	return e.Reason.Error()
+}
+
+// IsLocationDenied checks if the err is an error which
+// indicates a location should return HTTP code 503
+func IsLocationDenied(e error) bool {
+	_, ok := e.(LocationDeniedError)
+	return ok
+}
+
+// IsMissingAnnotations checks if the err is an error which
+// indicates the ingress does not contain annotations
+func IsMissingAnnotations(e error) bool {
+	return e == ErrMissingAnnotations
+}
+
+// IsInvalidContent checks if the err is an error which
+// indicates an annotations value is not valid
+func IsInvalidContent(e error) bool {
+	_, ok := e.(InvalidContentError)
+	return ok
+}
+
+// New returns a new error
+func New(m string) error {
+	return errors.New(m)
+}
+
+// Errorf formats according to a format specifier and returns the string
+// as a value that satisfies error.
+func Errorf(format string, args ...interface{}) error {
+	return fmt.Errorf(format, args...)
+}
+
+type ValidationError struct {
+	Reason error
+}
+
+type RiskyAnnotationError struct {
+	Reason error
+}
+
+func (e ValidationError) Error() string {
+	return e.Reason.Error()
+}
+
+// NewValidationError returns a new LocationDenied error
+func NewValidationError(annotation string) error {
+	return ValidationError{
+		Reason: fmt.Errorf("annotation %s contains invalid value", annotation),
+	}
+}
+
+// IsValidationError checks if the err is an error which
+// indicates that some annotation value is invalid
+func IsValidationError(e error) bool {
+	_, ok := e.(ValidationError)
+	return ok
+}
+
+// NewRiskyAnnotations returns a new LocationDenied error
+func NewRiskyAnnotations(name string) error {
+	return RiskyAnnotationError{
+		Reason: fmt.Errorf("annotation group %s contains risky annotation based on ingress configuration", name),
+	}
+}
+
+// IsRiskyAnnotationError checks if the err is an error which
+// indicates that some annotation value is invalid
+func IsRiskyAnnotationError(e error) bool {
+	_, ok := e.(ValidationError)
+	return ok
+}
+
+func (e RiskyAnnotationError) Error() string {
+	return e.Reason.Error()
+}
\ No newline at end of file
Index: /branches/main/array-ingress-controller/admission/route/cerrors/go.mod
===================================================================
--- /branches/main/array-ingress-controller/admission/route/cerrors/go.mod	(nonexistent)
+++ /branches/main/array-ingress-controller/admission/route/cerrors/go.mod	(working copy)
@@ -0,0 +1,3 @@
+module cerrors
+
+go 1.23.6
Index: /branches/main/array-ingress-controller/admission/route/cerrors/go.sum	(added)
===================================================================
--- /branches/main/array-ingress-controller/admission/route/cerrors/go.sum	(revision 0)
+++ /branches/main/array-ingress-controller/admission/route/cerrors/go.sum	(revision 0)
Index: /branches/main/array-ingress-controller/admission/secret.yaml
===================================================================
--- /branches/main/array-ingress-controller/admission/secret.yaml	(nonexistent)
+++ /branches/main/array-ingress-controller/admission/secret.yaml	(working copy)
@@ -0,0 +1,11 @@
+apiVersion: v1
+kind: Secret
+metadata:
+  name: <name>
+  namespace: <namespace>
+type: kubernetes.io/tls
+data:
+  tls.crt: <tls.crt>
+  # The value of tls.crt should be the base64 encoded value of the certificate file
+  # The value of tls.key should be the base64 encoded value of the key file
+  tls.key: <tls.key>
Index: /branches/main/array-ingress-controller/admission/service.yaml
===================================================================
--- /branches/main/array-ingress-controller/admission/service.yaml	(nonexistent)
+++ /branches/main/array-ingress-controller/admission/service.yaml	(working copy)
@@ -0,0 +1,14 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: <service-name>
+  namespace: an-val
+  labels:
+    app: <app-name>
+spec:
+  ports:
+    - port: 443
+      targetPort: 8443
+      protocol: TCP
+  selector:
+    app: <app-name>
Index: /branches/main/array-ingress-controller/admission/validating-webhook.yaml
===================================================================
--- /branches/main/array-ingress-controller/admission/validating-webhook.yaml	(nonexistent)
+++ /branches/main/array-ingress-controller/admission/validating-webhook.yaml	(working copy)
@@ -0,0 +1,19 @@
+apiVersion: admissionregistration.k8s.io/v1
+kind: ValidatingWebhookConfiguration
+metadata:
+  name: annotation-validator-webhook
+webhooks:
+  - name: validate.annotations.arraynetworks.com
+    clientConfig:
+      service:
+        name: annotation-validator
+        namespace: an-val
+        path: "/validate"
+      caBundle: "CABundle-CERT-HERE"
+    admissionReviewVersions: ["v1"]
+    sideEffects: None
+    rules:
+      - operations: ["CREATE", "UPDATE"]
+        apiGroups: ["route.openshift.io"]
+        apiVersions: ["v1"]
+        resources: ["routes"]
Index: /branches/main/array-ingress-controller/apvadapter/README.md
===================================================================
--- /branches/main/array-ingress-controller/apvadapter/README.md	(nonexistent)
+++ /branches/main/array-ingress-controller/apvadapter/README.md	(working copy)
@@ -0,0 +1,20 @@
+## Steps to Run the Test
+
+### 1. Set Environment Variables
+Run the following command in the terminal within the project directory:
+
+```sh
+export APV_INSECURE_SKIP=true
+export APV_TIMEOUT=30s
+export APV_USERNAME=testapi
+export APV_PASSWORD=test@123
+export APV_IP=13.233.204.131
+export TEST="true"
+```
+
+### 2. Execute Tests
+Run the tests using the following command:
+
+```sh
+go test -v
+```
\ No newline at end of file
Index: /branches/main/array-ingress-controller/apvadapter/annotations/health/go.mod
===================================================================
--- /branches/main/array-ingress-controller/apvadapter/annotations/health/go.mod	(nonexistent)
+++ /branches/main/array-ingress-controller/apvadapter/annotations/health/go.mod	(working copy)
@@ -0,0 +1,27 @@
+module arraynetworks.com/array-ingress-controller/apvadapter/health
+
+go 1.23.6
+
+replace (
+	arraynetworks.com/array-ingress-controller/apvclient => ../../apvclient
+	arraynetworks.com/array-ingress-controller/configstore => ../../../configstore
+	arraynetworks.com/array-ingress-controller/logger => ../../../logger
+)
+
+require (
+	arraynetworks.com/array-ingress-controller/apvclient v0.0.0-00010101000000-000000000000
+	arraynetworks.com/array-ingress-controller/logger v0.0.0-00010101000000-000000000000
+	gorm.io/gorm v1.25.12
+)
+
+require (
+	github.com/go-resty/resty/v2 v2.16.5 // indirect
+	github.com/jinzhu/inflection v1.0.0 // indirect
+	github.com/jinzhu/now v1.1.5 // indirect
+	go.uber.org/multierr v1.10.0 // indirect
+	go.uber.org/zap v1.27.0 // indirect
+	golang.org/x/net v0.33.0 // indirect
+	golang.org/x/text v0.21.0 // indirect
+	gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+)
Index: /branches/main/array-ingress-controller/apvadapter/annotations/health/go.sum
===================================================================
--- /branches/main/array-ingress-controller/apvadapter/annotations/health/go.sum	(nonexistent)
+++ /branches/main/array-ingress-controller/apvadapter/annotations/health/go.sum	(working copy)
@@ -0,0 +1,50 @@
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
+github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
+github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
+github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
+github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
+github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
+github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
+github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
+github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=
+github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=
+github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
+github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
+go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
+go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
+golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
+golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
+golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
+golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
+golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
+golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
+golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
+gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
Index: /branches/main/array-ingress-controller/apvadapter/annotations/health/health.go
===================================================================
--- /branches/main/array-ingress-controller/apvadapter/annotations/health/health.go	(nonexistent)
+++ /branches/main/array-ingress-controller/apvadapter/annotations/health/health.go	(working copy)
@@ -0,0 +1,172 @@
+/*
+	Copyright 2025 Array Networks
+*/
+
+package health
+
+import (
+	"encoding/json"
+
+	"arraynetworks.com/array-ingress-controller/apvadapter/apvclient"
+	"arraynetworks.com/array-ingress-controller/apvadapter/apvurlbuilder"
+	"arraynetworks.com/array-ingress-controller/logger"
+	"github.com/go-resty/resty/v2"
+)
+
+var (
+	log  logger.Logger
+	resp *resty.Response
+)
+
+func init() {
+	log = logger.GetLogger()
+}
+
+var DefaultHttpRequest = []HttpRequest{
+	{ID: 0, AssoIdx: 0},
+}
+
+var DefaultHttpResponse = []HttpResponse{
+	{ID: 0, AssoIdx: 0},
+}
+
+// HealthCheckConfig represents the health check configuration for a real service
+type HealthCheckConfig struct {
+	HCName         string         `json:"hc_name"`
+	RealService    []RealService  `json:"real_service"`
+	Type           string         `json:"type"`
+	HcUp           int            `json:"hc_up"`
+	HcDown         int            `json:"hc_down"`
+	IP             IP             `json:"ip"`
+	Port           int            `json:"port"`
+	SendInterval   int            `json:"send_interval"`
+	ServerTimeout  int            `json:"server_timeout"`
+	HCHttpRequest  []HttpRequest  `json:"hc_http_request"`
+	HCHttpResponse []HttpResponse `json:"hc_http_response"`
+}
+
+type HealthCheckUpdate struct {
+	HcUp           int            `json:"hc_up"`
+	HcDown         int            `json:"hc_down"`
+	IP             IP             `json:"ip"`
+	Port           int            `json:"port"`
+	SendInterval   int            `json:"send_interval"`
+	ServerTimeout  int            `json:"server_timeout"`
+	HCHttpRequest  []HttpRequest  `json:"hc_http_request"`
+	HCHttpResponse []HttpResponse `json:"hc_http_response"`
+}
+
+type RealService struct {
+	ServiceName string `json:"service_name"`
+}
+
+type IP struct {
+	IPv4 string `json:"ipv4"`
+}
+
+type HttpRequest struct {
+	ID      int `json:"id"`
+	AssoIdx int `json:"_asso_idx"`
+}
+
+type HttpResponse struct {
+	ID      int `json:"id"`
+	AssoIdx int `json:"_asso_idx"`
+}
+
+// NewHealthCheckConfig initializes and returns a new HealthCheckConfig struct
+
+func NewHealthCheckConfig(hcName, hcType, serviceName, ip string, hcUp, hcDown, port, sendInterval, serverTimeout int) *HealthCheckConfig {
+	return &HealthCheckConfig{
+		HCName: hcName,
+		RealService: []RealService{
+			{
+				ServiceName: serviceName,
+			},
+		},
+		Type:           hcType,
+		HcUp:           hcUp,
+		HcDown:         hcDown,
+		IP:             IP{IPv4: ip},
+		Port:           port,
+		SendInterval:   sendInterval,
+		ServerTimeout:  serverTimeout,
+		HCHttpRequest:  DefaultHttpRequest,
+		HCHttpResponse: DefaultHttpResponse,
+	}
+}
+
+// CreateHealthCheck applies the health check settings to the given real service
+func (hc *HealthCheckConfig) CreateHealthCheck() error {
+	client := apvclient.GetAPVClient()
+	jsonData, err := json.Marshal(hc)
+	log.Infof("Creating health check with payload: %s", string(jsonData))
+	if err != nil {
+		log.Errorf("Error marshalling health check JSON: %v", err)
+		return err
+	}
+	log.Infof("Creating health check: %s", hc.HCName)
+
+	annotationUrl := apvurlbuilder.GetAnnotationURL(apvurlbuilder.BaseURL["HealthCheck"])
+	resp, err = client.Create(annotationUrl, jsonData)
+	if err != nil {
+		log.Errorf("Error creating health check: %v", err)
+		return err
+	}
+	log.Infof("Health check created successfully: %s", hc.HCName)
+	log.Infof("Response Body: %s", resp.String())
+	log.Infof("Response Status Code: %v", resp.StatusCode())
+
+	return nil
+}
+
+// UpdateHealthCheck updates the exiString health check settings
+func (hc *HealthCheckConfig) UpdateHealthCheck() error {
+	client := apvclient.GetAPVClient()
+	healthUpdatePayload := HealthCheckUpdate{
+		HcUp:           hc.HcUp,
+		HcDown:         hc.HcDown,
+		IP:             hc.IP,
+		Port:           hc.Port,
+		SendInterval:   hc.SendInterval,
+		ServerTimeout:  hc.ServerTimeout,
+		HCHttpRequest:  hc.HCHttpRequest,
+		HCHttpResponse: hc.HCHttpResponse,
+	}
+	jsonData, err := json.Marshal(healthUpdatePayload)
+	log.Infof("Updating health check with payload: %s", string(jsonData))
+	if err != nil {
+		log.Errorf("Error marshalling health check update JSON: %v", err)
+		return err
+	}
+	log.Infof("Updating health check: %s", hc.HCName)
+	annotationUrl := apvurlbuilder.GetAnnotationURL(apvurlbuilder.BaseURL["HealthCheck"], hc.HCName)
+
+	resp, err = client.Update(annotationUrl, jsonData)
+
+	if err != nil {
+		log.Errorf("Error updating health check: %v", err)
+		return err
+	}
+	log.Infof("Health check updated successfully: %s", hc.HCName)
+	log.Infof("Response Body: %s", resp.String())
+	log.Infof("Response Status Code: %v", resp.StatusCode())
+
+	return nil
+}
+
+// DeleteHealthCheck removes the health check settings
+func (hc *HealthCheckConfig) DeleteHealthCheck() error {
+	client := apvclient.GetAPVClient()
+	log.Infof("Deleting health check: %s", hc.HCName)
+	endpoint := apvurlbuilder.GetAnnotationURL(apvurlbuilder.BaseURL["RealService"], hc.RealService[0].ServiceName+"/health_checks/"+hc.HCName)
+	resp, err := client.Delete(endpoint)
+	if err != nil {
+		log.Errorf("Error deleting health check: %v", err)
+		return err
+	}
+	log.Infof("Health check deleted successfully: %s", hc.HCName)
+	log.Infof("Response Body: %s", resp.String())
+	log.Infof("Response Status Code: %v", resp.StatusCode())
+	return nil
+}
Index: /branches/main/array-ingress-controller/apvadapter/annotations/health/health_test.go
===================================================================
--- /branches/main/array-ingress-controller/apvadapter/annotations/health/health_test.go	(nonexistent)
+++ /branches/main/array-ingress-controller/apvadapter/annotations/health/health_test.go	(working copy)
@@ -0,0 +1,158 @@
+/*
+   Copyright 2025 Array Networks
+*/
+
+package health
+
+import (
+	"reflect"
+	"testing"
+	"arraynetworks.com/array-ingress-controller/apvadapter/realservice"
+)
+
+// createTestRealService creates a real service for testing
+func createTestRealService(t *testing.T) *realservice.RealService {
+	rs := realservice.NewRealService("service1", "192.168.1.100", "TCP", 80, true)
+	err := rs.CreateRealService()
+	if err != nil {
+		t.Fatalf("Failed to create real service: %v", err)
+	}
+	return rs
+}
+
+// cleanupTestRealService deletes the test real service
+func cleanupTestRealService(t *testing.T, rs *realservice.RealService) {
+	err := rs.DeleteRealService()
+	if err != nil {
+		t.Fatalf("Failed to delete real service: %v", err)
+	}
+}
+
+func TestNewHealthCheckConfig(t *testing.T) {
+	rs := createTestRealService(t)
+	defer cleanupTestRealService(t, rs)
+
+	hcName := "hc-service1"
+
+	config := NewHealthCheckConfig(
+		hcName,
+		"http",
+		rs.ServiceName,
+		rs.IPDomain.IPv4,
+		3, 2, rs.Port, 5, 3, 
+	)
+
+	want := &HealthCheckConfig{
+		HCName: hcName,
+		RealService: []RealService{
+			{ServiceName: rs.ServiceName},
+		},
+		Type:           "http",
+		HcUp:           3,
+		HcDown:         2,
+		IP:             IP{IPv4: rs.IPDomain.IPv4},
+		Port:           rs.Port,
+		SendInterval:   5,
+		ServerTimeout:  3, 
+		HCHttpRequest:  DefaultHttpRequest,
+		HCHttpResponse: DefaultHttpResponse,
+	}
+
+	if !reflect.DeepEqual(config, want) {
+		t.Errorf("NewHealthCheckConfig() = %v, want %v", config, want)
+	}
+}
+
+func TestHealthCheckConfig_CreateHealthCheck(t *testing.T) {
+	rs := createTestRealService(t)
+	defer cleanupTestRealService(t, rs)
+
+	hcName := "hc-service1"
+
+	config := &HealthCheckConfig{
+		HCName: hcName,
+		RealService: []RealService{
+			{ServiceName: rs.ServiceName},
+		},
+		Type:           "http",
+		HcUp:           3,
+		HcDown:         2,
+		IP:             IP{IPv4: rs.IPDomain.IPv4},
+		Port:           rs.Port,
+		SendInterval:   5,
+		ServerTimeout:  3, 
+		HCHttpRequest:  DefaultHttpRequest,
+		HCHttpResponse: DefaultHttpResponse,
+	}
+
+	if err := config.CreateHealthCheck(); err != nil {
+		t.Fatalf("CreateHealthCheck() failed: %v", err)
+	}
+}
+
+func TestHealthCheckConfig_UpdateHealthCheck(t *testing.T) {
+	rs := createTestRealService(t)
+	defer cleanupTestRealService(t, rs)
+
+	hcName := "hc-service1"
+
+	// ✅ First create the health check to avoid 404 error during update
+	config := &HealthCheckConfig{
+		HCName: hcName,
+		RealService: []RealService{
+			{ServiceName: rs.ServiceName},
+		},
+		Type:           "http",
+		HcUp:           3,
+		HcDown:         2,
+		IP:             IP{IPv4: rs.IPDomain.IPv4},
+		Port:           rs.Port,
+		SendInterval:   5,
+		ServerTimeout:  3, 
+		HCHttpRequest:  DefaultHttpRequest,
+		HCHttpResponse: DefaultHttpResponse,
+	}
+
+	if err := config.CreateHealthCheck(); err != nil {
+		t.Fatalf("CreateHealthCheck() failed: %v", err)
+	}
+
+	// ✅ Update health check
+	config.HcUp = 5
+	if err := config.UpdateHealthCheck(); err != nil {
+		t.Fatalf("UpdateHealthCheck() failed: %v", err)
+	}
+}
+
+func TestHealthCheckConfig_DeleteHealthCheck(t *testing.T) {
+	rs := createTestRealService(t)
+	defer cleanupTestRealService(t, rs)
+
+	hcName := "hc-service1"
+
+	
+	config := &HealthCheckConfig{
+		HCName: hcName,
+		RealService: []RealService{
+			{ServiceName: rs.ServiceName},
+		},
+		Type:           "http",
+		HcUp:           3,
+		HcDown:         2,
+		IP:             IP{IPv4: rs.IPDomain.IPv4},
+		Port:           rs.Port,
+		SendInterval:   5,
+		ServerTimeout:  3, 
+		HCHttpRequest:  DefaultHttpRequest,
+		HCHttpResponse: DefaultHttpResponse,
+	}
+
+	if err := config.CreateHealthCheck(); err != nil {
+		t.Fatalf("CreateHealthCheck() failed: %v", err)
+	}
+
+	// ✅ Delete health check with correct name
+	if err := config.DeleteHealthCheck(); err != nil {
+		t.Fatalf("DeleteHealthCheck() failed: %v", err)
+	}
+}
Index: /branches/main/array-ingress-controller/apvadapter/annotations/ratelimit/const.go
===================================================================
--- /branches/main/array-ingress-controller/apvadapter/annotations/ratelimit/const.go	(nonexistent)
+++ /branches/main/array-ingress-controller/apvadapter/annotations/ratelimit/const.go	(working copy)
@@ -0,0 +1,5 @@
+package ratelimit
+
+const (
+	HttpServiceType = "http"
+)
Index: /branches/main/array-ingress-controller/apvadapter/annotations/ratelimit/go.mod
===================================================================
--- /branches/main/array-ingress-controller/apvadapter/annotations/ratelimit/go.mod	(nonexistent)
+++ /branches/main/array-ingress-controller/apvadapter/annotations/ratelimit/go.mod	(working copy)
@@ -0,0 +1,23 @@
+module arraynetworks.com/array-ingress-controller/apvclient/ratelimit
+
+go 1.23.6
+
+replace (
+	arraynetworks.com/array-ingress-controller/apvclient => ../../apvclient
+	arraynetworks.com/array-ingress-controller/configstore => ../../../configstore
+	arraynetworks.com/array-ingress-controller/logger => ../../../logger
+)
+
+require (
+	arraynetworks.com/array-ingress-controller/apvclient v0.0.0-00010101000000-000000000000
+	arraynetworks.com/array-ingress-controller/logger v0.0.0-00010101000000-000000000000
+)
+
+require (
+	github.com/go-resty/resty/v2 v2.16.5 // indirect
+	go.uber.org/multierr v1.10.0 // indirect
+	go.uber.org/zap v1.27.0 // indirect
+	golang.org/x/net v0.33.0 // indirect
+	gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+)
Index: /branches/main/array-ingress-controller/apvadapter/annotations/ratelimit/go.sum
===================================================================
--- /branches/main/array-ingress-controller/apvadapter/annotations/ratelimit/go.sum	(nonexistent)
+++ /branches/main/array-ingress-controller/apvadapter/annotations/ratelimit/go.sum	(working copy)
@@ -0,0 +1,44 @@
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
+github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
+github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
+github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
+github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
+github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
+github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
+github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
+github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=
+github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=
+github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
+github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
+go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
+go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
+golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
+golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
+golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
+golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
+golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
+golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
+golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Index: /branches/main/array-ingress-controller/apvadapter/annotations/ratelimit/ratelimit.go
===================================================================
--- /branches/main/array-ingress-controller/apvadapter/annotations/ratelimit/ratelimit.go	(nonexistent)
+++ /branches/main/array-ingress-controller/apvadapter/annotations/ratelimit/ratelimit.go	(working copy)
@@ -0,0 +1,173 @@
+package ratelimit
+
+import (
+	"encoding/json"
+
+	"arraynetworks.com/array-ingress-controller/apvadapter/apvclient"
+	"arraynetworks.com/array-ingress-controller/apvadapter/apvurlbuilder"
+	"arraynetworks.com/array-ingress-controller/logger"
+	"github.com/go-resty/resty/v2"
+)
+
+var (
+	log  logger.Logger
+	resp *resty.Response
+)
+
+func init() {
+	log = logger.GetLogger()
+}
+
+type RateLimitManager interface {
+	CreateRateLimit() error
+	UpdateRateLimit() error
+	DeleteRateLimit() error
+}
+
+type RateLimitConfig struct {
+	ServiceName          string `json:"service_name"`
+	MaxCPS               int    `json:"max_cps"`
+	SoftBandwidth        int    `json:"soft_bandwidth"`
+	HardBandwidth        int    `json:"hard_bandwidth"`
+	ServerConnReuse      bool   `json:"server_conn_reuse"`
+	MaxReq               int    `json:"max_req"`
+	Timeout              int    `json:"time_out"`
+	ServerPersistentConn bool   `json:"server_persist"`
+	Protocol             string `json:"protocol"`
+}
+
+type RatelimitUpdateParams struct {
+	MaxCPS               int  `json:"max_cps"`
+	SoftBandwidth        int  `json:"soft_bandwidth"`
+	HardBandwidth        int  `json:"hard_bandwidth"`
+	MaxReq               int  `json:"max_req,omitempty"`
+	Timeout              int  `json:"time_out,omitempty"`
+	ServerConnReuse      bool `json:"server_conn_reuse,omitempty"`
+	ServerPersistentConn bool `json:"server_persist,omitempty"`
+}
+
+func NewRateLimitConfig(serviceName, protocol string, maxCPS, softBW, hardBW, maxReq, timeout int, serverConnReuse, serverPersistentConn bool) *RateLimitConfig {
+	return &RateLimitConfig{
+		ServiceName:          serviceName,
+		Protocol:             protocol,
+		MaxCPS:               maxCPS,
+		SoftBandwidth:        softBW,
+		HardBandwidth:        hardBW,
+		MaxReq:               maxReq,
+		Timeout:              timeout,
+		ServerConnReuse:      serverConnReuse,
+		ServerPersistentConn: serverPersistentConn,
+	}
+}
+
+func (rl *RateLimitConfig) CreateRateLimit() error {
+	client := apvclient.GetAPVClient()
+
+	ratelimitUpdatePayload := RatelimitUpdateParams{
+		MaxCPS:        rl.MaxCPS,
+		SoftBandwidth: rl.SoftBandwidth,
+		HardBandwidth: rl.HardBandwidth,
+	}
+
+	if rl.Protocol == "http" {
+		ratelimitUpdatePayload.MaxReq = rl.MaxReq
+		ratelimitUpdatePayload.Timeout = rl.Timeout
+		ratelimitUpdatePayload.ServerConnReuse = rl.ServerConnReuse
+		ratelimitUpdatePayload.ServerPersistentConn = rl.ServerPersistentConn
+	}
+
+	jsonData, err := json.Marshal(ratelimitUpdatePayload)
+
+	if err != nil {
+		log.Errorf("Error marshalling rate limit JSON: %v", err)
+		return err
+	}
+
+	annotationURL := apvurlbuilder.GetAnnotationURL(apvurlbuilder.BaseURL["RealService"], rl.ServiceName)
+	resp, err = client.Update(annotationURL, jsonData)
+	if err != nil {
+		log.Errorf("Error creating rate limit: %v", err)
+		return err
+	}
+	log.Infof("Response Body: %s", resp.String())
+	log.Infof("Response Status Code: %v", resp.StatusCode())
+	log.Infof("Ratelimit Created Successfully")
+
+	return nil
+}
+
+func (rl *RateLimitConfig) UpdateRateLimit() error {
+	client := apvclient.GetAPVClient()
+
+	ratelimitUpdatePayload := RatelimitUpdateParams{
+		MaxCPS:        rl.MaxCPS,
+		SoftBandwidth: rl.SoftBandwidth,
+		HardBandwidth: rl.HardBandwidth,
+	}
+
+	if rl.Protocol == "http" {
+		ratelimitUpdatePayload.MaxReq = rl.MaxReq
+		ratelimitUpdatePayload.Timeout = rl.Timeout
+		ratelimitUpdatePayload.ServerConnReuse = rl.ServerConnReuse
+		ratelimitUpdatePayload.ServerPersistentConn = rl.ServerPersistentConn
+	}
+
+	jsonData, err := json.Marshal(ratelimitUpdatePayload)
+	if err != nil {
+		log.Errorf("Error marshalling rate limit JSON: %v", err)
+		return err
+	}
+
+	annotationURL := apvurlbuilder.GetAnnotationURL(apvurlbuilder.BaseURL["RealService"], rl.ServiceName)
+	resp, err = client.Update(annotationURL, jsonData)
+	if err != nil {
+		log.Errorf("Error updating rate limit: %v", err)
+		return err
+	}
+	log.Infof("Response Body: %s", resp.String())
+	log.Infof("Response Status Code: %v", resp.StatusCode())
+	log.Infof("Ratelimit Updated Successfully")
+	return nil
+}
+
+func (rl *RateLimitConfig) DeleteRateLimit() error {
+	client := apvclient.GetAPVClient()
+
+	var defaultRateLimitPayload RatelimitUpdateParams
+
+	if rl.Protocol == "http" {
+		defaultRateLimitPayload = RatelimitUpdateParams{
+			MaxCPS:               1,
+			SoftBandwidth:        1000,
+			HardBandwidth:        1500,
+			MaxReq:               0,
+			Timeout:              180,
+			ServerConnReuse:      true,
+			ServerPersistentConn: true,
+		}
+	} else {
+		defaultRateLimitPayload = RatelimitUpdateParams{
+			MaxCPS:        1,
+			SoftBandwidth: 1000,
+			HardBandwidth: 1500,
+		}
+	}
+
+	jsonData, err := json.Marshal(defaultRateLimitPayload)
+	if err != nil {
+		log.Errorf("Error marshalling default rate limit JSON: %v", err)
+		return err
+	}
+
+	annotationURL := apvurlbuilder.GetAnnotationURL(apvurlbuilder.BaseURL["RealService"], rl.ServiceName)
+	resp, err = client.Update(annotationURL, jsonData)
+	if err != nil {
+		log.Errorf("Error resetting rate limit: %v", err)
+		return err
+	}
+	log.Infof("Response Body: %s", resp.String())
+	log.Infof("Response Status Code: %v", resp.StatusCode())
+	log.Infof("Ratelimit Deleted Successfully")
+
+	return nil
+}
Index: /branches/main/array-ingress-controller/apvadapter/annotations/ratelimit/ratelimit_test.go
===================================================================
--- /branches/main/array-ingress-controller/apvadapter/annotations/ratelimit/ratelimit_test.go	(nonexistent)
+++ /branches/main/array-ingress-controller/apvadapter/annotations/ratelimit/ratelimit_test.go	(working copy)
@@ -0,0 +1,167 @@
+/*
+   Copyright 2025 Array Networks
+*/
+
+package ratelimit
+
+import (
+	"testing"
+	"arraynetworks.com/array-ingress-controller/apvadapter/realservice"
+)
+
+func createTestRealService(t *testing.T, name, ip, protocol string, port int, enabled bool) *realservice.RealService {
+	rs := realservice.NewRealService(name, ip, protocol, port, enabled)
+	err := rs.CreateRealService()
+	if err != nil {
+		t.Fatalf("Failed to create real service: %v", err)
+	}
+	return rs
+}
+
+func cleanupTestRealService(t *testing.T, rs *realservice.RealService) {
+	err := rs.DeleteRealService()
+	if err != nil {
+		t.Fatalf("Failed to delete real service: %v", err)
+	}
+}
+
+func TestRateLimitConfig_CreateRateLimit(t *testing.T) {
+	rs_http := createTestRealService(t, "service-http", "192.168.1.100", "HTTP", 80, true)
+	defer cleanupTestRealService(t, rs_http)
+
+	rs_tcp := createTestRealService(t, "service-tcp", "192.168.1.101", "TCP", 80, true)
+	defer cleanupTestRealService(t, rs_tcp)
+
+	tests := []struct {
+		name    string
+		config  *RateLimitConfig
+		wantErr bool
+	}{
+		{
+			name: "Create HTTP rate limit",
+			config: &RateLimitConfig{
+				ServiceName:          rs_http.ServiceName,
+				Protocol:             "http",
+				MaxCPS:               100,
+				SoftBandwidth:        1000,
+				HardBandwidth:        1500,
+				MaxReq:               10,
+				Timeout:              30,
+				ServerConnReuse:      true,
+				ServerPersistentConn: true,
+			},
+			wantErr: false,
+		},
+		{
+			name: "Create TCP rate limit",
+			config: &RateLimitConfig{
+				ServiceName:   rs_tcp.ServiceName,
+				Protocol:      "tcp",
+				MaxCPS:        200,
+				SoftBandwidth: 500,
+				HardBandwidth: 1000,
+				MaxReq:        5,
+				Timeout:       30,
+			},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := tt.config.CreateRateLimit()
+			if (err != nil) != tt.wantErr {
+				t.Errorf("RateLimitConfig.CreateRateLimit() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func TestRateLimitConfig_UpdateRateLimit(t *testing.T) {
+	rs_http := createTestRealService(t, "service-http", "192.168.1.100", "HTTP", 80, true)
+	defer cleanupTestRealService(t, rs_http)
+
+	rs_tcp := createTestRealService(t, "service-tcp", "192.168.1.101", "TCP", 80, true)
+	defer cleanupTestRealService(t, rs_tcp)
+
+	tests := []struct {
+		name    string
+		config  *RateLimitConfig
+		wantErr bool
+	}{
+		{
+			name: "Update HTTP rate limit",
+			config: &RateLimitConfig{
+				ServiceName:          rs_http.ServiceName,
+				Protocol:             "http",
+				MaxCPS:               200,
+				SoftBandwidth:        1500,
+				HardBandwidth:        2000,
+				MaxReq:               20,
+				Timeout:              60,
+				ServerConnReuse:      true,
+				ServerPersistentConn: true,
+			},
+			wantErr: false,
+		},
+		{
+			name: "Update TCP rate limit",
+			config: &RateLimitConfig{
+				ServiceName:   rs_tcp.ServiceName,
+				Protocol:      "tcp",
+				MaxCPS:        300,
+				SoftBandwidth: 700,
+				HardBandwidth: 1200,
+				MaxReq:        15,
+				Timeout:       60, 
+			},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := tt.config.UpdateRateLimit()
+			if (err != nil) != tt.wantErr {
+				t.Errorf("RateLimitConfig.UpdateRateLimit() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func TestRateLimitConfig_DeleteRateLimit(t *testing.T) {
+	rs_http := createTestRealService(t, "service-http", "192.168.1.100", "HTTP", 80, true)
+	defer cleanupTestRealService(t, rs_http)
+
+	rs_tcp := createTestRealService(t, "service-tcp", "192.168.1.101", "TCP", 80, true)
+	defer cleanupTestRealService(t, rs_tcp)
+
+	tests := []struct {
+		name    string
+		config  *RateLimitConfig
+		wantErr bool
+	}{
+		{
+			name: "Delete HTTP rate limit",
+			config: &RateLimitConfig{
+				ServiceName: rs_http.ServiceName,
+				Protocol:    "http",
+			},
+			wantErr: false,
+		},
+		{
+			name: "Delete TCP rate limit",
+			config: &RateLimitConfig{
+				ServiceName: rs_tcp.ServiceName,
+				Protocol:    "tcp",
+			},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := tt.config.DeleteRateLimit()
+			if (err != nil) != tt.wantErr {
+				t.Errorf("RateLimitConfig.DeleteRateLimit() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
Index: /branches/main/array-ingress-controller/apvadapter/apvclient/apvclient.go
===================================================================
--- /branches/main/array-ingress-controller/apvadapter/apvclient/apvclient.go	(nonexistent)
+++ /branches/main/array-ingress-controller/apvadapter/apvclient/apvclient.go	(working copy)
@@ -0,0 +1,27 @@
+/*
+	Copyright 2025 Array Networks
+*/
+
+package apvclient
+
+import (
+	"arraynetworks.com/array-ingress-controller/common/baseclient"
+)
+
+type APVClient struct {
+	*baseclient.BaseClient
+	instType string
+}
+
+var apvInstance *APVClient
+
+func GetAPVClient() *APVClient {
+	if apvInstance == nil || !apvInstance.IsHealthy("health-check-url") {
+		config := baseclient.LoadConfig("APV")
+		apvInstance = &APVClient{
+			BaseClient: baseclient.InitBaseClient("APV", config),
+			instType:   "APV",
+		}
+	}
+	return apvInstance
+}
Index: /branches/main/array-ingress-controller/apvadapter/apvclient/apvclient_test.go
===================================================================
--- /branches/main/array-ingress-controller/apvadapter/apvclient/apvclient_test.go	(nonexistent)
+++ /branches/main/array-ingress-controller/apvadapter/apvclient/apvclient_test.go	(working copy)
@@ -0,0 +1,33 @@
+/*
+	Copyright 2025 Array Networks
+*/
+
+package apvclient
+
+import (
+	"testing"
+)
+
+func TestGetAPVClient(t *testing.T) {
+	tests := []struct {
+		name string
+	}{
+		{name: "Initialize APVClient when instance is nil"},
+		{name: "Return existing APVClient instance when already initialized"},
+	}
+
+	// Reset the global instance before running tests
+	apvInstance = nil
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			client := GetAPVClient()
+			if client == nil {
+				t.Errorf("GetAPVClient() returned nil, expected a valid APVClient instance")
+			}
+			if client.instType != "APV" {
+				t.Errorf("GetAPVClient().instType = %v, want %v", client.instType, "APV")
+			}
+		})
+	}
+}
\ No newline at end of file
Index: /branches/main/array-ingress-controller/apvadapter/apvclient/go.mod
===================================================================
--- /branches/main/array-ingress-controller/apvadapter/apvclient/go.mod	(nonexistent)
+++ /branches/main/array-ingress-controller/apvadapter/apvclient/go.mod	(working copy)
@@ -0,0 +1,18 @@
+module arraynetworks.com/array-ingress-controller/apvclient
+
+go 1.23.6
+
+replace arraynetworks.com/array-ingress-controller/logger => ../../logger
+
+require (
+	arraynetworks.com/array-ingress-controller/logger v0.0.0-00010101000000-000000000000
+	github.com/go-resty/resty/v2 v2.16.5
+)
+
+require (
+	go.uber.org/multierr v1.10.0 // indirect
+	go.uber.org/zap v1.27.0 // indirect
+	golang.org/x/net v0.33.0 // indirect
+	gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+)
Index: /branches/main/array-ingress-controller/apvadapter/apvclient/go.sum
===================================================================
--- /branches/main/array-ingress-controller/apvadapter/apvclient/go.sum	(nonexistent)
+++ /branches/main/array-ingress-controller/apvadapter/apvclient/go.sum	(working copy)
@@ -0,0 +1,44 @@
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
+github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
+github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
+github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
+github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
+github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
+github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
+github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
+github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=
+github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=
+github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
+github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
+go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
+go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
+golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
+golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
+golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
+golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
+golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
+golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
+golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Index: /branches/main/array-ingress-controller/apvadapter/apvurlbuilder/apvurlbuilder.go
===================================================================
--- /branches/main/array-ingress-controller/apvadapter/apvurlbuilder/apvurlbuilder.go	(nonexistent)
+++ /branches/main/array-ingress-controller/apvadapter/apvurlbuilder/apvurlbuilder.go	(working copy)
@@ -0,0 +1,23 @@
+/*
+	Copyright 2025 Array Networks
+*/
+
+package apvurlbuilder
+
+import (
+	"fmt"
+)
+
+// BaseURL for different annotations
+var BaseURL = map[string]string{
+	"RealService": "loadbalancing/slb/rs/RealService",
+	"HealthCheck": "loadbalancing/slb/healthcheck/types/HTTPHealthCheck",
+}
+
+// GetAnnotationURL generates a URL with the given base path and optional service name.
+func GetAnnotationURL(basePath string, serviceName ...string) string {
+	if len(serviceName) > 0 && serviceName[0] != "" {
+		return fmt.Sprintf("%s/%s", basePath, serviceName[0])
+	}
+	return basePath
+}
Index: /branches/main/array-ingress-controller/apvadapter/apvurlbuilder/apvurlbuilder_test.go
===================================================================
--- /branches/main/array-ingress-controller/apvadapter/apvurlbuilder/apvurlbuilder_test.go	(nonexistent)
+++ /branches/main/array-ingress-controller/apvadapter/apvurlbuilder/apvurlbuilder_test.go	(working copy)
@@ -0,0 +1,59 @@
+/*
+	Copyright 2025 Array Networks
+*/
+
+package apvurlbuilder
+
+import "testing"
+
+func TestGetAnnotationURL(t *testing.T) {
+	type args struct {
+		basePath    string
+		serviceName []string
+	}
+	tests := []struct {
+		name string
+		args args
+		want string
+	}{
+		{
+			name: "BasePathOnly",
+			args: args{
+				basePath:    "loadbalancing/slb/rs/RealService",
+				serviceName: []string{},
+			},
+			want: "loadbalancing/slb/rs/RealService",
+		},
+		{
+			name: "BasePathWithServiceName",
+			args: args{
+				basePath:    "loadbalancing/slb/rs/RealService",
+				serviceName: []string{"Service1"},
+			},
+			want: "loadbalancing/slb/rs/RealService/Service1",
+		},
+		{
+			name: "BasePathWithEmptyServiceName",
+			args: args{
+				basePath:    "loadbalancing/slb/rs/RealService",
+				serviceName: []string{""},
+			},
+			want: "loadbalancing/slb/rs/RealService",
+		},
+		{
+			name: "BasePathWithMultipleServiceNames",
+			args: args{
+				basePath:    "loadbalancing/slb/rs/RealService",
+				serviceName: []string{"Service1", "Service2"},
+			},
+			want: "loadbalancing/slb/rs/RealService/Service1",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := GetAnnotationURL(tt.args.basePath, tt.args.serviceName...); got != tt.want {
+				t.Errorf("GetAnnotationURL() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
Index: /branches/main/array-ingress-controller/apvadapter/apvurlbuilder/go.mod
===================================================================
--- /branches/main/array-ingress-controller/apvadapter/apvurlbuilder/go.mod	(nonexistent)
+++ /branches/main/array-ingress-controller/apvadapter/apvurlbuilder/go.mod	(working copy)
@@ -0,0 +1,3 @@
+module arraynetworks.com/array-ingress-controller/apvadapter/apvurlbuilder
+
+go 1.23.6
Index: /branches/main/array-ingress-controller/apvadapter/client.go
===================================================================
--- /branches/main/array-ingress-controller/apvadapter/client.go	(revision 11)
+++ /branches/main/array-ingress-controller/apvadapter/client.go	(nonexistent)
@@ -1,201 +0,0 @@
-/*
-	Copyright 2025 Array Networks
-
-*/
-
-package apvadapter
-
-import (
-    "crypto/tls"
-    "fmt"
-    "log"
-    "sync"
-
-    "github.com/go-resty/resty/v2"
-	"arraynetworks.com/array-ingress-controller/logger"
-)
-
-// Configuration holds settings for the ServiceClient
-type Configuration struct {
-    Username     string
-    Password     string
-    InsecureSkip bool
-}
-
-// Authenticator defines methods for setting up authentication
-type Authenticator interface {
-    Setup(request *resty.Request)
-}
-
-// BasicAuthenticator implements basic authentication
-type BasicAuthenticator struct {
-    Username string
-    Password string
-}
-
-func (a *BasicAuthenticator) Setup(request *resty.Request) {
-    request.SetBasicAuth(a.Username, a.Password)
-}
-
-// ServiceClient manages HTTP requests to a service
-type ServiceClient struct {
-    Client        *resty.Client
-    Authenticator Authenticator
-}
-
-var (
-    log            logger.Logger
-    clientInstance *ServiceClient
-    once           sync.Once
-    mu             sync.Mutex
-)
-
-func init() {
-    log = logger.GetLogger()
-}
-
-// LoadConfig loads configuration from environment variables
-func LoadConfig() Configuration {
-    insecureSkip, err := strconv.ParseBool(os.Getenv("SERVICE_INSECURE_SKIP"))
-    if err != nil {
-        log.Fatalf("Invalid value for SERVICE_INSECURE_SKIP: %v", err)
-    }
-
-    timeout, err := time.ParseDuration(os.Getenv("SERVICE_TIMEOUT"))
-    if err != nil {
-        log.Fatalf("Invalid value for SERVICE_TIMEOUT: %v", err)
-    }
-
-    return Configuration{
-        Username:     os.Getenv("SERVICE_USERNAME"),
-        Password:     os.Getenv("SERVICE_PASSWORD"),
-        InsecureSkip: insecureSkip,
-	Timeout:      timeout,
-    }
-}
-
-func initServiceClientWithPool(config Configuration) *ServiceClient {
-    client := resty.New()
-
-    // Configure the HTTP client's transport settings
-    client.SetTransport(&http.Transport{
-        TLSClientConfig: &tls.Config{
-            InsecureSkipVerify: config.InsecureSkip,
-        },
-        MaxIdleConns:        100,
-        MaxIdleConnsPerHost: 10,
-        IdleConnTimeout:     90 * time.Second,
-        DialContext: (&net.Dialer{
-            Timeout:   30 * time.Second,
-            KeepAlive: 30 * time.Second,
-        }).DialContext,
-    })
-
-    client.SetTimeout(config.Timeout)
-
-    authenticator := &BasicAuthenticator{
-        Username: config.Username,
-        Password: config.Password,
-    }
-
-    return &ServiceClient{
-        Client:        client,
-        Authenticator: authenticator,
-    }
-}
-
-// IsHealthy checks if the client is healthy
-func (s *ServiceClient) isHealthy() bool {
-    // Implement a simple health check, e.g., a ping request or checking last error state
-    resp, err := s.requestSetup().Get(url)
-    if err != nil || resp.StatusCode() != http.StatusOK {
-        return false
-    }
-    return true
-}
-
-// GetLogger return logger instance
-func GetClient() *ServiceClient {
-    if clientInstance == nil || !clientInstance.isHealthy() {
-        config := LoadConfig()
-        clientInstance = initServiceClientWithPool(config)
-    }
-    return clientInstance
-}
-
-// requestSetup sets up the request with authentication
-func (s *ServiceClient) requestSetup() *resty.Request {
-    request := s.Client.R()
-    s.Authenticator.Setup(request)
-    return request
-}
-
-// Fetch performs a GET request
-func (s *ServiceClient) Fetch(url string) error {
-    resp, err := s.requestSetup().Get(url)
-    if err != nil {
-        log.Printf("Error during GET request: %v", err)
-        return err
-    }
-
-    log.Printf("GET Response Status Code: %d", resp.StatusCode())
-    log.Printf("GET Response Body: %s", resp.String())
-
-    return nil
-}
-
-// Create performs a POST request
-func (s *ServiceClient) Create(url string, payload interface{}) error {
-    log.Printf("Request Payload: %v", payload)
-
-    resp, err := s.requestSetup().
-        SetBody(payload).
-        Post(url)
-    if err != nil {
-        log.Printf("Error during POST request: %v", err)
-        return err
-    }
-
-    log.Printf("POST Response Status Code: %d", resp.StatusCode())
-    log.Printf("POST Response Body: %s", resp.String())
-
-    return nil
-}
-
-// Delete performs a DELETE request with dynamic URL
-func (s *ServiceClient) Delete(urlTemplate string, serviceName string, payload interface{}) error {
-    url := fmt.Sprintf(urlTemplate, serviceName)
-    log.Printf("Dynamic URL: %s", url)
-
-    resp, err := s.requestSetup().
-        SetBody(payload).
-        Delete(url)
-    if err != nil {
-        log.Printf("Error during DELETE request: %v", err)
-        return err
-    }
-
-    log.Printf("DELETE Response Status Code: %d", resp.StatusCode())
-    log.Printf("DELETE Response Body: %s", resp.String())
-
-    return nil
-}
-
-// Patch performs a PATCH request with dynamic URL
-func (s *ServiceClient) Patch(urlTemplate string, serviceName string, payload interface{}) error {
-    url := fmt.Sprintf(urlTemplate, serviceName)
-    log.Printf("Dynamic URL: %s", url)
-
-    resp, err := s.requestSetup().
-        SetBody(payload).
-        Patch(url)
-    if err != nil {
-        log.Printf("Error during PATCH request: %v", err)
-        return err
-    }
-
-    log.Printf("PATCH Response Status Code: %d", resp.StatusCode())
-    log.Printf("PATCH Response Body: %s", resp.String())
-
-    return nil
-}
\ No newline at end of file
Index: /branches/main/array-ingress-controller/apvadapter/client_test.go
===================================================================
--- /branches/main/array-ingress-controller/apvadapter/client_test.go	(nonexistent)
+++ /branches/main/array-ingress-controller/apvadapter/client_test.go	(working copy)
@@ -0,0 +1,94 @@
+//go:build !production
+// +build !production
+
+package apvadapter_test
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"array-ingress-ctrl/apvadapter"
+	"array-ingress-ctrl/mocks"
+	"github.com/golang/mock/gomock"
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	"github.com/go-resty/resty/v2"
+)
+
+func TestServiceClient(t *testing.T) {
+	RegisterFailHandler(Fail)
+	RunSpecs(t, "ServiceClient Suite")
+}
+
+var _ = Describe("ServiceClient", func() {
+	var (
+		mockCtrl    *gomock.Controller
+		mockClient  *mocks.MockHttpClient
+		serviceClient *apvadapter.ServiceClient
+		testServer  *httptest.Server
+	)
+
+	BeforeEach(func() {
+		mockCtrl = gomock.NewController(GinkgoT())
+		mockClient = mocks.NewMockHttpClient(mockCtrl)
+		testServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+			w.WriteHeader(http.StatusOK)
+			w.Write([]byte(`{"message": "success"}`))
+		}))
+
+		serviceClient = &apvadapter.ServiceClient{
+			Client: resty.New(),
+			Authenticator: &apvadapter.BasicAuthenticator{
+				Username: "test-user",
+				Password: "test-pass",
+			},
+		}
+	})
+
+	AfterEach(func() {
+		mockCtrl.Finish()
+		testServer.Close()
+	})
+
+	Context("Fetch", func() {
+		It("should return success response", func() {
+			err := serviceClient.Fetch(testServer.URL)
+			Expect(err).To(BeNil())
+		})
+
+		It("should return an error when request fails", func() {
+			testServer.Close()
+			err := serviceClient.Fetch(testServer.URL)
+			Expect(err).ToNot(BeNil())
+		})
+	})
+
+	Context("Create", func() {
+		It("should successfully send a POST request", func() {
+			payload := map[string]string{"key": "value"}
+			body, _ := json.Marshal(payload)
+			err := serviceClient.Create(testServer.URL, bytes.NewBuffer(body))
+			Expect(err).To(BeNil())
+		})
+	})
+
+	Context("Delete", func() {
+		It("should successfully send a DELETE request", func() {
+			payload := map[string]string{"key": "value"}
+			err := serviceClient.Delete(testServer.URL+"/%s", "test-service", payload)
+			Expect(err).To(BeNil())
+		})
+	})
+
+	Context("Patch", func() {
+		It("should successfully send a PATCH request", func() {
+			payload := map[string]string{"key": "value"}
+			err := serviceClient.Patch(testServer.URL+"/%s", "test-service", payload)
+			Expect(err).To(BeNil())
+		})
+	})
+})
\ No newline at end of file
Index: /branches/main/array-ingress-controller/apvadapter/cmd.go
===================================================================
--- /branches/main/array-ingress-controller/apvadapter/cmd.go	(revision 11)
+++ /branches/main/array-ingress-controller/apvadapter/cmd.go	(working copy)
@@ -1,18 +1,19 @@
 /*
-	Copyright 2025 Array Networks
-
+   Copyright 2025 Array Networks
 */
 
 package apvadapter
 
 import (
+	"os"
 	"time"
 
-	"gorm.io/gorm"
-
+	"arraynetworks.com/array-ingress-controller/apvadapter/annotations/health"
+	"arraynetworks.com/array-ingress-controller/apvadapter/annotations/ratelimit"
+	"arraynetworks.com/array-ingress-controller/apvadapter/realservice"
 	"arraynetworks.com/array-ingress-controller/configstore"
 	"arraynetworks.com/array-ingress-controller/logger"
-	"arraynetworks.com/array-ingress-controller/apvadapter/realservice"
+	"arraynetworks.com/array-ingress-controller/watchtower"
 )
 
 var (
@@ -23,11 +24,20 @@
 	log = logger.GetLogger()
 }
 
-// fetchServices retrieves services from the DB where status = 'WAITING'
-func fetchServices() []configstore.Service {
-	var services []configstore.Service
-	dbclient := configstore.Get()
-	if err := dbclient.db.Where("status = ?", "WAITING").Find(&services).Error; err != nil {
+// FetchServices fetches all services with status = 'WAITING' from the database
+func FetchServices() []configstore.RealService {
+	log.Info("Fetching realservices...")
+
+	var services []configstore.RealService
+	dbclient, err := configstore.Get()
+	if dbclient == nil {
+		log.Info("DB Client is NIL")
+		return nil
+	}
+
+	// Fetch only services with status = 'WAITING'
+	err = dbclient.ReadByStatus("WAITING", &services)
+	if err != nil {
 		log.Errorf("Error fetching services: %v", err)
 		return nil
 	}
@@ -35,14 +45,90 @@
 	return services
 }
 
-// processService processes each service by calling realservice functions
-func processService(svc *configstore.Service) {
-	dbclient := configstore.Get()
-	const maxRetries = 5
+// FetchHealthCheck fetches all healthchecks with status = 'WAITING' from the database
+func FetchHealthCheck() []configstore.HealthCheck {
+	log.Info("Fetching Healthchecks...")
+
+	var healthchecks []configstore.HealthCheck
+	dbclient, err := configstore.Get()
+	if dbclient == nil {
+		log.Info("DB Client is NIL")
+		return nil
+	}
+
+	// Fetch only healthchecks with status = 'WAITING'
+	err = dbclient.ReadByStatus("WAITING", &healthchecks)
+	if err != nil {
+		log.Errorf("Error fetching healthchecks: %v", err)
+		return nil
+	}
+
+	return healthchecks
+}
+
+// FetchRateLimit fetches all ratelimits with status = 'WAITING' from the database
+func FetchRateLimit() []configstore.RateLimit {
+	log.Info("Fetching RateLimits...")
+
+	var rateLimits []configstore.RateLimit
+	dbclient, err := configstore.Get()
+	if dbclient == nil {
+		log.Info("DB Client is NIL")
+		return nil
+	}
+
+	// Fetch only ratelimits with status = 'WAITING'
+	err = dbclient.ReadByStatus("WAITING", &rateLimits)
+	if err != nil {
+		log.Errorf("Error fetching ratelimits: %v", err)
+		return nil
+	}
+
+	return rateLimits
+}
+
+// ProcessService processes each service by calling realservice functions
+func ProcessService(svc *configstore.RealService) {
+	dbclient, err := configstore.Get()
+	if dbclient == nil {
+		log.Error("DB Client is NIL")
+		return
+	}
+
+	const MaxRetries = 5
 	retryCount := 0
 
+	if os.Getenv("TEST") == "true" {
+		log.Info("TEST environment detected, setting APV availability to true")
+		available := true
+		if !available {
+			log.Error("APV is not available")
+			return
+		} else {
+			log.Info("APV is available")
+		}
+	} else {
+		select {
+		case available := <-watchtower.GetDeviceStatusChan():
+			if !available {
+				log.Error("APV is not available")
+				return
+			} else {
+				log.Info("APV is available")
+			}
+		case <-time.After(30 * time.Second):
+			log.Warn("APV status not received")
+			return
+		}
+	}
+
 	// Update status to INPROGRESS
-	if err := dbclient.db.Model(svc).Update("status", "INPROGRESS").Error; err != nil {
+	conditions := map[string]interface{}{
+		"service_name": svc.ServiceName,
+		// Add more conditions as needed
+	}
+	svc.Status = "INPROGRESS"
+	if err = dbclient.Update(svc, conditions); err != nil {
 		log.Errorf("Failed to update status to INPROGRESS for service %s: %v", svc.ServiceName, err)
 		return
 	}
@@ -56,56 +142,454 @@
 		svc.Enable,
 	)
 
-	var err error
-
-	// Retry loop
-	for retryCount < maxRetries {
+	// Retry loop for create, update, or delete
+	for retryCount < MaxRetries {
 		switch svc.CurrentOp {
-		case "create":
+		case "CREATE":
 			err = rs.CreateRealService()
-		case "update":
+		case "UPDATE":
 			err = rs.UpdateRealService()
-		case "delete":
+		case "DELETE":
 			err = rs.DeleteRealService()
 		default:
 			log.Errorf("Invalid operation %s for service %s", svc.CurrentOp, svc.ServiceName)
-			dbclient.db.Model(svc).Update("status", "FAILED")
+			svc.Status = "FAILED"
+			_ = dbclient.Update(svc, conditions)
+			MarkLinkedResourcesAsFailed(svc.ServiceName)
 			return
 		}
 
 		if err == nil {
-			// Success: Mark as COMPLETED and return
-			if err := dbclient.db.Model(svc).Update("status", "COMPLETED").Error; err != nil {
+			// Success → Update status to COMPLETED
+			svc.Status = "COMPLETED"
+			if err := dbclient.Update(svc, conditions); err != nil {
 				log.Errorf("Failed to update status to COMPLETED for service %s: %v", svc.ServiceName, err)
 			}
+			if svc.CurrentOp == "DELETE" {
+				//When RealService is successfully deleted then the Ratelimit and HealthCheck
+				// which is associated with the RealService are deleted automatically by APV
+				// Hence there is no need to delete them individually after deleteing a Realservice
+
+				// Removes Associated Healthcheck with the RealService
+				removeAssociatedHealthChecks(svc.ServiceName)
+				// Removes Associated Ratelimit with the RealService
+				removeAssociatedRateLimits(svc.ServiceName)
+				//Removes The realservice Entry by itself
+				removeRealService(svc.ServiceName)
+				log.Infof("Deleted Associated resources for Realservice %s", svc.ServiceName)
+			}
 			return
 		}
 
 		// Log the error and retry
-		log.Errorf("Error processing service %s (attempt %d/%d): %v", svc.ServiceName, retryCount+1, maxRetries, err)
+		log.Errorf("Error processing service %s (attempt %d/%d): %v", svc.ServiceName, retryCount+1, MaxRetries, err)
 		retryCount++
 		time.Sleep(5 * time.Second)
 	}
 
-	// If we reach max retries, update status to FAILED
-	if err := dbclient.db.Model(svc).Update("status", "FAILED").Error; err != nil {
+	// If we reach max retries → Mark as FAILED
+	svc.Status = "FAILED"
+	if err := dbclient.Update(svc, conditions); err != nil {
 		log.Errorf("Failed to update status to FAILED for service %s: %v", svc.ServiceName, err)
 	} else {
-		log.Errorf("Service %s failed after %d retries, marked as FAILED", svc.ServiceName, maxRetries)
+		log.Errorf("Service %s failed after %d retries, marked as FAILED", svc.ServiceName, MaxRetries)
+		MarkLinkedResourcesAsFailed(svc.ServiceName)
+	}
+
+}
+
+func ProcessHealthCheck(HChecks *configstore.HealthCheck) {
+	dbclient, err := configstore.Get()
+	if dbclient == nil {
+		log.Error("DB Client is NIL")
+		return
+	}
+
+	const MaxRetries = 5
+	retryCount := 0
+
+	if os.Getenv("TEST") == "true" {
+		log.Info("TEST environment detected, setting APV availability to true")
+		available := true
+		if !available {
+			log.Error("APV is not available")
+			return
+		} else {
+			log.Info("APV is available")
+		}
+	} else {
+		select {
+		case available := <-watchtower.GetDeviceStatusChan():
+			if !available {
+				log.Error("APV is not available")
+				return
+			} else {
+				log.Info("APV is available")
+			}
+		case <-time.After(30 * time.Second):
+			log.Warn("APV status not received")
+			return
+		}
+	}
+
+	// Update status to INPROGRESS
+	HChecks.Status = "INPROGRESS"
+	conditions := map[string]interface{}{
+		"hc_name": HChecks.HCName,
+		// Add more conditions as needed
+	}
+	if err = dbclient.Update(HChecks, conditions); err != nil {
+		log.Errorf("Failed to update status to INPROGRESS for healthcheck %s: %v", HChecks.HCName, err)
+		return
+	}
+
+	hc := health.NewHealthCheckConfig(
+		HChecks.HCName,
+		HChecks.Type,
+		HChecks.ServiceName,
+		HChecks.IPv4,
+		HChecks.HcUp,
+		HChecks.HcDown,
+		HChecks.Port,
+		HChecks.SendInterval,
+		HChecks.ServerTimeout,
+	)
+
+	// Retry loop for create, update, or delete
+	for retryCount < MaxRetries {
+		switch HChecks.CurrentOp {
+		case "CREATE":
+			err = hc.CreateHealthCheck()
+		case "UPDATE":
+			err = hc.UpdateHealthCheck()
+		case "DELETE":
+			err = hc.DeleteHealthCheck()
+		default:
+			log.Errorf("Invalid operation %s for healthcheck %s", HChecks.CurrentOp, HChecks.HCName)
+			HChecks.Status = "FAILED"
+			_ = dbclient.Update(HChecks, conditions)
+			return
+		}
+
+		if err == nil {
+			// Success → Update status to COMPLETED
+			HChecks.Status = "COMPLETED"
+			if err := dbclient.Update(HChecks, conditions); err != nil {
+				log.Errorf("Failed to update status to COMPLETED for healthcheck %s: %v", HChecks.HCName, err)
+			}
+			if HChecks.CurrentOp == "DELETE" {
+				//Remove HealthCheck entry after deletion process
+				removeHealthCheck(HChecks.ServiceName)
+				log.Infof("Deleted HealthCheck record after successful deletion %s", HChecks.ServiceName)
+			}
+			return
+		}
+
+		// Log the error and retry
+		log.Errorf("Error processing healthcheck %s (attempt %d/%d): %v", HChecks.HCName, retryCount+1, MaxRetries, err)
+		retryCount++
+		time.Sleep(5 * time.Second)
+	}
+
+	// If we reach max retries → Mark as FAILED
+	HChecks.Status = "FAILED"
+	if err := dbclient.Update(HChecks, conditions); err != nil {
+		log.Errorf("Failed to update status to FAILED for healthcheck %s: %v", HChecks.HCName, err)
+	} else {
+		log.Errorf("Healthcheck %s failed after %d retries, marked as FAILED", HChecks.HCName, MaxRetries)
+	}
+}
+
+func ProcessRateLimit(rl *configstore.RateLimit) {
+	dbclient, err := configstore.Get()
+	if err != nil {
+		log.Errorf("Failed to get DB client: %v", err)
+		return
+	}
+	if dbclient == nil {
+		log.Error("DB Client is NIL")
+		return
+	}
+
+	const MaxRetries = 5
+	retryCount := 0
+
+	// Update status to INPROGRESS
+	rl.Status = "INPROGRESS"
+	conditions := map[string]interface{}{
+		"service_name": rl.ServiceName,
+		// Add more conditions as needed
+	}
+	if err = dbclient.Update(rl, conditions); err != nil {
+		log.Errorf("Failed to update status to INPROGRESS for ratelimit %s: %v", rl.ServiceName, err)
+		return
+	}
+
+	rl1 := ratelimit.NewRateLimitConfig(
+		rl.ServiceName,
+		rl.Protocol,
+		rl.MaxCPS,
+		rl.SoftBandwidth,
+		rl.HardBandwidth,
+		rl.MaxReq,
+		rl.Timeout,
+		rl.ServerConnReuse,
+		rl.ServerPersistentConn,
+	)
+
+	// Retry loop for create, update, or delete
+	for retryCount < MaxRetries {
+		switch rl.CurrentOp {
+		case "CREATE":
+			err = rl1.CreateRateLimit()
+		case "UPDATE":
+			err = rl1.UpdateRateLimit()
+		case "DELETE":
+			err = rl1.DeleteRateLimit()
+		default:
+			log.Errorf("Invalid operation %s for ratelimit %s", rl.CurrentOp, rl.ServiceName)
+			rl.Status = "FAILED"
+			_ = dbclient.Update(rl, conditions)
+			return
+		}
+
+		if err == nil {
+			// Success → Update status to COMPLETED
+			rl.Status = "COMPLETED"
+			if err := dbclient.Update(rl, conditions); err != nil {
+				log.Errorf("Failed to update status to COMPLETED for ratelimit %s: %v", rl.ServiceName, err)
+			}
+			if rl.CurrentOp == "DELETE" {
+				//Remove Ratelimit entry after deletion process
+				removeRateLimit(rl.ServiceName)
+				log.Infof("Deleted Ratelimit record after successful deletion %s", rl.ServiceName)
+			}
+			return
+		}
+
+		// Log the error and retry
+		log.Errorf("Error processing ratelimit %s (attempt %d/%d): %v", rl.ServiceName, retryCount+1, MaxRetries, err)
+		retryCount++
+		time.Sleep(5 * time.Second)
+	}
+
+	// If we reach max retries → Mark as FAILED
+	rl.Status = "FAILED"
+	if err := dbclient.Update(rl, conditions); err != nil {
+		log.Errorf("Failed to update status to FAILED for ratelimit %s: %v", rl.ServiceName, err)
+	} else {
+		log.Errorf("Ratelimit %s failed after %d retries, marked as FAILED", rl.ServiceName, MaxRetries)
 	}
 }
 
 // StartReconcile continuously processes services in the queue
 func StartReconcile() {
 	for {
-		svcs := fetchServices()
-		if svcs == nil {
-			time.Sleep(5 * time.Second)
-			continue
+		log.Info("Reconciling....")
+
+		svcs := FetchServices()
+		if len(svcs) == 0 {
+			log.Info("No RealServices in db to process")
+		} else {
+			for i := range svcs {
+				svc := &svcs[i]
+				log.Infof("Processing RealService: %s", svc.ServiceName)
+				ProcessService(svc)
+			}
 		}
-		for i := range svcs {
-			go processService(&svcs[i])
+
+		hcs := FetchHealthCheck()
+		if len(hcs) == 0 {
+			log.Info("No HealthChecks")
+		} else {
+			for i := range hcs {
+				hc := &hcs[i]
+				log.Infof("Processing HealthCheck: %s", hc.HCName)
+				ProcessHealthCheck(hc)
+			}
 		}
-		time.Sleep(5 * time.Second)
+
+		rls := FetchRateLimit()
+		if len(rls) == 0 {
+			log.Info("No RateLimits in db to process")
+		} else {
+			for i := range rls {
+				rl := &rls[i]
+				log.Infof("Processing RateLimit: %s", rl.ServiceName)
+				ProcessRateLimit(rl)
+			}
+		}
+
+		time.Sleep(120 * time.Second)
+	}
+}
+
+//UTILS functions
+
+func removeAssociatedHealthChecks(serviceName string) {
+	dbclient, err := configstore.Get()
+	if err != nil {
+		log.Errorf("Error getting DB client: %v", err)
+		return
+	}
+	if dbclient == nil {
+		log.Error("DB Client is NIL")
+		return
+	}
+
+	log.Infof("Removing health checks associated with service %s", serviceName)
+
+	// Define condition for deletion
+	conditions := map[string]interface{}{
+		"service_name": serviceName,
+	}
+
+	// Call the structured delete function with retries
+	if deleteErr := dbclient.Delete(&configstore.HealthCheck{}, conditions); deleteErr != nil {
+		log.Errorf("Failed to remove health checks for service %s: %v", serviceName, deleteErr)
+	} else {
+		log.Infof("Successfully removed health checks for service %s", serviceName)
+	}
+}
+
+func removeAssociatedRateLimits(serviceName string) {
+	dbclient, err := configstore.Get()
+	if err != nil {
+		log.Errorf("Error getting DB client: %v", err)
+		return
+	}
+	if dbclient == nil {
+		log.Error("DB Client is NIL")
+		return
+	}
+
+	log.Infof("Removing rate limits associated with service %s", serviceName)
+
+	// Define deletion conditions based on service name
+	conditions := map[string]interface{}{
+		"service_name": serviceName,
+	}
+
+	// Use your ConfigStore.Delete method with the provided model and conditions.
+	if delErr := dbclient.Delete(&configstore.RateLimit{}, conditions); delErr != nil {
+		log.Errorf("Failed to remove rate limits for service %s: %v", serviceName, delErr)
+	} else {
+		log.Infof("Successfully removed rate limits for service %s", serviceName)
+	}
+}
+
+// Helper function to mark linked HealthChecks and RateLimits as FAILED
+func MarkLinkedResourcesAsFailed(serviceName string) {
+	dbclient, err := configstore.Get()
+	if dbclient == nil {
+		log.Error("DB Client is NIL")
+		return
+	}
+
+	// Fetch and update linked HealthChecks
+	var healthChecks []configstore.HealthCheck
+	err = dbclient.ReadByCondition(map[string]interface{}{"service_name": serviceName}, &healthChecks)
+	if err != nil {
+		log.Errorf("Failed to fetch linked HealthChecks for service %s: %v", serviceName, err)
+	} else {
+		for _, hc := range healthChecks {
+			hc.Status = "FAILED"
+			if err := dbclient.Update(&hc, map[string]interface{}{"hc_name": hc.HCName}); err != nil {
+				log.Errorf("Failed to update status to FAILED for HealthCheck %s: %v", hc.HCName, err)
+			} else {
+				log.Infof("Marked HealthCheck %s as FAILED due to service %s failure", hc.HCName, serviceName)
+			}
+		}
+	}
+
+	// Fetch and update linked RateLimits
+	var rateLimits []configstore.RateLimit
+	err = dbclient.ReadByCondition(map[string]interface{}{"service_name": serviceName}, &rateLimits)
+	if err != nil {
+		log.Errorf("Failed to fetch linked RateLimits for service %s: %v", serviceName, err)
+	} else {
+		for _, rl := range rateLimits {
+			rl.Status = "FAILED"
+			if err := dbclient.Update(&rl, map[string]interface{}{"service_name": rl.ServiceName}); err != nil {
+				log.Errorf("Failed to update status to FAILED for RateLimit %s: %v", rl.ServiceName, err)
+			} else {
+				log.Infof("Marked RateLimit %s as FAILED due to service %s failure", rl.ServiceName, serviceName)
+			}
+		}
+	}
+}
+
+// removeHealthCheck deletes all health checks associated with the given service name.
+func removeHealthCheck(serviceName string) {
+	dbclient, err := configstore.Get()
+	if err != nil {
+		log.Errorf("Error getting DB client: %v", err)
+		return
+	}
+	if dbclient == nil {
+		log.Error("DB Client is NIL")
+		return
+	}
+
+	log.Infof("Removing health checks associated with service %s", serviceName)
+
+	conditions := map[string]interface{}{
+		"service_name": serviceName,
+	}
+
+	if deleteErr := dbclient.Delete(&configstore.HealthCheck{}, conditions); deleteErr != nil {
+		log.Errorf("Failed to remove health checks for service %s: %v", serviceName, deleteErr)
+	} else {
+		log.Infof("Successfully removed health checks for service %s", serviceName)
+	}
+}
+
+// removeRateLimit deletes all rate limits associated with the given service name.
+func removeRateLimit(serviceName string) {
+	dbclient, err := configstore.Get()
+	if err != nil {
+		log.Errorf("Error getting DB client: %v", err)
+		return
+	}
+	if dbclient == nil {
+		log.Error("DB Client is NIL")
+		return
+	}
+
+	log.Infof("Removing rate limits associated with service %s", serviceName)
+
+	conditions := map[string]interface{}{
+		"service_name": serviceName,
+	}
+
+	if deleteErr := dbclient.Delete(&configstore.RateLimit{}, conditions); deleteErr != nil {
+		log.Errorf("Failed to remove rate limits for service %s: %v", serviceName, deleteErr)
+	} else {
+		log.Infof("Successfully removed rate limits for service %s", serviceName)
+	}
+}
+
+// removeRealService deletes all real services associated with the given service name.
+func removeRealService(serviceName string) {
+	dbclient, err := configstore.Get()
+	if err != nil {
+		log.Errorf("Error getting DB client: %v", err)
+		return
+	}
+	if dbclient == nil {
+		log.Error("DB Client is NIL")
+		return
+	}
+
+	log.Infof("Removing real services associated with service %s", serviceName)
+
+	conditions := map[string]interface{}{
+		"service_name": serviceName,
+	}
+
+	if deleteErr := dbclient.Delete(&configstore.RealService{}, conditions); deleteErr != nil {
+		log.Errorf("Failed to remove real services for service %s: %v", serviceName, deleteErr)
+	} else {
+		log.Infof("Successfully removed real services for service %s", serviceName)
 	}
 }
Index: /branches/main/array-ingress-controller/apvadapter/cmd_test.go
===================================================================
--- /branches/main/array-ingress-controller/apvadapter/cmd_test.go	(nonexistent)
+++ /branches/main/array-ingress-controller/apvadapter/cmd_test.go	(working copy)
@@ -0,0 +1,110 @@
+package apvadapter
+
+import (
+	"os"
+	"reflect"
+	"testing"
+	"time"
+
+	"arraynetworks.com/array-ingress-controller/configstore"
+	"arraynetworks.com/array-ingress-controller/logger"
+	"gorm.io/driver/sqlite"
+	"gorm.io/gorm"
+)
+
+func resetDatabase() {
+	os.Remove("test_db.db")
+	db, err := gorm.Open(sqlite.Open("test_db.db"), &gorm.Config{})
+	if err != nil {
+		panic("Failed to create a fresh database")
+	}
+
+	// Drop existing tables to ensure a clean slate
+	db.Migrator().DropTable(&configstore.RealService{}, &configstore.HealthCheck{})
+
+	// Recreate the tables
+	db.AutoMigrate(&configstore.RealService{}, &configstore.HealthCheck{})
+
+	// Add dummy data
+	addDummyData(db)
+}
+
+func addDummyData(db *gorm.DB) {
+	realServices := []configstore.RealService{
+		{IngressName: "ingress1", ServiceName: "service1", Protocol: "http", IPAddress: "192.168.1.1", Port: 8080, Enable: true, Status: "WAITING", CurrentOp: "CREATE"},
+	}
+	healthChecks := []configstore.HealthCheck{
+		{IngressName: "ingress1", HCName: "hc1", Type: "http", ServiceName: "service1", IPv4: "192.168.1.1", Port: 8080, HcUp: 5, HcDown: 1, SendInterval: 10, ServerTimeout: 4, Retries: 3, Status: "WAITING", CurrentOp: "CREATE"},
+	}
+	db.Create(&realServices)
+	db.Create(&healthChecks)
+}
+
+func showDB() {
+	db, err := gorm.Open(sqlite.Open("test_db.db"), &gorm.Config{})
+	if err != nil {
+		panic("Failed to open the database")
+	}
+
+	var realServices []configstore.RealService
+	var healthChecks []configstore.HealthCheck
+
+	// Fetch all RealService records
+	if err := db.Find(&realServices).Error; err != nil {
+		panic("Failed to fetch RealService records")
+	}
+
+	// Fetch all HealthCheck records
+	if err := db.Find(&healthChecks).Error; err != nil {
+		panic("Failed to fetch HealthCheck records")
+	}
+
+	// Log the contents of the database
+	logger.Info("RealService Records:", realServices)
+	logger.Info("HealthCheck Records:", healthChecks)
+}
+
+func init() {
+	resetDatabase()
+}
+
+func TestFetchServices(t *testing.T) {
+	got := FetchServices()
+	logger.Info("Database contents while fetching service: ")
+	showDB()
+	expected := []configstore.RealService{{IngressName: "ingress1", ServiceName: "service1", Protocol: "http", IPAddress: "192.168.1.1", Port: 8080, Enable: true, Status: "WAITING", CurrentOp: "CREATE"}}
+	if !reflect.DeepEqual(got, expected) {
+		t.Errorf("FetchServices() = %v, want %v", got, expected)
+	}
+}
+
+func TestFetchHealthCheck(t *testing.T) {
+	got := FetchHealthCheck()
+	logger.Info("Database contents while fetching health: ")
+	showDB()
+	expected := []configstore.HealthCheck{{IngressName: "ingress1", HCName: "hc1", Type: "http", ServiceName: "service1", IPv4: "192.168.1.1", Port: 8080, HcUp: 5, HcDown: 1, SendInterval: 10, ServerTimeout: 4, Retries: 3, Status: "WAITING", CurrentOp: "CREATE"}}
+	if !reflect.DeepEqual(got, expected) {
+		t.Errorf("FetchHealthCheck() = %v, want %v", got, expected)
+	}
+}
+
+func TestProcessService(t *testing.T) {
+	svc := &configstore.RealService{IngressName: "ingress1", ServiceName: "service1", Protocol: "http", IPAddress: "192.168.1.1", Port: 8080, Enable: true, Status: "WAITING", CurrentOp: "CREATE"}
+	ProcessService(svc)
+	logger.Info("Database contents after processing realservice: ")
+	showDB()
+}
+
+func TestProcessHealthCheck(t *testing.T) {
+	hc := &configstore.HealthCheck{HCName: "hc1", Type: "http", ServiceName: "service1", IPv4: "192.168.1.1", Port: 8080, HcUp: 5, HcDown: 1, SendInterval: 10, ServerTimeout: 4, Retries: 3, Status: "WAITING", CurrentOp: "CREATE"}
+	ProcessHealthCheck(hc)
+	logger.Info("Database contents after processing health: ")
+	showDB()
+}
+
+func TestStartReconcile(t *testing.T) {
+	t.Run("Start Reconcile", func(t *testing.T) {
+		go StartReconcile()
+		time.Sleep(2 * time.Second)
+	})
+}
Index: /branches/main/array-ingress-controller/apvadapter/consts.go
===================================================================
--- /branches/main/array-ingress-controller/apvadapter/consts.go	(nonexistent)
+++ /branches/main/array-ingress-controller/apvadapter/consts.go	(working copy)
@@ -0,0 +1,5 @@
+package apvadapter
+
+const (
+	MaxRetries = 5
+)
\ No newline at end of file
Index: /branches/main/array-ingress-controller/apvadapter/go.mod
===================================================================
--- /branches/main/array-ingress-controller/apvadapter/go.mod	(revision 11)
+++ /branches/main/array-ingress-controller/apvadapter/go.mod	(working copy)
@@ -4,6 +4,7 @@
 
 replace (
 	arraynetworks.com/array-ingress-controller/apvadapter/realservice => ./realservice
+	arraynetworks.com/array-ingress-controller/apvclient => ./apvclient
 	arraynetworks.com/array-ingress-controller/configstore => ../configstore
 	arraynetworks.com/array-ingress-controller/logger => ../logger
-)
\ No newline at end of file
+)
Index: /branches/main/array-ingress-controller/apvadapter/realservice/go.mod
===================================================================
--- /branches/main/array-ingress-controller/apvadapter/realservice/go.mod	(nonexistent)
+++ /branches/main/array-ingress-controller/apvadapter/realservice/go.mod	(working copy)
@@ -0,0 +1,27 @@
+module arraynetworks.com/array-ingress-controller/apvadapter/realservice
+
+go 1.23.6
+
+replace (
+	arraynetworks.com/array-ingress-controller/apvclient => ../apvclient
+	arraynetworks.com/array-ingress-controller/configstore => ../../configstore
+	arraynetworks.com/array-ingress-controller/logger => ../../logger
+)
+
+require (
+	arraynetworks.com/array-ingress-controller/apvclient v0.0.0-00010101000000-000000000000
+	arraynetworks.com/array-ingress-controller/logger v0.0.0-00010101000000-000000000000
+	gorm.io/gorm v1.25.12
+)
+
+require (
+	github.com/go-resty/resty/v2 v2.16.5 // indirect
+	github.com/jinzhu/inflection v1.0.0 // indirect
+	github.com/jinzhu/now v1.1.5 // indirect
+	go.uber.org/multierr v1.10.0 // indirect
+	go.uber.org/zap v1.27.0 // indirect
+	golang.org/x/net v0.33.0 // indirect
+	golang.org/x/text v0.21.0 // indirect
+	gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+)
Index: /branches/main/array-ingress-controller/apvadapter/realservice/go.sum
===================================================================
--- /branches/main/array-ingress-controller/apvadapter/realservice/go.sum	(nonexistent)
+++ /branches/main/array-ingress-controller/apvadapter/realservice/go.sum	(working copy)
@@ -0,0 +1,50 @@
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
+github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
+github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
+github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
+github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
+github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
+github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
+github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
+github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=
+github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=
+github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
+github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
+go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
+go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
+golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
+golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
+golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
+golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
+golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
+golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
+golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
+gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
Index: /branches/main/array-ingress-controller/apvadapter/realservice/realservice.go
===================================================================
--- /branches/main/array-ingress-controller/apvadapter/realservice/realservice.go	(nonexistent)
+++ /branches/main/array-ingress-controller/apvadapter/realservice/realservice.go	(working copy)
@@ -0,0 +1,116 @@
+/*
+	Copyright 2025 Array Networks
+*/
+
+package realservice
+
+import (
+	"encoding/json"
+	"strings"
+
+	"arraynetworks.com/array-ingress-controller/apvadapter/apvclient"
+	"arraynetworks.com/array-ingress-controller/apvadapter/apvurlbuilder"
+	"arraynetworks.com/array-ingress-controller/logger"
+	"github.com/go-resty/resty/v2"
+)
+
+var (
+	log  logger.Logger
+	resp *resty.Response
+)
+
+func init() {
+	log = logger.GetLogger()
+}
+
+type RealSvc interface {
+	CreateRealService() error
+	UpdateRealService() error
+	DeleteRealService() error
+}
+
+type RealService struct {
+	ServiceName string   `json:"service_name"`
+	IPDomain    IPDomain `json:"ip_domain"`
+	Port        int      `json:"port"`
+	Protocol    string   `json:"protocol"`
+	Enable      bool     `json:"enable"`
+}
+
+type UpdatePayload struct {
+	Port   int  `json:"port"`
+	Enable bool `json:"enable"`
+}
+type IPDomain struct {
+	IPv4 string `json:"ipv4"`
+}
+
+// NewRealService initializes and returns a RealService instance
+func NewRealService(instanceID, serverIP, protocol string, serverPort int, enable bool) *RealService {
+	return &RealService{
+		ServiceName: instanceID,
+		IPDomain:    IPDomain{serverIP},
+		Port:        serverPort,
+		Protocol:    strings.ToLower(protocol),
+		Enable:      enable,
+	}
+}
+
+func (rs *RealService) CreateRealService() error {
+	client := apvclient.GetAPVClient()
+	jsonData, err := json.Marshal(rs)
+	if err != nil {
+		log.Errorf("Error marshalling to JSON: %v", err)
+		return err
+	}
+	log.Infof("Creating real service: %s", rs.ServiceName)
+	annotationUrl := apvurlbuilder.GetAnnotationURL(apvurlbuilder.BaseURL["RealService"], "")
+	resp, err = client.Create(annotationUrl, jsonData)
+	if err != nil {
+		log.Errorf("Error creating real service: %v", err)
+		return err
+	}
+	log.Infof("Real service created successfully: %s", rs.ServiceName)
+	log.Infof("Response Body: %s", resp.String())
+	log.Infof("Response Status Code: %v", resp.StatusCode())
+	return nil
+}
+
+func (rs *RealService) UpdateRealService() error {
+	client := apvclient.GetAPVClient()
+	updatePayload := &UpdatePayload{
+		Port:   rs.Port,
+		Enable: rs.Enable,
+	}
+	jsonData, err := json.Marshal(updatePayload)
+	if err != nil {
+		log.Errorf("Error marshalling to JSON: %v", err)
+		return err
+	}
+	log.Infof("Updating real service: %s", rs.ServiceName)
+	annotationUrl := apvurlbuilder.GetAnnotationURL(apvurlbuilder.BaseURL["RealService"], rs.ServiceName)
+	resp, err = client.Update(annotationUrl, jsonData)
+	if err != nil {
+		log.Errorf("Error updating real service: %v", err)
+		return err
+	}
+	log.Infof("Real service updated successfully: %s", rs.ServiceName)
+	log.Infof("Response Body: %s", resp.String())
+	log.Infof("Response Status Code: %v", resp.StatusCode())
+	return nil
+}
+
+func (rs *RealService) DeleteRealService() error {
+	client := apvclient.GetAPVClient()
+	log.Infof("Deleting real service: %s", rs.ServiceName)
+	annotationUrl := apvurlbuilder.GetAnnotationURL(apvurlbuilder.BaseURL["RealService"], rs.ServiceName)
+	resp, err := client.Delete(annotationUrl)
+	if err != nil {
+		log.Errorf("Error deleting real service: %v", err)
+		return err
+	}
+	log.Infof("Real service deleted successfully: %s", rs.ServiceName)
+	log.Infof("Response Body: %s", resp.String())
+	log.Infof("Response Status Code: %v", resp.StatusCode())
+	return nil
+}
Index: /branches/main/array-ingress-controller/apvadapter/realservice/realservice_test.go
===================================================================
--- /branches/main/array-ingress-controller/apvadapter/realservice/realservice_test.go	(nonexistent)
+++ /branches/main/array-ingress-controller/apvadapter/realservice/realservice_test.go	(working copy)
@@ -0,0 +1,167 @@
+/*
+    Copyright 2025 Array Networks
+*/
+
+package realservice
+
+import (
+	"reflect"
+	"testing"
+)
+
+// Test NewRealService
+func TestNewRealService(t *testing.T) {
+	type args struct {
+		instanceID string
+		serverIP   string
+		protocol   string
+		serverPort int
+		enable     bool
+	}
+	tests := []struct {
+		name string
+		args args
+		want *RealService
+	}{
+		{
+			name: "Valid HTTP RealService",
+			args: args{
+				instanceID: "test1",
+				serverIP:   "192.168.1.100",
+				protocol:   "http",
+				serverPort: 80,
+				enable:     true,
+			},
+			want: &RealService{
+				ServiceName: "test1",
+				IPDomain:    IPDomain{IPv4: "192.168.1.100"},
+				Protocol:    "http",
+				Port:        80,
+				Enable:      true,
+			},
+		},
+		{
+			name: "Valid HTTPS RealService",
+			args: args{
+				instanceID: "test2",
+				serverIP:   "192.168.1.101",
+				protocol:   "https",
+				serverPort: 443,
+				enable:     true,
+			},
+			want: &RealService{
+				ServiceName: "test2",
+				IPDomain:    IPDomain{IPv4: "192.168.1.101"},
+				Protocol:    "https",
+				Port:        443,
+				Enable:      true,
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := NewRealService(tt.args.instanceID, tt.args.serverIP, tt.args.protocol, tt.args.serverPort, tt.args.enable)
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("NewRealService() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+// Test CreateRealService
+func TestRealService_CreateRealService(t *testing.T) {
+	tests := []struct {
+		name    string
+		service *RealService
+		wantErr bool
+	}{
+		{
+			name: "Create valid HTTP service",
+			service: &RealService{
+				ServiceName: "web-app-1",
+				IPDomain:    IPDomain{IPv4: "192.168.1.100"},
+				Port:        80,
+				Protocol:    "http",
+				Enable:      true,
+			},
+			wantErr: false,
+		},
+		{
+			name: "Create valid HTTPS service",
+			service: &RealService{
+				ServiceName: "web-app-2",
+				IPDomain:    IPDomain{IPv4: "192.168.1.101"},
+				Port:        443,
+				Protocol:    "https",
+				Enable:      true,
+			},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := tt.service.CreateRealService()
+			if (err != nil) != tt.wantErr {
+				t.Errorf("RealService.CreateRealService() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+// Test UpdateRealService
+func TestRealService_UpdateRealService(t *testing.T) {
+	tests := []struct {
+		name    string
+		service *RealService
+		wantErr bool
+	}{
+		{
+			name: "Update existing service",
+			service: &RealService{
+				ServiceName: "web-app-1",
+				IPDomain:    IPDomain{IPv4: "192.168.1.100"},
+				Port:        80,
+				Protocol:    "http",
+				Enable:      true,
+			},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := tt.service.UpdateRealService()
+			if (err != nil) != tt.wantErr {
+				t.Errorf("RealService.UpdateRealService() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+// Test DeleteRealService
+func TestRealService_DeleteRealService(t *testing.T) {
+	tests := []struct {
+		name    string
+		service *RealService
+		wantErr bool
+	}{
+		{
+			name: "Delete existing service",
+			service: &RealService{
+				ServiceName: "web-app-1",
+				IPDomain:    IPDomain{IPv4: "192.168.1.100"},
+				Port:        80,
+				Protocol:    "http",
+				Enable:      true,
+			},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := tt.service.DeleteRealService()
+			if (err != nil) != tt.wantErr {
+				t.Errorf("RealService.DeleteRealService() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
Index: /branches/main/array-ingress-controller/asfadapter/README.md
===================================================================
--- /branches/main/array-ingress-controller/asfadapter/README.md	(nonexistent)
+++ /branches/main/array-ingress-controller/asfadapter/README.md	(working copy)
@@ -0,0 +1,20 @@
+## Steps to Run the Test
+
+### 1. Set Environment Variables
+Run the following command in the terminal within the project directory:
+
+```sh
+export ASF_INSECURE_SKIP=true
+export ASF_TIMEOUT=30s
+export ASF_USERNAME=rest
+export ASF_PASSWORD=QwertY@0192
+export ASF_IP=43.204.194.91
+export TEST="true"
+```
+
+### 2. Execute Tests
+Run the tests using the following command:
+
+```sh
+go test -v
+```
\ No newline at end of file
Index: /branches/main/array-ingress-controller/asfadapter/asfclient/asfclient.go
===================================================================
--- /branches/main/array-ingress-controller/asfadapter/asfclient/asfclient.go	(nonexistent)
+++ /branches/main/array-ingress-controller/asfadapter/asfclient/asfclient.go	(working copy)
@@ -0,0 +1,23 @@
+package asfclient
+
+import (
+	"arraynetworks.com/array-ingress-controller/common/baseclient"
+)
+
+type ASFClient struct {
+	*baseclient.BaseClient
+	instType string
+}
+
+var asfInstance *ASFClient
+
+func GetASFClient() *ASFClient {
+	if asfInstance == nil || !asfInstance.IsHealthy("health-check-url") {
+		config := baseclient.LoadConfig("ASF")
+		asfInstance = &ASFClient{
+			BaseClient: baseclient.InitBaseClient("ASF", config),
+			instType:   "ASF",
+		}
+	}
+	return asfInstance
+}
Index: /branches/main/array-ingress-controller/asfadapter/asfclient/asfclient_test.go
===================================================================
--- /branches/main/array-ingress-controller/asfadapter/asfclient/asfclient_test.go	(nonexistent)
+++ /branches/main/array-ingress-controller/asfadapter/asfclient/asfclient_test.go	(working copy)
@@ -0,0 +1,33 @@
+/*
+	Copyright 2025 Array Networks
+*/
+
+package asfclient
+
+import (
+	"testing"
+)
+
+func TestGetASFClient(t *testing.T) {
+	tests := []struct {
+		name string
+	}{
+		{name: "Initialize ASFClient when instance is nil"},
+		{name: "Return existing ASFClient instance when already initialized"},
+	}
+
+	// Reset the global instance before running tests
+	asfInstance = nil
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			client := GetASFClient()
+			if client == nil {
+				t.Errorf("GetASFClient() returned nil, expected a valid ASFClient instance")
+			}
+			if client.instType != "ASF" {
+				t.Errorf("GetASFClient().instType = %v, want %v", client.instType, "ASF")
+			}
+		})
+	}
+}
\ No newline at end of file
Index: /branches/main/array-ingress-controller/asfadapter/asfurlbuilder/asfurlbuilder.go
===================================================================
--- /branches/main/array-ingress-controller/asfadapter/asfurlbuilder/asfurlbuilder.go	(nonexistent)
+++ /branches/main/array-ingress-controller/asfadapter/asfurlbuilder/asfurlbuilder.go	(working copy)
@@ -0,0 +1,22 @@
+/*
+	Copyright 2025 Array Networks
+*/
+
+package asfurlbuilder
+
+import (
+	"fmt"
+)
+
+// BaseURL for different annotations
+var BaseURL = map[string]string{
+	"RealService":    "/rest/asf/cli_extend",
+}
+
+// GetAnnotationURL generates a URL with the given base path and service name.
+func GetAnnotationURL(basePath, serviceName string) string {
+	if serviceName != "" {
+		return fmt.Sprintf("%s/%s", basePath, serviceName)
+	}
+	return basePath
+}
\ No newline at end of file
Index: /branches/main/array-ingress-controller/asfadapter/asfurlbuilder/asfurlbuilder_test.go
===================================================================
--- /branches/main/array-ingress-controller/asfadapter/asfurlbuilder/asfurlbuilder_test.go	(nonexistent)
+++ /branches/main/array-ingress-controller/asfadapter/asfurlbuilder/asfurlbuilder_test.go	(working copy)
@@ -0,0 +1,59 @@
+/*
+	Copyright 2025 Array Networks
+*/
+
+package asfurlbuilder
+
+import "testing"
+
+func TestGetAnnotationURL(t *testing.T) {
+	type args struct {
+		basePath    string
+		serviceName string
+	}
+	tests := []struct {
+		name string
+		args args
+		want string
+	}{
+		{
+			name: "With serviceName",
+			args: args{
+				basePath:    "/rest/asf/cli_extend",
+				serviceName: "service1",
+			},
+			want: "/rest/asf/cli_extend/service1",
+		},
+		{
+			name: "Without serviceName",
+			args: args{
+				basePath:    "/rest/asf/cli_extend",
+				serviceName: "",
+			},
+			want: "/rest/asf/cli_extend",
+		},
+		{
+			name: "Empty basePath with serviceName",
+			args: args{
+				basePath:    "",
+				serviceName: "service1",
+			},
+			want: "/service1",
+		},
+		{
+			name: "Empty basePath and serviceName",
+			args: args{
+				basePath:    "",
+				serviceName: "",
+			},
+			want: "",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := GetAnnotationURL(tt.args.basePath, tt.args.serviceName); got != tt.want {
+				t.Errorf("GetAnnotationURL() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
Index: /branches/main/array-ingress-controller/asfadapter/cmd.go
===================================================================
--- /branches/main/array-ingress-controller/asfadapter/cmd.go	(nonexistent)
+++ /branches/main/array-ingress-controller/asfadapter/cmd.go	(working copy)
@@ -0,0 +1,142 @@
+package asfadapter
+
+import (
+	"sync"
+	"time"
+
+	"arraynetworks.com/array-ingress-controller/asfadapter/realservice"
+	"arraynetworks.com/array-ingress-controller/configstore"
+	log "arraynetworks.com/array-ingress-controller/logger"
+	"arraynetworks.com/array-ingress-controller/watchtower"
+)
+
+// fetchAsfServices fetches all AsfRealServices with status = 'WAITING' from the database
+func fetchAsfServices() []configstore.AsfRealService {
+	log.Info("Fetching AsfRealServices...")
+
+	var services []configstore.AsfRealService
+	dbclient, err := configstore.Get()
+	if dbclient == nil {
+		log.Error("DB Client is NIL")
+		return nil
+	}
+
+	err = dbclient.ReadByStatus("WAITING", &services)
+	if err != nil {
+		log.Errorf("Error fetching AsfRealServices: %v", err)
+		return nil
+	}
+
+	return services
+}
+
+// processService processes each AsfRealService
+func processService(svc *configstore.AsfRealService) {
+	dbclient, err := configstore.Get()
+	if dbclient == nil {
+		log.Error("DB Client is NIL")
+		return
+	}
+
+	const maxRetries = 5
+	retryCount := 0
+
+	select {
+	case available := <-watchtower.GetDeviceStatusChan():
+		if !available {
+			log.Error("ASF is not available")
+			return
+		} else {
+			log.Info("ASF is available")
+		}
+	case <-time.After(30 * time.Second):
+		log.Warn("ASF status not received")
+		return
+	}
+	
+
+
+	// Update status to INPROGRESS
+	conditions := map[string]interface{}{
+		"service_name": svc.ServiceName,
+	}
+	svc.Status = "INPROGRESS"
+	if err = dbclient.Update(svc, conditions); err != nil {
+		log.Errorf("Failed to update status to INPROGRESS for service %s: %v", svc.ServiceName, err)
+		return
+	}
+
+	rs := realservice.NewAsfRealService(
+		svc.ServiceName,
+		svc.ServiceType,
+		svc.IPv4,
+		svc.CheckType,
+		svc.Port,
+		svc.HCUp,
+		svc.HCDown,
+	)
+
+	for retryCount < maxRetries {
+		switch svc.CurrentOp {
+		case "CREATE":
+			err = rs.CreateRealService()
+		case "UPDATE":
+			err = rs.UpdateRealService()
+		case "DELETE":
+			err = rs.DeleteRealService()
+		default:
+			log.Errorf("Invalid operation %s for service %s", svc.CurrentOp, svc.ServiceName)
+			svc.Status = "FAILED"
+			_ = dbclient.Update(svc, conditions)
+			return
+		}
+
+		if err == nil {
+			svc.Status = "COMPLETED"
+			if err := dbclient.Update(svc, conditions); err != nil {
+				log.Errorf("Failed to update status to COMPLETED for service %s: %v", svc.ServiceName, err)
+			}
+			return
+		}
+
+		log.Errorf("Error processing service %s (attempt %d/%d): %v", svc.ServiceName, retryCount+1, maxRetries, err)
+		retryCount++
+		time.Sleep(5 * time.Second)
+	}
+
+	// Mark as FAILED after max retries
+	svc.Status = "FAILED"
+	if err := dbclient.Update(svc, conditions); err != nil {
+		log.Errorf("Failed to update status to FAILED for service %s: %v", svc.ServiceName, err)
+	} else {
+		log.Errorf("Service %s failed after %d retries, marked as FAILED", svc.ServiceName, maxRetries)
+	}
+}
+
+// StartReconcile continuously processes services in the queue
+func StartReconcile() {
+	for {
+		log.Info("Reconciling....")
+		svcs := fetchAsfServices()
+
+		if len(svcs) == 0 {
+			log.Info("No AsfRealServices in DB to process")
+			time.Sleep(120 * time.Second)
+			continue
+		}
+
+		var wg sync.WaitGroup
+
+		for i := range svcs {
+			wg.Add(1)
+			go func(svc *configstore.AsfRealService) {
+				defer wg.Done()
+				log.Infof("Processing AsfRealService: %s", svc.ServiceName)
+				processService(svc)
+			}(&svcs[i])
+		}
+
+		wg.Wait()
+		time.Sleep(120 * time.Second)
+	}
+}
Index: /branches/main/array-ingress-controller/asfadapter/cmd_test.go
===================================================================
--- /branches/main/array-ingress-controller/asfadapter/cmd_test.go	(nonexistent)
+++ /branches/main/array-ingress-controller/asfadapter/cmd_test.go	(working copy)
@@ -0,0 +1,127 @@
+package asfadapter
+
+import (
+	"os"
+	"reflect"
+	"testing"
+	"gorm.io/driver/sqlite"
+	"gorm.io/gorm"
+	"arraynetworks.com/array-ingress-controller/logger"
+	"arraynetworks.com/array-ingress-controller/configstore"
+)
+
+func resetDatabase() {
+	// Remove the database file if it exists
+	os.Remove("test_db.db")
+
+	// Open a fresh database
+	db, err := gorm.Open(sqlite.Open("test_db.db"), &gorm.Config{})
+	if err != nil {
+		panic("Failed to create a fresh database")
+	}
+
+	// Ensure table structure is recreated
+	err = db.AutoMigrate(&configstore.AsfRealService{})
+	if err != nil {
+		panic("Failed to migrate database structure")
+	}
+
+	// Add dummy data for AsfRealService
+	addDummyData(db)
+}
+
+func addDummyData(db *gorm.DB) {
+	// Adding dummy AsfRealService data
+	asfServices := []configstore.AsfRealService{
+		{IngressName: "ingress1", ServiceName: "service1", ServiceType: "http", IPv4: "192.168.1.1", Port: 8080, CheckType: "ping", HCUp: 5, HCDown: 0, Status: "WAITING", CurrentOp: "CREATE"},
+	}
+
+	// Insert the AsfRealService data into the database
+	for _, service := range asfServices {
+		if err := db.Create(&service).Error; err != nil {
+			panic("Failed to insert AsfRealService data")
+		}
+	}
+}
+
+func showDB() {
+	db, err := gorm.Open(sqlite.Open("test_db.db"), &gorm.Config{})
+	if err != nil {
+		panic("Failed to open the database")
+	}
+
+	var asfServicesrealServices []configstore.AsfRealService
+
+	// Fetch all RealService records
+	if err := db.Find(&asfServicesrealServices).Error; err != nil {
+		panic("Failed to fetch RealService records")
+	}
+
+	// Log the contents of the database
+	logger.Info("RealService Records:", asfServicesrealServices)
+}
+
+func init() {
+	// Set up the database for testing
+	resetDatabase()
+}
+
+func TestFetchAsfServices(t *testing.T) {
+	tests := []struct {
+		name string
+		want []configstore.AsfRealService
+	}{
+		{
+			name: "Fetch AsfRealService Data",
+			want: []configstore.AsfRealService{
+				{IngressName: "ingress1", ServiceName: "service1", ServiceType: "http", IPv4: "192.168.1.1", Port: 8080, CheckType: "ping", HCUp: 5, HCDown: 0, Status: "WAITING", CurrentOp: "CREATE"},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := fetchAsfServices()
+			logger.Info("Database contents while fetching service: ")
+			showDB()
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("fetchAsfServices() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestProcessService(t *testing.T) {
+	type args struct {
+		svc *configstore.AsfRealService
+	}
+	tests := []struct {
+		name string
+		args args
+	}{
+		{
+			name: "Process AsfRealService",
+			args: args{
+				svc: &configstore.AsfRealService{
+					IngressName: "ingress1", ServiceName: "service1", ServiceType: "http", IPv4: "192.168.1.1", Port: 8080, CheckType: "ping", HCUp: 5, HCDown: 0, Status: "WAITING", CurrentOp: "CREATE",
+				},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			processService(tt.args.svc)
+			logger.Info("Database contents while processing service: ")
+			showDB()
+		})
+	}
+}
+
+func TestStartReconcile(t *testing.T) {
+	t.Run("Start Reconcile", func(t *testing.T) {
+		go func() {
+			StartReconcile()
+		}()
+		// Allow some time for the reconcile loop to process
+		// This is a basic test to ensure no panics or errors occur
+	})
+}
Index: /branches/main/array-ingress-controller/asfadapter/consts.go
===================================================================
--- /branches/main/array-ingress-controller/asfadapter/consts.go	(nonexistent)
+++ /branches/main/array-ingress-controller/asfadapter/consts.go	(working copy)
@@ -0,0 +1,5 @@
+package asfadapter
+
+const (
+	MaxRetries = 5
+)
\ No newline at end of file
Index: /branches/main/array-ingress-controller/asfadapter/realservice/realservice.go
===================================================================
--- /branches/main/array-ingress-controller/asfadapter/realservice/realservice.go	(nonexistent)
+++ /branches/main/array-ingress-controller/asfadapter/realservice/realservice.go	(working copy)
@@ -0,0 +1,136 @@
+/*
+	Copyright 2025 Array Networks
+*/
+
+package realservice
+
+import (
+	"encoding/json"
+	"fmt"
+
+	"arraynetworks.com/array-ingress-controller/asfadapter/asfclient"
+	"arraynetworks.com/array-ingress-controller/asfadapter/asfurlbuilder"
+	"arraynetworks.com/array-ingress-controller/logger"
+)
+
+var (
+	log logger.Logger
+)
+
+func init() {
+	log = logger.GetLogger()
+}
+
+type RealSvc interface {
+	CreateRealService() error
+	UpdateRealService() error
+	DeleteRealService() error
+}
+
+type AsfRealService struct {
+	ServiceName string   `json:"service_name"`
+	ServiceType string   `json:"service_type"`
+	IPDomain    IPDomain `json:"ip_domain"`
+	Port        int      `json:"port"`
+	CheckType   string   `json:"check_type"`
+	HCUp        int      `json:"hc_up"`
+	HCDown      int      `json:"hc_down"`
+	Enable      bool     `json:"enable"`
+}
+
+type IPDomain struct {
+	IPv4 string `json:"ipv4"`
+}
+
+// NewAsfRealService initializes and returns an AsfRealService instance
+func NewAsfRealService(serviceName, serviceType, serverIP, checkType string, serverPort, hcUp, hcDown int) *AsfRealService {
+	return &AsfRealService{
+		ServiceName: serviceName,
+		ServiceType: serviceType,
+		IPDomain:    IPDomain{IPv4: serverIP},
+		Port:        serverPort,
+		CheckType:   checkType,
+		HCUp:        hcUp,
+		HCDown:      hcDown,
+	}
+}
+
+// GeneratePayload creates the command payload for the ASF API request
+func (rs *AsfRealService) GeneratePayload() ([]byte, error) {
+	cmd := fmt.Sprintf(
+		"security real service \"%s\" %s %s %d %s %d %d",
+		rs.ServiceName,
+		rs.ServiceType,
+		rs.IPDomain.IPv4,
+		rs.Port,
+		rs.CheckType,
+		rs.HCUp,
+		rs.HCDown,
+	)
+
+	payload := map[string]string{
+		"cmd": cmd,
+	}
+
+	jsonData, err := json.Marshal(payload)
+	if err != nil {
+		log.Errorf("Error marshalling to JSON: %v", err)
+		return nil, err
+	}
+
+	return jsonData, nil
+}
+
+// CreateRealService sends a POST request to create the real service
+func (rs *AsfRealService) CreateRealService() error {
+	client := asfclient.GetASFClient()
+	jsonData, err := rs.GeneratePayload()
+	if err != nil {
+		log.Errorf("Error generating payload: %v", err)
+		return err
+	}
+
+	annotationUrl := asfurlbuilder.GetAnnotationURL(asfurlbuilder.BaseURL["RealService"], "")
+	_, err = client.Create(annotationUrl, jsonData)
+	if err != nil {
+		log.Errorf("Error creating real service: %v", err)
+		return err
+	}
+
+	log.Infof("Successfully created real service: %s", rs.ServiceName)
+	return nil
+}
+
+// UpdateRealService sends a PUT request to update the real service
+func (rs *AsfRealService) UpdateRealService() error {
+	client := asfclient.GetASFClient()
+	jsonData, err := rs.GeneratePayload()
+	if err != nil {
+		log.Errorf("Error generating payload: %v", err)
+		return err
+	}
+
+	annotationUrl := asfurlbuilder.GetAnnotationURL(asfurlbuilder.BaseURL["RealService"], rs.ServiceName)
+	_, err = client.Update(annotationUrl, jsonData)
+	if err != nil {
+		log.Errorf("Error updating real service: %v", err)
+		return err
+	}
+
+	log.Infof("Successfully updated real service: %s", rs.ServiceName)
+	return nil
+}
+
+// DeleteRealService sends a DELETE request to remove the real service
+func (rs *AsfRealService) DeleteRealService() error {
+	client := asfclient.GetASFClient()
+	annotationUrl := asfurlbuilder.GetAnnotationURL(asfurlbuilder.BaseURL["RealService"], rs.ServiceName)
+	_, err := client.Delete(annotationUrl)
+	if err != nil {
+		log.Errorf("Error deleting real service: %v", err)
+		return err
+	}
+
+	log.Infof("Successfully deleted real service: %s", rs.ServiceName)
+	return nil
+}
Index: /branches/main/array-ingress-controller/asfadapter/realservice/realservice_test.go
===================================================================
--- /branches/main/array-ingress-controller/asfadapter/realservice/realservice_test.go	(nonexistent)
+++ /branches/main/array-ingress-controller/asfadapter/realservice/realservice_test.go	(working copy)
@@ -0,0 +1,191 @@
+/*
+    Copyright 2025 Array Networks
+*/
+
+package realservice
+
+import (
+	"reflect"
+	"testing"
+)
+
+// Test NewAsfRealService
+func TestNewAsfRealService(t *testing.T) {
+	type args struct {
+		serviceName string
+		serviceType string
+		serverIP    string
+		checkType   string
+		serverPort  int
+		hcUp        int
+		hcDown      int
+	}
+	tests := []struct {
+		name string
+		args args
+		want *AsfRealService
+	}{
+		{
+			name: "Valid HTTP AsfRealService",
+			args: args{
+				serviceName: "test1",
+				serviceType: "http",
+				serverIP:    "192.168.1.100",
+				checkType:   "tcp",
+				serverPort:  80,
+				hcUp:        2,
+				hcDown:      2,
+			},
+			want: &AsfRealService{
+				ServiceName: "test1",
+				ServiceType: "http",
+				IPDomain:    IPDomain{IPv4: "192.168.1.100"},
+				Port:        80,
+				CheckType:   "tcp",
+				HCUp:        2,
+				HCDown:      2,
+				Enable:      true,
+			},
+		},
+		{
+			name: "Valid HTTPS AsfRealService",
+			args: args{
+				serviceName: "test2",
+				serviceType: "https",
+				serverIP:    "192.168.1.101",
+				checkType:   "tcp",
+				serverPort:  443,
+				hcUp:        3,
+				hcDown:      3,
+			},
+			want: &AsfRealService{
+				ServiceName: "test2",
+				ServiceType: "https",
+				IPDomain:    IPDomain{IPv4: "192.168.1.101"},
+				Port:        443,
+				CheckType:   "tcp",
+				HCUp:        3,
+				HCDown:      3,
+				Enable:      true,
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := NewAsfRealService(tt.args.serviceName, tt.args.serviceType, tt.args.serverIP, tt.args.checkType, tt.args.serverPort, tt.args.hcUp, tt.args.hcDown)
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("NewAsfRealService() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+// Test CreateRealService
+func TestAsfRealService_CreateRealService(t *testing.T) {
+	tests := []struct {
+		name    string
+		service *AsfRealService
+		wantErr bool
+	}{
+		{
+			name: "Create valid HTTP service",
+			service: &AsfRealService{
+				ServiceName: "web-app-1",
+				IPDomain:    IPDomain{IPv4: "192.168.1.100"},
+				Port:        80,
+				ServiceType: "http",
+				CheckType:   "tcp",
+				HCUp:        2,
+				HCDown:      2,
+				Enable:      true,
+			},
+			wantErr: false,
+		},
+		{
+			name: "Create valid HTTPS service",
+			service: &AsfRealService{
+				ServiceName: "web-app-2",
+				IPDomain:    IPDomain{IPv4: "192.168.1.101"},
+				Port:        443,
+				ServiceType: "https",
+				CheckType:   "tcp",
+				HCUp:        3,
+				HCDown:      3,
+				Enable:      true,
+			},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := tt.service.CreateRealService()
+			if (err != nil) != tt.wantErr {
+				t.Errorf("AsfRealService.CreateRealService() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+// Test UpdateRealService
+func TestAsfRealService_UpdateRealService(t *testing.T) {
+	tests := []struct {
+		name    string
+		service *AsfRealService
+		wantErr bool
+	}{
+		{
+			name: "Update existing service",
+			service: &AsfRealService{
+				ServiceName: "web-app-1",
+				IPDomain:    IPDomain{IPv4: "192.168.1.100"},
+				Port:        80,
+				ServiceType: "http",
+				CheckType:   "tcp",
+				HCUp:        2,
+				HCDown:      2,
+				Enable:      true,
+			},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := tt.service.UpdateRealService()
+			if (err != nil) != tt.wantErr {
+				t.Errorf("AsfRealService.UpdateRealService() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+// Test DeleteRealService
+func TestAsfRealService_DeleteRealService(t *testing.T) {
+	tests := []struct {
+		name    string
+		service *AsfRealService
+		wantErr bool
+	}{
+		{
+			name: "Delete existing service",
+			service: &AsfRealService{
+				ServiceName: "web-app-1",
+				IPDomain:    IPDomain{IPv4: "192.168.1.100"},
+				Port:        80,
+				ServiceType: "http",
+				CheckType:   "tcp",
+				HCUp:        2,
+				HCDown:      2,
+				Enable:      true,
+			},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := tt.service.DeleteRealService()
+			if (err != nil) != tt.wantErr {
+				t.Errorf("AsfRealService.DeleteRealService() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
Index: /branches/main/array-ingress-controller/common/baseclient/README.md
===================================================================
--- /branches/main/array-ingress-controller/common/baseclient/README.md	(nonexistent)
+++ /branches/main/array-ingress-controller/common/baseclient/README.md	(working copy)
@@ -0,0 +1,22 @@
+## Steps to Run the Test
+
+### 1. Resolve Dependencies
+Run the following command to ensure all dependencies are installed (if needed):
+
+```sh
+go mod tidy
+```
+
+### 2. Set Environment Variable
+Export the required environment variable:
+
+```sh
+export TEST="true"
+```
+
+### 3. Execute Tests
+Run the tests using the following command:
+
+```sh
+go test -v
+```
\ No newline at end of file
Index: /branches/main/array-ingress-controller/common/baseclient/baseclient.go
===================================================================
--- /branches/main/array-ingress-controller/common/baseclient/baseclient.go	(nonexistent)
+++ /branches/main/array-ingress-controller/common/baseclient/baseclient.go	(working copy)
@@ -0,0 +1,184 @@
+package baseclient
+
+import (
+	"crypto/tls"
+	"fmt"
+	"net"
+	"net/http"
+	"os"
+	"strconv"
+	"sync"
+	"time"
+
+	"arraynetworks.com/array-ingress-controller/logger"
+	"github.com/go-resty/resty/v2"
+)
+
+// Client defines the interface for making HTTP requests.
+type Client interface {
+	Fetch(url string) error
+	Create(url string, payload interface{}) error
+	Delete(url string, payload interface{}) error
+	Patch(url string, payload interface{}) error
+	IsHealthy(url string) bool
+}
+
+// Configuration holds settings for the clients.
+type Configuration struct {
+	Username     string
+	Password     string
+	InsecureSkip bool
+	Timeout      time.Duration
+}
+
+// BaseClient implements Client and manages HTTP requests.
+type BaseClient struct {
+	client        *resty.Client
+	instType      string
+	authenticator Authenticator
+}
+
+type Authenticator struct {
+	Username string
+	Password string
+}
+
+var (
+	log  logger.Logger
+	once sync.Once
+)
+
+func init() {
+	log = logger.GetLogger()
+}
+
+// LoadConfig loads configuration from environment variables.
+func LoadConfig(prefix string) Configuration {
+	insecureSkip, err := strconv.ParseBool(os.Getenv(prefix + "_INSECURE_SKIP"))
+	if err != nil {
+		log.Fatalf("Invalid value for %s_INSECURE_SKIP: %v", prefix, err)
+	}
+	timeout, err := time.ParseDuration(os.Getenv(prefix + "_TIMEOUT"))
+	if err != nil {
+		log.Fatalf("Invalid value for %s_TIMEOUT: %v", prefix, err)
+	}
+	return Configuration{
+		Username:     os.Getenv(prefix + "_USERNAME"),
+		Password:     os.Getenv(prefix + "_PASSWORD"),
+		InsecureSkip: insecureSkip,
+		Timeout:      timeout,
+	}
+}
+
+// InitBaseClient initializes and returns a BaseClient instance.
+func InitBaseClient(instType string, config Configuration) *BaseClient {
+	client := resty.New()
+	client.SetTransport(&http.Transport{
+		TLSClientConfig:     &tls.Config{InsecureSkipVerify: config.InsecureSkip},
+		MaxIdleConns:        100,
+		MaxIdleConnsPerHost: 10,
+		IdleConnTimeout:     90 * time.Second,
+		DialContext:         (&net.Dialer{Timeout: 30 * time.Second, KeepAlive: 30 * time.Second}).DialContext,
+	})
+	client.SetTimeout(config.Timeout)
+
+	return &BaseClient{
+		client:   client,
+		instType: instType,
+		authenticator: Authenticator{
+			Username: config.Username,
+			Password: config.Password,
+		},
+	}
+}
+
+// requestSetup sets up the request with authentication.
+func (b *BaseClient) requestSetup() *resty.Request {
+	return b.client.R().SetBasicAuth(b.authenticator.Username, b.authenticator.Password)
+}
+
+// IsHealthy checks if the client is healthy.
+func (b *BaseClient) IsHealthy(url string) bool {
+	resp, err := b.requestSetup().Get(url)
+	return err == nil && resp.StatusCode() == http.StatusOK
+}
+
+// sendRequest is a generic HTTP request handler.
+func (b *BaseClient) sendRequest(method, url string, body interface{}) (*resty.Response, error) {
+	req := b.requestSetup()
+
+	if body != nil {
+		req.SetBody(body)
+	}
+
+	var resp *resty.Response
+	var err error
+
+	switch method {
+	case http.MethodGet:
+		resp, err = req.Get(url)
+	case http.MethodPost:
+		resp, err = req.Post(url)
+	case http.MethodDelete:
+		resp, err = req.Delete(url)
+	case http.MethodPatch:
+		resp, err = req.Patch(url)
+	case http.MethodPut:
+		resp, err = req.Put(url)
+	default:
+		return nil, fmt.Errorf("unsupported method: %s", method)
+	}
+
+	if err != nil {
+		log.Errorf("%s request failed: %v", method, err)
+		return nil, fmt.Errorf("%s request to %s failed: %v", method, url, err)
+	}
+
+	if resp.StatusCode() >= http.StatusBadRequest {
+		log.Errorf("%s request to %s returned status %d: %s", method, url, resp.StatusCode(), resp.String())
+		return nil, fmt.Errorf("%s request to %s returned status %d: %s", method, url, resp.StatusCode(), resp.String())
+	}
+
+	return resp, nil
+}
+
+// Client methods implementing the interface.
+func (b *BaseClient) Fetch(annotationUrl string) (*resty.Response, error) {
+	url := b.constructUrl(b.instType, annotationUrl)
+	return b.sendRequest(http.MethodGet, url, nil)
+}
+
+func (b *BaseClient) Create(annotationUrl string, body interface{}) (*resty.Response, error) {
+	url := b.constructUrl(b.instType, annotationUrl)
+	return b.sendRequest(http.MethodPost, url, body)
+}
+
+func (b *BaseClient) Delete(annotationUrl string) (*resty.Response, error) {
+	url := b.constructUrl(b.instType, annotationUrl)
+	return b.sendRequest(http.MethodDelete, url, nil)
+}
+
+func (b *BaseClient) Update(annotationUrl string, body interface{}) (*resty.Response, error) {
+	url := b.constructUrl(b.instType, annotationUrl)
+	return b.sendRequest(http.MethodPut, url, body)
+}
+
+// Dynamically constructs url for API calls based on the instance type fetched from the client
+// Uses Environment variables to set
+func (b *BaseClient) constructUrl(instType, annotationUrl string) string {
+	apvIP := os.Getenv("APV_IP")
+	asfIP := os.Getenv("ASF_IP")
+
+	baseURLs := map[string]string{
+		"APV": fmt.Sprintf("https://%s:9997/rest/apv/%%s", apvIP),
+		"ASF": fmt.Sprintf("https://%s:9997/rest/asf/%%s", asfIP),
+	}
+
+	baseURL, exists := baseURLs[instType]
+	if !exists {
+		log.Errorf("Unknown instance type: %s", instType)
+		return ""
+	}
+
+	return fmt.Sprintf(baseURL, annotationUrl)
+}
Index: /branches/main/array-ingress-controller/common/baseclient/baseclient_test.go
===================================================================
--- /branches/main/array-ingress-controller/common/baseclient/baseclient_test.go	(nonexistent)
+++ /branches/main/array-ingress-controller/common/baseclient/baseclient_test.go	(working copy)
@@ -0,0 +1,116 @@
+package baseclient
+
+import (
+	"crypto/tls"
+	"fmt"
+	"net"
+	"net/http"
+	"os"
+	"testing"
+	"time"
+
+	"github.com/go-resty/resty/v2"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestLoadConfig(t *testing.T) {
+	// Set environment variables
+	os.Setenv("APV_INSECURE_SKIP", "true")
+	os.Setenv("APV_TIMEOUT", "30s")
+	os.Setenv("APV_USERNAME", "testapi")
+	os.Setenv("APV_PASSWORD", "test@123")
+
+	// Load configuration
+	config := LoadConfig("APV")
+
+	// Assertions
+	assert.Equal(t, "testapi", config.Username)
+	assert.Equal(t, "test@123", config.Password)
+	assert.True(t, config.InsecureSkip)
+	assert.Equal(t, 30*time.Second, config.Timeout)
+}
+
+func TestInitBaseClient(t *testing.T) {
+	// Mock configuration
+	config := Configuration{
+		Username:     "testapi",
+		Password:     "test@123",
+		InsecureSkip: true,
+		Timeout:      30 * time.Second,
+	}
+
+	// Initialize BaseClient
+	client := InitBaseClient("APV", config)
+
+	// Assertions
+	assert.NotNil(t, client)
+	assert.Equal(t, "testapi", client.authenticator.Username)
+	assert.Equal(t, "test@123", client.authenticator.Password)
+	assert.NotNil(t, client.client)
+}
+
+func TestConstructUrl(t *testing.T) {
+	// Set environment variables
+	os.Setenv("APV_IP", "13.233.204.131")
+	os.Setenv("ASF_IP", "192.168.1.1")
+
+	// Mock configuration
+	config := Configuration{
+		Username:     "testapi",
+		Password:     "test@123",
+		InsecureSkip: true,
+		Timeout:      30 * time.Second,
+	}
+
+	// Initialize BaseClient
+	client := InitBaseClient("APV", config)
+
+	// Test URL construction
+	apvIP := os.Getenv("APV_IP")
+	url := client.constructUrl("APV", "rs/RealService")
+	expected := fmt.Sprintf("https://%s:9997/rest/apv/rs/RealService", apvIP)
+	assert.Equal(t, expected, url)
+}
+
+func TestIsHealthy(t *testing.T) {
+	// Mock configuration
+	config := Configuration{
+		Username:     "testapi",
+		Password:     "test@123",
+		InsecureSkip: true,
+		Timeout:      30 * time.Second,
+	}
+
+	// Initialize BaseClient
+	client := InitBaseClient("APV", config)
+
+	// Mock Resty client with a custom RoundTripper
+	client.client = resty.New()
+	client.client.SetTransport(&http.Transport{
+		DialContext: (&net.Dialer{
+			Timeout:   30 * time.Second,
+			KeepAlive: 30 * time.Second,
+		}).DialContext,
+		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+	})
+
+	// Replace the RoundTripper with a mock implementation
+	client.client.GetClient().Transport = roundTripperFunc(func(req *http.Request) (*http.Response, error) {
+		return &http.Response{
+			StatusCode: http.StatusOK,
+			Body:       http.NoBody,
+			Header:     make(http.Header),
+		}, nil
+	})
+
+	// Test IsHealthy
+	isHealthy := client.IsHealthy("https://13.233.204.131:9997/rest/apv")
+	assert.True(t, isHealthy)
+}
+
+// roundTripperFunc is a helper to mock http.RoundTripper
+type roundTripperFunc func(req *http.Request) (*http.Response, error)
+
+func (f roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) {
+	return f(req)
+}
Index: /branches/main/array-ingress-controller/common/go.mod
===================================================================
--- /branches/main/array-ingress-controller/common/go.mod	(nonexistent)
+++ /branches/main/array-ingress-controller/common/go.mod	(working copy)
@@ -0,0 +1,5 @@
+module arraynetworks.com/array-ingress-controller/common
+
+go 1.23.6
+
+
Index: /branches/main/array-ingress-controller/configstore/configstore.go
===================================================================
--- /branches/main/array-ingress-controller/configstore/configstore.go	(revision 11)
+++ /branches/main/array-ingress-controller/configstore/configstore.go	(working copy)
@@ -37,9 +37,9 @@
 
 // Create is a public method to create a new record
 func (cs *ConfigStore) Create(model interface{}) error {
-    cs.mu.Lock()
-    defer cs.mu.Unlock()
-    return RetryFunction(func() error {
+	cs.mu.Lock()
+	defer cs.mu.Unlock()
+	return RetryFunction(func() error {
 		return cs.DB.Create(model).Error
 	}, 3, 100*time.Millisecond, 1)
 }
@@ -71,20 +71,57 @@
 	}, 3, 100*time.Millisecond, 1)
 }
 
+// ReadByCondition retrieves records matching specific conditions
+func (cs *ConfigStore) ReadByCondition(conditions map[string]interface{}, model interface{}) error {
+	cs.mu.RLock()
+	defer cs.mu.RUnlock()
+	return RetryFunction(func() error {
+		return cs.DB.Where(conditions).Find(model).Error
+	}, 3, 100*time.Millisecond, 1)
+}
+
 // Update is a public method to update a record
 func (cs *ConfigStore) Update(model interface{}, conditions map[string]interface{}) error {
-    cs.mu.Lock()
-    defer cs.mu.Unlock()
-    return RetryFunction(func() error {
-        return cs.DB.Where(conditions).Save(model).Error 
-    }, 3, 100*time.Millisecond, 1)
+	cs.mu.Lock()
+	defer cs.mu.Unlock()
+	return RetryFunction(func() error {
+		return cs.DB.Where(conditions).Save(model).Error
+	}, 3, 100*time.Millisecond, 1)
 }
 
 // Delete is a public method to delete a record
 func (cs *ConfigStore) Delete(model interface{}, conditions map[string]interface{}) error {
-    cs.mu.Lock()
-    defer cs.mu.Unlock()
-    return RetryFunction(func() error {
-        return cs.DB.Where(conditions).Delete(model).Error
-    }, 3, 100*time.Millisecond, 1)
+	cs.mu.Lock()
+	defer cs.mu.Unlock()
+	return RetryFunction(func() error {
+		return cs.DB.Where(conditions).Delete(model).Error
+	}, 3, 100*time.Millisecond, 1)
+
+}
+
+// ReadByRealServiceName retrieves records matching a specific realservice name
+func (cs *ConfigStore) ReadByRealServiceName(realSvcName string, model interface{}) error {
+	cs.mu.RLock()
+	defer cs.mu.RUnlock()
+	return RetryFunction(func() error {
+		return cs.DB.Where("service_name = ?", realSvcName).Find(model).Error
+	}, 3, 100*time.Millisecond, 1)
+}
+
+// ReadHealthCheckName retrieves records matching a specific healthcheck name
+func (cs *ConfigStore) ReadHealthCheckName(hcName string, model interface{}) error {
+	cs.mu.RLock()
+	defer cs.mu.RUnlock()
+	return RetryFunction(func() error {
+		return cs.DB.Where("hc_name = ?", hcName).Find(model).Error
+	}, 3, 100*time.Millisecond, 1)
+}
+
+// ReadRateLimitByServiceName retrieves RateLimit records matching a specific service name
+func (cs *ConfigStore) ReadRateLimitByServiceName(serviceName string, model interface{}) error {
+	cs.mu.RLock()
+	defer cs.mu.RUnlock()
+	return RetryFunction(func() error {
+		return cs.DB.Where("service_name = ?", serviceName).Find(model).Error
+	}, 3, 100*time.Millisecond, 1)
 }
Index: /branches/main/array-ingress-controller/configstore/db.go
===================================================================
--- /branches/main/array-ingress-controller/configstore/db.go	(revision 11)
+++ /branches/main/array-ingress-controller/configstore/db.go	(working copy)
@@ -5,6 +5,7 @@
 package configstore
 
 import (
+	"os"
 	"sync"
 
 	"gorm.io/driver/sqlite"
@@ -42,9 +43,13 @@
 	mu.Lock()
 	var err error
 	defer mu.Unlock()
+	dbName := "/var/log/array.db"
+	// Check if TEST environment variable is set to "true"
+	if os.Getenv("TEST") == "true" {
+		dbName = "test_db.db"
+	}
 
 	if dbClient == nil || !isHealthy(dbClient) {
-		dbName := "/var/log/array.db"
 		dbClient, err = gorm.Open(sqlite.Open(dbName), &gorm.Config{})
 		if err != nil {
 			log.Errorf("DB client creation failed: %s", err)
Index: /branches/main/array-ingress-controller/configstore/models.go
===================================================================
--- /branches/main/array-ingress-controller/configstore/models.go	(revision 11)
+++ /branches/main/array-ingress-controller/configstore/models.go	(working copy)
@@ -33,9 +33,9 @@
 	Port          int    `gorm:"column:port;not null" json:"port"`
 	SendInterval  int    `gorm:"column:send_interval;not null" json:"send_interval"`
 	ServerTimeout int    `gorm:"column:server_timeout;not null" json:"server_timeout"`
-	Retries 	  int    `gorm:"column:retries;not null" json:"retries"`
-	Status      string `gorm:"column:status;not null"`
-	CurrentOp   string `gorm:"column:current_op;not null"`
+	Retries       int    `gorm:"column:retries;not null" json:"retries"`
+	Status        string `gorm:"column:status;not null"`
+	CurrentOp     string `gorm:"column:current_op;not null"`
 }
 
 // Group represents the structure for storing group data in SQLite using GORM.
@@ -57,7 +57,7 @@
 	MaxCPS               int    `gorm:"column:max_cps;not null" json:"max_cps"`
 	SoftBandwidth        int    `gorm:"column:soft_bandwidth;not null" json:"soft_bandwidth"`
 	HardBandwidth        int    `gorm:"column:hard_bandwidth;not null" json:"hard_bandwidth"`
-	ServiceType          string `gorm:"column:service_type;not null"`
+	Protocol             string `gorm:"column:protocol;not null"`
 	ServerConnReuse      bool   `gorm:"column:server_conn_reuse;not null"`
 	MaxReq               int    `gorm:"column:max_req;not null"`
 	Timeout              int    `gorm:"column:timeout;not null"`
Index: /branches/main/array-ingress-controller/controller/consts.go
===================================================================
--- /branches/main/array-ingress-controller/controller/consts.go	(revision 11)
+++ /branches/main/array-ingress-controller/controller/consts.go	(working copy)
@@ -1,5 +1,7 @@
 package controller
 
-const(
-	LeaderElectionID = "array-ingress-controller-leader"
-)
\ No newline at end of file
+// Constants for leader election
+const (
+	LeaderElectionID   = "array-ingress-controller-leader"
+	ControllerNSEnvVar = "CONTROLLER_NAMESPACE" // Mandatory environment variable
+)
Index: /branches/main/array-ingress-controller/controller/leader_election.go
===================================================================
--- /branches/main/array-ingress-controller/controller/leader_election.go	(nonexistent)
+++ /branches/main/array-ingress-controller/controller/leader_election.go	(working copy)
@@ -0,0 +1,75 @@
+/*
+   Copyright 2025 Array Networks
+*/
+
+package controller
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"time"
+
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/manager"
+
+	"arraynetworks.com/array-ingress-controller/logger"
+)
+
+var (
+	leaseDuration = 15 * time.Second
+	renewDeadline = 10 * time.Second
+	retryPeriod   = 5 * time.Second
+	log           = logger.GetLogger()
+)
+
+//go:generate mockgen -source=leader_election.go -destination=mocks/mock_leader_elector.go -package=mocks
+
+// LeaderElector defines an interface for leader election
+type LeaderElector interface {
+	StartLeaderElection(ctx context.Context, leaderFunc func(context.Context)) error
+}
+
+// DefaultLeaderElector is the production implementation
+type DefaultLeaderElector struct {
+	Manager manager.Manager
+}
+
+// NewLeaderElector creates a new DefaultLeaderElector with a Kubernetes manager
+func NewLeaderElector() (LeaderElector, error) {
+	namespace, exists := os.LookupEnv(ControllerNSEnvVar)
+	if !exists || namespace == "" {
+		return nil, fmt.Errorf("environment variable %s is required but not set", ControllerNSEnvVar)
+	}
+
+	mgr, err := manager.New(ctrl.GetConfigOrDie(), manager.Options{
+		LeaderElection:          true,
+		LeaderElectionID:        LeaderElectionID,
+		LeaderElectionNamespace: namespace,
+		LeaseDuration:           &leaseDuration,
+		RenewDeadline:           &renewDeadline,
+		RetryPeriod:             &retryPeriod,
+	})
+	if err != nil {
+		return nil, fmt.Errorf("failed to create Kubernetes manager: %w", err)
+	}
+
+	return &DefaultLeaderElector{Manager: mgr}, nil
+}
+
+// StartLeaderElection starts the leader election process
+func (e *DefaultLeaderElector) StartLeaderElection(ctx context.Context, leaderFunc func(context.Context)) error {
+	log.Info("Starting leader election...")
+
+	go func() {
+		<-e.Manager.Elected() // Blocks until this pod is elected
+		log.Info("Pod is elected as leader.")
+		leaderFunc(ctx)
+	}()
+
+	if err := e.Manager.Start(ctx); err != nil {
+		return fmt.Errorf("leader election manager failed: %w", err)
+	}
+
+	return nil
+}
Index: /branches/main/array-ingress-controller/controller/leader_election_test.go
===================================================================
--- /branches/main/array-ingress-controller/controller/leader_election_test.go	(nonexistent)
+++ /branches/main/array-ingress-controller/controller/leader_election_test.go	(working copy)
@@ -0,0 +1,93 @@
+package controller
+
+import (
+	"context"
+	"errors"
+	"os"
+	"testing"
+	"time"
+
+	"arraynetworks.com/array-ingress-controller/controller/mocks"
+	"github.com/golang/mock/gomock"
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+)
+
+func TestLeaderElection(t *testing.T) {
+	RegisterFailHandler(Fail)
+	RunSpecs(t, "Leader Election Suite")
+}
+
+var _ = Describe("Leader Election", func() {
+	var (
+		ctrl            *gomock.Controller
+		mockLeaderElect *mocks.MockLeaderElector
+	)
+
+	BeforeEach(func() {
+		ctrl = gomock.NewController(GinkgoT())
+		mockLeaderElect = mocks.NewMockLeaderElector(ctrl)
+	})
+
+	AfterEach(func() {
+		ctrl.Finish()
+	})
+
+	Context("Leader Election Initialization", func() {
+		It("should return an error if CONTROLLER_NAMESPACE is not set", func() {
+			os.Unsetenv(ControllerNSEnvVar)
+
+			_, err := NewLeaderElector()
+			Expect(err).To(HaveOccurred())
+			Expect(err.Error()).To(ContainSubstring("environment variable CONTROLLER_NAMESPACE is required but not set"))
+		})
+
+		It("should create a leader elector successfully", func() {
+			err := os.Setenv(ControllerNSEnvVar, "test-namespace")
+			Expect(err).To(BeNil())
+			defer os.Unsetenv(ControllerNSEnvVar) // Ensure cleanup after test
+
+			leaderElector, err := NewLeaderElector()
+			Expect(err).To(BeNil())
+			Expect(leaderElector).NotTo(BeNil())
+		})
+	})
+
+	Context("Leader Election Process", func() {
+		It("should successfully start leader election and execute leader function", func() {
+			ctx, cancel := context.WithCancel(context.Background())
+			defer cancel()
+
+			// Use channel to track execution of leader function
+			leaderExecuted := make(chan bool, 1)
+
+			leaderFunc := func(ctx context.Context) {
+				// Notify test that leader function executed successfully
+				leaderExecuted <- true
+			}
+
+			mockLeaderElect.EXPECT().StartLeaderElection(ctx, gomock.Any()).DoAndReturn(func(ctx context.Context, leaderFunc func(context.Context)) error {
+				// Simulate election success by executing leader function
+				go leaderFunc(ctx)
+				return nil
+			}).Times(1)
+
+			err := mockLeaderElect.StartLeaderElection(ctx, leaderFunc)
+			Expect(err).To(BeNil())
+
+			// Wait until leaderFunc actually executes before completing test
+			Eventually(leaderExecuted).Should(Receive(BeTrue()))
+		})
+
+		It("should return an error if leader election fails", func() {
+			mockLeaderElect.EXPECT().StartLeaderElection(gomock.Any(), gomock.Any()).Return(errors.New("leader election failed")).Times(1)
+
+			ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
+			defer cancel()
+
+			err := mockLeaderElect.StartLeaderElection(ctx, func(ctx context.Context) {})
+			Expect(err).To(HaveOccurred())
+			Expect(err.Error()).To(Equal("leader election failed"))
+		})
+	})
+})
Index: /branches/main/array-ingress-controller/controller/mocks/mock_leader_elector.go
===================================================================
--- /branches/main/array-ingress-controller/controller/mocks/mock_leader_elector.go	(nonexistent)
+++ /branches/main/array-ingress-controller/controller/mocks/mock_leader_elector.go	(working copy)
@@ -0,0 +1,49 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: leader_election.go
+
+// Package mocks is a generated GoMock package.
+package mocks
+
+import (
+	context "context"
+	reflect "reflect"
+
+	gomock "github.com/golang/mock/gomock"
+)
+
+// MockLeaderElector is a mock of LeaderElector interface.
+type MockLeaderElector struct {
+	ctrl     *gomock.Controller
+	recorder *MockLeaderElectorMockRecorder
+}
+
+// MockLeaderElectorMockRecorder is the mock recorder for MockLeaderElector.
+type MockLeaderElectorMockRecorder struct {
+	mock *MockLeaderElector
+}
+
+// NewMockLeaderElector creates a new mock instance.
+func NewMockLeaderElector(ctrl *gomock.Controller) *MockLeaderElector {
+	mock := &MockLeaderElector{ctrl: ctrl}
+	mock.recorder = &MockLeaderElectorMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockLeaderElector) EXPECT() *MockLeaderElectorMockRecorder {
+	return m.recorder
+}
+
+// StartLeaderElection mocks base method.
+func (m *MockLeaderElector) StartLeaderElection(ctx context.Context, leaderFunc func(context.Context)) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "StartLeaderElection", ctx, leaderFunc)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// StartLeaderElection indicates an expected call of StartLeaderElection.
+func (mr *MockLeaderElectorMockRecorder) StartLeaderElection(ctx, leaderFunc interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartLeaderElection", reflect.TypeOf((*MockLeaderElector)(nil).StartLeaderElection), ctx, leaderFunc)
+}
Index: /branches/main/array-ingress-controller/controller/openshift-controller/cmd.go
===================================================================
--- /branches/main/array-ingress-controller/controller/openshift-controller/cmd.go	(revision 11)
+++ /branches/main/array-ingress-controller/controller/openshift-controller/cmd.go	(working copy)
@@ -1,7 +1,7 @@
 /*
 	Copyright 2025 Array Networks
 */
- 
+
 package openshift
 
 import (
@@ -10,54 +10,54 @@
 	"syscall"
 	"time"
 
-    arrayinformers "arraynetworks.com/array-ingress-controller/controller/openshift-controller/informers"
-    "arraynetworks.com/array-ingress-controller/controller/openshift-controller/utils"
+	arrayinformers "arraynetworks.com/array-ingress-controller/controller/openshift-controller/informers"
+	"arraynetworks.com/array-ingress-controller/controller/openshift-controller/utils"
 	"arraynetworks.com/array-ingress-controller/logger"
-    //routev1 "github.com/openshift/client-go/route/clientset/versioned"
+
+	//routev1 "github.com/openshift/client-go/route/clientset/versioned"
 	"k8s.io/client-go/informers"
 	//"k8s.io/client-go/kubernetes"
 )
 
-var(
-    log	        logger.Logger
+var (
+	log logger.Logger
 )
 
-func init(){
-	log	= logger.GetLogger()
+func init() {
+	log = logger.GetLogger()
 }
 
-func  StartControllers() {
-    // Create Kubernetes client
-    kubeClient, err := utils.CreateKubeClient()
-    if err != nil {
-        log.Errorf("Error creating Kubernetes client: %v\n", err)
-        os.Exit(1)
-    }
+func StartControllers() {
+	// Create Kubernetes client
+	kubeClient, err := utils.CreateKubeClient()
+	if err != nil {
+		log.Errorf("Error creating Kubernetes client: %v\n", err)
+		os.Exit(1)
+	}
 
 	// Create OpenShift Route client
-    routeClient, err := utils.CreateRouteClient()
-    if err != nil {
-        log.Errorf("Error creating OpenShift route client: %v\n", err)
-        os.Exit(1)
-    }
-
-    // Create shared informer factory
-    factory := informers.NewSharedInformerFactory(kubeClient, 1*time.Minute)
-    
-    // Create a channel to stop the informers
-    stopCh := make(chan struct{})
-
-    // Start Pod informer
-    go arrayinformers.StartRouteInformer(routeClient, stopCh)
-    go arrayinformers.StartEndpointInformer(factory, stopCh)
-    //informers.StartServiceInformer(factory, stopCh)
-
-    // Handle shutdown signals
-    sigCh := make(chan os.Signal, 1)
-    signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
-    <-sigCh
-
-    // Stop informers
-    close(stopCh)
-    log.Info("Shutting down informers...")
+	routeClient, err := utils.CreateRouteClient()
+	if err != nil {
+		log.Errorf("Error creating OpenShift route client: %v\n", err)
+		os.Exit(1)
+	}
+
+	// Create shared informer factory
+	factory := informers.NewSharedInformerFactory(kubeClient, 1*time.Second)
+
+	// Create a channel to stop the informers
+	stopCh := make(chan struct{})
+
+	// Start Pod informer
+	go arrayinformers.StartRouteInformer(routeClient, stopCh)
+	go arrayinformers.StartEndpointInformer(factory, stopCh)
+
+	// Handle shutdown signals
+	sigCh := make(chan os.Signal, 1)
+	signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
+	<-sigCh
+
+	// Stop informers
+	close(stopCh)
+	log.Info("Shutting down informers...")
 }
Index: /branches/main/array-ingress-controller/controller/openshift-controller/handlers/endpoint_event_handler.go
===================================================================
--- /branches/main/array-ingress-controller/controller/openshift-controller/handlers/endpoint_event_handler.go	(revision 11)
+++ /branches/main/array-ingress-controller/controller/openshift-controller/handlers/endpoint_event_handler.go	(working copy)
@@ -11,7 +11,7 @@
 	corev1 "k8s.io/api/core/v1"
 )
 
-func EndPointAdded(obj interface{}){
+func EndPointAdded(obj interface{}) {
 
 	endpt := obj.(*corev1.Endpoints)
 
@@ -19,16 +19,16 @@
 	if err != nil || len(routes) == 0 {
 		return
 	}
-
+	log.Info("Inside endpoint add...")
 	// Create OpenShift-specific handlers
 	osAPV := &apv.OpenShiftAPV{}
 	osASF := &asf.OpenShiftASF{}
 
 	// Initialize Task Manager Facade with OpenShift handlers
 	taskManager := task.New(osAPV, osASF)
-	
+
 	// Create a new task
-	deleteTask := &task.Task{
+	addTask := &task.Task{
 		Route:  routes,
 		Svc:    nil,
 		Pods:   nil,
@@ -39,7 +39,7 @@
 	ctx = context.WithValue(ctx, task.ResourceTypeKey{}, "ENDPOINT")
 
 	// Prepare and execute the delete task
-	if err := taskManager.PrepareTask(ctx, deleteTask, "ARRAY-APV"); err != nil {
+	if err := taskManager.PrepareTask(ctx, addTask, "ARRAY-APV"); err != nil {
 		log.Fatalf("Failed to prepare delete task: %v", err)
 	}
 
@@ -47,7 +47,7 @@
 
 }
 
-func EndPointDeleted(obj interface{}){
+func EndPointDeleted(obj interface{}) {
 
 	endpt := obj.(*corev1.Endpoints)
 
@@ -63,7 +63,7 @@
 
 	// Initialize Task Manager Facade with OpenShift handlers
 	taskManager := task.New(osAPV, osASF)
-	
+
 	// Create a new task
 	deleteTask := &task.Task{
 		Route:  routes,
@@ -84,3 +84,73 @@
 
 }
 
+func EndPointUpdated(oldObj, newObj interface{}) {
+	oldEndpt := oldObj.(*corev1.Endpoints)
+	newEndpt := newObj.(*corev1.Endpoints)
+
+	routes, err := utils.GetAssocRoutes(newEndpt.Namespace, newEndpt.Name)
+	if err != nil || len(routes) == 0 {
+		return
+	}
+
+	// Extract IP addresses from old and new Endpoints
+	oldIPs := utils.ExtractIPs(oldEndpt)
+	log.Info("Old IPs:", oldIPs)
+	newIPs := utils.ExtractIPs(newEndpt)
+	log.Info("Old IPs:", newIPs)
+
+	// Determine added and removed IPs
+	addedIPs, removedIPs := utils.CompareIPs(oldIPs, newIPs)
+
+	if len(addedIPs) > 0 || len(removedIPs) > 0 {
+		log.Info("IP addresses have changed.")
+		log.Infof("Added IPs: %v", addedIPs)
+		log.Infof("Removed IPs: %v", removedIPs)
+		// Add logic to handle added and removed IPs here
+	}
+
+	// Create OpenShift-specific handlers
+	osAPV := &apv.OpenShiftAPV{}
+	osASF := &asf.OpenShiftASF{}
+
+	// Initialize Task Manager Facade with OpenShift handlers
+	taskManager := task.New(osAPV, osASF)
+
+	// Handle removed IPs from EndPoints
+	if len(removedIPs) > 0 {
+		deleteTask := &task.Task{
+			Route:  routes,
+			Svc:    nil,
+			Pods:   nil,
+			EndPts: utils.CreateEndpointsWithIPs(oldEndpt.Namespace, oldEndpt.Name, removedIPs),
+		}
+
+		ctx := context.WithValue(context.Background(), task.TaskTypeKey{}, "DELETE")
+		ctx = context.WithValue(ctx, task.ResourceTypeKey{}, "ENDPOINT")
+
+		// Prepare and execute the delete task
+		if err := taskManager.PrepareTask(ctx, deleteTask, "ARRAY-APV"); err != nil {
+			log.Fatalf("Failed to prepare delete task: %v", err)
+		}
+	}
+
+	// Handle added IPs from EndPoints
+	if len(addedIPs) > 0 {
+		addTask := &task.Task{
+			Route:  routes,
+			Svc:    nil,
+			Pods:   nil,
+			EndPts: utils.CreateEndpointsWithIPs(newEndpt.Namespace, newEndpt.Name, addedIPs),
+		}
+
+		ctx := context.WithValue(context.Background(), task.TaskTypeKey{}, "ADD")
+		ctx = context.WithValue(ctx, task.ResourceTypeKey{}, "ENDPOINT")
+
+		// Prepare and execute the delete task
+		if err := taskManager.PrepareTask(ctx, addTask, "ARRAY-APV"); err != nil {
+			log.Fatalf("Failed to prepare delete task: %v", err)
+		}
+	}
+
+	log.Info("Inside EndPoint update..")
+}
Index: /branches/main/array-ingress-controller/controller/openshift-controller/handlers/route_event_handler.go
===================================================================
--- /branches/main/array-ingress-controller/controller/openshift-controller/handlers/route_event_handler.go	(revision 11)
+++ /branches/main/array-ingress-controller/controller/openshift-controller/handlers/route_event_handler.go	(working copy)
@@ -13,14 +13,19 @@
 	"arraynetworks.com/array-ingress-controller/task-manager/openshift/asf"
 	"arraynetworks.com/array-ingress-controller/task-manager/task"
 
-	corev1 "k8s.io/api/core/v1"
 	routev1 "github.com/openshift/api/route/v1"
+	corev1 "k8s.io/api/core/v1"
+)
+
+const (
+	HealthCheckAnnotKey = "apv.arraynetworks.com/healthcheck"
+	RateLimitAnnotKey   = "apv.arraynetworks.com/ratelimit"
 )
 
 // RouteAdded handles Route addition events.
 func RouteAdded(obj interface{}) {
 	route := obj.(*routev1.Route)
-	
+
 	routeClass, flag := utils.CheckRouteClass(*route)
 
 	if !flag {
@@ -70,93 +75,124 @@
 	log.Println("Task prepared and executed successfully")
 }
 
-
 func RouteUpdated(oldObj, newObj interface{}) {
-    oldRoute := oldObj.(*routev1.Route)
-    newRoute := newObj.(*routev1.Route)
-
-    
+	oldRoute := oldObj.(*routev1.Route)
+	newRoute := newObj.(*routev1.Route)
 
-    routeClass, flag := utils.CheckRouteClass(*oldRoute)
+	routeClass, flag := utils.CheckRouteClass(*oldRoute)
 	if !flag {
 		log.Debugf("Ignoring Route: %s/%s\n", oldRoute.Namespace, oldRoute.Name)
 		return
 	}
 	log.Infof("Route updated: %s/%s\n", newRoute.Namespace, oldRoute.Name)
 
-    // Identify if the annotations have been updated
-    annotationsChanged := false
-    if len(oldRoute.Annotations) != len(newRoute.Annotations) {
-        annotationsChanged = true
-    } else {
-        for key, oldValue := range oldRoute.Annotations {
-            if newValue, exists := newRoute.Annotations[key]; !exists || oldValue != newValue {
-                annotationsChanged = true
-                break
-            }
-        }
-    }
-
-    // Log annotation update if detected
-    if annotationsChanged {
-		//WHAT MUST BE DONE?
-        log.Infof("Annotations updated for route %s/%s", newRoute.Namespace, newRoute.Name)
-    }
+	// Get associated service name from route
+	assocSvc, err := utils.GetAssocSvc(newRoute.Namespace, newRoute.Name)
+	if err != nil {
+		log.Errorf("Cannot find service for route '%s' in namespace '%s'", newRoute.Name, newRoute.Namespace)
+		return
+	}
+	log.Infof("Associated realservice: %s", assocSvc)
+	// Get endpoint using service name
+	endpoint, err := utils.GetAssocEndpt(assocSvc, newRoute)
+	if err != nil {
+		log.Errorf("Cannot fetch endpoint '%s' in namespace '%s'", assocSvc, newRoute.Namespace)
+		return
+	}
+	log.Infof("Associated endpoint: %s", endpoint)
 
 	// Initialize the Task Manager Facade with OpenShift handlers
-    taskManager := task.New(&apv.OpenShiftAPV{}, &asf.OpenShiftASF{})
+	taskManager := task.New(&apv.OpenShiftAPV{}, &asf.OpenShiftASF{})
 
-    // Create a new task for Route Update
-    newTask := &task.Task{
-        Route:  []*routev1.Route{newRoute},
-        Svc:    nil,
-        Pods:   nil,
-        EndPts: nil,
-    }
+	// Create a new task for Route Update
+	newTask := &task.Task{
+		Route:  []*routev1.Route{newRoute},
+		Svc:    nil,
+		Pods:   nil,
+		EndPts: []*corev1.Endpoints{endpoint},
+	}
 
 	// Set context for task preparation
-    ctx := context.WithValue(context.Background(), task.TaskTypeKey{}, "UPDATE")
-    ctx = context.WithValue(ctx, task.ResourceTypeKey{}, "ROUTE")
+	ctx := context.WithValue(context.Background(), task.TaskTypeKey{}, "UPDATE")
+	ctx = context.WithValue(ctx, task.ResourceTypeKey{}, "ROUTE")
 
 	// Identify if the path or port has changed
-    if (oldRoute.Spec.Path != newRoute.Spec.Path) || (oldRoute.Spec.Port.TargetPort != newRoute.Spec.Port.TargetPort) {
-        log.Infof("Spec updated for route %s in namespace %s", newRoute.Name, newRoute.Namespace)
+	if (oldRoute.Spec.Path != newRoute.Spec.Path) || (oldRoute.Spec.Port.TargetPort != newRoute.Spec.Port.TargetPort) {
+		log.Infof("Spec updated for route %s in namespace %s", newRoute.Name, newRoute.Namespace)
 		ctx = context.WithValue(ctx, "spec_updated", "True")
-    }
+	}
+
+	if utils.AnnotationExists(oldRoute, HealthCheckAnnotKey) {
+		ctx = context.WithValue(ctx, "health_in_old", "True")
+	} else {
+		ctx = context.WithValue(ctx, "health_in_old", "False")
+	}
 
-	healthAnnotChange := utils.CompareJSONAnnotations(oldRoute, newRoute, "arraynetworks.com/healthcheck")
+	if utils.AnnotationExists(newRoute, HealthCheckAnnotKey) {
+		ctx = context.WithValue(ctx, "health_in_new", "True")
+	} else {
+		ctx = context.WithValue(ctx, "health_in_new", "False")
+	}
+
+	healthAnnotChange := utils.CompareJSONAnnotations(oldRoute, newRoute, HealthCheckAnnotKey)
 	if healthAnnotChange {
 		log.Infof("Health Check annotation updated for route %s in namespace %s", newRoute.Name, newRoute.Namespace)
-		ctx = context.WithValue(ctx, "health_check", "True")
+		ctx = context.WithValue(ctx, "health_check_updated", "True")
+	}
+
+	if utils.AnnotationExists(oldRoute, RateLimitAnnotKey) {
+		ctx = context.WithValue(ctx, "ratelimit_in_old", "True")
+	} else {
+		ctx = context.WithValue(ctx, "ratelimit_in_old", "False")
 	}
 
-	ratelimitAnnotChang := utils.CompareJSONAnnotations(oldRoute, newRoute, "arraynetworks.com/ratelimit")
+	if utils.AnnotationExists(newRoute, RateLimitAnnotKey) {
+		ctx = context.WithValue(ctx, "ratelimit_in_new", "True")
+	} else {
+		ctx = context.WithValue(ctx, "ratelimit_in_new", "False")
+	}
+
+	ratelimitAnnotChang := utils.CompareJSONAnnotations(oldRoute, newRoute, RateLimitAnnotKey)
 	if ratelimitAnnotChang {
 		log.Infof("Health Check annotation updated for route %s in namespace %s", newRoute.Name, newRoute.Namespace)
-		ctx = context.WithValue(ctx, "ratelimit", "True")
+		ctx = context.WithValue(ctx, "ratelimit_updated", "True")
 	}
 
+	// Prepare and execute the update task
+	if err := taskManager.PrepareTask(ctx, newTask, routeClass); err != nil {
+		log.Fatalf("Failed to prepare update task: %v", err)
+	}
 
-    // Prepare and execute the update task
-    if err := taskManager.PrepareTask(ctx, newTask, routeClass); err != nil {
-        log.Fatalf("Failed to prepare update task: %v", err)
-    }
-
-    log.Println("Route update task prepared and executed successfully")
+	log.Println("Route update task prepared and executed successfully")
 }
 
 // RouteDeleted handles Route deletion events.
 func RouteDeleted(obj interface{}) {
 	route := obj.(*routev1.Route)
-	
-	routeClass, flag := utils.CheckRouteClass(*route)
 
+	routeClass, flag := utils.CheckRouteClass(*route)
 	if !flag {
 		log.Debugf("Ignoring Route: %s/%s\n", route.Namespace, route.Name)
 		return
 	}
 	log.Infof("Route deleted: %s/%s\n", route.Namespace, route.Name)
 
+	// Extract associated service name directly before deletion
+	assocSvc := route.Spec.To.Name
+	if assocSvc == "" {
+		log.Errorf("Route '%s' in namespace '%s' has no associated service", route.Name, route.Namespace)
+		return
+	}
+	log.Infof("Associated realservice: %s", assocSvc)
+
+	// Get endpoint using service name
+	endpoint, err := utils.GetAssocEndpt(assocSvc, route)
+	if err != nil {
+		log.Errorf("Cannot fetch endpoint '%s' in namespace '%s'", assocSvc, route.Namespace)
+		return
+	}
+	log.Infof("Associated endpoint: %s", endpoint)
+
 	// Create OpenShift-specific handlers
 	osAPV := &apv.OpenShiftAPV{}
 	osASF := &asf.OpenShiftASF{}
@@ -169,7 +205,7 @@
 		Route:  []*routev1.Route{route},
 		Svc:    nil,
 		Pods:   nil,
-		EndPts: nil,
+		EndPts: []*corev1.Endpoints{endpoint},
 	}
 
 	ctx := context.WithValue(context.Background(), task.TaskTypeKey{}, "DELETE")
@@ -182,3 +218,4 @@
 
 	log.Println("Route delete task prepared and executed successfully")
 }
+
Index: /branches/main/array-ingress-controller/controller/openshift-controller/informers/endpoint_informer.go
===================================================================
--- /branches/main/array-ingress-controller/controller/openshift-controller/informers/endpoint_informer.go	(revision 11)
+++ /branches/main/array-ingress-controller/controller/openshift-controller/informers/endpoint_informer.go	(working copy)
@@ -1,27 +1,27 @@
 package informers
 
 import (
-    "arraynetworks.com/array-ingress-controller/controller/openshift-controller/handlers"
-    "k8s.io/client-go/informers"
-    "k8s.io/client-go/tools/cache"
-    //corev1 "k8s.io/api/core/v1"
+	"arraynetworks.com/array-ingress-controller/controller/openshift-controller/handlers"
+	"k8s.io/client-go/informers"
+	"k8s.io/client-go/tools/cache"
+	//corev1 "k8s.io/api/core/v1"
 )
 
 // StartEndpointInformer sets up the Endpoint informer and ensures the cache is synced.
 func StartEndpointInformer(factory informers.SharedInformerFactory, stopCh chan struct{}) {
-    endpointInformer := factory.Core().V1().Endpoints().Informer()
+	endpointInformer := factory.Core().V1().Endpoints().Informer()
 
-    endpointInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
-        AddFunc:    handlers.EndPointAdded,
-        //UpdateFunc: handlers.EndPointUpdated,
-        DeleteFunc: handlers.EndPointDeleted,
-    })
+	endpointInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
+		AddFunc:    handlers.EndPointAdded,
+		UpdateFunc: handlers.EndPointUpdated,
+		DeleteFunc: handlers.EndPointDeleted,
+	})
 
-    go endpointInformer.Run(stopCh)
+	go endpointInformer.Run(stopCh)
 
-    if !cache.WaitForCacheSync(stopCh, endpointInformer.HasSynced) {
-        log.Println("Endpoint informer cache sync failed")
-        return
-    }
-    log.Println("Endpoint informer cache synced successfully")
-}
\ No newline at end of file
+	if !cache.WaitForCacheSync(stopCh, endpointInformer.HasSynced) {
+		log.Println("Endpoint informer cache sync failed")
+		return
+	}
+	log.Println("Endpoint informer cache synced successfully")
+}
Index: /branches/main/array-ingress-controller/controller/openshift-controller/informers/route_informer.go
===================================================================
--- /branches/main/array-ingress-controller/controller/openshift-controller/informers/route_informer.go	(revision 11)
+++ /branches/main/array-ingress-controller/controller/openshift-controller/informers/route_informer.go	(working copy)
@@ -15,7 +15,7 @@
 
 // StartRouteInformer sets up the Route informer and ensures the cache is synced.
 func StartRouteInformer(clientset *routev1.Clientset, stopCh chan struct{}) {
-	factory := routeinformers.NewSharedInformerFactory(clientset, 1*time.Minute)
+	factory := routeinformers.NewSharedInformerFactory(clientset, 1*time.Second)
 	routeInformer := factory.Route().V1().Routes().Informer()
 	log.Info("Starting route Informer")
 	routeInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
Index: /branches/main/array-ingress-controller/controller/openshift-controller/utils/k8s_helper.go
===================================================================
--- /branches/main/array-ingress-controller/controller/openshift-controller/utils/k8s_helper.go	(revision 11)
+++ /branches/main/array-ingress-controller/controller/openshift-controller/utils/k8s_helper.go	(working copy)
@@ -71,9 +71,11 @@
 		}
 	}
 
-	if !reflect.DeepEqual(oldData, newData) {
-		logger.Info("Annotation fields have changed.")
-		return true
+	if oldExists && newExists {
+		if !reflect.DeepEqual(oldData, newData) {
+			logger.Info("Annotation fields have changed.")
+			return true
+		}
 	}
 
 	return false
@@ -246,3 +248,65 @@
 	}
 	return true
 }
+
+// Helper function to extract IP addresses from Endpoints
+func ExtractIPs(endpt *corev1.Endpoints) []string {
+	var ips []string
+	for _, subset := range endpt.Subsets {
+		for _, address := range subset.Addresses {
+			ips = append(ips, address.IP)
+		}
+	}
+	return ips
+}
+
+// Helper function to compare two slices of IP addresses
+func CompareIPs(oldIPs, newIPs []string) (added []string, removed []string) {
+	oldIPSet := make(map[string]bool)
+	newIPSet := make(map[string]bool)
+
+	for _, ip := range oldIPs {
+		oldIPSet[ip] = true
+	}
+
+	for _, ip := range newIPs {
+		newIPSet[ip] = true
+		if !oldIPSet[ip] {
+			added = append(added, ip)
+		}
+	}
+
+	for _, ip := range oldIPs {
+		if !newIPSet[ip] {
+			removed = append(removed, ip)
+		}
+	}
+
+	return added, removed
+}
+
+// Helper function to create Endpoints object from a list of IPs
+func CreateEndpointsWithIPs(namespace, name string, ips []string) []*corev1.Endpoints {
+	endpts := &corev1.Endpoints{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: namespace,
+			Name:      name,
+		},
+		Subsets: []corev1.EndpointSubset{
+			{
+				Addresses: createEndpointAddresses(ips),
+			},
+		},
+	}
+
+	return []*corev1.Endpoints{endpts}
+}
+
+// Helper function to create EndpointAddress objects from IPs
+func createEndpointAddresses(ips []string) []corev1.EndpointAddress {
+	var addresses []corev1.EndpointAddress
+	for _, ip := range ips {
+		addresses = append(addresses, corev1.EndpointAddress{IP: ip})
+	}
+	return addresses
+}
Index: /branches/main/array-ingress-controller/controller/openshift-controller/utils/kube_client.go
===================================================================
--- /branches/main/array-ingress-controller/controller/openshift-controller/utils/kube_client.go	(revision 11)
+++ /branches/main/array-ingress-controller/controller/openshift-controller/utils/kube_client.go	(working copy)
@@ -5,32 +5,37 @@
 package utils
 
 import (
-    "k8s.io/client-go/kubernetes"
-    "k8s.io/client-go/rest"
+	"k8s.io/client-go/kubernetes"
+	"k8s.io/client-go/rest"
 
-    "os"
+	"os"
 )
 
 var (
-    initErr     error
+	initErr error
 )
 
 // CreateKubeClient creates a Kubernetes client using in-cluster configuration.
 func CreateKubeClient() (*kubernetes.Clientset, error) {
-    var config *rest.Config
-    var err error
-    // Use in-cluster configuration
-    config, err = rest.InClusterConfig()
-    if err != nil {
-        initErr = err  
-        log.Error(initErr)
-        os.Exit(1)
-    }
-    // Create the clientset
-    kubeClient, initErr = kubernetes.NewForConfig(config)
-    if initErr != nil {
-        log.Error(initErr)
-        os.Exit(1)
-    }
-    return kubeClient , initErr
+
+	if os.Getenv("TEST") == "true" {
+		return nil, nil
+	}
+
+	var config *rest.Config
+	var err error
+	// Use in-cluster configuration
+	config, err = rest.InClusterConfig()
+	if err != nil {
+		initErr = err
+		log.Error(initErr)
+		os.Exit(1)
+	}
+	// Create the clientset
+	kubeClient, initErr = kubernetes.NewForConfig(config)
+	if initErr != nil {
+		log.Error(initErr)
+		os.Exit(1)
+	}
+	return kubeClient , initErr
 }
\ No newline at end of file
Index: /branches/main/array-ingress-controller/controller/openshift-controller/utils/route_client.go
===================================================================
--- /branches/main/array-ingress-controller/controller/openshift-controller/utils/route_client.go	(revision 11)
+++ /branches/main/array-ingress-controller/controller/openshift-controller/utils/route_client.go	(working copy)
@@ -4,30 +4,32 @@
 
 package utils
 
- import (
-     "os"
- 
-     "k8s.io/client-go/rest"
-     routev1 "github.com/openshift/client-go/route/clientset/versioned"
- )
- 
- var (
-     routeinitErr     error
- )
- 
- // CreateRouteClient creates an OpenShift Route client using in-cluster configuration.
- func CreateRouteClient() (*routev1.Clientset, error) {
-    config, err := rest.InClusterConfig()
-    if err != nil {
-        routeinitErr = err  
-        log.Error(routeinitErr)
-        os.Exit(1)
-    }
-    routeClient, routeinitErr = routev1.NewForConfig(config)
-    if routeinitErr != nil {
-        log.Error(routeinitErr)
-        os.Exit(1)
-    }
-    return routeClient, routeinitErr 
- }
+import (
+	"os"
 
+	"k8s.io/client-go/rest"
+    routev1 "github.com/openshift/client-go/route/clientset/versioned"
+)
+
+var (
+	routeinitErr     error
+)
+
+// CreateRouteClient creates an OpenShift Route client using in-cluster configuration.
+func CreateRouteClient() (*routev1.Clientset, error) {
+	if os.Getenv("TEST") == "true" {
+		return nil, nil
+	}
+	config, err := rest.InClusterConfig()
+	if err != nil {
+		routeinitErr = err
+		log.Error(routeinitErr)
+		os.Exit(1)
+	}
+	routeClient, routeinitErr = routev1.NewForConfig(config)
+	if routeinitErr != nil {
+		log.Error(routeinitErr)
+		os.Exit(1)
+	}
+	return routeClient, routeinitErr
+}
Index: /branches/main/array-ingress-controller/go.mod
===================================================================
--- /branches/main/array-ingress-controller/go.mod	(revision 11)
+++ /branches/main/array-ingress-controller/go.mod	(working copy)
@@ -4,11 +4,11 @@
 
 replace (
 	arraynetworks.com/array-ingress-controller/apvadapter => ./apvadapter
+	arraynetworks.com/array-ingress-controller/apvadapter/annotations/health => ./apvadapter/annotations/health
+	arraynetworks.com/array-ingress-controller/apvadapter/annotations/ratelimit => ./apvadapter/annotations/ratelimit
+	arraynetworks.com/array-ingress-controller/apvadapter/annotations/whitelisting => ./apvadapter/annotations/whitelisting
 	arraynetworks.com/array-ingress-controller/apvadapter/apvurlbuilder => ./apvadapter/apvurlbuilder
-	arraynetworks.com/array-ingress-controller/apvadapter/health => ./apvadapter/annotations/health
-	arraynetworks.com/array-ingress-controller/apvadapter/ratelimit => ./apvadapter/annotations/ratelimit
 	arraynetworks.com/array-ingress-controller/apvadapter/realservice => ./apvadapter/realservice
-	arraynetworks.com/array-ingress-controller/apvadapter/whitelisting => ./apvadapter/annotations/whitelisting
 	arraynetworks.com/array-ingress-controller/apvclient => ./apvadapter/apvclient
 	arraynetworks.com/array-ingress-controller/common => ./common
 	arraynetworks.com/array-ingress-controller/configstore => ./configstore
@@ -18,15 +18,13 @@
 	arraynetworks.com/array-ingress-controller/controller/openshift-controller/informers => ./controller/openshift-controller/informers
 	arraynetworks.com/array-ingress-controller/controller/openshift-controller/utils => ./controller/openshift-controller/utils
 	arraynetworks.com/array-ingress-controller/logger => ./logger
-	arraynetworks.com/array-ingress-controller/task-manager/openshift/apv => ./task-manager/openshift/apv
-	arraynetworks.com/array-ingress-controller/task-manager/openshift/asf => ./task-manager/openshift/asf
+	arraynetworks.com/array-ingress-controller/task-manager/openshift => ./task-manager/openshift
 	arraynetworks.com/array-ingress-controller/task-manager/task => ./task-manager/task
 	arraynetworks.com/array-ingress-controller/watchtower => ./watchtower
 )
 
 require (
 	arraynetworks.com/array-ingress-controller/apvadapter v0.0.0-00010101000000-000000000000
-	arraynetworks.com/array-ingress-controller/common v0.0.0-00010101000000-000000000000
 	arraynetworks.com/array-ingress-controller/controller/openshift v0.0.0-00010101000000-000000000000
 	arraynetworks.com/array-ingress-controller/logger v0.0.0-00010101000000-000000000000
 	arraynetworks.com/array-ingress-controller/watchtower v0.0.0-00010101000000-000000000000
@@ -34,20 +32,19 @@
 	k8s.io/apimachinery v0.32.2
 	k8s.io/client-go v0.32.2
 	k8s.io/klog/v2 v2.130.1
-	sigs.k8s.io/controller-runtime v0.20.3
+	sigs.k8s.io/controller-runtime v0.20.2
 )
 
 require (
 	arraynetworks.com/array-ingress-controller/apvadapter/apvurlbuilder v0.0.0-00010101000000-000000000000 // indirect
-	arraynetworks.com/array-ingress-controller/apvadapter/health v0.0.0-00010101000000-000000000000 // indirect
 	arraynetworks.com/array-ingress-controller/apvadapter/realservice v0.0.0-00010101000000-000000000000 // indirect
 	arraynetworks.com/array-ingress-controller/apvclient v0.0.0-00010101000000-000000000000 // indirect
+	arraynetworks.com/array-ingress-controller/common v0.0.0-00010101000000-000000000000 // indirect
 	arraynetworks.com/array-ingress-controller/configstore v0.0.0-00010101000000-000000000000 // indirect
 	arraynetworks.com/array-ingress-controller/controller/openshift-controller/handlers v0.0.0-00010101000000-000000000000 // indirect
 	arraynetworks.com/array-ingress-controller/controller/openshift-controller/informers v0.0.0-00010101000000-000000000000 // indirect
 	arraynetworks.com/array-ingress-controller/controller/openshift-controller/utils v0.0.0-00010101000000-000000000000 // indirect
-	arraynetworks.com/array-ingress-controller/task-manager/openshift/apv v0.0.0-00010101000000-000000000000 // indirect
-	arraynetworks.com/array-ingress-controller/task-manager/openshift/asf v0.0.0-00010101000000-000000000000 // indirect
+	arraynetworks.com/array-ingress-controller/task-manager/openshift v0.0.0-00010101000000-000000000000 // indirect
 	arraynetworks.com/array-ingress-controller/task-manager/task v0.0.0-00010101000000-000000000000 // indirect
 	filippo.io/edwards25519 v1.1.0 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
@@ -80,7 +77,7 @@
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
-	github.com/openshift/api v0.0.0-20250305225826-b8da3bfeaf77 // indirect
+	github.com/openshift/api v0.0.0-20250228110707-635291d6fdf1 // indirect
 	github.com/openshift/client-go v0.0.0-20250131180035-f7ec47e2d87a // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/prometheus/client_golang v1.19.1 // indirect
@@ -110,7 +107,11 @@
 	gorm.io/driver/mysql v1.5.6 // indirect
 	gorm.io/driver/sqlite v1.5.7 // indirect
 	gorm.io/gorm v1.25.12 // indirect
-	k8s.io/apiextensions-apiserver v0.32.1 // indirect
+  k8s.io/apiextensions-apiserver v0.32.1 // indirect
+	k8s.io/api v0.32.2 // indirect
+	k8s.io/apimachinery v0.32.2 // indirect
+	k8s.io/client-go v0.32.2 // indirect
+	k8s.io/klog/v2 v2.130.1 // indirect
 	k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
 	k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
 	sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
Index: /branches/main/array-ingress-controller/go.sum
===================================================================
--- /branches/main/array-ingress-controller/go.sum	(revision 11)
+++ /branches/main/array-ingress-controller/go.sum	(working copy)
@@ -55,8 +55,8 @@
 github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
 github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
-github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
 github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -102,14 +102,14 @@
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
-github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=
-github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=
-github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
-github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
-github.com/openshift/api v0.0.0-20250305225826-b8da3bfeaf77 h1:w6F0sEhlUB1K54Ev4EELsLo5w/xur9pFT19VtemlB4Y=
-github.com/openshift/api v0.0.0-20250305225826-b8da3bfeaf77/go.mod h1:yk60tHAmHhtVpJQo3TwVYq2zpuP70iJIFDCmeKMIzPw=
-github.com/openshift/client-go v0.0.0-20250131180035-f7ec47e2d87a h1:duO3JMrUOqVx50QhzxvDeOYIwTNOB8/EEuRLPyvAMBg=
-github.com/openshift/client-go v0.0.0-20250131180035-f7ec47e2d87a/go.mod h1:Qw3ThpzVZ0bfTILpBNYg4LGyjtNxfyCiGh/uDLOOTP8=
+github.com/onsi/ginkgo/v2 v2.23.3 h1:edHxnszytJ4lD9D5Jjc4tiDkPBZ3siDeJJkUZJJVkp0=
+github.com/onsi/ginkgo/v2 v2.23.3/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM=
+github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU=
+github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
+github.com/openshift/api v0.0.0-20250325155304-0f14a211af33 h1:zUW6xTSXnD/ffvFqjKA07lKdfEYHQvylLdc4bXhdWA4=
+github.com/openshift/api v0.0.0-20250325155304-0f14a211af33/go.mod h1:yk60tHAmHhtVpJQo3TwVYq2zpuP70iJIFDCmeKMIzPw=
+github.com/openshift/client-go v0.0.0-20250324153519-f0faeb0f2f2e h1:OfOI0eTTUdwqaUHgm0YsinRYtOj4OGO+ioauVNPuDeQ=
+github.com/openshift/client-go v0.0.0-20250324153519-f0faeb0f2f2e/go.mod h1:MWRA5YlclrxlrDIOni+AaeOrHchTslLvLTs9pIWuCiw=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -140,6 +140,7 @@
 github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
 go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
 go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
@@ -149,50 +150,58 @@
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
-golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
+golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
+golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
-golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
+golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
 golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
 golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
-golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
+golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
-golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
-golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
+golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
+golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
-golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
+golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
+golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
 golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
 golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
-golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
+golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
+golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
 gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
-google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
-google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
+google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
+google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -222,25 +231,27 @@
 gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
 gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
 gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
-k8s.io/api v0.32.2 h1:bZrMLEkgizC24G9eViHGOPbW+aRo9duEISRIJKfdJuw=
-k8s.io/api v0.32.2/go.mod h1:hKlhk4x1sJyYnHENsrdCWw31FEmCijNGPJO5WzHiJ6Y=
+k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls=
+k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k=
 k8s.io/apiextensions-apiserver v0.32.1 h1:hjkALhRUeCariC8DiVmb5jj0VjIc1N0DREP32+6UXZw=
 k8s.io/apiextensions-apiserver v0.32.1/go.mod h1:sxWIGuGiYov7Io1fAS2X06NjMIk5CbRHc2StSmbaQto=
-k8s.io/apimachinery v0.32.2 h1:yoQBR9ZGkA6Rgmhbp/yuT9/g+4lxtsGYwW6dR6BDPLQ=
-k8s.io/apimachinery v0.32.2/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
-k8s.io/client-go v0.32.2 h1:4dYCD4Nz+9RApM2b/3BtVvBHw54QjMFUl1OLcJG5yOA=
-k8s.io/client-go v0.32.2/go.mod h1:fpZ4oJXclZ3r2nDOv+Ux3XcJutfrwjKTCHz2H3sww94=
+k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U=
+k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
+k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU=
+k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY=
 k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
 k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
 k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y=
 k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4=
-k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
-k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
-sigs.k8s.io/controller-runtime v0.20.3 h1:I6Ln8JfQjHH7JbtCD2HCYHoIzajoRxPNuvhvcDbZgkI=
-sigs.k8s.io/controller-runtime v0.20.3/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY=
-sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
-sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
-sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA=
-sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
+k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0=
+k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+sigs.k8s.io/controller-runtime v0.20.4 h1:X3c+Odnxz+iPTRobG4tp092+CvBU9UK0t/bRf+n0DGU=
+sigs.k8s.io/controller-runtime v0.20.4/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY=
+sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
+sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
+sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016 h1:kXv6kKdoEtedwuqMmkqhbkgvYKeycVbC8+iPCP9j5kQ=
+sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
+sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc=
+sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
 sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
 sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
Index: /branches/main/array-ingress-controller/helmchart/array-ingress/templates/deployment.yaml
===================================================================
--- /branches/main/array-ingress-controller/helmchart/array-ingress/templates/deployment.yaml	(revision 11)
+++ /branches/main/array-ingress-controller/helmchart/array-ingress/templates/deployment.yaml	(working copy)
@@ -1,22 +1,26 @@
 apiVersion: apps/v1
-kind: Deployment
+kind: StatefulSet
 metadata:
   name: {{ include "array-ingress.fullname" . }}
+  annotations:
+    openshift.io/scc: {{ .Values.serviceAccount.scc }}
   labels:
     app: {{ include "array-ingress.name" . }}
     release: {{ .Release.Name }}
     chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
 spec:
+  serviceName: {{ include "array-ingress.fullname" . }}-headless
+  replicas: 3
   selector:
     matchLabels:
       app: {{ include "array-ingress.name" . }}
-      role: array-ingress
+      role: {{ .Values.serviceAccount.clusterRole }}
   template:
     metadata:
       labels:
         app: {{ include "array-ingress.name" . }}
         release: {{ .Release.Name }}
-        role: array-ingress
+        role: {{ .Values.serviceAccount.clusterRole }}
     spec:
       serviceAccountName: {{ .Values.serviceAccount.name }}
       containers:
@@ -28,31 +32,59 @@
           command: ["/bin/bash", "-c"]
           args: ["./array-ingress;"]
           env:
-          - name: APV_IP
-            value: .Values.apv.ip
-          - name: APV_USERNAME
-            value: .Values.apv.username
-          - name: APV_PASSWORD
-            value: .Values.apv.password
-          - name: APV_INSECURE_SKIP
-            value: .Values.apv.insecureskip
-          - name: APV_TIMEOUT
-            value: .Values.apv.timeout
+            - name: PLATFORM
+              value: {{ .Values.platform }}
+            - name: CONTROLLER_TYPE
+              value: {{ .Values.controller_type }}
+            - name: CONTROLLER_NAMESPACE
+              value: {{ .Values.namespace }}
+            - name: APV_IP
+              value: {{ .Values.apv.ip }}
+            - name: APV_USERNAME
+              value: {{ .Values.apv.username }}
+            - name: APV_PASSWORD
+              value: {{ .Values.apv.password }}
+            - name: APV_INSECURE_SKIP
+              value: {{ .Values.apv.insecureskip | quote }}
+            - name: APV_TIMEOUT
+              value: {{ .Values.apv.timeout }}
+            - name: EMAIL_FROM
+              value: {{ .Values.email.from }}
+            - name: EMAIL_PASSWORD
+              value: {{ .Values.email.password }}
+            - name: EMAIL_RECIPIENTS
+              value: {{ .Values.email.recipients }}
+            - name: EMAIL_CC
+              value: {{ .Values.email.cc }}
           volumeMounts:
             - name: log-volume
-              mountPath: /var/log/
-      volumes:
-        - name: log-volume
-          persistentVolumeClaim:
-            claimName: array-ingress-log-pvc
----
-apiVersion: v1
-kind: PersistentVolumeClaim
-metadata:
-  name: array-ingress-log-pvc
-spec:
-  accessModes:
-    - ReadWriteOnce
-  resources:
-    requests:
-      storage: 1Gi
\ No newline at end of file
+              mountPath: {{ .Values.pvc.logMountPath }}
+            - name: db-volume
+              mountPath: {{ .Values.pvc.dbMountPath }}
+          securityContext:
+            capabilities:
+              add: [ "NET_RAW" ]
+              drop: [ "ALL" ]
+            allowPrivilegeEscalation: false
+            privileged: false
+            seccompProfile:
+              type: RuntimeDefault
+
+  volumeClaimTemplates:
+    - metadata:
+        name: log-volume
+      spec:
+        accessModes: [ "ReadWriteOnce" ]
+        storageClassName: {{ .Values.pvc.storageClassName }}
+        resources:
+          requests:
+            storage: {{ .Values.pvc.logSize }}
+
+    - metadata:
+        name: db-volume
+      spec:
+        accessModes: [ "ReadWriteOnce" ]
+        storageClassName: {{ .Values.pvc.storageClassName }}
+        resources:
+          requests:
+            storage: {{ .Values.pvc.dbSize }}
Index: /branches/main/array-ingress-controller/helmchart/array-ingress/templates/serviceaccount.yaml
===================================================================
--- /branches/main/array-ingress-controller/helmchart/array-ingress/templates/serviceaccount.yaml	(revision 11)
+++ /branches/main/array-ingress-controller/helmchart/array-ingress/templates/serviceaccount.yaml	(working copy)
@@ -1,7 +1,7 @@
 apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRole
 metadata:
-  name: ingress-access
+  name: {{ .Values.serviceAccount.clusterRole }}
 rules:
 - apiGroups: ["networking.k8s.io"]
   resources: ["ingresses"]
@@ -10,43 +10,53 @@
   resources: ["routes"]
   verbs: ["get", "list", "watch", "update"]
 - apiGroups: [""]
-  resources: ["services", "pods", "endpoints"]
-  verbs: ["get", "list", "watch", "update"]
+  resources: ["services", "pods", "endpoints", "events"]
+  verbs: ["get", "list", "watch", "create", "update", "patch"]
+- apiGroups: ["coordination.k8s.io"]
+  resources: ["leases"]
+  verbs: ["get", "watch", "list", "create", "update", "patch"]
 ---
 apiVersion: v1
 kind: ServiceAccount
 metadata:
   name: {{ .Values.serviceAccount.name }}
-  namespace: array-ingress
+  namespace: {{ .Values.namespace }}
 ---
 apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRoleBinding
 metadata:
-  name: ingress-access-binding
+  name: "scc-{{ .Values.serviceAccount.name }}"
 roleRef:
   apiGroup: rbac.authorization.k8s.io
   kind: ClusterRole
-  name: ingress-access
+  name: {{ .Values.serviceAccount.clusterRole }}
 subjects:
 - kind: ServiceAccount
   name: {{ .Values.serviceAccount.name }}
-  namespace: array-ingress
+  namespace: {{ .Values.namespace }}
 ---
 apiVersion: security.openshift.io/v1
 kind: SecurityContextConstraints
 metadata:
-  name: ingress-access-scc
-allowPrivilegedContainer: false
-readOnlyRootFilesystem: false
+  name: {{ .Values.serviceAccount.scc }}
+priority: null
+allowPrivilegedContainer: false  # No full privileged access
+readOnlyRootFilesystem: false  # Allows modifying files inside the container
 allowHostPorts: false
-allowHostNetwork: false
+allowHostNetwork: false  # Change to 'true' only if needed
 allowHostPID: false
 allowHostIPC: false
 runAsUser:
-  type: RunAsAny
+  type: RunAsAny  # Allows running as any UID, including root if needed
 seLinuxContext:
   type: RunAsAny
-fsGroup:
-  type: RunAsAny
+volumes:
+  - '*'  # Allows all volume types, including PVCs
+seccompProfiles:
+  - runtime/default
+defaultAddCapabilities: []
+allowedCapabilities:
+  - NET_RAW  # Allows ICMP (ping)
+allowPrivilegeEscalation: false  # Prevents privilege escalation
 users:
-- system:serviceaccount:array-ingress:{{ .Values.serviceAccount.name }}
+  - "system:serviceaccount:{{ .Values.namespace }}:{{ .Values.serviceAccount.name }}"
Index: /branches/main/array-ingress-controller/helmchart/array-ingress/values.yaml
===================================================================
--- /branches/main/array-ingress-controller/helmchart/array-ingress/values.yaml	(revision 11)
+++ /branches/main/array-ingress-controller/helmchart/array-ingress/values.yaml	(working copy)
@@ -2,10 +2,14 @@
 # This is a YAML-formatted file.
 # Declare variables to be passed into your templates.
     
-replicaCount: 1
+replicaCount: 3
+
+namespace: array-ingress
+controller_type: APV
+platform: openshift
 
 image:
-  repository: vinaynagraj/array-ingress
+  repository: arraynetworks/array-ingress
   pullPolicy: Always
   # Overrides the image tag whose default is the chart appVersion.
   tag: 'latest'
@@ -16,15 +20,40 @@
 
 serviceAccount:
   name: array-ingress-ctrl
+  scc: ingress-access-scc
+  clusterRole: ingress-access
+
+securityContext:
+  runAsNonRoot: true
+  runAsUser: null    
+  capabilities:
+    add:
+      - NET_RAW
+    drop:
+      - ALL
 
 resources:
   requests:
     cpu: 100m
     memory: 500Mi
 
+pvc:
+  name: array-ingress-volume
+  logMountPath: /var/log/
+  dbMountPath: /var/db/
+  logSize: 1Gi
+  dbSize: 1Gi
+  storageClassName: gp3-csi
+
 apv:
   ip: "some_ip"
   username: "some_username"
   password: "some_passowrd"
   insecureskip: "true"
   timeout: "5s"
+
+email:
+  from: "example@example.com"
+  password: "example_password"
+  recipients: "recipient1@example.com,recipient2@example.com"
+  cc: "cc@example.com"
Index: /branches/main/array-ingress-controller/main.go
===================================================================
--- /branches/main/array-ingress-controller/main.go	(revision 11)
+++ /branches/main/array-ingress-controller/main.go	(working copy)
@@ -2,46 +2,85 @@
 
 import (
 	"context"
-	"time"
+	"os"
+	"strings"
+
 	"arraynetworks.com/array-ingress-controller/apvadapter"
-	"arraynetworks.com/array-ingress-controller/controller/openshift"
+	"arraynetworks.com/array-ingress-controller/asfadapter"
+	"arraynetworks.com/array-ingress-controller/controller"
+	"arraynetworks.com/array-ingress-controller/controller/openshift-controller"
 	"arraynetworks.com/array-ingress-controller/logger"
 	"arraynetworks.com/array-ingress-controller/watchtower"
+	"sigs.k8s.io/controller-runtime/pkg/manager/signals"
 )
 
 var (
-	log logger.Logger
+	log      logger.Logger
+	platform string
 )
 
 func init() {
 	log = logger.GetLogger()
+	platform = strings.ToLower(os.Getenv("PLATFORM"))
 }
 
 func main() {
 
-	for {
-		ctx, cancel := context.WithCancel(context.Background()) // Create a context
-		done := make(chan error)
+	if platform == "" {
+		log.Fatalf("PLATFORM environment variable is not set. Use 'openshift' or 'kubernetes'.")
+		os.Exit(1)
+	}
+
+	// Use Kubernetes signal handling for graceful shutdown
+	ctx := signals.SetupSignalHandler()
 
-		log.Infof("Starting ApvAdapter...")
-		go apvadapter.StartReconcile()
+	// Create a new leader elector
+	leaderElector, err := controller.NewLeaderElector()
+	if err != nil {
+		log.Fatalf("Failed to create leader elector: %v", err) // Exit on failure
+	}
 
-		log.Infof("Starting Controllers...")
-		go openshift.StartControllers()
+	// Define the leader function
+	leaderFunc := func(ctx context.Context) {
+		log.Infof("Running as leader...")
 
 		log.Infof("Starting WatchTower...")
 		go watchtower.StartMonitoring(ctx)
-    
-		// Wait for the goroutines to complete
-		err := <-done
-
-		// Check for errors and restart all modules
-		if err != nil {
-			log.Errorf("Error occurred:", err)
-			cancel()                     // Cancel the context
-			time.Sleep(10 * time.Second) // Wait before restarting
-			continue
+
+		deviceType := os.Getenv("CONTROLLER_TYPE")
+
+		if deviceType == "APV" {
+			log.Infof("Starting ApvAdapter...")
+			go apvadapter.StartReconcile()
 		}
-	}
 
+		if deviceType == "ASF" {
+			log.Infof("Starting ASF Controller...")
+			go asfadapter.StartReconcile()
+		}
+
+		switch platform {
+		case "openshift":
+			log.Infof("Starting OpenShift Controllers...")
+			go openshift.StartControllers()
+
+		// TODO when kubernetes contoller is deployed
+
+		// case "kubernetes":
+		// 	log.Infof("Starting Kubernetes Controllers...")
+		// 	go kubernetes.StartControllers()
+		default:
+			log.Fatalf("Invalid PLATFORM value: %s. Use 'openshift' or 'kubernetes'.", platform)
+		}
+
+		// Block until leader role is lost
+		<-ctx.Done()
+
+		// If leader role is lost, log and exit
+		log.Fatal("Leader role lost. Exiting pod...")
+	}
+	// Start leader election
+	if err := leaderElector.StartLeaderElection(ctx, leaderFunc); err != nil {
+		log.Fatalf("Leader election failed: %v", err)
+	}
 }
Index: /branches/main/array-ingress-controller/task-manager/openshift/apv/README.md
===================================================================
--- /branches/main/array-ingress-controller/task-manager/openshift/apv/README.md	(nonexistent)
+++ /branches/main/array-ingress-controller/task-manager/openshift/apv/README.md	(working copy)
@@ -0,0 +1,7 @@
+# Running Tests
+
+To run the tests, use the following commands:
+
+```sh
+export TEST=true
+go test -v
Index: /branches/main/array-ingress-controller/task-manager/openshift/apv/apv.go
===================================================================
--- /branches/main/array-ingress-controller/task-manager/openshift/apv/apv.go	(revision 11)
+++ /branches/main/array-ingress-controller/task-manager/openshift/apv/apv.go	(working copy)
@@ -12,7 +12,6 @@
 	"arraynetworks.com/array-ingress-controller/controller/openshift-controller/utils"
 	taskManager "arraynetworks.com/array-ingress-controller/task-manager/task"
 	routev1 "github.com/openshift/api/route/v1"
-
 )
 
 type SetUpFunc func(ctx context.Context, task *taskManager.Task) error
@@ -82,27 +81,52 @@
 		}
 	}
 
-	// Check if health check annotation exists
-	// If it does, update health check
-  
-	if utils.AnnotationExists(task.Route[0], HealthCheckAnnotKey) {
+	health_in_old, _ := ctx.Value("health_in_old").(string)
+	health_in_new, _ := ctx.Value("health_in_new").(string)
+	health_check_updated, _ := ctx.Value("health_check_updated").(string)
+
+	if health_in_old == "True" && health_in_new == "False" {
+		err = deleteHealthCheck(task)
+		if err != nil {
+			return err
+		}
+	} else if health_in_old == "False" && health_in_new == "True" {
+		err = addHealthCheck(task)
+		if err != nil {
+			return err
+		}
+	} else if health_check_updated == "True" {
 		err = updateHealthCheck(task)
 		if err != nil {
 			return err
-
 		}
+	} else {
+		log.Info("HealthCheck might not be present in old or new route. Doing nothing.")
 	}
 
-	// Check if rate limit annotation exists
-	// If it does, update rate limit
+	ratelimit_in_old, _ := ctx.Value("ratelimit_in_old").(string)
+	ratelimit_in_new, _ := ctx.Value("ratelimit_in_new").(string)
+	ratelimit_updated, _ := ctx.Value("ratelimit_updated").(string)
 
-	if utils.AnnotationExists(task.Route[0], RateLimitAnnotKey) {
-		err = updateRateLimit(task)
+	if ratelimit_in_old == "True" && ratelimit_in_new == "False" {
+		err = updateDeleteRateLimit(task)
 		if err != nil {
 			return err
-
 		}
+	} else if ratelimit_in_old == "False" && ratelimit_in_new == "True" {
+		err = updateAddRateLimit(task)
+		if err != nil {
+			return err
+		}
+	} else if ratelimit_updated == "True" {
+		err = updateAddRateLimit(task)
+		if err != nil {
+			return err
+		}
+	} else {
+		log.Info("Ratelimit might not be present in old or new route. Doing nothing.")
 	}
+
 	return nil
 }
 
@@ -198,7 +222,7 @@
 		// Check if rate limit annotation exists
 		// If it does, delete rate limit
 		if utils.AnnotationExists(route, RateLimitAnnotKey) {
-			err = deleteRateLimit(task)
+			err = deleteEndpointRateLimit(task)
 			if err != nil {
 				return err
 			}
@@ -208,61 +232,3 @@
 
 	return nil
 }
-
-/*
-func handleServiceUpdate(ctx context.Context, task *taskManager.Task) error {
-	log.Info("Handling Service Update with additional context data")
-
-	// Initialize the config store
-	configStore, err := configstore.Get()
-	if err != nil {
-		log.Errorf("Error initializing config store: %v", err)
-		return err
-	}
-
-	// Retrieve existing service records
-	var realServices []configstore.RealService
-	if err = configStore.ReadByRouteName(task.Route.Name, &realServices); err != nil {
-		log.Errorf("Error retrieving service records for route %s: %v", task.Route.Name, err)
-		return err
-	}
-
-	// Create a map of current Pod IPs for quick lookup
-	currentPodIPs := make(map[string]bool)
-	for _, pod := range task.Pods {
-		currentPodIPs[pod.Status.PodIP] = true
-	}
-
-	// Update service records with new details from updated service
-	for _, service := range realServices {
-		if _, exists := currentPodIPs[service.IPAddress]; exists {
-			// Pod is still part of the service, update only if necessary
-			updatedProtocol := string(task.Svc.Spec.Ports[0].Protocol)
-			updatedPort := int(task.Svc.Spec.Ports[0].Port)
-
-			if service.Protocol != updatedProtocol || service.Port != updatedPort {
-				service.Protocol = updatedProtocol
-				service.Port = updatedPort
-				service.Status = StatusWaiting
-				service.CurrentOp = OperationUpdate
-
-				if err = configStore.Update(service); err != nil {
-					log.Errorf("Error updating service record for %s: %v", service.ServiceName, err)
-					return err
-				} else {
-					log.Infof("Service record updated successfully for %s", service.ServiceName)
-				}
-			}
-		} else {
-			// Pod is no longer part of the service, delete directly
-			if err = configStore.Delete(service); err != nil {
-				log.Error("Error deleting outdated service record for %s: %v", service.ServiceName, err)
-				return err
-			} else {
-				log.Info("Deleted outdated service record for %s", service.ServiceName)
-			}
-		}
-	}
-	return nil
-}
-*/
\ No newline at end of file
Index: /branches/main/array-ingress-controller/task-manager/openshift/apv/apv_test.go
===================================================================
--- /branches/main/array-ingress-controller/task-manager/openshift/apv/apv_test.go	(nonexistent)
+++ /branches/main/array-ingress-controller/task-manager/openshift/apv/apv_test.go	(working copy)
@@ -0,0 +1,115 @@
+package apv
+
+import (
+	"context"
+	"testing"
+
+	routev1 "github.com/openshift/api/route/v1"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
+
+	taskManager "arraynetworks.com/array-ingress-controller/task-manager/task"
+)
+
+func getTestTaskWithAnnotations() *taskManager.Task {
+	return &taskManager.Task{
+		Route: []*routev1.Route{
+			{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-route",
+					Namespace: "default",
+					Annotations: map[string]string{
+						HealthCheckAnnotKey: `{"type": "http", "interval": 5, "timeout": 10, "retries": 2}`,
+						RateLimitAnnotKey:   `{"max_cps": 100}`,
+					},
+				},
+				Spec: routev1.RouteSpec{
+					Port: &routev1.RoutePort{
+						TargetPort: intstr.FromInt(8081),
+					},
+				},
+			},
+		},
+		EndPts: []*corev1.Endpoints{
+			{
+				Subsets: []corev1.EndpointSubset{
+					{
+						Addresses: []corev1.EndpointAddress{
+							{IP: "10.0.0.1"},
+						},
+					},
+				},
+			},
+		},
+	}
+}
+
+func Test_handleRouteAdd(t *testing.T) {
+	task := getTestTaskWithAnnotations()
+	ctx := context.WithValue(context.Background(), taskManager.TaskTypeKey{}, "ADD")
+	ctx = context.WithValue(ctx, taskManager.ResourceTypeKey{}, "ROUTE")
+
+	apv := &OpenShiftAPV{}
+	err := apv.SetupAPV(ctx, task)
+	if err != nil {
+		t.Errorf("handleRouteAdd failed: %v", err)
+	}
+}
+
+func Test_handleRouteUpdate(t *testing.T) {
+	seedRealService(t)
+
+	task := getTestTaskWithAnnotations()
+	ctx := context.WithValue(context.Background(), taskManager.TaskTypeKey{}, "UPDATE")
+	ctx = context.WithValue(ctx, taskManager.ResourceTypeKey{}, "ROUTE")
+	ctx = context.WithValue(ctx, "spec_updated", "True")
+	ctx = context.WithValue(ctx, "health_check", "True")
+	ctx = context.WithValue(ctx, "ratelimit", "True")
+
+	apv := &OpenShiftAPV{}
+	err := apv.SetupAPV(ctx, task)
+	if err != nil {
+		t.Errorf("handleRouteUpdate failed: %v", err)
+	}
+}
+
+func Test_handleRouteDelete(t *testing.T) {
+	seedRealService(t)
+
+	task := getTestTaskWithAnnotations()
+	ctx := context.WithValue(context.Background(), taskManager.TaskTypeKey{}, "DELETE")
+	ctx = context.WithValue(ctx, taskManager.ResourceTypeKey{}, "ROUTE")
+
+	apv := &OpenShiftAPV{}
+	err := apv.SetupAPV(ctx, task)
+	if err != nil {
+		t.Errorf("handleRouteDelete failed: %v", err)
+	}
+}
+
+func Test_handleEndpointAdd(t *testing.T) {
+	task := getTestTaskWithAnnotations()
+	ctx := context.WithValue(context.Background(), taskManager.TaskTypeKey{}, "ADD")
+	ctx = context.WithValue(ctx, taskManager.ResourceTypeKey{}, "ENDPOINT")
+
+	apv := &OpenShiftAPV{}
+	err := apv.SetupAPV(ctx, task)
+	if err != nil {
+		t.Errorf("handleEndpointAdd failed: %v", err)
+	}
+}
+
+func Test_handleEndpointDelete(t *testing.T) {
+	seedRealService(t)
+
+	task := getTestTaskWithAnnotations()
+	ctx := context.WithValue(context.Background(), taskManager.TaskTypeKey{}, "DELETE")
+	ctx = context.WithValue(ctx, taskManager.ResourceTypeKey{}, "ENDPOINT")
+
+	apv := &OpenShiftAPV{}
+	err := apv.SetupAPV(ctx, task)
+	if err != nil {
+		t.Errorf("handleEndpointDelete failed: %v", err)
+	}
+}
Index: /branches/main/array-ingress-controller/task-manager/openshift/apv/const.go
===================================================================
--- /branches/main/array-ingress-controller/task-manager/openshift/apv/const.go	(revision 11)
+++ /branches/main/array-ingress-controller/task-manager/openshift/apv/const.go	(working copy)
@@ -30,29 +30,28 @@
 	DefaultMaxCPS               = 100    // Maximum connections per second
 	DefaultSoftBandwidth        = 1000   // Soft bandwidth limit in kbps
 	DefaultHardBandwidth        = 1500   // Hard bandwidth limit in kbps
-	DefaultServiceType          = "http" // Service type (http, https, tcp, etc.)
+	DefaultProtocol             = "http" // Service type (http, https, tcp, etc.)
 	DefaultServerConnReuse      = true   // Enable server connection reuse
 	DefaultMaxReq               = 1000   // Maximum requests per second
 	DefaultTimeout              = 5000   // Timeout in milliseconds (5s)
 	DefaultServerPersistentConn = true   // Enable server persistent connection
 
-	HealthCheckAnnotKey = "arraynetworks.com/healthcheck"
-	RateLimitAnnotKey   = "arraynetworks.com/ratelimit"
+	HealthCheckAnnotKey = "apv.arraynetworks.com/healthcheck"
+	RateLimitAnnotKey   = "apv.arraynetworks.com/ratelimit"
 
 	AsfRealServiceAnnotKey = "arraynetworks.com/asfrealservice"
-	DefaultCheckType = "http"
+	DefaultCheckType       = "http"
 )
 
 // Function map to handle different task/resource combinations
 var apvFunctionMap = map[string]SetUpFunc{
-	"ROUTE_ADD":      handleRouteAdd,
-	"ROUTE_UPDATE":   handleRouteUpdate,
-	"ROUTE_DELETE":   handleRouteDelete,
-	"ENDPOINT_ADD":   handleEndpointAdd,
+	"ROUTE_ADD":    handleRouteAdd,
+	"ROUTE_UPDATE": handleRouteUpdate,
+	"ROUTE_DELETE": handleRouteDelete,
+	"ENDPOINT_ADD": handleEndpointAdd,
 	//"ENDPOINT_UPDATE": handleEndpointUpdate,
 	"ENDPOINT_DELETE": handleEndpointDelete,
 	//"POD_ADD":        handlePodAdd,
 	//"POD_DELETE":     handlePodDelete,
 	//"SERVICE_UPDATE": handleServiceUpdate,
 }
-
Index: /branches/main/array-ingress-controller/task-manager/openshift/apv/health.go
===================================================================
--- /branches/main/array-ingress-controller/task-manager/openshift/apv/health.go	(revision 11)
+++ /branches/main/array-ingress-controller/task-manager/openshift/apv/health.go	(working copy)
@@ -4,81 +4,88 @@
 
 package apv
 
-import(
-	"fmt"
+import (
 	"encoding/json"
+	"fmt"
 
 	"arraynetworks.com/array-ingress-controller/configstore"
 	"arraynetworks.com/array-ingress-controller/controller/openshift-controller/utils"
 	taskManager "arraynetworks.com/array-ingress-controller/task-manager/task"
 )
 
-type healthCheckAnnotStruct struct{
+type healthCheckAnnotStruct struct {
 	Path         *string `json:"path,omitempty"`
-    Type         *string `json:"type,omitempty"`
-    Method       *string `json:"method,omitempty"`
-    ExpectedCode *string `json:"expected Code,omitempty"`
-    Interval     *int    `json:"interval,omitempty"`
-    Timeout      *int    `json:"timeout,omitempty"`
-    Retries      *int    `json:"retries,omitempty"`
+	Type         *string `json:"type,omitempty"`
+	Method       *string `json:"method,omitempty"`
+	ExpectedCode *string `json:"expected Code,omitempty"`
+	Interval     *int    `json:"interval,omitempty"`
+	Timeout      *int    `json:"timeout,omitempty"`
+	Retries      *int    `json:"retries,omitempty"`
 }
 
 func addHealthCheck(task *taskManager.Task) error {
-	
+
 	configStore, err := configstore.Get()
 	if err != nil {
 		log.Errorf("Error initializing config store: %v", err)
 		return err
 	}
 
-	annots, exists := utils.FetchJSONStringFromAnnotation(task.Route[0], HealthCheckAnnotKey)
-
-	if !exists {
-		return nil
-	}
+	annots, _ := utils.FetchJSONStringFromAnnotation(task.Route[0], HealthCheckAnnotKey)
 
 	// Unmarshal the JSON string into a HealthCheck struct
-    var healthCheckAnnot healthCheckAnnotStruct
-    err = json.Unmarshal([]byte(annots), &healthCheckAnnot)
-    if err != nil {
-        return fmt.Errorf("error unmarshaling JSON from annotation: %v", err)
-    }
-
-	// Retrieve existing service records
-	var realServices []configstore.RealService
-	if err = configStore.ReadByRouteName(task.Route[0].Name, &realServices); err != nil {
-		log.Errorf("Error retrieving service records for route %s: %v", task.Route[0].Name, err)
-		return err
+	var healthCheckAnnot healthCheckAnnotStruct
+	err = json.Unmarshal([]byte(annots), &healthCheckAnnot)
+	if err != nil {
+		return fmt.Errorf("error unmarshaling JSON from annotation: %v", err)
 	}
+	if task.EndPts == nil || len(task.EndPts) == 0 || task.EndPts[0].Subsets == nil {
+		log.Warnf("No endpoint subsets found for route %s", task.Route[0].Name)
+		return nil
+	}
+	// Create a service entry for each endpoint address
+	for _, subset := range task.EndPts[0].Subsets {
+		for _, address := range subset.Addresses {
+
+			realServiceName := fmt.Sprintf("%s_%s_%s", task.Route[0].Name, task.Route[0].Namespace, address.IP) // using format - routename_namespace_endptIP
+			hcName := "hc" + realServiceName
+
+			// Retrieve existing healthcheck records
+			var healthchecks []configstore.HealthCheck
+			if err = configStore.ReadHealthCheckName(hcName, &healthchecks); err != nil {
+				log.Errorf("Error retrieving healthcheck records for healthcheck %s: %v", hcName, err)
+				return err
+			}
+			if len(healthchecks) > 0 {
+				log.Infof("HealthCheck for route: %s and realservice: %s already present with name %s", task.Route[0].Name, realServiceName, hcName)
+				return nil
+			}
+
+			healthCheck := configstore.HealthCheck{
+				IngressName:   task.Route[0].Name,
+				HCName:        hcName,
+				ServiceName:   realServiceName,
+				Type:          getStringValue(healthCheckAnnot.Type, ""),
+				HcUp:          DefaultHcUp,
+				HcDown:        DefaultHcDown,
+				IPv4:          address.IP,
+				Port:          int(task.Route[0].Spec.Port.TargetPort.IntValue()),
+				SendInterval:  getIntValue(healthCheckAnnot.Interval, 3),
+				ServerTimeout: getIntValue(healthCheckAnnot.Timeout, 5),
+				Retries:       getIntValue(healthCheckAnnot.Retries, 3),
+				Status:        StatusWaiting,
+				CurrentOp:     OperationCreate,
+			}
+
+			if err := configStore.Create(&healthCheck); err != nil {
+				log.Errorf("Error creating health check record for pod %s: %v", realServiceName, err)
+				return err
+			}
 
-	// Set status to delete for each service record
-	for _, realService := range realServices {
-		
-		healthCheck := configstore.HealthCheck{
-			IngressName:   realService.IngressName,
-			HCName:        "hc" + realService.ServiceName,
-			ServiceName:   realService.ServiceName,
-			Type:          getStringValue(healthCheckAnnot.Type, ""),
-			HcUp:          DefaultHcUp,
-			HcDown:        DefaultHcDown,
-			IPv4:          realService.IPAddress,
-			Port:          realService.Port,
-			SendInterval:  getIntValue(healthCheckAnnot.Interval, 3),
-			ServerTimeout: getIntValue(healthCheckAnnot.Timeout, 5),
-			Retries:	   getIntValue(healthCheckAnnot.Retries, 3),
-			Status:        StatusWaiting,
-			CurrentOp:     OperationCreate,
-		}
-
-		if err := configStore.Create(&healthCheck); err != nil {
-			log.Errorf("Error creating health check record for pod %s: %v", realService.ServiceName, err)
-			return err
+			log.Infof("Health check record created successfully for pod %s", realServiceName)
 		}
-
-		log.Infof("Health check record created successfully for pod %s", realService.ServiceName)
-
 	}
-	
+
 	return nil
 }
 
@@ -97,11 +104,11 @@
 	}
 
 	// Unmarshal the JSON string into a HealthCheck struct
-    var healthCheckAnnot healthCheckAnnotStruct
-    err = json.Unmarshal([]byte(annots), &healthCheckAnnot)
-    if err != nil {
-        return fmt.Errorf("error unmarshaling JSON from annotation: %v", err)
-    }
+	var healthCheckAnnot healthCheckAnnotStruct
+	err = json.Unmarshal([]byte(annots), &healthCheckAnnot)
+	if err != nil {
+		return fmt.Errorf("error unmarshaling JSON from annotation: %v", err)
+	}
 
 	// Retrieve existing service records
 	var realServices []configstore.RealService
@@ -112,7 +119,7 @@
 
 	// Set status to delete for each service record
 	for _, realService := range realServices {
-		// TODO: Update only the fields which are changed		
+		// TODO: Update only the fields which are changed
 		healthCheck := configstore.HealthCheck{
 			IngressName:   realService.IngressName,
 			HCName:        "hc" + realService.ServiceName,
@@ -124,14 +131,14 @@
 			Port:          realService.Port,
 			SendInterval:  getIntValue(healthCheckAnnot.Interval, 3),
 			ServerTimeout: getIntValue(healthCheckAnnot.Timeout, 5),
-			Retries:	   getIntValue(healthCheckAnnot.Retries, 3),
+			Retries:       getIntValue(healthCheckAnnot.Retries, 3),
 			Status:        StatusWaiting,
 			CurrentOp:     OperationUpdate,
 		}
 
 		conditions := map[string]interface{}{
 			"hc_name": healthCheck.HCName,
-		}		
+		}
 
 		if err := configStore.Update(&healthCheck, conditions); err != nil {
 			log.Errorf("Error updating health check record for realservice %s: %v", realService.ServiceName, err)
@@ -141,45 +148,65 @@
 		log.Infof("Health check record updated successfully realservice %s", realService.ServiceName)
 
 	}
-	
+
 	return nil
 
 }
 
 func deleteHealthCheck(task *taskManager.Task) error {
-	
+
 	// Initialize the config store
 	configStore, err := configstore.Get()
 	if err != nil {
 		log.Errorf("Error initializing config store: %v", err)
 		return err
 	}
-
-	// Retrieve existing service records
-	var healthchecks []configstore.HealthCheck
-	if err = configStore.ReadByRouteName(task.Route[0].Name, &healthchecks); err != nil {
-		log.Errorf("Error retrieving healthcheck records for route %s: %v", task.Route[0].Name, err)
-		return err
+	if task.EndPts == nil || len(task.EndPts) == 0 || task.EndPts[0].Subsets == nil {
+		log.Warnf("No endpoint subsets found for route %s", task.Route[0].Name)
+		return nil
 	}
-
-	// Set status to delete for each service record
-	for _, healthcheck := range healthchecks {
-		
-		conditions := map[string]interface{}{
-			"hc_name": healthcheck.HCName,
-		}
-
-		healthcheck.Status = StatusWaiting
-		healthcheck.CurrentOp = OperationDelete
-
-		if err = configStore.Update(&healthcheck, conditions); err != nil {
-			log.Errorf("Error updating service record for %s to status 'delete': %v", healthcheck.ServiceName, err)
-			return err
-		} else {
-			log.Infof("Service record status updated to 'delete' for %s", healthcheck.ServiceName)
+	// Create a service entry for each endpoint address
+	for _, subset := range task.EndPts[0].Subsets {
+		for _, address := range subset.Addresses {
+
+			realServiceName := fmt.Sprintf("%s_%s_%s", task.Route[0].Name, task.Route[0].Namespace, address.IP) // using format - routename_namespace_endptIP
+			hcName := "hc" + realServiceName
+
+			// Retrieve existing healthcheck records
+			var healthchecks []configstore.HealthCheck
+			if err = configStore.ReadHealthCheckName(hcName, &healthchecks); err != nil {
+				log.Errorf("Error retrieving healthcheck records for healthcheck %s: %v", hcName, err)
+				return err
+			}
+			if len(healthchecks) == 0 {
+				log.Infof("HealthCheck for route: %s and realservice: %s does not exists", task.Route[0].Name, realServiceName)
+				return nil
+			}
+			healthcheck := healthchecks[0]
+			conditions := map[string]interface{}{
+				"hc_name": hcName,
+			}
+			if healthcheck.Status == StatusFailed && healthcheck.CurrentOp == OperationCreate || healthcheck.Status == StatusWaiting && healthcheck.CurrentOp == OperationCreate {
+				if err := configStore.Delete(&healthcheck, conditions); err != nil {
+					log.Errorf("Error deleting real service %s: %v", healthcheck.HCName, err)
+					return err
+				}
+				log.Infof("Real service %s deleted successfully", healthcheck.HCName)
+				continue
+			}
+
+			healthcheck.Status = StatusWaiting
+			healthcheck.CurrentOp = OperationDelete
+
+			if err = configStore.Update(&healthcheck, conditions); err != nil {
+				log.Errorf("Error updating healthcheck record for %s to status 'delete': %v", realServiceName, err)
+				return err
+			} else {
+				log.Infof("HealthCheck record status updated to 'delete' for %s", realServiceName)
+			}
 		}
 	}
 
 	return nil
 
-}
\ No newline at end of file
+}
Index: /branches/main/array-ingress-controller/task-manager/openshift/apv/health_test.go
===================================================================
--- /branches/main/array-ingress-controller/task-manager/openshift/apv/health_test.go	(nonexistent)
+++ /branches/main/array-ingress-controller/task-manager/openshift/apv/health_test.go	(working copy)
@@ -0,0 +1,173 @@
+package apv
+
+import (
+	"testing"
+
+	routev1 "github.com/openshift/api/route/v1"
+	"gorm.io/driver/sqlite"
+	"gorm.io/gorm"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
+
+	"arraynetworks.com/array-ingress-controller/configstore"
+	taskManager "arraynetworks.com/array-ingress-controller/task-manager/task"
+)
+
+func Test_addHealthCheck(t *testing.T) {
+	db, err := gorm.Open(sqlite.Open("test_db.db"), &gorm.Config{})
+	if err != nil {
+		t.Fatalf("failed to open db: %v", err)
+	}
+
+	// Seed RealService
+	err = db.Create(&configstore.RealService{
+		IngressName: "test-route",
+		ServiceName: "test-route_default_10.0.0.1",
+		Protocol:    "HTTP",
+		IPAddress:   "10.0.0.1",
+		Enable:      true,
+		Port:        8081,
+		MaxConn:     100,
+		Status:      StatusWaiting,
+		CurrentOp:   OperationCreate,
+	}).Error
+	if err != nil {
+		t.Fatalf("seeding real service failed: %v", err)
+	}
+
+	task := &taskManager.Task{
+		Route: []*routev1.Route{
+			{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-route",
+					Namespace: "default",
+					Annotations: map[string]string{
+						HealthCheckAnnotKey: `{"type": "http", "interval": 5, "timeout": 10, "retries": 2}`,
+					},
+				},
+				Spec: routev1.RouteSpec{
+					Port: &routev1.RoutePort{
+						TargetPort: intstr.FromInt(8081),
+					},
+				},
+			},
+		},
+		EndPts: []*corev1.Endpoints{
+			{
+				Subsets: []corev1.EndpointSubset{
+					{
+						Addresses: []corev1.EndpointAddress{
+							{IP: "10.0.0.1"},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	t.Log("Calling addHealthCheck for the first time")
+	err = addHealthCheck(task)
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+}
+
+func Test_updateHealthCheck(t *testing.T) {
+	db, err := gorm.Open(sqlite.Open("test_db.db"), &gorm.Config{})
+	if err != nil {
+		t.Fatalf("failed to open db: %v", err)
+	}
+
+	err = db.Create(&configstore.HealthCheck{
+		IngressName:   "test-route",
+		HCName:        "hctest-route_default_10.0.0.1",
+		ServiceName:   "test-route_default_10.0.0.1",
+		Type:          "http",
+		IPv4:          "10.0.0.1",
+		Port:          8081,
+		SendInterval:  5,
+		ServerTimeout: 10,
+		Retries:       2,
+		Status:        StatusWaiting,
+		CurrentOp:     OperationCreate,
+	}).Error
+	if err != nil {
+		t.Fatalf("seeding health check failed: %v", err)
+	}
+
+	task := &taskManager.Task{
+		Route: []*routev1.Route{
+			{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-route",
+					Namespace: "default",
+					Annotations: map[string]string{
+						HealthCheckAnnotKey: `{"type": "tcp", "interval": 7, "timeout": 12, "retries": 3}`,
+					},
+				},
+				Spec: routev1.RouteSpec{
+					Port: &routev1.RoutePort{
+						TargetPort: intstr.FromInt(8081),
+					},
+				},
+			},
+		},
+	}
+
+	err = updateHealthCheck(task)
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+}
+
+func Test_deleteHealthCheck(t *testing.T) {
+	db, err := gorm.Open(sqlite.Open("test_db.db"), &gorm.Config{})
+	if err != nil {
+		t.Fatalf("failed to open db: %v", err)
+	}
+
+	err = db.Create(&configstore.HealthCheck{
+		IngressName:   "test-route",
+		HCName:        "hctest-route_default_10.0.0.1",
+		ServiceName:   "test-route_default_10.0.0.1",
+		Type:          "http",
+		IPv4:          "10.0.0.1",
+		Port:          8081,
+		SendInterval:  5,
+		ServerTimeout: 10,
+		Retries:       2,
+		Status:        StatusWaiting,
+		CurrentOp:     OperationCreate,
+	}).Error
+	if err != nil {
+		t.Fatalf("seeding health check failed: %v", err)
+	}
+
+	task := &taskManager.Task{
+		Route: []*routev1.Route{
+			{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-route",
+					Namespace: "default",
+				},
+			},
+		},
+		EndPts: []*corev1.Endpoints{
+			{
+				Subsets: []corev1.EndpointSubset{
+					{
+						Addresses: []corev1.EndpointAddress{
+							{IP: "10.0.0.1"},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	err = deleteHealthCheck(task)
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+}
Index: /branches/main/array-ingress-controller/task-manager/openshift/apv/init.go
===================================================================
--- /branches/main/array-ingress-controller/task-manager/openshift/apv/init.go	(revision 11)
+++ /branches/main/array-ingress-controller/task-manager/openshift/apv/init.go	(working copy)
@@ -2,16 +2,16 @@
 	Copyright 2025 Array Networks
 */
 
- package apv
+package apv
 
- import(
-	 "arraynetworks.com/array-ingress-controller/logger"
- )
- 
- var (
-	 log	        logger.Logger
- )
- 
- func init(){
-	 log	= logger.GetLogger()
- }
\ No newline at end of file
+import (
+	"arraynetworks.com/array-ingress-controller/logger"
+)
+
+var (
+	log logger.Logger
+)
+
+func init() {
+	log = logger.GetLogger()
+}
Index: /branches/main/array-ingress-controller/task-manager/openshift/apv/ratelimit.go
===================================================================
--- /branches/main/array-ingress-controller/task-manager/openshift/apv/ratelimit.go	(revision 11)
+++ /branches/main/array-ingress-controller/task-manager/openshift/apv/ratelimit.go	(working copy)
@@ -17,7 +17,7 @@
 	MaxCPS               *int    `json:"max_cps,omitempty"`
 	SoftBW               *int    `json:"soft_bandwidth,omitempty"`
 	HardBW               *int    `json:"hard_bandwidth,omitempty"`
-	ServiceType          *string `json:"service_type,omitempty"`
+	Protocol             *string `json:"protocol,omitempty"`
 	MaxReq               *int    `json:"max_req,omitempty"`
 	Timeout              *int    `json:"time_out,omitempty"`
 	ServerConnReuse      *bool   `json:"server_conn_reuse,omitempty"`
@@ -27,86 +27,95 @@
 func addRateLimit(task *taskManager.Task) error {
 	configStore, err := configstore.Get()
 	if err != nil {
-		log.Errorf("Error initializing config store: %v", err)
+		log.Errorf("Error initializing Configstore: %v", err)
 		return err
 	}
 
-	annots, exists := utils.FetchJSONStringFromAnnotation(task.Route[0], RateLimitAnnotKey)
-	if !exists {
-		return nil
-	}
+	annots, _ := utils.FetchJSONStringFromAnnotation(task.Route[0], RateLimitAnnotKey)
 
-	// Unmarshal the JSON string into a HealthCheck struct
+	// Unmarshal the JSON string into a RateLimit struct
 	var rateLimitAnnot RateLimitAnnotStruct
 	err = json.Unmarshal([]byte(annots), &rateLimitAnnot)
 	if err != nil {
-		return fmt.Errorf("error unmarshaling JSON from annotation: %v", err)
+		return fmt.Errorf("Error unmarshaling JSON from annotation: %v", err)
 	}
-
-	// Retrieve existing service records
-	var realServices []configstore.RealService
-	if err = configStore.ReadByRouteName(task.Route[0].Name, &realServices); err != nil {
-		log.Errorf("Error retrieving service records for route %s: %v", task.Route[0].Name, err)
-		return err
+	if task.EndPts == nil || len(task.EndPts) == 0 || task.EndPts[0].Subsets == nil {
+		log.Warnf("No endpoint subsets found for route %s", task.Route[0].Name)
+		return nil
 	}
+	// Create a service entry for each endpoint address
+	for _, subset := range task.EndPts[0].Subsets {
+		for _, address := range subset.Addresses {
+
+			realServiceName := fmt.Sprintf("%s_%s_%s", task.Route[0].Name, task.Route[0].Namespace, address.IP) // using format - routename_namespace_endptIP
+
+			// Retrieve existing RateLimit records
+			var rateLimits []configstore.RateLimit
+			if err = configStore.ReadByRealServiceName(realServiceName, &rateLimits); err != nil {
+				log.Errorf("Error retrieving RateLimit records for service %s: %v", realServiceName, err)
+				return err
+			}
+			if len(rateLimits) > 0 {
+				log.Infof("RateLimit for route: %s and realservice: %s already present with name %s", task.Route[0].Name, realServiceName, realServiceName)
+				return nil
+			}
+
+			rateLimit := configstore.RateLimit{
+				IngressName:          task.Route[0].Name,
+				ServiceName:          realServiceName,
+				MaxCPS:               getIntValue(rateLimitAnnot.MaxCPS, DefaultMaxCPS),
+				SoftBandwidth:        getIntValue(rateLimitAnnot.SoftBW, DefaultSoftBandwidth),
+				HardBandwidth:        getIntValue(rateLimitAnnot.HardBW, DefaultHardBandwidth),
+				Protocol:             getStringValue(rateLimitAnnot.Protocol, DefaultProtocol),
+				ServerConnReuse:      getBoolValue(rateLimitAnnot.ServerConnReuse, DefaultServerConnReuse),
+				MaxReq:               getIntValue(rateLimitAnnot.MaxReq, DefaultMaxReq),
+				Timeout:              getIntValue(rateLimitAnnot.Timeout, DefaultTimeout),
+				ServerPersistentConn: getBoolValue(rateLimitAnnot.ServerPersistentConn, DefaultServerPersistentConn),
+				Status:               StatusWaiting,
+				CurrentOp:            OperationCreate,
+			}
+
+			if err := configStore.Create(&rateLimit); err != nil {
+				log.Errorf("Error creating RateLimit record for Pod %s: %v", realServiceName, err)
+				return err
+			}
 
-	// Set status to delete for each service record
-	for _, realService := range realServices {
-
-		rateLimit := configstore.RateLimit{
-			IngressName:          realService.IngressName,
-			ServiceName:          realService.ServiceName,
-			MaxCPS:               getIntValue(rateLimitAnnot.MaxCPS, DefaultMaxCPS),
-			SoftBandwidth:        getIntValue(rateLimitAnnot.SoftBW, DefaultSoftBandwidth),
-			HardBandwidth:        getIntValue(rateLimitAnnot.HardBW, DefaultHardBandwidth),
-			ServiceType:          getStringValue(rateLimitAnnot.ServiceType, DefaultServiceType),
-			ServerConnReuse:      getBoolValue(rateLimitAnnot.ServerConnReuse, DefaultServerConnReuse),
-			MaxReq:               getIntValue(rateLimitAnnot.MaxReq, DefaultMaxReq),
-			Timeout:              getIntValue(rateLimitAnnot.Timeout, DefaultTimeout),
-			ServerPersistentConn: getBoolValue(rateLimitAnnot.ServerPersistentConn, DefaultServerPersistentConn),
-			Status:               StatusWaiting,
-			CurrentOp:            OperationCreate,
-		}
-
-		if err := configStore.Create(&rateLimit); err != nil {
-			log.Errorf("Error creating rate limit record for pod %s: %v", realService.ServiceName, err)
-			return err
+			log.Infof("RateLimit record created successfully for Pod %s", realServiceName)
 		}
-		log.Infof("Health check record created successfully for pod %s", realService.ServiceName)
 	}
 
 	return nil
 }
 
-func updateRateLimit(task *taskManager.Task) error {
-	
+func updateAddRateLimit(task *taskManager.Task) error {
+
 	configStore, err := configstore.Get()
-	
+
 	if err != nil {
-		log.Errorf("Error initializing config store: %v", err)
+		log.Errorf("Error initializing Configstore: %v", err)
 		return err
 	}
-	
+
 	annots, exists := utils.FetchJSONStringFromAnnotation(task.Route[0], RateLimitAnnotKey)
 	if !exists {
 		return nil
 	}
-	
+
 	// Unmarshal the JSON string into a HealthCheck struct
 	var rateLimitAnnot RateLimitAnnotStruct
-	
+
 	err = json.Unmarshal([]byte(annots), &rateLimitAnnot)
 	if err != nil {
-		return fmt.Errorf("error unmarshaling JSON from annotation: %v", err)
+		return fmt.Errorf("Error unmarshaling JSON from Annotation: %v", err)
 	}
-	
+
 	// Retrieve existing service records
 	var realServices []configstore.RealService
 	if err = configStore.ReadByRouteName(task.Route[0].Name, &realServices); err != nil {
-		log.Errorf("Error retrieving service records for route %s: %v", task.Route[0].Name, err)
+		log.Errorf("Error retrieving service records for Route %s: %v", task.Route[0].Name, err)
 		return err
 	}
-	
+
 	// Set status to update for each service record
 	for _, realService := range realServices {
 		// TODO: Update only the fields which are changed
@@ -116,7 +125,7 @@
 			MaxCPS:               getIntValue(rateLimitAnnot.MaxCPS, DefaultMaxCPS),
 			SoftBandwidth:        getIntValue(rateLimitAnnot.SoftBW, DefaultSoftBandwidth),
 			HardBandwidth:        getIntValue(rateLimitAnnot.HardBW, DefaultHardBandwidth),
-			ServiceType:          getStringValue(rateLimitAnnot.ServiceType, DefaultServiceType),
+			Protocol:             getStringValue(rateLimitAnnot.Protocol, DefaultProtocol),
 			ServerConnReuse:      getBoolValue(rateLimitAnnot.ServerConnReuse, DefaultServerConnReuse),
 			MaxReq:               getIntValue(rateLimitAnnot.MaxReq, DefaultMaxReq),
 			Timeout:              getIntValue(rateLimitAnnot.Timeout, DefaultTimeout),
@@ -130,11 +139,11 @@
 		}
 
 		if err := configStore.Update(&rateLimit, conditions); err != nil {
-			log.Errorf("Error updating rate limit record for realservice %s: %v", realService.ServiceName, err)
+			log.Errorf("Error updating Ratelimit record for Realservice %s: %v", realService.ServiceName, err)
 			return err
 		}
 
-		log.Infof("Rate limit record updated successfully realservice %s", realService.ServiceName)
+		log.Infof("Ratelimit record updated successfully Realservice %s", realService.ServiceName)
 
 	}
 
@@ -142,37 +151,155 @@
 
 }
 
-func deleteRateLimit(task *taskManager.Task) error {
-
-	// Initialize the config store
+func updateDeleteRateLimit(task *taskManager.Task) error {
 	configStore, err := configstore.Get()
 	if err != nil {
-		log.Errorf("Error initializing config store: %v", err)
+		log.Errorf("Error initializing Configstore: %v", err)
 		return err
 	}
 
 	// Retrieve existing service records
-	var rateLimits []configstore.RateLimit
-	if err = configStore.ReadByRouteName(task.Route[0].Name, &rateLimits); err != nil {
-		log.Errorf("Error retrieving rate limit records for route %s: %v", task.Route[0].Name, err)
+	var realServices []configstore.RealService
+	if err = configStore.ReadByRouteName(task.Route[0].Name, &realServices); err != nil {
+		log.Errorf("Error retrieving service records for Route %s: %v", task.Route[0].Name, err)
 		return err
 	}
 
-	// Set status to delete for each service record
-	for _, rateLimit := range rateLimits {
-		rateLimit.Status = StatusWaiting
-		rateLimit.CurrentOp = OperationDelete
+	// Reset rate limit values to defaults
+	for _, realService := range realServices {
+		rateLimit := configstore.RateLimit{
+			IngressName:          realService.IngressName,
+			ServiceName:          realService.ServiceName,
+			MaxCPS:               DefaultMaxCPS,
+			SoftBandwidth:        DefaultSoftBandwidth,
+			HardBandwidth:        DefaultHardBandwidth,
+			Protocol:             DefaultProtocol,
+			MaxReq:               DefaultMaxReq,
+			Timeout:              DefaultTimeout,
+			ServerConnReuse:      DefaultServerConnReuse,
+			ServerPersistentConn: DefaultServerPersistentConn,
+			Status:               StatusWaiting,
+			CurrentOp:            OperationDelete,
+		}
+
 		conditions := map[string]interface{}{
 			"service_name": rateLimit.ServiceName,
 		}
-		if err = configStore.Update(&rateLimit, conditions); err != nil {
-			log.Errorf("Error updating service record for %s to status '%s': %v", rateLimit.ServiceName, StatusDeleted, err)
+
+		if err := configStore.Update(&rateLimit, conditions); err != nil {
+			log.Errorf("Error resetting RateLimit for %s: %v", rateLimit.ServiceName, err)
 			return err
-		} else {
-			log.Infof("Service record status updated to '%s' for %s", StatusDeleted, rateLimit.ServiceName)
+		}
+
+		log.Infof("RateLimit for %s has been reset to default", rateLimit.ServiceName)
+	}
+
+	return nil
+}
+
+func deleteRateLimit(task *taskManager.Task) error {
+	// Initialize the config store
+	configStore, err := configstore.Get()
+	if err != nil {
+		log.Errorf("Error initializing ConfigStore: %v", err)
+		return err
+	}
+
+	if task.EndPts == nil || len(task.EndPts) == 0 || task.EndPts[0].Subsets == nil {
+		log.Warnf("No endpoint subsets found for route %s", task.Route[0].Name)
+		return nil
+	}
+
+	// Create a service entry for each endpoint address
+	for _, subset := range task.EndPts[0].Subsets {
+		for _, address := range subset.Addresses {
+			realServiceName := fmt.Sprintf("%s_%s_%s", task.Route[0].Name, task.Route[0].Namespace, address.IP) // routename_namespace_endptIP
+
+			// Retrieve existing RateLimit records
+			var rateLimits []configstore.RateLimit
+			if err = configStore.ReadRateLimitByServiceName(realServiceName, &rateLimits); err != nil {
+				log.Errorf("Error retrieving RateLimit records for %s: %v", realServiceName, err)
+				return err
+			}
+
+			if len(rateLimits) == 0 {
+				log.Infof("RateLimit for route: %s and real service: %s does not exist", task.Route[0].Name, realServiceName)
+				continue
+			}
+
+			rateLimit := rateLimits[0]
+			conditions := map[string]interface{}{
+				"service_name": rateLimit.ServiceName,
+			}
+
+			// If RateLimit creation failed, delete it completely
+			if rateLimit.Status == StatusFailed && rateLimit.CurrentOp == OperationCreate || rateLimit.Status == StatusWaiting && rateLimit.CurrentOp == OperationCreate {
+				if err := configStore.Delete(&rateLimit, conditions); err != nil {
+					log.Errorf("Error deleting RateLimit %s: %v", rateLimit.ServiceName, err)
+					return err
+				}
+				log.Infof("RateLimit %s deleted successfully", rateLimit.ServiceName)
+				continue
+			}
+
+			// Otherwise, mark RateLimit for deletion
+			rateLimit.Status = StatusWaiting
+			rateLimit.CurrentOp = OperationDelete
+
+			if err = configStore.Update(&rateLimit, conditions); err != nil {
+				log.Errorf("Error updating RateLimit record for %s to status 'delete': %v", realServiceName, err)
+				return err
+			} else {
+				log.Infof("RateLimit record status updated to 'delete' for %s", realServiceName)
+			}
 		}
 	}
 
 	return nil
+}
 
+func deleteEndpointRateLimit(task *taskManager.Task) error {
+	// Initialize the config store
+	configStore, err := configstore.Get()
+	if err != nil {
+		log.Errorf("Error initializing Configstore: %v", err)
+		return err
+	}
+	if task.EndPts == nil || len(task.EndPts) == 0 || task.EndPts[0].Subsets == nil {
+		log.Warnf("No endpoint subsets found for route %s", task.Route[0].Name)
+		return nil
+	}
+	// Create a service entry for each endpoint address
+	for _, subset := range task.EndPts[0].Subsets {
+		for _, address := range subset.Addresses {
+
+			realServiceName := fmt.Sprintf("%s_%s_%s", task.Route[0].Name, task.Route[0].Namespace, address.IP) // using format - routename_namespace_endptIP
+
+			// Retrieve existing RateLimit records
+			var rateLimits []configstore.RateLimit
+			if err = configStore.ReadRateLimitByServiceName(realServiceName, &rateLimits); err != nil {
+				log.Errorf("Error retrieving RateLimit records for service %s: %v", realServiceName, err)
+				return err
+			}
+			if len(rateLimits) == 0 {
+				log.Infof("RateLimit for route: %s and realservice: %s does not exist", task.Route[0].Name, realServiceName)
+				return nil
+			}
+
+			rateLimit := rateLimits[0]
+			rateLimit.Status = StatusWaiting
+			rateLimit.CurrentOp = OperationDelete
+			conditions := map[string]interface{}{
+				"service_name": rateLimit.ServiceName,
+			}
+			if err = configStore.Update(&rateLimit, conditions); err != nil {
+				log.Errorf("Error updating RateLimit record for %s to status 'delete': %v", realServiceName, err)
+				return err
+			} else {
+				log.Infof("RateLimit record status updated to 'delete' for %s", realServiceName)
+			}
+		}
+	}
+
+	return nil
 }
Index: /branches/main/array-ingress-controller/task-manager/openshift/apv/ratelimit_test.go
===================================================================
--- /branches/main/array-ingress-controller/task-manager/openshift/apv/ratelimit_test.go	(nonexistent)
+++ /branches/main/array-ingress-controller/task-manager/openshift/apv/ratelimit_test.go	(working copy)
@@ -0,0 +1,173 @@
+package apv
+
+import (
+	"testing"
+
+	routev1 "github.com/openshift/api/route/v1"
+	"gorm.io/driver/sqlite"
+	"gorm.io/gorm"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
+
+	"arraynetworks.com/array-ingress-controller/configstore"
+	taskManager "arraynetworks.com/array-ingress-controller/task-manager/task"
+)
+
+func seedRealService(t *testing.T) {
+	db, err := gorm.Open(sqlite.Open("test_db.db"), &gorm.Config{})
+	if err != nil {
+		t.Fatalf("failed to open db: %v", err)
+	}
+
+	err = db.Create(&configstore.RealService{
+		IngressName: "test-route",
+		ServiceName: "test-route_default_10.0.0.1",
+		Protocol:    "HTTP",
+		IPAddress:   "10.0.0.1",
+		Enable:      true,
+		Port:        8081,
+		MaxConn:     100,
+		Status:      StatusWaiting,
+		CurrentOp:   OperationCreate,
+	}).Error
+	if err != nil {
+		t.Fatalf("seeding real service failed: %v", err)
+	}
+}
+
+func Test_addRateLimit(t *testing.T) {
+	seedRealService(t)
+
+	task := &taskManager.Task{
+		Route: []*routev1.Route{
+			{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-route",
+					Namespace: "default",
+					Annotations: map[string]string{
+						RateLimitAnnotKey: `{"max_cps": 200, "soft_bandwidth": 500, "hard_bandwidth": 1000, "protocol": "http", "max_req": 20, "time_out": 15, "server_conn_reuse": true, "server_persistent_conn": true}`,
+					},
+				},
+				Spec: routev1.RouteSpec{
+					Port: &routev1.RoutePort{
+						TargetPort: intstr.FromInt(8081),
+					},
+				},
+			},
+		},
+		EndPts: []*corev1.Endpoints{
+			{
+				Subsets: []corev1.EndpointSubset{
+					{
+						Addresses: []corev1.EndpointAddress{
+							{IP: "10.0.0.1"},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	err := addRateLimit(task)
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+}
+
+func Test_updateRateLimit(t *testing.T) {
+	seedRealService(t)
+
+	db, err := gorm.Open(sqlite.Open("test_db.db"), &gorm.Config{})
+	if err != nil {
+		t.Fatalf("failed to open db: %v", err)
+	}
+
+	err = db.Create(&configstore.RateLimit{
+		IngressName:          "test-route",
+		ServiceName:          "test-route_default_10.0.0.1",
+		MaxCPS:               100,
+		SoftBandwidth:        200,
+		HardBandwidth:        400,
+		Protocol:             "tcp",
+		MaxReq:               10,
+		Timeout:              10,
+		ServerConnReuse:      false,
+		ServerPersistentConn: false,
+		Status:               StatusWaiting,
+		CurrentOp:            OperationCreate,
+	}).Error
+	if err != nil {
+		t.Fatalf("seeding ratelimit failed: %v", err)
+	}
+
+	task := &taskManager.Task{
+		Route: []*routev1.Route{
+			{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-route",
+					Namespace: "default",
+					Annotations: map[string]string{
+						RateLimitAnnotKey: `{"max_cps": 150, "soft_bandwidth": 300, "hard_bandwidth": 600, "protocol": "tcp", "max_req": 15, "time_out": 20, "server_conn_reuse": true, "server_persistent_conn": false}`,
+					},
+				},
+				Spec: routev1.RouteSpec{
+					Port: &routev1.RoutePort{
+						TargetPort: intstr.FromInt(8081),
+					},
+				},
+			},
+		},
+	}
+
+	err = updateAddRateLimit(task)
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+}
+
+func Test_deleteRateLimit(t *testing.T) {
+	seedRealService(t)
+
+	db, err := gorm.Open(sqlite.Open("test_db.db"), &gorm.Config{})
+	if err != nil {
+		t.Fatalf("failed to open db: %v", err)
+	}
+
+	err = db.Create(&configstore.RateLimit{
+		IngressName:          "test-route",
+		ServiceName:          "test-route_default_10.0.0.1",
+		MaxCPS:               150,
+		SoftBandwidth:        300,
+		HardBandwidth:        600,
+		Protocol:             "tcp",
+		MaxReq:               15,
+		Timeout:              20,
+		ServerConnReuse:      true,
+		ServerPersistentConn: false,
+		Status:               StatusWaiting,
+		CurrentOp:            OperationUpdate,
+	}).Error
+	if err != nil {
+		t.Fatalf("seeding ratelimit failed: %v", err)
+	}
+
+	task := &taskManager.Task{
+		Route: []*routev1.Route{
+			{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:        "test-route",
+					Namespace:   "default",
+					Annotations: map[string]string{
+						// Simulate annotation removal by omitting RateLimitAnnotKey
+					},
+				},
+			},
+		},
+	}
+
+	err = deleteRateLimit(task)
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+}
Index: /branches/main/array-ingress-controller/task-manager/openshift/apv/realservice.go
===================================================================
--- /branches/main/array-ingress-controller/task-manager/openshift/apv/realservice.go	(revision 11)
+++ /branches/main/array-ingress-controller/task-manager/openshift/apv/realservice.go	(working copy)
@@ -2,6 +2,7 @@
 
 import (
 	"fmt"
+
 	"arraynetworks.com/array-ingress-controller/configstore"
 	"arraynetworks.com/array-ingress-controller/controller/openshift-controller/utils"
 	taskManager "arraynetworks.com/array-ingress-controller/task-manager/task"
@@ -16,13 +17,28 @@
 		log.Error("Error initializing config store: %v", err)
 		return err
 	}
-
+	if task.EndPts == nil || len(task.EndPts) == 0 || task.EndPts[0].Subsets == nil {
+		log.Warnf("No endpoint subsets found for route %s", task.Route[0].Name)
+		return nil
+	}
 	// Create a service entry for each endpoint address
 	for _, subset := range task.EndPts[0].Subsets {
 		for _, address := range subset.Addresses {
 
 			realServiceName := fmt.Sprintf("%s_%s_%s", task.Route[0].Name, task.Route[0].Namespace, address.IP) // using format - routename_namespace_endptIP
-			
+
+			// Retrieve existing service records
+			var realServices []configstore.RealService
+			if err = configStore.ReadByRealServiceName(realServiceName, &realServices); err != nil {
+				log.Errorf("Error retrieving service records for route %s: %v", task.Route[0].Name, err)
+				return err
+			}
+
+			if len(realServices) > 0 {
+				log.Infof("RealService for route: %s and ip: %s already present with name %s", task.Route[0].Name, address.IP, realServiceName)
+				return nil
+			}
+
 			realService := configstore.RealService{
 				IngressName: task.Route[0].Name,
 				ServiceName: realServiceName,
@@ -36,8 +52,6 @@
 				CurrentOp:   OperationCreate,
 			}
 
-			
-
 			if err = configStore.Create(&realService); err != nil {
 				log.Errorf("Error creating service record for pod %s: %v", realServiceName, err)
 				return err
@@ -51,7 +65,7 @@
 }
 
 func deleteRealService(task *taskManager.Task) error {
-	
+
 	// Initialize the config store
 	configStore, err := configstore.Get()
 	if err != nil {
@@ -65,22 +79,50 @@
 		log.Errorf("Error retrieving service records for route %s: %v", task.Route[0].Name, err)
 		return err
 	}
+	if task.EndPts == nil || len(task.EndPts) == 0 || task.EndPts[0].Subsets == nil {
+		log.Warnf("No endpoint subsets found for route %s", task.Route[0].Name)
+		return nil
+	}
+	// Create a service entry for each endpoint address
+	for _, subset := range task.EndPts[0].Subsets {
+		for _, address := range subset.Addresses {
 
-	// Set status to delete for each service record
-	for _, realService := range realServices {
-		
-		conditions := map[string]interface{}{
-			"service_name": realService.ServiceName,
-		}
+			realServiceName := fmt.Sprintf("%s_%s_%s", task.Route[0].Name, task.Route[0].Namespace, address.IP) // using format - routename_namespace_endptIP
 
-		realService.Status = StatusWaiting
-		realService.CurrentOp = OperationDelete
-		
-		if err = configStore.Update(&realService, conditions); err != nil {
-			log.Errorf("Error updating service record for %s to status 'delete': %v", realService.ServiceName, err)
-			return err
-		} else {
-			log.Infof("Service record status updated to 'delete' for %s", realService.ServiceName)
+			// Retrieve existing service records
+			var realServices []configstore.RealService
+			if err = configStore.ReadByRealServiceName(realServiceName, &realServices); err != nil {
+				log.Errorf("Error retrieving service records for route %s: %v", task.Route[0].Name, err)
+				return err
+			}
+			// Check if there is atleast one realservice
+			if len(realServices) == 0 {
+				log.Warnf("No real service found for %s", realServiceName)
+				continue
+			}
+			realService := realServices[0]
+			conditions := map[string]interface{}{
+				"service_name": realServiceName,
+			}
+			// If Realservice is in failed state and current operation is create, delete the record
+			if realService.Status == StatusFailed && realService.CurrentOp == OperationCreate || realService.Status == StatusWaiting && realService.CurrentOp == OperationCreate {
+				if err := configStore.Delete(&realService, conditions); err != nil {
+					log.Errorf("Error deleting real service %s: %v", realServiceName, err)
+					return err
+				}
+				log.Infof("Real service %s deleted successfully", realServiceName)
+				continue
+			}
+
+			realService.Status = StatusWaiting
+			realService.CurrentOp = OperationDelete
+
+			if err = configStore.Update(&realService, conditions); err != nil {
+				log.Errorf("Error updating service record for %s to status 'delete': %v", realService.ServiceName, err)
+				return err
+			} else {
+				log.Infof("Service record status updated to 'delete' for %s", realService.ServiceName)
+			}
 		}
 	}
 
@@ -105,7 +147,7 @@
 
 	// Update service records
 	for _, realService := range realServices {
-		
+
 		realService.Status = StatusWaiting
 		realService.CurrentOp = OperationUpdate
 		realService.Port = int(task.Route[0].Spec.Port.TargetPort.IntValue())
@@ -126,5 +168,5 @@
 	}
 
 	return nil
-	
-}
\ No newline at end of file
+
+}
Index: /branches/main/array-ingress-controller/task-manager/openshift/apv/realservice_test.go
===================================================================
--- /branches/main/array-ingress-controller/task-manager/openshift/apv/realservice_test.go	(nonexistent)
+++ /branches/main/array-ingress-controller/task-manager/openshift/apv/realservice_test.go	(working copy)
@@ -0,0 +1,204 @@
+package apv
+
+import (
+	"fmt"
+	"testing"
+
+	routev1 "github.com/openshift/api/route/v1"
+	"gorm.io/driver/sqlite"
+	"gorm.io/gorm"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
+
+	"arraynetworks.com/array-ingress-controller/configstore"
+	taskManager "arraynetworks.com/array-ingress-controller/task-manager/task"
+)
+
+func resetDatabase() {
+
+	db, err := gorm.Open(sqlite.Open("test_db.db"), &gorm.Config{})
+	if err != nil {
+		log.Errorf("Failed to create a fresh database: %v", err)
+		return
+	}
+
+	// Drop existing tables to ensure a clean slate
+	db.Migrator().DropTable(&configstore.RealService{}, &configstore.HealthCheck{})
+
+	// Ensure table structure is recreated
+	err = db.AutoMigrate(&configstore.RealService{}, &configstore.HealthCheck{}, &configstore.RateLimit{})
+	if err != nil {
+		log.Errorf("Failed to migrate database structure: %v", err)
+	}
+	log.Info("Database reset completed")
+}
+
+// Function to print contents of test_db.db
+func printDatabaseContents() {
+	db, err := gorm.Open(sqlite.Open("test_db.db"), &gorm.Config{})
+	if err != nil {
+		log.Errorf("Failed to connect to test_db.db: %v", err)
+		return
+	}
+
+	var realServices []configstore.RealService
+	result := db.Find(&realServices)
+	if result.Error != nil {
+		log.Errorf("Error fetching records: %v", result.Error)
+		return
+	}
+
+	log.Info("Contents of test_db.db:")
+	for _, service := range realServices {
+		log.Infof("%+v", service)
+	}
+}
+
+func Test_addRealService(t *testing.T) {
+	resetDatabase()
+	log.Info("Starting Test_addRealService")
+
+	task := &taskManager.Task{
+		Route: []*routev1.Route{
+			{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-route",
+					Namespace: "default",
+				},
+				Spec: routev1.RouteSpec{
+					Port: &routev1.RoutePort{
+						TargetPort: intstr.FromInt(80),
+					},
+				},
+			},
+		},
+		EndPts: []*corev1.Endpoints{
+			{
+				Subsets: []corev1.EndpointSubset{
+					{
+						Addresses: []corev1.EndpointAddress{
+							{IP: "192.168.1.1"},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	err := addRealService(task)
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+		log.Errorf("Error in addRealService: %v", err)
+	}
+
+	// Print the database contents after running the test
+	printDatabaseContents()
+}
+
+func Test_updateRealService(t *testing.T) {
+	log.Info("Starting Test_updateRealService")
+
+	task := &taskManager.Task{
+		Route: []*routev1.Route{
+			{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-route",
+					Namespace: "default",
+				},
+				Spec: routev1.RouteSpec{
+					Port: &routev1.RoutePort{
+						TargetPort: intstr.FromInt(8080),
+					},
+				},
+			},
+		},
+	}
+
+	err := updateRealService(task)
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+		log.Errorf("Error in updateRealService: %v", err)
+	}
+
+	// Print the database contents after running the test
+	printDatabaseContents()
+}
+
+func Test_deleteRealService(t *testing.T) {
+	log.Info("Starting Test_deleteRealService")
+
+	// Mocking a task with a route and endpoint data
+	task := &taskManager.Task{
+		Route: []*routev1.Route{
+			{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-route",
+					Namespace: "test-namespace",
+				},
+			},
+		},
+		EndPts: []*corev1.Endpoints{
+			{
+				Subsets: []corev1.EndpointSubset{
+					{
+						Addresses: []corev1.EndpointAddress{
+							{IP: "192.168.1.10"},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	// Mocking the config store
+	configStore, err := configstore.Get()
+	if err != nil {
+		t.Fatalf("Error initializing config store: %v", err)
+	}
+
+	// Adding mock data to the config store
+	for _, subset := range task.EndPts[0].Subsets {
+		for _, address := range subset.Addresses {
+			realServiceName := fmt.Sprintf("%s_%s_%s", task.Route[0].Name, task.Route[0].Namespace, address.IP)
+
+			mockService := configstore.RealService{
+				IngressName: task.Route[0].Name,
+				ServiceName: realServiceName,
+				IPAddress:   address.IP,
+				Status:      "active",
+				CurrentOp:   "none",
+			}
+
+			err := configStore.Create(&mockService)
+			if err != nil {
+				t.Fatalf("Failed to create mock real service: %v", err)
+			}
+		}
+	}
+
+	// Execute deleteRealService
+	err = deleteRealService(task)
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+		log.Errorf("Error in deleteRealService: %v", err)
+	}
+
+	// Verify if the services have been marked for deletion
+	for _, subset := range task.EndPts[0].Subsets {
+		for _, address := range subset.Addresses {
+			realServiceName := fmt.Sprintf("%s_%s_%s", task.Route[0].Name, task.Route[0].Namespace, address.IP)
+
+			var realServices []configstore.RealService
+			err := configStore.ReadByRealServiceName(realServiceName, &realServices)
+			if err != nil || len(realServices) == 0 {
+				t.Errorf("Expected RealService %s to exist, but not found", realServiceName)
+			} else if realServices[0].CurrentOp != OperationDelete {
+				t.Errorf("Expected RealService %s to be marked for deletion, but got status: %s", realServiceName, realServices[0].CurrentOp)
+			}
+		}
+	}
+
+	// Print the database contents after running the test
+	printDatabaseContents()
+}
Index: /branches/main/array-ingress-controller/task-manager/openshift/apv/utils_test.go
===================================================================
--- /branches/main/array-ingress-controller/task-manager/openshift/apv/utils_test.go	(nonexistent)
+++ /branches/main/array-ingress-controller/task-manager/openshift/apv/utils_test.go	(working copy)
@@ -0,0 +1,45 @@
+package apv
+
+import "testing"
+
+func Test_getStringValue(t *testing.T) {
+	defaultVal := "default"
+	actual := getStringValue(nil, defaultVal)
+	if actual != defaultVal {
+		t.Errorf("Expected default value '%s', got '%s'", defaultVal, actual)
+	}
+
+	str := "custom"
+	actual = getStringValue(&str, defaultVal)
+	if actual != str {
+		t.Errorf("Expected '%s', got '%s'", str, actual)
+	}
+}
+
+func Test_getIntValue(t *testing.T) {
+	defaultVal := 42
+	actual := getIntValue(nil, defaultVal)
+	if actual != defaultVal {
+		t.Errorf("Expected default value %d, got %d", defaultVal, actual)
+	}
+
+	val := 99
+	actual = getIntValue(&val, defaultVal)
+	if actual != val {
+		t.Errorf("Expected %d, got %d", val, actual)
+	}
+}
+
+func Test_getBoolValue(t *testing.T) {
+	defaultVal := true
+	actual := getBoolValue(nil, defaultVal)
+	if actual != defaultVal {
+		t.Errorf("Expected default value %v, got %v", defaultVal, actual)
+	}
+
+	val := false
+	actual = getBoolValue(&val, defaultVal)
+	if actual != val {
+		t.Errorf("Expected %v, got %v", val, actual)
+	}
+}
Index: /branches/main/array-ingress-controller/task-manager/openshift/asf/asf_test.go
===================================================================
--- /branches/main/array-ingress-controller/task-manager/openshift/asf/asf_test.go	(nonexistent)
+++ /branches/main/array-ingress-controller/task-manager/openshift/asf/asf_test.go	(working copy)
@@ -0,0 +1,138 @@
+package asf
+
+import (
+	"context"
+	"testing"
+
+	routev1 "github.com/openshift/api/route/v1"
+	"gorm.io/driver/sqlite"
+	"gorm.io/gorm"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
+
+	"arraynetworks.com/array-ingress-controller/configstore"
+	taskManager "arraynetworks.com/array-ingress-controller/task-manager/task"
+)
+
+func seedASFRealService(t *testing.T) {
+	db, err := gorm.Open(sqlite.Open("test_db.db"), &gorm.Config{})
+	if err != nil {
+		t.Fatalf("failed to open db: %v", err)
+	}
+
+	err = db.Create(&configstore.AsfRealService{
+		IngressName: "asf-route",
+		ServiceName: "asf-route_default_10.0.0.1",
+		ServiceType: "HTTP",
+		IPv4:        "10.0.0.1",
+		Port:        8080,
+		CheckType:   "PING",
+		HCUp:        3,
+		HCDown:      2,
+		Status:      StatusWaiting,
+		CurrentOp:   OperationCreate,
+	}).Error
+	if err != nil {
+		t.Fatalf("seeding ASF real service failed: %v", err)
+	}
+}
+
+func getASFTestTask() *taskManager.Task {
+	return &taskManager.Task{
+		Route: []*routev1.Route{
+			{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "asf-route",
+					Namespace: "default",
+					Annotations: map[string]string{
+						AsfRealServiceAnnotKey: `{"service_type": "HTTP", "check_type": "PING", "hc_up": 3, "hc_down": 2}`,
+					},
+				},
+				Spec: routev1.RouteSpec{
+					Port: &routev1.RoutePort{
+						TargetPort: intstr.FromInt(8080),
+					},
+				},
+			},
+		},
+		EndPts: []*corev1.Endpoints{
+			{
+				Subsets: []corev1.EndpointSubset{
+					{
+						Addresses: []corev1.EndpointAddress{
+							{IP: "10.0.0.1"},
+						},
+					},
+				},
+			},
+		},
+	}
+}
+
+func Test_ASF_handleRouteAdd(t *testing.T) {
+	task := getASFTestTask()
+	ctx := context.WithValue(context.Background(), taskManager.TaskTypeKey{}, "ADD")
+	ctx = context.WithValue(ctx, taskManager.ResourceTypeKey{}, "ROUTE")
+
+	asf := &OpenShiftASF{}
+	err := asf.SetupASF(ctx, task)
+	if err != nil {
+		t.Errorf("handleRouteAdd failed: %v", err)
+	}
+}
+
+func Test_ASF_handleRouteUpdate(t *testing.T) {
+	seedASFRealService(t)
+
+	task := getASFTestTask()
+	ctx := context.WithValue(context.Background(), taskManager.TaskTypeKey{}, "UPDATE")
+	ctx = context.WithValue(ctx, taskManager.ResourceTypeKey{}, "ROUTE")
+	ctx = context.WithValue(ctx, "spec_updated", "True")
+
+	asf := &OpenShiftASF{}
+	err := asf.SetupASF(ctx, task)
+	if err != nil {
+		t.Errorf("handleRouteUpdate failed: %v", err)
+	}
+}
+
+func Test_ASF_handleRouteDelete(t *testing.T) {
+	seedASFRealService(t)
+
+	task := getASFTestTask()
+	ctx := context.WithValue(context.Background(), taskManager.TaskTypeKey{}, "DELETE")
+	ctx = context.WithValue(ctx, taskManager.ResourceTypeKey{}, "ROUTE")
+
+	asf := &OpenShiftASF{}
+	err := asf.SetupASF(ctx, task)
+	if err != nil {
+		t.Errorf("handleRouteDelete failed: %v", err)
+	}
+}
+
+func Test_ASF_handleEndpointAdd(t *testing.T) {
+	task := getASFTestTask()
+	ctx := context.WithValue(context.Background(), taskManager.TaskTypeKey{}, "ADD")
+	ctx = context.WithValue(ctx, taskManager.ResourceTypeKey{}, "ENDPOINT")
+
+	asf := &OpenShiftASF{}
+	err := asf.SetupASF(ctx, task)
+	if err != nil {
+		t.Errorf("handleEndpointAdd failed: %v", err)
+	}
+}
+
+func Test_ASF_handleEndpointDelete(t *testing.T) {
+	seedASFRealService(t)
+
+	task := getASFTestTask()
+	ctx := context.WithValue(context.Background(), taskManager.TaskTypeKey{}, "DELETE")
+	ctx = context.WithValue(ctx, taskManager.ResourceTypeKey{}, "ENDPOINT")
+
+	asf := &OpenShiftASF{}
+	err := asf.SetupASF(ctx, task)
+	if err != nil {
+		t.Errorf("handleEndpointDelete failed: %v", err)
+	}
+}
Index: /branches/main/array-ingress-controller/task-manager/openshift/asf/realservice_test.go
===================================================================
--- /branches/main/array-ingress-controller/task-manager/openshift/asf/realservice_test.go	(nonexistent)
+++ /branches/main/array-ingress-controller/task-manager/openshift/asf/realservice_test.go	(working copy)
@@ -0,0 +1,121 @@
+package asf
+
+import (
+	"os"
+	"testing"
+
+	routev1 "github.com/openshift/api/route/v1"
+	"gorm.io/driver/sqlite"
+	"gorm.io/gorm"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
+
+	"arraynetworks.com/array-ingress-controller/configstore"
+	"arraynetworks.com/array-ingress-controller/logger"
+	taskManager "arraynetworks.com/array-ingress-controller/task-manager/task"
+)
+
+var log = logger.GetLogger()
+
+func TestMain(m *testing.M) {
+	log.Infof("Resetting DB for ASF RealService tests")
+	resetDatabase()
+	os.Exit(m.Run())
+}
+
+func resetDatabase() {
+	dbPath := "test_db.db"
+	_ = os.Remove(dbPath)
+
+	file, err := os.Create(dbPath)
+	if err != nil {
+		log.Errorf("Failed to create DB file: %v", err)
+		return
+	}
+	file.Close()
+
+	if err := os.Chmod(dbPath, 0644); err != nil {
+		log.Errorf("Failed to set DB permissions: %v", err)
+		return
+	}
+
+	db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
+	if err != nil {
+		log.Errorf("Failed to open DB: %v", err)
+		return
+	}
+
+	if err := db.AutoMigrate(&configstore.AsfRealService{}); err != nil {
+		log.Errorf("Failed to migrate schema: %v", err)
+	}
+}
+
+func getTestTask() *taskManager.Task {
+	return &taskManager.Task{
+		Route: []*routev1.Route{
+			{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "asf-route",
+					Namespace: "default",
+					Annotations: map[string]string{
+						AsfRealServiceAnnotKey: `{"service_type": "HTTP", "check_type": "PING", "hc_up": 3, "hc_down": 2}`,
+					},
+				},
+				Spec: routev1.RouteSpec{
+					Port: &routev1.RoutePort{
+						TargetPort: intstr.FromInt(8080),
+					},
+				},
+			},
+		},
+		EndPts: []*corev1.Endpoints{
+			{
+				Subsets: []corev1.EndpointSubset{
+					{
+						Addresses: []corev1.EndpointAddress{
+							{IP: "192.168.1.10"},
+						},
+					},
+				},
+			},
+		},
+	}
+}
+
+func Test_addRealService(t *testing.T) {
+	task := getTestTask()
+
+	err := addRealService(task)
+	if err != nil {
+		t.Errorf("addRealService failed: %v", err)
+	}
+}
+
+func Test_updateRealService(t *testing.T) {
+	// First add
+	task := getTestTask()
+	if err := addRealService(task); err != nil {
+		t.Fatalf("addRealService failed during update test setup: %v", err)
+	}
+
+	// Then update
+	task.Route[0].Annotations[AsfRealServiceAnnotKey] = `{"service_type": "HTTPS", "check_type": "TCP", "hc_up": 5, "hc_down": 1}`
+	task.Route[0].Spec.Port.TargetPort = intstr.FromInt(9090)
+
+	if err := updateRealService(task); err != nil {
+		t.Errorf("updateRealService failed: %v", err)
+	}
+}
+
+func Test_deleteRealService(t *testing.T) {
+	// Ensure the service exists before delete
+	task := getTestTask()
+	if err := addRealService(task); err != nil {
+		t.Fatalf("addRealService failed during delete test setup: %v", err)
+	}
+
+	if err := deleteRealService(task); err != nil {
+		t.Errorf("deleteRealService failed: %v", err)
+	}
+}
Index: /branches/main/array-ingress-controller/task-manager/openshift/asf/utils_test.go
===================================================================
--- /branches/main/array-ingress-controller/task-manager/openshift/asf/utils_test.go	(nonexistent)
+++ /branches/main/array-ingress-controller/task-manager/openshift/asf/utils_test.go	(working copy)
@@ -0,0 +1,31 @@
+package asf
+
+import "testing"
+
+func Test_getStringValue(t *testing.T) {
+	defaultVal := "default"
+	got := getStringValue(nil, defaultVal)
+	if got != defaultVal {
+		t.Errorf("Expected default '%s', got '%s'", defaultVal, got)
+	}
+
+	val := "asf-string"
+	got = getStringValue(&val, defaultVal)
+	if got != val {
+		t.Errorf("Expected '%s', got '%s'", val, got)
+	}
+}
+
+func Test_getIntValue(t *testing.T) {
+	defaultVal := 999
+	got := getIntValue(nil, defaultVal)
+	if got != defaultVal {
+		t.Errorf("Expected default %d, got %d", defaultVal, got)
+	}
+
+	val := 123
+	got = getIntValue(&val, defaultVal)
+	if got != val {
+		t.Errorf("Expected %d, got %d", val, got)
+	}
+}
Index: /branches/main/array-ingress-controller/watchtower/const.go
===================================================================
--- /branches/main/array-ingress-controller/watchtower/const.go	(revision 11)
+++ /branches/main/array-ingress-controller/watchtower/const.go	(working copy)
@@ -1,6 +1,29 @@
 package watchtower
 
-const(
+import "time"
+
+// Constants for SMTP configuration
+const (
 	SMTPServer = "smtp.office365.com"
 	SMTPPort   = 587
-)
\ No newline at end of file
+)
+
+// Predefined env vars
+const (
+	CONTROLLER_TYPE_ENV  = "CONTROLLER_TYPE"
+	EMAIL_RECIPIENTS_ENV = "EMAIL_RECIPIENTS"
+	EMAIL_CC_ENV         = "EMAIL_CC"
+	EMAIL_FROM_ENV       = "EMAIL_FROM"
+	EMAIL_PASSWORD_ENV   = "EMAIL_PASSWORD"
+	APV_IP_ENV           = "APV_IP"
+	ASF_IP_ENV           = "ASF_IP"
+	APV_DEVICE           = "APV"
+	ASF_DEVICE           = "ASF"
+)
+
+// WatchTower defaults
+const (
+	PingInterval = 5 * time.Second
+	FailureLimit = 3
+	SuccessLimit = 1
+)
Index: /branches/main/array-ingress-controller/watchtower/email.go
===================================================================
--- /branches/main/array-ingress-controller/watchtower/email.go	(revision 11)
+++ /branches/main/array-ingress-controller/watchtower/email.go	(working copy)
@@ -8,7 +8,14 @@
 	"gopkg.in/gomail.v2"
 )
 
-// Email struct
+//go:generate mockgen -source=email.go -destination=mocks/mock_email.go -package=mocks
+
+// EmailSender is an interface to send emails
+type EmailSender interface {
+	Send() error
+}
+
+// Email struct implements EmailSender
 type Email struct {
 	Subject    string
 	Body       string
@@ -16,9 +23,9 @@
 	CC         []string
 }
 
-// New initializes and returns an Email instance
-func NewEmail(subject, body string, recipients, cc []string) Email {
-	return Email{
+// NewEmail initializes and returns an Email instance
+func NewEmail(subject, body string, recipients, cc []string) EmailSender {
+	return &Email{
 		Subject:    subject,
 		Body:       body,
 		Recipients: recipients,
@@ -29,12 +36,12 @@
 // Send sends an email using Gomail with environment variables
 func (e *Email) Send() error {
 	// Fetch credentials from environment variables
-	fromEmail := os.Getenv("EMAIL_FROM")
-	password := os.Getenv("EMAIL_PASSWORD") // Auth token works as well
+	fromEmail := os.Getenv(EMAIL_FROM_ENV)
+	password := os.Getenv(EMAIL_PASSWORD_ENV) // Auth token works as well
 
 	// Validation: Check for missing values
 	if fromEmail == "" || password == "" {
-		return errors.New("missing EMAIL_FROM or EMAIL_PASSWORD environment variables")
+		return fmt.Errorf("missing %s or %s environment variables", EMAIL_FROM_ENV, EMAIL_PASSWORD_ENV)
 	}
 	if len(e.Recipients) == 0 {
 		return errors.New("at least one recipient is required")
@@ -52,10 +59,5 @@
 	// Configure the SMTP server
 	d := gomail.NewDialer(SMTPServer, SMTPPort, fromEmail, password)
 
-	// Send the email
-	if err := d.DialAndSend(m); err != nil {
-		return fmt.Errorf("failed to send email: %w", err)
-	}
-
-	return nil
+	return d.DialAndSend(m)
 }
Index: /branches/main/array-ingress-controller/watchtower/email_test.go
===================================================================
--- /branches/main/array-ingress-controller/watchtower/email_test.go	(nonexistent)
+++ /branches/main/array-ingress-controller/watchtower/email_test.go	(working copy)
@@ -0,0 +1,64 @@
+package watchtower
+
+import (
+	"errors"
+	"os"
+	"testing"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+)
+
+func TestEmail(t *testing.T) {
+	RegisterFailHandler(Fail)
+	RunSpecs(t, "Email Unit Test Suite")
+}
+
+// Mock implementation of EmailSender for isolated testing
+type mockEmail struct {
+	shouldFail bool
+}
+
+func (m *mockEmail) Send() error {
+	if m.shouldFail {
+		return errors.New("mock send failure")
+	}
+	return nil
+}
+
+var _ = Describe("EmailSender", func() {
+	Context("Validation", func() {
+		It("should fail if EMAIL_FROM or EMAIL_PASSWORD is missing", func() {
+			os.Unsetenv(EMAIL_FROM_ENV)
+			os.Unsetenv(EMAIL_PASSWORD_ENV)
+
+			email := NewEmail("Test Subject", "Body", []string{"user@example.com"}, nil)
+			err := email.Send()
+			Expect(err).To(MatchError(ContainSubstring("missing EMAIL_FROM or EMAIL_PASSWORD")))
+		})
+
+		It("should fail if no recipients are provided", func() {
+			os.Setenv(EMAIL_FROM_ENV, "test@example.com")
+			os.Setenv(EMAIL_PASSWORD_ENV, "dummy-pass")
+
+			email := NewEmail("Test Subject", "Body", []string{}, nil)
+			err := email.Send()
+			Expect(err).To(MatchError(ContainSubstring("at least one recipient is required")))
+		})
+	})
+
+	Context("Email Send Function", func() {
+		It("should succeed using email sender", func() {
+			mock := &mockEmail{shouldFail: false}
+			err := mock.Send()
+			Expect(err).To(BeNil())
+		})
+
+		It("should fail using email sender", func() {
+			mock := &mockEmail{shouldFail: true}
+			err := mock.Send()
+			Expect(err).To(HaveOccurred())
+			Expect(err.Error()).To(ContainSubstring("mock send failure"))
+		})
+	})
+})
Index: /branches/main/array-ingress-controller/watchtower/mocks/mock_email.go
===================================================================
--- /branches/main/array-ingress-controller/watchtower/mocks/mock_email.go	(nonexistent)
+++ /branches/main/array-ingress-controller/watchtower/mocks/mock_email.go	(working copy)
@@ -0,0 +1,48 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: email.go
+
+// Package mocks is a generated GoMock package.
+package mocks
+
+import (
+	reflect "reflect"
+
+	gomock "github.com/golang/mock/gomock"
+)
+
+// MockEmailSender is a mock of EmailSender interface.
+type MockEmailSender struct {
+	ctrl     *gomock.Controller
+	recorder *MockEmailSenderMockRecorder
+}
+
+// MockEmailSenderMockRecorder is the mock recorder for MockEmailSender.
+type MockEmailSenderMockRecorder struct {
+	mock *MockEmailSender
+}
+
+// NewMockEmailSender creates a new mock instance.
+func NewMockEmailSender(ctrl *gomock.Controller) *MockEmailSender {
+	mock := &MockEmailSender{ctrl: ctrl}
+	mock.recorder = &MockEmailSenderMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockEmailSender) EXPECT() *MockEmailSenderMockRecorder {
+	return m.recorder
+}
+
+// Send mocks base method.
+func (m *MockEmailSender) Send() error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Send")
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// Send indicates an expected call of Send.
+func (mr *MockEmailSenderMockRecorder) Send() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Send", reflect.TypeOf((*MockEmailSender)(nil).Send))
+}
Index: /branches/main/array-ingress-controller/watchtower/mocks/mock_watchtower.go
===================================================================
--- /branches/main/array-ingress-controller/watchtower/mocks/mock_watchtower.go	(nonexistent)
+++ /branches/main/array-ingress-controller/watchtower/mocks/mock_watchtower.go	(working copy)
@@ -0,0 +1,74 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: watchtower.go
+
+// Package mocks is a generated GoMock package.
+package mocks
+
+import (
+	reflect "reflect"
+
+	gomock "github.com/golang/mock/gomock"
+)
+
+// MockWatchtowerInterface is a mock of WatchtowerInterface interface.
+type MockWatchtowerInterface struct {
+	ctrl     *gomock.Controller
+	recorder *MockWatchtowerInterfaceMockRecorder
+}
+
+// MockWatchtowerInterfaceMockRecorder is the mock recorder for MockWatchtowerInterface.
+type MockWatchtowerInterfaceMockRecorder struct {
+	mock *MockWatchtowerInterface
+}
+
+// NewMockWatchtowerInterface creates a new mock instance.
+func NewMockWatchtowerInterface(ctrl *gomock.Controller) *MockWatchtowerInterface {
+	mock := &MockWatchtowerInterface{ctrl: ctrl}
+	mock.recorder = &MockWatchtowerInterfaceMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockWatchtowerInterface) EXPECT() *MockWatchtowerInterfaceMockRecorder {
+	return m.recorder
+}
+
+// GetDeviceStatusChan mocks base method.
+func (m *MockWatchtowerInterface) GetDeviceStatusChan() chan bool {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetDeviceStatusChan")
+	ret0, _ := ret[0].(chan bool)
+	return ret0
+}
+
+// GetDeviceStatusChan indicates an expected call of GetDeviceStatusChan.
+func (mr *MockWatchtowerInterfaceMockRecorder) GetDeviceStatusChan() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDeviceStatusChan", reflect.TypeOf((*MockWatchtowerInterface)(nil).GetDeviceStatusChan))
+}
+
+// Monitor mocks base method.
+func (m *MockWatchtowerInterface) Monitor() {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "Monitor")
+}
+
+// Monitor indicates an expected call of Monitor.
+func (mr *MockWatchtowerInterfaceMockRecorder) Monitor() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Monitor", reflect.TypeOf((*MockWatchtowerInterface)(nil).Monitor))
+}
+
+// PingDevice mocks base method.
+func (m *MockWatchtowerInterface) PingDevice() error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "PingDevice")
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// PingDevice indicates an expected call of PingDevice.
+func (mr *MockWatchtowerInterfaceMockRecorder) PingDevice() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PingDevice", reflect.TypeOf((*MockWatchtowerInterface)(nil).PingDevice))
+}
Index: /branches/main/array-ingress-controller/watchtower/watchtower.go
===================================================================
--- /branches/main/array-ingress-controller/watchtower/watchtower.go	(revision 11)
+++ /branches/main/array-ingress-controller/watchtower/watchtower.go	(working copy)
@@ -12,20 +12,15 @@
 )
 
 var (
-	// Lists of email recipients and CC addresses
-	EmailRecipients = getEmailsFromEnv("EMAIL_RECIPIENTS")
-	EmailCC         = getEmailsFromEnv("EMAIL_CC")
-	PingInterval    = 5 * time.Second
-	FailureLimit    = 3
-	SuccessLimit    = 1
-
-	// APV and ASF monitoring channels
-	APV_IP       = os.Getenv("APV_IP")
-	ASF_IP       = os.Getenv("ASF_IP")
-	APVAvailable = make(chan bool, 1)
-	ASFAvailable = make(chan bool, 1)
+	log              = logger.GetLogger()
+	deviceStatusChan = make(chan bool, 1)
 )
 
+// GetDeviceStatusChan returns the device status channel
+func GetDeviceStatusChan() chan bool {
+	return deviceStatusChan
+}
+
 // getEmailsFromEnv reads a comma-separated list of emails from an environment variable
 func getEmailsFromEnv(envVar string) []string {
 	val := os.Getenv(envVar)
@@ -35,152 +30,242 @@
 	return strings.Split(val, ",")
 }
 
+// SafeChannelUpdate sends a status update to the provided channel, preventing blocking if full.
+func SafeChannelUpdate(statusChan chan bool, status bool, deviceType, deviceIP string) {
+	select {
+	// update the channel value if it is empty
+	// channel will only be empty if it is being read continously
+	case statusChan <- status:
+		log.Infof("Updated %s device (%s) status to: %v", deviceType, deviceIP, status)
+	default:
+		// channel update was not performed, meaning channel's previous value was not read
+		// clear the previous value of channel and update new value
+		<-statusChan
+		statusChan <- status
+		log.Infof("Replaced previous status for %s device (%s) to: %v", deviceType, deviceIP, status)
+	}
+}
+
+//go:generate mockgen -source=watchtower.go -destination=mocks/mock_watchtower.go -package=mocks
+
+// WatchtowerInterface defines an interface for dependency injection
+type WatchtowerInterface interface {
+	PingDevice() error
+	Monitor()
+	GetDeviceStatusChan() chan bool
+}
+
 // Config holds settings for Watch Tower monitoring
 type Config struct {
-	IP           string
-	PingInterval time.Duration
-	FailureLimit int
-	SuccessLimit int
-	StatusChan   chan bool
+	IP              string
+	DeviceType      string
+	PingInterval    time.Duration
+	FailureLimit    int
+	SuccessLimit    int
+	EmailRecipients []string
+	EmailCC         []string
 }
 
-// NewConfig returns a default configuration for WatchTower
-func NewConfig(ip string, statusChan chan bool) Config {
+// NewConfig returns a configuration for WatchTower
+func NewConfig(ip, deviceType string, recipients, cc []string) Config {
 	return Config{
-		IP:           ip,
-		PingInterval: PingInterval,
-		FailureLimit: FailureLimit,
-		SuccessLimit: SuccessLimit,
-		StatusChan:   statusChan,
+		IP:              ip,
+		DeviceType:      deviceType,
+		PingInterval:    PingInterval,
+		FailureLimit:    FailureLimit,
+		SuccessLimit:    SuccessLimit,
+		EmailRecipients: recipients,
+		EmailCC:         cc,
 	}
 }
 
-// WatchTower monitors the connectivity of device
+// WatchTower monitors the connectivity of a device
 type WatchTower struct {
+	ctx                  context.Context
 	config               Config
 	consecutiveFailures  int
 	consecutiveSuccesses int
-	deviceIsDown         bool
-	statusChan           chan bool // true = UP, false = DOWN
+	emailSent            bool
+	emailFactory         func(subject, body string) EmailSender // injectable
+
 }
 
 // New initializes and returns a new WatchTower instance
-func New(c Config) *WatchTower {
+func New(ctx context.Context, c Config) *WatchTower {
 	return &WatchTower{
+		ctx:                  ctx,
 		config:               c,
 		consecutiveFailures:  0,
 		consecutiveSuccesses: 0,
-		deviceIsDown:         false,
-		statusChan:           make(chan bool, 1), // Buffered channel
+		emailSent:            false,
+		emailFactory: func(subject, body string) EmailSender {
+			return NewEmail(subject, body, c.EmailRecipients, c.EmailCC)
+		},
 	}
 }
 
-// Ping pings the device using the system `Ping` command and only checks for errors
+// Ping checks device connectivity using system `ping` command
 func (wt *WatchTower) Ping() error {
-	cmd := exec.Command("ping", "-c", "3", "-W", "5", wt.config.IP)
-	output, err := cmd.CombinedOutput()
+	cmd := exec.Command("ping", "-c", "3", "-W", "3", wt.config.IP)
+	cmdOutput := make(chan string, 1)
+	cmdErr := make(chan error, 1)
 
-	if err != nil {
-		return fmt.Errorf("ping failed: %s", strings.TrimSpace(string(output)))
+	go func() {
+		output, err := cmd.CombinedOutput()
+		cmdOutput <- string(output)
+		cmdErr <- err
+	}()
+
+	select {
+	case output := <-cmdOutput:
+		err := <-cmdErr
+		if err != nil {
+			return fmt.Errorf("ping failed: %s", strings.TrimSpace(output))
+		}
+
+		if !strings.Contains(string(output), "bytes from") ||
+			strings.Contains(string(output), "100% packet loss") ||
+			strings.Contains(string(output), "wrong data byte") ||
+			strings.Contains(string(output), "Destination Net Unreachable") ||
+			strings.Contains(string(output), "time of day goes back") {
+			return fmt.Errorf("ping failed: %s", strings.TrimSpace(string(output)))
+		}
+		return nil
+	case <-time.After(4 * time.Second): // Timeout Handling
+		return fmt.Errorf("ping command timed out")
 	}
-	return nil
 }
 
 // Monitor starts monitoring the device connectivity
-func (wt *WatchTower) Monitor(ctx context.Context) {
+func (wt *WatchTower) Monitor() {
 	tick := time.Tick(wt.config.PingInterval)
 
-	go func() {
-		defer logger.Info("WatchTower monitoring stopped.")
-
-		for {
-			select {
-			case <-ctx.Done():
-				return
-			case <-tick:
-				// Try to ping the device
-				if err := wt.Ping(); err != nil {
-					wt.consecutiveFailures++
-					wt.consecutiveSuccesses = 0
-
-					logger.WithFields(logger.Fields{
-						"AIP":              wt.config.IP,
-						"Error":            err.Error(),
-						"FailureCount":     wt.consecutiveFailures,
-						"FailureThreshold": wt.config.FailureLimit,
-					}).Error("Device unreachable")
-					// Mark Device as down if failure limit is reached
-					if wt.consecutiveFailures >= wt.config.FailureLimit && !wt.deviceIsDown {
-						wt.deviceIsDown = true
-						DeviceStatus(wt.statusChan, false)
-						wt.SendDownEmail()
+	for {
+		select {
+		case <-wt.ctx.Done():
+			log.Infof("Stopping monitoring for %s device (%s)", wt.config.DeviceType, wt.config.IP)
+			return
+
+		case <-tick:
+			log.Debugf("Pinging %s device (%s)", wt.config.DeviceType, wt.config.IP)
+			if err := wt.Ping(); err != nil {
+				wt.consecutiveFailures++
+				wt.consecutiveSuccesses = 0
+
+				log.Debugf("Ping failed for %s device (%s): %s", wt.config.DeviceType, wt.config.IP, err.Error())
+				log.WithFields(logger.Fields{
+					"IP":               wt.config.IP,
+					"FailureCount":     wt.consecutiveFailures,
+					"FailureThreshold": wt.config.FailureLimit,
+				}).Errorf("%s Device unreachable", wt.config.DeviceType)
+
+				// Mark Device as down if failure limit is reached
+				if wt.consecutiveFailures >= wt.config.FailureLimit {
+					SafeChannelUpdate(deviceStatusChan, false, wt.config.DeviceType, wt.config.IP)
+
+					log.Errorf("%s device (%s) is down", wt.config.DeviceType, wt.config.IP)
+
+					if !wt.emailSent {
+						if err := wt.SendDownEmail(); err != nil {
+							log.Errorf("Failed to send %s down email: %s", wt.config.DeviceType, err)
+						} else {
+							log.Infof("%s down email sent successfully", wt.config.DeviceType)
+							wt.emailSent = true
+						}
 					}
-				} else {
-					wt.consecutiveSuccesses++
-					wt.consecutiveFailures = 0
-					// Mark device as up only after SuccessLimit consecutive successes
-					if wt.deviceIsDown && wt.consecutiveSuccesses >= wt.config.SuccessLimit {
-						wt.deviceIsDown = false
-						DeviceStatus(wt.statusChan, true)
+				}
+			} else {
+				wt.consecutiveSuccesses++
+				SafeChannelUpdate(deviceStatusChan, true, wt.config.DeviceType, wt.config.IP)
+
+				if wt.consecutiveSuccesses == wt.config.SuccessLimit {
+					wt.emailSent = false
+
+					// format log message based on conditions
+					if wt.consecutiveFailures > 0 {
+						log.Infof("%s device (%s) is up again!", wt.config.DeviceType, wt.config.IP)
+					} else {
+						log.Infof("%s device (%s) is up", wt.config.DeviceType, wt.config.IP)
 					}
 				}
+
+				if wt.consecutiveSuccesses > wt.config.SuccessLimit {
+					log.Debugf("%s device (%s) is up", wt.config.DeviceType, wt.config.IP)
+				}
+				wt.consecutiveFailures = 0
+
 			}
 		}
-	}()
+	}
 }
 
-// SendDownEmail sends an email notification when WatchTower goes down
-func (wt *WatchTower) SendDownEmail() {
-	subject := "WatchTower Alert: Device Down"
-	body := fmt.Sprintf("The device at IP %s is down.", wt.config.IP)
-	wt.SendWatchtowerEmail(subject, body)
+// SendDownEmail sends an email notification when the device goes down
+func (wt *WatchTower) SendDownEmail() error {
+	subject := fmt.Sprintf("WatchTower Alert: %s Down", wt.config.DeviceType)
+	body := fmt.Sprintf("%s device at IP %s is down.", wt.config.DeviceType, wt.config.IP)
+	return wt.SendWatchtowerEmail(subject, body)
 }
 
 // SendWatchtowerEmail sends an email using the email package
-func (wt *WatchTower) SendWatchtowerEmail(subject, body string) {
-	emailInstance := NewEmail(
-		subject,
-		body,
-		EmailRecipients,
-		EmailCC,
-	)
-	err := emailInstance.Send()
-	if err != nil {
-		logger.Errorf("Error sending email: %v", err)
-	} else {
-		logger.Infof("Email sent successfully: Subject='%s'", subject)
+func (wt *WatchTower) SendWatchtowerEmail(subject, body string) error {
+	email := wt.emailFactory(subject, body)
+	return email.Send()
+}
+
+// getDeviceIP retrieves the device IP based on the controller type
+func getDeviceIP() (string, string, error) {
+	controllerType := os.Getenv(CONTROLLER_TYPE_ENV)
+
+	var envVar string
+	switch controllerType {
+	case APV_DEVICE:
+		envVar = APV_IP_ENV
+	case ASF_DEVICE:
+		envVar = ASF_IP_ENV
+	case "":
+		return "", "", fmt.Errorf("%s not set", CONTROLLER_TYPE_ENV)
+	default:
+		return "", "", fmt.Errorf("unsupported %s: %s", CONTROLLER_TYPE_ENV, controllerType)
 	}
-}
 
-// DeviceStatus updates the status channel with the device's availability
-func DeviceStatus(statusChan chan bool, status bool) {
-	select {
-	case statusChan <- status:
-	default:
+	deviceIP := os.Getenv(envVar)
+	if deviceIP == "" {
+		return "", "", fmt.Errorf("missing required environment variable: %s", envVar)
 	}
+
+	return controllerType, deviceIP, nil
 }
 
-// StartMonitoring initializes monitoring for both APV and ASF
+// StartMonitoring initializes monitoring for APV or ASF Device
 func StartMonitoring(ctx context.Context) {
-	log := logger.GetLogger()
+	deviceType, deviceIP, err := getDeviceIP()
+	if err != nil {
+		log.Errorf("Error retrieving device info: %v", err)
+		return
+	}
 
-	// Ensure APV and ASF IPs are set
-	if APV_IP == "" || ASF_IP == "" {
-		log.Error("APV_IP or ASF_IP not set in environment variables. Exiting...")
+	recipients := getEmailsFromEnv(EMAIL_RECIPIENTS_ENV)
+	cc := getEmailsFromEnv(EMAIL_CC_ENV)
+
+	if len(recipients) == 0 {
+		log.Errorf("Missing email recipients. Exiting...")
 		return
 	}
 
-	log.Infof("Starting WatchTower Monitoring...")
+	if os.Getenv(EMAIL_FROM_ENV) == "" || os.Getenv(EMAIL_PASSWORD_ENV) == "" {
+		log.Errorf("Missing email credentials. Exiting...")
+		return
+	}
+
+	log.Infof("Starting %s monitoring with IP: %s", deviceType, deviceIP)
+
+	config := NewConfig(deviceIP, deviceType, recipients, cc)
+	wt := New(ctx, config)
+
+	go func() {
+		log.Infof("Monitor started for %s (%s)", deviceType, deviceIP)
+		wt.Monitor()
+	}()
 
-	// Create configurations for both APV and ASF
-	apvConfig := NewConfig(APV_IP, APVAvailable)
-	asfConfig := NewConfig(ASF_IP, ASFAvailable)
-
-	// Create WatchTower instances
-	apvWatcher := New(apvConfig)
-	asfWatcher := New(asfConfig)
-
-	// Start monitoring both devices
-	go apvWatcher.Monitor(ctx)
-	go asfWatcher.Monitor(ctx)
+	<-ctx.Done() // Wait for cancellation
 }
Index: /branches/main/array-ingress-controller/watchtower/watchtower_test.go
===================================================================
--- /branches/main/array-ingress-controller/watchtower/watchtower_test.go	(nonexistent)
+++ /branches/main/array-ingress-controller/watchtower/watchtower_test.go	(working copy)
@@ -0,0 +1,97 @@
+package watchtower
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+)
+
+func TestWatchtower(t *testing.T) {
+	RegisterFailHandler(Fail)
+	RunSpecs(t, "Watchtower Unit Test Suite")
+}
+
+var _ = Describe("WatchTower Unit Tests", func() {
+	var (
+		ctx    context.Context
+		config Config
+		wt     *WatchTower
+	)
+
+	BeforeEach(func() {
+		ctx = context.Background()
+		config = NewConfig("127.0.0.1", "APV", []string{"test@example.com"}, []string{"cc@example.com"})
+		wt = New(ctx, config)
+
+		// Inject the mock email sender
+		wt.emailFactory = func(subject, body string) EmailSender {
+			return &mockEmail{shouldFail: false}
+		}
+	})
+
+	Describe("Ping", func() {
+		It("should return an error for unreachable IP (invalid domain)", func() {
+			unreachableConfig := NewConfig("invalid.ip", "APV", nil, nil)
+			unreachable := New(ctx, unreachableConfig)
+
+			// Inject a no-op email factory to avoid nil panic
+			unreachable.emailFactory = func(subject, body string) EmailSender {
+				return &mockEmail{}
+			}
+
+			err := unreachable.Ping()
+			Expect(err).To(HaveOccurred())
+		})
+	})
+
+	Describe("SafeChannelUpdate", func() {
+		It("should send value if channel is available", func() {
+			statusChan := make(chan bool, 1)
+			SafeChannelUpdate(statusChan, true, "APV", "127.0.0.1")
+
+			select {
+			case val := <-statusChan:
+				Expect(val).To(BeTrue())
+			case <-time.After(1 * time.Second):
+				Fail("Expected channel to receive value")
+			}
+		})
+
+		It("should not panic if channel is full", func() {
+			statusChan := make(chan bool, 1)
+			statusChan <- true // fill it
+			Expect(func() {
+				SafeChannelUpdate(statusChan, false, "APV", "127.0.0.1")
+			}).NotTo(Panic())
+		})
+	})
+
+	Describe("Email Sending", func() {
+		It("should call SendWatchtowerEmail and succeed", func() {
+			err := wt.SendWatchtowerEmail("Test Subject", "This is a test email.")
+			Expect(err).To(Succeed())
+		})
+
+		It("should fail if mock is set to fail", func() {
+			wt.emailFactory = func(subject, body string) EmailSender {
+				return &mockEmail{shouldFail: true}
+			}
+			err := wt.SendWatchtowerEmail("Test Subject", "This is a test email.")
+			Expect(err).To(MatchError("mock send failure"))
+		})
+	})
+
+	Describe("Configuration", func() {
+		It("should generate config with default constants", func() {
+			cfg := NewConfig("1.2.3.4", "ASF", []string{"xyz@abc.com"}, nil)
+			Expect(cfg.IP).To(Equal("1.2.3.4"))
+			Expect(cfg.DeviceType).To(Equal("ASF"))
+			Expect(cfg.PingInterval).To(Equal(PingInterval))
+			Expect(cfg.FailureLimit).To(Equal(FailureLimit))
+			Expect(cfg.SuccessLimit).To(Equal(SuccessLimit))
+		})
+	})
+})
