Go over the existing routes and check whether they still match the spec.

    If the chosen router has failed or is questionable or is not in the host
    list anymore, the route needs to be updated. If the CIDR isn't in the spec
    at all anymore then it needs to be deleted.

    Keeps track of the routes we have seen in each RT and populates the
    passed-in routes_in_rts dictionary with that info.

    Returns a dict with the routers chosen for the various routes we
    encountered.

def _update_existing_routes(route_spec, failed_ips, questionable_ips,
                            vpc_info, con, routes_in_rts):
    """
    Go over the existing routes and check whether they still match the spec.

    If the chosen router has failed or is questionable or is not in the host
    list anymore, the route needs to be updated. If the CIDR isn't in the spec
    at all anymore then it needs to be deleted.

    Keeps track of the routes we have seen in each RT and populates the
    passed-in routes_in_rts dictionary with that info.

    Returns a dict with the routers chosen for the various routes we
    encountered.

    """
    chosen_routers = {}              # keep track of chosen routers for CIDRs
    NONE_HEALTHY   = "none-healthy"  # used as marker in chosen_routers
    for rt in vpc_info['route_tables']:
        # Check whether this RT is associated with cluster node subnets. If it
        # is not then we will perform NO operations on this RT. We will also
        # not collect the routes from this RT.
        rt_subnets           = \
                    set(vpc_info.get('rt_subnet_lookup', {}).get(rt.id, []))
        cluster_node_subnets = \
                    set(vpc_info.get('cluster_node_subnets', []))
        if cluster_node_subnets and \
                        (not rt_subnets or
                         not rt_subnets.intersection(cluster_node_subnets)):
            logging.debug("Skipping processing of RT '%s' since RT's "
                          "subnets (%s) are not part of cluster (%s)." %
                          (rt.id,
                           ", ".join(rt_subnets) if rt_subnets else "none",
                           ", ".join(cluster_node_subnets)))
            continue

        routes_in_rts[rt.id] = []
        # Iterate over all the routes we find in each RT
        for r in rt.routes:
            dcidr = r.destination_cidr_block

            if _is_cidr_in_ignore_routes(dcidr):
                # A list of CIDRs may have been specified on the command line
                # via the --ignore_routes option. If the destination CIDR of
                # this route here is contained in any of those specified CIDRs
                # then we will not touch or change this route. Often this is
                # used to protect routes to special instances, such as
                # proxies or NAT instances.
                _rt_state_update(rt.id, dcidr,
                                 msg="Ignored: Protected CIDR.")
                continue

            if r.instance_id is None and r.interface_id is None:
                # There are some routes already present in the route table,
                # which we don't need to mess with. Specifically, routes that
                # aren't attached to a particular instance or interface.
                # We skip those.
                _rt_state_update(rt.id, dcidr,
                                 msg="Ignored: Not a route to an instance")
                continue

            routes_in_rts[rt.id].append(dcidr)  # remember we've seen the route

            hosts = route_spec.get(dcidr)       # eligible routers for CIDR

            # Current router host for this CIDR/route.
            inst_id, ipaddr, eni_id = \
                                _get_host_for_route(vpc_info, r, rt, dcidr)

            if not hosts:
                # The route isn't in the spec anymore and should be
                # deleted.
                logging.info("--- route not in spec, deleting in "
                             "RT '%s': %s -> ... (%s, %s)" %
                             (rt.id, dcidr, inst_id, eni_id))

                con.delete_route(route_table_id         = rt.id,
                                 destination_cidr_block = dcidr)
                if dcidr in CURRENT_STATE.routes:
                    del CURRENT_STATE.routes[dcidr]

                continue

            # We have a route and it's still in the spec. Do we need to update
            # the router? Multiple reasons for that:
            # - Router is not in the list of eligible hosts anymore
            # - Router has failed or is questionable
            # - In a different route table we used a different host as the
            #   router.

            # Seen this route in another RT before? This will be None if we'be
            # not seen it before, and will be NONE_HEALTHY, but we couldn't
            # find a single healthy eligible router host for it.
            stored_router_ip = chosen_routers.get(dcidr)

            # Has our current router failed or is questionable?
            ipaddr_should_be_replaced = ipaddr in failed_ips or \
                                        ipaddr in questionable_ips

            # Is the host not eligible anymore?
            ipaddr_not_eligible = ipaddr not in hosts

            shouldnt_use_ipaddr = \
                            ipaddr_should_be_replaced or ipaddr_not_eligible

            if (not stored_router_ip or stored_router_ip == ipaddr) and \
                                                    not shouldnt_use_ipaddr:
                # Haven't seen it before, or points to same router AND
                # router is healthy: All good
                if not stored_router_ip:
                    # Remember this IP as a suitable router for CIDR
                    chosen_routers[dcidr] = ipaddr
                logging.info("--- route exists already in RT '%s': "
                             "%s -> %s (%s, %s)" %
                             (rt.id, dcidr,
                              ipaddr, inst_id, eni_id))
                CURRENT_STATE.routes[dcidr] = (ipaddr, inst_id, eni_id)
                _rt_state_update(rt.id, dcidr, ipaddr, inst_id, eni_id,
                                 msg="Current: Route exist and up to date")
                continue

            if stored_router_ip == NONE_HEALTHY:
                # We've tried to set a route for this before, but
                # couldn't find any healthy hosts. Can't do anything and
                # need to skip.
                CURRENT_STATE.routes[dcidr] = (ipaddr, inst_id, eni_id)
                _rt_state_update(rt.id, dcidr, ipaddr, inst_id, eni_id,
                                 msg="None healthy, black hole: "
                                     "Determined earlier")
                continue

            if stored_router_ip:
                # Just use the router we've seen before. We know that'll work,
                # because only healthy hosts make it into the chosen_routers
                # dict.
                new_router_ip = stored_router_ip
                update_reason = "other RT used different IP"
            else:
                # Haven't seen this route in another RT, so we'll
                # choose a new router
                new_router_ip = _choose_different_host(ipaddr, hosts,
                                                       failed_ips,
                                                       questionable_ips)
                if new_router_ip is None:
                    # Couldn't find healthy host to be router, forced
                    # to skip this one.
                    CURRENT_STATE.routes[dcidr] = (ipaddr, inst_id, eni_id)
                    chosen_routers[dcidr] = NONE_HEALTHY
                    logging.warning("--- cannot find available target "
                                    "for route update %s! "
                                    "Nothing I can do..." % (dcidr))
                    continue

                chosen_routers[dcidr] = new_router_ip
                update_reason = "old IP failed/questionable or " \
                                "not eligible anymore"

            _update_route(dcidr, new_router_ip, ipaddr,
                          vpc_info, con, rt.id, update_reason)

    return chosen_routers