Index: /.reviewboardrc
===================================================================
--- /.reviewboardrc	(revision 9)
+++ /.reviewboardrc	(working copy)
@@ -1,3 +1,3 @@
-REVIEWBOARD_URL = "https://reviewboard.arraynetworks.net"
+REVIEWBOARD_URL = "https://reviewboard.arraynetworks.net/"
 REPOSITORY = "Ingress_controller"
 REPOSITORY_TYPE = "svn"
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/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,46 @@
+{
+    "$schema": "http://json-schema.org/draft-07/schema",
+    "type": "object",
+    "required": ["ingress_name", "hc_name", "service_name", "type", "hc_up", "hc_down", "ip.ipv4", "port", "send_interval", "server_timeout"],
+    "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
+      },
+      "send_interval": {
+        "type": "integer",
+        "minimum": 1
+      },
+      "server_timeout": {
+        "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": ["ingress_name", "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,118 @@
+package admission
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+
+	"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"
+)
+
+const (
+	AnnotationClass = "arraynetworks.com/class"
+)
+
+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[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,124 @@
+package annotations
+
+import (
+	"encoding/json"
+	"fmt"
+
+	"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:"send_interval"`
+	ServerTimeout int    `json:"server_timeout"`
+}
+
+// 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 schema loading
+	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)
+	}
+
+	// Check if the schema is valid or not
+	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())
+	}
+
+	// Parse the raw data into a map to check types
+	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)
+	}
+
+	// Validate HCUp
+	hcUp, ok := healthCheckMap["hc_up"].(float64)
+	if !ok {
+		log.Errorf("Invalid HCUp value: %v", healthCheckMap["hc_up"])
+		return fmt.Errorf("invalid HCUp value: %v", healthCheckMap["hc_up"])
+	}
+
+	if hcUp <= 0 {
+		log.Errorf("Invalid HCUp value: %d", int(hcUp))
+		return fmt.Errorf("invalid HCUp value: %d", int(hcUp))
+	}
+
+	// Validate HCDown
+	hcDown, ok := healthCheckMap["hc_down"].(float64)
+	if !ok {
+		log.Errorf("Invalid HCDown value: %v", healthCheckMap["hc_down"])
+		return fmt.Errorf("invalid HCDown value: %v", healthCheckMap["hc_down"])
+	}
+
+	if hcDown <= 0 {
+		log.Errorf("Invalid HCDown value: %d", int(hcDown))
+		return fmt.Errorf("invalid HCDown value: %d", int(hcDown))
+	}
+
+	// Validate Port
+	port, ok := healthCheckMap["port"].(float64)
+	if !ok {
+		log.Errorf("Invalid Port value: %v", healthCheckMap["port"])
+		return fmt.Errorf("invalid Port value: %v", healthCheckMap["port"])
+	}
+
+	if port <= 0 || port > 65535 {
+		log.Errorf("Invalid Port value: %d", int(port))
+		return fmt.Errorf("invalid Port value: %d", int(port))
+	}
+
+	// Validate SendInterval
+	sendInterval, ok := healthCheckMap["send_interval"].(float64)
+	if !ok {
+		log.Errorf("Invalid SendInterval value: %v", healthCheckMap["send_interval"])
+		return fmt.Errorf("invalid SendInterval value: %v", healthCheckMap["send_interval"])
+	}
+
+	if sendInterval <= 0 {
+		log.Errorf("Invalid SendInterval value: %d", int(sendInterval))
+		return fmt.Errorf("invalid SendInterval value: %d", int(sendInterval))
+	}
+
+	// Validate ServerTimeout
+	serverTimeout, ok := healthCheckMap["server_timeout"].(float64)
+	if !ok {
+		log.Errorf("Invalid ServerTimeout value: %v", healthCheckMap["server_timeout"])
+		return fmt.Errorf("invalid ServerTimeout value: %v", healthCheckMap["server_timeout"])
+	}
+
+	if serverTimeout <= 0 {
+		log.Errorf("Invalid ServerTimeout value: %d", int(serverTimeout))
+		return fmt.Errorf("invalid ServerTimeout value: %d", int(serverTimeout))
+	}
+
+	// Log successful validation
+	log.Infof("HealthCheck is valid: %+v", healthCheckMap)
+	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 integers are correct
+	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)
+	}
+
+	// Ensure MaxCPS is an integer
+	if maxCPS, ok := rateLimitMap["max_cps"].(float64); ok {
+		if maxCPS != float64(int(maxCPS)) { // Check for integer value
+			log.Errorf("MaxCPS is not an integer: %v", maxCPS)
+			return fmt.Errorf("max_cps must be an integer, got: %v", maxCPS)
+		}
+	} else {
+		log.Errorf("Missing or invalid MaxCPS value: %v", rateLimitMap["max_cps"])
+		return fmt.Errorf("max_cps must be provided as an integer")
+	}
+
+	// Ensure SoftBandwidth is an integer
+	if softBandwidth, ok := rateLimitMap["soft_bandwidth"].(float64); ok {
+		if softBandwidth != float64(int(softBandwidth)) { // Check for integer value
+			log.Errorf("SoftBandwidth is not an integer: %v", softBandwidth)
+			return fmt.Errorf("soft_bandwidth must be an integer, got: %v", softBandwidth)
+		}
+	} else {
+		log.Errorf("Missing or invalid SoftBandwidth value: %v", rateLimitMap["soft_bandwidth"])
+		return fmt.Errorf("soft_bandwidth must be provided as an integer")
+	}
+
+	// Ensure HardBandwidth is an integer
+	if hardBandwidth, ok := rateLimitMap["hard_bandwidth"].(float64); ok {
+		if hardBandwidth != float64(int(hardBandwidth)) { // Check for integer value
+			log.Errorf("HardBandwidth is not an integer: %v", hardBandwidth)
+			return fmt.Errorf("hard_bandwidth must be an integer, got: %v", hardBandwidth)
+		}
+	} else {
+		log.Errorf("Missing or invalid HardBandwidth value: %v", rateLimitMap["hard_bandwidth"])
+		return fmt.Errorf("hard_bandwidth must be provided as an integer")
+	}
+
+	// Log successful validation
+	log.Infof("RateLimit is valid and all values are integers.")
+	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,57 @@
+package admission
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"os"
+	"os/signal"
+	"syscall"
+	"time"
+)
+
+const (
+	port           = 8443
+	shutdownPeriod = 5 * time.Second
+)
+
+// 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", port),
+		Handler: mux,
+	}
+
+	// Start server in a separate goroutine with logging
+	go func() {
+		log.Infof("Starting webhook server on port %d", 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, 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")
+	}
+}
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/health":    true,
+	"apv.arraynetworks.com/ratelimit": true,
+}
+
+// allowedASFAnnotations - Allowed keys for ASF class
+var allowedASFAnnotations = map[string]bool{
+	"asf.arraynetworks.com/health":    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 apv.arraynetworks.com/ except healthcheck and ratelimit
+		if strings.HasPrefix(key, "apv.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/health"]; 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 asf.arraynetworks.com/ except healthcheck and ratelimit
+		if strings.HasPrefix(key, "asf.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/health"]; 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/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 (
+	admission-controller v0.0.0-00010101000000-000000000000
+	github.com/xeipuuv/gojsonschema v1.2.0
+	go.uber.org/zap v1.27.0
+	k8s.io/api v0.32.2
+	k8s.io/apimachinery v0.32.2
+)
+
+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/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,242 @@
+/*
+	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{}
+
+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.
+	// The system shutsdown after logging the message.
+	FatalLevel = "fatal"
+)
+
+// logger instance
+const (
+	InstanceZapLogger int = iota
+)
+
+// default config log timestamp
+const (
+	TimestampFormat    = "15:04:05.999 02/01/2006 (MST)"
+	DEFAULTLOGFILEPATH = "/var/log/arraycontroller.log"
+	DEFAULTLOGLEVEL    = InfoLevel
+)
+
+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/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,25 @@
+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
+    # namespaceSelector:
+    #   matchExpressions:
+    #   - key: kubernetes.io/metadata.name
+    #     operator: In
+    #     values:
+    #     - an-val
+    rules:
+      - operations: ["CREATE", "UPDATE"]
+        apiGroups: ["route.openshift.io"]
+        apiVersions: ["v1"]
+        resources: ["routes"]
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,89 @@
+/*
+	Copyright 2025 Array Networks
+*/
+
+package health
+
+import (
+	"encoding/json"
+
+	"arraynetworks.com/array-ingress-controller/apvadapter/apvurlbuilder"
+	"arraynetworks.com/array-ingress-controller/apvclient"
+	"arraynetworks.com/array-ingress-controller/logger"
+)
+
+var (
+	log logger.Logger
+)
+
+func init() {
+	log = logger.GetLogger()
+}
+
+// HealthCheckConfig represents the health check configuration for a real service
+type HealthCheckConfig struct {
+	RealService string `json:"real_service"`
+	Interval    int    `json:"interval"`
+	Timeout     int    `json:"timeout"`
+	Retries     int    `json:"retries"`
+}
+
+// NewHealthCheckConfig initializes and returns a new HealthCheckConfig struct
+func NewHealthCheckConfig(realService string, interval, timeout, retries int) *HealthCheckConfig {
+	return &HealthCheckConfig{
+		RealService: realService,
+		Interval:    interval,
+		Timeout:     timeout,
+		Retries:     retries,
+	}
+}
+
+// CreateHealthCheck applies the health check settings to the given real service
+func (hc *HealthCheckConfig) CreateHealthCheck() error {
+	client := apvclient.GetAPVClient()
+	jsonData, err := json.Marshal(hc)
+	if err != nil {
+		log.Errorf("Error marshalling health check JSON: %v", err)
+		return err
+	}
+
+	annotationUrl := apvurlbuilder.GetAnnotationURL(apvurlbuilder.BaseURL["HealthCheck"], hc.RealService)
+	_, err = client.Create(annotationUrl, jsonData)
+	if err != nil {
+		log.Errorf("Error creating health check: %v", err)
+		return err
+	}
+
+	return nil
+}
+
+// UpdateHealthCheck updates the existing health check settings
+func (hc *HealthCheckConfig) UpdateHealthCheck() error {
+	client := apvclient.GetAPVClient()
+	jsonData, err := json.Marshal(hc)
+	if err != nil {
+		log.Errorf("Error marshalling health check JSON: %v", err)
+		return err
+	}
+
+	annotationUrl := apvurlbuilder.GetAnnotationURL(apvurlbuilder.BaseURL["HealthCheck"], hc.RealService)
+	_, err = client.Update(annotationUrl, jsonData)
+	if err != nil {
+		log.Errorf("Error updating health check: %v", err)
+		return err
+	}
+
+	return nil
+}
+
+// DeleteHealthCheck removes the health check settings
+func (hc *HealthCheckConfig) DeleteHealthCheck() error {
+	client := apvclient.GetAPVClient()
+	endpoint := apvurlbuilder.GetAnnotationURL(apvurlbuilder.BaseURL["HealthCheck"], hc.RealService)
+	_, err := client.Delete(endpoint)
+	if err != nil {
+		log.Errorf("Error deleting health check: %v", err)
+		return err
+	}
+	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,93 @@
+//go:build !production
+// +build !production
+
+package health_test
+
+import (
+	"bytes"
+	"encoding/json"
+	"testing"
+
+	"array-ingress-ctrl/health"
+	"array-ingress-ctrl/mocks"
+
+	"github.com/golang/mock/gomock"
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+)
+
+func TestHealth(t *testing.T) {
+	RegisterFailHandler(Fail)
+	RunSpecs(t, "Health Suite")
+}
+
+var _ = Describe("HealthCheckClient", func() {
+	var (
+		mockCtrl   *gomock.Controller
+		mockClient *mocks.MockServiceClient
+		mockConfig *mocks.MockConfigStorePublic
+		hcClient   *health.HealthCheckClient
+		hcConfig   *health.HealthCheckConfig
+	)
+
+	BeforeEach(func() {
+		mockCtrl = gomock.NewController(GinkgoT())
+		mockClient = mocks.NewMockServiceClient(mockCtrl)
+		mockConfig = mocks.NewMockConfigStorePublic(mockCtrl)
+
+		hcConfig = &health.HealthCheckConfig{
+			HCName:        "test-hc",
+			ServiceName:   "test-service",
+			Type:          "http",
+			HcUp:          3,
+			HcDown:        3,
+			IPv4:          "192.168.1.1",
+			Port:          80,
+			SendInterval:  10,
+			ServerTimeout: 5,
+		}
+
+		hcClient = &health.HealthCheckClient{
+			Config: hcConfig,
+			Client: mockClient,
+		}
+	})
+
+	AfterEach(func() {
+		mockCtrl.Finish()
+	})
+
+	Describe("CreateHealthCheck", func() {
+		It("should create a health check successfully", func() {
+			jsonData, _ := json.Marshal(hcConfig)
+			mockClient.EXPECT().Create(health.HealthCheckEndpoint, bytes.NewBuffer(jsonData)).Return(nil)
+			mockConfig.EXPECT().Create(hcConfig).Return(nil)
+
+			expectErr := hcClient.CreateHealthCheck()
+			Expect(expectErr).ToNot(HaveOccurred())
+		})
+	})
+
+	Describe("UpdateHealthCheck", func() {
+		It("should update a health check successfully", func() {
+			jsonData, _ := json.Marshal(hcConfig)
+			mockClient.EXPECT().Patch(health.HealthCheckEndpoint, bytes.NewBuffer(jsonData)).Return(nil)
+			mockConfig.EXPECT().Update(hcConfig).Return(nil)
+
+			expectErr := hcClient.UpdateHealthCheck()
+			Expect(expectErr).ToNot(HaveOccurred())
+		})
+	})
+
+	Describe("DeleteHealthCheck", func() {
+		It("should delete a health check successfully", func() {
+			deleteRequest := health.HealthCheckDeleteRequest{HCName: hcConfig.HCName}
+			jsonData, _ := json.Marshal(deleteRequest)
+			mockClient.EXPECT().Delete(health.HealthCheckEndpoint, bytes.NewBuffer(jsonData)).Return(nil)
+			mockConfig.EXPECT().Delete(hcConfig).Return(nil)
+
+			expectErr := hcClient.DeleteHealthCheck()
+			Expect(expectErr).ToNot(HaveOccurred())
+		})
+	})
+})
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,120 @@
+/*
+	Copyright 2025 Array Networks
+*/
+
+package ratelimit
+
+import (
+	"encoding/json"
+
+	"arraynetworks.com/array-ingress-controller/apvadapter/apvurlbuilder"
+	"arraynetworks.com/array-ingress-controller/apvclient"
+	"arraynetworks.com/array-ingress-controller/logger"
+)
+
+const (
+	httpServiceType = "http"
+)
+
+var (
+	log logger.Logger
+)
+
+func init() {
+	log = logger.GetLogger()
+}
+
+//go:generate mockgen -source=ratelimit.go -destination=mocks/mock_ratelimit.go -package=mocks
+
+// RateLimitManager defines the interface for managing rate limit configurations
+type RateLimitManager interface {
+	CreateRateLimit() error
+	UpdateRateLimit() error
+	DeleteRateLimit() error
+}
+
+// RateLimitConfig represents the rate limit configuration for a real service
+type RateLimitConfig struct {
+	RealService          string `json:"real_service"`
+	MaxCPS               int    `json:"max_cps"`
+	SoftBandwidth        int    `json:"soft_bandwidth"`
+	HardBandwidth        int    `json:"hard_bandwidth"`
+	ServerConnReuse      bool   `json:"server_conn_reuse,omitempty"`
+	MaxReq               int    `json:"max_req,omitempty"`
+	Timeout              int    `json:"time_out,omitempty"`
+	ServerPersistentConn bool   `json:"server_persistent_conn,omitempty"`
+	ServiceType          string `json:"-"`
+}
+
+type DeleteRateLimitRequest struct {
+	RealService string `json:"real_service"`
+}
+
+type RateLimitConfigParams struct {
+	RealService          string
+	ServiceType          string
+	MaxCPS               int
+	SoftBW               int
+	HardBW               int
+	MaxReq               int
+	Timeout              int
+	ServerConnReuse      bool
+	ServerPersistentConn bool
+}
+
+// NewRateLimitConfig initializes and returns a new RateLimitConfig struct
+func NewRateLimitConfig(params RateLimitConfigParams) RateLimitManager {
+	rl := &RateLimitConfig{
+		RealService:          params.RealService,
+		MaxCPS:               params.MaxCPS,
+		SoftBandwidth:        params.SoftBW,
+		HardBandwidth:        params.HardBW,
+		ServiceType:          params.ServiceType,
+		ServerConnReuse:      false,
+		MaxReq:               0,
+		Timeout:              0,
+		ServerPersistentConn: false,
+	}
+
+	if params.ServiceType == httpServiceType {
+		rl.ServerConnReuse = params.ServerConnReuse
+		rl.MaxReq = params.MaxReq
+		rl.Timeout = params.Timeout
+		rl.ServerPersistentConn = params.ServerPersistentConn
+	}
+
+	return rl
+}
+
+// CreateRateLimit applies the rate limit settings to the given real service
+func (rl *RateLimitConfig) CreateRateLimit() error {
+	client := apvclient.GetAPVClient()
+	jsonData, err := json.Marshal(rl)
+	if err != nil {
+		log.Errorf("Error marshalling rate limit JSON: %v", err)
+		return err
+	}
+
+	annotationUrl := apvurlbuilder.GetAnnotationURL(apvurlbuilder.BaseURL["RateLimit"], rl.RealService)
+	return client.Create(annotationUrl, jsonData)
+}
+
+// UpdateRateLimit updates the existing rate limit settings
+func (rl *RateLimitConfig) UpdateRateLimit() error {
+	client := apvclient.GetAPVClient()
+	jsonData, err := json.Marshal(rl)
+	if err != nil {
+		log.Errorf("Error marshalling rate limit JSON: %v", err)
+		return err
+	}
+
+	annotationUrl := apvurlbuilder.GetAnnotationURL(apvurlbuilder.BaseURL["RateLimit"], rl.RealService)
+	return client.Patch(annotationUrl, jsonData)
+}
+
+// DeleteRateLimit removes the rate limit settings
+func (rl *RateLimitConfig) DeleteRateLimit() error {
+	client := apvclient.GetAPVClient()
+	annotationUrl := apvurlbuilder.GetAnnotationURL(apvurlbuilder.BaseURL["RateLimit"], rl.RealService)
+	return client.Delete(annotationUrl)
+}
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,96 @@
+//go:build !production
+// +build !production
+
+package ratelimit_test
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"testing"
+
+	"array-ingress-ctrl/mocks"
+	"array-ingress-ctrl/ratelimit"
+
+	"github.com/golang/mock/gomock"
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+)
+
+func TestRateLimit(t *testing.T) {
+	RegisterFailHandler(Fail)
+	RunSpecs(t, "RateLimit Suite")
+}
+
+var _ = Describe("RateLimitConfig", func() {
+	var (
+		mockCtrl   *gomock.Controller
+		mockClient *mocks.MockServiceClient
+		mockStore  *mocks.MockConfigStorePublic
+		rateLimit  *ratelimit.RateLimitConfig
+	)
+
+	BeforeEach(func() {
+		mockCtrl = gomock.NewController(GinkgoT())
+		mockClient = mocks.NewMockServiceClient(mockCtrl)
+		mockStore = mocks.NewMockConfigStorePublic(mockCtrl)
+		rateLimit = &ratelimit.RateLimitConfig{
+			RealService:   "test-service",
+			MaxCPS:        100,
+			SoftBandwidth: 200,
+			HardBandwidth: 300,
+		}
+	})
+
+	AfterEach(func() {
+		mockCtrl.Finish()
+	})
+
+	Describe("CreateRateLimit", func() {
+		Context("when the API call succeeds", func() {
+			It("should successfully create a rate limit", func() {
+				jsonData, _ := json.Marshal(rateLimit)
+				mockClient.EXPECT().Patch(gomock.Any(), bytes.NewBuffer(jsonData)).Return(nil)
+				mockStore.EXPECT().Create(rateLimit).Return(nil)
+
+				expectErr := rateLimit.CreateRateLimit(mockClient)
+				Expect(expectErr).To(BeNil())
+			})
+		})
+
+		Context("when the API call fails", func() {
+			It("should return an error", func() {
+				mockClient.EXPECT().Patch(gomock.Any(), gomock.Any()).Return(errors.New("API failure"))
+
+				expectErr := rateLimit.CreateRateLimit(mockClient)
+				Expect(expectErr).To(HaveOccurred())
+			})
+		})
+	})
+
+	Describe("UpdateRateLimit", func() {
+		Context("when updating the rate limit successfully", func() {
+			It("should successfully update a rate limit", func() {
+				jsonData, _ := json.Marshal(rateLimit)
+				mockClient.EXPECT().Patch(gomock.Any(), bytes.NewBuffer(jsonData)).Return(nil)
+				mockStore.EXPECT().Update(rateLimit).Return(nil)
+
+				expectErr := rateLimit.UpdateRateLimit(mockClient)
+				Expect(expectErr).To(BeNil())
+			})
+		})
+	})
+
+	Describe("DeleteRateLimit", func() {
+		Context("when deleting the rate limit successfully", func() {
+			It("should successfully delete a rate limit", func() {
+				jsonData, _ := json.Marshal(ratelimit.DeleteRateLimitRequest{RealService: "test-service"})
+				mockClient.EXPECT().Delete(gomock.Any(), bytes.NewBuffer(jsonData)).Return(nil)
+				mockStore.EXPECT().Delete(rateLimit).Return(nil)
+
+				expectErr := rateLimit.DeleteRateLimit(mockClient)
+				Expect(expectErr).To(BeNil())
+			})
+		})
+	})
+})
\ No newline at end of file
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/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,24 @@
+/*
+	Copyright 2025 Array Networks
+*/
+
+package apvurlbuilder
+
+import (
+	"fmt"
+)
+
+// BaseURL for different annotations
+var BaseURL = map[string]string{
+	"RealService": "loadbalancing/slb/rs/RealService",
+	"RateLimit":   "loadbalancing/slb/rs/",
+	"HealthCheck": "loadbalancing/slb/rs/healthcheck",
+}
+
+// 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
+}
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 9)
+++ /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 9)
+++ /branches/main/array-ingress-controller/apvadapter/cmd.go	(working copy)
@@ -1,18 +1,17 @@
 /*
 	Copyright 2025 Array Networks
-
 */
 
 package apvadapter
 
 import (
+	"sync"
 	"time"
 
-	"gorm.io/gorm"
-
+	"arraynetworks.com/array-ingress-controller/apvadapter/health"
+	"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"
 )
 
 var (
@@ -23,11 +22,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 +43,66 @@
 	return services
 }
 
+// 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.Service) {
-	dbclient := configstore.Get()
+func processService(svc *configstore.RealService) {
+	dbclient, err := configstore.Get()
+	if dbclient == nil {
+		log.Error("DB Client is NIL")
+		return
+	}
+
 	const maxRetries = 5
 	retryCount := 0
 
 	// 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,26 +116,26 @@
 		svc.Enable,
 	)
 
-	var err error
-
-	// Retry loop
+	// 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)
 			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)
 			}
 			return
@@ -87,25 +147,219 @@
 		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)
 	}
 }
 
+func processHealthCheck(HChecks *configstore.HealthCheck) {
+	dbclient, err := configstore.Get()
+	if dbclient == nil {
+		log.Error("DB Client is NIL")
+		return
+	}
+
+	const maxRetries = 5
+	retryCount := 0
+
+	// 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.ServiceName,
+		HChecks.SendInterval,
+		HChecks.ServerTimeout,
+		HChecks.Retries,
+	)
+
+	// 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)
+			}
+			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 dbclient == nil {
+// 		log.Error("DB Client is NIL")
+// 		return
+// 	}
+
+// 	const maxRetries = 5
+// 	retryCount := 0
+
+// 	// Update status to INPROGRESS
+// 	rl.Status = "INPROGRESS"
+// 	if err = dbclient.Update(rl); err != nil {
+// 		log.Errorf("Failed to update status to INPROGRESS for ratelimit %s: %v", rlHCName, err)
+// 		return
+// 	}
+
+// 	rl1 := ratelimit.NewRateLimitConfig(
+// 		rlHCName,
+// 		rl.MaxCPS,
+// 		rl.SoftBandwidth,
+// 		rl.HardBandwidth,
+// 	)
+
+// 	// 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, rlHCName)
+// 			rl.Status = "FAILED"
+// 			_ = dbclient.Update(rl)
+// 			return
+// 		}
+
+// 		if err == nil {
+// 			// Success → Update status to COMPLETED
+// 			rl.Status = "COMPLETED"
+// 			if err := dbclient.Update(rl); err != nil {
+// 				log.Errorf("Failed to update status to COMPLETED for ratelimit %s: %v", rlHCName, err)
+// 			}
+// 			return
+// 		}
+
+// 		// Log the error and retry
+// 		log.Errorf("Error processing ratelimit %s (attempt %d/%d): %v", rlHCName, 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); err != nil {
+// 		log.Errorf("Failed to update status to FAILED for ratelimit %s: %v", rlHCName, err)
+// 	} else {
+// 		log.Errorf("Ratelimit %s failed after %d retries, marked as FAILED", rlHCName, maxRetries)
+// 	}
+// }
+
 // StartReconcile continuously processes services in the queue
 func StartReconcile() {
 	for {
+		log.Info("Reconciling....")
 		svcs := fetchServices()
-		if svcs == nil {
-			time.Sleep(5 * time.Second)
+		hcs := fetchHealthCheck()
+		rls := fetchRateLimit()
+
+		if len(hcs) == 0 && len(rls) == 0 && len(svcs) == 0 {
+			log.Info("No RealServices, HealthChecks, or RateLimits in db to process")
+			time.Sleep(120 * time.Second)
 			continue
 		}
+
+		var wg sync.WaitGroup
+
 		for i := range svcs {
-			go processService(&svcs[i])
+			svc := &svcs[i]
+			log.Infof("Processing RealService: %s", svc.ServiceName)
+			processService(svc)
 		}
-		time.Sleep(5 * time.Second)
+
+		conflictMap := make(map[string]bool)
+		var mu sync.Mutex
+		//Assuming Conflicts will be on service name
+		for i := range hcs {
+			wg.Add(1)
+			go func(hc *configstore.HealthCheck) {
+				defer wg.Done()
+
+				mu.Lock()
+				if conflictMap[hc.ServiceName] {
+					log.Warnf("Skipping HealthCheck %s due to conflict", hc.HCName)
+					mu.Unlock()
+					return
+				}
+				conflictMap[hc.ServiceName] = true
+				mu.Unlock()
+
+				log.Infof("Processing HealthCheck: %s", hc.HCName)
+				processHealthCheck(hc)
+
+				mu.Lock()
+				delete(conflictMap, hc.ServiceName)
+				mu.Unlock()
+			}(&hcs[i])
+		}
+
+		// for i := range rls {
+		// 	wg.Add(1)
+		// 	go func(rl *configstore.RateLimit) {
+		// 		defer wg.Done()
+
+		// 		mu.Lock()
+		// 		if conflictMap[rl.ServiceName] {
+		// 			log.Warnf("Skipping RateLimit %s due to conflict", rl.RLName)
+		// 			mu.Unlock()
+		// 			return
+		// 		}
+		// 		conflictMap[rl.ServiceName] = true
+		// 		mu.Unlock()
+
+		// 		log.Infof("Processing RateLimit: %s", rl.RLName)
+		// 		processRateLimit(rl)
+
+		// 		mu.Lock()
+		// 		delete(conflictMap, rl.ServiceName)
+		// 		mu.Unlock()
+		// 	}(&rls[i])
+		// }
+
+		wg.Wait()
+		time.Sleep(120 * time.Second)
 	}
 }
Index: /branches/main/array-ingress-controller/apvadapter/go.mod
===================================================================
--- /branches/main/array-ingress-controller/apvadapter/go.mod	(revision 9)
+++ /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,100 @@
+/*
+	Copyright 2025 Array Networks
+*/
+
+package realservice
+
+import (
+	"encoding/json"
+	"strings"
+
+	"arraynetworks.com/array-ingress-controller/apvadapter/apvurlbuilder"
+	"arraynetworks.com/array-ingress-controller/apvclient"
+	"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 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 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"], "")
+	_, 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)
+	return nil
+}
+
+func (rs *RealService) UpdateRealService() error {
+	client := apvclient.GetAPVClient()
+	jsonData, err := json.Marshal(rs)
+	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)
+	_, 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)
+	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)
+	_, 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)
+	return nil
+}
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/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,182 @@
+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)
+	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.MethodPatch, 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/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/go.mod
===================================================================
--- /branches/main/array-ingress-controller/go.mod	(revision 9)
+++ /branches/main/array-ingress-controller/go.mod	(working copy)
@@ -1,53 +1,120 @@
-module arraynetworks.com/array-ingress-controller
+module arraynetworks.com/array-ingress-controller/main
 
 go 1.23.6
 
+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/realservice => ./apvadapter/realservice
+	arraynetworks.com/array-ingress-controller/apvclient => ./apvadapter/apvclient
+	arraynetworks.com/array-ingress-controller/common => ./common
+	arraynetworks.com/array-ingress-controller/configstore => ./configstore
+	arraynetworks.com/array-ingress-controller/controller => ./controller
+	arraynetworks.com/array-ingress-controller/controller/openshift => ./controller/openshift-controller
+	arraynetworks.com/array-ingress-controller/controller/openshift-controller/handlers => ./controller/openshift-controller/handlers
+	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 => ./task-manager/openshift
+	arraynetworks.com/array-ingress-controller/task-manager/task => ./task-manager/task
+	arraynetworks.com/array-ingress-controller/watchtower => ./watchtower
+)
+
 require (
-	github.com/golang/mock v1.6.0
-	github.com/onsi/ginkgo/v2 v2.22.2
-	github.com/onsi/gomega v1.36.2
-	github.com/openshift/api v0.0.0-20250220025300-cfbda0b31000 // indirect
-	go.uber.org/zap v1.27.0
-	gopkg.in/natefinch/lumberjack.v2 v2.2.1
-	gorm.io/driver/sqlite v1.5.7
-	gorm.io/gorm v1.25.12
-	k8s.io/api v0.32.2 // indirect
-	k8s.io/apimachinery v0.32.2 // indirect
-	k8s.io/klog/v2 v2.130.1 // indirect
+	arraynetworks.com/array-ingress-controller/apvadapter 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
+	k8s.io/api v0.32.2
+	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.2
 )
 
 require (
+	arraynetworks.com/array-ingress-controller/apvadapter/apvurlbuilder 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 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
+	github.com/cenkalti/backoff/v4 v4.3.0 // indirect
+	github.com/cespare/xxhash/v2 v2.3.0 // indirect
+	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
+	github.com/emicklei/go-restful/v3 v3.11.0 // indirect
+	github.com/evanphx/json-patch/v5 v5.9.11 // indirect
+	github.com/fsnotify/fsnotify v1.7.0 // indirect
 	github.com/fxamacker/cbor/v2 v2.7.0 // indirect
 	github.com/go-logr/logr v1.4.2 // indirect
-	github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
+	github.com/go-openapi/jsonpointer v0.21.0 // indirect
+	github.com/go-openapi/jsonreference v0.20.2 // indirect
+	github.com/go-openapi/swag v0.23.0 // indirect
+	github.com/go-resty/resty/v2 v2.16.5 // indirect
+	github.com/go-sql-driver/mysql v1.8.1 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
+	github.com/golang/protobuf v1.5.4 // indirect
+	github.com/google/btree v1.1.3 // indirect
+	github.com/google/gnostic-models v0.6.8 // indirect
 	github.com/google/go-cmp v0.6.0 // indirect
 	github.com/google/gofuzz v1.2.0 // indirect
-	github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
+	github.com/google/uuid v1.6.0 // indirect
 	github.com/jinzhu/inflection v1.0.0 // indirect
 	github.com/jinzhu/now v1.1.5 // indirect
+	github.com/josharian/intern v1.0.0 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/mailru/easyjson v0.7.7 // indirect
 	github.com/mattn/go-sqlite3 v1.14.22 // indirect
 	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-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
+	github.com/prometheus/client_model v0.6.1 // indirect
+	github.com/prometheus/common v0.55.0 // indirect
+	github.com/prometheus/procfs v0.15.1 // indirect
+	github.com/spf13/pflag v1.0.5 // indirect
 	github.com/x448/float16 v0.8.4 // indirect
-	go.uber.org/multierr v1.10.0 // indirect
+	go.uber.org/multierr v1.11.0 // indirect
+	go.uber.org/zap v1.27.0 // indirect
 	golang.org/x/net v0.33.0 // indirect
+	golang.org/x/oauth2 v0.23.0 // indirect
+	golang.org/x/sync v0.10.0 // indirect
 	golang.org/x/sys v0.28.0 // indirect
+	golang.org/x/term v0.27.0 // indirect
 	golang.org/x/text v0.21.0 // indirect
-	golang.org/x/tools v0.28.0 // indirect
+	golang.org/x/time v0.7.0 // indirect
+	gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
+	google.golang.org/protobuf v1.35.1 // indirect
+	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
+	gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
+	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
+	gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
+	gorm.io/datatypes v1.2.5 // indirect
+	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/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
 	sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
 	sigs.k8s.io/yaml v1.4.0 // indirect
 )
-
-require arraynetworks.com/array-ingress-controller/task-manager/task v0.0.0-00010101000000-000000000000
-
-replace (
-	arraynetworks.com/array-ingress-controller/apvadapter => ./apvadapter
-	arraynetworks.com/array-ingress-controller/controller/openshift-controller/ => ./controller/openshift-controller
-	arraynetworks.com/array-ingress-controller/task-manager/task => ./task-manager/task
-)
