commit
935c171ee8
193 changed files with 5841 additions and 1369 deletions
@ -0,0 +1,59 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2015, 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. |
||||
* |
||||
*/ |
||||
|
||||
/** Support zookeeper as alternative name system in addition to DNS
|
||||
* Zookeeper name in gRPC is represented as a URI: |
||||
* zookeeper://host:port/path/service/instance
|
||||
* |
||||
* Where zookeeper is the name system scheme |
||||
* host:port is the address of a zookeeper server |
||||
* /path/service/instance is the zookeeper name to be resolved |
||||
* |
||||
* Refer doc/naming.md for more details |
||||
*/ |
||||
|
||||
#ifndef GRPC_GRPC_ZOOKEEPER_H |
||||
#define GRPC_GRPC_ZOOKEEPER_H |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
/** Register zookeeper name resolver in grpc */ |
||||
void grpc_zookeeper_register(); |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
|
||||
#endif /* GRPC_GRPC_ZOOKEEPER_H */ |
@ -0,0 +1,501 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2015, 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/client_config/resolvers/zookeeper_resolver.h" |
||||
|
||||
#include <string.h> |
||||
|
||||
#include <grpc/support/alloc.h> |
||||
#include <grpc/support/string_util.h> |
||||
|
||||
#include <grpc/grpc_zookeeper.h> |
||||
#include <zookeeper/zookeeper.h> |
||||
|
||||
#include "src/core/client_config/lb_policies/pick_first.h" |
||||
#include "src/core/client_config/resolver_registry.h" |
||||
#include "src/core/iomgr/resolve_address.h" |
||||
#include "src/core/support/string.h" |
||||
#include "src/core/json/json.h" |
||||
|
||||
/** Zookeeper session expiration time in milliseconds */ |
||||
#define GRPC_ZOOKEEPER_SESSION_TIMEOUT 15000 |
||||
|
||||
typedef struct { |
||||
/** base class: must be first */ |
||||
grpc_resolver base; |
||||
/** refcount */ |
||||
gpr_refcount refs; |
||||
/** name to resolve */ |
||||
char *name; |
||||
/** subchannel factory */ |
||||
grpc_subchannel_factory *subchannel_factory; |
||||
/** load balancing policy factory */ |
||||
grpc_lb_policy *(*lb_policy_factory)(grpc_subchannel **subchannels, |
||||
size_t num_subchannels); |
||||
|
||||
/** mutex guarding the rest of the state */ |
||||
gpr_mu mu; |
||||
/** are we currently resolving? */ |
||||
int resolving; |
||||
/** which version of resolved_config have we published? */ |
||||
int published_version; |
||||
/** which version of resolved_config is current? */ |
||||
int resolved_version; |
||||
/** pending next completion, or NULL */ |
||||
grpc_iomgr_closure *next_completion; |
||||
/** target config address for next completion */ |
||||
grpc_client_config **target_config; |
||||
/** current (fully resolved) config */ |
||||
grpc_client_config *resolved_config; |
||||
|
||||
/** zookeeper handle */ |
||||
zhandle_t *zookeeper_handle; |
||||
/** zookeeper resolved addresses */ |
||||
grpc_resolved_addresses *resolved_addrs; |
||||
/** total number of addresses to be resolved */ |
||||
int resolved_total; |
||||
/** number of addresses resolved */ |
||||
int resolved_num; |
||||
} zookeeper_resolver; |
||||
|
||||
static void zookeeper_destroy(grpc_resolver *r); |
||||
|
||||
static void zookeeper_start_resolving_locked(zookeeper_resolver *r); |
||||
static void zookeeper_maybe_finish_next_locked(zookeeper_resolver *r); |
||||
|
||||
static void zookeeper_shutdown(grpc_resolver *r); |
||||
static void zookeeper_channel_saw_error(grpc_resolver *r, |
||||
struct sockaddr *failing_address, |
||||
int failing_address_len); |
||||
static void zookeeper_next(grpc_resolver *r, grpc_client_config **target_config, |
||||
grpc_iomgr_closure *on_complete); |
||||
|
||||
static const grpc_resolver_vtable zookeeper_resolver_vtable = { |
||||
zookeeper_destroy, zookeeper_shutdown, zookeeper_channel_saw_error, |
||||
zookeeper_next}; |
||||
|
||||
static void zookeeper_shutdown(grpc_resolver *resolver) { |
||||
zookeeper_resolver *r = (zookeeper_resolver *)resolver; |
||||
gpr_mu_lock(&r->mu); |
||||
if (r->next_completion != NULL) { |
||||
*r->target_config = NULL; |
||||
grpc_iomgr_add_callback(r->next_completion); |
||||
r->next_completion = NULL; |
||||
} |
||||
zookeeper_close(r->zookeeper_handle); |
||||
gpr_mu_unlock(&r->mu); |
||||
} |
||||
|
||||
static void zookeeper_channel_saw_error(grpc_resolver *resolver, |
||||
struct sockaddr *sa, int len) { |
||||
zookeeper_resolver *r = (zookeeper_resolver *)resolver; |
||||
gpr_mu_lock(&r->mu); |
||||
if (r->resolving == 0) { |
||||
zookeeper_start_resolving_locked(r); |
||||
} |
||||
gpr_mu_unlock(&r->mu); |
||||
} |
||||
|
||||
static void zookeeper_next(grpc_resolver *resolver, |
||||
grpc_client_config **target_config, |
||||
grpc_iomgr_closure *on_complete) { |
||||
zookeeper_resolver *r = (zookeeper_resolver *)resolver; |
||||
gpr_mu_lock(&r->mu); |
||||
GPR_ASSERT(r->next_completion == NULL); |
||||
r->next_completion = on_complete; |
||||
r->target_config = target_config; |
||||
if (r->resolved_version == 0 && r->resolving == 0) { |
||||
zookeeper_start_resolving_locked(r); |
||||
} else { |
||||
zookeeper_maybe_finish_next_locked(r); |
||||
} |
||||
gpr_mu_unlock(&r->mu); |
||||
} |
||||
|
||||
/** Zookeeper global watcher for connection management
|
||||
TODO: better connection management besides logs */ |
||||
static void zookeeper_global_watcher(zhandle_t *zookeeper_handle, int type, |
||||
int state, const char *path, |
||||
void *watcher_ctx) { |
||||
if (type == ZOO_SESSION_EVENT) { |
||||
if (state == ZOO_EXPIRED_SESSION_STATE) { |
||||
gpr_log(GPR_ERROR, "Zookeeper session expired"); |
||||
} else if (state == ZOO_AUTH_FAILED_STATE) { |
||||
gpr_log(GPR_ERROR, "Zookeeper authentication failed"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** Zookeeper watcher triggered by changes to watched nodes
|
||||
Once triggered, it tries to resolve again to get updated addresses */ |
||||
static void zookeeper_watcher(zhandle_t *zookeeper_handle, int type, int state, |
||||
const char *path, void *watcher_ctx) { |
||||
if (watcher_ctx != NULL) { |
||||
zookeeper_resolver *r = (zookeeper_resolver *)watcher_ctx; |
||||
if (state == ZOO_CONNECTED_STATE) { |
||||
gpr_mu_lock(&r->mu); |
||||
if (r->resolving == 0) { |
||||
zookeeper_start_resolving_locked(r); |
||||
} |
||||
gpr_mu_unlock(&r->mu); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** Callback function after getting all resolved addresses
|
||||
Creates a subchannel for each address */ |
||||
static void zookeeper_on_resolved(void *arg, |
||||
grpc_resolved_addresses *addresses) { |
||||
zookeeper_resolver *r = arg; |
||||
grpc_client_config *config = NULL; |
||||
grpc_subchannel **subchannels; |
||||
grpc_subchannel_args args; |
||||
grpc_lb_policy *lb_policy; |
||||
size_t i; |
||||
if (addresses != NULL) { |
||||
config = grpc_client_config_create(); |
||||
subchannels = gpr_malloc(sizeof(grpc_subchannel *) * addresses->naddrs); |
||||
for (i = 0; i < addresses->naddrs; i++) { |
||||
memset(&args, 0, sizeof(args)); |
||||
args.addr = (struct sockaddr *)(addresses->addrs[i].addr); |
||||
args.addr_len = addresses->addrs[i].len; |
||||
subchannels[i] = grpc_subchannel_factory_create_subchannel( |
||||
r->subchannel_factory, &args); |
||||
} |
||||
lb_policy = r->lb_policy_factory(subchannels, addresses->naddrs); |
||||
grpc_client_config_set_lb_policy(config, lb_policy); |
||||
GRPC_LB_POLICY_UNREF(lb_policy, "construction"); |
||||
grpc_resolved_addresses_destroy(addresses); |
||||
gpr_free(subchannels); |
||||
} |
||||
gpr_mu_lock(&r->mu); |
||||
GPR_ASSERT(r->resolving == 1); |
||||
r->resolving = 0; |
||||
if (r->resolved_config != NULL) { |
||||
grpc_client_config_unref(r->resolved_config); |
||||
} |
||||
r->resolved_config = config; |
||||
r->resolved_version++; |
||||
zookeeper_maybe_finish_next_locked(r); |
||||
gpr_mu_unlock(&r->mu); |
||||
|
||||
GRPC_RESOLVER_UNREF(&r->base, "zookeeper-resolving"); |
||||
} |
||||
|
||||
/** Callback function for each DNS resolved address */ |
||||
static void zookeeper_dns_resolved(void *arg, |
||||
grpc_resolved_addresses *addresses) { |
||||
size_t i; |
||||
zookeeper_resolver *r = arg; |
||||
int resolve_done = 0; |
||||
|
||||
gpr_mu_lock(&r->mu); |
||||
r->resolved_num++; |
||||
r->resolved_addrs->addrs = |
||||
gpr_realloc(r->resolved_addrs->addrs, |
||||
sizeof(grpc_resolved_address) * |
||||
(r->resolved_addrs->naddrs + addresses->naddrs)); |
||||
for (i = 0; i < addresses->naddrs; i++) { |
||||
memcpy(r->resolved_addrs->addrs[i + r->resolved_addrs->naddrs].addr, |
||||
addresses->addrs[i].addr, addresses->addrs[i].len); |
||||
r->resolved_addrs->addrs[i + r->resolved_addrs->naddrs].len = |
||||
addresses->addrs[i].len; |
||||
} |
||||
|
||||
r->resolved_addrs->naddrs += addresses->naddrs; |
||||
grpc_resolved_addresses_destroy(addresses); |
||||
|
||||
/** Wait for all addresses to be resolved */ |
||||
resolve_done = (r->resolved_num == r->resolved_total); |
||||
gpr_mu_unlock(&r->mu); |
||||
if (resolve_done) { |
||||
zookeeper_on_resolved(r, r->resolved_addrs); |
||||
} |
||||
} |
||||
|
||||
/** Parses JSON format address of a zookeeper node */ |
||||
static char *zookeeper_parse_address(const char *value, int value_len) { |
||||
grpc_json *json; |
||||
grpc_json *cur; |
||||
const char *host; |
||||
const char *port; |
||||
char* buffer; |
||||
char *address = NULL; |
||||
|
||||
buffer = gpr_malloc(value_len); |
||||
memcpy(buffer, value, value_len); |
||||
json = grpc_json_parse_string_with_len(buffer, value_len); |
||||
if (json != NULL) { |
||||
host = NULL; |
||||
port = NULL; |
||||
for (cur = json->child; cur != NULL; cur = cur->next) { |
||||
if (!strcmp(cur->key, "host")) { |
||||
host = cur->value; |
||||
if (port != NULL) { |
||||
break; |
||||
} |
||||
} else if (!strcmp(cur->key, "port")) { |
||||
port = cur->value; |
||||
if (host != NULL) { |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
if (host != NULL && port != NULL) { |
||||
gpr_asprintf(&address, "%s:%s", host, port); |
||||
} |
||||
grpc_json_destroy(json); |
||||
} |
||||
gpr_free(buffer); |
||||
|
||||
return address; |
||||
} |
||||
|
||||
static void zookeeper_get_children_node_completion(int rc, const char *value, |
||||
int value_len, |
||||
const struct Stat *stat, |
||||
const void *arg) { |
||||
char *address = NULL; |
||||
zookeeper_resolver *r = (zookeeper_resolver *)arg; |
||||
int resolve_done = 0; |
||||
|
||||
if (rc != 0) { |
||||
gpr_log(GPR_ERROR, "Error in getting a child node of %s", r->name); |
||||
return; |
||||
} |
||||
|
||||
address = zookeeper_parse_address(value, value_len); |
||||
if (address != NULL) { |
||||
/** Further resolves address by DNS */ |
||||
grpc_resolve_address(address, NULL, zookeeper_dns_resolved, r); |
||||
gpr_free(address); |
||||
} else { |
||||
gpr_log(GPR_ERROR, "Error in resolving a child node of %s", r->name); |
||||
gpr_mu_lock(&r->mu); |
||||
r->resolved_total--; |
||||
resolve_done = (r->resolved_num == r->resolved_total); |
||||
gpr_mu_unlock(&r->mu); |
||||
if (resolve_done) { |
||||
zookeeper_on_resolved(r, r->resolved_addrs); |
||||
} |
||||
} |
||||
} |
||||
|
||||
static void zookeeper_get_children_completion( |
||||
int rc, const struct String_vector *children, const void *arg) { |
||||
char *path; |
||||
int status; |
||||
int i; |
||||
zookeeper_resolver *r = (zookeeper_resolver *)arg; |
||||
|
||||
if (rc != 0) { |
||||
gpr_log(GPR_ERROR, "Error in getting zookeeper children of %s", r->name); |
||||
return; |
||||
} |
||||
|
||||
if (children->count == 0) { |
||||
gpr_log(GPR_ERROR, "Error in resolving zookeeper address %s", r->name); |
||||
return; |
||||
} |
||||
|
||||
r->resolved_addrs = gpr_malloc(sizeof(grpc_resolved_addresses)); |
||||
r->resolved_addrs->addrs = NULL; |
||||
r->resolved_addrs->naddrs = 0; |
||||
r->resolved_total = children->count; |
||||
|
||||
/** TODO: Replace expensive heap allocation with stack
|
||||
if we can get maximum length of zookeeper path */ |
||||
for (i = 0; i < children->count; i++) { |
||||
gpr_asprintf(&path, "%s/%s", r->name, children->data[i]); |
||||
status = zoo_awget(r->zookeeper_handle, path, zookeeper_watcher, r, |
||||
zookeeper_get_children_node_completion, r); |
||||
gpr_free(path); |
||||
if (status != 0) { |
||||
gpr_log(GPR_ERROR, "Error in getting zookeeper node %s", path); |
||||
} |
||||
} |
||||
} |
||||
|
||||
static void zookeeper_get_node_completion(int rc, const char *value, |
||||
int value_len, |
||||
const struct Stat *stat, |
||||
const void *arg) { |
||||
int status; |
||||
char *address = NULL; |
||||
zookeeper_resolver *r = (zookeeper_resolver *)arg; |
||||
r->resolved_addrs = NULL; |
||||
r->resolved_total = 0; |
||||
r->resolved_num = 0; |
||||
|
||||
if (rc != 0) { |
||||
gpr_log(GPR_ERROR, "Error in getting zookeeper node %s", r->name); |
||||
return; |
||||
} |
||||
|
||||
/** If zookeeper node of path r->name does not have address
|
||||
(i.e. service node), get its children */ |
||||
address = zookeeper_parse_address(value, value_len); |
||||
if (address != NULL) { |
||||
r->resolved_addrs = gpr_malloc(sizeof(grpc_resolved_addresses)); |
||||
r->resolved_addrs->addrs = NULL; |
||||
r->resolved_addrs->naddrs = 0; |
||||
r->resolved_total = 1; |
||||
/** Further resolves address by DNS */ |
||||
grpc_resolve_address(address, NULL, zookeeper_dns_resolved, r); |
||||
gpr_free(address); |
||||
return; |
||||
} |
||||
|
||||
status = zoo_awget_children(r->zookeeper_handle, r->name, zookeeper_watcher, |
||||
r, zookeeper_get_children_completion, r); |
||||
if (status != 0) { |
||||
gpr_log(GPR_ERROR, "Error in getting zookeeper children of %s", r->name); |
||||
} |
||||
} |
||||
|
||||
static void zookeeper_resolve_address(zookeeper_resolver *r) { |
||||
int status; |
||||
status = zoo_awget(r->zookeeper_handle, r->name, zookeeper_watcher, r, |
||||
zookeeper_get_node_completion, r); |
||||
if (status != 0) { |
||||
gpr_log(GPR_ERROR, "Error in getting zookeeper node %s", r->name); |
||||
} |
||||
} |
||||
|
||||
static void zookeeper_start_resolving_locked(zookeeper_resolver *r) { |
||||
GRPC_RESOLVER_REF(&r->base, "zookeeper-resolving"); |
||||
GPR_ASSERT(r->resolving == 0); |
||||
r->resolving = 1; |
||||
zookeeper_resolve_address(r); |
||||
} |
||||
|
||||
static void zookeeper_maybe_finish_next_locked(zookeeper_resolver *r) { |
||||
if (r->next_completion != NULL && |
||||
r->resolved_version != r->published_version) { |
||||
*r->target_config = r->resolved_config; |
||||
if (r->resolved_config != NULL) { |
||||
grpc_client_config_ref(r->resolved_config); |
||||
} |
||||
grpc_iomgr_add_callback(r->next_completion); |
||||
r->next_completion = NULL; |
||||
r->published_version = r->resolved_version; |
||||
} |
||||
} |
||||
|
||||
static void zookeeper_destroy(grpc_resolver *gr) { |
||||
zookeeper_resolver *r = (zookeeper_resolver *)gr; |
||||
gpr_mu_destroy(&r->mu); |
||||
if (r->resolved_config != NULL) { |
||||
grpc_client_config_unref(r->resolved_config); |
||||
} |
||||
grpc_subchannel_factory_unref(r->subchannel_factory); |
||||
gpr_free(r->name); |
||||
gpr_free(r); |
||||
} |
||||
|
||||
static grpc_resolver *zookeeper_create( |
||||
grpc_uri *uri, |
||||
grpc_lb_policy *(*lb_policy_factory)(grpc_subchannel **subchannels, |
||||
size_t num_subchannels), |
||||
grpc_subchannel_factory *subchannel_factory) { |
||||
zookeeper_resolver *r; |
||||
size_t length; |
||||
char *path = uri->path; |
||||
|
||||
if (0 == strcmp(uri->authority, "")) { |
||||
gpr_log(GPR_ERROR, "No authority specified in zookeeper uri"); |
||||
return NULL; |
||||
} |
||||
|
||||
/** Removes the trailing slash if exists */ |
||||
length = strlen(path); |
||||
if (length > 1 && path[length - 1] == '/') { |
||||
path[length - 1] = 0; |
||||
} |
||||
|
||||
r = gpr_malloc(sizeof(zookeeper_resolver)); |
||||
memset(r, 0, sizeof(*r)); |
||||
gpr_ref_init(&r->refs, 1); |
||||
gpr_mu_init(&r->mu); |
||||
grpc_resolver_init(&r->base, &zookeeper_resolver_vtable); |
||||
r->name = gpr_strdup(path); |
||||
|
||||
r->subchannel_factory = subchannel_factory; |
||||
r->lb_policy_factory = lb_policy_factory; |
||||
grpc_subchannel_factory_ref(subchannel_factory); |
||||
|
||||
/** Initializes zookeeper client */ |
||||
zoo_set_debug_level(ZOO_LOG_LEVEL_WARN); |
||||
r->zookeeper_handle = zookeeper_init(uri->authority, zookeeper_global_watcher, |
||||
GRPC_ZOOKEEPER_SESSION_TIMEOUT, 0, 0, 0); |
||||
if (r->zookeeper_handle == NULL) { |
||||
gpr_log(GPR_ERROR, "Unable to connect to zookeeper server"); |
||||
return NULL; |
||||
} |
||||
|
||||
return &r->base; |
||||
} |
||||
|
||||
static void zookeeper_plugin_init() { |
||||
grpc_register_resolver_type("zookeeper", |
||||
grpc_zookeeper_resolver_factory_create()); |
||||
} |
||||
|
||||
void grpc_zookeeper_register() { |
||||
grpc_register_plugin(zookeeper_plugin_init, NULL); |
||||
} |
||||
|
||||
/*
|
||||
* FACTORY |
||||
*/ |
||||
|
||||
static void zookeeper_factory_ref(grpc_resolver_factory *factory) {} |
||||
|
||||
static void zookeeper_factory_unref(grpc_resolver_factory *factory) {} |
||||
|
||||
static grpc_resolver *zookeeper_factory_create_resolver( |
||||
grpc_resolver_factory *factory, grpc_uri *uri, |
||||
grpc_subchannel_factory *subchannel_factory) { |
||||
return zookeeper_create(uri, grpc_create_pick_first_lb_policy, |
||||
subchannel_factory); |
||||
} |
||||
|
||||
static const grpc_resolver_factory_vtable zookeeper_factory_vtable = { |
||||
zookeeper_factory_ref, zookeeper_factory_unref, |
||||
zookeeper_factory_create_resolver}; |
||||
static grpc_resolver_factory zookeeper_resolver_factory = { |
||||
&zookeeper_factory_vtable}; |
||||
|
||||
grpc_resolver_factory *grpc_zookeeper_resolver_factory_create() { |
||||
return &zookeeper_resolver_factory; |
||||
} |
@ -0,0 +1,42 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2015, 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. |
||||
* |
||||
*/ |
||||
|
||||
#ifndef GRPC_INTERNAL_CORE_CLIENT_CONFIG_RESOLVERS_ZOOKEEPER_RESOLVER_H |
||||
#define GRPC_INTERNAL_CORE_CLIENT_CONFIG_RESOLVERS_ZOOKEEPER_RESOLVER_H |
||||
|
||||
#include "src/core/client_config/resolver_factory.h" |
||||
|
||||
/** Create a zookeeper resolver factory */ |
||||
grpc_resolver_factory *grpc_zookeeper_resolver_factory_create(void); |
||||
|
||||
#endif /* GRPC_INTERNAL_CORE_CLIENT_CONFIG_RESOLVERS_ZOOKEEPER_RESOLVER_H */ |
@ -0,0 +1,438 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2015, 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. |
||||
* |
||||
*/ |
||||
|
||||
/* FIXME: "posix" files shouldn't be depending on _GNU_SOURCE */ |
||||
#ifndef _GNU_SOURCE |
||||
#define _GNU_SOURCE |
||||
#endif |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#ifdef GPR_POSIX_SOCKET |
||||
|
||||
#include "src/core/iomgr/udp_server.h" |
||||
|
||||
#include <errno.h> |
||||
#include <fcntl.h> |
||||
#include <limits.h> |
||||
#include <netinet/in.h> |
||||
#include <netinet/tcp.h> |
||||
#include <string.h> |
||||
#include <sys/socket.h> |
||||
#include <sys/stat.h> |
||||
#include <sys/types.h> |
||||
#include <sys/un.h> |
||||
#include <unistd.h> |
||||
|
||||
#include "src/core/iomgr/fd_posix.h" |
||||
#include "src/core/iomgr/pollset_posix.h" |
||||
#include "src/core/iomgr/resolve_address.h" |
||||
#include "src/core/iomgr/sockaddr_utils.h" |
||||
#include "src/core/iomgr/socket_utils_posix.h" |
||||
#include "src/core/support/string.h" |
||||
#include <grpc/support/alloc.h> |
||||
#include <grpc/support/log.h> |
||||
#include <grpc/support/sync.h> |
||||
#include <grpc/support/string_util.h> |
||||
#include <grpc/support/time.h> |
||||
|
||||
#define INIT_PORT_CAP 2 |
||||
|
||||
/* one listening port */ |
||||
typedef struct { |
||||
int fd; |
||||
grpc_fd *emfd; |
||||
grpc_udp_server *server; |
||||
union { |
||||
gpr_uint8 untyped[GRPC_MAX_SOCKADDR_SIZE]; |
||||
struct sockaddr sockaddr; |
||||
struct sockaddr_un un; |
||||
} addr; |
||||
int addr_len; |
||||
grpc_iomgr_closure read_closure; |
||||
grpc_iomgr_closure destroyed_closure; |
||||
grpc_udp_server_read_cb read_cb; |
||||
} server_port; |
||||
|
||||
static void unlink_if_unix_domain_socket(const struct sockaddr_un *un) { |
||||
struct stat st; |
||||
|
||||
if (stat(un->sun_path, &st) == 0 && (st.st_mode & S_IFMT) == S_IFSOCK) { |
||||
unlink(un->sun_path); |
||||
} |
||||
} |
||||
|
||||
/* the overall server */ |
||||
struct grpc_udp_server { |
||||
grpc_udp_server_cb cb; |
||||
void *cb_arg; |
||||
|
||||
gpr_mu mu; |
||||
gpr_cv cv; |
||||
|
||||
/* active port count: how many ports are actually still listening */ |
||||
size_t active_ports; |
||||
/* destroyed port count: how many ports are completely destroyed */ |
||||
size_t destroyed_ports; |
||||
|
||||
/* is this server shutting down? (boolean) */ |
||||
int shutdown; |
||||
|
||||
/* all listening ports */ |
||||
server_port *ports; |
||||
size_t nports; |
||||
size_t port_capacity; |
||||
|
||||
/* shutdown callback */ |
||||
void (*shutdown_complete)(void *); |
||||
void *shutdown_complete_arg; |
||||
|
||||
/* all pollsets interested in new connections */ |
||||
grpc_pollset **pollsets; |
||||
/* number of pollsets in the pollsets array */ |
||||
size_t pollset_count; |
||||
}; |
||||
|
||||
grpc_udp_server *grpc_udp_server_create(void) { |
||||
grpc_udp_server *s = gpr_malloc(sizeof(grpc_udp_server)); |
||||
gpr_mu_init(&s->mu); |
||||
gpr_cv_init(&s->cv); |
||||
s->active_ports = 0; |
||||
s->destroyed_ports = 0; |
||||
s->shutdown = 0; |
||||
s->cb = NULL; |
||||
s->cb_arg = NULL; |
||||
s->ports = gpr_malloc(sizeof(server_port) * INIT_PORT_CAP); |
||||
s->nports = 0; |
||||
s->port_capacity = INIT_PORT_CAP; |
||||
|
||||
return s; |
||||
} |
||||
|
||||
static void finish_shutdown(grpc_udp_server *s) { |
||||
s->shutdown_complete(s->shutdown_complete_arg); |
||||
|
||||
gpr_mu_destroy(&s->mu); |
||||
gpr_cv_destroy(&s->cv); |
||||
|
||||
gpr_free(s->ports); |
||||
gpr_free(s); |
||||
} |
||||
|
||||
static void destroyed_port(void *server, int success) { |
||||
grpc_udp_server *s = server; |
||||
gpr_mu_lock(&s->mu); |
||||
s->destroyed_ports++; |
||||
if (s->destroyed_ports == s->nports) { |
||||
gpr_mu_unlock(&s->mu); |
||||
finish_shutdown(s); |
||||
} else { |
||||
gpr_mu_unlock(&s->mu); |
||||
} |
||||
} |
||||
|
||||
static void dont_care_about_shutdown_completion(void *ignored) {} |
||||
|
||||
/* called when all listening endpoints have been shutdown, so no further
|
||||
events will be received on them - at this point it's safe to destroy |
||||
things */ |
||||
static void deactivated_all_ports(grpc_udp_server *s) { |
||||
size_t i; |
||||
|
||||
/* delete ALL the things */ |
||||
gpr_mu_lock(&s->mu); |
||||
|
||||
if (!s->shutdown) { |
||||
gpr_mu_unlock(&s->mu); |
||||
return; |
||||
} |
||||
|
||||
if (s->nports) { |
||||
for (i = 0; i < s->nports; i++) { |
||||
server_port *sp = &s->ports[i]; |
||||
if (sp->addr.sockaddr.sa_family == AF_UNIX) { |
||||
unlink_if_unix_domain_socket(&sp->addr.un); |
||||
} |
||||
sp->destroyed_closure.cb = destroyed_port; |
||||
sp->destroyed_closure.cb_arg = s; |
||||
grpc_fd_orphan(sp->emfd, &sp->destroyed_closure, "udp_listener_shutdown"); |
||||
} |
||||
gpr_mu_unlock(&s->mu); |
||||
} else { |
||||
gpr_mu_unlock(&s->mu); |
||||
finish_shutdown(s); |
||||
} |
||||
} |
||||
|
||||
void grpc_udp_server_destroy( |
||||
grpc_udp_server *s, void (*shutdown_complete)(void *shutdown_complete_arg), |
||||
void *shutdown_complete_arg) { |
||||
size_t i; |
||||
gpr_mu_lock(&s->mu); |
||||
|
||||
GPR_ASSERT(!s->shutdown); |
||||
s->shutdown = 1; |
||||
|
||||
s->shutdown_complete = shutdown_complete |
||||
? shutdown_complete |
||||
: dont_care_about_shutdown_completion; |
||||
s->shutdown_complete_arg = shutdown_complete_arg; |
||||
|
||||
/* shutdown all fd's */ |
||||
if (s->active_ports) { |
||||
for (i = 0; i < s->nports; i++) { |
||||
grpc_fd_shutdown(s->ports[i].emfd); |
||||
} |
||||
gpr_mu_unlock(&s->mu); |
||||
} else { |
||||
gpr_mu_unlock(&s->mu); |
||||
deactivated_all_ports(s); |
||||
} |
||||
} |
||||
|
||||
/* Prepare a recently-created socket for listening. */ |
||||
static int prepare_socket(int fd, const struct sockaddr *addr, int addr_len) { |
||||
struct sockaddr_storage sockname_temp; |
||||
socklen_t sockname_len; |
||||
int get_local_ip; |
||||
int rc; |
||||
|
||||
if (fd < 0) { |
||||
goto error; |
||||
} |
||||
|
||||
get_local_ip = 1; |
||||
rc = setsockopt(fd, IPPROTO_IP, IP_PKTINFO, |
||||
&get_local_ip, sizeof(get_local_ip)); |
||||
if (rc == 0 && addr->sa_family == AF_INET6) { |
||||
rc = setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, |
||||
&get_local_ip, sizeof(get_local_ip)); |
||||
} |
||||
|
||||
if (bind(fd, addr, addr_len) < 0) { |
||||
char *addr_str; |
||||
grpc_sockaddr_to_string(&addr_str, addr, 0); |
||||
gpr_log(GPR_ERROR, "bind addr=%s: %s", addr_str, strerror(errno)); |
||||
gpr_free(addr_str); |
||||
goto error; |
||||
} |
||||
|
||||
sockname_len = sizeof(sockname_temp); |
||||
if (getsockname(fd, (struct sockaddr *)&sockname_temp, &sockname_len) < 0) { |
||||
goto error; |
||||
} |
||||
|
||||
return grpc_sockaddr_get_port((struct sockaddr *)&sockname_temp); |
||||
|
||||
error: |
||||
if (fd >= 0) { |
||||
close(fd); |
||||
} |
||||
return -1; |
||||
} |
||||
|
||||
/* event manager callback when reads are ready */ |
||||
static void on_read(void *arg, int success) { |
||||
server_port *sp = arg; |
||||
|
||||
if (success == 0) { |
||||
gpr_mu_lock(&sp->server->mu); |
||||
if (0 == --sp->server->active_ports) { |
||||
gpr_mu_unlock(&sp->server->mu); |
||||
deactivated_all_ports(sp->server); |
||||
} else { |
||||
gpr_mu_unlock(&sp->server->mu); |
||||
} |
||||
return; |
||||
} |
||||
|
||||
/* Tell the registered callback that data is available to read. */ |
||||
GPR_ASSERT(sp->read_cb); |
||||
sp->read_cb(sp->fd, sp->server->cb, sp->server->cb_arg); |
||||
|
||||
/* Re-arm the notification event so we get another chance to read. */ |
||||
grpc_fd_notify_on_read(sp->emfd, &sp->read_closure); |
||||
} |
||||
|
||||
static int add_socket_to_server(grpc_udp_server *s, int fd, |
||||
const struct sockaddr *addr, int addr_len, |
||||
grpc_udp_server_read_cb read_cb) { |
||||
server_port *sp; |
||||
int port; |
||||
char *addr_str; |
||||
char *name; |
||||
|
||||
port = prepare_socket(fd, addr, addr_len); |
||||
if (port >= 0) { |
||||
grpc_sockaddr_to_string(&addr_str, (struct sockaddr *)&addr, 1); |
||||
gpr_asprintf(&name, "udp-server-listener:%s", addr_str); |
||||
gpr_mu_lock(&s->mu); |
||||
GPR_ASSERT(!s->cb && "must add ports before starting server"); |
||||
/* append it to the list under a lock */ |
||||
if (s->nports == s->port_capacity) { |
||||
s->port_capacity *= 2; |
||||
s->ports = gpr_realloc(s->ports, sizeof(server_port) * s->port_capacity); |
||||
} |
||||
sp = &s->ports[s->nports++]; |
||||
sp->server = s; |
||||
sp->fd = fd; |
||||
sp->emfd = grpc_fd_create(fd, name); |
||||
memcpy(sp->addr.untyped, addr, addr_len); |
||||
sp->addr_len = addr_len; |
||||
sp->read_cb = read_cb; |
||||
GPR_ASSERT(sp->emfd); |
||||
gpr_mu_unlock(&s->mu); |
||||
} |
||||
|
||||
return port; |
||||
} |
||||
|
||||
int grpc_udp_server_add_port(grpc_udp_server *s, const void *addr, |
||||
int addr_len, grpc_udp_server_read_cb read_cb) { |
||||
int allocated_port1 = -1; |
||||
int allocated_port2 = -1; |
||||
unsigned i; |
||||
int fd; |
||||
grpc_dualstack_mode dsmode; |
||||
struct sockaddr_in6 addr6_v4mapped; |
||||
struct sockaddr_in wild4; |
||||
struct sockaddr_in6 wild6; |
||||
struct sockaddr_in addr4_copy; |
||||
struct sockaddr *allocated_addr = NULL; |
||||
struct sockaddr_storage sockname_temp; |
||||
socklen_t sockname_len; |
||||
int port; |
||||
|
||||
if (((struct sockaddr *)addr)->sa_family == AF_UNIX) { |
||||
unlink_if_unix_domain_socket(addr); |
||||
} |
||||
|
||||
/* Check if this is a wildcard port, and if so, try to keep the port the same
|
||||
as some previously created listener. */ |
||||
if (grpc_sockaddr_get_port(addr) == 0) { |
||||
for (i = 0; i < s->nports; i++) { |
||||
sockname_len = sizeof(sockname_temp); |
||||
if (0 == getsockname(s->ports[i].fd, (struct sockaddr *)&sockname_temp, |
||||
&sockname_len)) { |
||||
port = grpc_sockaddr_get_port((struct sockaddr *)&sockname_temp); |
||||
if (port > 0) { |
||||
allocated_addr = malloc(addr_len); |
||||
memcpy(allocated_addr, addr, addr_len); |
||||
grpc_sockaddr_set_port(allocated_addr, port); |
||||
addr = allocated_addr; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (grpc_sockaddr_to_v4mapped(addr, &addr6_v4mapped)) { |
||||
addr = (const struct sockaddr *)&addr6_v4mapped; |
||||
addr_len = sizeof(addr6_v4mapped); |
||||
} |
||||
|
||||
/* Treat :: or 0.0.0.0 as a family-agnostic wildcard. */ |
||||
if (grpc_sockaddr_is_wildcard(addr, &port)) { |
||||
grpc_sockaddr_make_wildcards(port, &wild4, &wild6); |
||||
|
||||
/* Try listening on IPv6 first. */ |
||||
addr = (struct sockaddr *)&wild6; |
||||
addr_len = sizeof(wild6); |
||||
fd = grpc_create_dualstack_socket(addr, SOCK_DGRAM, IPPROTO_UDP, &dsmode); |
||||
allocated_port1 = add_socket_to_server(s, fd, addr, addr_len, read_cb); |
||||
if (fd >= 0 && dsmode == GRPC_DSMODE_DUALSTACK) { |
||||
goto done; |
||||
} |
||||
|
||||
/* If we didn't get a dualstack socket, also listen on 0.0.0.0. */ |
||||
if (port == 0 && allocated_port1 > 0) { |
||||
grpc_sockaddr_set_port((struct sockaddr *)&wild4, allocated_port1); |
||||
} |
||||
addr = (struct sockaddr *)&wild4; |
||||
addr_len = sizeof(wild4); |
||||
} |
||||
|
||||
fd = grpc_create_dualstack_socket(addr, SOCK_DGRAM, IPPROTO_UDP, &dsmode); |
||||
if (fd < 0) { |
||||
gpr_log(GPR_ERROR, "Unable to create socket: %s", strerror(errno)); |
||||
} |
||||
if (dsmode == GRPC_DSMODE_IPV4 && |
||||
grpc_sockaddr_is_v4mapped(addr, &addr4_copy)) { |
||||
addr = (struct sockaddr *)&addr4_copy; |
||||
addr_len = sizeof(addr4_copy); |
||||
} |
||||
allocated_port2 = add_socket_to_server(s, fd, addr, addr_len, read_cb); |
||||
|
||||
done: |
||||
gpr_free(allocated_addr); |
||||
return allocated_port1 >= 0 ? allocated_port1 : allocated_port2; |
||||
} |
||||
|
||||
int grpc_udp_server_get_fd(grpc_udp_server *s, unsigned index) { |
||||
return (index < s->nports) ? s->ports[index].fd : -1; |
||||
} |
||||
|
||||
void grpc_udp_server_start(grpc_udp_server *s, grpc_pollset **pollsets, |
||||
size_t pollset_count, |
||||
grpc_udp_server_cb new_transport_cb, void *cb_arg) { |
||||
size_t i, j; |
||||
GPR_ASSERT(new_transport_cb); |
||||
gpr_mu_lock(&s->mu); |
||||
GPR_ASSERT(!s->cb); |
||||
GPR_ASSERT(s->active_ports == 0); |
||||
s->cb = new_transport_cb; |
||||
s->cb_arg = cb_arg; |
||||
s->pollsets = pollsets; |
||||
for (i = 0; i < s->nports; i++) { |
||||
for (j = 0; j < pollset_count; j++) { |
||||
grpc_pollset_add_fd(pollsets[j], s->ports[i].emfd); |
||||
} |
||||
s->ports[i].read_closure.cb = on_read; |
||||
s->ports[i].read_closure.cb_arg = &s->ports[i]; |
||||
grpc_fd_notify_on_read(s->ports[i].emfd, &s->ports[i].read_closure); |
||||
s->active_ports++; |
||||
} |
||||
gpr_mu_unlock(&s->mu); |
||||
} |
||||
|
||||
/* TODO(rjshade): Add a test for this method. */ |
||||
void grpc_udp_server_write(server_port *sp, const char *buffer, size_t buf_len, |
||||
const struct sockaddr *peer_address) { |
||||
int rc; |
||||
rc = sendto(sp->fd, buffer, buf_len, 0, peer_address, sizeof(peer_address)); |
||||
if (rc < 0) { |
||||
gpr_log(GPR_ERROR, "Unable to send data: %s", strerror(errno)); |
||||
} |
||||
} |
||||
|
||||
#endif |
@ -0,0 +1,85 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2015, 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. |
||||
* |
||||
*/ |
||||
|
||||
#ifndef GRPC_INTERNAL_CORE_IOMGR_UDP_SERVER_H |
||||
#define GRPC_INTERNAL_CORE_IOMGR_UDP_SERVER_H |
||||
|
||||
#include "src/core/iomgr/endpoint.h" |
||||
|
||||
/* Forward decl of grpc_udp_server */ |
||||
typedef struct grpc_udp_server grpc_udp_server; |
||||
|
||||
/* New server callback: ep is the newly connected connection */ |
||||
typedef void (*grpc_udp_server_cb)(void *arg, grpc_endpoint *ep); |
||||
|
||||
/* Called when data is available to read from the socket. */ |
||||
typedef void (*grpc_udp_server_read_cb)(int fd, |
||||
grpc_udp_server_cb new_transport_cb, |
||||
void *cb_arg); |
||||
|
||||
/* Create a server, initially not bound to any ports */ |
||||
grpc_udp_server *grpc_udp_server_create(void); |
||||
|
||||
/* Start listening to bound ports */ |
||||
void grpc_udp_server_start(grpc_udp_server *server, grpc_pollset **pollsets, |
||||
size_t pollset_count, grpc_udp_server_cb cb, |
||||
void *cb_arg); |
||||
|
||||
int grpc_udp_server_get_fd(grpc_udp_server *s, unsigned index); |
||||
|
||||
/* Add a port to the server, returning port number on success, or negative
|
||||
on failure. |
||||
|
||||
The :: and 0.0.0.0 wildcard addresses are treated identically, accepting |
||||
both IPv4 and IPv6 connections, but :: is the preferred style. This usually |
||||
creates one socket, but possibly two on systems which support IPv6, |
||||
but not dualstack sockets. */ |
||||
|
||||
/* TODO(ctiller): deprecate this, and make grpc_udp_server_add_ports to handle
|
||||
all of the multiple socket port matching logic in one place */ |
||||
int grpc_udp_server_add_port(grpc_udp_server *s, const void *addr, int addr_len, |
||||
grpc_udp_server_read_cb read_cb); |
||||
|
||||
void grpc_udp_server_destroy(grpc_udp_server *server, |
||||
void (*shutdown_done)(void *shutdown_done_arg), |
||||
void *shutdown_done_arg); |
||||
|
||||
/* Write the contents of buffer to the underlying UDP socket. */ |
||||
/*
|
||||
void grpc_udp_server_write(grpc_udp_server *s, |
||||
const char *buffer, |
||||
int buf_len, |
||||
const struct sockaddr* to); |
||||
*/ |
||||
|
||||
#endif /* GRPC_INTERNAL_CORE_IOMGR_UDP_SERVER_H */ |
@ -0,0 +1,273 @@ |
||||
# Copyright 2015, 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. |
||||
|
||||
"""The base interface of RPC Framework.""" |
||||
|
||||
import abc |
||||
import enum |
||||
|
||||
# abandonment is referenced from specification in this module. |
||||
from grpc.framework.foundation import abandonment # pylint: disable=unused-import |
||||
|
||||
|
||||
class NoSuchMethodError(Exception): |
||||
"""Indicates that an unrecognized operation has been called.""" |
||||
|
||||
|
||||
@enum.unique |
||||
class Outcome(enum.Enum): |
||||
"""Operation outcomes.""" |
||||
|
||||
COMPLETED = 'completed' |
||||
CANCELLED = 'cancelled' |
||||
EXPIRED = 'expired' |
||||
LOCAL_SHUTDOWN = 'local shutdown' |
||||
REMOTE_SHUTDOWN = 'remote shutdown' |
||||
RECEPTION_FAILURE = 'reception failure' |
||||
TRANSMISSION_FAILURE = 'transmission failure' |
||||
LOCAL_FAILURE = 'local failure' |
||||
REMOTE_FAILURE = 'remote failure' |
||||
|
||||
|
||||
class Completion(object): |
||||
"""An aggregate of the values exchanged upon operation completion. |
||||
|
||||
Attributes: |
||||
terminal_metadata: A terminal metadata value for the operaton. |
||||
code: A code value for the operation. |
||||
message: A message value for the operation. |
||||
""" |
||||
__metaclass__ = abc.ABCMeta |
||||
|
||||
|
||||
class OperationContext(object): |
||||
"""Provides operation-related information and action.""" |
||||
__metaclass__ = abc.ABCMeta |
||||
|
||||
@abc.abstractmethod |
||||
def outcome(self): |
||||
"""Indicates the operation's outcome (or that the operation is ongoing). |
||||
|
||||
Returns: |
||||
None if the operation is still active or the Outcome value for the |
||||
operation if it has terminated. |
||||
""" |
||||
raise NotImplementedError() |
||||
|
||||
@abc.abstractmethod |
||||
def add_termination_callback(self, callback): |
||||
"""Adds a function to be called upon operation termination. |
||||
|
||||
Args: |
||||
callback: A callable to be passed an Outcome value on operation |
||||
termination. |
||||
|
||||
Returns: |
||||
None if the operation has not yet terminated and the passed callback will |
||||
later be called when it does terminate, or if the operation has already |
||||
terminated an Outcome value describing the operation termination and the |
||||
passed callback will not be called as a result of this method call. |
||||
""" |
||||
raise NotImplementedError() |
||||
|
||||
@abc.abstractmethod |
||||
def time_remaining(self): |
||||
"""Describes the length of allowed time remaining for the operation. |
||||
|
||||
Returns: |
||||
A nonnegative float indicating the length of allowed time in seconds |
||||
remaining for the operation to complete before it is considered to have |
||||
timed out. Zero is returned if the operation has terminated. |
||||
""" |
||||
raise NotImplementedError() |
||||
|
||||
@abc.abstractmethod |
||||
def cancel(self): |
||||
"""Cancels the operation if the operation has not yet terminated.""" |
||||
raise NotImplementedError() |
||||
|
||||
@abc.abstractmethod |
||||
def fail(self, exception): |
||||
"""Indicates that the operation has failed. |
||||
|
||||
Args: |
||||
exception: An exception germane to the operation failure. May be None. |
||||
""" |
||||
raise NotImplementedError() |
||||
|
||||
|
||||
class Operator(object): |
||||
"""An interface through which to participate in an operation.""" |
||||
__metaclass__ = abc.ABCMeta |
||||
|
||||
@abc.abstractmethod |
||||
def advance( |
||||
self, initial_metadata=None, payload=None, completion=None, |
||||
allowance=None): |
||||
"""Progresses the operation. |
||||
|
||||
Args: |
||||
initial_metadata: An initial metadata value. Only one may ever be |
||||
communicated in each direction for an operation, and they must be |
||||
communicated no later than either the first payload or the completion. |
||||
payload: A payload value. |
||||
completion: A Completion value. May only ever be non-None once in either |
||||
direction, and no payloads may be passed after it has been communicated. |
||||
allowance: A positive integer communicating the number of additional |
||||
payloads allowed to be passed by the remote side of the operation. |
||||
""" |
||||
raise NotImplementedError() |
||||
|
||||
|
||||
class Subscription(object): |
||||
"""Describes customer code's interest in values from the other side. |
||||
|
||||
Attributes: |
||||
kind: A Kind value describing the overall kind of this value. |
||||
termination_callback: A callable to be passed the Outcome associated with |
||||
the operation after it has terminated. Must be non-None if kind is |
||||
Kind.TERMINATION_ONLY. Must be None otherwise. |
||||
allowance: A callable behavior that accepts positive integers representing |
||||
the number of additional payloads allowed to be passed to the other side |
||||
of the operation. Must be None if kind is Kind.FULL. Must not be None |
||||
otherwise. |
||||
operator: An Operator to be passed values from the other side of the |
||||
operation. Must be non-None if kind is Kind.FULL. Must be None otherwise. |
||||
""" |
||||
|
||||
@enum.unique |
||||
class Kind(enum.Enum): |
||||
|
||||
NONE = 'none' |
||||
TERMINATION_ONLY = 'termination only' |
||||
FULL = 'full' |
||||
|
||||
|
||||
class Servicer(object): |
||||
"""Interface for service implementations.""" |
||||
__metaclass__ = abc.ABCMeta |
||||
|
||||
@abc.abstractmethod |
||||
def service(self, group, method, context, output_operator): |
||||
"""Services an operation. |
||||
|
||||
Args: |
||||
group: The group identifier of the operation to be serviced. |
||||
method: The method identifier of the operation to be serviced. |
||||
context: An OperationContext object affording contextual information and |
||||
actions. |
||||
output_operator: An Operator that will accept output values of the |
||||
operation. |
||||
|
||||
Returns: |
||||
A Subscription via which this object may or may not accept more values of |
||||
the operation. |
||||
|
||||
Raises: |
||||
NoSuchMethodError: If this Servicer does not handle operations with the |
||||
given group and method. |
||||
abandonment.Abandoned: If the operation has been aborted and there no |
||||
longer is any reason to service the operation. |
||||
""" |
||||
raise NotImplementedError() |
||||
|
||||
|
||||
class End(object): |
||||
"""Common type for entry-point objects on both sides of an operation.""" |
||||
__metaclass__ = abc.ABCMeta |
||||
|
||||
@abc.abstractmethod |
||||
def start(self): |
||||
"""Starts this object's service of operations.""" |
||||
raise NotImplementedError() |
||||
|
||||
@abc.abstractmethod |
||||
def stop_gracefully(self): |
||||
"""Gracefully stops this object's service of operations. |
||||
|
||||
Operations in progress will be allowed to complete, and this method blocks |
||||
until all of them have. |
||||
""" |
||||
raise NotImplementedError() |
||||
|
||||
@abc.abstractmethod |
||||
def stop_immediately(self): |
||||
"""Immediately stops this object's service of operations. |
||||
|
||||
Operations in progress will not be allowed to complete. |
||||
""" |
||||
raise NotImplementedError() |
||||
|
||||
@abc.abstractmethod |
||||
def operate( |
||||
self, group, method, subscription, timeout, initial_metadata=None, |
||||
payload=None, completion=None): |
||||
"""Commences an operation. |
||||
|
||||
Args: |
||||
group: The group identifier of the invoked operation. |
||||
method: The method identifier of the invoked operation. |
||||
subscription: A Subscription to which the results of the operation will be |
||||
passed. |
||||
timeout: A length of time in seconds to allow for the operation. |
||||
initial_metadata: An initial metadata value to be sent to the other side |
||||
of the operation. May be None if the initial metadata will be later |
||||
passed via the returned operator or if there will be no initial metadata |
||||
passed at all. |
||||
payload: An initial payload for the operation. |
||||
completion: A Completion value indicating the end of transmission to the |
||||
other side of the operation. |
||||
|
||||
Returns: |
||||
A pair of objects affording information about the operation and action |
||||
continuing the operation. The first element of the returned pair is an |
||||
OperationContext for the operation and the second element of the |
||||
returned pair is an Operator to which operation values not passed in |
||||
this call should later be passed. |
||||
""" |
||||
raise NotImplementedError() |
||||
|
||||
@abc.abstractmethod |
||||
def operation_stats(self): |
||||
"""Reports the number of terminated operations broken down by outcome. |
||||
|
||||
Returns: |
||||
A dictionary from Outcome value to an integer identifying the number |
||||
of operations that terminated with that outcome. |
||||
""" |
||||
raise NotImplementedError() |
||||
|
||||
@abc.abstractmethod |
||||
def add_idle_action(self, action): |
||||
"""Adds an action to be called when this End has no ongoing operations. |
||||
|
||||
Args: |
||||
action: A callable that accepts no arguments. |
||||
""" |
||||
raise NotImplementedError() |
@ -0,0 +1,79 @@ |
||||
# Copyright 2015, 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. |
||||
|
||||
"""Utilities for use with the base interface of RPC Framework.""" |
||||
|
||||
import collections |
||||
|
||||
from grpc.framework.interfaces.base import base |
||||
|
||||
|
||||
class _Completion( |
||||
base.Completion, |
||||
collections.namedtuple( |
||||
'_Completion', ('terminal_metadata', 'code', 'message',))): |
||||
"""A trivial implementation of base.Completion.""" |
||||
|
||||
|
||||
class _Subscription( |
||||
base.Subscription, |
||||
collections.namedtuple( |
||||
'_Subscription', |
||||
('kind', 'termination_callback', 'allowance', 'operator',))): |
||||
"""A trivial implementation of base.Subscription.""" |
||||
|
||||
_NONE_SUBSCRIPTION = _Subscription( |
||||
base.Subscription.Kind.NONE, None, None, None) |
||||
|
||||
|
||||
def completion(terminal_metadata, code, message): |
||||
"""Creates a base.Completion aggregating the given operation values. |
||||
|
||||
Args: |
||||
terminal_metadata: A terminal metadata value for an operaton. |
||||
code: A code value for an operation. |
||||
message: A message value for an operation. |
||||
|
||||
Returns: |
||||
A base.Completion aggregating the given operation values. |
||||
""" |
||||
return _Completion(terminal_metadata, code, message) |
||||
|
||||
|
||||
def full_subscription(operator): |
||||
"""Creates a "full" base.Subscription for the given base.Operator. |
||||
|
||||
Args: |
||||
operator: A base.Operator to be used in an operation. |
||||
|
||||
Returns: |
||||
A base.Subscription of kind base.Subscription.Kind.FULL wrapping the given |
||||
base.Operator. |
||||
""" |
||||
return _Subscription(base.Subscription.Kind.FULL, None, None, operator) |
@ -1,4 +1,5 @@ |
||||
-I. |
||||
-Ipb |
||||
--require spec_helper |
||||
--format documentation |
||||
--color |
||||
|
@ -1,8 +0,0 @@ |
||||
Interop test protos |
||||
=================== |
||||
|
||||
These ruby classes were generated with protoc v3, using grpc's ruby compiler |
||||
plugin. |
||||
|
||||
- As of 2015/01 protoc v3 is available in the |
||||
[google-protobuf](https://github.com/google/protobuf) repo |
@ -0,0 +1,42 @@ |
||||
Protocol Buffers |
||||
================ |
||||
|
||||
This folder contains protocol buffers provided with gRPC ruby, and the generated |
||||
code to them. |
||||
|
||||
PREREQUISITES |
||||
------------- |
||||
|
||||
The code is is generated using the protoc (> 3.0.0.alpha.1) and the |
||||
grpc_ruby_plugin. These must be installed to regenerate the IDL defined |
||||
classes, but that's not necessary just to use them. |
||||
|
||||
health_check/v1alpha |
||||
-------------------- |
||||
|
||||
This package defines the surface of a simple health check service that gRPC |
||||
servers may choose to implement, and provides an implementation for it. To |
||||
re-generate the surface. |
||||
|
||||
```bash |
||||
$ # (from this directory) |
||||
$ protoc -I . grpc/health/v1alpha/health.proto \ |
||||
--grpc_out=. \ |
||||
--ruby_out=. \ |
||||
--plugin=protoc-gen-grpc=`which grpc_ruby_plugin` |
||||
``` |
||||
|
||||
test |
||||
---- |
||||
|
||||
This package defines the surface of the gRPC interop test service and client |
||||
To re-generate the surface, it's necessary to have checked-out versions of |
||||
the grpc interop test proto, e.g, by having the full gRPC repository. E.g, |
||||
|
||||
```bash |
||||
$ # (from this directory within the grpc repo) |
||||
$ protoc -I../../.. ../../../test/proto/{messages,test,empty}.proto \ |
||||
--grpc_out=. \ |
||||
--ruby_out=. \ |
||||
--plugin=protoc-gen-grpc=`which grpc_ruby_plugin` |
||||
``` |
@ -0,0 +1,75 @@ |
||||
# Copyright 2015, 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. |
||||
|
||||
require 'grpc' |
||||
require 'grpc/health/v1alpha/health_services' |
||||
require 'thread' |
||||
|
||||
module Grpc |
||||
# Health contains classes and modules that support providing a health check |
||||
# service. |
||||
module Health |
||||
# Checker is implementation of the schema-specified health checking service. |
||||
class Checker < V1alpha::Health::Service |
||||
StatusCodes = GRPC::Core::StatusCodes |
||||
HealthCheckResponse = V1alpha::HealthCheckResponse |
||||
|
||||
# Initializes the statuses of participating services |
||||
def initialize |
||||
@statuses = {} |
||||
@status_mutex = Mutex.new # guards access to @statuses |
||||
end |
||||
|
||||
# Implements the rpc IDL API method |
||||
def check(req, _call) |
||||
status = nil |
||||
@status_mutex.synchronize do |
||||
status = @statuses["#{req.host}/#{req.service}"] |
||||
end |
||||
fail GRPC::BadStatus, StatusCodes::NOT_FOUND if status.nil? |
||||
HealthCheckResponse.new(status: status) |
||||
end |
||||
|
||||
# Adds the health status for a given host and service. |
||||
def add_status(host, service, status) |
||||
@status_mutex.synchronize { @statuses["#{host}/#{service}"] = status } |
||||
end |
||||
|
||||
# Clears the status for the given host or service. |
||||
def clear_status(host, service) |
||||
@status_mutex.synchronize { @statuses.delete("#{host}/#{service}") } |
||||
end |
||||
|
||||
# Clears alls the statuses. |
||||
def clear_all |
||||
@status_mutex.synchronize { @statuses = {} } |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,50 @@ |
||||
// Copyright 2015, 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. |
||||
|
||||
syntax = "proto3"; |
||||
|
||||
package grpc.health.v1alpha; |
||||
|
||||
message HealthCheckRequest { |
||||
string host = 1; |
||||
string service = 2; |
||||
} |
||||
|
||||
message HealthCheckResponse { |
||||
enum ServingStatus { |
||||
UNKNOWN = 0; |
||||
SERVING = 1; |
||||
NOT_SERVING = 2; |
||||
} |
||||
ServingStatus status = 1; |
||||
} |
||||
|
||||
service Health { |
||||
rpc Check(HealthCheckRequest) returns (HealthCheckResponse); |
||||
} |
@ -0,0 +1,29 @@ |
||||
# Generated by the protocol buffer compiler. DO NOT EDIT! |
||||
# source: grpc/health/v1alpha/health.proto |
||||
|
||||
require 'google/protobuf' |
||||
|
||||
Google::Protobuf::DescriptorPool.generated_pool.build do |
||||
add_message "grpc.health.v1alpha.HealthCheckRequest" do |
||||
optional :host, :string, 1 |
||||
optional :service, :string, 2 |
||||
end |
||||
add_message "grpc.health.v1alpha.HealthCheckResponse" do |
||||
optional :status, :enum, 1, "grpc.health.v1alpha.HealthCheckResponse.ServingStatus" |
||||
end |
||||
add_enum "grpc.health.v1alpha.HealthCheckResponse.ServingStatus" do |
||||
value :UNKNOWN, 0 |
||||
value :SERVING, 1 |
||||
value :NOT_SERVING, 2 |
||||
end |
||||
end |
||||
|
||||
module Grpc |
||||
module Health |
||||
module V1alpha |
||||
HealthCheckRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.health.v1alpha.HealthCheckRequest").msgclass |
||||
HealthCheckResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.health.v1alpha.HealthCheckResponse").msgclass |
||||
HealthCheckResponse::ServingStatus = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.health.v1alpha.HealthCheckResponse.ServingStatus").enummodule |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,28 @@ |
||||
# Generated by the protocol buffer compiler. DO NOT EDIT! |
||||
# Source: grpc/health/v1alpha/health.proto for package 'grpc.health.v1alpha' |
||||
|
||||
require 'grpc' |
||||
require 'grpc/health/v1alpha/health' |
||||
|
||||
module Grpc |
||||
module Health |
||||
module V1alpha |
||||
module Health |
||||
|
||||
# TODO: add proto service documentation here |
||||
class Service |
||||
|
||||
include GRPC::GenericService |
||||
|
||||
self.marshal_class_method = :encode |
||||
self.unmarshal_class_method = :decode |
||||
self.service_name = 'grpc.health.v1alpha.Health' |
||||
|
||||
rpc :Check, HealthCheckRequest, HealthCheckResponse |
||||
end |
||||
|
||||
Stub = Service.rpc_stub_class |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,453 @@ |
||||
#!/usr/bin/env ruby |
||||
|
||||
# Copyright 2015, 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. |
||||
|
||||
# client is a testing tool that accesses a gRPC interop testing server and runs |
||||
# a test on it. |
||||
# |
||||
# Helps validate interoperation b/w different gRPC implementations. |
||||
# |
||||
# Usage: $ path/to/client.rb --server_host=<hostname> \ |
||||
# --server_port=<port> \ |
||||
# --test_case=<testcase_name> |
||||
|
||||
this_dir = File.expand_path(File.dirname(__FILE__)) |
||||
lib_dir = File.join(File.dirname(File.dirname(this_dir)), 'lib') |
||||
pb_dir = File.dirname(File.dirname(this_dir)) |
||||
$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir) |
||||
$LOAD_PATH.unshift(pb_dir) unless $LOAD_PATH.include?(pb_dir) |
||||
$LOAD_PATH.unshift(this_dir) unless $LOAD_PATH.include?(this_dir) |
||||
|
||||
require 'optparse' |
||||
|
||||
require 'grpc' |
||||
require 'googleauth' |
||||
require 'google/protobuf' |
||||
|
||||
require 'test/proto/empty' |
||||
require 'test/proto/messages' |
||||
require 'test/proto/test_services' |
||||
|
||||
require 'signet/ssl_config' |
||||
|
||||
AUTH_ENV = Google::Auth::CredentialsLoader::ENV_VAR |
||||
|
||||
# AssertionError is use to indicate interop test failures. |
||||
class AssertionError < RuntimeError; end |
||||
|
||||
# Fails with AssertionError if the block does evaluate to true |
||||
def assert(msg = 'unknown cause') |
||||
fail 'No assertion block provided' unless block_given? |
||||
fail AssertionError, msg unless yield |
||||
end |
||||
|
||||
# loads the certificates used to access the test server securely. |
||||
def load_test_certs |
||||
this_dir = File.expand_path(File.dirname(__FILE__)) |
||||
data_dir = File.join(File.dirname(File.dirname(this_dir)), 'spec/testdata') |
||||
files = ['ca.pem', 'server1.key', 'server1.pem'] |
||||
files.map { |f| File.open(File.join(data_dir, f)).read } |
||||
end |
||||
|
||||
# loads the certificates used to access the test server securely. |
||||
def load_prod_cert |
||||
fail 'could not find a production cert' if ENV['SSL_CERT_FILE'].nil? |
||||
GRPC.logger.info("loading prod certs from #{ENV['SSL_CERT_FILE']}") |
||||
File.open(ENV['SSL_CERT_FILE']).read |
||||
end |
||||
|
||||
# creates SSL Credentials from the test certificates. |
||||
def test_creds |
||||
certs = load_test_certs |
||||
GRPC::Core::Credentials.new(certs[0]) |
||||
end |
||||
|
||||
# creates SSL Credentials from the production certificates. |
||||
def prod_creds |
||||
cert_text = load_prod_cert |
||||
GRPC::Core::Credentials.new(cert_text) |
||||
end |
||||
|
||||
# creates the SSL Credentials. |
||||
def ssl_creds(use_test_ca) |
||||
return test_creds if use_test_ca |
||||
prod_creds |
||||
end |
||||
|
||||
# creates a test stub that accesses host:port securely. |
||||
def create_stub(opts) |
||||
address = "#{opts.host}:#{opts.port}" |
||||
if opts.secure |
||||
stub_opts = { |
||||
:creds => ssl_creds(opts.use_test_ca), |
||||
GRPC::Core::Channel::SSL_TARGET => opts.host_override |
||||
} |
||||
|
||||
# Add service account creds if specified |
||||
wants_creds = %w(all compute_engine_creds service_account_creds) |
||||
if wants_creds.include?(opts.test_case) |
||||
unless opts.oauth_scope.nil? |
||||
auth_creds = Google::Auth.get_application_default(opts.oauth_scope) |
||||
stub_opts[:update_metadata] = auth_creds.updater_proc |
||||
end |
||||
end |
||||
|
||||
if opts.test_case == 'oauth2_auth_token' |
||||
auth_creds = Google::Auth.get_application_default(opts.oauth_scope) |
||||
kw = auth_creds.updater_proc.call({}) # gives as an auth token |
||||
|
||||
# use a metadata update proc that just adds the auth token. |
||||
stub_opts[:update_metadata] = proc { |md| md.merge(kw) } |
||||
end |
||||
|
||||
if opts.test_case == 'jwt_token_creds' # don't use a scope |
||||
auth_creds = Google::Auth.get_application_default |
||||
stub_opts[:update_metadata] = auth_creds.updater_proc |
||||
end |
||||
|
||||
GRPC.logger.info("... connecting securely to #{address}") |
||||
Grpc::Testing::TestService::Stub.new(address, **stub_opts) |
||||
else |
||||
GRPC.logger.info("... connecting insecurely to #{address}") |
||||
Grpc::Testing::TestService::Stub.new(address) |
||||
end |
||||
end |
||||
|
||||
# produces a string of null chars (\0) of length l. |
||||
def nulls(l) |
||||
fail 'requires #{l} to be +ve' if l < 0 |
||||
[].pack('x' * l).force_encoding('utf-8') |
||||
end |
||||
|
||||
# a PingPongPlayer implements the ping pong bidi test. |
||||
class PingPongPlayer |
||||
include Grpc::Testing |
||||
include Grpc::Testing::PayloadType |
||||
attr_accessor :queue |
||||
attr_accessor :canceller_op |
||||
|
||||
# reqs is the enumerator over the requests |
||||
def initialize(msg_sizes) |
||||
@queue = Queue.new |
||||
@msg_sizes = msg_sizes |
||||
@canceller_op = nil # used to cancel after the first response |
||||
end |
||||
|
||||
def each_item |
||||
return enum_for(:each_item) unless block_given? |
||||
req_cls, p_cls = StreamingOutputCallRequest, ResponseParameters # short |
||||
count = 0 |
||||
@msg_sizes.each do |m| |
||||
req_size, resp_size = m |
||||
req = req_cls.new(payload: Payload.new(body: nulls(req_size)), |
||||
response_type: :COMPRESSABLE, |
||||
response_parameters: [p_cls.new(size: resp_size)]) |
||||
yield req |
||||
resp = @queue.pop |
||||
assert('payload type is wrong') { :COMPRESSABLE == resp.payload.type } |
||||
assert("payload body #{count} has the wrong length") do |
||||
resp_size == resp.payload.body.length |
||||
end |
||||
p "OK: ping_pong #{count}" |
||||
count += 1 |
||||
unless @canceller_op.nil? |
||||
canceller_op.cancel |
||||
break |
||||
end |
||||
end |
||||
end |
||||
end |
||||
|
||||
# defines methods corresponding to each interop test case. |
||||
class NamedTests |
||||
include Grpc::Testing |
||||
include Grpc::Testing::PayloadType |
||||
|
||||
def initialize(stub, args) |
||||
@stub = stub |
||||
@args = args |
||||
end |
||||
|
||||
def empty_unary |
||||
resp = @stub.empty_call(Empty.new) |
||||
assert('empty_unary: invalid response') { resp.is_a?(Empty) } |
||||
p 'OK: empty_unary' |
||||
end |
||||
|
||||
def large_unary |
||||
perform_large_unary |
||||
p 'OK: large_unary' |
||||
end |
||||
|
||||
def service_account_creds |
||||
# ignore this test if the oauth options are not set |
||||
if @args.oauth_scope.nil? |
||||
p 'NOT RUN: service_account_creds; no service_account settings' |
||||
return |
||||
end |
||||
json_key = File.read(ENV[AUTH_ENV]) |
||||
wanted_email = MultiJson.load(json_key)['client_email'] |
||||
resp = perform_large_unary(fill_username: true, |
||||
fill_oauth_scope: true) |
||||
assert("#{__callee__}: bad username") { wanted_email == resp.username } |
||||
assert("#{__callee__}: bad oauth scope") do |
||||
@args.oauth_scope.include?(resp.oauth_scope) |
||||
end |
||||
p "OK: #{__callee__}" |
||||
end |
||||
|
||||
def jwt_token_creds |
||||
json_key = File.read(ENV[AUTH_ENV]) |
||||
wanted_email = MultiJson.load(json_key)['client_email'] |
||||
resp = perform_large_unary(fill_username: true) |
||||
assert("#{__callee__}: bad username") { wanted_email == resp.username } |
||||
p "OK: #{__callee__}" |
||||
end |
||||
|
||||
def compute_engine_creds |
||||
resp = perform_large_unary(fill_username: true, |
||||
fill_oauth_scope: true) |
||||
assert("#{__callee__}: bad username") do |
||||
@args.default_service_account == resp.username |
||||
end |
||||
p "OK: #{__callee__}" |
||||
end |
||||
|
||||
def oauth2_auth_token |
||||
resp = perform_large_unary(fill_username: true, |
||||
fill_oauth_scope: true) |
||||
json_key = File.read(ENV[AUTH_ENV]) |
||||
wanted_email = MultiJson.load(json_key)['client_email'] |
||||
assert("#{__callee__}: bad username") { wanted_email == resp.username } |
||||
assert("#{__callee__}: bad oauth scope") do |
||||
@args.oauth_scope.include?(resp.oauth_scope) |
||||
end |
||||
p "OK: #{__callee__}" |
||||
end |
||||
|
||||
def per_rpc_creds |
||||
auth_creds = Google::Auth.get_application_default(@args.oauth_scope) |
||||
kw = auth_creds.updater_proc.call({}) |
||||
resp = perform_large_unary(fill_username: true, |
||||
fill_oauth_scope: true, |
||||
**kw) |
||||
json_key = File.read(ENV[AUTH_ENV]) |
||||
wanted_email = MultiJson.load(json_key)['client_email'] |
||||
assert("#{__callee__}: bad username") { wanted_email == resp.username } |
||||
assert("#{__callee__}: bad oauth scope") do |
||||
@args.oauth_scope.include?(resp.oauth_scope) |
||||
end |
||||
p "OK: #{__callee__}" |
||||
end |
||||
|
||||
def client_streaming |
||||
msg_sizes = [27_182, 8, 1828, 45_904] |
||||
wanted_aggregate_size = 74_922 |
||||
reqs = msg_sizes.map do |x| |
||||
req = Payload.new(body: nulls(x)) |
||||
StreamingInputCallRequest.new(payload: req) |
||||
end |
||||
resp = @stub.streaming_input_call(reqs) |
||||
assert("#{__callee__}: aggregate payload size is incorrect") do |
||||
wanted_aggregate_size == resp.aggregated_payload_size |
||||
end |
||||
p "OK: #{__callee__}" |
||||
end |
||||
|
||||
def server_streaming |
||||
msg_sizes = [31_415, 9, 2653, 58_979] |
||||
response_spec = msg_sizes.map { |s| ResponseParameters.new(size: s) } |
||||
req = StreamingOutputCallRequest.new(response_type: :COMPRESSABLE, |
||||
response_parameters: response_spec) |
||||
resps = @stub.streaming_output_call(req) |
||||
resps.each_with_index do |r, i| |
||||
assert("#{__callee__}: too many responses") { i < msg_sizes.length } |
||||
assert("#{__callee__}: payload body #{i} has the wrong length") do |
||||
msg_sizes[i] == r.payload.body.length |
||||
end |
||||
assert("#{__callee__}: payload type is wrong") do |
||||
:COMPRESSABLE == r.payload.type |
||||
end |
||||
end |
||||
p "OK: #{__callee__}" |
||||
end |
||||
|
||||
def ping_pong |
||||
msg_sizes = [[27_182, 31_415], [8, 9], [1828, 2653], [45_904, 58_979]] |
||||
ppp = PingPongPlayer.new(msg_sizes) |
||||
resps = @stub.full_duplex_call(ppp.each_item) |
||||
resps.each { |r| ppp.queue.push(r) } |
||||
p "OK: #{__callee__}" |
||||
end |
||||
|
||||
def timeout_on_sleeping_server |
||||
msg_sizes = [[27_182, 31_415]] |
||||
ppp = PingPongPlayer.new(msg_sizes) |
||||
resps = @stub.full_duplex_call(ppp.each_item, timeout: 0.001) |
||||
resps.each { |r| ppp.queue.push(r) } |
||||
fail 'Should have raised GRPC::BadStatus(DEADLINE_EXCEEDED)' |
||||
rescue GRPC::BadStatus => e |
||||
assert("#{__callee__}: status was wrong") do |
||||
e.code == GRPC::Core::StatusCodes::DEADLINE_EXCEEDED |
||||
end |
||||
p "OK: #{__callee__}" |
||||
end |
||||
|
||||
def empty_stream |
||||
ppp = PingPongPlayer.new([]) |
||||
resps = @stub.full_duplex_call(ppp.each_item) |
||||
count = 0 |
||||
resps.each do |r| |
||||
ppp.queue.push(r) |
||||
count += 1 |
||||
end |
||||
assert("#{__callee__}: too many responses expected 0") do |
||||
count == 0 |
||||
end |
||||
p "OK: #{__callee__}" |
||||
end |
||||
|
||||
def cancel_after_begin |
||||
msg_sizes = [27_182, 8, 1828, 45_904] |
||||
reqs = msg_sizes.map do |x| |
||||
req = Payload.new(body: nulls(x)) |
||||
StreamingInputCallRequest.new(payload: req) |
||||
end |
||||
op = @stub.streaming_input_call(reqs, return_op: true) |
||||
op.cancel |
||||
op.execute |
||||
fail 'Should have raised GRPC:Cancelled' |
||||
rescue GRPC::Cancelled |
||||
assert("#{__callee__}: call operation should be CANCELLED") { op.cancelled } |
||||
p "OK: #{__callee__}" |
||||
end |
||||
|
||||
def cancel_after_first_response |
||||
msg_sizes = [[27_182, 31_415], [8, 9], [1828, 2653], [45_904, 58_979]] |
||||
ppp = PingPongPlayer.new(msg_sizes) |
||||
op = @stub.full_duplex_call(ppp.each_item, return_op: true) |
||||
ppp.canceller_op = op # causes ppp to cancel after the 1st message |
||||
op.execute.each { |r| ppp.queue.push(r) } |
||||
fail 'Should have raised GRPC:Cancelled' |
||||
rescue GRPC::Cancelled |
||||
assert("#{__callee__}: call operation should be CANCELLED") { op.cancelled } |
||||
op.wait |
||||
p "OK: #{__callee__}" |
||||
end |
||||
|
||||
def all |
||||
all_methods = NamedTests.instance_methods(false).map(&:to_s) |
||||
all_methods.each do |m| |
||||
next if m == 'all' || m.start_with?('assert') |
||||
p "TESTCASE: #{m}" |
||||
method(m).call |
||||
end |
||||
end |
||||
|
||||
private |
||||
|
||||
def perform_large_unary(fill_username: false, fill_oauth_scope: false, **kw) |
||||
req_size, wanted_response_size = 271_828, 314_159 |
||||
payload = Payload.new(type: :COMPRESSABLE, body: nulls(req_size)) |
||||
req = SimpleRequest.new(response_type: :COMPRESSABLE, |
||||
response_size: wanted_response_size, |
||||
payload: payload) |
||||
req.fill_username = fill_username |
||||
req.fill_oauth_scope = fill_oauth_scope |
||||
resp = @stub.unary_call(req, **kw) |
||||
assert('payload type is wrong') do |
||||
:COMPRESSABLE == resp.payload.type |
||||
end |
||||
assert('payload body has the wrong length') do |
||||
wanted_response_size == resp.payload.body.length |
||||
end |
||||
assert('payload body is invalid') do |
||||
nulls(wanted_response_size) == resp.payload.body |
||||
end |
||||
resp |
||||
end |
||||
end |
||||
|
||||
# Args is used to hold the command line info. |
||||
Args = Struct.new(:default_service_account, :host, :host_override, |
||||
:oauth_scope, :port, :secure, :test_case, |
||||
:use_test_ca) |
||||
|
||||
# validates the the command line options, returning them as a Hash. |
||||
def parse_args |
||||
args = Args.new |
||||
args.host_override = 'foo.test.google.fr' |
||||
OptionParser.new do |opts| |
||||
opts.on('--oauth_scope scope', |
||||
'Scope for OAuth tokens') { |v| args['oauth_scope'] = v } |
||||
opts.on('--server_host SERVER_HOST', 'server hostname') do |v| |
||||
args['host'] = v |
||||
end |
||||
opts.on('--default_service_account email_address', |
||||
'email address of the default service account') do |v| |
||||
args['default_service_account'] = v |
||||
end |
||||
opts.on('--server_host_override HOST_OVERRIDE', |
||||
'override host via a HTTP header') do |v| |
||||
args['host_override'] = v |
||||
end |
||||
opts.on('--server_port SERVER_PORT', 'server port') { |v| args['port'] = v } |
||||
# instance_methods(false) gives only the methods defined in that class |
||||
test_cases = NamedTests.instance_methods(false).map(&:to_s) |
||||
test_case_list = test_cases.join(',') |
||||
opts.on('--test_case CODE', test_cases, {}, 'select a test_case', |
||||
" (#{test_case_list})") { |v| args['test_case'] = v } |
||||
opts.on('-s', '--use_tls', 'require a secure connection?') do |v| |
||||
args['secure'] = v |
||||
end |
||||
opts.on('-t', '--use_test_ca', |
||||
'if secure, use the test certificate?') do |v| |
||||
args['use_test_ca'] = v |
||||
end |
||||
end.parse! |
||||
_check_args(args) |
||||
end |
||||
|
||||
def _check_args(args) |
||||
%w(host port test_case).each do |a| |
||||
if args[a].nil? |
||||
fail(OptionParser::MissingArgument, "please specify --#{a}") |
||||
end |
||||
end |
||||
args |
||||
end |
||||
|
||||
def main |
||||
opts = parse_args |
||||
stub = create_stub(opts) |
||||
NamedTests.new(stub, opts).method(opts['test_case']).call |
||||
end |
||||
|
||||
main |
@ -0,0 +1,15 @@ |
||||
# Generated by the protocol buffer compiler. DO NOT EDIT! |
||||
# source: test/proto/empty.proto |
||||
|
||||
require 'google/protobuf' |
||||
|
||||
Google::Protobuf::DescriptorPool.generated_pool.build do |
||||
add_message "grpc.testing.Empty" do |
||||
end |
||||
end |
||||
|
||||
module Grpc |
||||
module Testing |
||||
Empty = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.Empty").msgclass |
||||
end |
||||
end |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue