func processRoutes(ipv6DualStackEnabled bool, routeTable network.RouteTable, exists bool, err error) ([]*cloudprovider.Route, error) {
	if err != nil {
		return nil, err
	}
	if !exists {
		return []*cloudprovider.Route{}, nil
	}
	var kubeRoutes []*cloudprovider.Route
	if routeTable.RouteTablePropertiesFormat != nil && routeTable.Routes != nil {
		kubeRoutes = make([]*cloudprovider.Route, len(*routeTable.Routes))
		for i, route := range *routeTable.Routes {
			instance := mapRouteNameToNodeName(ipv6DualStackEnabled, *route.Name)
			cidr := *route.AddressPrefix
			klog.V(10).Infof("ListRoutes: * instance=%q, cidr=%q", instance, cidr)
			kubeRoutes[i] = &cloudprovider.Route{
				Name:            *route.Name,
				TargetNode:      instance,
				DestinationCIDR: cidr,
			}
		}
	}
	klog.V(10).Info("ListRoutes: FINISH")
	return kubeRoutes, nil
}
Injectable for testing
package azure
import (
	"context"
	"fmt"
	"strings"
	"sync"
	"time"
	"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network"
	"github.com/Azure/go-autorest/autorest/to"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/wait"
	cloudprovider "k8s.io/cloud-provider"
	"k8s.io/klog/v2"
	azcache "k8s.io/legacy-cloud-providers/azure/cache"
	"k8s.io/legacy-cloud-providers/azure/metrics"
	utilnet "k8s.io/utils/net"
)
var (
	routeUpdateInterval = 30 * time.Second
)
type routeOperation string
const (
	routeNameFmt       = "%s____%s"
	routeNameSeparator = "____"
	routeOperationAdd             routeOperation = "add"
	routeOperationDelete          routeOperation = "delete"
	routeTableOperationUpdateTags routeOperation = "updateRouteTableTags"
)
type delayedRouteOperation struct {
	route          network.Route
	routeTableTags map[string]*string
	operation      routeOperation
	result         chan error
}
func (op *delayedRouteOperation) wait() error {
	return <-op.result
}
type delayedRouteUpdater struct {
	az       *Cloud
	interval time.Duration
	lock           sync.Mutex
	routesToUpdate []*delayedRouteOperation
}
func newDelayedRouteUpdater(az *Cloud, interval time.Duration) *delayedRouteUpdater {
	return &delayedRouteUpdater{
		az:             az,
		interval:       interval,
		routesToUpdate: make([]*delayedRouteOperation, 0),
	}
}
func (d *delayedRouteUpdater) run() {
	err := wait.PollImmediateInfinite(d.interval, func() (bool, error) {
		d.updateRoutes()
		return false, nil
	})
	if err != nil {  
		panic(err)
	}
}
func (d *delayedRouteUpdater) updateRoutes() {
	d.lock.Lock()
	defer d.lock.Unlock()
	if len(d.routesToUpdate) == 0 {
		return
	}
	var err error
	defer func() {
		for _, rt := range d.routesToUpdate {
			rt.result <- err
		}
		d.routesToUpdate = make([]*delayedRouteOperation, 0)
	}()
	var (
		routeTable       network.RouteTable
		existsRouteTable bool
	)
	routeTable, existsRouteTable, err = d.az.getRouteTable(azcache.CacheReadTypeDefault)
	if err != nil {
		klog.Errorf("getRouteTable() failed with error: %v", err)
		return
	}
	if !existsRouteTable {
		err = d.az.createRouteTable()
		if err != nil {
			klog.Errorf("createRouteTable() failed with error: %v", err)
			return
		}
		routeTable, _, err = d.az.getRouteTable(azcache.CacheReadTypeDefault)
		if err != nil {
			klog.Errorf("getRouteTable() failed with error: %v", err)
			return
		}
	}
	dirty, onlyUpdateTags := false, true
	routes := []network.Route{}
	if routeTable.Routes != nil {
		routes = *routeTable.Routes
	}
	routes, dirty = d.cleanupOutdatedRoutes(routes)
	if dirty {
		onlyUpdateTags = false
	}
	for _, rt := range d.routesToUpdate {
		if rt.operation == routeTableOperationUpdateTags {
			routeTable.Tags = rt.routeTableTags
			dirty = true
			continue
		}
		routeMatch := false
		onlyUpdateTags = false
		for i, existingRoute := range routes {
			if strings.EqualFold(to.String(existingRoute.Name), to.String(rt.route.Name)) {
				routes = append(routes[:i], routes[i+1:]...)
				if existingRoute.RoutePropertiesFormat != nil &&
					rt.route.RoutePropertiesFormat != nil &&
					strings.EqualFold(to.String(existingRoute.AddressPrefix), to.String(rt.route.AddressPrefix)) &&
					strings.EqualFold(to.String(existingRoute.NextHopIPAddress), to.String(rt.route.NextHopIPAddress)) {
					routeMatch = true
				}
				if rt.operation == routeOperationDelete {
					dirty = true
				}
				break
			}
		}
		if rt.operation == routeOperationAdd {
			routes = append(routes, rt.route)
			if !routeMatch {
				dirty = true
			}
			continue
		}
	}
	if dirty {
		if !onlyUpdateTags {
			klog.V(2).Infof("updateRoutes: updating routes")
			routeTable.Routes = &routes
		}
		err = d.az.CreateOrUpdateRouteTable(routeTable)
		if err != nil {
			klog.Errorf("CreateOrUpdateRouteTable() failed with error: %v", err)
			return
		}
	}
}
func (d *delayedRouteUpdater) cleanupOutdatedRoutes(existingRoutes []network.Route) (routes []network.Route, changed bool) {
	for i := len(existingRoutes) - 1; i >= 0; i-- {
		existingRouteName := to.String(existingRoutes[i].Name)
		split := strings.Split(existingRouteName, routeNameSeparator)
		deleteRoute := false
		if d.az.nodeNames.Has(split[0]) {
			if d.az.ipv6DualStackEnabled && len(split) == 1 {
				klog.V(2).Infof("cleanupOutdatedRoutes: deleting outdated non-dualstack route %s", existingRouteName)
				deleteRoute = true
			} else if !d.az.ipv6DualStackEnabled && len(split) == 2 {
				klog.V(2).Infof("cleanupOutdatedRoutes: deleting outdated dualstack route %s", existingRouteName)
				deleteRoute = true
			}
			if deleteRoute {
				existingRoutes = append(existingRoutes[:i], existingRoutes[i+1:]...)
				changed = true
			}
		}
	}
	return existingRoutes, changed
}
func (d *delayedRouteUpdater) addRouteOperation(operation routeOperation, route network.Route) (*delayedRouteOperation, error) {
	d.lock.Lock()
	defer d.lock.Unlock()
	op := &delayedRouteOperation{
		route:     route,
		operation: operation,
		result:    make(chan error),
	}
	d.routesToUpdate = append(d.routesToUpdate, op)
	return op, nil
}
func (d *delayedRouteUpdater) addUpdateRouteTableTagsOperation(operation routeOperation, tags map[string]*string) (*delayedRouteOperation, error) {
	d.lock.Lock()
	defer d.lock.Unlock()
	op := &delayedRouteOperation{
		routeTableTags: tags,
		operation:      operation,
		result:         make(chan error),
	}
	d.routesToUpdate = append(d.routesToUpdate, op)
	return op, nil
}
func (az *Cloud) ListRoutes(ctx context.Context, clusterName string) ([]*cloudprovider.Route, error) {
	klog.V(10).Infof("ListRoutes: START clusterName=%q", clusterName)
	routeTable, existsRouteTable, err := az.getRouteTable(azcache.CacheReadTypeDefault)
	routes, err := processRoutes(az.ipv6DualStackEnabled, routeTable, existsRouteTable, err)
	if err != nil {
		return nil, err
	}
	unmanagedNodes, err := az.GetUnmanagedNodes()
	if err != nil {
		return nil, err
	}
	az.routeCIDRsLock.Lock()
	defer az.routeCIDRsLock.Unlock()
	for _, nodeName := range unmanagedNodes.List() {
		if cidr, ok := az.routeCIDRs[nodeName]; ok {
			routes = append(routes, &cloudprovider.Route{
				Name:            nodeName,
				TargetNode:      mapRouteNameToNodeName(az.ipv6DualStackEnabled, nodeName),
				DestinationCIDR: cidr,
			})
		}
	}
	tags, changed := az.ensureRouteTableTagged(&routeTable)
	if changed {
		klog.V(2).Infof("ListRoutes: updating tags on route table %s", to.String(routeTable.Name))
		op, err := az.routeUpdater.addUpdateRouteTableTagsOperation(routeTableOperationUpdateTags, tags)
		if err != nil {
			klog.Errorf("ListRoutes: failed to add route table operation with error: %v", err)
			return nil, err
		}
		err = op.wait()
		if err != nil {
			klog.Errorf("ListRoutes: failed to update route table tags with error: %v", err)
			return nil, err
		}
	}
	return routes, nil
}