Read the client table.

    Args:
        redis_client: A client to the primary Redis shard.

    Returns:
        A list of information about the nodes in the cluster.

def parse_client_table(redis_client):
    """Read the client table.

    Args:
        redis_client: A client to the primary Redis shard.

    Returns:
        A list of information about the nodes in the cluster.
    """
    NIL_CLIENT_ID = ray.ObjectID.nil().binary()
    message = redis_client.execute_command("RAY.TABLE_LOOKUP",
                                           ray.gcs_utils.TablePrefix.CLIENT,
                                           "", NIL_CLIENT_ID)

    # Handle the case where no clients are returned. This should only
    # occur potentially immediately after the cluster is started.
    if message is None:
        return []

    node_info = {}
    gcs_entry = ray.gcs_utils.GcsTableEntry.GetRootAsGcsTableEntry(message, 0)

    ordered_client_ids = []

    # Since GCS entries are append-only, we override so that
    # only the latest entries are kept.
    for i in range(gcs_entry.EntriesLength()):
        client = (ray.gcs_utils.ClientTableData.GetRootAsClientTableData(
            gcs_entry.Entries(i), 0))

        resources = {
            decode(client.ResourcesTotalLabel(i)):
            client.ResourcesTotalCapacity(i)
            for i in range(client.ResourcesTotalLabelLength())
        }
        client_id = ray.utils.binary_to_hex(client.ClientId())

        # If this client is being removed, then it must
        # have previously been inserted, and
        # it cannot have previously been removed.
        if not client.IsInsertion():
            assert client_id in node_info, "Client removed not found!"
            assert node_info[client_id]["IsInsertion"], (
                "Unexpected duplicate removal of client.")
        else:
            ordered_client_ids.append(client_id)

        node_info[client_id] = {
            "ClientID": client_id,
            "IsInsertion": client.IsInsertion(),
            "NodeManagerAddress": decode(
                client.NodeManagerAddress(), allow_none=True),
            "NodeManagerPort": client.NodeManagerPort(),
            "ObjectManagerPort": client.ObjectManagerPort(),
            "ObjectStoreSocketName": decode(
                client.ObjectStoreSocketName(), allow_none=True),
            "RayletSocketName": decode(
                client.RayletSocketName(), allow_none=True),
            "Resources": resources
        }
    # NOTE: We return the list comprehension below instead of simply doing
    # 'list(node_info.values())' in order to have the nodes appear in the order
    # that they joined the cluster. Python dictionaries do not preserve
    # insertion order. We could use an OrderedDict, but then we'd have to be
    # sure to only insert a given node a single time (clients that die appear
    # twice in the GCS log).
    return [node_info[client_id] for client_id in ordered_client_ids]