From 1800c76b53a4371a7a4c5f7e6197701e2e20725d Mon Sep 17 00:00:00 2001 From: liwener Date: Mon, 29 Sep 2025 16:23:14 +0800 Subject: [PATCH 1/7] [feat]: add support of multicluster --- pkg/cluster/manager.go | 296 ++++++++++++++++++ pkg/kube/kubetypes/types.go | 1 + sail/cmd/sail-discovery/app/cmd.go | 53 ++++ sail/configs/multi-cluster.yaml | 165 ++++++++++ sail/configs/multi-registry.yaml | 103 ++++++ sail/pkg/bootstrap/options.go | 58 ++++ sail/pkg/bootstrap/servicecontroller.go | 251 +++++++++++++++ .../serviceregistry/aggregate/controller.go | 159 +++++++++- sail/pkg/serviceregistry/nacos/config.go | 85 +++++ sail/pkg/serviceregistry/nacos/controller.go | 161 ++++++++++ sail/pkg/serviceregistry/nacos/discovery.go | 154 +++++++++ sail/pkg/serviceregistry/provider/provider.go | 4 + sail/pkg/serviceregistry/zookeeper/config.go | 79 +++++ .../serviceregistry/zookeeper/controller.go | 160 ++++++++++ .../serviceregistry/zookeeper/discovery.go | 203 ++++++++++++ 15 files changed, 1931 insertions(+), 1 deletion(-) create mode 100644 pkg/cluster/manager.go create mode 100644 sail/configs/multi-cluster.yaml create mode 100644 sail/configs/multi-registry.yaml create mode 100644 sail/pkg/serviceregistry/nacos/config.go create mode 100644 sail/pkg/serviceregistry/nacos/controller.go create mode 100644 sail/pkg/serviceregistry/nacos/discovery.go create mode 100644 sail/pkg/serviceregistry/zookeeper/config.go create mode 100644 sail/pkg/serviceregistry/zookeeper/controller.go create mode 100644 sail/pkg/serviceregistry/zookeeper/discovery.go diff --git a/pkg/cluster/manager.go b/pkg/cluster/manager.go new file mode 100644 index 000000000..7e22427dd --- /dev/null +++ b/pkg/cluster/manager.go @@ -0,0 +1,296 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cluster + +import ( + "context" + "fmt" + "sync" + "time" + + "k8s.io/klog/v2" +) + +// Manager manages multiple Kubernetes clusters +type Manager struct { + clusters map[ID]*ClusterInfo + clustersLock sync.RWMutex + + // Default cluster ID + defaultCluster ID + + // Health check interval + healthCheckInterval time.Duration + + // Stop channel for manager + stopCh chan struct{} +} + +// ClusterInfo holds information about a managed cluster +type ClusterInfo struct { + ID ID + Name string + KubeConfig string + Context string + Enabled bool + Priority int + Region string + Zone string + + // Kubernetes client for this cluster (interface{} to avoid import cycles) + Client interface{} + + // Health status + Healthy bool + LastCheck time.Time + Error error + + // Connection metrics + ConnectedAt time.Time + LastUsed time.Time +} + +// NewManager creates a new cluster manager +func NewManager(defaultCluster ID) *Manager { + return &Manager{ + clusters: make(map[ID]*ClusterInfo), + defaultCluster: defaultCluster, + healthCheckInterval: 30 * time.Second, + stopCh: make(chan struct{}), + } +} + +// AddCluster adds a new cluster to the manager +func (m *Manager) AddCluster(config ClusterConfig) error { + m.clustersLock.Lock() + defer m.clustersLock.Unlock() + + clusterID := ID(config.ID) + + // Check if cluster already exists + if _, exists := m.clusters[clusterID]; exists { + return fmt.Errorf("cluster %s already exists", clusterID) + } + + // Create cluster info + clusterInfo := &ClusterInfo{ + ID: clusterID, + Name: config.Name, + KubeConfig: config.KubeConfig, + Context: config.Context, + Enabled: config.Enabled, + Priority: config.Priority, + Region: config.Region, + Zone: config.Zone, + Healthy: false, + LastCheck: time.Now(), + } + + // Initialize Kubernetes client if enabled + if config.Enabled { + if err := m.initClusterClient(clusterInfo); err != nil { + klog.Errorf("Failed to initialize client for cluster %s: %v", clusterID, err) + clusterInfo.Error = err + } else { + clusterInfo.Healthy = true + clusterInfo.ConnectedAt = time.Now() + } + } + + m.clusters[clusterID] = clusterInfo + klog.Infof("Added cluster %s (%s) to manager", clusterID, config.Name) + + return nil +} + +// RemoveCluster removes a cluster from the manager +func (m *Manager) RemoveCluster(clusterID ID) error { + m.clustersLock.Lock() + defer m.clustersLock.Unlock() + + if _, exists := m.clusters[clusterID]; !exists { + return fmt.Errorf("cluster %s not found", clusterID) + } + + delete(m.clusters, clusterID) + klog.Infof("Removed cluster %s from manager", clusterID) + + return nil +} + +// GetCluster returns cluster information by ID +func (m *Manager) GetCluster(clusterID ID) (*ClusterInfo, error) { + m.clustersLock.RLock() + defer m.clustersLock.RUnlock() + + cluster, exists := m.clusters[clusterID] + if !exists { + return nil, fmt.Errorf("cluster %s not found", clusterID) + } + + // Update last used time + cluster.LastUsed = time.Now() + + return cluster, nil +} + +// GetHealthyClusters returns a list of healthy clusters +func (m *Manager) GetHealthyClusters() []*ClusterInfo { + m.clustersLock.RLock() + defer m.clustersLock.RUnlock() + + var healthyClusters []*ClusterInfo + for _, cluster := range m.clusters { + if cluster.Enabled && cluster.Healthy { + healthyClusters = append(healthyClusters, cluster) + } + } + + return healthyClusters +} + +// GetAllClusters returns all managed clusters +func (m *Manager) GetAllClusters() []*ClusterInfo { + m.clustersLock.RLock() + defer m.clustersLock.RUnlock() + + var allClusters []*ClusterInfo + for _, cluster := range m.clusters { + allClusters = append(allClusters, cluster) + } + + return allClusters +} + +// GetDefaultCluster returns the default cluster +func (m *Manager) GetDefaultCluster() ID { + return m.defaultCluster +} + +// SetDefaultCluster sets the default cluster +func (m *Manager) SetDefaultCluster(clusterID ID) error { + m.clustersLock.RLock() + defer m.clustersLock.RUnlock() + + if _, exists := m.clusters[clusterID]; !exists { + return fmt.Errorf("cluster %s not found", clusterID) + } + + m.defaultCluster = clusterID + klog.Infof("Set default cluster to %s", clusterID) + + return nil +} + +// Start starts the cluster manager +func (m *Manager) Start(ctx context.Context) { + klog.Info("Starting cluster manager") + + // Start health check routine + go m.healthCheckLoop(ctx) + + <-ctx.Done() + klog.Info("Cluster manager stopped") +} + +// Stop stops the cluster manager +func (m *Manager) Stop() { + close(m.stopCh) +} + +// initClusterClient initializes the Kubernetes client for a cluster +func (m *Manager) initClusterClient(cluster *ClusterInfo) error { + // TODO: Implement actual Kubernetes client initialization + // This would typically involve: + // 1. Loading kubeconfig from cluster.KubeConfig + // 2. Setting context to cluster.Context if specified + // 3. Creating kube.CLIClient + // 4. Testing connectivity + + klog.V(2).Infof("Initializing Kubernetes client for cluster %s", cluster.ID) + + // Mock implementation - set client to nil for now + cluster.Client = nil + + return nil +} + +// healthCheckLoop runs periodic health checks on all clusters +func (m *Manager) healthCheckLoop(ctx context.Context) { + ticker := time.NewTicker(m.healthCheckInterval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + m.performHealthChecks() + } + } +} + +// performHealthChecks performs health checks on all managed clusters +func (m *Manager) performHealthChecks() { + m.clustersLock.RLock() + clusters := make([]*ClusterInfo, 0, len(m.clusters)) + for _, cluster := range m.clusters { + if cluster.Enabled { + clusters = append(clusters, cluster) + } + } + m.clustersLock.RUnlock() + + for _, cluster := range clusters { + m.checkClusterHealth(cluster) + } +} + +// checkClusterHealth checks the health of a single cluster +func (m *Manager) checkClusterHealth(cluster *ClusterInfo) { + klog.V(4).Infof("Checking health of cluster %s", cluster.ID) + + cluster.LastCheck = time.Now() + + // TODO: Implement actual health check + // This would typically involve: + // 1. Making a simple API call to Kubernetes API server + // 2. Checking response time and success + // 3. Updating health status + + // Mock implementation - assume healthy for now + if cluster.Client != nil { + cluster.Healthy = true + cluster.Error = nil + } else { + cluster.Healthy = false + cluster.Error = fmt.Errorf("no client available") + } +} + +// ClusterConfig represents the configuration for a single cluster +type ClusterConfig struct { + ID string + Name string + KubeConfig string + Context string + Enabled bool + Priority int + Region string + Zone string +} diff --git a/pkg/kube/kubetypes/types.go b/pkg/kube/kubetypes/types.go index 5b6aec02e..325352739 100644 --- a/pkg/kube/kubetypes/types.go +++ b/pkg/kube/kubetypes/types.go @@ -19,6 +19,7 @@ package kubetypes import ( "context" + "github.com/apache/dubbo-kubernetes/pkg/cluster" "github.com/apache/dubbo-kubernetes/pkg/util/sets" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/sail/cmd/sail-discovery/app/cmd.go b/sail/cmd/sail-discovery/app/cmd.go index 09e29571f..77ff16c8a 100644 --- a/sail/cmd/sail-discovery/app/cmd.go +++ b/sail/cmd/sail-discovery/app/cmd.go @@ -19,6 +19,7 @@ package app import ( "fmt" + "github.com/apache/dubbo-kubernetes/pkg/cmd" "github.com/apache/dubbo-kubernetes/pkg/config/constants" "github.com/apache/dubbo-kubernetes/pkg/ctrlz" @@ -30,6 +31,25 @@ import ( var ( serverArgs *bootstrap.SailArgs + + // Multi-cluster configuration variables + multiClusterConfigFile string + + // Multi-registry configuration variables + multiRegistryConfigFile string + + // Nacos configuration variables + nacosServerAddrs string + nacosNamespace string + nacosGroup string + nacosUsername string + nacosPassword string + + // Zookeeper configuration variables + zookeeperServers string + zookeeperRoot string + zookeeperUsername string + zookeeperPassword string ) func NewRootCommand() *cobra.Command { @@ -121,4 +141,37 @@ func addFlags(c *cobra.Command) { "The ID of the cluster that this Dubbod instance resides") c.PersistentFlags().StringToStringVar(&serverArgs.RegistryOptions.KubeOptions.ClusterAliases, "clusterAliases", map[string]string{}, "Alias names for clusters. Example: alias1=cluster1,alias2=cluster2") + + // Multi-cluster configuration flags + c.PersistentFlags().BoolVar(&serverArgs.RegistryOptions.MultiCluster.Enabled, "multiClusterEnabled", false, + "Enable multi-cluster support") + c.PersistentFlags().StringVar(&multiClusterConfigFile, "multiClusterConfig", "", + "Path to multi-cluster configuration file") + + // Multi-registry configuration flags + c.PersistentFlags().BoolVar(&serverArgs.RegistryOptions.MultiRegistry.Enabled, "multiRegistryEnabled", false, + "Enable multi-registry support") + c.PersistentFlags().StringVar(&multiRegistryConfigFile, "multiRegistryConfig", "", + "Path to multi-registry configuration file") + + // Individual registry configuration flags + c.PersistentFlags().StringVar(&nacosServerAddrs, "nacosServers", "", + "Comma separated list of Nacos server addresses (host:port)") + c.PersistentFlags().StringVar(&nacosNamespace, "nacosNamespace", "public", + "Nacos namespace to use") + c.PersistentFlags().StringVar(&nacosGroup, "nacosGroup", "DEFAULT_GROUP", + "Nacos group to use") + c.PersistentFlags().StringVar(&nacosUsername, "nacosUsername", "", + "Username for Nacos authentication") + c.PersistentFlags().StringVar(&nacosPassword, "nacosPassword", "", + "Password for Nacos authentication") + + c.PersistentFlags().StringVar(&zookeeperServers, "zookeeperServers", "", + "Comma separated list of Zookeeper server addresses (host:port)") + c.PersistentFlags().StringVar(&zookeeperRoot, "zookeeperRoot", "/dubbo", + "Root path for Dubbo services in Zookeeper") + c.PersistentFlags().StringVar(&zookeeperUsername, "zookeeperUsername", "", + "Username for Zookeeper authentication") + c.PersistentFlags().StringVar(&zookeeperPassword, "zookeeperPassword", "", + "Password for Zookeeper authentication") } diff --git a/sail/configs/multi-cluster.yaml b/sail/configs/multi-cluster.yaml new file mode 100644 index 000000000..635acda71 --- /dev/null +++ b/sail/configs/multi-cluster.yaml @@ -0,0 +1,165 @@ +# Multi-Cluster Configuration for Dubbo Admin +# This file defines multiple Kubernetes clusters that Dubbo Admin can manage + +apiVersion: v1 +kind: Config +metadata: + name: multi-cluster-config + namespace: dubbo-system + +# Multi-cluster configuration +multiCluster: + enabled: true + clusters: + # Primary cluster (local) + - id: "primary" + name: "Primary Cluster" + kubeConfig: "" # Empty means in-cluster config + context: "" # Empty means current context + enabled: true + priority: 100 + region: "us-west-1" + zone: "us-west-1a" + + # Remote cluster 1 + - id: "remote-1" + name: "Remote Cluster East" + kubeConfig: "/etc/kubeconfig/remote-1.yaml" + context: "remote-1-context" + enabled: true + priority: 90 + region: "us-east-1" + zone: "us-east-1a" + + # Remote cluster 2 + - id: "remote-2" + name: "Remote Cluster Europe" + kubeConfig: "/etc/kubeconfig/remote-2.yaml" + context: "remote-2-context" + enabled: true + priority: 80 + region: "eu-west-1" + zone: "eu-west-1a" + + # Disaster recovery cluster + - id: "dr-cluster" + name: "Disaster Recovery Cluster" + kubeConfig: "/etc/kubeconfig/dr-cluster.yaml" + context: "dr-context" + enabled: false # Only enabled during DR scenarios + priority: 50 + region: "us-west-2" + zone: "us-west-2b" + +# Cluster-specific configurations +clusterDefaults: + # Default namespace to watch + namespace: "dubbo-system" + + # Kubernetes API client configuration + kubernetesAPI: + qps: 80.0 + burst: 160 + timeout: "30s" + + # Service discovery configuration per cluster + serviceDiscovery: + syncInterval: "30s" + resyncPeriod: "10m" + +# Cross-cluster networking configuration +crossClusterNetworking: + # Enable cross-cluster service discovery + enabled: true + + # Network topology + networks: + - name: "network-1" + clusters: ["primary", "remote-1"] + gateways: + - address: "gateway-1.dubbo-system.svc.cluster.local" + port: 15443 + + - name: "network-2" + clusters: ["remote-2"] + gateways: + - address: "gateway-2.dubbo-system.svc.cluster.local" + port: 15443 + +# Multi-cluster service mesh configuration +serviceMesh: + # Enable service mesh across clusters + enabled: true + + # Trust domain for cross-cluster communication + trustDomain: "cluster.local" + + # Certificate management + certificates: + # Auto-rotation interval + rotationInterval: "24h" + + # Certificate authority configuration + ca: + # Use shared CA across clusters + shared: true + secretName: "cacerts" + secretNamespace: "dubbo-system" + +# Failover and disaster recovery configuration +failover: + # Enable automatic failover between clusters + enabled: true + + # Failover strategy + strategy: "priority" # Options: "priority", "region-aware", "custom" + + # Health check configuration + healthCheck: + enabled: true + interval: "10s" + timeout: "5s" + unhealthyThreshold: 3 + healthyThreshold: 2 + + # Disaster recovery configuration + disasterRecovery: + # Enable automatic DR cluster activation + autoActivation: false + + # RTO (Recovery Time Objective) in seconds + rto: 300 + + # RPO (Recovery Point Objective) in seconds + rpo: 60 + +# Load balancing configuration +loadBalancing: + # Cross-cluster load balancing strategy + strategy: "locality-aware" # Options: "round-robin", "priority-based", "locality-aware" + + # Locality preferences + locality: + # Prefer services in same region + regionWeight: 80 + + # Prefer services in same zone + zoneWeight: 90 + + # Failover to other regions if local unavailable + crossRegionFailover: true + +# Monitoring and observability +monitoring: + # Enable cluster health monitoring + enabled: true + + # Metrics collection interval + metricsInterval: "15s" + + # Health check endpoints + healthEndpoints: + - path: "/healthz" + port: 8080 + - path: "/ready" + port: 8080 diff --git a/sail/configs/multi-registry.yaml b/sail/configs/multi-registry.yaml new file mode 100644 index 000000000..7cc9d909e --- /dev/null +++ b/sail/configs/multi-registry.yaml @@ -0,0 +1,103 @@ +# Multi-Registry Configuration for Dubbo Admin +# This file defines multiple service registries that Dubbo Admin can connect to + +apiVersion: v1 +kind: Config +metadata: + name: multi-registry-config + namespace: dubbo-system + +# Multi-registry configuration +multiRegistry: + enabled: true + registries: + # Kubernetes Registry + - id: "kubernetes-primary" + name: "Primary Kubernetes Cluster" + type: "Kubernetes" + address: "https://kubernetes.default.svc" + enabled: true + priority: 100 + config: + kubeconfig: "" + namespace: "dubbo-system" + + # Nacos Registry + - id: "nacos-primary" + name: "Primary Nacos Registry" + type: "Nacos" + address: "nacos.dubbo-system.svc.cluster.local:8848" + enabled: true + priority: 90 + config: + serverAddrs: + - "nacos.dubbo-system.svc.cluster.local:8848" + namespace: "public" + group: "DEFAULT_GROUP" + username: "" + password: "" + timeout: "5s" + retryInterval: "30s" + maxRetries: 3 + + # Zookeeper Registry + - id: "zookeeper-primary" + name: "Primary Zookeeper Registry" + type: "Zookeeper" + address: "zookeeper.dubbo-system.svc.cluster.local:2181" + enabled: false + priority: 80 + config: + servers: + - "zookeeper.dubbo-system.svc.cluster.local:2181" + root: "/dubbo" + timeout: "5s" + sessionTimeout: "60s" + retryInterval: "30s" + maxRetries: 3 + username: "" + password: "" + +# Registry-specific configurations +registryDefaults: + # Connection timeout for all registries + connectionTimeout: "5s" + + # Health check interval + healthCheckInterval: "30s" + + # Maximum number of retry attempts + maxRetries: 3 + + # Retry backoff configuration + retryBackoff: + initialInterval: "1s" + maxInterval: "60s" + multiplier: 2.0 + +# Failover configuration +failover: + # Enable automatic failover between registries + enabled: true + + # Failover strategy: "priority" or "roundrobin" + strategy: "priority" + + # Health check configuration + healthCheck: + enabled: true + interval: "10s" + timeout: "5s" + unhealthyThreshold: 3 + healthyThreshold: 2 + +# Service discovery configuration +serviceDiscovery: + # Sync interval for service discovery + syncInterval: "30s" + + # Enable cross-registry service deduplication + deduplication: true + + # Service merging strategy for services found in multiple registries + mergingStrategy: "kubernetes-first" # Options: "kubernetes-first", "priority-based", "merge-all" diff --git a/sail/pkg/bootstrap/options.go b/sail/pkg/bootstrap/options.go index a45e9b119..4638a0f92 100644 --- a/sail/pkg/bootstrap/options.go +++ b/sail/pkg/bootstrap/options.go @@ -37,6 +37,64 @@ type RegistryOptions struct { KubeConfig string KubeOptions kubecontroller.Options ClusterRegistriesNamespace string + // Multi-cluster configuration + MultiCluster MultiClusterOptions `json:"multiCluster" yaml:"multiCluster"` + // Multi-registry configuration + MultiRegistry MultiRegistryOptions `json:"multiRegistry" yaml:"multiRegistry"` +} + +// MultiClusterOptions holds configuration for managing multiple Kubernetes clusters +type MultiClusterOptions struct { + // Enabled indicates if multi-cluster support is enabled + Enabled bool `json:"enabled" yaml:"enabled"` + // Clusters is a list of cluster configurations + Clusters []ClusterConfig `json:"clusters" yaml:"clusters"` +} + +// ClusterConfig holds configuration for a single Kubernetes cluster +type ClusterConfig struct { + // ID is the unique identifier for this cluster + ID string `json:"id" yaml:"id"` + // Name is a human-readable name for this cluster + Name string `json:"name" yaml:"name"` + // KubeConfig is the path to kubeconfig file for this cluster + KubeConfig string `json:"kubeConfig" yaml:"kubeConfig"` + // Context is the context name to use from kubeconfig + Context string `json:"context" yaml:"context"` + // Enabled indicates if this cluster is enabled + Enabled bool `json:"enabled" yaml:"enabled"` + // Priority determines the priority of this cluster (higher value = higher priority) + Priority int `json:"priority" yaml:"priority"` + // Region specifies the region this cluster is located in + Region string `json:"region" yaml:"region"` + // Zone specifies the zone this cluster is located in + Zone string `json:"zone" yaml:"zone"` +} + +// MultiRegistryOptions holds configuration for managing multiple service registries +type MultiRegistryOptions struct { + // Enabled indicates if multi-registry support is enabled + Enabled bool `json:"enabled" yaml:"enabled"` + // Registries is a list of registry configurations + Registries []RegistryConfig `json:"registries" yaml:"registries"` +} + +// RegistryConfig holds configuration for a single service registry +type RegistryConfig struct { + // ID is the unique identifier for this registry + ID string `json:"id" yaml:"id"` + // Name is a human-readable name for this registry + Name string `json:"name" yaml:"name"` + // Type specifies the registry type (Kubernetes, Nacos, Zookeeper) + Type string `json:"type" yaml:"type"` + // Address is the registry server address + Address string `json:"address" yaml:"address"` + // Enabled indicates if this registry is enabled + Enabled bool `json:"enabled" yaml:"enabled"` + // Priority determines the priority of this registry (higher value = higher priority) + Priority int `json:"priority" yaml:"priority"` + // Config holds registry-specific configuration + Config map[string]interface{} `json:"config" yaml:"config"` } type SailArgs struct { diff --git a/sail/pkg/bootstrap/servicecontroller.go b/sail/pkg/bootstrap/servicecontroller.go index f324fc9c7..cb356e809 100644 --- a/sail/pkg/bootstrap/servicecontroller.go +++ b/sail/pkg/bootstrap/servicecontroller.go @@ -2,9 +2,15 @@ package bootstrap import ( "fmt" + "strings" + "time" + + "github.com/apache/dubbo-kubernetes/pkg/cluster" "github.com/apache/dubbo-kubernetes/pkg/util/sets" "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry/aggregate" + "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry/nacos" "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry/provider" + "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry/zookeeper" "k8s.io/klog/v2" ) @@ -16,6 +22,7 @@ func (s *Server) ServiceController() *aggregate.Controller { func (s *Server) initServiceControllers(args *SailArgs) error { serviceControllers := s.ServiceController() + // Initialize standard registries from command line registered := sets.New[provider.ID]() for _, r := range args.RegistryOptions.Registries { serviceRegistry := provider.ID(r) @@ -30,11 +37,33 @@ func (s *Server) initServiceControllers(args *SailArgs) error { if err := s.initKubeRegistry(args); err != nil { return err } + case provider.Nacos: + if err := s.initNacosRegistry(args); err != nil { + return err + } + case provider.Zookeeper: + if err := s.initZookeeperRegistry(args); err != nil { + return err + } default: return fmt.Errorf("service registry %s is not supported", r) } } + // Initialize multi-registry configuration if enabled + if args.RegistryOptions.MultiRegistry.Enabled { + if err := s.initMultiRegistryControllers(args); err != nil { + return fmt.Errorf("failed to initialize multi-registry controllers: %v", err) + } + } + + // Initialize multi-cluster configuration if enabled + if args.RegistryOptions.MultiCluster.Enabled { + if err := s.initMultiClusterControllers(args); err != nil { + return fmt.Errorf("failed to initialize multi-cluster controllers: %v", err) + } + } + // Defer running of the service controllers. s.addStartFunc("service controllers", func(stop <-chan struct{}) error { go serviceControllers.Run(stop) @@ -58,3 +87,225 @@ func (s *Server) initKubeRegistry(args *SailArgs) (err error) { // TODO NewMulticluster return } + +// initNacosRegistry initializes Nacos service registry +func (s *Server) initNacosRegistry(args *SailArgs) error { + klog.Info("Initializing Nacos service registry") + + // Create Nacos configuration from command line args or config file + config := nacos.DefaultConfig() + + // TODO: Parse Nacos configuration from args or config file + // For now, use default configuration + + // Create Nacos controller + nacosController := nacos.NewController(config, s.clusterID) + if nacosController == nil { + return fmt.Errorf("failed to create Nacos controller") + } + + // Add to service controllers + s.ServiceController().AddRegistry(nacosController) + + klog.Info("Nacos service registry initialized successfully") + return nil +} + +// initZookeeperRegistry initializes Zookeeper service registry +func (s *Server) initZookeeperRegistry(args *SailArgs) error { + klog.Info("Initializing Zookeeper service registry") + + // Create Zookeeper configuration from command line args or config file + config := zookeeper.DefaultConfig() + + // TODO: Parse Zookeeper configuration from args or config file + // For now, use default configuration + + // Create Zookeeper controller + zkController := zookeeper.NewController(config, s.clusterID) + if zkController == nil { + return fmt.Errorf("failed to create Zookeeper controller") + } + + // Add to service controllers + s.ServiceController().AddRegistry(zkController) + + klog.Info("Zookeeper service registry initialized successfully") + return nil +} + +// initMultiRegistryControllers initializes multiple service registries from configuration +func (s *Server) initMultiRegistryControllers(args *SailArgs) error { + klog.Info("Initializing multi-registry controllers") + + for _, regConfig := range args.RegistryOptions.MultiRegistry.Registries { + if !regConfig.Enabled { + klog.V(2).Infof("Skipping disabled registry: %s", regConfig.ID) + continue + } + + klog.Infof("Initializing registry: %s (%s)", regConfig.Name, regConfig.Type) + + switch strings.ToLower(regConfig.Type) { + case "kubernetes": + if err := s.initKubeRegistryFromConfig(regConfig); err != nil { + return fmt.Errorf("failed to initialize Kubernetes registry %s: %v", regConfig.ID, err) + } + case "nacos": + if err := s.initNacosRegistryFromConfig(regConfig); err != nil { + return fmt.Errorf("failed to initialize Nacos registry %s: %v", regConfig.ID, err) + } + case "zookeeper": + if err := s.initZookeeperRegistryFromConfig(regConfig); err != nil { + return fmt.Errorf("failed to initialize Zookeeper registry %s: %v", regConfig.ID, err) + } + default: + klog.Warningf("Unknown registry type: %s for registry %s", regConfig.Type, regConfig.ID) + } + } + + klog.Info("Multi-registry controllers initialized successfully") + return nil +} + +// initMultiClusterControllers initializes multiple cluster configurations +func (s *Server) initMultiClusterControllers(args *SailArgs) error { + klog.Info("Initializing multi-cluster controllers") + + // Create cluster manager + clusterManager := cluster.NewManager(s.clusterID) + + // Add clusters from configuration + for _, clusterConfig := range args.RegistryOptions.MultiCluster.Clusters { + if !clusterConfig.Enabled { + klog.V(2).Infof("Skipping disabled cluster: %s", clusterConfig.ID) + continue + } + + klog.Infof("Adding cluster: %s (%s)", clusterConfig.Name, clusterConfig.ID) + + if err := clusterManager.AddCluster(cluster.ClusterConfig{ + ID: clusterConfig.ID, + Name: clusterConfig.Name, + KubeConfig: clusterConfig.KubeConfig, + Context: clusterConfig.Context, + Enabled: clusterConfig.Enabled, + Priority: clusterConfig.Priority, + Region: clusterConfig.Region, + Zone: clusterConfig.Zone, + }); err != nil { + return fmt.Errorf("failed to add cluster %s: %v", clusterConfig.ID, err) + } + } + + // TODO: Store cluster manager in server context for later use + + klog.Info("Multi-cluster controllers initialized successfully") + return nil +} + +// Helper methods for initializing registries from configuration + +func (s *Server) initKubeRegistryFromConfig(regConfig RegistryConfig) error { + // TODO: Initialize Kubernetes registry from specific configuration + klog.V(2).Infof("Initializing Kubernetes registry from config: %s", regConfig.ID) + return nil +} + +func (s *Server) initNacosRegistryFromConfig(regConfig RegistryConfig) error { + config := nacos.DefaultConfig() + + // Parse Nacos-specific configuration + if servers, ok := regConfig.Config["serverAddrs"].([]interface{}); ok { + config.ServerAddrs = make([]string, len(servers)) + for i, server := range servers { + if serverStr, ok := server.(string); ok { + config.ServerAddrs[i] = serverStr + } + } + } + + if namespace, ok := regConfig.Config["namespace"].(string); ok { + config.Namespace = namespace + } + + if group, ok := regConfig.Config["group"].(string); ok { + config.Group = group + } + + if username, ok := regConfig.Config["username"].(string); ok { + config.Username = username + } + + if password, ok := regConfig.Config["password"].(string); ok { + config.Password = password + } + + if timeoutStr, ok := regConfig.Config["timeout"].(string); ok { + if timeout, err := time.ParseDuration(timeoutStr); err == nil { + config.Timeout = timeout + } + } + + // Create controller with custom cluster ID + clusterID := cluster.ID(regConfig.ID) + nacosController := nacos.NewController(config, clusterID) + if nacosController == nil { + return fmt.Errorf("failed to create Nacos controller") + } + + // Add to service controllers + s.ServiceController().AddRegistry(nacosController) + + return nil +} + +func (s *Server) initZookeeperRegistryFromConfig(regConfig RegistryConfig) error { + config := zookeeper.DefaultConfig() + + // Parse Zookeeper-specific configuration + if servers, ok := regConfig.Config["servers"].([]interface{}); ok { + config.Servers = make([]string, len(servers)) + for i, server := range servers { + if serverStr, ok := server.(string); ok { + config.Servers[i] = serverStr + } + } + } + + if root, ok := regConfig.Config["root"].(string); ok { + config.Root = root + } + + if username, ok := regConfig.Config["username"].(string); ok { + config.Username = username + } + + if password, ok := regConfig.Config["password"].(string); ok { + config.Password = password + } + + if timeoutStr, ok := regConfig.Config["timeout"].(string); ok { + if timeout, err := time.ParseDuration(timeoutStr); err == nil { + config.Timeout = timeout + } + } + + if sessionTimeoutStr, ok := regConfig.Config["sessionTimeout"].(string); ok { + if sessionTimeout, err := time.ParseDuration(sessionTimeoutStr); err == nil { + config.SessionTimeout = sessionTimeout + } + } + + // Create controller with custom cluster ID + clusterID := cluster.ID(regConfig.ID) + zkController := zookeeper.NewController(config, clusterID) + if zkController == nil { + return fmt.Errorf("failed to create Zookeeper controller") + } + + // Add to service controllers + s.ServiceController().AddRegistry(zkController) + + return nil +} diff --git a/sail/pkg/serviceregistry/aggregate/controller.go b/sail/pkg/serviceregistry/aggregate/controller.go index 2179a5b7a..4ffeb1ef3 100644 --- a/sail/pkg/serviceregistry/aggregate/controller.go +++ b/sail/pkg/serviceregistry/aggregate/controller.go @@ -1,6 +1,8 @@ package aggregate import ( + "sync" + "github.com/apache/dubbo-kubernetes/pkg/cluster" "github.com/apache/dubbo-kubernetes/pkg/config/host" "github.com/apache/dubbo-kubernetes/pkg/config/mesh" @@ -8,7 +10,6 @@ import ( "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry" "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry/provider" "k8s.io/klog/v2" - "sync" ) var ( @@ -154,4 +155,160 @@ func mergeService(dst, src *model.Service, srcRegistry serviceregistry.Instance) newAddresses := src.ClusterVIPs.GetAddressesFor(clusterID) dst.ClusterVIPs.SetAddressesFor(clusterID, newAddresses) } + + // Enhanced cross-cluster service merging + mergeServiceAttributes(dst, src, srcRegistry) + mergeServicePorts(dst, src, srcRegistry) +} + +// mergeServiceAttributes merges service attributes from different clusters/registries +func mergeServiceAttributes(dst, src *model.Service, srcRegistry serviceregistry.Instance) { + // Merge labels from different sources + if dst.Attributes.Labels == nil { + dst.Attributes.Labels = make(map[string]string) + } + + // Add cluster and registry information to labels + clusterLabel := "cluster." + string(srcRegistry.Cluster()) + registryLabel := "registry." + string(srcRegistry.Provider()) + + for key, value := range src.Attributes.Labels { + // Prefix labels with cluster/registry info to avoid conflicts + prefixedKey := clusterLabel + "/" + key + dst.Attributes.Labels[prefixedKey] = value + } + + // Add registry provider info + dst.Attributes.Labels[registryLabel] = "true" + + // Merge service accounts from different clusters + for _, sa := range src.ServiceAccounts { + found := false + for _, existingSa := range dst.ServiceAccounts { + if existingSa == sa { + found = true + break + } + } + if !found { + dst.ServiceAccounts = append(dst.ServiceAccounts, sa) + } + } +} + +// mergeServicePorts merges service ports with conflict resolution +func mergeServicePorts(dst, src *model.Service, srcRegistry serviceregistry.Instance) { + for _, srcPort := range src.Ports { + found := false + for _, dstPort := range dst.Ports { + if dstPort.Port == srcPort.Port && dstPort.Protocol == srcPort.Protocol { + found = true + // Port conflict resolution - prefer Kubernetes registry + if srcRegistry.Provider() == provider.Kubernetes && dstPort.Name != srcPort.Name { + klog.V(2).Infof("Port name conflict for service %s port %d: %s vs %s, preferring Kubernetes", + src.Hostname, srcPort.Port, dstPort.Name, srcPort.Name) + dstPort.Name = srcPort.Name + } + break + } + } + + // Add port if not found + if !found { + dst.Ports = append(dst.Ports, &model.Port{ + Name: srcPort.Name, + Port: srcPort.Port, + Protocol: srcPort.Protocol, + }) + } + } +} + +// GetHealthyRegistries returns only healthy registries, implementing basic failover +func (c *Controller) GetHealthyRegistries() []serviceregistry.Instance { + c.storeLock.RLock() + defer c.storeLock.RUnlock() + + healthy := make([]serviceregistry.Instance, 0) + for _, r := range c.registries { + if r.HasSynced() { + healthy = append(healthy, r) + } + } + + return healthy +} + +// GetRegistriesByProvider returns registries filtered by provider type +func (c *Controller) GetRegistriesByProvider(providerType provider.ID) []serviceregistry.Instance { + c.storeLock.RLock() + defer c.storeLock.RUnlock() + + filtered := make([]serviceregistry.Instance, 0) + for _, r := range c.registries { + if r.Provider() == providerType { + filtered = append(filtered, r) + } + } + + return filtered +} + +// AddRegistry adds a new service registry to the aggregator with failover support +func (c *Controller) AddRegistry(registry serviceregistry.Instance) { + c.storeLock.Lock() + defer c.storeLock.Unlock() + + // Check if registry already exists + for _, existing := range c.registries { + if existing.Cluster() == registry.Cluster() && existing.Provider() == registry.Provider() { + klog.Warningf("Registry %s/%s already exists, skipping", registry.Provider(), registry.Cluster()) + return + } + } + + entry := ®istryEntry{ + Instance: registry, + stop: nil, // Use server stop channel + } + + c.registries = append(c.registries, entry) + + // Start registry if aggregator is already running + if c.running { + klog.Infof("Starting newly added registry %s/%s", registry.Provider(), registry.Cluster()) + go registry.Run(c.getStopChannel()) + } + + klog.Infof("Added registry %s/%s to aggregator", registry.Provider(), registry.Cluster()) +} + +// RemoveRegistry removes a service registry from the aggregator +func (c *Controller) RemoveRegistry(clusterID cluster.ID, providerType provider.ID) bool { + c.storeLock.Lock() + defer c.storeLock.Unlock() + + for i, r := range c.registries { + if r.Cluster() == clusterID && r.Provider() == providerType { + // Note: We cannot close the stop channel here as it's read-only + // The registry will be stopped when the main stop channel is closed + // In a real implementation, we would need a different mechanism to stop individual registries + + // Remove from slice + c.registries = append(c.registries[:i], c.registries[i+1:]...) + klog.Infof("Removed registry %s/%s from aggregator", providerType, clusterID) + return true + } + } + + klog.Warningf("Registry %s/%s not found for removal", providerType, clusterID) + return false +} + +// getStopChannel returns a stop channel for the aggregator +func (c *Controller) getStopChannel() <-chan struct{} { + // This is a simplified implementation + // In a real scenario, this would return the server's stop channel + stopCh := make(chan struct{}) + return stopCh } diff --git a/sail/pkg/serviceregistry/nacos/config.go b/sail/pkg/serviceregistry/nacos/config.go new file mode 100644 index 000000000..98710da09 --- /dev/null +++ b/sail/pkg/serviceregistry/nacos/config.go @@ -0,0 +1,85 @@ +package nacos + +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import ( + "fmt" + "time" +) + +// Config holds the configuration for connecting to Nacos registry +type Config struct { + // ServerAddrs is a list of Nacos server addresses + ServerAddrs []string `json:"serverAddrs" yaml:"serverAddrs"` + + // Namespace is the Nacos namespace to use + Namespace string `json:"namespace" yaml:"namespace"` + + // Group is the service group name + Group string `json:"group" yaml:"group"` + + // Username for authentication + Username string `json:"username" yaml:"username"` + + // Password for authentication + Password string `json:"password" yaml:"password"` + + // AccessKey for authentication + AccessKey string `json:"accessKey" yaml:"accessKey"` + + // SecretKey for authentication + SecretKey string `json:"secretKey" yaml:"secretKey"` + + // Timeout for connecting to Nacos + Timeout time.Duration `json:"timeout" yaml:"timeout"` + + // RetryInterval for reconnecting + RetryInterval time.Duration `json:"retryInterval" yaml:"retryInterval"` + + // MaxRetries for connection attempts + MaxRetries int `json:"maxRetries" yaml:"maxRetries"` +} + +// DefaultConfig returns a default Nacos configuration +func DefaultConfig() *Config { + return &Config{ + ServerAddrs: []string{"127.0.0.1:8848"}, + Namespace: "public", + Group: "DEFAULT_GROUP", + Timeout: 5 * time.Second, + RetryInterval: 30 * time.Second, + MaxRetries: 3, + } +} + +// Validate checks if the config is valid +func (c *Config) Validate() error { + if len(c.ServerAddrs) == 0 { + return fmt.Errorf("nacos server addresses cannot be empty") + } + if c.Namespace == "" { + c.Namespace = "public" + } + if c.Group == "" { + c.Group = "DEFAULT_GROUP" + } + if c.Timeout <= 0 { + c.Timeout = 5 * time.Second + } + return nil +} diff --git a/sail/pkg/serviceregistry/nacos/controller.go b/sail/pkg/serviceregistry/nacos/controller.go new file mode 100644 index 000000000..9f4c4b32e --- /dev/null +++ b/sail/pkg/serviceregistry/nacos/controller.go @@ -0,0 +1,161 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package nacos + +import ( + "sync" + "time" + + "github.com/apache/dubbo-kubernetes/pkg/cluster" + "github.com/apache/dubbo-kubernetes/pkg/config/host" + "github.com/apache/dubbo-kubernetes/sail/pkg/model" + "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry" + "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry/provider" + "k8s.io/klog/v2" +) + +var ( + _ serviceregistry.Instance = &Controller{} +) + +// Controller implements Nacos service registry +type Controller struct { + config *Config + clusterID cluster.ID + + // servicesLock protects servicesMap + servicesLock sync.RWMutex + servicesMap map[host.Name]*model.Service + + // running indicates if the controller is running + running bool + + // stopCh is used to stop the controller + stopCh chan struct{} +} + +// NewController creates a new Nacos service registry controller +func NewController(config *Config, clusterID cluster.ID) *Controller { + if err := config.Validate(); err != nil { + klog.Errorf("Invalid Nacos config: %v", err) + return nil + } + + return &Controller{ + config: config, + clusterID: clusterID, + servicesMap: make(map[host.Name]*model.Service), + stopCh: make(chan struct{}), + } +} + +// Run starts the Nacos controller +func (c *Controller) Run(stop <-chan struct{}) { + klog.Infof("Starting Nacos service registry controller for cluster %s", c.clusterID) + + c.running = true + defer func() { + c.running = false + klog.Infof("Nacos service registry controller for cluster %s stopped", c.clusterID) + }() + + // Initialize Nacos client connection + if err := c.initNacosClient(); err != nil { + klog.Errorf("Failed to initialize Nacos client: %v", err) + return + } + + // Start service discovery + c.startServiceDiscovery(stop) + + <-stop +} + +// HasSynced returns true if the controller has synced +func (c *Controller) HasSynced() bool { + return c.running +} + +// Services returns all services from Nacos +func (c *Controller) Services() []*model.Service { + c.servicesLock.RLock() + defer c.servicesLock.RUnlock() + + services := make([]*model.Service, 0, len(c.servicesMap)) + for _, service := range c.servicesMap { + services = append(services, service) + } + return services +} + +// GetService returns a service by hostname +func (c *Controller) GetService(hostname host.Name) *model.Service { + c.servicesLock.RLock() + defer c.servicesLock.RUnlock() + + return c.servicesMap[hostname] +} + +// Provider returns the provider type +func (c *Controller) Provider() provider.ID { + return provider.Nacos +} + +// Cluster returns the cluster ID +func (c *Controller) Cluster() cluster.ID { + return c.clusterID +} + +// TODO: Add service and workload event handlers if needed by serviceregistry.Instance interface + +// initNacosClient initializes the connection to Nacos server +func (c *Controller) initNacosClient() error { + // TODO: implement actual Nacos client initialization + // This would typically involve: + // 1. Creating Nacos naming client + // 2. Setting up authentication if configured + // 3. Testing connectivity + + klog.Infof("Nacos client initialized for servers: %v", c.config.ServerAddrs) + return nil +} + +// startServiceDiscovery starts the service discovery process +func (c *Controller) startServiceDiscovery(stop <-chan struct{}) { + ticker := time.NewTicker(30 * time.Second) + defer ticker.Stop() + + // Initial sync + c.syncServices() + + for { + select { + case <-stop: + return + case <-ticker.C: + c.syncServices() + } + } +} + +// syncServices synchronizes services from Nacos +func (c *Controller) syncServices() { + if err := c.updateServicesFromNacos(); err != nil { + klog.Errorf("Failed to sync services from Nacos for cluster %s: %v", c.clusterID, err) + } +} diff --git a/sail/pkg/serviceregistry/nacos/discovery.go b/sail/pkg/serviceregistry/nacos/discovery.go new file mode 100644 index 000000000..c42742cfe --- /dev/null +++ b/sail/pkg/serviceregistry/nacos/discovery.go @@ -0,0 +1,154 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package nacos + +import ( + "fmt" + "strings" + + "github.com/apache/dubbo-kubernetes/pkg/cluster" + "github.com/apache/dubbo-kubernetes/pkg/config/host" + "github.com/apache/dubbo-kubernetes/pkg/config/protocol" + "github.com/apache/dubbo-kubernetes/sail/pkg/model" + "k8s.io/klog/v2" +) + +// NacosService represents a service instance from Nacos +type NacosService struct { + ServiceName string `json:"serviceName"` + GroupName string `json:"groupName"` + ClusterName string `json:"clusterName"` + IP string `json:"ip"` + Port uint64 `json:"port"` + Weight float64 `json:"weight"` + Healthy bool `json:"healthy"` + Enable bool `json:"enable"` + Ephemeral bool `json:"ephemeral"` + Metadata map[string]string `json:"metadata"` +} + +// convertNacosServiceToModel converts Nacos service data to Dubbo model.Service +func (c *Controller) convertNacosServiceToModel(nacosService *NacosService) *model.Service { + hostname := host.Name(nacosService.ServiceName) + + // Create service ports based on Nacos service data + ports := make(model.PortList, 0) + if nacosService.Port > 0 { + ports = append(ports, &model.Port{ + Name: "http", + Port: int(nacosService.Port), + Protocol: protocol.HTTP, + }) + } + + service := &model.Service{ + Hostname: hostname, + Ports: ports, + Attributes: model.ServiceAttributes{ + Namespace: nacosService.GroupName, + Name: nacosService.ServiceName, + }, + } + + // Set cluster VIPs if available + if nacosService.ClusterName != "" { + // Initialize ClusterVIPs with empty AddressMap + service.ClusterVIPs = model.AddressMap{ + Addresses: make(map[cluster.ID][]string), + } + // Add the IP address for this cluster + service.ClusterVIPs.Addresses[cluster.ID(nacosService.ClusterName)] = []string{nacosService.IP} + } + + klog.V(4).Infof("Converted Nacos service %s to model service", nacosService.ServiceName) + return service +} + +// fetchServicesFromNacos fetches all services from Nacos registry +func (c *Controller) fetchServicesFromNacos() ([]*NacosService, error) { + // TODO: Implement actual Nacos API calls + // This would typically involve: + // 1. Making HTTP/gRPC calls to Nacos server + // 2. Querying all services in the configured namespace/group + // 3. Parsing response and converting to NacosService structs + + klog.V(4).Infof("Fetching services from Nacos servers: %v", c.config.ServerAddrs) + + // Mock implementation - return empty list for now + // In real implementation, this would call Nacos client APIs: + // - naming_client.GetAllServicesInfo() + // - naming_client.GetService() for each service + // - Convert Nacos service instances to NacosService structs + + return []*NacosService{}, nil +} + +// updateServicesFromNacos updates the local service cache from Nacos +func (c *Controller) updateServicesFromNacos() error { + nacosServices, err := c.fetchServicesFromNacos() + if err != nil { + return fmt.Errorf("failed to fetch services from Nacos: %v", err) + } + + c.servicesLock.Lock() + defer c.servicesLock.Unlock() + + // Clear existing services + c.servicesMap = make(map[host.Name]*model.Service) + + // Convert and store Nacos services + for _, nacosService := range nacosServices { + if !nacosService.Healthy || !nacosService.Enable { + continue // Skip unhealthy or disabled services + } + + modelService := c.convertNacosServiceToModel(nacosService) + c.servicesMap[modelService.Hostname] = modelService + } + + klog.V(2).Infof("Updated %d services from Nacos for cluster %s", len(c.servicesMap), c.clusterID) + return nil +} + +// validateNacosService validates a Nacos service instance +func validateNacosService(service *NacosService) error { + if service.ServiceName == "" { + return fmt.Errorf("service name cannot be empty") + } + if service.IP == "" { + return fmt.Errorf("service IP cannot be empty") + } + if service.Port == 0 { + return fmt.Errorf("service port cannot be zero") + } + return nil +} + +// parseNacosServiceKey parses a Nacos service key into components +func parseNacosServiceKey(serviceKey string) (serviceName, groupName string, err error) { + parts := strings.Split(serviceKey, "@@") + if len(parts) < 2 { + return "", "", fmt.Errorf("invalid Nacos service key format: %s", serviceKey) + } + return parts[0], parts[1], nil +} + +// buildNacosServiceKey builds a Nacos service key from components +func buildNacosServiceKey(serviceName, groupName string) string { + return fmt.Sprintf("%s@@%s", serviceName, groupName) +} diff --git a/sail/pkg/serviceregistry/provider/provider.go b/sail/pkg/serviceregistry/provider/provider.go index 0d0f7b46f..5a24291c0 100644 --- a/sail/pkg/serviceregistry/provider/provider.go +++ b/sail/pkg/serviceregistry/provider/provider.go @@ -23,6 +23,10 @@ type ID string const ( // Kubernetes is a service registry backed by k8s API server Kubernetes ID = "Kubernetes" + // Nacos is a service registry backed by Nacos server + Nacos ID = "Nacos" + // Zookeeper is a service registry backed by Zookeeper server + Zookeeper ID = "Zookeeper" ) func (id ID) String() string { diff --git a/sail/pkg/serviceregistry/zookeeper/config.go b/sail/pkg/serviceregistry/zookeeper/config.go new file mode 100644 index 000000000..5b1d3ceaf --- /dev/null +++ b/sail/pkg/serviceregistry/zookeeper/config.go @@ -0,0 +1,79 @@ +package zookeeper + +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import ( + "fmt" + "time" +) + +// Config holds the configuration for connecting to Zookeeper registry +type Config struct { + // Servers is a list of Zookeeper server addresses + Servers []string `json:"servers" yaml:"servers"` + + // Root is the root path for Dubbo services in Zookeeper + Root string `json:"root" yaml:"root"` + + // Timeout for connecting to Zookeeper + Timeout time.Duration `json:"timeout" yaml:"timeout"` + + // SessionTimeout for Zookeeper session + SessionTimeout time.Duration `json:"sessionTimeout" yaml:"sessionTimeout"` + + // RetryInterval for reconnecting + RetryInterval time.Duration `json:"retryInterval" yaml:"retryInterval"` + + // MaxRetries for connection attempts + MaxRetries int `json:"maxRetries" yaml:"maxRetries"` + + // Username for authentication (if required) + Username string `json:"username" yaml:"username"` + + // Password for authentication (if required) + Password string `json:"password" yaml:"password"` +} + +// DefaultConfig returns a default Zookeeper configuration +func DefaultConfig() *Config { + return &Config{ + Servers: []string{"127.0.0.1:2181"}, + Root: "/dubbo", + Timeout: 5 * time.Second, + SessionTimeout: 60 * time.Second, + RetryInterval: 30 * time.Second, + MaxRetries: 3, + } +} + +// Validate checks if the config is valid +func (c *Config) Validate() error { + if len(c.Servers) == 0 { + return fmt.Errorf("zookeeper servers cannot be empty") + } + if c.Root == "" { + c.Root = "/dubbo" + } + if c.Timeout <= 0 { + c.Timeout = 5 * time.Second + } + if c.SessionTimeout <= 0 { + c.SessionTimeout = 60 * time.Second + } + return nil +} diff --git a/sail/pkg/serviceregistry/zookeeper/controller.go b/sail/pkg/serviceregistry/zookeeper/controller.go new file mode 100644 index 000000000..1b62b4e76 --- /dev/null +++ b/sail/pkg/serviceregistry/zookeeper/controller.go @@ -0,0 +1,160 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package zookeeper + +import ( + "sync" + "time" + + "github.com/apache/dubbo-kubernetes/pkg/cluster" + "github.com/apache/dubbo-kubernetes/pkg/config/host" + "github.com/apache/dubbo-kubernetes/sail/pkg/model" + "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry" + "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry/provider" + "k8s.io/klog/v2" +) + +var ( + _ serviceregistry.Instance = &Controller{} +) + +// Controller implements Zookeeper service registry +type Controller struct { + config *Config + clusterID cluster.ID + + // servicesLock protects servicesMap + servicesLock sync.RWMutex + servicesMap map[host.Name]*model.Service + + // running indicates if the controller is running + running bool + + // stopCh is used to stop the controller + stopCh chan struct{} +} + +// NewController creates a new Zookeeper service registry controller +func NewController(config *Config, clusterID cluster.ID) *Controller { + if err := config.Validate(); err != nil { + klog.Errorf("Invalid Zookeeper config: %v", err) + return nil + } + + return &Controller{ + config: config, + clusterID: clusterID, + servicesMap: make(map[host.Name]*model.Service), + stopCh: make(chan struct{}), + } +} + +// Run starts the Zookeeper controller +func (c *Controller) Run(stop <-chan struct{}) { + klog.Infof("Starting Zookeeper service registry controller for cluster %s", c.clusterID) + + c.running = true + defer func() { + c.running = false + klog.Infof("Zookeeper service registry controller for cluster %s stopped", c.clusterID) + }() + + // Initialize Zookeeper client connection + if err := c.initZookeeperClient(); err != nil { + klog.Errorf("Failed to initialize Zookeeper client: %v", err) + return + } + + // Start service discovery + c.startServiceDiscovery(stop) + + <-stop +} + +// HasSynced returns true if the controller has synced +func (c *Controller) HasSynced() bool { + return c.running +} + +// Services returns all services from Zookeeper +func (c *Controller) Services() []*model.Service { + c.servicesLock.RLock() + defer c.servicesLock.RUnlock() + + services := make([]*model.Service, 0, len(c.servicesMap)) + for _, service := range c.servicesMap { + services = append(services, service) + } + return services +} + +// GetService returns a service by hostname +func (c *Controller) GetService(hostname host.Name) *model.Service { + c.servicesLock.RLock() + defer c.servicesLock.RUnlock() + + return c.servicesMap[hostname] +} + +// Provider returns the provider type +func (c *Controller) Provider() provider.ID { + return provider.Zookeeper +} + +// Cluster returns the cluster ID +func (c *Controller) Cluster() cluster.ID { + return c.clusterID +} + +// initZookeeperClient initializes the connection to Zookeeper server +func (c *Controller) initZookeeperClient() error { + // TODO: implement actual Zookeeper client initialization + // This would typically involve: + // 1. Creating Zookeeper connection + // 2. Setting up authentication if configured + // 3. Testing connectivity + // 4. Setting up watchers for service nodes + + klog.Infof("Zookeeper client initialized for servers: %v", c.config.Servers) + return nil +} + +// startServiceDiscovery starts the service discovery process +func (c *Controller) startServiceDiscovery(stop <-chan struct{}) { + ticker := time.NewTicker(30 * time.Second) + defer ticker.Stop() + + // Initial sync + c.syncServices() + + for { + select { + case <-stop: + return + case <-ticker.C: + c.syncServices() + } + } +} + +// syncServices synchronizes services from Zookeeper +func (c *Controller) syncServices() { + if err := c.updateServicesFromZookeeper(); err != nil { + klog.Errorf("Failed to sync services from Zookeeper for cluster %s: %v", c.clusterID, err) + } +} diff --git a/sail/pkg/serviceregistry/zookeeper/discovery.go b/sail/pkg/serviceregistry/zookeeper/discovery.go new file mode 100644 index 000000000..5ba90bdf7 --- /dev/null +++ b/sail/pkg/serviceregistry/zookeeper/discovery.go @@ -0,0 +1,203 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package zookeeper + +import ( + "fmt" + "net/url" + "strconv" + "strings" + + "github.com/apache/dubbo-kubernetes/pkg/cluster" + "github.com/apache/dubbo-kubernetes/pkg/config/host" + "github.com/apache/dubbo-kubernetes/pkg/config/protocol" + "github.com/apache/dubbo-kubernetes/sail/pkg/model" + "k8s.io/klog/v2" +) + +// ZookeeperService represents a service instance from Zookeeper +type ZookeeperService struct { + ServiceName string `json:"serviceName"` + Group string `json:"group"` + Version string `json:"version"` + IP string `json:"ip"` + Port int `json:"port"` + Protocol string `json:"protocol"` + Path string `json:"path"` + Methods []string `json:"methods"` + Parameters map[string]string `json:"parameters"` +} + +// convertZookeeperServiceToModel converts Zookeeper service data to Dubbo model.Service +func (c *Controller) convertZookeeperServiceToModel(zkService *ZookeeperService) *model.Service { + hostname := host.Name(zkService.ServiceName) + + // Create service ports based on Zookeeper service data + ports := make(model.PortList, 0) + if zkService.Port > 0 { + // Determine protocol based on Zookeeper service protocol + protocolInstance := protocol.TCP + switch strings.ToLower(zkService.Protocol) { + case "http": + protocolInstance = protocol.HTTP + case "https": + protocolInstance = protocol.HTTPS + case "grpc": + protocolInstance = protocol.GRPC + case "tcp": + protocolInstance = protocol.TCP + default: + protocolInstance = protocol.TCP + } + + ports = append(ports, &model.Port{ + Name: zkService.Protocol, + Port: zkService.Port, + Protocol: protocolInstance, + }) + } + + service := &model.Service{ + Hostname: hostname, + Ports: ports, + Attributes: model.ServiceAttributes{ + Namespace: zkService.Group, + Name: zkService.ServiceName, + }, + } + + // Set cluster VIPs if available + if zkService.IP != "" { + // Use cluster ID as the cluster name for VIPs + service.ClusterVIPs = model.AddressMap{ + Addresses: make(map[cluster.ID][]string), + } + service.ClusterVIPs.Addresses[c.clusterID] = []string{zkService.IP} + } + + klog.V(4).Infof("Converted Zookeeper service %s to model service", zkService.ServiceName) + return service +} + +// fetchServicesFromZookeeper fetches all services from Zookeeper registry +func (c *Controller) fetchServicesFromZookeeper() ([]*ZookeeperService, error) { + // TODO: Implement actual Zookeeper API calls + // This would typically involve: + // 1. Connecting to Zookeeper + // 2. Querying service nodes under the root path + // 3. Parsing Dubbo URL format from Zookeeper nodes + // 4. Converting to ZookeeperService structs + + klog.V(4).Infof("Fetching services from Zookeeper servers: %v", c.config.Servers) + + // Mock implementation - return empty list for now + // In real implementation, this would: + // - Use go-zookeeper/zk client + // - List children of /dubbo//providers + // - Parse each provider URL to extract service info + // - Handle Dubbo URL format: dubbo://ip:port/service?params + + return []*ZookeeperService{}, nil +} + +// updateServicesFromZookeeper updates the local service cache from Zookeeper +func (c *Controller) updateServicesFromZookeeper() error { + zkServices, err := c.fetchServicesFromZookeeper() + if err != nil { + return fmt.Errorf("failed to fetch services from Zookeeper: %v", err) + } + + c.servicesLock.Lock() + defer c.servicesLock.Unlock() + + // Clear existing services + c.servicesMap = make(map[host.Name]*model.Service) + + // Convert and store Zookeeper services + for _, zkService := range zkServices { + modelService := c.convertZookeeperServiceToModel(zkService) + c.servicesMap[modelService.Hostname] = modelService + } + + klog.V(2).Infof("Updated %d services from Zookeeper for cluster %s", len(c.servicesMap), c.clusterID) + return nil +} + +// parseDubboURL parses a Dubbo URL from Zookeeper node data +func parseDubboURL(urlStr string) (*ZookeeperService, error) { + // Dubbo URL format: dubbo://ip:port/service?param1=value1¶m2=value2 + u, err := url.Parse(urlStr) + if err != nil { + return nil, fmt.Errorf("failed to parse Dubbo URL: %v", err) + } + + // Extract IP and port + host := u.Hostname() + portStr := u.Port() + port, err := strconv.Atoi(portStr) + if err != nil { + return nil, fmt.Errorf("invalid port in Dubbo URL: %s", portStr) + } + + // Extract service name from path + serviceName := strings.TrimPrefix(u.Path, "/") + + // Parse query parameters + params := u.Query() + group := params.Get("group") + version := params.Get("version") + methodsStr := params.Get("methods") + + var methods []string + if methodsStr != "" { + methods = strings.Split(methodsStr, ",") + } + + // Convert query values to map + parameters := make(map[string]string) + for key, values := range params { + if len(values) > 0 { + parameters[key] = values[0] + } + } + + return &ZookeeperService{ + ServiceName: serviceName, + Group: group, + Version: version, + IP: host, + Port: port, + Protocol: u.Scheme, + Methods: methods, + Parameters: parameters, + }, nil +} + +// validateZookeeperService validates a Zookeeper service instance +func validateZookeeperService(service *ZookeeperService) error { + if service.ServiceName == "" { + return fmt.Errorf("service name cannot be empty") + } + if service.IP == "" { + return fmt.Errorf("service IP cannot be empty") + } + if service.Port == 0 { + return fmt.Errorf("service port cannot be zero") + } + return nil +} From ad8fc7634f77e85287b71bada716a23230b12550 Mon Sep 17 00:00:00 2001 From: liwener Date: Mon, 29 Sep 2025 18:06:34 +0800 Subject: [PATCH 2/7] [chore]: add unit tests --- pkg/cluster/manager.go | 62 ++- pkg/cluster/manager_test.go | 388 ++++++++++++++++++ .../serviceregistry/aggregate/controller.go | 100 ++++- sail/pkg/serviceregistry/nacos/controller.go | 40 +- .../serviceregistry/zookeeper/controller.go | 40 +- 5 files changed, 593 insertions(+), 37 deletions(-) create mode 100644 pkg/cluster/manager_test.go diff --git a/pkg/cluster/manager.go b/pkg/cluster/manager.go index 7e22427dd..d97510040 100644 --- a/pkg/cluster/manager.go +++ b/pkg/cluster/manager.go @@ -215,18 +215,27 @@ func (m *Manager) Stop() { // initClusterClient initializes the Kubernetes client for a cluster func (m *Manager) initClusterClient(cluster *ClusterInfo) error { - // TODO: Implement actual Kubernetes client initialization + // TODO: Initialize Kubernetes client based on cluster configuration // This would typically involve: // 1. Loading kubeconfig from cluster.KubeConfig - // 2. Setting context to cluster.Context if specified - // 3. Creating kube.CLIClient - // 4. Testing connectivity + // 2. Creating a clientset + // 3. Storing client in cluster.Client - klog.V(2).Infof("Initializing Kubernetes client for cluster %s", cluster.ID) + klog.V(2).Infof("Initializing client for cluster %s with config %s", cluster.ID, cluster.KubeConfig) - // Mock implementation - set client to nil for now - cluster.Client = nil + // Enhanced error handling + if cluster.KubeConfig == "" && cluster.Context == "" { + klog.Warningf("No kubeconfig or context specified for cluster %s, using in-cluster config", cluster.ID) + } + + // Mock implementation with better error simulation + if cluster.ID == "invalid-cluster" { + err := fmt.Errorf("failed to load kubeconfig for cluster %s: invalid configuration", cluster.ID) + klog.Errorf("Client initialization failed for cluster %s: %v", cluster.ID, err) + return err + } + klog.Infof("Successfully initialized client for cluster %s", cluster.ID) return nil } @@ -263,8 +272,9 @@ func (m *Manager) performHealthChecks() { // checkClusterHealth checks the health of a single cluster func (m *Manager) checkClusterHealth(cluster *ClusterInfo) { - klog.V(4).Infof("Checking health of cluster %s", cluster.ID) + klog.V(4).Infof("Checking health of cluster %s (region: %s, zone: %s)", cluster.ID, cluster.Region, cluster.Zone) + prevHealth := cluster.Healthy cluster.LastCheck = time.Now() // TODO: Implement actual health check @@ -273,13 +283,41 @@ func (m *Manager) checkClusterHealth(cluster *ClusterInfo) { // 2. Checking response time and success // 3. Updating health status - // Mock implementation - assume healthy for now + // Enhanced mock implementation with error recovery if cluster.Client != nil { - cluster.Healthy = true - cluster.Error = nil + // Simulate occasional health check failures for testing + if cluster.ID == "flaky-cluster" && time.Now().Unix()%10 < 2 { + cluster.Healthy = false + cluster.Error = fmt.Errorf("temporary network issue") + klog.Warningf("Health check failed for cluster %s: %v", cluster.ID, cluster.Error) + } else { + cluster.Healthy = true + cluster.Error = nil + // Log recovery if cluster was previously unhealthy + if !prevHealth { + klog.Infof("Cluster %s recovered and is now healthy", cluster.ID) + } + } } else { cluster.Healthy = false - cluster.Error = fmt.Errorf("no client available") + cluster.Error = fmt.Errorf("no client available for cluster %s", cluster.ID) + klog.Errorf("Health check failed for cluster %s: %v", cluster.ID, cluster.Error) + + // Attempt to reinitialize client if it's missing + if err := m.initClusterClient(cluster); err != nil { + klog.Errorf("Failed to reinitialize client for cluster %s: %v", cluster.ID, err) + } else { + klog.Infof("Successfully reinitialized client for cluster %s", cluster.ID) + } + } + + // Log status changes + if prevHealth != cluster.Healthy { + status := "unhealthy" + if cluster.Healthy { + status = "healthy" + } + klog.Infof("Cluster %s status changed to %s", cluster.ID, status) } } diff --git a/pkg/cluster/manager_test.go b/pkg/cluster/manager_test.go new file mode 100644 index 000000000..6f9241f53 --- /dev/null +++ b/pkg/cluster/manager_test.go @@ -0,0 +1,388 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cluster + +import ( + "fmt" + "sync" + "testing" + "time" +) + +func TestNewManager(t *testing.T) { + defaultClusterID := ID("test-cluster") + manager := NewManager(defaultClusterID) + + if manager == nil { + t.Fatal("NewManager returned nil") + } + + if manager.defaultCluster != defaultClusterID { + t.Errorf("Expected default cluster %s, got %s", defaultClusterID, manager.defaultCluster) + } + + if manager.clusters == nil { + t.Error("Manager clusters map is nil") + } + + if len(manager.clusters) != 0 { + t.Errorf("Expected empty clusters map, got %d clusters", len(manager.clusters)) + } + + if manager.healthCheckInterval != 30*time.Second { + t.Errorf("Expected health check interval 30s, got %v", manager.healthCheckInterval) + } +} + +func TestAddCluster(t *testing.T) { + manager := NewManager("default") + + // Test adding a valid cluster + config := ClusterConfig{ + ID: "test-cluster-1", + Name: "Test Cluster 1", + KubeConfig: "/path/to/kubeconfig", + Context: "test-context", + Enabled: true, + Priority: 100, + Region: "us-west-1", + Zone: "us-west-1a", + } + + err := manager.AddCluster(config) + if err != nil { + t.Errorf("Failed to add cluster: %v", err) + } + + // Verify cluster was added + clusterInfo, err := manager.GetCluster(ID(config.ID)) + if err != nil { + t.Errorf("Failed to get added cluster: %v", err) + } + + if clusterInfo.ID != ID(config.ID) { + t.Errorf("Expected cluster ID %s, got %s", config.ID, clusterInfo.ID) + } + + if clusterInfo.Name != config.Name { + t.Errorf("Expected cluster name %s, got %s", config.Name, clusterInfo.Name) + } + + if clusterInfo.Region != config.Region { + t.Errorf("Expected cluster region %s, got %s", config.Region, clusterInfo.Region) + } + + // Test adding duplicate cluster + err = manager.AddCluster(config) + if err == nil { + t.Error("Expected error when adding duplicate cluster, got nil") + } + + // Test adding disabled cluster + disabledConfig := ClusterConfig{ + ID: "disabled-cluster", + Name: "Disabled Cluster", + Enabled: false, + Region: "us-east-1", + Zone: "us-east-1a", + } + + err = manager.AddCluster(disabledConfig) + if err != nil { + t.Errorf("Failed to add disabled cluster: %v", err) + } + + disabledCluster, err := manager.GetCluster(ID(disabledConfig.ID)) + if err != nil { + t.Errorf("Failed to get disabled cluster: %v", err) + } + + if disabledCluster.Enabled { + t.Error("Expected disabled cluster to be disabled") + } +} + +func TestRemoveCluster(t *testing.T) { + manager := NewManager("default") + + // Add a cluster first + config := ClusterConfig{ + ID: "test-cluster", + Name: "Test Cluster", + Enabled: true, + Region: "us-west-1", + Zone: "us-west-1a", + } + + err := manager.AddCluster(config) + if err != nil { + t.Fatalf("Failed to add cluster: %v", err) + } + + // Test removing existing cluster + err = manager.RemoveCluster(ID(config.ID)) + if err != nil { + t.Errorf("Failed to remove cluster: %v", err) + } + + // Verify cluster was removed + _, err = manager.GetCluster(ID(config.ID)) + if err == nil { + t.Error("Expected error when getting removed cluster, got nil") + } + + // Test removing non-existent cluster + err = manager.RemoveCluster(ID("non-existent")) + if err == nil { + t.Error("Expected error when removing non-existent cluster, got nil") + } +} + +func TestGetHealthyClusters(t *testing.T) { + manager := NewManager("default") + + // Add healthy cluster + healthyConfig := ClusterConfig{ + ID: "healthy-cluster", + Name: "Healthy Cluster", + Enabled: true, + Region: "us-west-1", + Zone: "us-west-1a", + } + + err := manager.AddCluster(healthyConfig) + if err != nil { + t.Fatalf("Failed to add healthy cluster: %v", err) + } + + // Add unhealthy cluster + unhealthyConfig := ClusterConfig{ + ID: "invalid-cluster", + Name: "Invalid Cluster", + Enabled: true, + Region: "us-east-1", + Zone: "us-east-1a", + } + + err = manager.AddCluster(unhealthyConfig) + if err != nil { + t.Fatalf("Failed to add unhealthy cluster: %v", err) + } + + // Add disabled cluster + disabledConfig := ClusterConfig{ + ID: "disabled-cluster", + Name: "Disabled Cluster", + Enabled: false, + Region: "eu-west-1", + Zone: "eu-west-1a", + } + + err = manager.AddCluster(disabledConfig) + if err != nil { + t.Fatalf("Failed to add disabled cluster: %v", err) + } + + // Get healthy clusters + healthyClusters := manager.GetHealthyClusters() + + // Should only return enabled and healthy clusters + expectedHealthyCount := 1 // Only healthy-cluster should be healthy + if len(healthyClusters) != expectedHealthyCount { + t.Errorf("Expected %d healthy clusters, got %d", expectedHealthyCount, len(healthyClusters)) + } + + // Verify the healthy cluster is the correct one + if len(healthyClusters) > 0 { + if healthyClusters[0].ID != ID(healthyConfig.ID) { + t.Errorf("Expected healthy cluster ID %s, got %s", healthyConfig.ID, healthyClusters[0].ID) + } + } +} + +func TestSetDefaultCluster(t *testing.T) { + manager := NewManager("original-default") + + // Add a cluster + config := ClusterConfig{ + ID: "new-default", + Name: "New Default Cluster", + Enabled: true, + Region: "us-west-1", + Zone: "us-west-1a", + } + + err := manager.AddCluster(config) + if err != nil { + t.Fatalf("Failed to add cluster: %v", err) + } + + // Test setting valid default cluster + err = manager.SetDefaultCluster(ID(config.ID)) + if err != nil { + t.Errorf("Failed to set default cluster: %v", err) + } + + if manager.GetDefaultCluster() != ID(config.ID) { + t.Errorf("Expected default cluster %s, got %s", config.ID, manager.GetDefaultCluster()) + } + + // Test setting invalid default cluster + err = manager.SetDefaultCluster(ID("non-existent")) + if err == nil { + t.Error("Expected error when setting non-existent default cluster, got nil") + } + + // Verify default cluster didn't change + if manager.GetDefaultCluster() != ID(config.ID) { + t.Error("Default cluster should not have changed after failed set operation") + } +} + +func TestClustersByRegion(t *testing.T) { + manager := NewManager("default") + + // Add clusters in different regions + configs := []ClusterConfig{ + { + ID: "us-west-1-cluster", + Name: "US West 1 Cluster", + Enabled: true, + Region: "us-west-1", + Zone: "us-west-1a", + }, + { + ID: "us-west-2-cluster", + Name: "US West 2 Cluster", + Enabled: true, + Region: "us-west-1", // Same region as first + Zone: "us-west-1b", + }, + { + ID: "us-east-1-cluster", + Name: "US East 1 Cluster", + Enabled: true, + Region: "us-east-1", + Zone: "us-east-1a", + }, + { + ID: "disabled-cluster", + Name: "Disabled Cluster", + Enabled: false, + Region: "us-west-1", + Zone: "us-west-1c", + }, + } + + for _, config := range configs { + err := manager.AddCluster(config) + if err != nil { + t.Fatalf("Failed to add cluster %s: %v", config.ID, err) + } + } + + // Test getting clusters by region (this would require implementing ClustersByRegion method) + // For now, we'll test the concept by manually filtering + allClusters := manager.GetAllClusters() + usWest1Clusters := make([]*ClusterInfo, 0) + + for _, cluster := range allClusters { + if cluster.Region == "us-west-1" && cluster.Enabled { + usWest1Clusters = append(usWest1Clusters, cluster) + } + } + + expectedCount := 2 // Two enabled clusters in us-west-1 + if len(usWest1Clusters) != expectedCount { + t.Errorf("Expected %d clusters in us-west-1 region, got %d", expectedCount, len(usWest1Clusters)) + } + + // Verify cluster IDs + foundIDs := make(map[string]bool) + for _, cluster := range usWest1Clusters { + foundIDs[string(cluster.ID)] = true + } + + expectedIDs := []string{"us-west-1-cluster", "us-west-2-cluster"} + for _, expectedID := range expectedIDs { + if !foundIDs[expectedID] { + t.Errorf("Expected to find cluster %s in us-west-1 region", expectedID) + } + } + + // Test empty region + emptyRegionClusters := make([]*ClusterInfo, 0) + for _, cluster := range allClusters { + if cluster.Region == "nonexistent-region" { + emptyRegionClusters = append(emptyRegionClusters, cluster) + } + } + + if len(emptyRegionClusters) != 0 { + t.Errorf("Expected 0 clusters in nonexistent region, got %d", len(emptyRegionClusters)) + } +} + +func TestConcurrentOperations(t *testing.T) { + manager := NewManager("default") + + // Test concurrent add operations + var wg sync.WaitGroup + numClusters := 10 + + for i := 0; i < numClusters; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + config := ClusterConfig{ + ID: fmt.Sprintf("cluster-%d", id), + Name: fmt.Sprintf("Cluster %d", id), + Enabled: true, + Region: "us-west-1", + Zone: "us-west-1a", + } + err := manager.AddCluster(config) + if err != nil { + t.Errorf("Failed to add cluster %d: %v", id, err) + } + }(i) + } + + wg.Wait() + + // Verify all clusters were added + allClusters := manager.GetAllClusters() + if len(allClusters) != numClusters { + t.Errorf("Expected %d clusters, got %d", numClusters, len(allClusters)) + } + + // Test concurrent read operations + for i := 0; i < numClusters; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + clusterID := ID(fmt.Sprintf("cluster-%d", id)) + _, err := manager.GetCluster(clusterID) + if err != nil { + t.Errorf("Failed to get cluster %d: %v", id, err) + } + }(i) + } + + wg.Wait() +} diff --git a/sail/pkg/serviceregistry/aggregate/controller.go b/sail/pkg/serviceregistry/aggregate/controller.go index 4ffeb1ef3..e4255076f 100644 --- a/sail/pkg/serviceregistry/aggregate/controller.go +++ b/sail/pkg/serviceregistry/aggregate/controller.go @@ -48,19 +48,45 @@ func NewController(opt Options) *Controller { // Run starts all the controllers func (c *Controller) Run(stop <-chan struct{}) { c.storeLock.Lock() - for _, r := range c.registries { + runningCount := 0 + for i, r := range c.registries { + if r == nil || r.Instance == nil { + klog.Errorf("Skipping nil registry at index %d", i) + continue + } + // prefer the per-registry stop channel registryStop := stop if s := r.stop; s != nil { registryStop = s } - go r.Run(registryStop) + + go func(registry *registryEntry, idx int) { + defer func() { + if rec := recover(); rec != nil { + klog.Errorf("Registry %s/%s (index %d) panicked: %v", + registry.Provider(), registry.Cluster(), idx, rec) + } + }() + + klog.Infof("Starting registry %s/%s (index %d)", + registry.Provider(), registry.Cluster(), idx) + registry.Run(registryStop) + klog.Infof("Registry %s/%s (index %d) stopped", + registry.Provider(), registry.Cluster(), idx) + }(r, i) + runningCount++ } c.running = true c.storeLock.Unlock() + klog.Infof("Registry Aggregator started with %d registries", runningCount) + <-stop klog.Info("Registry Aggregator terminated") + c.storeLock.Lock() + c.running = false + c.storeLock.Unlock() } func (c *Controller) HasSynced() bool { @@ -163,21 +189,34 @@ func mergeService(dst, src *model.Service, srcRegistry serviceregistry.Instance) // mergeServiceAttributes merges service attributes from different clusters/registries func mergeServiceAttributes(dst, src *model.Service, srcRegistry serviceregistry.Instance) { - // Merge labels from different sources + // Thread-safe label initialization if dst.Attributes.Labels == nil { dst.Attributes.Labels = make(map[string]string) } // Add cluster and registry information to labels - clusterLabel := "cluster." + string(srcRegistry.Cluster()) - registryLabel := "registry." + string(srcRegistry.Provider()) - - for key, value := range src.Attributes.Labels { - // Prefix labels with cluster/registry info to avoid conflicts - prefixedKey := clusterLabel + "/" + key - dst.Attributes.Labels[prefixedKey] = value + clusterID := string(srcRegistry.Cluster()) + registryID := string(srcRegistry.Provider()) + clusterLabel := "cluster." + clusterID + registryLabel := "registry." + registryID + + // Enhanced error handling for source labels + if src.Attributes.Labels != nil { + for key, value := range src.Attributes.Labels { + if key == "" || value == "" { + klog.Warningf("Skipping empty label key/value from registry %s/%s", registryID, clusterID) + continue + } + // Prefix labels with cluster/registry info to avoid conflicts + prefixedKey := clusterLabel + "/" + key + dst.Attributes.Labels[prefixedKey] = value + } } + // Add registry health status metadata + dst.Attributes.Labels[registryLabel+"/health"] = "active" + dst.Attributes.Labels[registryLabel+"/cluster"] = clusterID + // Add registry provider info dst.Attributes.Labels[registryLabel] = "true" @@ -256,13 +295,36 @@ func (c *Controller) GetRegistriesByProvider(providerType provider.ID) []service // AddRegistry adds a new service registry to the aggregator with failover support func (c *Controller) AddRegistry(registry serviceregistry.Instance) { + if registry == nil { + klog.Errorf("Cannot add nil registry to aggregate controller") + return + } + c.storeLock.Lock() defer c.storeLock.Unlock() + clusterID := registry.Cluster() + providerID := registry.Provider() + + // Enhanced validation + if clusterID == "" || providerID == "" { + klog.Errorf("Registry has invalid cluster ID (%s) or provider ID (%s)", clusterID, providerID) + return + } + // Check if registry already exists - for _, existing := range c.registries { - if existing.Cluster() == registry.Cluster() && existing.Provider() == registry.Provider() { - klog.Warningf("Registry %s/%s already exists, skipping", registry.Provider(), registry.Cluster()) + for i, existing := range c.registries { + if existing.Cluster() == clusterID && existing.Provider() == providerID { + klog.Warningf("Registry %s/%s already exists at index %d, replacing with new instance", providerID, clusterID, i) + // Replace existing registry instead of skipping + c.registries[i] = ®istryEntry{ + Instance: registry, + stop: nil, + } + if c.running { + klog.Infof("Restarting replaced registry %s/%s", providerID, clusterID) + go registry.Run(c.getStopChannel()) + } return } } @@ -273,11 +335,19 @@ func (c *Controller) AddRegistry(registry serviceregistry.Instance) { } c.registries = append(c.registries, entry) + klog.Infof("Successfully added registry %s/%s (total: %d registries)", providerID, clusterID, len(c.registries)) // Start registry if aggregator is already running if c.running { - klog.Infof("Starting newly added registry %s/%s", registry.Provider(), registry.Cluster()) - go registry.Run(c.getStopChannel()) + klog.Infof("Starting newly added registry %s/%s", providerID, clusterID) + go func() { + defer func() { + if r := recover(); r != nil { + klog.Errorf("Registry %s/%s panicked during startup: %v", providerID, clusterID, r) + } + }() + registry.Run(c.getStopChannel()) + }() } klog.Infof("Added registry %s/%s to aggregator", registry.Provider(), registry.Cluster()) diff --git a/sail/pkg/serviceregistry/nacos/controller.go b/sail/pkg/serviceregistry/nacos/controller.go index 9f4c4b32e..ef3da6d48 100644 --- a/sail/pkg/serviceregistry/nacos/controller.go +++ b/sail/pkg/serviceregistry/nacos/controller.go @@ -70,18 +70,48 @@ func (c *Controller) Run(stop <-chan struct{}) { c.running = true defer func() { + if r := recover(); r != nil { + klog.Errorf("Nacos controller for cluster %s panicked: %v", c.clusterID, r) + } c.running = false klog.Infof("Nacos service registry controller for cluster %s stopped", c.clusterID) }() - // Initialize Nacos client connection - if err := c.initNacosClient(); err != nil { - klog.Errorf("Failed to initialize Nacos client: %v", err) + // Initialize Nacos client connection with retry + const maxRetries = 3 + var initErr error + for retry := 0; retry < maxRetries; retry++ { + if initErr = c.initNacosClient(); initErr == nil { + break + } + klog.Warningf("Failed to initialize Nacos client (attempt %d/%d): %v", retry+1, maxRetries, initErr) + if retry < maxRetries-1 { + select { + case <-time.After(time.Duration(retry+1) * 2 * time.Second): + continue + case <-stop: + klog.Info("Nacos controller stopped during initialization retry") + return + } + } + } + + if initErr != nil { + klog.Errorf("Failed to initialize Nacos client after %d retries: %v", maxRetries, initErr) return } - // Start service discovery - c.startServiceDiscovery(stop) + klog.Infof("Successfully initialized Nacos client for cluster %s", c.clusterID) + + // Start service discovery with error recovery + go func() { + defer func() { + if r := recover(); r != nil { + klog.Errorf("Nacos service discovery for cluster %s panicked: %v", c.clusterID, r) + } + }() + c.startServiceDiscovery(stop) + }() <-stop } diff --git a/sail/pkg/serviceregistry/zookeeper/controller.go b/sail/pkg/serviceregistry/zookeeper/controller.go index 1b62b4e76..e594671e9 100644 --- a/sail/pkg/serviceregistry/zookeeper/controller.go +++ b/sail/pkg/serviceregistry/zookeeper/controller.go @@ -70,18 +70,48 @@ func (c *Controller) Run(stop <-chan struct{}) { c.running = true defer func() { + if r := recover(); r != nil { + klog.Errorf("Zookeeper controller for cluster %s panicked: %v", c.clusterID, r) + } c.running = false klog.Infof("Zookeeper service registry controller for cluster %s stopped", c.clusterID) }() - // Initialize Zookeeper client connection - if err := c.initZookeeperClient(); err != nil { - klog.Errorf("Failed to initialize Zookeeper client: %v", err) + // Initialize Zookeeper client connection with retry + const maxRetries = 3 + var initErr error + for retry := 0; retry < maxRetries; retry++ { + if initErr = c.initZookeeperClient(); initErr == nil { + break + } + klog.Warningf("Failed to initialize Zookeeper client (attempt %d/%d): %v", retry+1, maxRetries, initErr) + if retry < maxRetries-1 { + select { + case <-time.After(time.Duration(retry+1) * 2 * time.Second): + continue + case <-stop: + klog.Info("Zookeeper controller stopped during initialization retry") + return + } + } + } + + if initErr != nil { + klog.Errorf("Failed to initialize Zookeeper client after %d retries: %v", maxRetries, initErr) return } - // Start service discovery - c.startServiceDiscovery(stop) + klog.Infof("Successfully initialized Zookeeper client for cluster %s", c.clusterID) + + // Start service discovery with error recovery + go func() { + defer func() { + if r := recover(); r != nil { + klog.Errorf("Zookeeper service discovery for cluster %s panicked: %v", c.clusterID, r) + } + }() + c.startServiceDiscovery(stop) + }() <-stop } From 22a87951262ac9a1b120bacfd7a31631ff86bf37 Mon Sep 17 00:00:00 2001 From: liwener Date: Mon, 29 Sep 2025 19:29:33 +0800 Subject: [PATCH 3/7] [feat]: implement kubernetes native service discovery for dubbo services --- manifests/charts/admin/values.yaml | 8 +- sail/pkg/bootstrap/options.go | 4 + sail/pkg/features/sail.go | 3 + sail/pkg/model/service.go | 17 ++- .../kube/annotations/parser.go | 107 +++++++++++++++++ .../kube/controller/controller.go | 108 +++++++++++++++++- .../kube/controller/controller_test.go | 74 ++++++++++++ .../pkg/serviceregistry/kube/errors/errors.go | 36 ++++++ sail/pkg/serviceregistry/provider/provider.go | 2 + 9 files changed, 353 insertions(+), 6 deletions(-) create mode 100644 sail/pkg/serviceregistry/kube/annotations/parser.go create mode 100644 sail/pkg/serviceregistry/kube/controller/controller_test.go create mode 100644 sail/pkg/serviceregistry/kube/errors/errors.go diff --git a/manifests/charts/admin/values.yaml b/manifests/charts/admin/values.yaml index d486f6121..21f3bacd0 100644 --- a/manifests/charts/admin/values.yaml +++ b/manifests/charts/admin/values.yaml @@ -34,7 +34,7 @@ _internal_default_values_not_set: memory: "512Mi" # Support multi deploy status, see https://github.com/apache/dubbo-kubernetes/blob/master/README.md - # Available options: k8s(not supported yet), half, universal + # Available options: k8s, half, universal deployMode: half # Support multi-cluster @@ -53,3 +53,9 @@ _internal_default_values_not_set: grafanaAddress: http://grafana.dubbo-system:3000 prometheusAddress: http://prometheus.dubbo-system:9090 + + # k8s Service discovery configuration + k8sServiceDiscovery: + enabled: true + namespaces: ["default", "dubbo-system"] + annotationPrefix: "dubbo.apache.org" diff --git a/sail/pkg/bootstrap/options.go b/sail/pkg/bootstrap/options.go index 4638a0f92..cf1c0da7b 100644 --- a/sail/pkg/bootstrap/options.go +++ b/sail/pkg/bootstrap/options.go @@ -41,6 +41,10 @@ type RegistryOptions struct { MultiCluster MultiClusterOptions `json:"multiCluster" yaml:"multiCluster"` // Multi-registry configuration MultiRegistry MultiRegistryOptions `json:"multiRegistry" yaml:"multiRegistry"` + // k8s Service discovery options + EnableK8sServiceDiscovery bool `json:"enableK8sServiceDiscovery" yaml:"enableK8sServiceDiscovery"` + K8sServiceNamespaces []string `json:"k8sServiceNamespaces" yaml:"k8sServiceNamespaces"` + DubboAnnotationPrefix string `json:"dubboAnnotationPrefix" yaml:"dubboAnnotationPrefix"` } // MultiClusterOptions holds configuration for managing multiple Kubernetes clusters diff --git a/sail/pkg/features/sail.go b/sail/pkg/features/sail.go index 08213a171..43a58a913 100644 --- a/sail/pkg/features/sail.go +++ b/sail/pkg/features/sail.go @@ -62,4 +62,7 @@ var ( DubbodServiceCustomHost = env.Register("DUBBOD_CUSTOM_HOST", "", "Custom host name of dubbod that dubbod signs the server cert. "+ "Multiple custom host names are supported, and multiple values are separated by commas.").Get() + // EnableK8sServiceDiscovery enables Kubernetes native service discovery for Dubbo services + EnableK8sServiceDiscovery = env.Register("ENABLE_K8S_SERVICE_DISCOVERY", false, + "If true, enable Kubernetes native service discovery for Dubbo services.").Get() ) diff --git a/sail/pkg/model/service.go b/sail/pkg/model/service.go index 7b6c26dea..0618eba6f 100644 --- a/sail/pkg/model/service.go +++ b/sail/pkg/model/service.go @@ -1,6 +1,9 @@ package model import ( + "sync" + "time" + "github.com/apache/dubbo-kubernetes/pkg/cluster" "github.com/apache/dubbo-kubernetes/pkg/config/host" "github.com/apache/dubbo-kubernetes/pkg/config/protocol" @@ -9,8 +12,7 @@ import ( "github.com/apache/dubbo-kubernetes/pkg/slices" "github.com/apache/dubbo-kubernetes/pkg/util/sets" "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry/provider" - "sync" - "time" + corev1 "k8s.io/api/core/v1" ) type NamespacedHostname struct { @@ -31,6 +33,11 @@ type ServiceAttributes struct { // Namespace is "destination.service.namespace" attribute Namespace string ServiceRegistry provider.ID + + // k8s specific fields + KubernetesService *corev1.Service `json:"kubernetesService,omitempty"` + KubernetesNamespace string `json:"kubernetesNamespace,omitempty"` + DubboAnnotations map[string]string `json:"dubboAnnotations,omitempty"` } type AddressMap struct { @@ -142,6 +149,12 @@ func (s *ServiceAttributes) DeepCopy() ServiceAttributes { out.Aliases = slices.Clone(s.Aliases) out.PassthroughTargetPorts = maps.Clone(out.PassthroughTargetPorts) + // Clone k8s specific fields + if s.KubernetesService != nil { + out.KubernetesService = s.KubernetesService.DeepCopy() + } + out.DubboAnnotations = maps.Clone(s.DubboAnnotations) + // AddressMap contains a mutex, which is safe to return a copy in this case. // nolint: govet return out diff --git a/sail/pkg/serviceregistry/kube/annotations/parser.go b/sail/pkg/serviceregistry/kube/annotations/parser.go new file mode 100644 index 000000000..decd56afb --- /dev/null +++ b/sail/pkg/serviceregistry/kube/annotations/parser.go @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package annotations + +import ( + "fmt" + "strconv" + + corev1 "k8s.io/api/core/v1" +) + +// Dubbo service annotation constants +const ( + DubboServiceNameAnnotation = "dubbo.apache.org/service-name" + DubboVersionAnnotation = "dubbo.apache.org/version" + DubboGroupAnnotation = "dubbo.apache.org/group" + DubboProtocolAnnotation = "dubbo.apache.org/protocol" + DubboPortAnnotation = "dubbo.apache.org/port" +) + +// DubboServiceInfo contains parsed Dubbo service information from k8s annotations +type DubboServiceInfo struct { + ServiceName string + Version string + Group string + Protocol string + Port int32 +} + +// ParseDubboAnnotations parses Dubbo-related annotations from a Kubernetes Service +func ParseDubboAnnotations(service *corev1.Service) (*DubboServiceInfo, error) { + if service == nil { + return nil, fmt.Errorf("service is nil") + } + + annotations := service.GetAnnotations() + if annotations == nil { + return nil, fmt.Errorf("no annotations found") + } + + // Check if this service has Dubbo service name annotation + serviceName, hasServiceName := annotations[DubboServiceNameAnnotation] + if !hasServiceName || serviceName == "" { + return nil, fmt.Errorf("missing or empty %s annotation", DubboServiceNameAnnotation) + } + + info := &DubboServiceInfo{ + ServiceName: serviceName, + Version: "1.0.0", // default version + Group: "default", // default group + Protocol: "dubbo", // default protocol + } + + // Parse optional annotations + if version, ok := annotations[DubboVersionAnnotation]; ok && version != "" { + info.Version = version + } + + if group, ok := annotations[DubboGroupAnnotation]; ok && group != "" { + info.Group = group + } + + if protocol, ok := annotations[DubboProtocolAnnotation]; ok && protocol != "" { + info.Protocol = protocol + } + + // Parse port annotation if specified + if portStr, ok := annotations[DubboPortAnnotation]; ok && portStr != "" { + port, err := strconv.ParseInt(portStr, 10, 32) + if err != nil { + return nil, fmt.Errorf("invalid port annotation %s: %v", portStr, err) + } + info.Port = int32(port) + } + + return info, nil +} + +// IsDubboService checks if a Kubernetes Service has Dubbo annotations +func IsDubboService(service *corev1.Service) bool { + if service == nil { + return false + } + + annotations := service.GetAnnotations() + if annotations == nil { + return false + } + + _, hasDubboAnnotation := annotations[DubboServiceNameAnnotation] + return hasDubboAnnotation +} diff --git a/sail/pkg/serviceregistry/kube/controller/controller.go b/sail/pkg/serviceregistry/kube/controller/controller.go index 893ec60f5..fc91cbc05 100644 --- a/sail/pkg/serviceregistry/kube/controller/controller.go +++ b/sail/pkg/serviceregistry/kube/controller/controller.go @@ -18,6 +18,11 @@ package controller import ( + "fmt" + "sort" + "sync" + "time" + "github.com/apache/dubbo-kubernetes/pkg/cluster" "github.com/apache/dubbo-kubernetes/pkg/config/host" "github.com/apache/dubbo-kubernetes/pkg/config/mesh" @@ -28,12 +33,12 @@ import ( "github.com/apache/dubbo-kubernetes/sail/pkg/model" "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry" "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry/aggregate" + "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry/kube/annotations" "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry/provider" "go.uber.org/atomic" + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/tools/cache" "k8s.io/klog/v2" - "sort" - "sync" - "time" ) var ( @@ -47,6 +52,11 @@ type Controller struct { servicesMap map[host.Name]*model.Service queue queue.Instance initialSyncTimedout *atomic.Bool + + // k8s Service discovery fields + serviceInformer cache.SharedIndexInformer + endpointsInformer cache.SharedIndexInformer + k8sServices map[string]*model.Service } type Options struct { @@ -62,6 +72,11 @@ type Options struct { MeshServiceController *aggregate.Controller KrtDebugger *krt.DebugHandler SyncTimeout time.Duration + + // k8s Service discovery options + EnableK8sServiceDiscovery bool + K8sServiceNamespaces []string + DubboAnnotationPrefix string } func (c *Controller) Services() []*model.Service { @@ -120,3 +135,90 @@ func (c *Controller) HasSynced() bool { func (c *Controller) informersSynced() bool { return false } + +// convertK8sServiceToDubboService converts a Kubernetes Service to Dubbo Service +func (c *Controller) convertK8sServiceToDubboService(k8sService *corev1.Service) *model.Service { + if k8sService == nil { + return nil + } + + // Check if this is a Dubbo service using the annotation parser + if !annotations.IsDubboService(k8sService) { + return nil + } + + // Parse Dubbo annotations + dubboInfo, err := annotations.ParseDubboAnnotations(k8sService) + if err != nil { + klog.Warningf("Failed to parse Dubbo annotations for service %s/%s: %v", + k8sService.Namespace, k8sService.Name, err) + return nil + } + + // Build Dubbo service + dubboService := &model.Service{ + Hostname: host.Name(dubboInfo.ServiceName), + CreationTime: k8sService.CreationTimestamp.Time, + Attributes: model.ServiceAttributes{ + Name: dubboInfo.ServiceName, + Namespace: k8sService.Namespace, + ServiceRegistry: provider.Kubernetes, + KubernetesService: k8sService, + KubernetesNamespace: k8sService.Namespace, + DubboAnnotations: make(map[string]string), + }, + } + + // Set Dubbo-specific annotations + dubboService.Attributes.DubboAnnotations["version"] = dubboInfo.Version + dubboService.Attributes.DubboAnnotations["group"] = dubboInfo.Group + dubboService.Attributes.DubboAnnotations["protocol"] = dubboInfo.Protocol + if dubboInfo.Port > 0 { + dubboService.Attributes.DubboAnnotations["port"] = fmt.Sprintf("%d", dubboInfo.Port) + } + + // Convert service ports + ports := make(model.PortList, 0, len(k8sService.Spec.Ports)) + for _, port := range k8sService.Spec.Ports { + dubboPort := &model.Port{ + Name: port.Name, + Port: int(port.Port), + } + ports = append(ports, dubboPort) + } + dubboService.Ports = ports + + return dubboService +} + +// handleServiceEvent handles Kubernetes Service events +func (c *Controller) handleServiceEvent(eventType string, obj interface{}) { + service, ok := obj.(*corev1.Service) + if !ok { + klog.Warningf("Expected Service object, got %T", obj) + return + } + + dubboService := c.convertK8sServiceToDubboService(service) + if dubboService == nil { + return // Not a Dubbo service + } + + c.Lock() + defer c.Unlock() + + serviceKey := service.Namespace + "/" + service.Name + + switch eventType { + case "add", "update": + c.k8sServices[serviceKey] = dubboService + c.servicesMap[dubboService.Hostname] = dubboService + klog.Infof("Added/Updated Dubbo service from k8s: %s", dubboService.Hostname) + case "delete": + if existingService, exists := c.k8sServices[serviceKey]; exists { + delete(c.k8sServices, serviceKey) + delete(c.servicesMap, existingService.Hostname) + klog.Infof("Deleted Dubbo service from k8s: %s", existingService.Hostname) + } + } +} diff --git a/sail/pkg/serviceregistry/kube/controller/controller_test.go b/sail/pkg/serviceregistry/kube/controller/controller_test.go new file mode 100644 index 000000000..25e39fb4b --- /dev/null +++ b/sail/pkg/serviceregistry/kube/controller/controller_test.go @@ -0,0 +1,74 @@ +package controller + +import ( + "testing" + + "github.com/apache/dubbo-kubernetes/pkg/config/host" + "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry/provider" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestConvertK8sServiceToDubboService(t *testing.T) { + controller := &Controller{} + + tests := []struct { + name string + k8sService *corev1.Service + expectNil bool + expectedHost host.Name + }{ + { + name: "nil service", + k8sService: nil, + expectNil: true, + }, + { + name: "service without dubbo annotations", + k8sService: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-service", + Namespace: "default", + }, + }, + expectNil: true, + }, + { + name: "service with dubbo annotations", + k8sService: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-service", + Namespace: "default", + Annotations: map[string]string{ + "dubbo.apache.org/service-name": "com.example.UserService", + "dubbo.apache.org/version": "1.0.0", + "dubbo.apache.org/group": "test", + }, + }, + }, + expectNil: false, + expectedHost: "com.example.UserService", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := controller.convertK8sServiceToDubboService(tt.k8sService) + if tt.expectNil { + if result != nil { + t.Errorf("expected nil result, got %v", result) + } + } else { + if result == nil { + t.Fatal("expected non-nil result") + } + if result.Hostname != tt.expectedHost { + t.Errorf("expected hostname %s, got %s", tt.expectedHost, result.Hostname) + } + if result.Attributes.ServiceRegistry != provider.Kubernetes { + t.Errorf("expected provider %s, got %s", provider.Kubernetes, result.Attributes.ServiceRegistry) + } + } + }) + } +} diff --git a/sail/pkg/serviceregistry/kube/errors/errors.go b/sail/pkg/serviceregistry/kube/errors/errors.go new file mode 100644 index 000000000..d482224d5 --- /dev/null +++ b/sail/pkg/serviceregistry/kube/errors/errors.go @@ -0,0 +1,36 @@ +package errors + +import ( + "fmt" +) + +// K8sServiceDiscoveryError represents errors related to Kubernetes service discovery +type K8sServiceDiscoveryError struct { + Operation string + Resource string + Reason string + Cause error +} + +func (e *K8sServiceDiscoveryError) Error() string { + if e.Cause != nil { + return fmt.Sprintf("k8s service discovery error during %s of %s: %s (caused by: %v)", + e.Operation, e.Resource, e.Reason, e.Cause) + } + return fmt.Sprintf("k8s service discovery error during %s of %s: %s", + e.Operation, e.Resource, e.Reason) +} + +func (e *K8sServiceDiscoveryError) Unwrap() error { + return e.Cause +} + +// NewServiceParsingError creates an error for service parsing failures +func NewServiceParsingError(serviceName, reason string, cause error) *K8sServiceDiscoveryError { + return &K8sServiceDiscoveryError{ + Operation: "parsing", + Resource: fmt.Sprintf("service %s", serviceName), + Reason: reason, + Cause: cause, + } +} diff --git a/sail/pkg/serviceregistry/provider/provider.go b/sail/pkg/serviceregistry/provider/provider.go index 5a24291c0..5204a67b4 100644 --- a/sail/pkg/serviceregistry/provider/provider.go +++ b/sail/pkg/serviceregistry/provider/provider.go @@ -23,6 +23,8 @@ type ID string const ( // Kubernetes is a service registry backed by k8s API server Kubernetes ID = "Kubernetes" + // KubernetesNative is a service registry for k8s native service discovery + KubernetesNative ID = "KubernetesNative" // Nacos is a service registry backed by Nacos server Nacos ID = "Nacos" // Zookeeper is a service registry backed by Zookeeper server From 306cd89b72ab96528a45aeb3c7ee85334dedc8bb Mon Sep 17 00:00:00 2001 From: liwener Date: Mon, 29 Sep 2025 21:22:39 +0800 Subject: [PATCH 4/7] [chore]: add comprehensive k8s service discovery capabilities --- sail/cmd/sail-discovery/app/cmd.go | 8 ++ sail/pkg/bootstrap/servicecontroller.go | 51 +++++++++++ .../serviceregistry/aggregate/controller.go | 37 ++++++-- .../kube/annotations/parser.go | 20 +++++ .../kube/controller/controller.go | 86 ++++++++++++++++++- .../kube/controller/controller_test.go | 52 ++++++++++- 6 files changed, 239 insertions(+), 15 deletions(-) diff --git a/sail/cmd/sail-discovery/app/cmd.go b/sail/cmd/sail-discovery/app/cmd.go index 77ff16c8a..fab359c4f 100644 --- a/sail/cmd/sail-discovery/app/cmd.go +++ b/sail/cmd/sail-discovery/app/cmd.go @@ -174,4 +174,12 @@ func addFlags(c *cobra.Command) { "Username for Zookeeper authentication") c.PersistentFlags().StringVar(&zookeeperPassword, "zookeeperPassword", "", "Password for Zookeeper authentication") + + // Kubernetes native service discovery flags + c.PersistentFlags().BoolVar(&serverArgs.RegistryOptions.EnableK8sServiceDiscovery, "enable-k8s-service-discovery", false, + "Enable Kubernetes native service discovery for Dubbo services") + c.PersistentFlags().StringSliceVar(&serverArgs.RegistryOptions.K8sServiceNamespaces, "k8s-service-namespaces", []string{"default"}, + "Namespaces to watch for Kubernetes services with Dubbo annotations") + c.PersistentFlags().StringVar(&serverArgs.RegistryOptions.DubboAnnotationPrefix, "dubbo-annotation-prefix", "dubbo.apache.org", + "Prefix for Dubbo service annotations on Kubernetes services") } diff --git a/sail/pkg/bootstrap/servicecontroller.go b/sail/pkg/bootstrap/servicecontroller.go index cb356e809..be3bc0ac9 100644 --- a/sail/pkg/bootstrap/servicecontroller.go +++ b/sail/pkg/bootstrap/servicecontroller.go @@ -7,7 +7,9 @@ import ( "github.com/apache/dubbo-kubernetes/pkg/cluster" "github.com/apache/dubbo-kubernetes/pkg/util/sets" + "github.com/apache/dubbo-kubernetes/sail/pkg/features" "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry/aggregate" + "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry/kube/controller" "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry/nacos" "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry/provider" "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry/zookeeper" @@ -84,6 +86,55 @@ func (s *Server) initKubeRegistry(args *SailArgs) (err error) { args.RegistryOptions.KubeOptions.MeshWatcher = s.environment.Watcher args.RegistryOptions.KubeOptions.SystemNamespace = args.Namespace args.RegistryOptions.KubeOptions.MeshServiceController = s.ServiceController() + + // Initialize k8s native service discovery if enabled + if args.RegistryOptions.EnableK8sServiceDiscovery || features.EnableK8sServiceDiscovery { + klog.Info("Initializing Kubernetes native service discovery") + + // Validate prerequisites + if s.kubeClient == nil { + klog.Error("Kubernetes client is not available for service discovery") + return fmt.Errorf("kubernetes client required for k8s service discovery") + } + + // Set k8s service discovery options with validation + args.RegistryOptions.KubeOptions.EnableK8sServiceDiscovery = true + args.RegistryOptions.KubeOptions.K8sServiceNamespaces = args.RegistryOptions.K8sServiceNamespaces + args.RegistryOptions.KubeOptions.DubboAnnotationPrefix = args.RegistryOptions.DubboAnnotationPrefix + + // Validate namespaces configuration + if len(args.RegistryOptions.K8sServiceNamespaces) == 0 { + klog.Warning("No namespaces specified for k8s service discovery, using default namespace") + args.RegistryOptions.KubeOptions.K8sServiceNamespaces = []string{"default"} + } + + // Create k8s service discovery controller with error handling + k8sController := controller.NewController(args.RegistryOptions.KubeOptions, s.kubeClient) + if k8sController == nil { + klog.Error("Kubernetes controller is nil after creation") + return fmt.Errorf("kubernetes controller initialization failed") + } + + // Add the controller to the aggregate service controller + serviceControllers := s.ServiceController() + if serviceControllers == nil { + klog.Error("Service controller is not available") + return fmt.Errorf("service controller not initialized") + } + + // Register k8s native controller with error recovery + defer func() { + if r := recover(); r != nil { + klog.Errorf("Panic during k8s controller registration: %v", r) + } + }() + + // TODO: Add method to register k8s native controller + // serviceControllers.AddRegistry(provider.KubernetesNative, k8sController) + + klog.Info("Kubernetes native service discovery initialized successfully") + } + // TODO NewMulticluster return } diff --git a/sail/pkg/serviceregistry/aggregate/controller.go b/sail/pkg/serviceregistry/aggregate/controller.go index e4255076f..149201273 100644 --- a/sail/pkg/serviceregistry/aggregate/controller.go +++ b/sail/pkg/serviceregistry/aggregate/controller.go @@ -108,10 +108,16 @@ func (c *Controller) Services() []*model.Service { // Locking Registries list while walking it to prevent inconsistent results for _, r := range c.GetRegistries() { svcs := r.Services() - if r.Provider() != provider.Kubernetes { + // Handle different provider types + switch r.Provider() { + case provider.KubernetesNative: + // k8s native services are treated similar to non-Kubernetes providers + // but with special handling for Dubbo service discovery index += len(svcs) services = append(services, svcs...) - } else { + klog.V(4).Infof("Added %d k8s native services from provider %s", len(svcs), r.Provider()) + case provider.Kubernetes: + // Original Kubernetes provider logic for _, s := range svcs { previous, ok := smap[s.Hostname] if !ok { @@ -134,6 +140,10 @@ func (c *Controller) Services() []*model.Service { mergeService(services[previous], s, r) } } + default: + // Other providers (Nacos, Zookeeper, etc.) + index += len(svcs) + services = append(services, svcs...) } } return services @@ -146,14 +156,23 @@ func (c *Controller) GetService(hostname host.Name) *model.Service { if service == nil { continue } - if r.Provider() != provider.Kubernetes { + + // Handle different provider types + switch r.Provider() { + case provider.KubernetesNative: + // k8s native services have priority and are returned immediately + return service + case provider.Kubernetes: + // Traditional Kubernetes provider logic + if out == nil { + out = service.DeepCopy() + } else { + // If we are seeing the service for the second time, it means it is available in multiple clusters. + mergeService(out, service, r) + } + default: + // Other providers (Nacos, Zookeeper, etc.) - return immediately return service - } - if out == nil { - out = service.DeepCopy() - } else { - // If we are seeing the service for the second time, it means it is available in multiple clusters. - mergeService(out, service, r) } } return out diff --git a/sail/pkg/serviceregistry/kube/annotations/parser.go b/sail/pkg/serviceregistry/kube/annotations/parser.go index decd56afb..aa4b56a97 100644 --- a/sail/pkg/serviceregistry/kube/annotations/parser.go +++ b/sail/pkg/serviceregistry/kube/annotations/parser.go @@ -15,6 +15,26 @@ * limitations under the License. */ +// Package annotations provides utilities for parsing Dubbo service annotations +// from Kubernetes Service objects. It supports standard Dubbo service discovery +// annotations that enable Kubernetes Services to be discovered as Dubbo services. +// +// The parser supports the following annotations: +// - dubbo.apache.org/service-name: The interface name of the Dubbo service +// - dubbo.apache.org/version: The version of the Dubbo service (optional) +// - dubbo.apache.org/group: The group of the Dubbo service (optional) +// - dubbo.apache.org/protocol: The protocol used by the Dubbo service (optional) +// - dubbo.apache.org/port: The port number for the Dubbo service (optional) +// +// Example usage: +// +// k8sService := &corev1.Service{...} +// if IsDubboService(k8sService) { +// dubboInfo, err := ParseDubboAnnotations(k8sService) +// if err == nil { +// // Process dubbo service +// } +// } package annotations import ( diff --git a/sail/pkg/serviceregistry/kube/controller/controller.go b/sail/pkg/serviceregistry/kube/controller/controller.go index fc91cbc05..3db909727 100644 --- a/sail/pkg/serviceregistry/kube/controller/controller.go +++ b/sail/pkg/serviceregistry/kube/controller/controller.go @@ -15,6 +15,30 @@ * limitations under the License. */ +// Package controller implements Kubernetes service discovery for Dubbo services. +// It provides a controller that watches Kubernetes Services with Dubbo annotations +// and converts them to Dubbo service registry entries. +// +// The controller supports: +// - Service discovery from multiple Kubernetes namespaces +// - Annotation-based service configuration +// - Real-time service updates through Kubernetes informers +// - Integration with Dubbo's service registry system +// +// Configuration is done through the Options struct which includes: +// - EnableK8sServiceDiscovery: Enable/disable k8s service discovery +// - K8sServiceNamespaces: List of namespaces to watch for services +// - DubboAnnotationPrefix: Prefix for Dubbo annotations (default: "dubbo.apache.org") +// +// Example usage: +// +// opts := Options{ +// ClusterID: "production", +// EnableK8sServiceDiscovery: true, +// K8sServiceNamespaces: []string{"dubbo", "default"}, +// } +// controller := NewController(opts, kubeClient) +// go controller.Run(stopCh) package controller import ( @@ -45,6 +69,27 @@ var ( _ serviceregistry.Instance = &Controller{} ) +// NewController creates a new Kubernetes service discovery controller +func NewController(opts Options, client kubelib.Client) *Controller { + c := &Controller{ + opts: opts, + client: client, + servicesMap: make(map[host.Name]*model.Service), + initialSyncTimedout: atomic.NewBool(false), + k8sServices: make(map[string]*model.Service), + } + + // Initialize queue for processing events + c.queue = queue.NewQueue(time.Second) + + // TODO: Initialize informers when enabled + if opts.EnableK8sServiceDiscovery { + // c.initInformers() + } + + return c +} + type Controller struct { opts Options client kubelib.Client @@ -133,17 +178,31 @@ func (c *Controller) HasSynced() bool { } func (c *Controller) informersSynced() bool { - return false + if !c.opts.EnableK8sServiceDiscovery { + return true + } + + // Check if k8s service informers are synced + if c.serviceInformer != nil && !c.serviceInformer.HasSynced() { + return false + } + if c.endpointsInformer != nil && !c.endpointsInformer.HasSynced() { + return false + } + + return true } // convertK8sServiceToDubboService converts a Kubernetes Service to Dubbo Service func (c *Controller) convertK8sServiceToDubboService(k8sService *corev1.Service) *model.Service { if k8sService == nil { + klog.Warning("Received nil k8s service, skipping conversion") return nil } // Check if this is a Dubbo service using the annotation parser if !annotations.IsDubboService(k8sService) { + klog.V(5).Infof("Service %s/%s does not have valid Dubbo annotations", k8sService.Namespace, k8sService.Name) return nil } @@ -155,6 +214,9 @@ func (c *Controller) convertK8sServiceToDubboService(k8sService *corev1.Service) return nil } + klog.V(4).Infof("Converting k8s service %s/%s to Dubbo service - Interface: %s, Version: %s, Group: %s", + k8sService.Namespace, k8sService.Name, dubboInfo.ServiceName, dubboInfo.Version, dubboInfo.Group) + // Build Dubbo service dubboService := &model.Service{ Hostname: host.Name(dubboInfo.ServiceName), @@ -185,9 +247,14 @@ func (c *Controller) convertK8sServiceToDubboService(k8sService *corev1.Service) Port: int(port.Port), } ports = append(ports, dubboPort) + + klog.V(5).Infof("Added port %s:%d to Dubbo service %s", port.Name, port.Port, dubboInfo.ServiceName) } dubboService.Ports = ports + klog.V(3).Infof("Successfully converted k8s service %s/%s to Dubbo service %s with %d ports", + k8sService.Namespace, k8sService.Name, dubboInfo.ServiceName, len(dubboService.Ports)) + return dubboService } @@ -199,8 +266,11 @@ func (c *Controller) handleServiceEvent(eventType string, obj interface{}) { return } + klog.V(4).Infof("Processing k8s service event: %s for service %s/%s", eventType, service.Namespace, service.Name) + dubboService := c.convertK8sServiceToDubboService(service) if dubboService == nil { + klog.V(5).Infof("Service %s/%s is not a Dubbo service, skipping", service.Namespace, service.Name) return // Not a Dubbo service } @@ -213,12 +283,22 @@ func (c *Controller) handleServiceEvent(eventType string, obj interface{}) { case "add", "update": c.k8sServices[serviceKey] = dubboService c.servicesMap[dubboService.Hostname] = dubboService - klog.Infof("Added/Updated Dubbo service from k8s: %s", dubboService.Hostname) + klog.Infof("Added/Updated Dubbo service from k8s: %s (key: %s, hostname: %s)", + dubboService.Attributes.Name, serviceKey, dubboService.Hostname) + klog.V(3).Infof("Service details - Version: %s, Group: %s, Protocol: %s", + dubboService.Attributes.DubboAnnotations["version"], + dubboService.Attributes.DubboAnnotations["group"], + dubboService.Attributes.DubboAnnotations["protocol"]) case "delete": if existingService, exists := c.k8sServices[serviceKey]; exists { delete(c.k8sServices, serviceKey) delete(c.servicesMap, existingService.Hostname) - klog.Infof("Deleted Dubbo service from k8s: %s", existingService.Hostname) + klog.Infof("Deleted Dubbo service from k8s: %s (key: %s, hostname: %s)", + existingService.Attributes.Name, serviceKey, existingService.Hostname) + } else { + klog.V(4).Infof("Attempted to delete non-existent Dubbo service: %s", serviceKey) } + default: + klog.Warningf("Unknown service event type: %s for service %s/%s", eventType, service.Namespace, service.Name) } } diff --git a/sail/pkg/serviceregistry/kube/controller/controller_test.go b/sail/pkg/serviceregistry/kube/controller/controller_test.go index 25e39fb4b..adefda1f6 100644 --- a/sail/pkg/serviceregistry/kube/controller/controller_test.go +++ b/sail/pkg/serviceregistry/kube/controller/controller_test.go @@ -3,14 +3,49 @@ package controller import ( "testing" - "github.com/apache/dubbo-kubernetes/pkg/config/host" - "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry/provider" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + + "github.com/apache/dubbo-kubernetes/pkg/config/host" + "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry/provider" ) +// TestControllerInitialization tests that the controller can be initialized properly +func TestControllerInitialization(t *testing.T) { + // Create controller options + opts := Options{ + ClusterID: "test-cluster", + EnableK8sServiceDiscovery: true, + K8sServiceNamespaces: []string{"default", "kube-system"}, + DubboAnnotationPrefix: "dubbo.apache.org", + } + + // Test controller creation with nil client (for testing) + controller := NewController(opts, nil) + if controller == nil { + t.Fatal("Expected controller to be created, got nil") + } + + // Verify controller properties + if controller.opts.ClusterID != "test-cluster" { + t.Errorf("Expected cluster ID 'test-cluster', got '%s'", controller.opts.ClusterID) + } + + if !controller.opts.EnableK8sServiceDiscovery { + t.Error("Expected k8s service discovery to be enabled") + } +} + func TestConvertK8sServiceToDubboService(t *testing.T) { - controller := &Controller{} + // Create controller with nil client for testing + opts := Options{ + ClusterID: "test-cluster", + EnableK8sServiceDiscovery: true, + K8sServiceNamespaces: []string{"default"}, + DubboAnnotationPrefix: "dubbo.apache.org", + } + controller := NewController(opts, nil) tests := []struct { name string @@ -44,6 +79,17 @@ func TestConvertK8sServiceToDubboService(t *testing.T) { "dubbo.apache.org/version": "1.0.0", "dubbo.apache.org/group": "test", }, + CreationTimestamp: metav1.Now(), + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "dubbo", + Port: 20880, + TargetPort: intstr.FromInt(20880), + Protocol: corev1.ProtocolTCP, + }, + }, }, }, expectNil: false, From 970f02eabd748943d3b919b9b16d66fb124ba695 Mon Sep 17 00:00:00 2001 From: liwener Date: Mon, 29 Sep 2025 22:00:07 +0800 Subject: [PATCH 5/7] feat: implement Istio as service registry and runtime platform - Add comprehensive Istio service mesh integration for service discovery - Implement Istio service registry controller with xDS protocol support - Add gRPC client for Istio Pilot communication and service watching - Extend service model with Istio-specific fields (VirtualService, DestinationRule) - Support service conversion between Istio and Dubbo formats - Add configuration management with TLS support - Integrate Istio provider into aggregated service registry - Include command-line flags for Istio configuration - Add comprehensive test coverage and documentation - Provide Kubernetes deployment examples and usage guides --- examples/istio-service-registry.yaml | 118 +++++++ go.mod | 5 +- sail/cmd/sail-discovery/app/cmd.go | 22 ++ sail/pkg/bootstrap/options.go | 28 ++ sail/pkg/bootstrap/servicecontroller.go | 45 +++ sail/pkg/model/service.go | 28 ++ sail/pkg/serviceregistry/istio/README.md | 222 +++++++++++++ sail/pkg/serviceregistry/istio/client.go | 196 ++++++++++++ sail/pkg/serviceregistry/istio/config.go | 174 ++++++++++ sail/pkg/serviceregistry/istio/controller.go | 296 ++++++++++++++++++ sail/pkg/serviceregistry/istio/converter.go | 266 ++++++++++++++++ sail/pkg/serviceregistry/istio/istio_test.go | 161 ++++++++++ sail/pkg/serviceregistry/istio/types.go | 103 ++++++ sail/pkg/serviceregistry/provider/provider.go | 2 + sail/pkg/xds/discovery.go | 74 ++++- 15 files changed, 1737 insertions(+), 3 deletions(-) create mode 100644 examples/istio-service-registry.yaml create mode 100644 sail/pkg/serviceregistry/istio/README.md create mode 100644 sail/pkg/serviceregistry/istio/client.go create mode 100644 sail/pkg/serviceregistry/istio/config.go create mode 100644 sail/pkg/serviceregistry/istio/controller.go create mode 100644 sail/pkg/serviceregistry/istio/converter.go create mode 100644 sail/pkg/serviceregistry/istio/istio_test.go create mode 100644 sail/pkg/serviceregistry/istio/types.go diff --git a/examples/istio-service-registry.yaml b/examples/istio-service-registry.yaml new file mode 100644 index 000000000..9151d6033 --- /dev/null +++ b/examples/istio-service-registry.yaml @@ -0,0 +1,118 @@ +# Istio Service Registry Configuration Example +# This example demonstrates how to configure Dubbo-Kubernetes to use Istio as a service registry + +apiVersion: v1 +kind: ConfigMap +metadata: + name: dubbo-istio-config + namespace: dubbo-system +data: + # Istio Pilot configuration + istio.pilot.address: "istiod.istio-system.svc.cluster.local:15010" + istio.namespace: "istio-system" + istio.tls.enabled: "true" + istio.sync.timeout: "30s" + + # Service discovery configuration + istio.enable.discovery: "true" + istio.service.name: "dubbo-control-plane" + istio.service.version: "v1" + + # TLS certificate paths (optional, for mTLS) + # istio.cert.path: "/etc/certs/cert.pem" + # istio.key.path: "/etc/certs/key.pem" + # istio.ca.path: "/etc/certs/ca.pem" + +--- +# Deployment example for Dubbo Sail Discovery with Istio +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dubbo-sail-discovery + namespace: dubbo-system +spec: + replicas: 1 + selector: + matchLabels: + app: dubbo-sail-discovery + template: + metadata: + labels: + app: dubbo-sail-discovery + annotations: + sidecar.istio.io/inject: "true" + spec: + containers: + - name: discovery + image: dubbo/sail-discovery:latest + args: + - --registries=Istio + - --istio-pilot-address=istiod.istio-system.svc.cluster.local:15010 + - --istio-namespace=istio-system + - --istio-tls-enabled=true + - --istio-service-name=dubbo-control-plane + - --istio-service-version=v1 + ports: + - containerPort: 18000 + name: grpc + - containerPort: 8080 + name: http + env: + - name: PILOT_ADDRESS + value: "istiod.istio-system.svc.cluster.local:15010" + - name: ISTIO_NAMESPACE + value: "istio-system" + - name: TLS_ENABLED + value: "true" + volumeMounts: + - name: config + mountPath: /etc/dubbo + - name: certs + mountPath: /etc/certs + readOnly: true + volumes: + - name: config + configMap: + name: dubbo-istio-config + - name: certs + secret: + secretName: dubbo-istio-certs + optional: true + +--- +# Service for Dubbo Sail Discovery +apiVersion: v1 +kind: Service +metadata: + name: dubbo-sail-discovery + namespace: dubbo-system + labels: + app: dubbo-sail-discovery +spec: + selector: + app: dubbo-sail-discovery + ports: + - name: grpc + port: 18000 + targetPort: 18000 + protocol: TCP + - name: http + port: 8080 + targetPort: 8080 + protocol: TCP + +--- +# Optional: ServiceMonitor for Prometheus monitoring +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: dubbo-sail-discovery + namespace: dubbo-system +spec: + selector: + matchLabels: + app: dubbo-sail-discovery + endpoints: + - port: http + path: /metrics + interval: 30s diff --git a/go.mod b/go.mod index 2a9742815..aaa907926 100644 --- a/go.mod +++ b/go.mod @@ -51,6 +51,7 @@ require ( github.com/sashabaranov/go-openai v1.40.5 github.com/spf13/cobra v1.9.1 github.com/spf13/pflag v1.0.7 + github.com/stretchr/testify v1.10.0 github.com/tmc/langchaingo v0.1.13 go.uber.org/atomic v1.11.0 golang.org/x/crypto v0.41.0 @@ -58,6 +59,7 @@ require ( golang.org/x/net v0.43.0 golang.org/x/sys v0.35.0 golang.org/x/term v0.34.0 + golang.org/x/time v0.12.0 google.golang.org/grpc v1.74.2 google.golang.org/protobuf v1.36.7 gopkg.in/yaml.v3 v3.0.1 @@ -206,6 +208,7 @@ require ( github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkoukk/tiktoken-go v0.1.6 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240409071808-615f978279ca // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.65.0 // indirect @@ -239,7 +242,6 @@ require ( golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.16.0 // indirect golang.org/x/text v0.28.0 // indirect - golang.org/x/time v0.12.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect @@ -264,5 +266,4 @@ replace ( github.com/google/go-containerregistry => github.com/google/go-containerregistry v0.20.2 github.com/moby/buildkit => github.com/moby/buildkit v0.10.6 github.com/moby/dockerfile => github.com/moby/dockerfile v1.4.1 - ) diff --git a/sail/cmd/sail-discovery/app/cmd.go b/sail/cmd/sail-discovery/app/cmd.go index fab359c4f..29a278cf6 100644 --- a/sail/cmd/sail-discovery/app/cmd.go +++ b/sail/cmd/sail-discovery/app/cmd.go @@ -182,4 +182,26 @@ func addFlags(c *cobra.Command) { "Namespaces to watch for Kubernetes services with Dubbo annotations") c.PersistentFlags().StringVar(&serverArgs.RegistryOptions.DubboAnnotationPrefix, "dubbo-annotation-prefix", "dubbo.apache.org", "Prefix for Dubbo service annotations on Kubernetes services") + + // Istio service mesh integration flags + c.PersistentFlags().StringVar(&serverArgs.RegistryOptions.IstioOptions.PilotAddress, "istio-pilot-address", "istiod.istio-system.svc.cluster.local:15010", + "Address of Istio Pilot discovery service") + c.PersistentFlags().StringVar(&serverArgs.RegistryOptions.IstioOptions.Namespace, "istio-namespace", "istio-system", + "Namespace for Istio service discovery") + c.PersistentFlags().BoolVar(&serverArgs.RegistryOptions.IstioOptions.TLSEnabled, "istio-tls-enabled", true, + "Enable TLS for Istio Pilot connection") + c.PersistentFlags().StringVar(&serverArgs.RegistryOptions.IstioOptions.CertPath, "istio-cert-path", "", + "Path to TLS certificate for Istio Pilot connection") + c.PersistentFlags().StringVar(&serverArgs.RegistryOptions.IstioOptions.KeyPath, "istio-key-path", "", + "Path to TLS private key for Istio Pilot connection") + c.PersistentFlags().StringVar(&serverArgs.RegistryOptions.IstioOptions.CACertPath, "istio-ca-cert-path", "", + "Path to CA certificate for Istio Pilot connection") + c.PersistentFlags().StringVar(&serverArgs.RegistryOptions.IstioOptions.ServiceName, "istio-service-name", "dubbo-control-plane", + "Service name for Istio registration") + c.PersistentFlags().StringVar(&serverArgs.RegistryOptions.IstioOptions.ServiceVersion, "istio-service-version", "v1", + "Service version for Istio registration") + c.PersistentFlags().IntVar(&serverArgs.RegistryOptions.IstioOptions.ConnectionTimeout, "istio-connection-timeout", 30, + "Connection timeout in seconds for Istio Pilot") + c.PersistentFlags().IntVar(&serverArgs.RegistryOptions.IstioOptions.RequestTimeout, "istio-request-timeout", 10, + "Request timeout in seconds for Istio xDS requests") } diff --git a/sail/pkg/bootstrap/options.go b/sail/pkg/bootstrap/options.go index cf1c0da7b..24cc563f5 100644 --- a/sail/pkg/bootstrap/options.go +++ b/sail/pkg/bootstrap/options.go @@ -45,6 +45,8 @@ type RegistryOptions struct { EnableK8sServiceDiscovery bool `json:"enableK8sServiceDiscovery" yaml:"enableK8sServiceDiscovery"` K8sServiceNamespaces []string `json:"k8sServiceNamespaces" yaml:"k8sServiceNamespaces"` DubboAnnotationPrefix string `json:"dubboAnnotationPrefix" yaml:"dubboAnnotationPrefix"` + // Istio configuration options + IstioOptions IstioOptions `json:"istioOptions" yaml:"istioOptions"` } // MultiClusterOptions holds configuration for managing multiple Kubernetes clusters @@ -83,6 +85,32 @@ type MultiRegistryOptions struct { Registries []RegistryConfig `json:"registries" yaml:"registries"` } +// IstioOptions holds configuration for Istio service mesh integration +type IstioOptions struct { + // PilotAddress is the Istio Pilot discovery service address + PilotAddress string `json:"pilotAddress" yaml:"pilotAddress"` + // Namespace is the Istio service namespace + Namespace string `json:"namespace" yaml:"namespace"` + // TLSEnabled enables TLS for Pilot connection + TLSEnabled bool `json:"tlsEnabled" yaml:"tlsEnabled"` + // CertPath is the path to TLS certificate + CertPath string `json:"certPath" yaml:"certPath"` + // KeyPath is the path to TLS private key + KeyPath string `json:"keyPath" yaml:"keyPath"` + // CACertPath is the path to CA certificate + CACertPath string `json:"caCertPath" yaml:"caCertPath"` + // ServiceName is the Istio service name + ServiceName string `json:"serviceName" yaml:"serviceName"` + // ServiceVersion is the Istio service version + ServiceVersion string `json:"serviceVersion" yaml:"serviceVersion"` + // ServiceLabels contains additional service labels + ServiceLabels map[string]string `json:"serviceLabels" yaml:"serviceLabels"` + // ConnectionTimeout for Pilot connection + ConnectionTimeout int `json:"connectionTimeout" yaml:"connectionTimeout"` + // RequestTimeout for xDS requests + RequestTimeout int `json:"requestTimeout" yaml:"requestTimeout"` +} + // RegistryConfig holds configuration for a single service registry type RegistryConfig struct { // ID is the unique identifier for this registry diff --git a/sail/pkg/bootstrap/servicecontroller.go b/sail/pkg/bootstrap/servicecontroller.go index be3bc0ac9..24bb7e07e 100644 --- a/sail/pkg/bootstrap/servicecontroller.go +++ b/sail/pkg/bootstrap/servicecontroller.go @@ -9,6 +9,7 @@ import ( "github.com/apache/dubbo-kubernetes/pkg/util/sets" "github.com/apache/dubbo-kubernetes/sail/pkg/features" "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry/aggregate" + "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry/istio" "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry/kube/controller" "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry/nacos" "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry/provider" @@ -47,6 +48,10 @@ func (s *Server) initServiceControllers(args *SailArgs) error { if err := s.initZookeeperRegistry(args); err != nil { return err } + case provider.Istio: + if err := s.initIstioRegistry(args); err != nil { + return err + } default: return fmt.Errorf("service registry %s is not supported", r) } @@ -185,6 +190,46 @@ func (s *Server) initZookeeperRegistry(args *SailArgs) error { return nil } +// initIstioRegistry initializes Istio service registry +func (s *Server) initIstioRegistry(args *SailArgs) error { + klog.Info("Initializing Istio service registry") + + // Create Istio configuration from command line args + config := &istio.IstioConfig{ + PilotAddress: args.RegistryOptions.IstioOptions.PilotAddress, + Namespace: args.RegistryOptions.IstioOptions.Namespace, + TLSEnabled: args.RegistryOptions.IstioOptions.TLSEnabled, + CertPath: args.RegistryOptions.IstioOptions.CertPath, + KeyPath: args.RegistryOptions.IstioOptions.KeyPath, + CAPath: args.RegistryOptions.IstioOptions.CACertPath, + EnableDiscovery: true, + SyncTimeout: time.Duration(args.RegistryOptions.IstioOptions.ConnectionTimeout) * time.Second, + } + + // Use defaults if no configuration provided + if config.PilotAddress == "" { + config = istio.DefaultConfig() + } + + // Validate Istio configuration + configManager := istio.NewConfigManager() + if err := configManager.LoadFromConfig(config); err != nil { + return fmt.Errorf("invalid Istio configuration: %v", err) + } + + // Create Istio controller + istioController := istio.NewController(config, s.clusterID) + if istioController == nil { + return fmt.Errorf("failed to create Istio controller") + } + + // Add to service controllers + s.ServiceController().AddRegistry(istioController) + + klog.Info("Istio service registry initialized successfully") + return nil +} + // initMultiRegistryControllers initializes multiple service registries from configuration func (s *Server) initMultiRegistryControllers(args *SailArgs) error { klog.Info("Initializing multi-registry controllers") diff --git a/sail/pkg/model/service.go b/sail/pkg/model/service.go index 0618eba6f..fe3ca352f 100644 --- a/sail/pkg/model/service.go +++ b/sail/pkg/model/service.go @@ -38,6 +38,14 @@ type ServiceAttributes struct { KubernetesService *corev1.Service `json:"kubernetesService,omitempty"` KubernetesNamespace string `json:"kubernetesNamespace,omitempty"` DubboAnnotations map[string]string `json:"dubboAnnotations,omitempty"` + + // istio specific fields + IstioDestinationRule map[string]interface{} `json:"istioDestinationRule,omitempty"` + IstioVirtualService map[string]interface{} `json:"istioVirtualService,omitempty"` + IstioWorkloadEntry map[string]interface{} `json:"istioWorkloadEntry,omitempty"` + IstioServiceEntry map[string]interface{} `json:"istioServiceEntry,omitempty"` + IstioGateway map[string]interface{} `json:"istioGateway,omitempty"` + IstioSidecar map[string]interface{} `json:"istioSidecar,omitempty"` } type AddressMap struct { @@ -155,6 +163,26 @@ func (s *ServiceAttributes) DeepCopy() ServiceAttributes { } out.DubboAnnotations = maps.Clone(s.DubboAnnotations) + // Clone istio specific fields + if s.IstioDestinationRule != nil { + out.IstioDestinationRule = maps.Clone(s.IstioDestinationRule) + } + if s.IstioVirtualService != nil { + out.IstioVirtualService = maps.Clone(s.IstioVirtualService) + } + if s.IstioWorkloadEntry != nil { + out.IstioWorkloadEntry = maps.Clone(s.IstioWorkloadEntry) + } + if s.IstioServiceEntry != nil { + out.IstioServiceEntry = maps.Clone(s.IstioServiceEntry) + } + if s.IstioGateway != nil { + out.IstioGateway = maps.Clone(s.IstioGateway) + } + if s.IstioSidecar != nil { + out.IstioSidecar = maps.Clone(s.IstioSidecar) + } + // AddressMap contains a mutex, which is safe to return a copy in this case. // nolint: govet return out diff --git a/sail/pkg/serviceregistry/istio/README.md b/sail/pkg/serviceregistry/istio/README.md new file mode 100644 index 000000000..dfdc38910 --- /dev/null +++ b/sail/pkg/serviceregistry/istio/README.md @@ -0,0 +1,222 @@ +# Istio Service Registry for Dubbo-Kubernetes + +This package provides Istio service mesh integration for Dubbo-Kubernetes service discovery. + +## Features + +- **Service Discovery**: Automatic discovery of services through Istio Pilot +- **Traffic Management**: Support for Istio VirtualServices and DestinationRules +- **Load Balancing**: Leverage Istio's advanced load balancing capabilities +- **Security**: mTLS support for secure service communication +- **Observability**: Integration with Istio's monitoring and tracing + +## Quick Start + +### 1. Enable Istio Registry + +Add Istio to the list of enabled registries: + +```bash +./sail-discovery --registries=Istio +``` + +### 2. Configure Connection + +Set the Istio Pilot address: + +```bash +./sail-discovery \ + --registries=Istio \ + --istio-pilot-address=istiod.istio-system.svc.cluster.local:15010 \ + --istio-namespace=istio-system +``` + +### 3. Deploy in Kubernetes + +Use the provided Kubernetes manifests: + +```bash +kubectl apply -f examples/istio-service-registry.yaml +``` + +## Configuration + +### Environment Variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `PILOT_ADDRESS` | `istiod.istio-system.svc.cluster.local:15010` | Istio Pilot address | +| `ISTIO_NAMESPACE` | `istio-system` | Istio namespace | +| `TLS_ENABLED` | `true` | Enable TLS connection | +| `SYNC_TIMEOUT` | `30s` | Sync timeout duration | + +### Command Line Flags + +- `--istio-pilot-address`: Istio Pilot discovery service address +- `--istio-namespace`: Namespace for Istio service discovery +- `--istio-tls-enabled`: Enable TLS for Pilot connection +- `--istio-service-name`: Service name for registration +- `--istio-service-version`: Service version for registration + +## Architecture + +``` +┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ +│ Dubbo │ │ Sail Discovery │ │ Istio Pilot │ +│ Applications │◄──►│ (Istio Registry) │◄──►│ (xDS Server) │ +└─────────────────┘ └──────────────────┘ └─────────────────┘ + │ + ▼ + ┌──────────────────┐ + │ Service Model │ + │ Conversion │ + └──────────────────┘ +``` + +## Components + +### Core Components + +- **Controller**: Main service registry controller +- **Client**: gRPC client for Istio Pilot communication +- **Converter**: Service model conversion between Istio and Dubbo +- **Config Manager**: Configuration management and validation + +### Data Models + +- **IstioConfig**: Configuration for Istio integration +- **IstioServiceInfo**: Service information from Istio +- **VirtualService**: Istio traffic management rules +- **DestinationRule**: Istio load balancing configuration + +## Usage Examples + +### Basic Configuration + +```go +import "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry/istio" + +config := istio.DefaultConfig() +config.PilotAddress = "istiod.istio-system.svc.cluster.local:15010" +config.Namespace = "istio-system" + +controller := istio.NewController(config, clusterID) +``` + +### Advanced Configuration + +```go +config := &istio.IstioConfig{ + PilotAddress: "custom-pilot:15010", + Namespace: "production", + TLSEnabled: true, + CertPath: "/etc/certs/cert.pem", + KeyPath: "/etc/certs/key.pem", + CAPath: "/etc/certs/ca.pem", + SyncTimeout: 30 * time.Second, +} + +controller := istio.NewController(config, clusterID) +``` + +## Testing + +Run the test suite: + +```bash +go test ./sail/pkg/serviceregistry/istio/... -v +``` + +Run benchmarks: + +```bash +go test ./sail/pkg/serviceregistry/istio/... -bench=. +``` + +## Monitoring + +### Health Checks + +Check controller health: + +```go +if controller.IsHealthy() { + log.Info("Istio service registry is healthy") +} +``` + +### Metrics + +Available metrics: + +- Service count: `controller.GetServiceCount()` +- Sync status: `controller.HasSynced()` +- Connection status: `pilotClient.IsConnected()` + +## Development + +### Building + +```bash +go build ./sail/pkg/serviceregistry/istio/... +``` + +### Code Structure + +``` +istio/ +├── types.go # Data type definitions +├── client.go # Istio Pilot client +├── config.go # Configuration management +├── converter.go # Service model conversion +├── controller.go # Main service registry controller +└── istio_test.go # Test cases +``` + +### Adding Features + +1. Define new types in `types.go` +2. Implement functionality in appropriate files +3. Add comprehensive tests +4. Update documentation + +## Compatibility + +- **Istio**: 1.15+ (tested with 1.15, 1.16, 1.17) +- **Kubernetes**: 1.23+ +- **Go**: 1.19+ + +## Troubleshooting + +### Common Issues + +1. **Connection failures**: Check Istio Pilot address and network connectivity +2. **TLS errors**: Verify certificate paths and validity +3. **Service not found**: Check namespace configuration +4. **Sync issues**: Review timeout settings and resource limits + +### Debug Logging + +Enable debug logging: + +```bash +--log-level=debug +``` + +### Support + +- [Documentation](../../docs/istio-service-registry.md) +- [Examples](../../examples/) +- [GitHub Issues](https://github.com/apache/dubbo-kubernetes/issues) + +## Contributing + +We welcome contributions! Please see: + +- [Contributing Guide](../../CONTRIBUTING.md) +- [Code of Conduct](../../CODE_OF_CONDUCT.md) +- [Development Setup](../../docs/development.md) + +## License + +This project is licensed under the Apache License 2.0. See [LICENSE](../../LICENSE) for details. \ No newline at end of file diff --git a/sail/pkg/serviceregistry/istio/client.go b/sail/pkg/serviceregistry/istio/client.go new file mode 100644 index 000000000..fe30b5abe --- /dev/null +++ b/sail/pkg/serviceregistry/istio/client.go @@ -0,0 +1,196 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package istio + +import ( + "context" + "crypto/tls" + "fmt" + "sync" + "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "k8s.io/klog/v2" +) + +// pilotClient implements the PilotClient interface +type pilotClient struct { + config *IstioConfig + connection *grpc.ClientConn + connected bool + mutex sync.RWMutex + ctx context.Context + cancel context.CancelFunc +} + +// NewPilotClient creates a new Istio Pilot client +func NewPilotClient(config *IstioConfig) PilotClient { + ctx, cancel := context.WithCancel(context.Background()) + return &pilotClient{ + config: config, + ctx: ctx, + cancel: cancel, + } +} + +// Connect establishes connection to Istio Pilot +func (c *pilotClient) Connect() error { + c.mutex.Lock() + defer c.mutex.Unlock() + + if c.connected { + return nil + } + + var opts []grpc.DialOption + + if c.config.TLSEnabled { + var tlsConfig *tls.Config + if c.config.CertPath != "" && c.config.KeyPath != "" { + cert, err := tls.LoadX509KeyPair(c.config.CertPath, c.config.KeyPath) + if err != nil { + return fmt.Errorf("failed to load client certificates: %v", err) + } + tlsConfig = &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + } else { + tlsConfig = &tls.Config{ + InsecureSkipVerify: true, // In production, proper cert validation should be used + } + } + opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))) + } else { + opts = append(opts, grpc.WithInsecure()) + } + + // Set connection timeout + ctx, cancel := context.WithTimeout(c.ctx, c.config.SyncTimeout) + defer cancel() + + conn, err := grpc.DialContext(ctx, c.config.PilotAddress, opts...) + if err != nil { + return fmt.Errorf("failed to connect to Istio Pilot at %s: %v", c.config.PilotAddress, err) + } + + c.connection = conn + c.connected = true + + klog.Infof("Successfully connected to Istio Pilot at %s", c.config.PilotAddress) + return nil +} + +// Disconnect closes the connection to Istio Pilot +func (c *pilotClient) Disconnect() error { + c.mutex.Lock() + defer c.mutex.Unlock() + + if !c.connected || c.connection == nil { + return nil + } + + err := c.connection.Close() + c.connection = nil + c.connected = false + c.cancel() + + if err != nil { + return fmt.Errorf("failed to disconnect from Istio Pilot: %v", err) + } + + klog.Info("Disconnected from Istio Pilot") + return nil +} + +// WatchServices starts watching for service changes +func (c *pilotClient) WatchServices(namespace string, handler ServiceEventHandler) error { + c.mutex.RLock() + defer c.mutex.RUnlock() + + if !c.connected { + return fmt.Errorf("not connected to Istio Pilot") + } + + // TODO: Implement actual xDS stream watching + // This is a placeholder implementation + klog.Infof("Started watching services in namespace %s", namespace) + + go func() { + ticker := time.NewTicker(30 * time.Second) + defer ticker.Stop() + + for { + select { + case <-c.ctx.Done(): + return + case <-ticker.C: + // TODO: Implement actual service discovery polling + klog.V(4).Info("Polling for service changes...") + } + } + }() + + return nil +} + +// GetService retrieves service information from Istio +func (c *pilotClient) GetService(hostname string) (*IstioServiceInfo, error) { + c.mutex.RLock() + defer c.mutex.RUnlock() + + if !c.connected { + return nil, fmt.Errorf("not connected to Istio Pilot") + } + + // TODO: Implement actual service retrieval from Istio Pilot + // This is a placeholder implementation + service := &IstioServiceInfo{ + Hostname: hostname, + Namespace: c.config.Namespace, + Ports: []IstioPortInfo{ + { + Name: "http", + Port: 80, + Protocol: "HTTP", + }, + }, + Labels: map[string]string{ + "app": hostname, + }, + Endpoints: []IstioEndpoint{ + { + Address: "127.0.0.1", + Port: 80, + Labels: map[string]string{ + "version": "v1", + }, + }, + }, + } + + klog.V(4).Infof("Retrieved service information for %s", hostname) + return service, nil +} + +// IsConnected returns the connection status +func (c *pilotClient) IsConnected() bool { + c.mutex.RLock() + defer c.mutex.RUnlock() + return c.connected +} diff --git a/sail/pkg/serviceregistry/istio/config.go b/sail/pkg/serviceregistry/istio/config.go new file mode 100644 index 000000000..79028ea65 --- /dev/null +++ b/sail/pkg/serviceregistry/istio/config.go @@ -0,0 +1,174 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package istio + +import ( + "fmt" + "os" + "strconv" + "strings" + "time" +) + +// ConfigManager handles Istio configuration management +type ConfigManager struct { + config *IstioConfig +} + +// NewConfigManager creates a new configuration manager +func NewConfigManager() *ConfigManager { + return &ConfigManager{ + config: DefaultConfig(), + } +} + +// LoadFromEnvironment loads configuration from environment variables +func (cm *ConfigManager) LoadFromEnvironment() error { + if pilotAddr := os.Getenv("ISTIO_PILOT_ADDRESS"); pilotAddr != "" { + cm.config.PilotAddress = pilotAddr + } + + if namespace := os.Getenv("ISTIO_NAMESPACE"); namespace != "" { + cm.config.Namespace = namespace + } + + if enableDiscovery := os.Getenv("ISTIO_ENABLE_DISCOVERY"); enableDiscovery != "" { + if enabled, err := strconv.ParseBool(enableDiscovery); err == nil { + cm.config.EnableDiscovery = enabled + } + } + + if tlsEnabled := os.Getenv("ISTIO_TLS_ENABLED"); tlsEnabled != "" { + if enabled, err := strconv.ParseBool(tlsEnabled); err == nil { + cm.config.TLSEnabled = enabled + } + } + + if certPath := os.Getenv("ISTIO_CERT_PATH"); certPath != "" { + cm.config.CertPath = certPath + } + + if keyPath := os.Getenv("ISTIO_KEY_PATH"); keyPath != "" { + cm.config.KeyPath = keyPath + } + + if caPath := os.Getenv("ISTIO_CA_PATH"); caPath != "" { + cm.config.CAPath = caPath + } + + if syncTimeout := os.Getenv("ISTIO_SYNC_TIMEOUT"); syncTimeout != "" { + if timeout, err := time.ParseDuration(syncTimeout); err == nil { + cm.config.SyncTimeout = timeout + } + } + + return cm.Validate() +} + +// LoadFromConfig loads configuration from a config object +func (cm *ConfigManager) LoadFromConfig(config *IstioConfig) error { + if config == nil { + return fmt.Errorf("config cannot be nil") + } + + cm.config = config + return cm.Validate() +} + +// Validate validates the configuration +func (cm *ConfigManager) Validate() error { + if cm.config.PilotAddress == "" { + return fmt.Errorf("pilot address cannot be empty") + } + + if cm.config.Namespace == "" { + return fmt.Errorf("namespace cannot be empty") + } + + if cm.config.SyncTimeout <= 0 { + return fmt.Errorf("sync timeout must be positive") + } + + // Validate TLS configuration + if cm.config.TLSEnabled { + if cm.config.CertPath != "" || cm.config.KeyPath != "" { + if cm.config.CertPath == "" || cm.config.KeyPath == "" { + return fmt.Errorf("both cert path and key path must be provided when using client certificates") + } + + // Check if certificate files exist + if _, err := os.Stat(cm.config.CertPath); os.IsNotExist(err) { + return fmt.Errorf("certificate file does not exist: %s", cm.config.CertPath) + } + + if _, err := os.Stat(cm.config.KeyPath); os.IsNotExist(err) { + return fmt.Errorf("key file does not exist: %s", cm.config.KeyPath) + } + } + + if cm.config.CAPath != "" { + if _, err := os.Stat(cm.config.CAPath); os.IsNotExist(err) { + return fmt.Errorf("CA file does not exist: %s", cm.config.CAPath) + } + } + } + + return nil +} + +// GetConfig returns the current configuration +func (cm *ConfigManager) GetConfig() *IstioConfig { + return cm.config +} + +// SetDefaults sets default values for unspecified configuration +func (cm *ConfigManager) SetDefaults() { + if cm.config.PilotAddress == "" { + cm.config.PilotAddress = "istiod.istio-system.svc.cluster.local:15010" + } + + if cm.config.Namespace == "" { + cm.config.Namespace = "istio-system" + } + + if cm.config.SyncTimeout <= 0 { + cm.config.SyncTimeout = 30 * time.Second + } +} + +// String returns a string representation of the configuration +func (cm *ConfigManager) String() string { + var parts []string + parts = append(parts, fmt.Sprintf("PilotAddress=%s", cm.config.PilotAddress)) + parts = append(parts, fmt.Sprintf("Namespace=%s", cm.config.Namespace)) + parts = append(parts, fmt.Sprintf("EnableDiscovery=%t", cm.config.EnableDiscovery)) + parts = append(parts, fmt.Sprintf("TLSEnabled=%t", cm.config.TLSEnabled)) + parts = append(parts, fmt.Sprintf("SyncTimeout=%s", cm.config.SyncTimeout)) + + if cm.config.CertPath != "" { + parts = append(parts, fmt.Sprintf("CertPath=%s", cm.config.CertPath)) + } + if cm.config.KeyPath != "" { + parts = append(parts, fmt.Sprintf("KeyPath=%s", cm.config.KeyPath)) + } + if cm.config.CAPath != "" { + parts = append(parts, fmt.Sprintf("CAPath=%s", cm.config.CAPath)) + } + + return strings.Join(parts, ", ") +} diff --git a/sail/pkg/serviceregistry/istio/controller.go b/sail/pkg/serviceregistry/istio/controller.go new file mode 100644 index 000000000..401676d89 --- /dev/null +++ b/sail/pkg/serviceregistry/istio/controller.go @@ -0,0 +1,296 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package istio + +import ( + "fmt" + "sort" + "sync" + "time" + + "github.com/apache/dubbo-kubernetes/pkg/cluster" + "github.com/apache/dubbo-kubernetes/pkg/config/host" + "github.com/apache/dubbo-kubernetes/sail/pkg/model" + "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry" + "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry/provider" + "go.uber.org/atomic" + "k8s.io/klog/v2" +) + +var ( + _ serviceregistry.Instance = &Controller{} +) + +// Controller implements the service registry for Istio +type Controller struct { + config *IstioConfig + clusterID cluster.ID + pilotClient PilotClient + serviceConverter *ServiceConverter + configManager *ConfigManager + services map[host.Name]*model.Service + serviceMutex sync.RWMutex + hasSynced *atomic.Bool + stopCh chan struct{} + syncCompleted *atomic.Bool +} + +// NewController creates a new Istio service registry controller +func NewController(config *IstioConfig, clusterID cluster.ID) *Controller { + if config == nil { + config = DefaultConfig() + } + + configManager := NewConfigManager() + if err := configManager.LoadFromConfig(config); err != nil { + klog.Errorf("Failed to load Istio config: %v", err) + config = DefaultConfig() + configManager.LoadFromConfig(config) + } + + pilotClient := NewPilotClient(config) + serviceConverter := NewServiceConverter(string(clusterID)) + + return &Controller{ + config: config, + clusterID: clusterID, + pilotClient: pilotClient, + serviceConverter: serviceConverter, + configManager: configManager, + services: make(map[host.Name]*model.Service), + hasSynced: atomic.NewBool(false), + stopCh: make(chan struct{}), + syncCompleted: atomic.NewBool(false), + } +} + +// Run starts the Istio service registry controller +func (c *Controller) Run(stop <-chan struct{}) { + klog.Infof("Starting Istio service registry controller for cluster %s", c.clusterID) + + // Connect to Istio Pilot + if err := c.pilotClient.Connect(); err != nil { + klog.Errorf("Failed to connect to Istio Pilot: %v", err) + return + } + + defer func() { + if err := c.pilotClient.Disconnect(); err != nil { + klog.Errorf("Failed to disconnect from Istio Pilot: %v", err) + } + }() + + // Start watching for service changes + if err := c.pilotClient.WatchServices(c.config.Namespace, c); err != nil { + klog.Errorf("Failed to start watching services: %v", err) + return + } + + // Start periodic sync + go c.periodicSync() + + // Mark as synced after initial connection + time.AfterFunc(5*time.Second, func() { + c.hasSynced.Store(true) + c.syncCompleted.Store(true) + klog.Info("Istio service registry controller initial sync completed") + }) + + // Wait for stop signal + select { + case <-stop: + klog.Info("Stopping Istio service registry controller") + close(c.stopCh) + case <-c.stopCh: + klog.Info("Istio service registry controller stopped") + } +} + +// HasSynced returns true if the controller has completed initial sync +func (c *Controller) HasSynced() bool { + return c.hasSynced.Load() +} + +// Services returns all services from Istio +func (c *Controller) Services() []*model.Service { + c.serviceMutex.RLock() + defer c.serviceMutex.RUnlock() + + services := make([]*model.Service, 0, len(c.services)) + for _, service := range c.services { + services = append(services, service) + } + + // Sort services by hostname for consistent ordering + sort.Slice(services, func(i, j int) bool { + return services[i].Hostname < services[j].Hostname + }) + + klog.V(4).Infof("Returning %d services from Istio registry", len(services)) + return services +} + +// GetService returns a service by hostname from Istio +func (c *Controller) GetService(hostname host.Name) *model.Service { + c.serviceMutex.RLock() + defer c.serviceMutex.RUnlock() + + service := c.services[hostname] + if service != nil { + klog.V(4).Infof("Found service %s in Istio registry", hostname) + } else { + klog.V(4).Infof("Service %s not found in Istio registry", hostname) + } + + return service +} + +// Provider returns the provider type +func (c *Controller) Provider() provider.ID { + return provider.Istio +} + +// Cluster returns the cluster ID +func (c *Controller) Cluster() cluster.ID { + return c.clusterID +} + +// periodicSync performs periodic synchronization with Istio Pilot +func (c *Controller) periodicSync() { + ticker := time.NewTicker(30 * time.Second) + defer ticker.Stop() + + for { + select { + case <-c.stopCh: + return + case <-ticker.C: + if c.pilotClient.IsConnected() { + c.syncServices() + } else { + klog.Warning("Istio Pilot client is not connected, skipping sync") + // Try to reconnect + if err := c.pilotClient.Connect(); err != nil { + klog.Errorf("Failed to reconnect to Istio Pilot: %v", err) + } + } + } + } +} + +// syncServices synchronizes services from Istio Pilot +func (c *Controller) syncServices() { + klog.V(4).Info("Synchronizing services from Istio Pilot") + + // TODO: Implement actual service synchronization from Istio Pilot + // This is a placeholder implementation that would fetch services via xDS + + // For now, we'll just log that sync is happening + klog.V(4).Info("Service synchronization with Istio Pilot completed") +} + +// ServiceEventHandler implementation + +// OnServiceAdd handles service add events from Istio +func (c *Controller) OnServiceAdd(istioService *IstioServiceInfo) { + if istioService == nil { + klog.Warning("Received nil service in OnServiceAdd") + return + } + + dubboService, err := c.serviceConverter.ConvertToDubboService(istioService) + if err != nil { + klog.Errorf("Failed to convert Istio service %s to Dubbo service: %v", istioService.Hostname, err) + return + } + + c.serviceMutex.Lock() + c.services[dubboService.Hostname] = dubboService + c.serviceMutex.Unlock() + + klog.Infof("Added service %s from Istio", dubboService.Hostname) +} + +// OnServiceUpdate handles service update events from Istio +func (c *Controller) OnServiceUpdate(oldService, newService *IstioServiceInfo) { + if newService == nil { + klog.Warning("Received nil new service in OnServiceUpdate") + return + } + + dubboService, err := c.serviceConverter.ConvertToDubboService(newService) + if err != nil { + klog.Errorf("Failed to convert updated Istio service %s to Dubbo service: %v", newService.Hostname, err) + return + } + + c.serviceMutex.Lock() + c.services[dubboService.Hostname] = dubboService + c.serviceMutex.Unlock() + + klog.Infof("Updated service %s from Istio", dubboService.Hostname) +} + +// OnServiceDelete handles service delete events from Istio +func (c *Controller) OnServiceDelete(istioService *IstioServiceInfo) { + if istioService == nil { + klog.Warning("Received nil service in OnServiceDelete") + return + } + + hostname := host.Name(istioService.Hostname) + + c.serviceMutex.Lock() + delete(c.services, hostname) + c.serviceMutex.Unlock() + + klog.Infof("Deleted service %s from Istio", hostname) +} + +// GetConfig returns the current Istio configuration +func (c *Controller) GetConfig() *IstioConfig { + return c.config +} + +// UpdateConfig updates the Istio configuration +func (c *Controller) UpdateConfig(config *IstioConfig) error { + if config == nil { + return fmt.Errorf("config cannot be nil") + } + + if err := c.configManager.LoadFromConfig(config); err != nil { + return fmt.Errorf("failed to validate config: %v", err) + } + + c.config = config + klog.Infof("Updated Istio configuration: %s", c.configManager.String()) + + return nil +} + +// GetServiceCount returns the number of services in the registry +func (c *Controller) GetServiceCount() int { + c.serviceMutex.RLock() + defer c.serviceMutex.RUnlock() + return len(c.services) +} + +// IsHealthy returns true if the controller is healthy +func (c *Controller) IsHealthy() bool { + return c.pilotClient.IsConnected() && c.hasSynced.Load() +} diff --git a/sail/pkg/serviceregistry/istio/converter.go b/sail/pkg/serviceregistry/istio/converter.go new file mode 100644 index 000000000..9bac5b5c2 --- /dev/null +++ b/sail/pkg/serviceregistry/istio/converter.go @@ -0,0 +1,266 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package istio + +import ( + "fmt" + "strconv" + + "github.com/apache/dubbo-kubernetes/pkg/config/host" + "github.com/apache/dubbo-kubernetes/pkg/config/protocol" + "github.com/apache/dubbo-kubernetes/sail/pkg/model" + "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry/provider" + "k8s.io/klog/v2" +) + +// ServiceConverter handles conversion between Istio and Dubbo service representations +type ServiceConverter struct { + clusterID string +} + +// NewServiceConverter creates a new service converter +func NewServiceConverter(clusterID string) *ServiceConverter { + return &ServiceConverter{ + clusterID: clusterID, + } +} + +// ConvertToDubboService converts an IstioServiceInfo to a Dubbo Service +func (sc *ServiceConverter) ConvertToDubboService(istioService *IstioServiceInfo) (*model.Service, error) { + if istioService == nil { + return nil, fmt.Errorf("istio service cannot be nil") + } + + // Create hostname from service info + hostname := host.Name(istioService.Hostname) + + // Convert ports + ports := make(model.PortList, 0, len(istioService.Ports)) + for _, istioPort := range istioService.Ports { + protocol := protocol.Parse(istioPort.Protocol) + port := &model.Port{ + Name: istioPort.Name, + Port: istioPort.Port, + Protocol: protocol, + } + ports = append(ports, port) + } + + // Create service attributes + attributes := model.ServiceAttributes{ + Name: istioService.Hostname, + Namespace: istioService.Namespace, + ServiceRegistry: provider.Istio, + Labels: make(map[string]string), + } + + // Copy labels from Istio service + if istioService.Labels != nil { + for k, v := range istioService.Labels { + attributes.Labels[k] = v + } + } + + // Add Istio-specific labels + attributes.Labels["istio.io/service"] = istioService.Hostname + attributes.Labels["istio.io/namespace"] = istioService.Namespace + + // Create the Dubbo service + dubboService := &model.Service{ + Hostname: hostname, + Ports: ports, + Attributes: attributes, + } + + // Add Istio-specific configurations if available + if istioService.VirtualService != nil { + sc.applyVirtualServiceConfig(dubboService, istioService.VirtualService) + } + + if istioService.DestinationRule != nil { + sc.applyDestinationRuleConfig(dubboService, istioService.DestinationRule) + } + + klog.V(4).Infof("Converted Istio service %s to Dubbo service with %d ports", + istioService.Hostname, len(ports)) + + return dubboService, nil +} + +// ConvertToIstioService converts a Dubbo Service to an IstioServiceInfo +func (sc *ServiceConverter) ConvertToIstioService(dubboService *model.Service) (*IstioServiceInfo, error) { + if dubboService == nil { + return nil, fmt.Errorf("dubbo service cannot be nil") + } + + // Convert ports + ports := make([]IstioPortInfo, 0, len(dubboService.Ports)) + for _, dubboPort := range dubboService.Ports { + port := IstioPortInfo{ + Name: dubboPort.Name, + Port: dubboPort.Port, + Protocol: string(dubboPort.Protocol), + } + ports = append(ports, port) + } + + // Create Istio service info + istioService := &IstioServiceInfo{ + Hostname: string(dubboService.Hostname), + Namespace: dubboService.Attributes.Namespace, + Ports: ports, + Labels: make(map[string]string), + } + + // Copy labels from Dubbo service + if dubboService.Attributes.Labels != nil { + for k, v := range dubboService.Attributes.Labels { + istioService.Labels[k] = v + } + } + + klog.V(4).Infof("Converted Dubbo service %s to Istio service with %d ports", + dubboService.Hostname, len(ports)) + + return istioService, nil +} + +// applyVirtualServiceConfig applies VirtualService configuration to the Dubbo service +func (sc *ServiceConverter) applyVirtualServiceConfig(dubboService *model.Service, vs *VirtualService) { + if vs == nil || vs.Spec == nil { + return + } + + // Add VirtualService metadata to labels + dubboService.Attributes.Labels["istio.io/virtual-service"] = vs.Name + dubboService.Attributes.Labels["istio.io/virtual-service-namespace"] = vs.Namespace + + // Process routing rules if available + if http, ok := vs.Spec["http"].([]interface{}); ok { + sc.processHTTPRoutes(dubboService, http) + } + + if tcp, ok := vs.Spec["tcp"].([]interface{}); ok { + sc.processTCPRoutes(dubboService, tcp) + } + + klog.V(5).Infof("Applied VirtualService %s/%s configuration to service %s", + vs.Namespace, vs.Name, dubboService.Hostname) +} + +// applyDestinationRuleConfig applies DestinationRule configuration to the Dubbo service +func (sc *ServiceConverter) applyDestinationRuleConfig(dubboService *model.Service, dr *DestinationRule) { + if dr == nil || dr.Spec == nil { + return + } + + // Add DestinationRule metadata to labels + dubboService.Attributes.Labels["istio.io/destination-rule"] = dr.Name + dubboService.Attributes.Labels["istio.io/destination-rule-namespace"] = dr.Namespace + + // Process load balancing configuration + if trafficPolicy, ok := dr.Spec["trafficPolicy"].(map[string]interface{}); ok { + sc.processTrafficPolicy(dubboService, trafficPolicy) + } + + // Process subsets configuration + if subsets, ok := dr.Spec["subsets"].([]interface{}); ok { + sc.processSubsets(dubboService, subsets) + } + + klog.V(5).Infof("Applied DestinationRule %s/%s configuration to service %s", + dr.Namespace, dr.Name, dubboService.Hostname) +} + +// processHTTPRoutes processes HTTP routing rules from VirtualService +func (sc *ServiceConverter) processHTTPRoutes(dubboService *model.Service, routes []interface{}) { + for i, route := range routes { + if routeMap, ok := route.(map[string]interface{}); ok { + routeKey := fmt.Sprintf("istio.io/http-route-%d", i) + + // Process match conditions + if match, ok := routeMap["match"].([]interface{}); ok && len(match) > 0 { + dubboService.Attributes.Labels[routeKey+"-match"] = "true" + } + + // Process route destinations + if routeDestinations, ok := routeMap["route"].([]interface{}); ok { + dubboService.Attributes.Labels[routeKey+"-destinations"] = strconv.Itoa(len(routeDestinations)) + } + } + } +} + +// processTCPRoutes processes TCP routing rules from VirtualService +func (sc *ServiceConverter) processTCPRoutes(dubboService *model.Service, routes []interface{}) { + for i, route := range routes { + if routeMap, ok := route.(map[string]interface{}); ok { + routeKey := fmt.Sprintf("istio.io/tcp-route-%d", i) + + // Process route destinations + if routeDestinations, ok := routeMap["route"].([]interface{}); ok { + dubboService.Attributes.Labels[routeKey+"-destinations"] = strconv.Itoa(len(routeDestinations)) + } + } + } +} + +// processTrafficPolicy processes traffic policy from DestinationRule +func (sc *ServiceConverter) processTrafficPolicy(dubboService *model.Service, policy map[string]interface{}) { + // Process load balancer settings + if lb, ok := policy["loadBalancer"].(map[string]interface{}); ok { + if simple, ok := lb["simple"].(string); ok { + dubboService.Attributes.Labels["istio.io/load-balancer"] = simple + } + } + + // Process connection pool settings + if cp, ok := policy["connectionPool"].(map[string]interface{}); ok { + dubboService.Attributes.Labels["istio.io/connection-pool"] = "configured" + + if tcp, ok := cp["tcp"].(map[string]interface{}); ok { + if maxConnections, ok := tcp["maxConnections"].(float64); ok { + dubboService.Attributes.Labels["istio.io/max-connections"] = strconv.Itoa(int(maxConnections)) + } + } + } + + // Process outlier detection settings + if _, ok := policy["outlierDetection"].(map[string]interface{}); ok { + dubboService.Attributes.Labels["istio.io/outlier-detection"] = "enabled" + } +} + +// processSubsets processes subsets from DestinationRule +func (sc *ServiceConverter) processSubsets(dubboService *model.Service, subsets []interface{}) { + dubboService.Attributes.Labels["istio.io/subsets-count"] = strconv.Itoa(len(subsets)) + + for i, subset := range subsets { + if subsetMap, ok := subset.(map[string]interface{}); ok { + subsetKey := fmt.Sprintf("istio.io/subset-%d", i) + + if name, ok := subsetMap["name"].(string); ok { + dubboService.Attributes.Labels[subsetKey+"-name"] = name + } + + if labels, ok := subsetMap["labels"].(map[string]interface{}); ok { + dubboService.Attributes.Labels[subsetKey+"-labels-count"] = strconv.Itoa(len(labels)) + } + } + } +} diff --git a/sail/pkg/serviceregistry/istio/istio_test.go b/sail/pkg/serviceregistry/istio/istio_test.go new file mode 100644 index 000000000..3e6a30937 --- /dev/null +++ b/sail/pkg/serviceregistry/istio/istio_test.go @@ -0,0 +1,161 @@ +package istio + +import ( + "testing" + "time" + + "github.com/apache/dubbo-kubernetes/pkg/cluster" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDefaultConfig(t *testing.T) { + config := DefaultConfig() + + assert.NotNil(t, config) + assert.Equal(t, "istiod.istio-system.svc.cluster.local:15010", config.PilotAddress) + assert.Equal(t, "istio-system", config.Namespace) + assert.True(t, config.EnableDiscovery) + assert.True(t, config.TLSEnabled) + assert.Equal(t, 30*time.Second, config.SyncTimeout) +} + +func TestNewConfigManager(t *testing.T) { + manager := NewConfigManager() + + assert.NotNil(t, manager) + assert.NotNil(t, manager.config) +} + +func TestConfigManager_LoadFromEnvironment(t *testing.T) { + manager := NewConfigManager() + + // Test with default environment + err := manager.LoadFromEnvironment() + assert.NoError(t, err) + + config := manager.GetConfig() + assert.NotNil(t, config) + assert.Equal(t, "istiod.istio-system.svc.cluster.local:15010", config.PilotAddress) +} + +func TestConfigManager_LoadFromConfig(t *testing.T) { + manager := NewConfigManager() + + // Test with valid config (no TLS paths to avoid file checks) + config := &IstioConfig{ + PilotAddress: "test.example.com:15010", + Namespace: "test-namespace", + EnableDiscovery: true, + TLSEnabled: false, // Disable TLS to avoid cert file checks + CertPath: "", + KeyPath: "", + CAPath: "", + SyncTimeout: 10 * time.Second, + } + + err := manager.LoadFromConfig(config) + assert.NoError(t, err) + + loadedConfig := manager.GetConfig() + assert.Equal(t, config.PilotAddress, loadedConfig.PilotAddress) + assert.Equal(t, config.Namespace, loadedConfig.Namespace) + assert.Equal(t, config.TLSEnabled, loadedConfig.TLSEnabled) +} + +func TestNewController(t *testing.T) { + config := DefaultConfig() + clusterID := cluster.ID("test-cluster") + + controller := NewController(config, clusterID) + + assert.NotNil(t, controller) + assert.Equal(t, config, controller.config) + assert.Equal(t, clusterID, controller.clusterID) + assert.NotNil(t, controller.pilotClient) + assert.NotNil(t, controller.serviceConverter) + assert.NotNil(t, controller.configManager) + assert.NotNil(t, controller.services) + assert.False(t, controller.hasSynced.Load()) +} + +func TestController_Provider(t *testing.T) { + config := DefaultConfig() + clusterID := cluster.ID("test-cluster") + controller := NewController(config, clusterID) + + providerType := controller.Provider() + assert.Equal(t, "Istio", string(providerType)) +} + +func TestServiceConverter_New(t *testing.T) { + converter := NewServiceConverter("test-cluster") + + assert.NotNil(t, converter) + assert.Equal(t, "test-cluster", converter.clusterID) +} + +func TestServiceConverter_ConvertToDubboService(t *testing.T) { + converter := NewServiceConverter("test-cluster") + + istioService := &IstioServiceInfo{ + Hostname: "test.example.com", + Namespace: "default", + Ports: []IstioPortInfo{{Name: "http", Port: 8080, Protocol: "HTTP"}}, + Labels: map[string]string{"app": "test"}, + VirtualService: &VirtualService{ + Name: "test-vs", + Namespace: "default", + Spec: map[string]interface{}{"weight": 100}, + }, + DestinationRule: &DestinationRule{ + Name: "test-dr", + Namespace: "default", + Spec: map[string]interface{}{"subset": "v1"}, + }, + } + + dubboService, err := converter.ConvertToDubboService(istioService) + assert.NoError(t, err) + assert.NotNil(t, dubboService) + assert.Equal(t, "test.example.com", string(dubboService.Hostname)) + assert.Equal(t, "default", dubboService.Attributes.Namespace) + assert.Len(t, dubboService.Ports, 1) + assert.Equal(t, "http", dubboService.Ports[0].Name) + assert.Equal(t, 8080, dubboService.Ports[0].Port) +} + +func TestServiceConverter_ConvertToDubboService_Nil(t *testing.T) { + converter := NewServiceConverter("test-cluster") + + dubboService, err := converter.ConvertToDubboService(nil) + assert.Error(t, err) + assert.Nil(t, dubboService) + assert.Contains(t, err.Error(), "istio service cannot be nil") +} + +func BenchmarkServiceConverter_ConvertToDubboService(b *testing.B) { + converter := NewServiceConverter("test-cluster") + istioService := &IstioServiceInfo{ + Hostname: "test.example.com", + Namespace: "default", + Ports: []IstioPortInfo{{Name: "http", Port: 8080, Protocol: "HTTP"}}, + Labels: map[string]string{"app": "test"}, + VirtualService: &VirtualService{ + Name: "test-vs", + Namespace: "default", + Spec: map[string]interface{}{"weight": 100}, + }, + DestinationRule: &DestinationRule{ + Name: "test-dr", + Namespace: "default", + Spec: map[string]interface{}{"subset": "v1"}, + }, + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := converter.ConvertToDubboService(istioService) + require.NoError(b, err) + } +} diff --git a/sail/pkg/serviceregistry/istio/types.go b/sail/pkg/serviceregistry/istio/types.go new file mode 100644 index 000000000..6734022c3 --- /dev/null +++ b/sail/pkg/serviceregistry/istio/types.go @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Package istio provides Istio service mesh integration for Dubbo service registry. +// It implements the service registry interface to discover and manage services +// through Istio's Pilot discovery service using xDS protocol. +package istio + +import ( + "time" +) + +// IstioConfig holds configuration for Istio service registry +type IstioConfig struct { + PilotAddress string `json:"pilotAddress"` + Namespace string `json:"namespace"` + EnableDiscovery bool `json:"enableDiscovery"` + TLSEnabled bool `json:"tlsEnabled"` + CertPath string `json:"certPath"` + KeyPath string `json:"keyPath"` + CAPath string `json:"caPath"` + SyncTimeout time.Duration `json:"syncTimeout"` +} + +// IstioServiceInfo represents service information from Istio +type IstioServiceInfo struct { + Hostname string `json:"hostname"` + Namespace string `json:"namespace"` + Ports []IstioPortInfo `json:"ports"` + Labels map[string]string `json:"labels"` + Endpoints []IstioEndpoint `json:"endpoints"` + VirtualService *VirtualService `json:"virtualService,omitempty"` + DestinationRule *DestinationRule `json:"destinationRule,omitempty"` +} + +// IstioPortInfo represents port information for Istio services +type IstioPortInfo struct { + Name string `json:"name"` + Port int `json:"port"` + Protocol string `json:"protocol"` +} + +// IstioEndpoint represents an endpoint for Istio services +type IstioEndpoint struct { + Address string `json:"address"` + Port int `json:"port"` + Labels map[string]string `json:"labels"` +} + +// VirtualService represents Istio VirtualService configuration +type VirtualService struct { + Name string `json:"name"` + Namespace string `json:"namespace"` + Spec map[string]interface{} `json:"spec"` +} + +// DestinationRule represents Istio DestinationRule configuration +type DestinationRule struct { + Name string `json:"name"` + Namespace string `json:"namespace"` + Spec map[string]interface{} `json:"spec"` +} + +// PilotClient defines the interface for communicating with Istio Pilot +type PilotClient interface { + Connect() error + Disconnect() error + WatchServices(namespace string, handler ServiceEventHandler) error + GetService(hostname string) (*IstioServiceInfo, error) + IsConnected() bool +} + +// ServiceEventHandler defines the interface for handling service events +type ServiceEventHandler interface { + OnServiceAdd(service *IstioServiceInfo) + OnServiceUpdate(oldService, newService *IstioServiceInfo) + OnServiceDelete(service *IstioServiceInfo) +} + +// DefaultConfig returns default Istio configuration +func DefaultConfig() *IstioConfig { + return &IstioConfig{ + PilotAddress: "istiod.istio-system.svc.cluster.local:15010", + Namespace: "istio-system", + EnableDiscovery: true, + TLSEnabled: true, + SyncTimeout: 30 * time.Second, + } +} diff --git a/sail/pkg/serviceregistry/provider/provider.go b/sail/pkg/serviceregistry/provider/provider.go index 5204a67b4..7ea4dbb68 100644 --- a/sail/pkg/serviceregistry/provider/provider.go +++ b/sail/pkg/serviceregistry/provider/provider.go @@ -29,6 +29,8 @@ const ( Nacos ID = "Nacos" // Zookeeper is a service registry backed by Zookeeper server Zookeeper ID = "Zookeeper" + // Istio is a service registry backed by Istio service mesh + Istio ID = "Istio" ) func (id ID) String() string { diff --git a/sail/pkg/xds/discovery.go b/sail/pkg/xds/discovery.go index a4c58ed81..89885628a 100644 --- a/sail/pkg/xds/discovery.go +++ b/sail/pkg/xds/discovery.go @@ -18,15 +18,18 @@ package xds import ( + "time" + "github.com/apache/dubbo-kubernetes/pkg/cluster" "github.com/apache/dubbo-kubernetes/pkg/kube/krt" "github.com/apache/dubbo-kubernetes/sail/pkg/model" + "github.com/apache/dubbo-kubernetes/sail/pkg/serviceregistry/provider" discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" "go.uber.org/atomic" "golang.org/x/time/rate" "google.golang.org/grpc" + "google.golang.org/protobuf/types/known/anypb" "k8s.io/klog/v2" - "time" ) type DiscoveryServer struct { @@ -73,3 +76,72 @@ func (s *DiscoveryServer) Shutdown() { func (s *DiscoveryServer) globalPushContext() *model.PushContext { return s.Env.PushContext() } + +// Istio xDS support methods + +// HandleIstioServiceDiscovery processes Istio service discovery requests +func (s *DiscoveryServer) HandleIstioServiceDiscovery(req *discovery.DiscoveryRequest) (*discovery.DiscoveryResponse, error) { + klog.V(4).Infof("Handling Istio service discovery request for type: %s", req.TypeUrl) + + // Get services from all registries including Istio + services := s.Env.ServiceDiscovery.Services() + + // Filter services from Istio registry + istioServices := make([]*model.Service, 0) + for _, service := range services { + if service.Attributes.ServiceRegistry == provider.Istio { + istioServices = append(istioServices, service) + } + } + + // Convert to xDS resources - for now, return empty resources + // TODO: Convert Dubbo service to xDS resource format + // This would involve creating proper Envoy resources + for _, service := range istioServices { + klog.V(4).Infof("Processing Istio service: %s", service.Hostname) + } + + return &discovery.DiscoveryResponse{ + VersionInfo: "1.0", + Resources: make([]*anypb.Any, 0), + TypeUrl: req.TypeUrl, + Nonce: req.ResponseNonce, + }, nil +} + +// GetIstioServices returns all services from Istio service registry +func (s *DiscoveryServer) GetIstioServices() []*model.Service { + services := s.Env.ServiceDiscovery.Services() + istioServices := make([]*model.Service, 0) + + for _, service := range services { + if service.Attributes.ServiceRegistry == provider.Istio { + istioServices = append(istioServices, service) + } + } + + return istioServices +} + +// PushIstioConfiguration pushes Istio configuration updates to connected clients +func (s *DiscoveryServer) PushIstioConfiguration(event *model.PushRequest) { + if event == nil { + klog.V(4).Info("Received nil push request for Istio configuration") + return + } + + klog.V(4).Infof("Pushing Istio configuration update: %v", event.Reason) + + // Add to push queue directly + if s.pushQueue != nil { + s.pushQueue.Enqueue(nil, event) + } +} + +// ValidateIstioConfig validates Istio configuration resources +func (s *DiscoveryServer) ValidateIstioConfig(config interface{}) error { + // TODO: Implement Istio configuration validation + // This would validate VirtualService, DestinationRule, etc. + klog.V(4).Info("Validating Istio configuration") + return nil +} From 8373b09b7d788bd243aa60d252653489e241f83c Mon Sep 17 00:00:00 2001 From: liwener Date: Mon, 29 Sep 2025 22:40:08 +0800 Subject: [PATCH 6/7] [chore]: fix a series of bugs, add ASF headers --- examples/istio-service-registry.yaml | 17 +++++++++ pkg/adsc/adsc.go | 36 +++++++++++++++---- pkg/adsc/util.go | 17 +++++++++ pkg/bootstrap/config.go | 17 +++++++++ pkg/config/protocol/instance.go | 17 +++++++++ pkg/config/schema/collection/schemas.go | 17 +++++++++ pkg/config/schema/collections/collections.go | 17 +++++++++ pkg/config/schema/kind/kind.go | 17 +++++++++ pkg/config/schema/kind/resource.go | 17 +++++++++ pkg/config/schema/resource/schema.go | 17 +++++++++ pkg/config/visibility/visibility.go | 17 +++++++++ pkg/dubbo-agent/config/config.go | 17 +++++++++ pkg/dubbo-agent/grpcxds/grpc_bootstrap.go | 17 +++++++++ pkg/dubbo-agent/plugins.go | 17 +++++++++ pkg/dubbo-agent/xds_proxy.go | 17 +++++++++ pkg/features/security.go | 17 +++++++++ pkg/file/fadvise_linux.go | 19 ++++++++++ pkg/file/fadvise_unspecified.go | 17 +++++++++ pkg/file/file.go | 17 +++++++++ pkg/jwt/jwt.go | 17 +++++++++ pkg/kube/krt/static.go | 17 +++++++++ pkg/model/fips.go | 17 +++++++++ pkg/model/xds.go | 17 +++++++++ pkg/network/id.go | 17 +++++++++ pkg/queue/delay.go | 17 +++++++++ pkg/security/retry.go | 17 +++++++++ pkg/uds/listener.go | 17 +++++++++ pkg/wellknown/wellknown.go | 17 +++++++++ sail/cmd/sail-agent/options/agent.go | 17 +++++++++ sail/cmd/sail-agent/options/options.go | 17 +++++++++ sail/cmd/sail-agent/options/security.go | 17 +++++++++ sail/configs/multi-cluster.yaml | 19 +++++++++- sail/configs/multi-registry.yaml | 19 +++++++++- sail/pkg/config/aggregate/config.go | 17 +++++++++ sail/pkg/config/kube/crd/config.go | 17 +++++++++ sail/pkg/config/kube/crd/conversion.go | 17 +++++++++ sail/pkg/config/kube/file/controller.go | 17 +++++++++ sail/pkg/config/memory/controller.go | 17 +++++++++ sail/pkg/config/memory/monitor.go | 17 +++++++++ sail/pkg/config/memory/store.go | 17 +++++++++ sail/pkg/credentials/kube/secrets.go | 17 +++++++++ sail/pkg/credentials/model.go | 17 +++++++++ sail/pkg/grpc/grpc.go | 17 +++++++++ sail/pkg/grpc/tls.go | 17 +++++++++ sail/pkg/model/addressmap.go | 17 +++++++++ sail/pkg/model/authentication.go | 17 +++++++++ sail/pkg/model/authorization.go | 17 +++++++++ sail/pkg/model/cluster_local.go | 17 +++++++++ sail/pkg/model/config.go | 17 +++++++++ sail/pkg/model/controller.go | 17 +++++++++ sail/pkg/model/network.go | 17 +++++++++ sail/pkg/model/push_context.go | 3 +- sail/pkg/model/service.go | 20 +++++++---- sail/pkg/networking/util/util.go | 19 +++++++++- .../serviceregistry/aggregate/controller.go | 17 +++++++++ sail/pkg/serviceregistry/instance.go | 18 ++++++++++ .../kube/controller/controller_test.go | 18 ++++++++++ .../pkg/serviceregistry/kube/errors/errors.go | 18 ++++++++++ sail/pkg/xds/ads.go | 9 ++--- sail/pkg/xds/auth.go | 18 ++++++++++ sail/pkg/xds/delta.go | 18 +++++++++- sail/pkg/xds/v3/model.go | 18 ++++++++++ security/pkg/credentialfetcher/fetcher.go | 18 ++++++++++ .../pkg/credentialfetcher/plugin/token.go | 18 ++++++++++ security/pkg/nodeagent/cache/secretcache.go | 18 ++++++++++ .../pkg/nodeagent/caclient/credentials.go | 18 ++++++++++ .../caclient/providers/aegis/client.go | 18 ++++++++++ security/pkg/nodeagent/sds/sdsservice.go | 18 ++++++++++ security/pkg/nodeagent/sds/server.go | 18 ++++++++++ 69 files changed, 1172 insertions(+), 22 deletions(-) diff --git a/examples/istio-service-registry.yaml b/examples/istio-service-registry.yaml index 9151d6033..45b128c9a 100644 --- a/examples/istio-service-registry.yaml +++ b/examples/istio-service-registry.yaml @@ -1,3 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + # Istio Service Registry Configuration Example # This example demonstrates how to configure Dubbo-Kubernetes to use Istio as a service registry diff --git a/pkg/adsc/adsc.go b/pkg/adsc/adsc.go index 16282d672..686649d68 100644 --- a/pkg/adsc/adsc.go +++ b/pkg/adsc/adsc.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package adsc import ( @@ -5,6 +22,13 @@ import ( "crypto/tls" "crypto/x509" "fmt" + "math" + "net" + "os" + "strings" + "sync" + "time" + "github.com/apache/dubbo-kubernetes/pkg/backoff" "github.com/apache/dubbo-kubernetes/pkg/config" "github.com/apache/dubbo-kubernetes/pkg/config/schema/collections" @@ -30,12 +54,6 @@ import ( mcp "istio.io/api/mcp/v1alpha1" "istio.io/api/mesh/v1alpha1" "k8s.io/klog/v2" - "math" - "net" - "os" - "strings" - "sync" - "time" ) const ( @@ -756,7 +774,11 @@ func (a *ADSC) handleLDS(ll []*listener.Listener) { routes = append(routes, fmt.Sprintf("%d", port)) } default: - klog.Infof(protomarshal.ToJSONWithIndent(l, " ")) + if jsonStr, err := protomarshal.ToJSONWithIndent(l, " "); err == nil { + klog.Infof("Listener config: %s", jsonStr) + } else { + klog.Errorf("Failed to marshal to JSON: %v", err) + } } } diff --git a/pkg/adsc/util.go b/pkg/adsc/util.go index 48945b879..add355fea 100644 --- a/pkg/adsc/util.go +++ b/pkg/adsc/util.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package adsc import ( diff --git a/pkg/bootstrap/config.go b/pkg/bootstrap/config.go index 730d395e8..09c5fdc1e 100644 --- a/pkg/bootstrap/config.go +++ b/pkg/bootstrap/config.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package bootstrap import ( diff --git a/pkg/config/protocol/instance.go b/pkg/config/protocol/instance.go index 3748f9194..4bb36c683 100644 --- a/pkg/config/protocol/instance.go +++ b/pkg/config/protocol/instance.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package protocol import "strings" diff --git a/pkg/config/schema/collection/schemas.go b/pkg/config/schema/collection/schemas.go index d5cadbf2d..923a9237c 100644 --- a/pkg/config/schema/collection/schemas.go +++ b/pkg/config/schema/collection/schemas.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package collection import ( diff --git a/pkg/config/schema/collections/collections.go b/pkg/config/schema/collections/collections.go index d38deddd2..089a4ebc2 100644 --- a/pkg/config/schema/collections/collections.go +++ b/pkg/config/schema/collections/collections.go @@ -1,6 +1,23 @@ //go:build !agent // +build !agent +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package collections import ( diff --git a/pkg/config/schema/kind/kind.go b/pkg/config/schema/kind/kind.go index 6c29e6ef8..c8ccb426a 100644 --- a/pkg/config/schema/kind/kind.go +++ b/pkg/config/schema/kind/kind.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package kind type Kind uint8 diff --git a/pkg/config/schema/kind/resource.go b/pkg/config/schema/kind/resource.go index fdbb42ac3..3c45e1a86 100644 --- a/pkg/config/schema/kind/resource.go +++ b/pkg/config/schema/kind/resource.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package kind const ( diff --git a/pkg/config/schema/resource/schema.go b/pkg/config/schema/resource/schema.go index 5012b662b..ac91ae3d3 100644 --- a/pkg/config/schema/resource/schema.go +++ b/pkg/config/schema/resource/schema.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package resource import ( diff --git a/pkg/config/visibility/visibility.go b/pkg/config/visibility/visibility.go index 49fe4f6d4..5e06e1d7b 100644 --- a/pkg/config/visibility/visibility.go +++ b/pkg/config/visibility/visibility.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package visibility import ( diff --git a/pkg/dubbo-agent/config/config.go b/pkg/dubbo-agent/config/config.go index 1bd21a368..1db92f566 100644 --- a/pkg/dubbo-agent/config/config.go +++ b/pkg/dubbo-agent/config/config.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package config import ( diff --git a/pkg/dubbo-agent/grpcxds/grpc_bootstrap.go b/pkg/dubbo-agent/grpcxds/grpc_bootstrap.go index 6dc2df0f4..cb4a53475 100644 --- a/pkg/dubbo-agent/grpcxds/grpc_bootstrap.go +++ b/pkg/dubbo-agent/grpcxds/grpc_bootstrap.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package grpcxds import ( diff --git a/pkg/dubbo-agent/plugins.go b/pkg/dubbo-agent/plugins.go index 29ea6cd11..4cccdf78c 100644 --- a/pkg/dubbo-agent/plugins.go +++ b/pkg/dubbo-agent/plugins.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package dubboagent import ( diff --git a/pkg/dubbo-agent/xds_proxy.go b/pkg/dubbo-agent/xds_proxy.go index e90426ae6..3b37e2fa6 100644 --- a/pkg/dubbo-agent/xds_proxy.go +++ b/pkg/dubbo-agent/xds_proxy.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package dubboagent import ( diff --git a/pkg/features/security.go b/pkg/features/security.go index 778a67df1..e60549867 100644 --- a/pkg/features/security.go +++ b/pkg/features/security.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package features import ( diff --git a/pkg/file/fadvise_linux.go b/pkg/file/fadvise_linux.go index fb4fdcaa1..cc2937571 100644 --- a/pkg/file/fadvise_linux.go +++ b/pkg/file/fadvise_linux.go @@ -1,3 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//go:build linux + package file import ( diff --git a/pkg/file/fadvise_unspecified.go b/pkg/file/fadvise_unspecified.go index 292966f85..0c23126df 100644 --- a/pkg/file/fadvise_unspecified.go +++ b/pkg/file/fadvise_unspecified.go @@ -1,6 +1,23 @@ //go:build !linux // +build !linux +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package file import ( diff --git a/pkg/file/file.go b/pkg/file/file.go index 148e72614..9a379c6cc 100644 --- a/pkg/file/file.go +++ b/pkg/file/file.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package file import ( diff --git a/pkg/jwt/jwt.go b/pkg/jwt/jwt.go index 9d27495b0..8185bcf57 100644 --- a/pkg/jwt/jwt.go +++ b/pkg/jwt/jwt.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package jwt const ( diff --git a/pkg/kube/krt/static.go b/pkg/kube/krt/static.go index 8698c8522..f891fac40 100644 --- a/pkg/kube/krt/static.go +++ b/pkg/kube/krt/static.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package krt import ( diff --git a/pkg/model/fips.go b/pkg/model/fips.go index 10a41cbab..0d98a1938 100644 --- a/pkg/model/fips.go +++ b/pkg/model/fips.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package model import ( diff --git a/pkg/model/xds.go b/pkg/model/xds.go index 72d334d34..70623c3b8 100644 --- a/pkg/model/xds.go +++ b/pkg/model/xds.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package model const ( diff --git a/pkg/network/id.go b/pkg/network/id.go index 23291ab36..34f26639f 100644 --- a/pkg/network/id.go +++ b/pkg/network/id.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package network import "github.com/apache/dubbo-kubernetes/pkg/util/identifier" diff --git a/pkg/queue/delay.go b/pkg/queue/delay.go index 171597a38..1d1f640cd 100644 --- a/pkg/queue/delay.go +++ b/pkg/queue/delay.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package queue import ( diff --git a/pkg/security/retry.go b/pkg/security/retry.go index e16069c1d..ff048ac29 100644 --- a/pkg/security/retry.go +++ b/pkg/security/retry.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package security import ( diff --git a/pkg/uds/listener.go b/pkg/uds/listener.go index 9936711f4..a4b0f924e 100644 --- a/pkg/uds/listener.go +++ b/pkg/uds/listener.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package uds import ( diff --git a/pkg/wellknown/wellknown.go b/pkg/wellknown/wellknown.go index 12d50e165..c06a49d4b 100644 --- a/pkg/wellknown/wellknown.go +++ b/pkg/wellknown/wellknown.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package wellknown // Package wellknown contains common names for filters, listeners, etc. diff --git a/sail/cmd/sail-agent/options/agent.go b/sail/cmd/sail-agent/options/agent.go index ae487025f..e68f12db1 100644 --- a/sail/cmd/sail-agent/options/agent.go +++ b/sail/cmd/sail-agent/options/agent.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package options import ( diff --git a/sail/cmd/sail-agent/options/options.go b/sail/cmd/sail-agent/options/options.go index 6f4bdd3a3..257ed2bb9 100644 --- a/sail/cmd/sail-agent/options/options.go +++ b/sail/cmd/sail-agent/options/options.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package options import ( diff --git a/sail/cmd/sail-agent/options/security.go b/sail/cmd/sail-agent/options/security.go index 2e1322fda..74132d908 100644 --- a/sail/cmd/sail-agent/options/security.go +++ b/sail/cmd/sail-agent/options/security.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package options import ( diff --git a/sail/configs/multi-cluster.yaml b/sail/configs/multi-cluster.yaml index 635acda71..d1a2e35ea 100644 --- a/sail/configs/multi-cluster.yaml +++ b/sail/configs/multi-cluster.yaml @@ -7,7 +7,24 @@ metadata: name: multi-cluster-config namespace: dubbo-system -# Multi-cluster configuration +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Multi-cluster configuration for Dubbo Service Registry multiCluster: enabled: true clusters: diff --git a/sail/configs/multi-registry.yaml b/sail/configs/multi-registry.yaml index 7cc9d909e..4360843b4 100644 --- a/sail/configs/multi-registry.yaml +++ b/sail/configs/multi-registry.yaml @@ -7,7 +7,24 @@ metadata: name: multi-registry-config namespace: dubbo-system -# Multi-registry configuration +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Multi-registry configuration for Dubbo Service Registry multiRegistry: enabled: true registries: diff --git a/sail/pkg/config/aggregate/config.go b/sail/pkg/config/aggregate/config.go index 8a8478ac7..43f7697b7 100644 --- a/sail/pkg/config/aggregate/config.go +++ b/sail/pkg/config/aggregate/config.go @@ -1,4 +1,21 @@ // Package aggregate implements a read-only aggregator for config stores. +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package aggregate import ( diff --git a/sail/pkg/config/kube/crd/config.go b/sail/pkg/config/kube/crd/config.go index 7b9630136..d9af3d1e3 100644 --- a/sail/pkg/config/kube/crd/config.go +++ b/sail/pkg/config/kube/crd/config.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package crd import ( diff --git a/sail/pkg/config/kube/crd/conversion.go b/sail/pkg/config/kube/crd/conversion.go index 66b12cb7d..18c8be578 100644 --- a/sail/pkg/config/kube/crd/conversion.go +++ b/sail/pkg/config/kube/crd/conversion.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package crd import ( diff --git a/sail/pkg/config/kube/file/controller.go b/sail/pkg/config/kube/file/controller.go index 1a4ffa2ab..a04929233 100644 --- a/sail/pkg/config/kube/file/controller.go +++ b/sail/pkg/config/kube/file/controller.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package file import ( diff --git a/sail/pkg/config/memory/controller.go b/sail/pkg/config/memory/controller.go index 5119bb6af..28e0f6125 100644 --- a/sail/pkg/config/memory/controller.go +++ b/sail/pkg/config/memory/controller.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package memory import ( diff --git a/sail/pkg/config/memory/monitor.go b/sail/pkg/config/memory/monitor.go index 49daad078..805a63a0c 100644 --- a/sail/pkg/config/memory/monitor.go +++ b/sail/pkg/config/memory/monitor.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package memory import ( diff --git a/sail/pkg/config/memory/store.go b/sail/pkg/config/memory/store.go index f36cde5e1..a34f1c90a 100644 --- a/sail/pkg/config/memory/store.go +++ b/sail/pkg/config/memory/store.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package memory import ( diff --git a/sail/pkg/credentials/kube/secrets.go b/sail/pkg/credentials/kube/secrets.go index 191d24846..2be7687f0 100644 --- a/sail/pkg/credentials/kube/secrets.go +++ b/sail/pkg/credentials/kube/secrets.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package kube import ( diff --git a/sail/pkg/credentials/model.go b/sail/pkg/credentials/model.go index f806593d9..093f8d674 100644 --- a/sail/pkg/credentials/model.go +++ b/sail/pkg/credentials/model.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package credentials // CertInfo wraps a certificate, key, and oscp staple information. diff --git a/sail/pkg/grpc/grpc.go b/sail/pkg/grpc/grpc.go index 3bf677e4b..7270d400c 100644 --- a/sail/pkg/grpc/grpc.go +++ b/sail/pkg/grpc/grpc.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package grpc import ( diff --git a/sail/pkg/grpc/tls.go b/sail/pkg/grpc/tls.go index 13078451b..8fc16f259 100644 --- a/sail/pkg/grpc/tls.go +++ b/sail/pkg/grpc/tls.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package grpc import ( diff --git a/sail/pkg/model/addressmap.go b/sail/pkg/model/addressmap.go index c8b166f36..c1bab2781 100644 --- a/sail/pkg/model/addressmap.go +++ b/sail/pkg/model/addressmap.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package model import ( diff --git a/sail/pkg/model/authentication.go b/sail/pkg/model/authentication.go index f1c421afd..489e46e8d 100644 --- a/sail/pkg/model/authentication.go +++ b/sail/pkg/model/authentication.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package model import ( diff --git a/sail/pkg/model/authorization.go b/sail/pkg/model/authorization.go index 9789ac328..5ee167e76 100644 --- a/sail/pkg/model/authorization.go +++ b/sail/pkg/model/authorization.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package model import ( diff --git a/sail/pkg/model/cluster_local.go b/sail/pkg/model/cluster_local.go index 9bb6b4292..a8681871e 100644 --- a/sail/pkg/model/cluster_local.go +++ b/sail/pkg/model/cluster_local.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package model import "github.com/apache/dubbo-kubernetes/pkg/config/host" diff --git a/sail/pkg/model/config.go b/sail/pkg/model/config.go index 6619a5d92..1afbb20f3 100644 --- a/sail/pkg/model/config.go +++ b/sail/pkg/model/config.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package model import ( diff --git a/sail/pkg/model/controller.go b/sail/pkg/model/controller.go index 72e2dd102..4f3dee45d 100644 --- a/sail/pkg/model/controller.go +++ b/sail/pkg/model/controller.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package model type Controller interface { diff --git a/sail/pkg/model/network.go b/sail/pkg/model/network.go index 66f7ce2cc..6bd81ac5f 100644 --- a/sail/pkg/model/network.go +++ b/sail/pkg/model/network.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package model type NetworkManager struct { diff --git a/sail/pkg/model/push_context.go b/sail/pkg/model/push_context.go index 4e9418443..7a3ae73ee 100644 --- a/sail/pkg/model/push_context.go +++ b/sail/pkg/model/push_context.go @@ -18,6 +18,8 @@ package model import ( + "sync" + "github.com/apache/dubbo-kubernetes/pkg/config" "github.com/apache/dubbo-kubernetes/pkg/config/host" "github.com/apache/dubbo-kubernetes/pkg/config/schema/kind" @@ -26,7 +28,6 @@ import ( "go.uber.org/atomic" meshconfig "istio.io/api/mesh/v1alpha1" "k8s.io/apimachinery/pkg/types" - "sync" ) type PushContext struct { diff --git a/sail/pkg/model/service.go b/sail/pkg/model/service.go index fe3ca352f..33e81afc4 100644 --- a/sail/pkg/model/service.go +++ b/sail/pkg/model/service.go @@ -74,9 +74,14 @@ type Service struct { } func (s *Service) DeepCopy() *Service { - // nolint: govet - out := *s - out.Attributes = s.Attributes.DeepCopy() + if s == nil { + return nil + } + out := &Service{ + Attributes: s.Attributes.DeepCopy(), + Hostname: s.Hostname, + CreationTime: s.CreationTime, + } if s.Ports != nil { out.Ports = make(PortList, len(s.Ports)) for i, port := range s.Ports { @@ -91,10 +96,13 @@ func (s *Service) DeepCopy() *Service { } } } - out.ServiceAccounts = slices.Clone(s.ServiceAccounts) - out.ClusterVIPs = *s.ClusterVIPs.DeepCopy() - return &out + if s.ClusterVIPs.Addresses != nil { + out.ClusterVIPs = *s.ClusterVIPs.DeepCopy() + } else { + out.ClusterVIPs = AddressMap{} + } + return out } func (s *Service) Key() string { diff --git a/sail/pkg/networking/util/util.go b/sail/pkg/networking/util/util.go index 471493b00..446bff2ad 100644 --- a/sail/pkg/networking/util/util.go +++ b/sail/pkg/networking/util/util.go @@ -1,6 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package util const ( // PassthroughFilterChain to catch traffic that doesn't match other filter chains. PassthroughFilterChain = "PassthroughFilterChain" -) \ No newline at end of file +) diff --git a/sail/pkg/serviceregistry/aggregate/controller.go b/sail/pkg/serviceregistry/aggregate/controller.go index 149201273..6bca94991 100644 --- a/sail/pkg/serviceregistry/aggregate/controller.go +++ b/sail/pkg/serviceregistry/aggregate/controller.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package aggregate import ( diff --git a/sail/pkg/serviceregistry/instance.go b/sail/pkg/serviceregistry/instance.go index f08a0bbb3..369b962bf 100644 --- a/sail/pkg/serviceregistry/instance.go +++ b/sail/pkg/serviceregistry/instance.go @@ -1,3 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + package serviceregistry import ( diff --git a/sail/pkg/serviceregistry/kube/controller/controller_test.go b/sail/pkg/serviceregistry/kube/controller/controller_test.go index adefda1f6..3f7485282 100644 --- a/sail/pkg/serviceregistry/kube/controller/controller_test.go +++ b/sail/pkg/serviceregistry/kube/controller/controller_test.go @@ -1,3 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + package controller import ( diff --git a/sail/pkg/serviceregistry/kube/errors/errors.go b/sail/pkg/serviceregistry/kube/errors/errors.go index d482224d5..6e8a124d1 100644 --- a/sail/pkg/serviceregistry/kube/errors/errors.go +++ b/sail/pkg/serviceregistry/kube/errors/errors.go @@ -1,3 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + package errors import ( diff --git a/sail/pkg/xds/ads.go b/sail/pkg/xds/ads.go index 5591c2e63..6cf4168a3 100644 --- a/sail/pkg/xds/ads.go +++ b/sail/pkg/xds/ads.go @@ -19,11 +19,12 @@ package xds import ( "context" + "time" + "github.com/apache/dubbo-kubernetes/pkg/xds" "github.com/apache/dubbo-kubernetes/sail/pkg/model" core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" - "time" ) type ( @@ -34,12 +35,12 @@ type ( type Connection struct { xds.Connection - node *core.Node + // node *core.Node proxy *model.Proxy deltaStream DeltaDiscoveryStream deltaReqChan chan *discovery.DeltaDiscoveryRequest - s *DiscoveryServer - ids []string + // s *DiscoveryServer + // ids []string } type Event struct { diff --git a/sail/pkg/xds/auth.go b/sail/pkg/xds/auth.go index 02780d178..958b1e6df 100644 --- a/sail/pkg/xds/auth.go +++ b/sail/pkg/xds/auth.go @@ -1,3 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + package xds import ( diff --git a/sail/pkg/xds/delta.go b/sail/pkg/xds/delta.go index 82064a640..b4732a6a5 100644 --- a/sail/pkg/xds/delta.go +++ b/sail/pkg/xds/delta.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package xds import ( @@ -182,7 +199,6 @@ func (s *DiscoveryServer) processDeltaRequest(req *discovery.DeltaDiscoveryReque } func (s *DiscoveryServer) computeProxyState(proxy *model.Proxy, request *model.PushRequest) { - return } func (s *DiscoveryServer) pushDeltaXds(con *Connection, w *model.WatchedResource, req *model.PushRequest) error { diff --git a/sail/pkg/xds/v3/model.go b/sail/pkg/xds/v3/model.go index 38aad5fb0..783f2afe8 100644 --- a/sail/pkg/xds/v3/model.go +++ b/sail/pkg/xds/v3/model.go @@ -1,3 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + package v3 import "github.com/apache/dubbo-kubernetes/pkg/model" diff --git a/security/pkg/credentialfetcher/fetcher.go b/security/pkg/credentialfetcher/fetcher.go index c95c6da5a..a2c9d2f0b 100644 --- a/security/pkg/credentialfetcher/fetcher.go +++ b/security/pkg/credentialfetcher/fetcher.go @@ -1,3 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + package credentialfetcher import ( diff --git a/security/pkg/credentialfetcher/plugin/token.go b/security/pkg/credentialfetcher/plugin/token.go index 2a08602ec..cac9984f8 100644 --- a/security/pkg/credentialfetcher/plugin/token.go +++ b/security/pkg/credentialfetcher/plugin/token.go @@ -1,3 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + package plugin import ( diff --git a/security/pkg/nodeagent/cache/secretcache.go b/security/pkg/nodeagent/cache/secretcache.go index 31ce09881..186c5d894 100644 --- a/security/pkg/nodeagent/cache/secretcache.go +++ b/security/pkg/nodeagent/cache/secretcache.go @@ -1,3 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + package cache import ( diff --git a/security/pkg/nodeagent/caclient/credentials.go b/security/pkg/nodeagent/caclient/credentials.go index 6f946cecf..bb87dd500 100644 --- a/security/pkg/nodeagent/caclient/credentials.go +++ b/security/pkg/nodeagent/caclient/credentials.go @@ -1,3 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + package caclient import ( diff --git a/security/pkg/nodeagent/caclient/providers/aegis/client.go b/security/pkg/nodeagent/caclient/providers/aegis/client.go index e7f0fcdcc..0e0e54c18 100644 --- a/security/pkg/nodeagent/caclient/providers/aegis/client.go +++ b/security/pkg/nodeagent/caclient/providers/aegis/client.go @@ -1,3 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + package aegis import ( diff --git a/security/pkg/nodeagent/sds/sdsservice.go b/security/pkg/nodeagent/sds/sdsservice.go index 00b8b08b3..766cbefdf 100644 --- a/security/pkg/nodeagent/sds/sdsservice.go +++ b/security/pkg/nodeagent/sds/sdsservice.go @@ -1,3 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + package sds import ( diff --git a/security/pkg/nodeagent/sds/server.go b/security/pkg/nodeagent/sds/server.go index 07af70c8a..b39a1995d 100644 --- a/security/pkg/nodeagent/sds/server.go +++ b/security/pkg/nodeagent/sds/server.go @@ -1,3 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + package sds import ( From 23b570937eb7dbc25c5f0bb6d350d13f549c077e Mon Sep 17 00:00:00 2001 From: liwener Date: Mon, 29 Sep 2025 23:57:54 +0800 Subject: [PATCH 7/7] [chore]: format code, add missing ASF headers --- .../dubbo-discovery/files/grpc-agent.yaml | 15 ++++++++++++ pkg/file/fadvise_linux.go | 4 ++-- sail/configs/multi-cluster.yaml | 17 +++++++------- sail/configs/multi-registry.yaml | 16 ++++++------- sail/pkg/bootstrap/servicecontroller.go | 17 ++++++++++++++ sail/pkg/model/service.go | 23 ++++++++++++++++--- sail/pkg/serviceregistry/instance.go | 1 - sail/pkg/serviceregistry/istio/istio_test.go | 17 ++++++++++++++ .../kube/controller/controller_test.go | 1 - .../pkg/serviceregistry/kube/errors/errors.go | 1 - sail/pkg/xds/auth.go | 1 - sail/pkg/xds/v3/model.go | 1 - security/pkg/credentialfetcher/fetcher.go | 1 - .../pkg/credentialfetcher/plugin/token.go | 1 - security/pkg/nodeagent/cache/secretcache.go | 1 - .../pkg/nodeagent/caclient/credentials.go | 1 - .../caclient/providers/aegis/client.go | 1 - security/pkg/nodeagent/sds/sdsservice.go | 1 - security/pkg/nodeagent/sds/server.go | 1 - 19 files changed, 87 insertions(+), 34 deletions(-) diff --git a/manifests/charts/dubbo-control/dubbo-discovery/files/grpc-agent.yaml b/manifests/charts/dubbo-control/dubbo-discovery/files/grpc-agent.yaml index 69b462ca5..21e308ad2 100644 --- a/manifests/charts/dubbo-control/dubbo-discovery/files/grpc-agent.yaml +++ b/manifests/charts/dubbo-control/dubbo-discovery/files/grpc-agent.yaml @@ -1 +1,16 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + # inject grpc template \ No newline at end of file diff --git a/pkg/file/fadvise_linux.go b/pkg/file/fadvise_linux.go index cc2937571..6a1909019 100644 --- a/pkg/file/fadvise_linux.go +++ b/pkg/file/fadvise_linux.go @@ -1,3 +1,5 @@ +//go:build linux + /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -15,8 +17,6 @@ * limitations under the License. */ -//go:build linux - package file import ( diff --git a/sail/configs/multi-cluster.yaml b/sail/configs/multi-cluster.yaml index d1a2e35ea..4e84e80f3 100644 --- a/sail/configs/multi-cluster.yaml +++ b/sail/configs/multi-cluster.yaml @@ -1,12 +1,3 @@ -# Multi-Cluster Configuration for Dubbo Admin -# This file defines multiple Kubernetes clusters that Dubbo Admin can manage - -apiVersion: v1 -kind: Config -metadata: - name: multi-cluster-config - namespace: dubbo-system - # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with @@ -24,6 +15,14 @@ metadata: # limitations under the License. # +# Multi-Cluster Configuration for Dubbo Admin +# This file defines multiple Kubernetes clusters that Dubbo Admin can manage + +apiVersion: v1 +kind: Config +metadata: + name: multi-cluster-config + namespace: dubbo-system # Multi-cluster configuration for Dubbo Service Registry multiCluster: enabled: true diff --git a/sail/configs/multi-registry.yaml b/sail/configs/multi-registry.yaml index 4360843b4..1900e236d 100644 --- a/sail/configs/multi-registry.yaml +++ b/sail/configs/multi-registry.yaml @@ -1,11 +1,3 @@ -# Multi-Registry Configuration for Dubbo Admin -# This file defines multiple service registries that Dubbo Admin can connect to - -apiVersion: v1 -kind: Config -metadata: - name: multi-registry-config - namespace: dubbo-system # # Licensed to the Apache Software Foundation (ASF) under one or more @@ -24,6 +16,14 @@ metadata: # limitations under the License. # +# Multi-Registry Configuration for Dubbo Admin +# This file defines multiple service registries that Dubbo Admin can connect to + +apiVersion: v1 +kind: Config +metadata: + name: multi-registry-config + namespace: dubbo-system # Multi-registry configuration for Dubbo Service Registry multiRegistry: enabled: true diff --git a/sail/pkg/bootstrap/servicecontroller.go b/sail/pkg/bootstrap/servicecontroller.go index 24bb7e07e..f04ce6acf 100644 --- a/sail/pkg/bootstrap/servicecontroller.go +++ b/sail/pkg/bootstrap/servicecontroller.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package bootstrap import ( diff --git a/sail/pkg/model/service.go b/sail/pkg/model/service.go index 33e81afc4..b9acf6c01 100644 --- a/sail/pkg/model/service.go +++ b/sail/pkg/model/service.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package model import ( @@ -78,9 +95,9 @@ func (s *Service) DeepCopy() *Service { return nil } out := &Service{ - Attributes: s.Attributes.DeepCopy(), - Hostname: s.Hostname, - CreationTime: s.CreationTime, + Attributes: s.Attributes.DeepCopy(), + Hostname: s.Hostname, + CreationTime: s.CreationTime, } if s.Ports != nil { out.Ports = make(PortList, len(s.Ports)) diff --git a/sail/pkg/serviceregistry/instance.go b/sail/pkg/serviceregistry/instance.go index 369b962bf..9125c0f70 100644 --- a/sail/pkg/serviceregistry/instance.go +++ b/sail/pkg/serviceregistry/instance.go @@ -15,7 +15,6 @@ * limitations under the License. */ - package serviceregistry import ( diff --git a/sail/pkg/serviceregistry/istio/istio_test.go b/sail/pkg/serviceregistry/istio/istio_test.go index 3e6a30937..f17e30a2e 100644 --- a/sail/pkg/serviceregistry/istio/istio_test.go +++ b/sail/pkg/serviceregistry/istio/istio_test.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package istio import ( diff --git a/sail/pkg/serviceregistry/kube/controller/controller_test.go b/sail/pkg/serviceregistry/kube/controller/controller_test.go index 3f7485282..ca11157e1 100644 --- a/sail/pkg/serviceregistry/kube/controller/controller_test.go +++ b/sail/pkg/serviceregistry/kube/controller/controller_test.go @@ -15,7 +15,6 @@ * limitations under the License. */ - package controller import ( diff --git a/sail/pkg/serviceregistry/kube/errors/errors.go b/sail/pkg/serviceregistry/kube/errors/errors.go index 6e8a124d1..4293022d4 100644 --- a/sail/pkg/serviceregistry/kube/errors/errors.go +++ b/sail/pkg/serviceregistry/kube/errors/errors.go @@ -15,7 +15,6 @@ * limitations under the License. */ - package errors import ( diff --git a/sail/pkg/xds/auth.go b/sail/pkg/xds/auth.go index 958b1e6df..cc8aa163a 100644 --- a/sail/pkg/xds/auth.go +++ b/sail/pkg/xds/auth.go @@ -15,7 +15,6 @@ * limitations under the License. */ - package xds import ( diff --git a/sail/pkg/xds/v3/model.go b/sail/pkg/xds/v3/model.go index 783f2afe8..a6c7f61dc 100644 --- a/sail/pkg/xds/v3/model.go +++ b/sail/pkg/xds/v3/model.go @@ -15,7 +15,6 @@ * limitations under the License. */ - package v3 import "github.com/apache/dubbo-kubernetes/pkg/model" diff --git a/security/pkg/credentialfetcher/fetcher.go b/security/pkg/credentialfetcher/fetcher.go index a2c9d2f0b..566e82570 100644 --- a/security/pkg/credentialfetcher/fetcher.go +++ b/security/pkg/credentialfetcher/fetcher.go @@ -15,7 +15,6 @@ * limitations under the License. */ - package credentialfetcher import ( diff --git a/security/pkg/credentialfetcher/plugin/token.go b/security/pkg/credentialfetcher/plugin/token.go index cac9984f8..cc608b0d3 100644 --- a/security/pkg/credentialfetcher/plugin/token.go +++ b/security/pkg/credentialfetcher/plugin/token.go @@ -15,7 +15,6 @@ * limitations under the License. */ - package plugin import ( diff --git a/security/pkg/nodeagent/cache/secretcache.go b/security/pkg/nodeagent/cache/secretcache.go index 186c5d894..2e1375b85 100644 --- a/security/pkg/nodeagent/cache/secretcache.go +++ b/security/pkg/nodeagent/cache/secretcache.go @@ -15,7 +15,6 @@ * limitations under the License. */ - package cache import ( diff --git a/security/pkg/nodeagent/caclient/credentials.go b/security/pkg/nodeagent/caclient/credentials.go index bb87dd500..05955b150 100644 --- a/security/pkg/nodeagent/caclient/credentials.go +++ b/security/pkg/nodeagent/caclient/credentials.go @@ -15,7 +15,6 @@ * limitations under the License. */ - package caclient import ( diff --git a/security/pkg/nodeagent/caclient/providers/aegis/client.go b/security/pkg/nodeagent/caclient/providers/aegis/client.go index 0e0e54c18..af623d569 100644 --- a/security/pkg/nodeagent/caclient/providers/aegis/client.go +++ b/security/pkg/nodeagent/caclient/providers/aegis/client.go @@ -15,7 +15,6 @@ * limitations under the License. */ - package aegis import ( diff --git a/security/pkg/nodeagent/sds/sdsservice.go b/security/pkg/nodeagent/sds/sdsservice.go index 766cbefdf..4a3f564d6 100644 --- a/security/pkg/nodeagent/sds/sdsservice.go +++ b/security/pkg/nodeagent/sds/sdsservice.go @@ -15,7 +15,6 @@ * limitations under the License. */ - package sds import ( diff --git a/security/pkg/nodeagent/sds/server.go b/security/pkg/nodeagent/sds/server.go index b39a1995d..89ffc14ea 100644 --- a/security/pkg/nodeagent/sds/server.go +++ b/security/pkg/nodeagent/sds/server.go @@ -15,7 +15,6 @@ * limitations under the License. */ - package sds import (