package snapshot import ( "context" "fmt" "time" listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" "github.com/envoyproxy/go-control-plane/pkg/cache/types" cachev3 "github.com/envoyproxy/go-control-plane/pkg/cache/v3" resourcev3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3" internallog "envoy-control-plane/internal/log" "envoy-control-plane/internal/storage" ) // AppendFilterChainToListener loads the current listener from the cache, appends the provided // FilterChain to its list of FilterChains, and updates the cache with the new snapshot. func (sm *SnapshotManager) AppendFilterChainToListener(ctx context.Context, listenerName string, newFilterChain *listenerv3.FilterChain) error { log := internallog.LogFromContext(ctx) // 1. Get the current Listener from the cache resource, err := sm.GetResourceFromCache(listenerName, resourcev3.ListenerType) if err != nil { return fmt.Errorf("failed to get listener '%s' from cache: %w", listenerName, err) } listener, ok := resource.(*listenerv3.Listener) if !ok { return fmt.Errorf("resource '%s' is not a Listener type", listenerName) } // 2. Append the new FilterChain to the listener's list of filter chains. listener.FilterChains = append(listener.FilterChains, newFilterChain) log.Infof("Appended new filter chain (match: %v) to listener '%s'", newFilterChain.FilterChainMatch, listenerName) // 3. Create a new snapshot with the modified listener (rest of logic remains similar) snap, err := sm.Cache.GetSnapshot(sm.NodeID) if err != nil { return fmt.Errorf("failed to get snapshot for modification: %w", err) } // Get all current resources resources := sm.getAllResourcesFromSnapshot(snap) // Replace the old listener with the modified one listenerList, ok := resources[resourcev3.ListenerType] if !ok { return fmt.Errorf("listener resource type not present in snapshot") } foundAndReplaced := false for i, res := range listenerList { if namer, ok := res.(interface{ GetName() string }); ok && namer.GetName() == listenerName { listenerList[i] = listener foundAndReplaced = true break } } if !foundAndReplaced { return fmt.Errorf("failed to locate listener '%s' in current resource list for replacement", listenerName) } // Create and set the new snapshot version := fmt.Sprintf("listener-update-%s-%d", listenerName, time.Now().UnixNano()) newSnap, err := cachev3.NewSnapshot(version, resources) if err != nil { return fmt.Errorf("failed to create new snapshot: %w", err) } if err := sm.Cache.SetSnapshot(ctx, sm.NodeID, newSnap); err != nil { return fmt.Errorf("failed to set new snapshot: %w", err) } log.Infof("Successfully updated listener '%s' in cache with new filter chain.", listenerName) return nil } // AddResourceToSnapshot adds any resource to the snapshot dynamically func (sm *SnapshotManager) AddResourceToSnapshot(resource types.Resource, typ resourcev3.Type) error { snap, err := sm.Cache.GetSnapshot(sm.NodeID) if err != nil { return fmt.Errorf("failed to get snapshot from cache: %w", err) } resources := sm.getAllResourcesFromSnapshot(snap) // Append to the appropriate slice switch typ { case resourcev3.ClusterType: resources[resourcev3.ClusterType] = append(resources[resourcev3.ClusterType], resource) case resourcev3.ListenerType: resources[resourcev3.ListenerType] = append(resources[resourcev3.ListenerType], resource) case resourcev3.EndpointType, resourcev3.SecretType, resourcev3.RuntimeType: resources[typ] = append(resources[typ], resource) default: return fmt.Errorf("unsupported resource type: %s", typ) } resourceNamer, ok := resource.(interface{ GetName() string }) if !ok { return fmt.Errorf("resource of type %s does not implement GetName()", typ) } newSnap, _ := cachev3.NewSnapshot( "snap-generic-"+resourceNamer.GetName(), resources, ) return sm.Cache.SetSnapshot(context.TODO(), sm.NodeID, newSnap) } // RemoveResource removes any resource by name dynamically func (sm *SnapshotManager) RemoveResource(name string, typ resourcev3.Type, strategy storage.DeleteStrategy) error { snap, _ := sm.Cache.GetSnapshot(sm.NodeID) resources := sm.getAllResourcesFromSnapshot(snap) // Flag to check if resource was found in cache var resourceFound = false // Filter the target type if targetResources, ok := resources[typ]; ok { resources[typ], resourceFound = filterAndCheckResourcesByName(targetResources, name) } if strategy == storage.DeleteActual { if resourceFound { return fmt.Errorf("actual delete requested but resource %s of type %s still exists in cache", name, typ) } if typ == resourcev3.ClusterType { if err := sm.DB.RemoveCluster(context.TODO(), name); err != nil { return fmt.Errorf("failed to delete cluster %s from DB: %w", name, err) } return nil } if typ == resourcev3.ListenerType { if err := sm.DB.RemoveListener(context.TODO(), name); err != nil { return fmt.Errorf("failed to delete listener %s from DB: %w", name, err) } return nil } return fmt.Errorf("actual delete not supported for resource type: %s", typ) } if !resourceFound { return fmt.Errorf("resource %s of type %s not found in cache", name, typ) } newSnap, _ := cachev3.NewSnapshot( "snap-remove-generic-"+name, resources, ) if err := sm.Cache.SetSnapshot(context.TODO(), sm.NodeID, newSnap); err != nil { return fmt.Errorf("failed to set snapshot: %w", err) } if err := sm.FlushCacheToDB(context.TODO(), strategy); err != nil { return fmt.Errorf("failed to flush cache to DB: %w", err) } return nil } // GetResourceFromCache retrieves a resource by name and type from the cache. func (sm *SnapshotManager) GetResourceFromCache(name string, typ resourcev3.Type) (types.Resource, error) { snap, err := sm.Cache.GetSnapshot(sm.NodeID) if err != nil { return nil, err } r, ok := snap.GetResources(string(typ))[name] if !ok { return nil, fmt.Errorf("%s resource %s not found in cache", typ, name) } // We rely on the type given to be correct, as all xDS resources implement GetName(). return r, nil } // getAllResourcesFromSnapshot retrieves all known resource types from a snapshot as a map. func (sm *SnapshotManager) getAllResourcesFromSnapshot(snap cachev3.ResourceSnapshot) map[resourcev3.Type][]types.Resource { // Only include types that might be manipulated by the generic functions resources := map[resourcev3.Type][]types.Resource{ resourcev3.ClusterType: mapToSlice(snap.GetResources(string(resourcev3.ClusterType))), resourcev3.ListenerType: mapToSlice(snap.GetResources(string(resourcev3.ListenerType))), // resourcev3.EndpointType: mapToSlice(snap.GetResources(string(resourcev3.EndpointType))), // resourcev3.SecretType: mapToSlice(snap.GetResources(string(resourcev3.SecretType))), // resourcev3.RuntimeType: mapToSlice(snap.GetResources(string(resourcev3.RuntimeType))), // Include other types as needed } return resources } // mapToSlice converts a map of named resources to a slice of resources. func mapToSlice(m map[string]types.Resource) []types.Resource { out := make([]types.Resource, 0, len(m)) for _, r := range m { out = append(out, r) } return out } // filterAndCheckResourcesByName filters a slice of resources by name, // returning the filtered slice and a boolean indicating if the named resource was found. func filterAndCheckResourcesByName(resources []types.Resource, name string) ([]types.Resource, bool) { filtered := []types.Resource{} var found = false for _, r := range resources { if namer, ok := r.(interface{ GetName() string }); ok { if namer.GetName() != name { filtered = append(filtered, r) } else { found = true } } else { // fallback, include unknown type filtered = append(filtered, r) } } return filtered, found }