pkg/hotreload/route_reloader.go (93 lines of code) (raw):
/*
* 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 hotreload
import (
"encoding/json"
)
import (
"github.com/pkg/errors"
)
import (
"github.com/apache/dubbo-go-pixiu/pkg/common/constant"
"github.com/apache/dubbo-go-pixiu/pkg/logger"
"github.com/apache/dubbo-go-pixiu/pkg/model"
"github.com/apache/dubbo-go-pixiu/pkg/server"
)
// RouteReloader implements the HotReloader interface for reloading route configurations.
type RouteReloader struct{}
// CheckUpdate compares the old and new route configurations to determine if a reload is needed.
func (r *RouteReloader) CheckUpdate(oldConfig, newConfig *model.Bootstrap) bool {
oldRoutes := extractRoutes(oldConfig)
newRoutes := extractRoutes(newConfig)
// Compare the number of routes
if len(oldRoutes.Routes) != len(newRoutes.Routes) || oldRoutes.Dynamic != newRoutes.Dynamic {
return true
}
// Compare each route
for i := range newRoutes.Routes {
if oldRoutes.Routes[i].Match.Prefix != newRoutes.Routes[i].Match.Prefix ||
oldRoutes.Routes[i].Route.Cluster != newRoutes.Routes[i].Route.Cluster {
return true
}
}
return false
}
// HotReload applies the new route configuration.
func (r *RouteReloader) HotReload(oldConfig, newConfig *model.Bootstrap) error {
oldRoutes := extractRoutes(oldConfig)
newRoutes := extractRoutes(newConfig)
// Update routes in the RouterManager
err := server.GetRouterManager().UpdateRoutes(oldRoutes.Routes, newRoutes.Routes)
if err != nil {
logger.Infof("Failed to Routes reloaded.")
return err
}
return nil
}
// extractRoutes extracts routes from the configuration by parsing the filters.
func extractRoutes(config *model.Bootstrap) model.RouteConfiguration {
var (
routeConfig model.RouteConfiguration
invalidRouteIDs []string
)
for _, listener := range config.StaticResources.Listeners {
for _, filterChain := range listener.FilterChain.Filters {
if filterChain.Name == constant.HTTPConnectManagerFilter {
// Extract route_config
rawRouteConfig, ok := filterChain.Config["route_config"]
if !ok {
logger.Debugf("No route_config found in filter chain: %+v", filterChain)
continue
}
logger.Debugf("Raw route_config: %+v", rawRouteConfig)
// Convert route_config to JSON bytes
routeConfigBytes, err := json.Marshal(rawRouteConfig)
if err != nil {
logger.Errorf("Failed to marshal route_config: %v", err)
continue
}
// Parse JSON bytes into model.RouteConfiguration
if err := json.Unmarshal(routeConfigBytes, &routeConfig); err != nil {
logger.Errorf("Failed to unmarshal route_config: %v", err)
continue
}
logger.Debugf("Parsed route_config: %+v", routeConfig)
// Validate and filter routes
validRoutes := make([]*model.Router, 0, len(routeConfig.Routes))
for _, route := range routeConfig.Routes {
if err := validateRoute(route); err != nil {
invalidRouteIDs = append(invalidRouteIDs, route.ID)
logger.Warnf("Skipping invalid route %s: %v", route.ID, err)
continue
}
validRoutes = append(validRoutes, route)
}
routeConfig.Routes = validRoutes
logger.Debugf("Valid routes after filtering: %+v", validRoutes)
// Return if we have valid routes
if len(validRoutes) > 0 {
return routeConfig
}
}
}
}
if len(invalidRouteIDs) > 0 {
logger.Warnf("No valid routes found in configuration: %v", invalidRouteIDs)
}
return routeConfig
}
// validateRoute validates a single route, returning an error if invalid.
func validateRoute(route *model.Router) error {
// Ensure route has a valid match condition
if route.Match.Prefix == "" && route.Match.Path == "" {
return errors.Errorf("route %s has no prefix or path defined", route.ID)
}
// Ensure cluster is specified
if route.Route.Cluster == "" {
return errors.Errorf("route %s has no cluster defined", route.ID)
}
return nil
}