Change:
<!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks! If the matter is security related, please disclose it privately via --What happened: Kubernetes shouldn't put route table when it's already been configured. Azure cloud provider should check whether the route has already been created before invoking . What you expected to happen: How to reproduce it (as minimally and precisely as possible): Anything else we need to know?: Environment: Kubernetes version (use ): Cloud provider or hardware configuration: OS (e.g: ): Kernel (e.g. ): Install tools: Network plugin and version (if this is a network-related bug): Others: /sig cloud-provider /priority important-soon
Why this is a bug? A PUT operation should be idempotent and multiple calls should get the same result?
Right. But cloud provider shouldn't make unnecessary API calls.
To check whether it is configured, we need a GET call, so we're going to replace PUT call with GET call in this case?
it's not a replace, we need to LIST routes and check whether it has already been there, then PUT if it's not.
I got your point. But if we directly send a PUT request, seems route RP will also do this comparation? Is there any issue making us have to do the comparation by ourselves instead of sending PUT request to route RP?
It would help to reduce the chances of ARM PUT throttling.
I'd like to work on this
/assign

Discussion:
type fakeRoutesClient struct { mutex     *sync.Mutex FakeStore map[string]map[string]network.Route <ins> Calls     []string </ins> } func newFakeRoutesClient() *fakeRoutesClient {

fRC.mutex.Lock() defer fRC.mutex.Unlock() <ins> fRC.Calls = append(fRC.Calls, "CreateOrUpdate") </ins> if _, ok := fRC.FakeStore[routeTableName]; !ok { fRC.FakeStore[routeTableName] = make(map[string]network.Route) }

fRC.mutex.Lock() defer fRC.mutex.Unlock() <ins> fRC.Calls = append(fRC.Calls, "Delete") </ins> if routes, ok := fRC.FakeStore[routeTableName]; ok { if _, ok := routes[routeName]; ok { delete(routes, routeName)

} if unmanaged { if az.ipv6DualStackEnabled { <del> //TODO (khenidak) add support for unmanaged nodes when the feature reaches  beta </del> <ins> //TODO (khenidak) add support for unmanaged nodes when the feature reaches beta </ins> return fmt.Errorf("unmanaged nodes are not supported in dual stack mode") } klog.V(2).Infof("CreateRoute: omitting unmanaged node %q", kubeRoute.TargetNode)

}, } <ins> actualRoutes, err := az.ListRoutes(ctx, clusterName) if err != nil { klog.V(3).Infof("CreateRoute: creating route: failed(ListRoutes) clusterName= %q instance=%q with error=%v", clusterName, kubeRoute.TargetNode, err) return err } for _, actualRoute := range actualRoutes { if strings.EqualFold(actualRoute.Name, kubeRoute.Name) && strings.EqualFold(string(actualRoute.TargetNode), string(kubeRoute.TargetNode)) && strings.EqualFold(actualRoute.DestinationCIDR, kubeRoute.DestinationCIDR) { klog.V(2).Infof("CreateRoute: route is already existed and matched, no need to re-create or update") return nil } } </ins> klog.V(3).Infof("CreateRoute: creating route: instance=%q cidr=%q", kubeRoute.TargetNode, kubeRoute.DestinationCIDR) err = az.CreateOrUpdateRoute(route) if err != nil {

if len(fakeTable.Calls) != 1 || fakeTable.Calls[0] != "Get" { t.Errorf("unexpected calls create if not exists, exists: %v", fakeTable.Calls) } <ins> if len(fakeRoutes.Calls) != 1 || fakeRoutes.Calls[0] != "CreateOrUpdate" { t.Errorf("unexpected route calls create if not exists, exists: %v", fakeRoutes.Calls) } </ins> routeName := mapNodeNameToRouteName(false, route.TargetNode, string(route.DestinationCIDR)) routeInfo, found := fakeRoutes.FakeStore[cloud.RouteTableName][routeName]

t.Errorf("Expected IP address: %s, saw %s", nodeIP, *routeInfo.NextHopIPAddress) } <ins> // test create again without real creation, clean fakeRoute calls fakeRoutes.Calls = []string{} routeInfo.Name = &routeName route.Name = routeName expectedTable.RouteTablePropertiesFormat = &network.RouteTablePropertiesFormat{ Routes: &[]network.Route{routeInfo}, } cloud.rtCache.Set(cloud.RouteTableName, &expectedTable) err = cloud.CreateRoute(context.TODO(), "cluster", "unused", &route) if err != nil { t.Errorf("unexpected error creating route: %v", err) t.FailNow() } if len(fakeRoutes.Calls) != 0 { t.Errorf("unexpected route calls create if not exists, exists: %v", fakeRoutes.Calls) } </ins> // test create route for unmanaged nodes. nodeName := "node1" nodeCIDR := "4.3.2.1/24"