gRPC LB policy
diff --git a/src/core/ext/lb_policy/grpclb/grpclb.c b/src/core/ext/lb_policy/grpclb/grpclb.c
new file mode 100644
index 0000000..cfa9669
--- /dev/null
+++ b/src/core/ext/lb_policy/grpclb/grpclb.c
@@ -0,0 +1,836 @@
+/*
+ *
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "src/core/ext/lb_policy/grpclb/grpclb.h"
+#include "src/core/ext/client_config/client_channel_factory.h"
+#include "src/core/ext/client_config/lb_policy_registry.h"
+#include "src/core/ext/client_config/parse_address.h"
+#include "src/core/ext/lb_policy/grpclb/load_balancer_api.h"
+#include "src/core/lib/iomgr/sockaddr_utils.h"
+#include "src/core/lib/support/string.h"
+#include "src/core/lib/surface/call.h"
+#include "src/core/lib/surface/channel.h"
+
+#include <string.h>
+
+#include <grpc/byte_buffer_reader.h>
+#include <grpc/grpc.h>
+#include <grpc/support/alloc.h>
+#include <grpc/support/host_port.h>
+#include <grpc/support/string_util.h>
+
+int grpc_lb_glb_trace = 0;
+
+typedef struct wrapped_rr_closure_arg {
+ grpc_closure *wrapped_closure;
+ grpc_lb_policy *rr_policy;
+} wrapped_rr_closure_arg;
+
+/* The \a on_complete closure passed as part of the pick requires keeping a
+ * reference to its associated round robin instance. We wrap this closure in
+ * order to unref the round robin instance upon its invocation */
+static void wrapped_rr_closure(grpc_exec_ctx *exec_ctx, void *arg,
+ bool success) {
+ wrapped_rr_closure_arg *wc = arg;
+
+ if (wc->rr_policy != NULL) {
+ if (grpc_lb_glb_trace) {
+ gpr_log(GPR_INFO, "Unreffing RR %p", wc->rr_policy);
+ }
+ GRPC_LB_POLICY_UNREF(exec_ctx, wc->rr_policy, "wrapped_rr_closure");
+ }
+
+ if (wc->wrapped_closure != NULL) {
+ grpc_exec_ctx_enqueue(exec_ctx, wc->wrapped_closure, success, NULL);
+ }
+ gpr_free(wc);
+}
+
+typedef struct pending_pick {
+ struct pending_pick *next;
+ grpc_polling_entity *pollent;
+ grpc_metadata_batch *initial_metadata;
+ uint32_t initial_metadata_flags;
+ grpc_connected_subchannel **target;
+ grpc_closure *wrapped_on_complete;
+ wrapped_rr_closure_arg *wrapped_on_complete_arg;
+} pending_pick;
+
+typedef struct pending_ping {
+ struct pending_ping *next;
+ grpc_closure *wrapped_notify;
+ wrapped_rr_closure_arg *wrapped_notify_arg;
+} pending_ping;
+
+typedef struct glb_lb_policy glb_lb_policy;
+
+#define MAX_LBCD_OPS_LEN 6
+typedef struct lb_client_data {
+ gpr_mu mu;
+ grpc_closure md_sent;
+ grpc_closure md_rcvd;
+ grpc_closure req_sent;
+ grpc_closure res_rcvd;
+ grpc_closure close_sent;
+ grpc_closure srv_status_rcvd;
+
+ grpc_call *c;
+ gpr_timespec deadline;
+
+ grpc_metadata_array initial_metadata_recv;
+ grpc_metadata_array trailing_metadata_recv;
+
+ grpc_byte_buffer *request_payload;
+ grpc_byte_buffer *response_payload;
+
+ grpc_status_code status;
+ char *status_details;
+ size_t status_details_capacity;
+
+ glb_lb_policy *p;
+} lb_client_data;
+
+/* Keeps track and reacts to changes in connectivity of the RR instance */
+typedef struct rr_connectivity_data {
+ grpc_closure on_change;
+ grpc_connectivity_state state;
+ glb_lb_policy *p;
+} rr_connectivity_data;
+
+struct glb_lb_policy {
+ /** base policy: must be first */
+ grpc_lb_policy base;
+
+ /** mutex protecting remaining members */
+ gpr_mu mu;
+
+ grpc_client_channel_factory *cc_factory;
+
+ /** for communicating with the LB server */
+ grpc_channel *lb_server_channel;
+
+ /** the RR policy to use of the backend servers returned by the LB server */
+ grpc_lb_policy *rr_policy;
+
+ bool started_picking;
+
+ /** our connectivity state tracker */
+ grpc_connectivity_state_tracker state_tracker;
+
+ grpc_grpclb_serverlist *serverlist;
+
+ /** list of picks that are waiting on connectivity */
+ pending_pick *pending_picks;
+
+ /** list of pings that are waiting on connectivity */
+ pending_ping *pending_pings;
+
+ /** data associated with the communication with the LB server */
+ lb_client_data *lbcd;
+
+ /** for tracking of the RR connectivity */
+ rr_connectivity_data *rr_connectivity;
+};
+
+static void rr_handover(grpc_exec_ctx *exec_ctx, glb_lb_policy *p);
+static void rr_connectivity_changed(grpc_exec_ctx *exec_ctx, void *arg,
+ bool iomgr_success) {
+ rr_connectivity_data *rrcd = arg;
+ if (!iomgr_success) {
+ gpr_free(rrcd);
+ return;
+ }
+ glb_lb_policy *p = rrcd->p;
+ const grpc_connectivity_state new_state = p->rr_connectivity->state;
+ if (new_state == GRPC_CHANNEL_SHUTDOWN && p->serverlist != NULL) {
+ /* a RR policy is shutting down but there's a serverlist available ->
+ * perform a handover */
+ rr_handover(exec_ctx, p);
+ } else {
+ grpc_connectivity_state_set(exec_ctx, &p->state_tracker, new_state,
+ "rr_connectivity_changed");
+ /* resubscribe */
+ grpc_lb_policy_notify_on_state_change(exec_ctx, p->rr_policy,
+ &p->rr_connectivity->state,
+ &p->rr_connectivity->on_change);
+ }
+}
+
+static void add_pending_pick(pending_pick **root, grpc_polling_entity *pollent,
+ grpc_metadata_batch *initial_metadata,
+ uint32_t initial_metadata_flags,
+ grpc_connected_subchannel **target,
+ grpc_closure *on_complete) {
+ pending_pick *pp = gpr_malloc(sizeof(*pp));
+ memset(pp, 0, sizeof(pending_pick));
+ pp->wrapped_on_complete_arg = gpr_malloc(sizeof(wrapped_rr_closure_arg));
+ memset(pp->wrapped_on_complete_arg, 0, sizeof(wrapped_rr_closure_arg));
+ pp->next = *root;
+ pp->pollent = pollent;
+ pp->target = target;
+ pp->initial_metadata = initial_metadata;
+ pp->initial_metadata_flags = initial_metadata_flags;
+ pp->wrapped_on_complete =
+ grpc_closure_create(wrapped_rr_closure, pp->wrapped_on_complete_arg);
+ pp->wrapped_on_complete_arg->wrapped_closure = on_complete;
+ *root = pp;
+}
+
+static void add_pending_ping(pending_ping **root, grpc_closure *notify) {
+ pending_ping *pping = gpr_malloc(sizeof(*pping));
+ memset(pping, 0, sizeof(pending_ping));
+ pping->wrapped_notify_arg = gpr_malloc(sizeof(wrapped_rr_closure_arg));
+ memset(pping->wrapped_notify_arg, 0, sizeof(wrapped_rr_closure_arg));
+ pping->next = *root;
+ pping->wrapped_notify =
+ grpc_closure_create(wrapped_rr_closure, pping->wrapped_notify_arg);
+ pping->wrapped_notify_arg->wrapped_closure = notify;
+ *root = pping;
+}
+
+static void lb_client_data_destroy(lb_client_data *lbcd);
+
+static void md_sent_cb(grpc_exec_ctx *exec_ctx, void *arg, bool success) {
+ lb_client_data *lbcd = arg;
+ GPR_ASSERT(lbcd->c);
+ grpc_call_error error;
+ grpc_op ops[1];
+ memset(ops, 0, sizeof(ops));
+ grpc_op *op = ops;
+ op->op = GRPC_OP_RECV_INITIAL_METADATA;
+ op->data.recv_initial_metadata = &lbcd->initial_metadata_recv;
+ op->flags = 0;
+ op->reserved = NULL;
+ op++;
+ error = grpc_call_start_batch_and_execute(exec_ctx, lbcd->c, ops,
+ (size_t)(op - ops), &lbcd->md_rcvd);
+ GPR_ASSERT(GRPC_CALL_OK == error);
+}
+
+static void md_recv_cb(grpc_exec_ctx *exec_ctx, void *arg, bool success) {
+ lb_client_data *lbcd = arg;
+ GPR_ASSERT(lbcd->c);
+ grpc_call_error error;
+ grpc_op ops[1];
+ memset(ops, 0, sizeof(ops));
+ grpc_op *op = ops;
+
+ op->op = GRPC_OP_SEND_MESSAGE;
+ op->data.send_message = lbcd->request_payload;
+ op->flags = 0;
+ op->reserved = NULL;
+ op++;
+ error = grpc_call_start_batch_and_execute(
+ exec_ctx, lbcd->c, ops, (size_t)(op - ops), &lbcd->req_sent);
+ GPR_ASSERT(GRPC_CALL_OK == error);
+}
+
+static void req_sent_cb(grpc_exec_ctx *exec_ctx, void *arg, bool success) {
+ lb_client_data *lbcd = arg;
+ grpc_call_error error;
+
+ grpc_op ops[1];
+ memset(ops, 0, sizeof(ops));
+ grpc_op *op = ops;
+
+ op->op = GRPC_OP_RECV_MESSAGE;
+ op->data.recv_message = &lbcd->response_payload;
+ op->flags = 0;
+ op->reserved = NULL;
+ op++;
+ error = grpc_call_start_batch_and_execute(
+ exec_ctx, lbcd->c, ops, (size_t)(op - ops), &lbcd->res_rcvd);
+ GPR_ASSERT(GRPC_CALL_OK == error);
+}
+
+static void res_rcvd_cb(grpc_exec_ctx *exec_ctx, void *arg, bool success) {
+ /* look inside lbcd->response_payload, ideally to send it back as the
+ * serverlist. */
+ lb_client_data *lbcd = arg;
+ grpc_op ops[2];
+ memset(ops, 0, sizeof(ops));
+ grpc_op *op = ops;
+ if (lbcd->response_payload) {
+ grpc_byte_buffer_reader bbr;
+ grpc_byte_buffer_reader_init(&bbr, lbcd->response_payload);
+ gpr_slice response_slice = grpc_byte_buffer_reader_readall(&bbr);
+ grpc_byte_buffer_destroy(lbcd->response_payload);
+ grpc_grpclb_serverlist *serverlist =
+ grpc_grpclb_response_parse_serverlist(response_slice);
+ if (serverlist) {
+ gpr_slice_unref(response_slice);
+ if (grpc_lb_glb_trace) {
+ gpr_log(GPR_INFO, "Serverlist with %zu servers received",
+ serverlist->num_servers);
+ }
+ /* update serverlist */
+ if (serverlist->num_servers > 0) {
+ if (grpc_grpclb_serverlist_equals(lbcd->p->serverlist, serverlist)) {
+ gpr_log(GPR_INFO,
+ "Incoming server list identical to current, ignoring.");
+ } else {
+ if (lbcd->p->serverlist != NULL) {
+ grpc_grpclb_destroy_serverlist(lbcd->p->serverlist);
+ }
+ lbcd->p->serverlist = serverlist;
+ }
+ }
+ if (lbcd->p->rr_policy == NULL) {
+ /* initial "handover", in this case from a null RR policy, meaning it'll
+ * just create the first one */
+ rr_handover(exec_ctx, lbcd->p);
+ } else {
+ /* unref the RR policy, eventually leading to its substitution with a
+ * new one constructed from the received serverlist (see
+ * rr_connectivity_changed) */
+ GRPC_LB_POLICY_UNREF(exec_ctx, lbcd->p->rr_policy,
+ "serverlist_received");
+ }
+
+ /* listen for a potential serverlist update */
+ op->op = GRPC_OP_RECV_MESSAGE;
+ op->data.recv_message = &lbcd->response_payload;
+ op->flags = 0;
+ op->reserved = NULL;
+ op++;
+ const grpc_call_error error = grpc_call_start_batch_and_execute(
+ exec_ctx, lbcd->c, ops, (size_t)(op - ops),
+ &lbcd->res_rcvd); /* loop */
+ GPR_ASSERT(GRPC_CALL_OK == error);
+ return;
+ } else {
+ gpr_log(GPR_ERROR, "Invalid LB response received: '%s'",
+ gpr_dump_slice(response_slice, GPR_DUMP_ASCII));
+ gpr_slice_unref(response_slice);
+
+ /* Disconnect from server returning invalid response. */
+ op->op = GRPC_OP_SEND_CLOSE_FROM_CLIENT;
+ op->flags = 0;
+ op->reserved = NULL;
+ op++;
+ grpc_call_error error = grpc_call_start_batch_and_execute(
+ exec_ctx, lbcd->c, ops, (size_t)(op - ops), &lbcd->close_sent);
+ GPR_ASSERT(GRPC_CALL_OK == error);
+ }
+ }
+ /* empty payload: call cancelled by server. Cleanups happening in
+ * srv_status_rcvd_cb */
+}
+static void close_sent_cb(grpc_exec_ctx *exec_ctx, void *arg, bool success) {
+ if (grpc_lb_glb_trace) {
+ gpr_log(GPR_INFO,
+ "Close from LB client sent. Waiting from server status now");
+ }
+}
+static void srv_status_rcvd_cb(grpc_exec_ctx *exec_ctx, void *arg,
+ bool success) {
+ lb_client_data *lbcd = arg;
+ glb_lb_policy *p = lbcd->p;
+ if (grpc_lb_glb_trace) {
+ gpr_log(
+ GPR_INFO,
+ "status from lb server received. Status = %d, Details = '%s', Capaticy "
+ "= %zu",
+ lbcd->status, lbcd->status_details, lbcd->status_details_capacity);
+ }
+
+ grpc_call_destroy(lbcd->c);
+ grpc_channel_destroy(lbcd->p->lb_server_channel);
+ lbcd->p->lb_server_channel = NULL;
+ lb_client_data_destroy(lbcd);
+ p->lbcd = NULL;
+}
+
+static lb_client_data *lb_client_data_create(glb_lb_policy *p) {
+ lb_client_data *lbcd = gpr_malloc(sizeof(lb_client_data));
+ memset(lbcd, 0, sizeof(lb_client_data));
+
+ gpr_mu_init(&lbcd->mu);
+ grpc_closure_init(&lbcd->md_sent, md_sent_cb, lbcd);
+
+ grpc_closure_init(&lbcd->md_rcvd, md_recv_cb, lbcd);
+ grpc_closure_init(&lbcd->req_sent, req_sent_cb, lbcd);
+ grpc_closure_init(&lbcd->res_rcvd, res_rcvd_cb, lbcd);
+ grpc_closure_init(&lbcd->close_sent, close_sent_cb, lbcd);
+ grpc_closure_init(&lbcd->srv_status_rcvd, srv_status_rcvd_cb, lbcd);
+
+ /* TODO(dgq): get the deadline from the client/user instead of fabricating
+ * one
+ * here. Make it a policy arg? */
+ lbcd->deadline = gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC),
+ gpr_time_from_seconds(3, GPR_TIMESPAN));
+
+ lbcd->c = grpc_channel_create_pollset_set_call(
+ p->lb_server_channel, NULL, GRPC_PROPAGATE_DEFAULTS,
+ p->base.interested_parties, "/BalanceLoad",
+ NULL, /* FIXME(dgq): which "host" value to use? */
+ lbcd->deadline, NULL);
+
+ grpc_metadata_array_init(&lbcd->initial_metadata_recv);
+ grpc_metadata_array_init(&lbcd->trailing_metadata_recv);
+
+ grpc_grpclb_request *request = grpc_grpclb_request_create(
+ "load.balanced.service.name"); /* FIXME(dgq): get the name of the load
+ balanced service from above. */
+ gpr_slice request_payload_slice = grpc_grpclb_request_encode(request);
+ lbcd->request_payload =
+ grpc_raw_byte_buffer_create(&request_payload_slice, 1);
+ gpr_slice_unref(request_payload_slice);
+ grpc_grpclb_request_destroy(request);
+
+ lbcd->status_details = NULL;
+ lbcd->status_details_capacity = 0;
+ lbcd->p = p;
+ return lbcd;
+}
+
+static void lb_client_data_destroy(lb_client_data *lbcd) {
+ grpc_metadata_array_destroy(&lbcd->initial_metadata_recv);
+ grpc_metadata_array_destroy(&lbcd->trailing_metadata_recv);
+
+ grpc_byte_buffer_destroy(lbcd->request_payload);
+
+ gpr_free(lbcd->status_details);
+ gpr_mu_destroy(&lbcd->mu);
+ gpr_free(lbcd);
+}
+
+static void glb_destroy(grpc_exec_ctx *exec_ctx, grpc_lb_policy *pol) {
+ glb_lb_policy *p = (glb_lb_policy *)pol;
+ GPR_ASSERT(p->pending_picks == NULL);
+ GPR_ASSERT(p->pending_pings == NULL);
+ grpc_connectivity_state_destroy(exec_ctx, &p->state_tracker);
+ if (p->serverlist != NULL) {
+ grpc_grpclb_destroy_serverlist(p->serverlist);
+ }
+ gpr_mu_destroy(&p->mu);
+ gpr_free(p);
+}
+
+static void glb_shutdown(grpc_exec_ctx *exec_ctx, grpc_lb_policy *pol) {
+ glb_lb_policy *p = (glb_lb_policy *)pol;
+ gpr_mu_lock(&p->mu);
+
+ pending_pick *pp = p->pending_picks;
+ p->pending_picks = NULL;
+ pending_ping *pping = p->pending_pings;
+ p->pending_pings = NULL;
+ gpr_mu_unlock(&p->mu);
+
+ while (pp != NULL) {
+ pending_pick *next = pp->next;
+ *pp->target = NULL;
+ grpc_exec_ctx_enqueue(exec_ctx, pp->wrapped_on_complete, true, NULL);
+ gpr_free(pp);
+ pp = next;
+ }
+
+ while (pping != NULL) {
+ pending_ping *next = pping->next;
+ grpc_exec_ctx_enqueue(exec_ctx, pping->wrapped_notify, true, NULL);
+ pping = next;
+ }
+
+ if (p->rr_policy) {
+ /* unsubscribe */
+ grpc_lb_policy_notify_on_state_change(exec_ctx, p->rr_policy, NULL,
+ &p->rr_connectivity->on_change);
+ GRPC_LB_POLICY_UNREF(exec_ctx, p->rr_policy, "glb_shutdown");
+ }
+
+ grpc_connectivity_state_set(exec_ctx, &p->state_tracker,
+ GRPC_CHANNEL_SHUTDOWN, "glb_shutdown");
+}
+
+static void glb_cancel_pick(grpc_exec_ctx *exec_ctx, grpc_lb_policy *pol,
+ grpc_connected_subchannel **target) {
+ glb_lb_policy *p = (glb_lb_policy *)pol;
+ gpr_mu_lock(&p->mu);
+ pending_pick *pp = p->pending_picks;
+ p->pending_picks = NULL;
+ while (pp != NULL) {
+ pending_pick *next = pp->next;
+ if (pp->target == target) {
+ grpc_polling_entity_del_from_pollset_set(exec_ctx, pp->pollent,
+ p->base.interested_parties);
+ *target = NULL;
+ grpc_exec_ctx_enqueue(exec_ctx, pp->wrapped_on_complete, false, NULL);
+ gpr_free(pp);
+ } else {
+ pp->next = p->pending_picks;
+ p->pending_picks = pp;
+ }
+ pp = next;
+ }
+ gpr_mu_unlock(&p->mu);
+}
+
+static void glb_cancel_picks(grpc_exec_ctx *exec_ctx, grpc_lb_policy *pol,
+ uint32_t initial_metadata_flags_mask,
+ uint32_t initial_metadata_flags_eq) {
+ glb_lb_policy *p = (glb_lb_policy *)pol;
+ gpr_mu_lock(&p->mu);
+ if (p->lbcd != NULL) {
+ /* cancel the call to the load balancer service, if any */
+ grpc_call_cancel(p->lbcd->c, NULL);
+ }
+ pending_pick *pp = p->pending_picks;
+ p->pending_picks = NULL;
+ while (pp != NULL) {
+ pending_pick *next = pp->next;
+ if ((pp->initial_metadata_flags & initial_metadata_flags_mask) ==
+ initial_metadata_flags_eq) {
+ grpc_polling_entity_del_from_pollset_set(exec_ctx, pp->pollent,
+ p->base.interested_parties);
+ grpc_exec_ctx_enqueue(exec_ctx, pp->wrapped_on_complete, false, NULL);
+ gpr_free(pp);
+ } else {
+ pp->next = p->pending_picks;
+ p->pending_picks = pp;
+ }
+ pp = next;
+ }
+ gpr_mu_unlock(&p->mu);
+}
+
+static void query_for_backends(grpc_exec_ctx *exec_ctx, glb_lb_policy *p) {
+ GPR_ASSERT(p->lb_server_channel != NULL);
+
+ p->lbcd = lb_client_data_create(p);
+ grpc_call_error error;
+ grpc_op ops[1];
+ memset(ops, 0, sizeof(ops));
+ grpc_op *op = ops;
+ op->op = GRPC_OP_SEND_INITIAL_METADATA;
+ op->data.send_initial_metadata.count = 0;
+ op->flags = 0;
+ op->reserved = NULL;
+ op++;
+ error = grpc_call_start_batch_and_execute(
+ exec_ctx, p->lbcd->c, ops, (size_t)(op - ops), &p->lbcd->md_sent);
+ GPR_ASSERT(GRPC_CALL_OK == error);
+
+ op = ops;
+ op->op = GRPC_OP_RECV_STATUS_ON_CLIENT;
+ op->data.recv_status_on_client.trailing_metadata =
+ &p->lbcd->trailing_metadata_recv;
+ op->data.recv_status_on_client.status = &p->lbcd->status;
+ op->data.recv_status_on_client.status_details = &p->lbcd->status_details;
+ op->data.recv_status_on_client.status_details_capacity =
+ &p->lbcd->status_details_capacity;
+ op->flags = 0;
+ op->reserved = NULL;
+ op++;
+ error = grpc_call_start_batch_and_execute(
+ exec_ctx, p->lbcd->c, ops, (size_t)(op - ops), &p->lbcd->srv_status_rcvd);
+ GPR_ASSERT(GRPC_CALL_OK == error);
+}
+
+static grpc_lb_policy *create_rr(grpc_exec_ctx *exec_ctx,
+ const grpc_grpclb_serverlist *serverlist,
+ glb_lb_policy *p) {
+ /* TODO(dgq): support mixed ip version */
+ GPR_ASSERT(serverlist != NULL && serverlist->num_servers > 0);
+ char **host_ports = gpr_malloc(sizeof(char *) * serverlist->num_servers);
+ for (size_t i = 0; i < serverlist->num_servers; ++i) {
+ gpr_join_host_port(&host_ports[i], serverlist->servers[i]->ip_address,
+ serverlist->servers[i]->port);
+ }
+
+ size_t uri_path_len;
+ char *concat_ipports = gpr_strjoin_sep(
+ (const char **)host_ports, serverlist->num_servers, ",", &uri_path_len);
+
+ grpc_lb_policy_args args;
+ args.client_channel_factory = p->cc_factory;
+ args.addresses = gpr_malloc(sizeof(grpc_resolved_addresses));
+ args.addresses->naddrs = serverlist->num_servers;
+ args.addresses->addrs =
+ gpr_malloc(sizeof(grpc_resolved_address) * args.addresses->naddrs);
+ size_t out_addrs_idx = 0;
+ for (size_t i = 0; i < serverlist->num_servers; ++i) {
+ grpc_uri uri;
+ struct sockaddr_storage sa;
+ size_t sa_len;
+ uri.path = host_ports[i];
+ if (parse_ipv4(&uri, &sa, &sa_len)) { /* TODO(dgq): add support for ipv6 */
+ memcpy(args.addresses->addrs[out_addrs_idx].addr, &sa, sa_len);
+ args.addresses->addrs[out_addrs_idx].len = sa_len;
+ ++out_addrs_idx;
+ } else {
+ gpr_log(GPR_ERROR, "Invalid LB service address '%s', ignoring.",
+ host_ports[i]);
+ }
+ }
+
+ grpc_lb_policy *rr = grpc_lb_policy_create(exec_ctx, "round_robin", &args);
+
+ gpr_free(concat_ipports);
+ for (size_t i = 0; i < serverlist->num_servers; i++) {
+ gpr_free(host_ports[i]);
+ }
+ gpr_free(host_ports);
+
+ gpr_free(args.addresses->addrs);
+ gpr_free(args.addresses);
+
+ return rr;
+}
+
+static void start_picking(grpc_exec_ctx *exec_ctx, glb_lb_policy *p) {
+ p->started_picking = true;
+ query_for_backends(exec_ctx, p);
+}
+
+static void rr_handover(grpc_exec_ctx *exec_ctx, glb_lb_policy *p) {
+ p->rr_policy = create_rr(exec_ctx, p->serverlist, p);
+ if (grpc_lb_glb_trace) {
+ gpr_log(GPR_INFO, "Created RR policy (0x%" PRIxPTR ")",
+ (intptr_t)p->rr_policy);
+ }
+ GPR_ASSERT(p->rr_policy != NULL);
+ p->rr_connectivity->state =
+ grpc_lb_policy_check_connectivity(exec_ctx, p->rr_policy);
+ grpc_lb_policy_notify_on_state_change(exec_ctx, p->rr_policy,
+ &p->rr_connectivity->state,
+ &p->rr_connectivity->on_change);
+ grpc_connectivity_state_set(exec_ctx, &p->state_tracker,
+ p->rr_connectivity->state, "rr_handover");
+ grpc_lb_policy_exit_idle(exec_ctx, p->rr_policy);
+
+ /* flush pending ops */
+ pending_pick *pp;
+ while ((pp = p->pending_picks)) {
+ p->pending_picks = pp->next;
+ GRPC_LB_POLICY_REF(p->rr_policy, "rr_handover_pending_pick");
+ pp->wrapped_on_complete_arg->rr_policy = p->rr_policy;
+ if (grpc_lb_glb_trace) {
+ gpr_log(GPR_INFO, "Pending pick about to PICK from 0x%"PRIxPTR"", (intptr_t)p->rr_policy);
+ }
+ grpc_lb_policy_pick(exec_ctx, p->rr_policy, pp->pollent,
+ pp->initial_metadata, pp->initial_metadata_flags,
+ pp->target, pp->wrapped_on_complete);
+ gpr_free(pp);
+ }
+
+ pending_ping *pping;
+ while ((pping = p->pending_pings)) {
+ p->pending_pings = pping->next;
+ GRPC_LB_POLICY_REF(p->rr_policy, "rr_handover_pending_ping");
+ pping->wrapped_notify_arg->rr_policy = p->rr_policy;
+ if (grpc_lb_glb_trace) {
+ gpr_log(GPR_INFO, "Pending ping about to PING from 0x%"PRIxPTR"", (intptr_t)p->rr_policy);
+ }
+ grpc_lb_policy_ping_one(exec_ctx, p->rr_policy, pping->wrapped_notify);
+ gpr_free(pping);
+ }
+}
+
+static void glb_exit_idle(grpc_exec_ctx *exec_ctx, grpc_lb_policy *pol) {
+ glb_lb_policy *p = (glb_lb_policy *)pol;
+ gpr_mu_lock(&p->mu);
+ if (!p->started_picking) {
+ start_picking(exec_ctx, p);
+ }
+ gpr_mu_unlock(&p->mu);
+}
+
+static int glb_pick(grpc_exec_ctx *exec_ctx, grpc_lb_policy *pol,
+ grpc_polling_entity *pollent,
+ grpc_metadata_batch *initial_metadata,
+ uint32_t initial_metadata_flags,
+ grpc_connected_subchannel **target,
+ grpc_closure *on_complete) {
+ glb_lb_policy *p = (glb_lb_policy *)pol;
+ gpr_mu_lock(&p->mu);
+ int r;
+
+ if (p->rr_policy != NULL) {
+ if (grpc_lb_glb_trace) {
+ gpr_log(GPR_INFO, "about to PICK from 0x%"PRIxPTR"", (intptr_t)p->rr_policy);
+ }
+ GRPC_LB_POLICY_REF(p->rr_policy, "rr_pick");
+ wrapped_rr_closure_arg *warg = gpr_malloc(sizeof(wrapped_rr_closure_arg));
+ warg->rr_policy = p->rr_policy;
+ warg->wrapped_closure = on_complete;
+ grpc_closure *wrapped_on_complete =
+ grpc_closure_create(wrapped_rr_closure, warg);
+ r = grpc_lb_policy_pick(exec_ctx, p->rr_policy, pollent, initial_metadata,
+ initial_metadata_flags, target,
+ wrapped_on_complete);
+ if (r != 0) {
+ /* the call to grpc_lb_policy_pick has been sychronous. Invoke a neutered
+ * wrapped closure */
+ warg->wrapped_closure = NULL;
+ grpc_exec_ctx_enqueue(exec_ctx, wrapped_on_complete, false, NULL);
+ }
+ } else {
+ grpc_polling_entity_add_to_pollset_set(exec_ctx, pollent,
+ p->base.interested_parties);
+ add_pending_pick(&p->pending_picks, pollent, initial_metadata,
+ initial_metadata_flags, target, on_complete);
+
+ if (!p->started_picking) {
+ start_picking(exec_ctx, p);
+ }
+ r = 0;
+ }
+ gpr_mu_unlock(&p->mu);
+ return r;
+}
+
+static grpc_connectivity_state glb_check_connectivity(grpc_exec_ctx *exec_ctx,
+ grpc_lb_policy *pol) {
+ glb_lb_policy *p = (glb_lb_policy *)pol;
+ grpc_connectivity_state st;
+ gpr_mu_lock(&p->mu);
+ st = grpc_connectivity_state_check(&p->state_tracker);
+ gpr_mu_unlock(&p->mu);
+ return st;
+}
+
+static void glb_ping_one(grpc_exec_ctx *exec_ctx, grpc_lb_policy *pol,
+ grpc_closure *closure) {
+ glb_lb_policy *p = (glb_lb_policy *)pol;
+ gpr_mu_lock(&p->mu);
+ if (p->rr_policy) {
+ grpc_lb_policy_ping_one(exec_ctx, p->rr_policy, closure);
+ } else {
+ add_pending_ping(&p->pending_pings, closure);
+ if (!p->started_picking) {
+ start_picking(exec_ctx, p);
+ }
+ }
+ gpr_mu_unlock(&p->mu);
+}
+
+static void glb_notify_on_state_change(grpc_exec_ctx *exec_ctx,
+ grpc_lb_policy *pol,
+ grpc_connectivity_state *current,
+ grpc_closure *notify) {
+ glb_lb_policy *p = (glb_lb_policy *)pol;
+ gpr_mu_lock(&p->mu);
+ grpc_connectivity_state_notify_on_state_change(exec_ctx, &p->state_tracker,
+ current, notify);
+
+ gpr_mu_unlock(&p->mu);
+}
+
+static const grpc_lb_policy_vtable glb_lb_policy_vtable = {
+ glb_destroy, glb_shutdown, glb_pick,
+ glb_cancel_pick, glb_cancel_picks, glb_ping_one,
+ glb_exit_idle, glb_check_connectivity, glb_notify_on_state_change};
+
+static void glb_factory_ref(grpc_lb_policy_factory *factory) {}
+
+static void glb_factory_unref(grpc_lb_policy_factory *factory) {}
+
+static grpc_lb_policy *glb_create(grpc_exec_ctx *exec_ctx,
+ grpc_lb_policy_factory *factory,
+ grpc_lb_policy_args *args) {
+ glb_lb_policy *p = gpr_malloc(sizeof(*p));
+ memset(p, 0, sizeof(*p));
+
+ /* all input addresses in args->addresses come from a resolver that claims
+ * they are LB services.
+ *
+ * Create a client channel over them to communicate with a LB service */
+ p->cc_factory = args->client_channel_factory;
+ GPR_ASSERT(p->cc_factory != NULL);
+ if (args->addresses->naddrs == 0) {
+ return NULL;
+ }
+
+ /* construct a target from the args->addresses, in the form
+ * ipvX://ip1:port1,ip2:port2,...
+ * TODO(dgq): support mixed ip version */
+ char **addr_strs = gpr_malloc(sizeof(char *) * args->addresses->naddrs);
+ addr_strs[0] =
+ grpc_sockaddr_to_uri((const struct sockaddr *)&args->addresses->addrs[0]);
+ for (size_t i = 1; i < args->addresses->naddrs; i++) {
+ GPR_ASSERT(grpc_sockaddr_to_string(
+ &addr_strs[i],
+ (const struct sockaddr *)&args->addresses->addrs[i],
+ true) == 0);
+ }
+ size_t uri_path_len;
+ char *target_uri_str = gpr_strjoin_sep(
+ (const char **)addr_strs, args->addresses->naddrs, ",", &uri_path_len);
+
+ /* will pick using pick_first */
+ p->lb_server_channel = grpc_client_channel_factory_create_channel(
+ exec_ctx, p->cc_factory, target_uri_str,
+ GRPC_CLIENT_CHANNEL_TYPE_LOAD_BALANCING, NULL);
+
+ gpr_free(target_uri_str);
+ for (size_t i = 0; i < args->addresses->naddrs; i++) {
+ gpr_free(addr_strs[i]);
+ }
+ gpr_free(addr_strs);
+
+ if (p->lb_server_channel == NULL) {
+ gpr_free(p);
+ return NULL;
+ }
+
+ rr_connectivity_data *rr_connectivity =
+ gpr_malloc(sizeof(rr_connectivity_data));
+ memset(rr_connectivity, 0, sizeof(rr_connectivity_data));
+ grpc_closure_init(&rr_connectivity->on_change, rr_connectivity_changed,
+ rr_connectivity);
+ rr_connectivity->p = p;
+ p->rr_connectivity = rr_connectivity;
+
+ grpc_lb_policy_init(&p->base, &glb_lb_policy_vtable);
+ gpr_mu_init(&p->mu);
+ grpc_connectivity_state_init(&p->state_tracker, GRPC_CHANNEL_IDLE, "grpclb");
+ return &p->base;
+}
+
+static const grpc_lb_policy_factory_vtable glb_factory_vtable = {
+ glb_factory_ref, glb_factory_unref, glb_create, "grpclb"};
+
+static grpc_lb_policy_factory glb_lb_policy_factory = {&glb_factory_vtable};
+
+grpc_lb_policy_factory *grpc_glb_lb_factory_create() {
+ return &glb_lb_policy_factory;
+}
+
+/* Plugin registration */
+
+void grpc_lb_policy_grpclb_init() {
+ grpc_register_lb_policy(grpc_glb_lb_factory_create());
+ grpc_register_tracer("glb", &grpc_lb_glb_trace);
+}
+
+void grpc_lb_policy_grpclb_shutdown() {}