Merge pull request #4188 from ctiller/no-transport-metadata

Remove metadata contexts
diff --git a/doc/grpc-auth-support.md b/doc/grpc-auth-support.md
deleted file mode 100644
index b210ff1..0000000
--- a/doc/grpc-auth-support.md
+++ /dev/null
@@ -1,289 +0,0 @@
-#gRPC Authentication support
-
-gRPC is designed to plug-in a number of authentication mechanisms. This document
-provides a quick overview of the various auth mechanisms supported, discusses
-the API with some examples, and concludes with a discussion of extensibility.
-More documentation and examples are coming soon!
-
-## Supported auth mechanisms
-
-###SSL/TLS
-gRPC has SSL/TLS integration and promotes the use of SSL/TLS to authenticate the
-server, and encrypt all the data exchanged between the client and the server.
-Optional mechanisms are available for clients to provide certificates to
-accomplish mutual authentication.
-
-###OAuth 2.0
-gRPC provides a generic mechanism (described below) to attach metadata to
-requests and responses. This mechanism can be used to attach OAuth 2.0 Access
-Tokens to RPCs being made at a client. Additional support for acquiring Access
-Tokens while accessing Google APIs through gRPC is provided for certain auth
-flows, demonstrated through code examples below.
-
-## API
-To reduce complexity and minimize API clutter, gRPC works with a unified concept
-of a Credentials object. Users construct gRPC credentials using corresponding
-bootstrap credentials (e.g., SSL client certs or Service Account Keys), and use
-the credentials while creating a gRPC channel to any server. Depending on the
-type of credential supplied, the channel uses the credentials during the initial
-SSL/TLS handshake with the server, or uses  the credential to generate and
-attach Access Tokens to each request being made on the channel.
-
-###SSL/TLS for server authentication and encryption
-This is the simplest authentication scenario, where a client just wants to
-authenticate the server and encrypt all data.
-
-```cpp
-SslCredentialsOptions ssl_opts;  // Options to override SSL params, empty by default
-// Create the credentials object by providing service account key in constructor
-std::shared_ptr<ChannelCredentials> creds = SslCredentials(ssl_opts);
-// Create a channel using the credentials created in the previous step
-std::shared_ptr<Channel> channel = CreateChannel(server_name, creds);
-// Create a stub on the channel
-std::unique_ptr<Greeter::Stub> stub(Greeter::NewStub(channel));
-// Make actual RPC calls on the stub.
-grpc::Status s = stub->sayHello(&context, *request, response);
-```
-
-For advanced use cases such as modifying the root CA or using client certs,
-the corresponding options can be set in the SslCredentialsOptions parameter
-passed to the factory method.
-
-
-###Authenticating with Google
-
-gRPC applications can use a simple API to create a credential that works in various deployment scenarios.
-
-```cpp
-std::shared_ptr<ChannelCredentials> creds = GoogleDefaultCredentials();
-// Create a channel, stub and make RPC calls (same as in the previous example)
-std::shared_ptr<Channel> channel = CreateChannel(server_name, creds);
-std::unique_ptr<Greeter::Stub> stub(Greeter::NewStub(channel));
-grpc::Status s = stub->sayHello(&context, *request, response);
-```
-
-This credential works for applications using Service Accounts as well as for
-applications running in [Google Compute Engine (GCE)](https://cloud.google.com/compute/). In the former case, the
-service account’s private keys are loaded from the file named in the environment
-variable `GOOGLE_APPLICATION_CREDENTIALS`. The
-keys are used to generate bearer tokens that are attached to each outgoing RPC
-on the corresponding channel.
-
-For applications running in GCE, a default service account and corresponding
-OAuth scopes can be configured during VM setup. At run-time, this credential
-handles communication with the authentication systems to obtain OAuth2 access
-tokens and attaches them to each outgoing RPC on the corresponding channel.
-Extending gRPC to support other authentication mechanisms
-The gRPC protocol is designed with a general mechanism for sending metadata
-associated with RPC. Clients can send metadata at the beginning of an RPC and
-servers can send back metadata at the beginning and end of the RPC. This
-provides a natural mechanism to support OAuth2 and other authentication
-mechanisms that need attach bearer tokens to individual request.
-
-In the simplest case, there is a single line of code required on the client
-to add a specific token as metadata to an RPC and a corresponding access on
-the server to retrieve this piece of metadata. The generation of the token
-on the client side and its verification at the server can be done separately.
-
-A deeper integration can be achieved by plugging in a gRPC credentials implementation for any custom authentication mechanism that needs to attach per-request tokens. gRPC internals also allow switching out SSL/TLS with other encryption mechanisms.
-
-## Examples
-
-These authentication mechanisms will be available in all gRPC's supported languages.
-The following sections demonstrate how authentication and authorization features described above appear in each language: more languages are coming soon.
-
-###SSL/TLS for server authentication and encryption (Ruby)
-```ruby
-# Base case - No encryption
-stub = Helloworld::Greeter::Stub.new('localhost:50051')
-...
-
-# With server authentication SSL/TLS
-creds = GRPC::Core::Credentials.new(load_certs)  # load_certs typically loads a CA roots file
-stub = Helloworld::Greeter::Stub.new('localhost:50051', creds: creds)
-```
-
-###SSL/TLS for server authentication and encryption (C#)
-```csharp
-// Base case - No encryption
-var channel = new Channel("localhost:50051");
-var client = new Greeter.GreeterClient(channel);
-...
-
-// With server authentication SSL/TLS
-var credentials = new SslCredentials(File.ReadAllText("ca.pem"));  // Load a CA file
-var channel = new Channel("localhost:50051", credentials);
-var client = new Greeter.GreeterClient(channel);
-```
-
-###SSL/TLS for server authentication and encryption (Objective-C)
-
-The default for Objective-C is to use SSL/TLS, as that's the most common use case when accessing
-remote APIs.
-
-```objective-c
-// Base case - With server authentication SSL/TLS
-HLWGreeter *client = [[HLWGreeter alloc] initWithHost:@"localhost:50051"];
-// Same as using @"https://localhost:50051".
-...
-
-// No encryption
-HLWGreeter *client = [[HLWGreeter alloc] initWithHost:@"http://localhost:50051"];
-// Specifying the HTTP scheme explicitly forces no encryption.
-```
-
-###SSL/TLS for server authentication and encryption (Python)
-```python
-# Base case - No encryption
-stub = early_adopter_create_GreeterService_stub('localhost', 50051)
-...
-
-# With server authentication SSL/TLS
-stub = early_adopter_create_GreeterService_stub(
-  'localhost', 50051, secure=True, root_certificates=open('ca.pem').read())
-...
-```
-n.b.: the beta API will look different
-
-###Authenticating with Google (Ruby)
-```ruby
-# Base case - No encryption/authorization
-stub = Helloworld::Greeter::Stub.new('localhost:50051')
-...
-
-# Authenticating with Google
-require 'googleauth'  # from http://www.rubydoc.info/gems/googleauth/0.1.0
-...
-creds = GRPC::Core::Credentials.new(load_certs)  # load_certs typically loads a CA roots file
-scope = 'https://www.googleapis.com/auth/grpc-testing'
-authorization = Google::Auth.get_application_default(scope)
-stub = Helloworld::Greeter::Stub.new('localhost:50051',
-                                     creds: creds,
-                                     update_metadata: authorization.updater_proc)
-```
-
-###Authenticating with Google (Node.js)
-
-```node
-// Base case - No encryption/authorization
-var stub = new helloworld.Greeter('localhost:50051');
-...
-// Authenticating with Google
-var GoogleAuth = require('google-auth-library'); // from https://www.npmjs.com/package/google-auth-library
-...
-var creds = grpc.Credentials.createSsl(load_certs); // load_certs typically loads a CA roots file
-var scope = 'https://www.googleapis.com/auth/grpc-testing';
-(new GoogleAuth()).getApplicationDefault(function(err, auth) {
-  if (auth.createScopeRequired()) {
-    auth = auth.createScoped(scope);
-  }
-  var stub = new helloworld.Greeter('localhost:50051',
-                                    {credentials: creds},
-                                    grpc.getGoogleAuthDelegate(auth));
-});
-```
-
-###Authenticating with Google (C#)
-```csharp
-// Base case - No encryption/authorization
-var channel = new Channel("localhost:50051");
-var client = new Greeter.GreeterClient(channel);
-...
-
-// Authenticating with Google
-using Grpc.Auth;  // from Grpc.Auth NuGet package
-...
-var credentials = new SslCredentials(File.ReadAllText("ca.pem"));  // Load a CA file
-var channel = new Channel("localhost:50051", credentials);
-
-string scope = "https://www.googleapis.com/auth/grpc-testing";
-var authorization = GoogleCredential.GetApplicationDefault();
-if (authorization.IsCreateScopedRequired)
-{
-    authorization = credential.CreateScoped(new[] { scope });
-}
-var client = new Greeter.GreeterClient(channel,
-        new StubConfiguration(OAuth2InterceptorFactory.Create(credential)));
-```
-
-###Authenticating with Google (PHP)
-```php
-// Base case - No encryption/authorization
-$client = new helloworld\GreeterClient(
-  new Grpc\BaseStub('localhost:50051', []));
-...
-
-// Authenticating with Google
-// the environment variable "GOOGLE_APPLICATION_CREDENTIALS" needs to be set
-$scope = "https://www.googleapis.com/auth/grpc-testing";
-$auth = Google\Auth\ApplicationDefaultCredentials::getCredentials($scope);
-$opts = [
-  'credentials' => Grpc\Credentials::createSsl(file_get_contents('ca.pem'));
-  'update_metadata' => $auth->getUpdateMetadataFunc(),
-];
-
-$client = new helloworld\GreeterClient(
-  new Grpc\BaseStub('localhost:50051', $opts));
-
-```
-
-###Authenticating with Google (Objective-C)
-
-This example uses the [Google iOS Sign-In library](https://developers.google.com/identity/sign-in/ios/),
-but it's easily extrapolated to any other OAuth2 library.
-
-```objective-c
-// Base case - No authentication
-[client sayHelloWithRequest:request handler:^(HLWHelloReply *response, NSError *error) {
-  ...
-}];
-
-...
-
-// Authenticating with Google
-
-// When signing the user in, ask her for the relevant scopes.
-GIDSignIn.sharedInstance.scopes = @[@"https://www.googleapis.com/auth/grpc-testing"];
-
-...
-
-#import <ProtoRPC/ProtoRPC.h>
-
-// Create a not-yet-started RPC. We want to set the request headers on this object before starting
-// it.
-ProtoRPC *call =
-    [client RPCToSayHelloWithRequest:request handler:^(HLWHelloReply *response, NSError *error) {
-      ...
-    }];
-
-// Set the access token to be used.
-NSString *accessToken = GIDSignIn.sharedInstance.currentUser.authentication.accessToken;
-call.requestMetadata[@"Authorization"] = [@"Bearer " stringByAppendingString:accessToken]}];
-
-// Start the RPC.
-[call start];
-```
-
-You can see a working example app, with a more detailed explanation, [here](examples/objective-c/auth_sample).
-
-### Authenticating with Google (Python)
-```python
-# Base case - No encryption
-stub = early_adopter_create_GreeterService_stub('localhost', 50051)
-...
-
-# With server authentication SSL/TLS
-import oauth2client.client
-credentials = oauth2client.GoogleCredentials.get_application_default()
-scope = 'https://www.googleapis.com/auth/grpc-testing'
-scoped_credentials = credentials.create_scoped([scope])
-access_token = scoped_credentials.get_access_token().access_token
-metadata_transformer = (
-    lambda x: [('Authorization', 'Bearer {}'.format(access_token))])
-
-stub = early_adopter_create_GreeterService_stub(
-  'localhost', 50051, secure=True, root_certificates=open('ca.pem').read(),
-  metadata_transformer=metadata_transformer)
-...
-```
-n.b.: the beta API will look different
diff --git a/examples/objective-c/auth_sample/AuthTestService.podspec b/examples/objective-c/auth_sample/AuthTestService.podspec
index 9521d49..e935626 100644
--- a/examples/objective-c/auth_sample/AuthTestService.podspec
+++ b/examples/objective-c/auth_sample/AuthTestService.podspec
@@ -29,7 +29,7 @@
     ss.source_files = "#{dir}/*.pbrpc.{h,m}", "#{dir}/**/*.pbrpc.{h,m}"
     ss.header_mappings_dir = dir
     ss.requires_arc = true
-    ss.dependency "gRPC", "~> 0.11"
+    ss.dependency "gRPC", "~> 0.12"
     ss.dependency "#{s.name}/Messages"
   end
 end
diff --git a/examples/objective-c/helloworld/HelloWorld.podspec b/examples/objective-c/helloworld/HelloWorld.podspec
index 600898f..bdf782f 100644
--- a/examples/objective-c/helloworld/HelloWorld.podspec
+++ b/examples/objective-c/helloworld/HelloWorld.podspec
@@ -29,7 +29,7 @@
     ss.source_files = "#{dir}/*.pbrpc.{h,m}", "#{dir}/**/*.pbrpc.{h,m}"
     ss.header_mappings_dir = dir
     ss.requires_arc = true
-    ss.dependency "gRPC", "~> 0.11"
+    ss.dependency "gRPC", "~> 0.12"
     ss.dependency "#{s.name}/Messages"
   end
 end
diff --git a/examples/objective-c/route_guide/RouteGuide.podspec b/examples/objective-c/route_guide/RouteGuide.podspec
index e00f827..4bc2c42 100644
--- a/examples/objective-c/route_guide/RouteGuide.podspec
+++ b/examples/objective-c/route_guide/RouteGuide.podspec
@@ -29,7 +29,7 @@
     ss.source_files = "#{dir}/*.pbrpc.{h,m}", "#{dir}/**/*.pbrpc.{h,m}"
     ss.header_mappings_dir = dir
     ss.requires_arc = true
-    ss.dependency "gRPC", "~> 0.11"
+    ss.dependency "gRPC", "~> 0.12"
     ss.dependency "#{s.name}/Messages"
   end
 end
diff --git a/gRPC.podspec b/gRPC.podspec
index 777638e..0262479 100644
--- a/gRPC.podspec
+++ b/gRPC.podspec
@@ -36,7 +36,7 @@
 
 Pod::Spec.new do |s|
   s.name     = 'gRPC'
-  version = '0.11.2'
+  version = '0.12.0'
   s.version  = version
   s.summary  = 'gRPC client library for iOS/OSX'
   s.homepage = 'http://www.grpc.io'
@@ -589,7 +589,7 @@
 
     ss.requires_arc = false
     ss.libraries = 'z'
-    ss.dependency 'OpenSSL', '~> 1.0.200'
+    ss.dependency 'OpenSSL', '~> 1.0.204.1'
 
     # ss.compiler_flags = '-GCC_WARN_INHIBIT_ALL_WARNINGS', '-w'
   end
diff --git a/include/grpc++/security/credentials.h b/include/grpc++/security/credentials.h
index cc827f0..e5652dc 100644
--- a/include/grpc++/security/credentials.h
+++ b/include/grpc++/security/credentials.h
@@ -38,6 +38,7 @@
 #include <memory>
 
 #include <grpc++/impl/grpc_library.h>
+#include <grpc++/security/auth_context.h>
 #include <grpc++/support/config.h>
 #include <grpc++/support/status.h>
 #include <grpc++/support/string_ref.h>
@@ -206,9 +207,17 @@
   // a different thread from the one processing the call.
   virtual bool IsBlocking() const { return true; }
 
+  // Type of credentials this plugin is implementing.
+  virtual const char* GetType() const { return ""; }
+
   // Gets the auth metatada produced by this plugin.
+  // The fully qualified method name is:
+  // service_url + "/" + method_name.
+  // The channel_auth_context contains (among other things), the identity of
+  // the server.
   virtual Status GetMetadata(
-      grpc::string_ref service_url,
+      grpc::string_ref service_url, grpc::string_ref method_name,
+      const AuthContext& channel_auth_context,
       std::multimap<grpc::string, grpc::string>* metadata) = 0;
 };
 
diff --git a/include/grpc/grpc_security.h b/include/grpc/grpc_security.h
index c8da593..655f45a 100644
--- a/include/grpc/grpc_security.h
+++ b/include/grpc/grpc_security.h
@@ -41,6 +41,81 @@
 extern "C" {
 #endif
 
+/* --- Authentication Context. --- */
+
+#define GRPC_TRANSPORT_SECURITY_TYPE_PROPERTY_NAME "transport_security_type"
+#define GRPC_SSL_TRANSPORT_SECURITY_TYPE "ssl"
+
+#define GRPC_X509_CN_PROPERTY_NAME "x509_common_name"
+#define GRPC_X509_SAN_PROPERTY_NAME "x509_subject_alternative_name"
+
+typedef struct grpc_auth_context grpc_auth_context;
+
+typedef struct grpc_auth_property_iterator {
+  const grpc_auth_context *ctx;
+  size_t index;
+  const char *name;
+} grpc_auth_property_iterator;
+
+/* value, if not NULL, is guaranteed to be NULL terminated. */
+typedef struct grpc_auth_property {
+  char *name;
+  char *value;
+  size_t value_length;
+} grpc_auth_property;
+
+/* Returns NULL when the iterator is at the end. */
+const grpc_auth_property *grpc_auth_property_iterator_next(
+    grpc_auth_property_iterator *it);
+
+/* Iterates over the auth context. */
+grpc_auth_property_iterator grpc_auth_context_property_iterator(
+    const grpc_auth_context *ctx);
+
+/* Gets the peer identity. Returns an empty iterator (first _next will return
+   NULL) if the peer is not authenticated. */
+grpc_auth_property_iterator grpc_auth_context_peer_identity(
+    const grpc_auth_context *ctx);
+
+/* Finds a property in the context. May return an empty iterator (first _next
+   will return NULL) if no property with this name was found in the context. */
+grpc_auth_property_iterator grpc_auth_context_find_properties_by_name(
+    const grpc_auth_context *ctx, const char *name);
+
+/* Gets the name of the property that indicates the peer identity. Will return
+   NULL if the peer is not authenticated. */
+const char *grpc_auth_context_peer_identity_property_name(
+    const grpc_auth_context *ctx);
+
+/* Returns 1 if the peer is authenticated, 0 otherwise. */
+int grpc_auth_context_peer_is_authenticated(const grpc_auth_context *ctx);
+
+/* Gets the auth context from the call. Caller needs to call
+   grpc_auth_context_release on the returned context. */
+grpc_auth_context *grpc_call_auth_context(grpc_call *call);
+
+/* Releases the auth context returned from grpc_call_auth_context. */
+void grpc_auth_context_release(grpc_auth_context *context);
+
+/* --
+   The following auth context methods should only be called by a server metadata
+   processor to set properties extracted from auth metadata.
+   -- */
+
+/* Add a property. */
+void grpc_auth_context_add_property(grpc_auth_context *ctx, const char *name,
+                                    const char *value, size_t value_length);
+
+/* Add a C string property. */
+void grpc_auth_context_add_cstring_property(grpc_auth_context *ctx,
+                                            const char *name,
+                                            const char *value);
+
+/* Sets the property name. Returns 1 if successful or 0 in case of failure
+   (which means that no property with this name exists). */
+int grpc_auth_context_set_peer_identity_property_name(grpc_auth_context *ctx,
+                                                      const char *name);
+
 /* --- grpc_channel_credentials object. ---
 
    A channel credentials object represents a way to authenticate a client on a
@@ -165,6 +240,24 @@
     void *user_data, const grpc_metadata *creds_md, size_t num_creds_md,
     grpc_status_code status, const char *error_details);
 
+/* Context that can be used by metadata credentials plugin in order to create
+   auth related metadata. */
+typedef struct {
+  /* The fully qualifed service url. */
+  const char *service_url;
+
+  /* The method name of the RPC being called (not fully qualified).
+     The fully qualified method name can be built from the service_url:
+     full_qualified_method_name = ctx->service_url + '/' + ctx->method_name. */
+  const char *method_name;
+
+  /* The auth_context of the channel which gives the server's identity. */
+  const grpc_auth_context *channel_auth_context;
+
+  /* Reserved for future use. */
+  void *reserved;
+} grpc_auth_metadata_context;
+
 /* grpc_metadata_credentials plugin is an API user provided structure used to
    create grpc_credentials objects that can be set on a channel (composed) or
    a call. See grpc_credentials_metadata_create_from_plugin below.
@@ -172,11 +265,11 @@
    every call in scope for the credentials created from it. */
 typedef struct {
   /* The implementation of this method has to be non-blocking.
-     - service_url is the fully qualified URL that the client stack is
-       connecting to.
+     - context is the information that can be used by the plugin to create auth
+       metadata.
      - cb is the callback that needs to be called when the metadata is ready.
      - user_data needs to be passed as the first parameter of the callback. */
-  void (*get_metadata)(void *state, const char *service_url,
+  void (*get_metadata)(void *state, grpc_auth_metadata_context context,
                        grpc_credentials_plugin_metadata_cb cb, void *user_data);
 
   /* Destroys the plugin state. */
@@ -184,6 +277,9 @@
 
   /* State that will be set as the first parameter of the methods above. */
   void *state;
+
+  /* Type of credentials that this plugin is implementing. */
+  const char *type;
 } grpc_metadata_credentials_plugin;
 
 /* Creates a credentials object from a plugin. */
@@ -239,81 +335,6 @@
 grpc_call_error grpc_call_set_credentials(grpc_call *call,
                                           grpc_call_credentials *creds);
 
-/* --- Authentication Context. --- */
-
-#define GRPC_TRANSPORT_SECURITY_TYPE_PROPERTY_NAME "transport_security_type"
-#define GRPC_SSL_TRANSPORT_SECURITY_TYPE "ssl"
-
-#define GRPC_X509_CN_PROPERTY_NAME "x509_common_name"
-#define GRPC_X509_SAN_PROPERTY_NAME "x509_subject_alternative_name"
-
-typedef struct grpc_auth_context grpc_auth_context;
-
-typedef struct grpc_auth_property_iterator {
-  const grpc_auth_context *ctx;
-  size_t index;
-  const char *name;
-} grpc_auth_property_iterator;
-
-/* value, if not NULL, is guaranteed to be NULL terminated. */
-typedef struct grpc_auth_property {
-  char *name;
-  char *value;
-  size_t value_length;
-} grpc_auth_property;
-
-/* Returns NULL when the iterator is at the end. */
-const grpc_auth_property *grpc_auth_property_iterator_next(
-    grpc_auth_property_iterator *it);
-
-/* Iterates over the auth context. */
-grpc_auth_property_iterator grpc_auth_context_property_iterator(
-    const grpc_auth_context *ctx);
-
-/* Gets the peer identity. Returns an empty iterator (first _next will return
-   NULL) if the peer is not authenticated. */
-grpc_auth_property_iterator grpc_auth_context_peer_identity(
-    const grpc_auth_context *ctx);
-
-/* Finds a property in the context. May return an empty iterator (first _next
-   will return NULL) if no property with this name was found in the context. */
-grpc_auth_property_iterator grpc_auth_context_find_properties_by_name(
-    const grpc_auth_context *ctx, const char *name);
-
-/* Gets the name of the property that indicates the peer identity. Will return
-   NULL if the peer is not authenticated. */
-const char *grpc_auth_context_peer_identity_property_name(
-    const grpc_auth_context *ctx);
-
-/* Returns 1 if the peer is authenticated, 0 otherwise. */
-int grpc_auth_context_peer_is_authenticated(const grpc_auth_context *ctx);
-
-/* Gets the auth context from the call. Caller needs to call
-   grpc_auth_context_release on the returned context. */
-grpc_auth_context *grpc_call_auth_context(grpc_call *call);
-
-/* Releases the auth context returned from grpc_call_auth_context. */
-void grpc_auth_context_release(grpc_auth_context *context);
-
-/* --
-   The following auth context methods should only be called by a server metadata
-   processor to set properties extracted from auth metadata.
-   -- */
-
-/* Add a property. */
-void grpc_auth_context_add_property(grpc_auth_context *ctx, const char *name,
-                                    const char *value, size_t value_length);
-
-/* Add a C string property. */
-void grpc_auth_context_add_cstring_property(grpc_auth_context *ctx,
-                                            const char *name,
-                                            const char *value);
-
-/* Sets the property name. Returns 1 if successful or 0 in case of failure
-   (which means that no property with this name exists). */
-int grpc_auth_context_set_peer_identity_property_name(grpc_auth_context *ctx,
-                                                      const char *name);
-
 /* --- Auth Metadata Processing --- */
 
 /* Callback function that is called when the metadata processing is done.
diff --git a/package.json b/package.json
index 1fd0ffb..9517c59 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "grpc",
-  "version": "0.11.1",
+  "version": "0.12.0",
   "author": "Google Inc.",
   "description": "gRPC Library for Node",
   "homepage": "http://www.grpc.io/",
diff --git a/src/core/iomgr/fd_posix.c b/src/core/iomgr/fd_posix.c
index 7ff80e6..2be0ea2 100644
--- a/src/core/iomgr/fd_posix.c
+++ b/src/core/iomgr/fd_posix.c
@@ -207,14 +207,21 @@
 }
 
 void grpc_fd_orphan(grpc_exec_ctx *exec_ctx, grpc_fd *fd, grpc_closure *on_done,
-                    const char *reason) {
+                    int *release_fd, const char *reason) {
   fd->on_done_closure = on_done;
-  shutdown(fd->fd, SHUT_RDWR);
+  fd->released = release_fd != NULL;
+  if (!fd->released) {
+    shutdown(fd->fd, SHUT_RDWR);
+  } else {
+    *release_fd = fd->fd;
+  }
   gpr_mu_lock(&fd->mu);
   REF_BY(fd, 1, reason); /* remove active status, but keep referenced */
   if (!has_watchers(fd)) {
     fd->closed = 1;
-    close(fd->fd);
+    if (!fd->released) {
+      close(fd->fd);
+    }
     grpc_exec_ctx_enqueue(exec_ctx, fd->on_done_closure, 1);
   } else {
     wake_all_watchers_locked(fd);
@@ -406,7 +413,9 @@
   }
   if (grpc_fd_is_orphaned(fd) && !has_watchers(fd) && !fd->closed) {
     fd->closed = 1;
-    close(fd->fd);
+    if (!fd->released) {
+      close(fd->fd);
+    }
     grpc_exec_ctx_enqueue(exec_ctx, fd->on_done_closure, 1);
   }
   gpr_mu_unlock(&fd->mu);
diff --git a/src/core/iomgr/fd_posix.h b/src/core/iomgr/fd_posix.h
index dc917eb..d628ef3 100644
--- a/src/core/iomgr/fd_posix.h
+++ b/src/core/iomgr/fd_posix.h
@@ -62,6 +62,7 @@
   gpr_mu mu;
   int shutdown;
   int closed;
+  int released;
 
   /* The watcher list.
 
@@ -107,11 +108,12 @@
 /* Releases fd to be asynchronously destroyed.
    on_done is called when the underlying file descriptor is definitely close()d.
    If on_done is NULL, no callback will be made.
+   If release_fd is not NULL, it's set to fd and fd will not be closed.
    Requires: *fd initialized; no outstanding notify_on_read or
    notify_on_write.
    MUST NOT be called with a pollset lock taken */
 void grpc_fd_orphan(grpc_exec_ctx *exec_ctx, grpc_fd *fd, grpc_closure *on_done,
-                    const char *reason);
+                    int *release_fd, const char *reason);
 
 /* Begin polling on an fd.
    Registers that the given pollset is interested in this fd - so that if read
diff --git a/src/core/iomgr/tcp_client_posix.c b/src/core/iomgr/tcp_client_posix.c
index abd6315..d9d24ee 100644
--- a/src/core/iomgr/tcp_client_posix.c
+++ b/src/core/iomgr/tcp_client_posix.c
@@ -196,7 +196,7 @@
 finish:
   if (fd != NULL) {
     grpc_pollset_set_del_fd(exec_ctx, ac->interested_parties, fd);
-    grpc_fd_orphan(exec_ctx, fd, NULL, "tcp_client_orphan");
+    grpc_fd_orphan(exec_ctx, fd, NULL, NULL, "tcp_client_orphan");
     fd = NULL;
   }
   done = (--ac->refs == 0);
@@ -265,7 +265,7 @@
 
   if (errno != EWOULDBLOCK && errno != EINPROGRESS) {
     gpr_log(GPR_ERROR, "connect error to '%s': %s", addr_str, strerror(errno));
-    grpc_fd_orphan(exec_ctx, fdobj, NULL, "tcp_client_connect_error");
+    grpc_fd_orphan(exec_ctx, fdobj, NULL, NULL, "tcp_client_connect_error");
     grpc_exec_ctx_enqueue(exec_ctx, closure, 0);
     goto done;
   }
diff --git a/src/core/iomgr/tcp_posix.c b/src/core/iomgr/tcp_posix.c
index 915553d..f3be41a 100644
--- a/src/core/iomgr/tcp_posix.c
+++ b/src/core/iomgr/tcp_posix.c
@@ -90,6 +90,8 @@
 
   grpc_closure *read_cb;
   grpc_closure *write_cb;
+  grpc_closure *release_fd_cb;
+  int *release_fd;
 
   grpc_closure read_closure;
   grpc_closure write_closure;
@@ -108,7 +110,8 @@
 }
 
 static void tcp_free(grpc_exec_ctx *exec_ctx, grpc_tcp *tcp) {
-  grpc_fd_orphan(exec_ctx, tcp->em_fd, NULL, "tcp_unref_orphan");
+  grpc_fd_orphan(exec_ctx, tcp->em_fd, tcp->release_fd_cb, tcp->release_fd,
+                 "tcp_unref_orphan");
   gpr_slice_buffer_destroy(&tcp->last_read_buffer);
   gpr_free(tcp->peer_string);
   gpr_free(tcp);
@@ -452,6 +455,8 @@
   tcp->fd = em_fd->fd;
   tcp->read_cb = NULL;
   tcp->write_cb = NULL;
+  tcp->release_fd_cb = NULL;
+  tcp->release_fd = NULL;
   tcp->incoming_buffer = NULL;
   tcp->slice_size = slice_size;
   tcp->iov_size = 1;
@@ -468,4 +473,13 @@
   return &tcp->base;
 }
 
+void grpc_tcp_destroy_and_release_fd(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep,
+                                     int *fd, grpc_closure *done) {
+  grpc_tcp *tcp = (grpc_tcp *)ep;
+  GPR_ASSERT(ep->vtable == &vtable);
+  tcp->release_fd = fd;
+  tcp->release_fd_cb = done;
+  TCP_UNREF(exec_ctx, tcp, "destroy");
+}
+
 #endif
diff --git a/src/core/iomgr/tcp_posix.h b/src/core/iomgr/tcp_posix.h
index 40b3ae2..b554983 100644
--- a/src/core/iomgr/tcp_posix.h
+++ b/src/core/iomgr/tcp_posix.h
@@ -56,4 +56,10 @@
 grpc_endpoint *grpc_tcp_create(grpc_fd *fd, size_t read_slice_size,
                                const char *peer_string);
 
+/* Destroy the tcp endpoint without closing its fd. *fd will be set and done
+ * will be called when the endpoint is destroyed.
+ * Requires: ep must be a tcp endpoint and fd must not be NULL. */
+void grpc_tcp_destroy_and_release_fd(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep,
+                                     int *fd, grpc_closure *done);
+
 #endif /* GRPC_INTERNAL_CORE_IOMGR_TCP_POSIX_H */
diff --git a/src/core/iomgr/tcp_server_posix.c b/src/core/iomgr/tcp_server_posix.c
index 0ece77c..a89ee02 100644
--- a/src/core/iomgr/tcp_server_posix.c
+++ b/src/core/iomgr/tcp_server_posix.c
@@ -193,7 +193,7 @@
       }
       sp->destroyed_closure.cb = destroyed_port;
       sp->destroyed_closure.cb_arg = s;
-      grpc_fd_orphan(exec_ctx, sp->emfd, &sp->destroyed_closure,
+      grpc_fd_orphan(exec_ctx, sp->emfd, &sp->destroyed_closure, NULL,
                      "tcp_listener_shutdown");
     }
     gpr_mu_unlock(&s->mu);
diff --git a/src/core/iomgr/udp_server.c b/src/core/iomgr/udp_server.c
index 9903e97..782fbd9 100644
--- a/src/core/iomgr/udp_server.c
+++ b/src/core/iomgr/udp_server.c
@@ -179,7 +179,7 @@
       }
       sp->destroyed_closure.cb = destroyed_port;
       sp->destroyed_closure.cb_arg = s;
-      grpc_fd_orphan(exec_ctx, sp->emfd, &sp->destroyed_closure,
+      grpc_fd_orphan(exec_ctx, sp->emfd, &sp->destroyed_closure, NULL,
                      "udp_listener_shutdown");
     }
     gpr_mu_unlock(&s->mu);
diff --git a/src/core/iomgr/workqueue_posix.c b/src/core/iomgr/workqueue_posix.c
index c087b88..2e30178 100644
--- a/src/core/iomgr/workqueue_posix.c
+++ b/src/core/iomgr/workqueue_posix.c
@@ -115,7 +115,7 @@
     /* HACK: let wakeup_fd code know that we stole the fd */
     workqueue->wakeup_fd.read_fd = 0;
     grpc_wakeup_fd_destroy(&workqueue->wakeup_fd);
-    grpc_fd_orphan(exec_ctx, workqueue->wakeup_read_fd, NULL, "destroy");
+    grpc_fd_orphan(exec_ctx, workqueue->wakeup_read_fd, NULL, NULL, "destroy");
     gpr_free(workqueue);
   } else {
     gpr_mu_lock(&workqueue->mu);
diff --git a/src/core/security/client_auth_filter.c b/src/core/security/client_auth_filter.c
index cd4b39f..4e5be03 100644
--- a/src/core/security/client_auth_filter.c
+++ b/src/core/security/client_auth_filter.c
@@ -62,7 +62,7 @@
   grpc_transport_stream_op op;
   gpr_uint8 security_context_set;
   grpc_linked_mdelem md_links[MAX_CREDENTIALS_METADATA_COUNT];
-  char *service_url;
+  grpc_auth_metadata_context auth_md_context;
 } call_data;
 
 /* We can have a per-channel credentials. */
@@ -70,11 +70,20 @@
   grpc_channel_security_connector *security_connector;
 } channel_data;
 
-static void reset_service_url(call_data *calld) {
-  if (calld->service_url != NULL) {
-    gpr_free(calld->service_url);
-    calld->service_url = NULL;
+static void reset_auth_metadata_context(
+    grpc_auth_metadata_context *auth_md_context) {
+  if (auth_md_context->service_url != NULL) {
+    gpr_free((char *)auth_md_context->service_url);
+    auth_md_context->service_url = NULL;
   }
+  if (auth_md_context->method_name != NULL) {
+    gpr_free((char *)auth_md_context->method_name);
+    auth_md_context->method_name = NULL;
+  }
+  GRPC_AUTH_CONTEXT_UNREF(
+      (grpc_auth_context *)auth_md_context->channel_auth_context,
+      "grpc_auth_metadata_context");
+  auth_md_context->channel_auth_context = NULL;
 }
 
 static void bubble_up_error(grpc_exec_ctx *exec_ctx, grpc_call_element *elem,
@@ -94,7 +103,7 @@
   grpc_transport_stream_op *op = &calld->op;
   grpc_metadata_batch *mdb;
   size_t i;
-  reset_service_url(calld);
+  reset_auth_metadata_context(&calld->auth_md_context);
   if (status != GRPC_CREDENTIALS_OK) {
     bubble_up_error(exec_ctx, elem, GRPC_STATUS_UNAUTHENTICATED,
                     "Credentials failed to get metadata.");
@@ -112,9 +121,13 @@
   grpc_call_next_op(exec_ctx, elem, op);
 }
 
-void build_service_url(const char *url_scheme, call_data *calld) {
+void build_auth_metadata_context(grpc_security_connector *sc,
+                                 call_data *calld) {
   char *service = gpr_strdup(grpc_mdstr_as_c_string(calld->method));
   char *last_slash = strrchr(service, '/');
+  char *method_name = NULL;
+  char *service_url = NULL;
+  reset_auth_metadata_context(&calld->auth_md_context);
   if (last_slash == NULL) {
     gpr_log(GPR_ERROR, "No '/' found in fully qualified method name");
     service[0] = '\0';
@@ -123,11 +136,16 @@
     service[1] = '\0';
   } else {
     *last_slash = '\0';
+    method_name = gpr_strdup(last_slash + 1);
   }
-  if (url_scheme == NULL) url_scheme = "";
-  reset_service_url(calld);
-  gpr_asprintf(&calld->service_url, "%s://%s%s", url_scheme,
+  if (method_name == NULL) method_name = gpr_strdup("");
+  gpr_asprintf(&service_url, "%s://%s%s",
+               sc->url_scheme == NULL ? "" : sc->url_scheme,
                grpc_mdstr_as_c_string(calld->host), service);
+  calld->auth_md_context.service_url = service_url;
+  calld->auth_md_context.method_name = method_name;
+  calld->auth_md_context.channel_auth_context =
+      GRPC_AUTH_CONTEXT_REF(sc->auth_context, "grpc_auth_metadata_context");
   gpr_free(service);
 }
 
@@ -161,12 +179,12 @@
         call_creds_has_md ? ctx->creds : channel_call_creds);
   }
 
-  build_service_url(chand->security_connector->base.url_scheme, calld);
+  build_auth_metadata_context(&chand->security_connector->base, calld);
   calld->op = *op; /* Copy op (originates from the caller's stack). */
   GPR_ASSERT(calld->pollset);
-  grpc_call_credentials_get_request_metadata(exec_ctx, calld->creds,
-                                             calld->pollset, calld->service_url,
-                                             on_credentials_metadata, elem);
+  grpc_call_credentials_get_request_metadata(
+      exec_ctx, calld->creds, calld->pollset, calld->auth_md_context,
+      on_credentials_metadata, elem);
 }
 
 static void on_host_checked(grpc_exec_ctx *exec_ctx, void *user_data,
@@ -280,7 +298,7 @@
   if (calld->method != NULL) {
     GRPC_MDSTR_UNREF(calld->method);
   }
-  reset_service_url(calld);
+  reset_auth_metadata_context(&calld->auth_md_context);
 }
 
 /* Constructor for channel_data */
diff --git a/src/core/security/credentials.c b/src/core/security/credentials.c
index f7a2e73..543c750 100644
--- a/src/core/security/credentials.c
+++ b/src/core/security/credentials.c
@@ -33,16 +33,16 @@
 
 #include "src/core/security/credentials.h"
 
-#include <string.h>
 #include <stdio.h>
+#include <string.h>
 
 #include "src/core/channel/channel_args.h"
 #include "src/core/channel/http_client_filter.h"
-#include "src/core/json/json.h"
 #include "src/core/httpcli/httpcli.h"
 #include "src/core/iomgr/iomgr.h"
-#include "src/core/surface/api_trace.h"
+#include "src/core/json/json.h"
 #include "src/core/support/string.h"
+#include "src/core/surface/api_trace.h"
 
 #include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
@@ -116,19 +116,17 @@
   grpc_call_credentials_unref(creds);
 }
 
-void grpc_call_credentials_get_request_metadata(grpc_exec_ctx *exec_ctx,
-                                                grpc_call_credentials *creds,
-                                                grpc_pollset *pollset,
-                                                const char *service_url,
-                                                grpc_credentials_metadata_cb cb,
-                                                void *user_data) {
+void grpc_call_credentials_get_request_metadata(
+    grpc_exec_ctx *exec_ctx, grpc_call_credentials *creds,
+    grpc_pollset *pollset, grpc_auth_metadata_context context,
+    grpc_credentials_metadata_cb cb, void *user_data) {
   if (creds == NULL || creds->vtable->get_request_metadata == NULL) {
     if (cb != NULL) {
       cb(exec_ctx, user_data, NULL, 0, GRPC_CREDENTIALS_OK);
     }
     return;
   }
-  creds->vtable->get_request_metadata(exec_ctx, creds, pollset, service_url, cb,
+  creds->vtable->get_request_metadata(exec_ctx, creds, pollset, context, cb,
                                       user_data);
 }
 
@@ -429,7 +427,7 @@
 static void jwt_get_request_metadata(grpc_exec_ctx *exec_ctx,
                                      grpc_call_credentials *creds,
                                      grpc_pollset *pollset,
-                                     const char *service_url,
+                                     grpc_auth_metadata_context context,
                                      grpc_credentials_metadata_cb cb,
                                      void *user_data) {
   grpc_service_account_jwt_access_credentials *c =
@@ -442,7 +440,7 @@
   {
     gpr_mu_lock(&c->cache_mu);
     if (c->cached.service_url != NULL &&
-        strcmp(c->cached.service_url, service_url) == 0 &&
+        strcmp(c->cached.service_url, context.service_url) == 0 &&
         c->cached.jwt_md != NULL &&
         (gpr_time_cmp(gpr_time_sub(c->cached.jwt_expiration,
                                    gpr_now(GPR_CLOCK_REALTIME)),
@@ -457,14 +455,15 @@
     /* Generate a new jwt. */
     gpr_mu_lock(&c->cache_mu);
     jwt_reset_cache(c);
-    jwt = grpc_jwt_encode_and_sign(&c->key, service_url, c->jwt_lifetime, NULL);
+    jwt = grpc_jwt_encode_and_sign(&c->key, context.service_url,
+                                   c->jwt_lifetime, NULL);
     if (jwt != NULL) {
       char *md_value;
       gpr_asprintf(&md_value, "Bearer %s", jwt);
       gpr_free(jwt);
       c->cached.jwt_expiration =
           gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), c->jwt_lifetime);
-      c->cached.service_url = gpr_strdup(service_url);
+      c->cached.service_url = gpr_strdup(context.service_url);
       c->cached.jwt_md = grpc_credentials_md_store_create(1);
       grpc_credentials_md_store_add_cstrings(
           c->cached.jwt_md, GRPC_AUTHORIZATION_METADATA_KEY, md_value);
@@ -649,7 +648,7 @@
 
 static void oauth2_token_fetcher_get_request_metadata(
     grpc_exec_ctx *exec_ctx, grpc_call_credentials *creds,
-    grpc_pollset *pollset, const char *service_url,
+    grpc_pollset *pollset, grpc_auth_metadata_context context,
     grpc_credentials_metadata_cb cb, void *user_data) {
   grpc_oauth2_token_fetcher_credentials *c =
       (grpc_oauth2_token_fetcher_credentials *)creds;
@@ -804,12 +803,10 @@
   grpc_exec_ctx_finish(&exec_ctx);
 }
 
-static void md_only_test_get_request_metadata(grpc_exec_ctx *exec_ctx,
-                                              grpc_call_credentials *creds,
-                                              grpc_pollset *pollset,
-                                              const char *service_url,
-                                              grpc_credentials_metadata_cb cb,
-                                              void *user_data) {
+static void md_only_test_get_request_metadata(
+    grpc_exec_ctx *exec_ctx, grpc_call_credentials *creds,
+    grpc_pollset *pollset, grpc_auth_metadata_context context,
+    grpc_credentials_metadata_cb cb, void *user_data) {
   grpc_md_only_test_credentials *c = (grpc_md_only_test_credentials *)creds;
 
   if (c->is_async) {
@@ -846,12 +843,10 @@
   grpc_credentials_md_store_unref(c->access_token_md);
 }
 
-static void access_token_get_request_metadata(grpc_exec_ctx *exec_ctx,
-                                              grpc_call_credentials *creds,
-                                              grpc_pollset *pollset,
-                                              const char *service_url,
-                                              grpc_credentials_metadata_cb cb,
-                                              void *user_data) {
+static void access_token_get_request_metadata(
+    grpc_exec_ctx *exec_ctx, grpc_call_credentials *creds,
+    grpc_pollset *pollset, grpc_auth_metadata_context context,
+    grpc_credentials_metadata_cb cb, void *user_data) {
   grpc_access_token_credentials *c = (grpc_access_token_credentials *)creds;
   cb(exec_ctx, user_data, c->access_token_md->entries, 1, GRPC_CREDENTIALS_OK);
 }
@@ -932,7 +927,7 @@
   grpc_composite_call_credentials *composite_creds;
   size_t creds_index;
   grpc_credentials_md_store *md_elems;
-  char *service_url;
+  grpc_auth_metadata_context auth_md_context;
   void *user_data;
   grpc_pollset *pollset;
   grpc_credentials_metadata_cb cb;
@@ -950,7 +945,6 @@
 static void composite_call_md_context_destroy(
     grpc_composite_call_credentials_metadata_context *ctx) {
   grpc_credentials_md_store_unref(ctx->md_elems);
-  if (ctx->service_url != NULL) gpr_free(ctx->service_url);
   gpr_free(ctx);
 }
 
@@ -978,9 +972,9 @@
   if (ctx->creds_index < ctx->composite_creds->inner.num_creds) {
     grpc_call_credentials *inner_creds =
         ctx->composite_creds->inner.creds_array[ctx->creds_index++];
-    grpc_call_credentials_get_request_metadata(exec_ctx, inner_creds,
-                                               ctx->pollset, ctx->service_url,
-                                               composite_call_metadata_cb, ctx);
+    grpc_call_credentials_get_request_metadata(
+        exec_ctx, inner_creds, ctx->pollset, ctx->auth_md_context,
+        composite_call_metadata_cb, ctx);
     return;
   }
 
@@ -990,26 +984,24 @@
   composite_call_md_context_destroy(ctx);
 }
 
-static void composite_call_get_request_metadata(grpc_exec_ctx *exec_ctx,
-                                                grpc_call_credentials *creds,
-                                                grpc_pollset *pollset,
-                                                const char *service_url,
-                                                grpc_credentials_metadata_cb cb,
-                                                void *user_data) {
+static void composite_call_get_request_metadata(
+    grpc_exec_ctx *exec_ctx, grpc_call_credentials *creds,
+    grpc_pollset *pollset, grpc_auth_metadata_context auth_md_context,
+    grpc_credentials_metadata_cb cb, void *user_data) {
   grpc_composite_call_credentials *c = (grpc_composite_call_credentials *)creds;
   grpc_composite_call_credentials_metadata_context *ctx;
 
   ctx = gpr_malloc(sizeof(grpc_composite_call_credentials_metadata_context));
   memset(ctx, 0, sizeof(grpc_composite_call_credentials_metadata_context));
-  ctx->service_url = gpr_strdup(service_url);
+  ctx->auth_md_context = auth_md_context;
   ctx->user_data = user_data;
   ctx->cb = cb;
   ctx->composite_creds = c;
   ctx->pollset = pollset;
   ctx->md_elems = grpc_credentials_md_store_create(c->inner.num_creds);
   grpc_call_credentials_get_request_metadata(
-      exec_ctx, c->inner.creds_array[ctx->creds_index++], pollset, service_url,
-      composite_call_metadata_cb, ctx);
+      exec_ctx, c->inner.creds_array[ctx->creds_index++], pollset,
+      auth_md_context, composite_call_metadata_cb, ctx);
 }
 
 static grpc_call_credentials_vtable composite_call_credentials_vtable = {
@@ -1103,7 +1095,7 @@
 static void iam_get_request_metadata(grpc_exec_ctx *exec_ctx,
                                      grpc_call_credentials *creds,
                                      grpc_pollset *pollset,
-                                     const char *service_url,
+                                     grpc_auth_metadata_context context,
                                      grpc_credentials_metadata_cb cb,
                                      void *user_data) {
   grpc_google_iam_credentials *c = (grpc_google_iam_credentials *)creds;
@@ -1192,7 +1184,7 @@
 static void plugin_get_request_metadata(grpc_exec_ctx *exec_ctx,
                                         grpc_call_credentials *creds,
                                         grpc_pollset *pollset,
-                                        const char *service_url,
+                                        grpc_auth_metadata_context context,
                                         grpc_credentials_metadata_cb cb,
                                         void *user_data) {
   grpc_plugin_credentials *c = (grpc_plugin_credentials *)creds;
@@ -1201,7 +1193,7 @@
     memset(request, 0, sizeof(*request));
     request->user_data = user_data;
     request->cb = cb;
-    c->plugin.get_metadata(c->plugin.state, service_url,
+    c->plugin.get_metadata(c->plugin.state, context,
                            plugin_md_request_metadata_ready, request);
   } else {
     cb(exec_ctx, user_data, NULL, 0, GRPC_CREDENTIALS_OK);
@@ -1218,7 +1210,7 @@
                  (reserved));
   GPR_ASSERT(reserved == NULL);
   memset(c, 0, sizeof(*c));
-  c->base.type = GRPC_CALL_CREDENTIALS_TYPE_METADATA_PLUGIN;
+  c->base.type = plugin.type;
   c->base.vtable = &plugin_vtable;
   gpr_ref_init(&c->base.refcount, 1);
   c->plugin = plugin;
diff --git a/src/core/security/credentials.h b/src/core/security/credentials.h
index 1320609..6d45895 100644
--- a/src/core/security/credentials.h
+++ b/src/core/security/credentials.h
@@ -59,7 +59,6 @@
   "FakeTransportSecurity"
 
 #define GRPC_CALL_CREDENTIALS_TYPE_OAUTH2 "Oauth2"
-#define GRPC_CALL_CREDENTIALS_TYPE_METADATA_PLUGIN "Plugin"
 #define GRPC_CALL_CREDENTIALS_TYPE_JWT "Jwt"
 #define GRPC_CALL_CREDENTIALS_TYPE_IAM "Iam"
 #define GRPC_CALL_CREDENTIALS_TYPE_COMPOSITE "Composite"
@@ -162,7 +161,7 @@
   void (*destruct)(grpc_call_credentials *c);
   void (*get_request_metadata)(grpc_exec_ctx *exec_ctx,
                                grpc_call_credentials *c, grpc_pollset *pollset,
-                               const char *service_url,
+                               grpc_auth_metadata_context context,
                                grpc_credentials_metadata_cb cb,
                                void *user_data);
 } grpc_call_credentials_vtable;
@@ -175,12 +174,10 @@
 
 grpc_call_credentials *grpc_call_credentials_ref(grpc_call_credentials *creds);
 void grpc_call_credentials_unref(grpc_call_credentials *creds);
-void grpc_call_credentials_get_request_metadata(grpc_exec_ctx *exec_ctx,
-                                                grpc_call_credentials *creds,
-                                                grpc_pollset *pollset,
-                                                const char *service_url,
-                                                grpc_credentials_metadata_cb cb,
-                                                void *user_data);
+void grpc_call_credentials_get_request_metadata(
+    grpc_exec_ctx *exec_ctx, grpc_call_credentials *creds,
+    grpc_pollset *pollset, grpc_auth_metadata_context context,
+    grpc_credentials_metadata_cb cb, void *user_data);
 
 typedef struct {
   grpc_call_credentials **creds_array;
diff --git a/src/cpp/client/secure_credentials.cc b/src/cpp/client/secure_credentials.cc
index a1b9a30..6409323 100644
--- a/src/cpp/client/secure_credentials.cc
+++ b/src/cpp/client/secure_credentials.cc
@@ -37,6 +37,7 @@
 #include <grpc++/support/channel_arguments.h>
 #include "src/cpp/client/create_channel_internal.h"
 #include "src/cpp/client/secure_credentials.h"
+#include "src/cpp/common/secure_auth_context.h"
 
 namespace grpc {
 
@@ -160,7 +161,7 @@
 }
 
 void MetadataCredentialsPluginWrapper::GetMetadata(
-    void* wrapper, const char* service_url,
+    void* wrapper, grpc_auth_metadata_context context,
     grpc_credentials_plugin_metadata_cb cb, void* user_data) {
   GPR_ASSERT(wrapper);
   MetadataCredentialsPluginWrapper* w =
@@ -171,18 +172,25 @@
   }
   if (w->plugin_->IsBlocking()) {
     w->thread_pool_->Add(
-        std::bind(&MetadataCredentialsPluginWrapper::InvokePlugin, w,
-                  service_url, cb, user_data));
+        std::bind(&MetadataCredentialsPluginWrapper::InvokePlugin, w, context,
+                  cb, user_data));
   } else {
-    w->InvokePlugin(service_url, cb, user_data);
+    w->InvokePlugin(context, cb, user_data);
   }
 }
 
 void MetadataCredentialsPluginWrapper::InvokePlugin(
-    const char* service_url, grpc_credentials_plugin_metadata_cb cb,
+    grpc_auth_metadata_context context, grpc_credentials_plugin_metadata_cb cb,
     void* user_data) {
   std::multimap<grpc::string, grpc::string> metadata;
-  Status status = plugin_->GetMetadata(service_url, &metadata);
+
+  // const_cast is safe since the SecureAuthContext does not take owndership and
+  // the object is passed as a const ref to plugin_->GetMetadata.
+  SecureAuthContext cpp_channel_auth_context(
+      const_cast<grpc_auth_context*>(context.channel_auth_context), false);
+
+  Status status = plugin_->GetMetadata(context.service_url, context.method_name,
+                                       cpp_channel_auth_context, &metadata);
   std::vector<grpc_metadata> md;
   for (auto it = metadata.begin(); it != metadata.end(); ++it) {
     grpc_metadata md_entry;
@@ -204,11 +212,12 @@
 std::shared_ptr<CallCredentials> MetadataCredentialsFromPlugin(
     std::unique_ptr<MetadataCredentialsPlugin> plugin) {
   GrpcLibrary init;  // To call grpc_init().
+  const char* type = plugin->GetType();
   MetadataCredentialsPluginWrapper* wrapper =
       new MetadataCredentialsPluginWrapper(std::move(plugin));
   grpc_metadata_credentials_plugin c_plugin = {
       MetadataCredentialsPluginWrapper::GetMetadata,
-      MetadataCredentialsPluginWrapper::Destroy, wrapper};
+      MetadataCredentialsPluginWrapper::Destroy, wrapper, type};
   return WrapCallCredentials(
       grpc_metadata_credentials_create_from_plugin(c_plugin, nullptr));
 }
diff --git a/src/cpp/client/secure_credentials.h b/src/cpp/client/secure_credentials.h
index e9afa9e..cef5929 100644
--- a/src/cpp/client/secure_credentials.h
+++ b/src/cpp/client/secure_credentials.h
@@ -79,7 +79,7 @@
 class MetadataCredentialsPluginWrapper GRPC_FINAL {
  public:
   static void Destroy(void* wrapper);
-  static void GetMetadata(void* wrapper, const char* service_url,
+  static void GetMetadata(void* wrapper, grpc_auth_metadata_context context,
                           grpc_credentials_plugin_metadata_cb cb,
                           void* user_data);
 
@@ -87,7 +87,7 @@
       std::unique_ptr<MetadataCredentialsPlugin> plugin);
 
  private:
-  void InvokePlugin(const char* service_url,
+  void InvokePlugin(grpc_auth_metadata_context context,
                     grpc_credentials_plugin_metadata_cb cb, void* user_data);
   std::unique_ptr<ThreadPoolInterface> thread_pool_;
   std::unique_ptr<MetadataCredentialsPlugin> plugin_;
diff --git a/src/csharp/Grpc.Core/VersionInfo.cs b/src/csharp/Grpc.Core/VersionInfo.cs
index e9ec30e..818ddb8 100644
--- a/src/csharp/Grpc.Core/VersionInfo.cs
+++ b/src/csharp/Grpc.Core/VersionInfo.cs
@@ -41,6 +41,6 @@
         /// <summary>
         /// Current version of gRPC C#
         /// </summary>
-        public const string CurrentVersion = "0.7.1";
+        public const string CurrentVersion = "0.12.0";
     }
 }
diff --git a/src/csharp/build_packages.bat b/src/csharp/build_packages.bat
index b864a95..45f6b26 100644
--- a/src/csharp/build_packages.bat
+++ b/src/csharp/build_packages.bat
@@ -1,8 +1,8 @@
 @rem Builds gRPC NuGet packages
 
 @rem Current package versions
-set VERSION=0.7.1
-set CORE_VERSION=0.11.1
+set VERSION=0.12.0
+set CORE_VERSION=0.12.0
 set PROTOBUF_VERSION=3.0.0-alpha4
 
 @rem Packages that depend on prerelease packages (like Google.Protobuf) need to have prerelease suffix as well.
diff --git a/src/csharp/ext/grpc_csharp_ext.c b/src/csharp/ext/grpc_csharp_ext.c
index 1839319..b8705c4 100644
--- a/src/csharp/ext/grpc_csharp_ext.c
+++ b/src/csharp/ext/grpc_csharp_ext.c
@@ -930,11 +930,12 @@
   void *state, const char *service_url, grpc_credentials_plugin_metadata_cb cb,
   void *user_data, gpr_int32 is_destroy);
 
-static void grpcsharp_get_metadata_handler(void *state, const char *service_url,
-  grpc_credentials_plugin_metadata_cb cb, void *user_data) {
+static void grpcsharp_get_metadata_handler(
+    void *state, grpc_auth_metadata_context context,
+    grpc_credentials_plugin_metadata_cb cb, void *user_data) {
   grpcsharp_metadata_interceptor_func interceptor =
       (grpcsharp_metadata_interceptor_func)(gpr_intptr)state;
-  interceptor(state, service_url, cb, user_data, 0);
+  interceptor(state, context.service_url, cb, user_data, 0);
 }
 
 static void grpcsharp_metadata_credentials_destroy_handler(void *state) {
@@ -949,6 +950,7 @@
   plugin.get_metadata = grpcsharp_get_metadata_handler;
   plugin.destroy = grpcsharp_metadata_credentials_destroy_handler;
   plugin.state = (void*)(gpr_intptr)metadata_interceptor;
+  plugin.type = "";
   return grpc_metadata_credentials_create_from_plugin(plugin, NULL);
 }
 
diff --git a/src/node/ext/byte_buffer.cc b/src/node/ext/byte_buffer.cc
index e1786dd..c306292 100644
--- a/src/node/ext/byte_buffer.cc
+++ b/src/node/ext/byte_buffer.cc
@@ -77,6 +77,7 @@
   while (grpc_byte_buffer_reader_next(&reader, &next) != 0) {
     memcpy(result + offset, GPR_SLICE_START_PTR(next), GPR_SLICE_LENGTH(next));
     offset += GPR_SLICE_LENGTH(next);
+    gpr_slice_unref(next);
   }
   return scope.Escape(MakeFastBuffer(
       Nan::NewBuffer(result, length).ToLocalChecked()));
diff --git a/src/node/ext/call.cc b/src/node/ext/call.cc
index a98ae85..c0e2b0f 100644
--- a/src/node/ext/call.cc
+++ b/src/node/ext/call.cc
@@ -234,6 +234,14 @@
 
 class SendMessageOp : public Op {
  public:
+  SendMessageOp() {
+    send_message = NULL;
+  }
+  ~SendMessageOp() {
+    if (send_message != NULL) {
+      grpc_byte_buffer_destroy(send_message);
+    }
+  }
   Local<Value> GetNodeValue() const {
     EscapableHandleScope scope;
     return scope.Escape(Nan::True());
@@ -253,7 +261,8 @@
         out->flags = maybe_flag.FromMaybe(0) & GRPC_WRITE_USED_MASK;
       }
     }
-    out->data.send_message = BufferToByteBuffer(value);
+    send_message = BufferToByteBuffer(value);
+    out->data.send_message = send_message;
     PersistentValue *handle = new PersistentValue(value);
     resources->handles.push_back(unique_ptr<PersistentValue>(handle));
     return true;
@@ -262,6 +271,8 @@
   std::string GetTypeString() const {
     return "send_message";
   }
+ private:
+  grpc_byte_buffer *send_message;
 };
 
 class SendClientCloseOp : public Op {
diff --git a/src/node/ext/call_credentials.cc b/src/node/ext/call_credentials.cc
index 9c5d9d2..8cbfb1e 100644
--- a/src/node/ext/call_credentials.cc
+++ b/src/node/ext/call_credentials.cc
@@ -162,6 +162,7 @@
   plugin.get_metadata = plugin_get_metadata;
   plugin.destroy = plugin_destroy_state;
   plugin.state = reinterpret_cast<void*>(state);
+  plugin.type = "";
   grpc_call_credentials *creds = grpc_metadata_credentials_create_from_plugin(
       plugin, NULL);
   info.GetReturnValue().Set(WrapStruct(creds));
@@ -225,7 +226,7 @@
   uv_close((uv_handle_t *)async, (uv_close_cb)free);
 }
 
-void plugin_get_metadata(void *state, const char *service_url,
+void plugin_get_metadata(void *state, grpc_auth_metadata_context context,
                          grpc_credentials_plugin_metadata_cb cb,
                          void *user_data) {
   uv_async_t *async = static_cast<uv_async_t*>(malloc(sizeof(uv_async_t)));
@@ -234,7 +235,7 @@
                 SendPluginCallback);
   plugin_callback_data *data = new plugin_callback_data;
   data->state = reinterpret_cast<plugin_state*>(state);
-  data->service_url = service_url;
+  data->service_url = context.service_url;
   data->cb = cb;
   data->user_data = user_data;
   async->data = data;
diff --git a/src/node/ext/call_credentials.h b/src/node/ext/call_credentials.h
index 1cd8d8d..a9bfe30 100644
--- a/src/node/ext/call_credentials.h
+++ b/src/node/ext/call_credentials.h
@@ -84,7 +84,7 @@
   void *user_data;
 } plugin_callback_data;
 
-void plugin_get_metadata(void *state, const char *service_url,
+void plugin_get_metadata(void *state, grpc_auth_metadata_context context,
                          grpc_credentials_plugin_metadata_cb cb,
                          void *user_data);
 
diff --git a/src/objective-c/README.md b/src/objective-c/README.md
index c1d25b9..30d9aad 100644
--- a/src/objective-c/README.md
+++ b/src/objective-c/README.md
@@ -48,8 +48,8 @@
   s.version  = '0.0.1'
   s.license  = '...'
 
-  s.ios.deployment_target = '6.0'
-  s.osx.deployment_target = '10.8'
+  s.ios.deployment_target = '7.1'
+  s.osx.deployment_target = '10.9'
 
   # Run protoc with the Objective-C and gRPC plugins to generate protocol messages and gRPC clients.
   # You can run this command manually if you later change your protos and need to regenerate.
@@ -60,7 +60,7 @@
     ms.source_files = "*.pbobjc.{h,m}"
     ms.header_mappings_dir = "."
     ms.requires_arc = false
-    ms.dependency "Protobuf", "~> 3.0.0-alpha-3"
+    ms.dependency "Protobuf", "~> 3.0.0-alpha-4"
   end
 
   # The --objcgrpc_out plugin generates a pair of .pbrpc.h/.pbrpc.m files for each .proto file with
@@ -69,7 +69,7 @@
     ss.source_files = "*.pbrpc.{h,m}"
     ss.header_mappings_dir = "."
     ss.requires_arc = true
-    ss.dependency "gRPC", "~> 0.5"
+    ss.dependency "gRPC", "~> 0.12"
     ss.dependency "#{s.name}/Messages"
   end
 end
@@ -156,7 +156,7 @@
 
 You need to compile the generated `.pbobjc.*` files (the enums and messages) without ARC support,
 and the generated `.pbrpc.*` files (the services) with ARC support. The generated code depends on
-v0.5+ of the Objective-C gRPC runtime library and v3.0.0-alpha-3+ of the Objective-C Protobuf
+v0.12+ of the Objective-C gRPC runtime library and v3.0.0-alpha-4+ of the Objective-C Protobuf
 runtime library.
 
 These libraries need to be integrated into your project as described in their respective Podspec
diff --git a/src/objective-c/examples/RemoteTestClient/RemoteTest.podspec b/src/objective-c/examples/RemoteTestClient/RemoteTest.podspec
index d4f8084..5addf26f 100644
--- a/src/objective-c/examples/RemoteTestClient/RemoteTest.podspec
+++ b/src/objective-c/examples/RemoteTestClient/RemoteTest.podspec
@@ -3,8 +3,8 @@
   s.version  = "0.0.1"
   s.license  = "New BSD"
 
-  s.ios.deployment_target = "6.0"
-  s.osx.deployment_target = "10.8"
+  s.ios.deployment_target = '7.1'
+  s.osx.deployment_target = '10.9'
 
   # Run protoc with the Objective-C and gRPC plugins to generate protocol messages and gRPC clients.
   s.prepare_command = <<-CMD
@@ -22,7 +22,7 @@
     ss.source_files = "*.pbrpc.{h,m}"
     ss.header_mappings_dir = "."
     ss.requires_arc = true
-    ss.dependency "gRPC", "~> 0.7"
+    ss.dependency "gRPC", "~> 0.12"
     ss.dependency "#{s.name}/Messages"
   end
 end
diff --git a/src/objective-c/tests/RemoteTestClient/RemoteTest.podspec b/src/objective-c/tests/RemoteTestClient/RemoteTest.podspec
index 8710753..6ecef05 100644
--- a/src/objective-c/tests/RemoteTestClient/RemoteTest.podspec
+++ b/src/objective-c/tests/RemoteTestClient/RemoteTest.podspec
@@ -3,8 +3,8 @@
   s.version  = "0.0.1"
   s.license  = "New BSD"
 
-  s.ios.deployment_target = "6.0"
-  s.osx.deployment_target = "10.8"
+  s.ios.deployment_target = '7.1'
+  s.osx.deployment_target = '10.9'
 
   # Run protoc with the Objective-C and gRPC plugins to generate protocol messages and gRPC clients.
   s.prepare_command = <<-CMD
@@ -18,14 +18,14 @@
     ms.source_files = "*.pbobjc.{h,m}"
     ms.header_mappings_dir = "."
     ms.requires_arc = false
-    ms.dependency "Protobuf", "~> 3.0.0-alpha-3"
+    ms.dependency "Protobuf", "~> 3.0.0-alpha-4"
   end
 
   s.subspec "Services" do |ss|
     ss.source_files = "*.pbrpc.{h,m}"
     ss.header_mappings_dir = "."
     ss.requires_arc = true
-    ss.dependency "gRPC", "~> 0.5"
+    ss.dependency "gRPC", "~> 0.12"
     ss.dependency "#{s.name}/Messages"
   end
 end
diff --git a/src/objective-c/tests/run_tests.sh b/src/objective-c/tests/run_tests.sh
index 598f4e7..c4fc564 100755
--- a/src/objective-c/tests/run_tests.sh
+++ b/src/objective-c/tests/run_tests.sh
@@ -51,4 +51,5 @@
     -scheme AllTests \
     -destination name="iPhone 6" \
     test \
-    | egrep "$XCODEBUILD_FILTER" -
+    | egrep "$XCODEBUILD_FILTER" \
+    | egrep -v "(GPBDictionary|GPBArray)" -
diff --git a/src/ruby/ext/grpc/extconf.rb b/src/ruby/ext/grpc/extconf.rb
index 7972272..db9385e 100644
--- a/src/ruby/ext/grpc/extconf.rb
+++ b/src/ruby/ext/grpc/extconf.rb
@@ -94,6 +94,10 @@
     end
     $CFLAGS << ' -I' + File.join(grpc_root, 'include')
     $LDFLAGS << ' -L' + grpc_lib_dir
+    if grpc_config == 'gcov'
+      $CFLAGS << ' -O0 -fprofile-arcs -ftest-coverage'
+      $LDFLAGS << ' -fprofile-arcs -ftest-coverage -rdynamic'
+    end
     raise 'gpr not found' unless have_library('gpr', 'gpr_now')
     raise 'grpc not found' unless have_library('grpc', 'grpc_channel_destroy')
   end
diff --git a/templates/gRPC.podspec.template b/templates/gRPC.podspec.template
index 3885cb3..de5ed7c 100644
--- a/templates/gRPC.podspec.template
+++ b/templates/gRPC.podspec.template
@@ -54,7 +54,7 @@
   %>
   Pod::Spec.new do |s|
     s.name     = 'gRPC'
-    version = '0.11.2'
+    version = '0.12.0'
     s.version  = version
     s.summary  = 'gRPC client library for iOS/OSX'
     s.homepage = 'http://www.grpc.io'
@@ -97,7 +97,7 @@
 
       ss.requires_arc = false
       ss.libraries = 'z'
-      ss.dependency 'OpenSSL', '~> 1.0.200'
+      ss.dependency 'OpenSSL', '~> 1.0.204.1'
 
       # ss.compiler_flags = '-GCC_WARN_INHIBIT_ALL_WARNINGS', '-w'
     end
diff --git a/test/core/client_config/lb_policies_test.c b/test/core/client_config/lb_policies_test.c
index 5aa8140..6f218e7 100644
--- a/test/core/client_config/lb_policies_test.c
+++ b/test/core/client_config/lb_policies_test.c
@@ -135,9 +135,8 @@
   gpr_log(GPR_INFO, "KILLING SERVER %d", i);
   GPR_ASSERT(f->servers[i] != NULL);
   grpc_server_shutdown_and_notify(f->servers[i], f->cq, tag(10000));
-  GPR_ASSERT(
-      grpc_completion_queue_pluck(f->cq, tag(10000), n_millis_time(5000), NULL)
-          .type == GRPC_OP_COMPLETE);
+  GPR_ASSERT(grpc_completion_queue_pluck(f->cq, tag(10000), n_millis_time(5000),
+                                         NULL).type == GRPC_OP_COMPLETE);
   grpc_server_destroy(f->servers[i]);
   f->servers[i] = NULL;
 }
@@ -203,8 +202,8 @@
     if (f->servers[i] == NULL) continue;
     grpc_server_shutdown_and_notify(f->servers[i], f->cq, tag(10000));
     GPR_ASSERT(grpc_completion_queue_pluck(f->cq, tag(10000),
-                                           n_millis_time(5000), NULL)
-                   .type == GRPC_OP_COMPLETE);
+                                           n_millis_time(5000),
+                                           NULL).type == GRPC_OP_COMPLETE);
     grpc_server_destroy(f->servers[i]);
   }
   grpc_completion_queue_shutdown(f->cq);
@@ -304,8 +303,8 @@
 
     s_idx = -1;
     while ((ev = grpc_completion_queue_next(
-                f->cq, GRPC_TIMEOUT_SECONDS_TO_DEADLINE(1), NULL))
-               .type != GRPC_QUEUE_TIMEOUT) {
+                f->cq, GRPC_TIMEOUT_SECONDS_TO_DEADLINE(1), NULL)).type !=
+           GRPC_QUEUE_TIMEOUT) {
       GPR_ASSERT(ev.type == GRPC_OP_COMPLETE);
       read_tag = ((int)(gpr_intptr)ev.tag);
       gpr_log(GPR_DEBUG, "EVENT: success:%d, type:%d, tag:%d iter:%d",
diff --git a/test/core/client_config/set_initial_connect_string_test.c b/test/core/client_config/set_initial_connect_string_test.c
index 13517b7..ceca56c 100644
--- a/test/core/client_config/set_initial_connect_string_test.c
+++ b/test/core/client_config/set_initial_connect_string_test.c
@@ -66,15 +66,15 @@
 
 static void handle_read(grpc_exec_ctx *exec_ctx, void *arg, int success) {
   GPR_ASSERT(success);
-  gpr_slice_buffer_move_into(
-    &state.temp_incoming_buffer, &state.incoming_buffer);
+  gpr_slice_buffer_move_into(&state.temp_incoming_buffer,
+                             &state.incoming_buffer);
   if (state.incoming_buffer.length > strlen(magic_connect_string)) {
     state.done = 1;
     grpc_endpoint_shutdown(exec_ctx, state.tcp);
     grpc_endpoint_destroy(exec_ctx, state.tcp);
   } else {
-    grpc_endpoint_read(
-      exec_ctx, state.tcp, &state.temp_incoming_buffer, &on_read);
+    grpc_endpoint_read(exec_ctx, state.tcp, &state.temp_incoming_buffer,
+                       &on_read);
   }
 }
 
diff --git a/test/core/end2end/tests/hpack_size.c b/test/core/end2end/tests/hpack_size.c
index 297ea8d..997969d 100644
--- a/test/core/end2end/tests/hpack_size.c
+++ b/test/core/end2end/tests/hpack_size.c
@@ -262,9 +262,9 @@
 static void shutdown_server(grpc_end2end_test_fixture *f) {
   if (!f->server) return;
   grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  GPR_ASSERT(grpc_completion_queue_pluck(
-                 f->cq, tag(1000), GRPC_TIMEOUT_SECONDS_TO_DEADLINE(5), NULL)
-                 .type == GRPC_OP_COMPLETE);
+  GPR_ASSERT(grpc_completion_queue_pluck(f->cq, tag(1000),
+                                         GRPC_TIMEOUT_SECONDS_TO_DEADLINE(5),
+                                         NULL).type == GRPC_OP_COMPLETE);
   grpc_server_destroy(f->server);
   f->server = NULL;
 }
diff --git a/test/core/iomgr/fd_posix_test.c b/test/core/iomgr/fd_posix_test.c
index f592f63..4be6957 100644
--- a/test/core/iomgr/fd_posix_test.c
+++ b/test/core/iomgr/fd_posix_test.c
@@ -121,7 +121,7 @@
                                 int success) {
   session *se = arg;
   server *sv = se->sv;
-  grpc_fd_orphan(exec_ctx, se->em_fd, NULL, "a");
+  grpc_fd_orphan(exec_ctx, se->em_fd, NULL, NULL, "a");
   gpr_free(se);
   /* Start to shutdown listen fd. */
   grpc_fd_shutdown(exec_ctx, sv->em_fd);
@@ -177,7 +177,7 @@
                                int success) {
   server *sv = arg;
 
-  grpc_fd_orphan(exec_ctx, sv->em_fd, NULL, "b");
+  grpc_fd_orphan(exec_ctx, sv->em_fd, NULL, NULL, "b");
 
   gpr_mu_lock(GRPC_POLLSET_MU(&g_pollset));
   sv->done = 1;
@@ -294,7 +294,7 @@
 static void client_session_shutdown_cb(grpc_exec_ctx *exec_ctx,
                                        void *arg /*client */, int success) {
   client *cl = arg;
-  grpc_fd_orphan(exec_ctx, cl->em_fd, NULL, "c");
+  grpc_fd_orphan(exec_ctx, cl->em_fd, NULL, NULL, "c");
   cl->done = 1;
   grpc_pollset_kick(&g_pollset, NULL);
 }
@@ -503,7 +503,7 @@
   GPR_ASSERT(b.cb_that_ran == second_read_callback);
   gpr_mu_unlock(GRPC_POLLSET_MU(&g_pollset));
 
-  grpc_fd_orphan(&exec_ctx, em_fd, NULL, "d");
+  grpc_fd_orphan(&exec_ctx, em_fd, NULL, NULL, "d");
   grpc_exec_ctx_finish(&exec_ctx);
   destroy_change_data(&a);
   destroy_change_data(&b);
diff --git a/test/core/iomgr/tcp_posix_test.c b/test/core/iomgr/tcp_posix_test.c
index f676454..9feac93 100644
--- a/test/core/iomgr/tcp_posix_test.c
+++ b/test/core/iomgr/tcp_posix_test.c
@@ -383,6 +383,76 @@
   grpc_exec_ctx_finish(&exec_ctx);
 }
 
+void on_fd_released(grpc_exec_ctx *exec_ctx, void *arg, int success) {
+  int *done = arg;
+  *done = 1;
+  grpc_pollset_kick(&g_pollset, NULL);
+}
+
+/* Do a read_test, then release fd and try to read/write again. */
+static void release_fd_test(size_t num_bytes, size_t slice_size) {
+  int sv[2];
+  grpc_endpoint *ep;
+  struct read_socket_state state;
+  size_t written_bytes;
+  int fd;
+  gpr_timespec deadline = GRPC_TIMEOUT_SECONDS_TO_DEADLINE(20);
+  grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
+  grpc_closure fd_released_cb;
+  int fd_released_done = 0;
+  grpc_closure_init(&fd_released_cb, &on_fd_released, &fd_released_done);
+
+  gpr_log(GPR_INFO, "Release fd read_test of size %d, slice size %d", num_bytes,
+          slice_size);
+
+  create_sockets(sv);
+
+  ep = grpc_tcp_create(grpc_fd_create(sv[1], "read_test"), slice_size, "test");
+  grpc_endpoint_add_to_pollset(&exec_ctx, ep, &g_pollset);
+
+  written_bytes = fill_socket_partial(sv[0], num_bytes);
+  gpr_log(GPR_INFO, "Wrote %d bytes", written_bytes);
+
+  state.ep = ep;
+  state.read_bytes = 0;
+  state.target_read_bytes = written_bytes;
+  gpr_slice_buffer_init(&state.incoming);
+  grpc_closure_init(&state.read_cb, read_cb, &state);
+
+  grpc_endpoint_read(&exec_ctx, ep, &state.incoming, &state.read_cb);
+
+  gpr_mu_lock(GRPC_POLLSET_MU(&g_pollset));
+  while (state.read_bytes < state.target_read_bytes) {
+    grpc_pollset_worker worker;
+    grpc_pollset_work(&exec_ctx, &g_pollset, &worker,
+                      gpr_now(GPR_CLOCK_MONOTONIC), deadline);
+    gpr_mu_unlock(GRPC_POLLSET_MU(&g_pollset));
+    grpc_exec_ctx_finish(&exec_ctx);
+    gpr_mu_lock(GRPC_POLLSET_MU(&g_pollset));
+  }
+  GPR_ASSERT(state.read_bytes == state.target_read_bytes);
+  gpr_mu_unlock(GRPC_POLLSET_MU(&g_pollset));
+
+  gpr_slice_buffer_destroy(&state.incoming);
+  grpc_tcp_destroy_and_release_fd(&exec_ctx, ep, &fd, &fd_released_cb);
+  gpr_mu_lock(GRPC_POLLSET_MU(&g_pollset));
+  while (!fd_released_done) {
+    grpc_pollset_worker worker;
+    grpc_pollset_work(&exec_ctx, &g_pollset, &worker,
+                      gpr_now(GPR_CLOCK_MONOTONIC), deadline);
+  }
+  gpr_mu_unlock(GRPC_POLLSET_MU(&g_pollset));
+  GPR_ASSERT(fd_released_done == 1);
+  GPR_ASSERT(fd == sv[1]);
+  grpc_exec_ctx_finish(&exec_ctx);
+
+  written_bytes = fill_socket_partial(sv[0], num_bytes);
+  drain_socket_blocking(fd, written_bytes, written_bytes);
+  written_bytes = fill_socket_partial(fd, num_bytes);
+  drain_socket_blocking(sv[0], written_bytes, written_bytes);
+  close(fd);
+}
+
 void run_tests(void) {
   size_t i = 0;
 
@@ -402,6 +472,8 @@
   for (i = 1; i < 1000; i = GPR_MAX(i + 1, i * 5 / 4)) {
     write_test(40320, i);
   }
+
+  release_fd_test(100, 8192);
 }
 
 static void clean_up(void) {}
diff --git a/test/core/security/credentials_test.c b/test/core/security/credentials_test.c
index 4cfef7b..b2b7cfd 100644
--- a/test/core/security/credentials_test.c
+++ b/test/core/security/credentials_test.c
@@ -126,6 +126,8 @@
 static const char test_service_url[] = "https://foo.com/foo.v1";
 static const char other_test_service_url[] = "https://bar.com/bar.v1";
 
+static const char test_method[] = "ThisIsNotAMethod";
+
 /* -- Utils. -- */
 
 static char *test_json_key_str(void) {
@@ -352,9 +354,10 @@
   grpc_call_credentials *creds = grpc_google_iam_credentials_create(
       test_google_iam_authorization_token, test_google_iam_authority_selector,
       NULL);
-  grpc_call_credentials_get_request_metadata(&exec_ctx, creds, NULL,
-                                             test_service_url,
-                                             check_google_iam_metadata, creds);
+  grpc_auth_metadata_context auth_md_ctx = {test_service_url, test_method, NULL,
+                                            NULL};
+  grpc_call_credentials_get_request_metadata(
+      &exec_ctx, creds, NULL, auth_md_ctx, check_google_iam_metadata, creds);
   grpc_exec_ctx_finish(&exec_ctx);
 }
 
@@ -375,10 +378,11 @@
   grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
   grpc_call_credentials *creds =
       grpc_access_token_credentials_create("blah", NULL);
+  grpc_auth_metadata_context auth_md_ctx = {test_service_url, test_method, NULL,
+                                            NULL};
   GPR_ASSERT(strcmp(creds->type, GRPC_CALL_CREDENTIALS_TYPE_OAUTH2) == 0);
   grpc_call_credentials_get_request_metadata(
-      &exec_ctx, creds, NULL, test_service_url, check_access_token_metadata,
-      creds);
+      &exec_ctx, creds, NULL, auth_md_ctx, check_access_token_metadata, creds);
   grpc_exec_ctx_finish(&exec_ctx);
 }
 
@@ -430,6 +434,8 @@
 static void test_oauth2_google_iam_composite_creds(void) {
   grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
   const grpc_call_credentials_array *creds_array;
+  grpc_auth_metadata_context auth_md_ctx = {test_service_url, test_method, NULL,
+                                            NULL};
   grpc_call_credentials *oauth2_creds = grpc_md_only_test_credentials_create(
       "authorization", test_oauth2_bearer_token, 0);
   grpc_call_credentials *google_iam_creds = grpc_google_iam_credentials_create(
@@ -450,7 +456,7 @@
   GPR_ASSERT(strcmp(creds_array->creds_array[1]->type,
                     GRPC_CALL_CREDENTIALS_TYPE_IAM) == 0);
   grpc_call_credentials_get_request_metadata(
-      &exec_ctx, composite_creds, NULL, test_service_url,
+      &exec_ctx, composite_creds, NULL, auth_md_ctx,
       check_oauth2_google_iam_composite_metadata, composite_creds);
   grpc_exec_ctx_finish(&exec_ctx);
 }
@@ -578,12 +584,14 @@
   grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
   grpc_call_credentials *compute_engine_creds =
       grpc_google_compute_engine_credentials_create(NULL);
+  grpc_auth_metadata_context auth_md_ctx = {test_service_url, test_method, NULL,
+                                            NULL};
 
   /* First request: http get should be called. */
   grpc_httpcli_set_override(compute_engine_httpcli_get_success_override,
                             httpcli_post_should_not_be_called);
   grpc_call_credentials_get_request_metadata(
-      &exec_ctx, compute_engine_creds, NULL, test_service_url,
+      &exec_ctx, compute_engine_creds, NULL, auth_md_ctx,
       on_oauth2_creds_get_metadata_success, (void *)test_user_data);
   grpc_exec_ctx_flush(&exec_ctx);
 
@@ -591,7 +599,7 @@
   grpc_httpcli_set_override(httpcli_get_should_not_be_called,
                             httpcli_post_should_not_be_called);
   grpc_call_credentials_get_request_metadata(
-      &exec_ctx, compute_engine_creds, NULL, test_service_url,
+      &exec_ctx, compute_engine_creds, NULL, auth_md_ctx,
       on_oauth2_creds_get_metadata_success, (void *)test_user_data);
   grpc_exec_ctx_finish(&exec_ctx);
 
@@ -601,12 +609,14 @@
 
 static void test_compute_engine_creds_failure(void) {
   grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
+  grpc_auth_metadata_context auth_md_ctx = {test_service_url, test_method, NULL,
+                                            NULL};
   grpc_call_credentials *compute_engine_creds =
       grpc_google_compute_engine_credentials_create(NULL);
   grpc_httpcli_set_override(compute_engine_httpcli_get_failure_override,
                             httpcli_post_should_not_be_called);
   grpc_call_credentials_get_request_metadata(
-      &exec_ctx, compute_engine_creds, NULL, test_service_url,
+      &exec_ctx, compute_engine_creds, NULL, auth_md_ctx,
       on_oauth2_creds_get_metadata_failure, (void *)test_user_data);
   grpc_call_credentials_unref(compute_engine_creds);
   grpc_httpcli_set_override(NULL, NULL);
@@ -658,6 +668,8 @@
 
 static void test_refresh_token_creds_success(void) {
   grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
+  grpc_auth_metadata_context auth_md_ctx = {test_service_url, test_method, NULL,
+                                            NULL};
   grpc_call_credentials *refresh_token_creds =
       grpc_google_refresh_token_credentials_create(test_refresh_token_str,
                                                    NULL);
@@ -666,7 +678,7 @@
   grpc_httpcli_set_override(httpcli_get_should_not_be_called,
                             refresh_token_httpcli_post_success);
   grpc_call_credentials_get_request_metadata(
-      &exec_ctx, refresh_token_creds, NULL, test_service_url,
+      &exec_ctx, refresh_token_creds, NULL, auth_md_ctx,
       on_oauth2_creds_get_metadata_success, (void *)test_user_data);
   grpc_exec_ctx_flush(&exec_ctx);
 
@@ -674,7 +686,7 @@
   grpc_httpcli_set_override(httpcli_get_should_not_be_called,
                             httpcli_post_should_not_be_called);
   grpc_call_credentials_get_request_metadata(
-      &exec_ctx, refresh_token_creds, NULL, test_service_url,
+      &exec_ctx, refresh_token_creds, NULL, auth_md_ctx,
       on_oauth2_creds_get_metadata_success, (void *)test_user_data);
   grpc_exec_ctx_flush(&exec_ctx);
 
@@ -685,13 +697,15 @@
 
 static void test_refresh_token_creds_failure(void) {
   grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
+  grpc_auth_metadata_context auth_md_ctx = {test_service_url, test_method, NULL,
+                                            NULL};
   grpc_call_credentials *refresh_token_creds =
       grpc_google_refresh_token_credentials_create(test_refresh_token_str,
                                                    NULL);
   grpc_httpcli_set_override(httpcli_get_should_not_be_called,
                             refresh_token_httpcli_post_failure);
   grpc_call_credentials_get_request_metadata(
-      &exec_ctx, refresh_token_creds, NULL, test_service_url,
+      &exec_ctx, refresh_token_creds, NULL, auth_md_ctx,
       on_oauth2_creds_get_metadata_failure, (void *)test_user_data);
   grpc_call_credentials_unref(refresh_token_creds);
   grpc_httpcli_set_override(NULL, NULL);
@@ -774,6 +788,8 @@
 static void test_jwt_creds_success(void) {
   char *json_key_string = test_json_key_str();
   grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
+  grpc_auth_metadata_context auth_md_ctx = {test_service_url, test_method, NULL,
+                                            NULL};
   grpc_call_credentials *jwt_creds =
       grpc_service_account_jwt_access_credentials_create(
           json_key_string, grpc_max_auth_token_lifetime, NULL);
@@ -781,7 +797,7 @@
   /* First request: jwt_encode_and_sign should be called. */
   grpc_jwt_encode_and_sign_set_override(encode_and_sign_jwt_success);
   grpc_call_credentials_get_request_metadata(
-      &exec_ctx, jwt_creds, NULL, test_service_url,
+      &exec_ctx, jwt_creds, NULL, auth_md_ctx,
       on_jwt_creds_get_metadata_success, (void *)test_user_data);
   grpc_exec_ctx_flush(&exec_ctx);
 
@@ -789,15 +805,16 @@
   grpc_jwt_encode_and_sign_set_override(
       encode_and_sign_jwt_should_not_be_called);
   grpc_call_credentials_get_request_metadata(
-      &exec_ctx, jwt_creds, NULL, test_service_url,
+      &exec_ctx, jwt_creds, NULL, auth_md_ctx,
       on_jwt_creds_get_metadata_success, (void *)test_user_data);
   grpc_exec_ctx_flush(&exec_ctx);
 
   /* Third request: Different service url so jwt_encode_and_sign should be
      called again (no caching). */
+  auth_md_ctx.service_url = other_test_service_url;
   grpc_jwt_encode_and_sign_set_override(encode_and_sign_jwt_success);
   grpc_call_credentials_get_request_metadata(
-      &exec_ctx, jwt_creds, NULL, other_test_service_url,
+      &exec_ctx, jwt_creds, NULL, auth_md_ctx,
       on_jwt_creds_get_metadata_success, (void *)test_user_data);
   grpc_exec_ctx_flush(&exec_ctx);
 
@@ -809,13 +826,15 @@
 static void test_jwt_creds_signing_failure(void) {
   char *json_key_string = test_json_key_str();
   grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
+  grpc_auth_metadata_context auth_md_ctx = {test_service_url, test_method, NULL,
+                                            NULL};
   grpc_call_credentials *jwt_creds =
       grpc_service_account_jwt_access_credentials_create(
           json_key_string, grpc_max_auth_token_lifetime, NULL);
 
   grpc_jwt_encode_and_sign_set_override(encode_and_sign_jwt_failure);
   grpc_call_credentials_get_request_metadata(
-      &exec_ctx, jwt_creds, NULL, test_service_url,
+      &exec_ctx, jwt_creds, NULL, auth_md_ctx,
       on_jwt_creds_get_metadata_failure, (void *)test_user_data);
 
   gpr_free(json_key_string);
@@ -886,13 +905,17 @@
 
 static const plugin_metadata plugin_md[] = {{"foo", "bar"}, {"hi", "there"}};
 
-static void plugin_get_metadata_success(void *state, const char *service_url,
+static void plugin_get_metadata_success(void *state,
+                                        grpc_auth_metadata_context context,
                                         grpc_credentials_plugin_metadata_cb cb,
                                         void *user_data) {
   size_t i;
   grpc_metadata md[GPR_ARRAY_SIZE(plugin_md)];
   plugin_state *s = (plugin_state *)state;
-  GPR_ASSERT(strcmp(service_url, test_service_url) == 0);
+  GPR_ASSERT(strcmp(context.service_url, test_service_url) == 0);
+  GPR_ASSERT(strcmp(context.method_name, test_method) == 0);
+  GPR_ASSERT(context.channel_auth_context == NULL);
+  GPR_ASSERT(context.reserved == NULL);
   *s = PLUGIN_GET_METADATA_CALLED_STATE;
   for (i = 0; i < GPR_ARRAY_SIZE(plugin_md); i++) {
     memset(&md[i], 0, sizeof(grpc_metadata));
@@ -903,11 +926,15 @@
   cb(user_data, md, GPR_ARRAY_SIZE(md), GRPC_STATUS_OK, NULL);
 }
 
-static void plugin_get_metadata_failure(void *state, const char *service_url,
+static void plugin_get_metadata_failure(void *state,
+                                        grpc_auth_metadata_context context,
                                         grpc_credentials_plugin_metadata_cb cb,
                                         void *user_data) {
   plugin_state *s = (plugin_state *)state;
-  GPR_ASSERT(strcmp(service_url, test_service_url) == 0);
+  GPR_ASSERT(strcmp(context.service_url, test_service_url) == 0);
+  GPR_ASSERT(strcmp(context.method_name, test_method) == 0);
+  GPR_ASSERT(context.channel_auth_context == NULL);
+  GPR_ASSERT(context.reserved == NULL);
   *s = PLUGIN_GET_METADATA_CALLED_STATE;
   cb(user_data, NULL, 0, GRPC_STATUS_UNAUTHENTICATED,
      "Could not get metadata for plugin.");
@@ -945,6 +972,8 @@
   plugin_state state = PLUGIN_INITIAL_STATE;
   grpc_metadata_credentials_plugin plugin;
   grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
+  grpc_auth_metadata_context auth_md_ctx = {test_service_url, test_method, NULL,
+                                            NULL};
 
   plugin.state = &state;
   plugin.get_metadata = plugin_get_metadata_success;
@@ -953,8 +982,8 @@
   creds = grpc_metadata_credentials_create_from_plugin(plugin, NULL);
   GPR_ASSERT(state == PLUGIN_INITIAL_STATE);
   grpc_call_credentials_get_request_metadata(
-      &exec_ctx, creds, NULL, test_service_url,
-      on_plugin_metadata_received_success, NULL);
+      &exec_ctx, creds, NULL, auth_md_ctx, on_plugin_metadata_received_success,
+      NULL);
   GPR_ASSERT(state == PLUGIN_GET_METADATA_CALLED_STATE);
   grpc_call_credentials_release(creds);
   GPR_ASSERT(state == PLUGIN_DESTROY_CALLED_STATE);
@@ -966,6 +995,8 @@
   plugin_state state = PLUGIN_INITIAL_STATE;
   grpc_metadata_credentials_plugin plugin;
   grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
+  grpc_auth_metadata_context auth_md_ctx = {test_service_url, test_method, NULL,
+                                            NULL};
 
   plugin.state = &state;
   plugin.get_metadata = plugin_get_metadata_failure;
@@ -974,8 +1005,8 @@
   creds = grpc_metadata_credentials_create_from_plugin(plugin, NULL);
   GPR_ASSERT(state == PLUGIN_INITIAL_STATE);
   grpc_call_credentials_get_request_metadata(
-      &exec_ctx, creds, NULL, test_service_url,
-      on_plugin_metadata_received_failure, NULL);
+      &exec_ctx, creds, NULL, auth_md_ctx, on_plugin_metadata_received_failure,
+      NULL);
   GPR_ASSERT(state == PLUGIN_GET_METADATA_CALLED_STATE);
   grpc_call_credentials_release(creds);
   GPR_ASSERT(state == PLUGIN_DESTROY_CALLED_STATE);
diff --git a/test/core/security/oauth2_utils.c b/test/core/security/oauth2_utils.c
index fcfe8a6..fb62bf4 100644
--- a/test/core/security/oauth2_utils.c
+++ b/test/core/security/oauth2_utils.c
@@ -80,13 +80,16 @@
   oauth2_request request;
   grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
   grpc_closure do_nothing_closure;
+  grpc_auth_metadata_context null_ctx = {"", "", NULL, NULL};
+
   grpc_pollset_init(&request.pollset);
   request.is_done = 0;
 
   grpc_closure_init(&do_nothing_closure, do_nothing, NULL);
 
   grpc_call_credentials_get_request_metadata(&exec_ctx, creds, &request.pollset,
-                                             "", on_oauth2_response, &request);
+                                             null_ctx, on_oauth2_response,
+                                             &request);
 
   grpc_exec_ctx_finish(&exec_ctx);
 
diff --git a/test/core/security/print_google_default_creds_token.c b/test/core/security/print_google_default_creds_token.c
index eb63724..50fe61c 100644
--- a/test/core/security/print_google_default_creds_token.c
+++ b/test/core/security/print_google_default_creds_token.c
@@ -74,10 +74,13 @@
   synchronizer sync;
   grpc_channel_credentials *creds = NULL;
   char *service_url = "https://test.foo.google.com/Foo";
+  grpc_auth_metadata_context context;
   gpr_cmdline *cl = gpr_cmdline_create("print_google_default_creds_token");
   gpr_cmdline_add_string(cl, "service_url",
                          "Service URL for the token request.", &service_url);
   gpr_cmdline_parse(cl, argc, argv);
+  memset(&context, 0, sizeof(context));
+  context.service_url = service_url;
 
   grpc_init();
 
@@ -93,7 +96,7 @@
 
   grpc_call_credentials_get_request_metadata(
       &exec_ctx, ((grpc_composite_channel_credentials *)creds)->call_creds,
-      &sync.pollset, service_url, on_metadata_response, &sync);
+      &sync.pollset, context, on_metadata_response, &sync);
 
   gpr_mu_lock(GRPC_POLLSET_MU(&sync.pollset));
   while (!sync.is_done) {
diff --git a/test/cpp/end2end/end2end_test.cc b/test/cpp/end2end/end2end_test.cc
index 6da9333..dd02c4a 100644
--- a/test/cpp/end2end/end2end_test.cc
+++ b/test/cpp/end2end/end2end_test.cc
@@ -119,10 +119,13 @@
 
   bool IsBlocking() const GRPC_OVERRIDE { return is_blocking_; }
 
-  Status GetMetadata(grpc::string_ref service_url,
+  Status GetMetadata(grpc::string_ref service_url, grpc::string_ref method_name,
+                     const grpc::AuthContext& channel_auth_context,
                      std::multimap<grpc::string, grpc::string>* metadata)
       GRPC_OVERRIDE {
     EXPECT_GT(service_url.length(), 0UL);
+    EXPECT_GT(method_name.length(), 0UL);
+    EXPECT_TRUE(channel_auth_context.IsPeerAuthenticated());
     EXPECT_TRUE(metadata != nullptr);
     if (is_successful_) {
       metadata->insert(std::make_pair(kMetadataKey, metadata_value_));
diff --git a/tools/jenkins/run_jenkins.sh b/tools/jenkins/run_jenkins.sh
index 0e1af2a..4bb6c39 100755
--- a/tools/jenkins/run_jenkins.sh
+++ b/tools/jenkins/run_jenkins.sh
@@ -63,7 +63,7 @@
   # Prevent msbuild from picking up "platform" env variable, which would break the build
   unset platform
 
-  python tools/run_tests/run_tests.py -t -l $language -x report.xml $@ || true
+  python tools/run_tests/run_tests.py -t -l $language -c $config -x report.xml $@ || true
 
 elif [ "$platform" == "macos" ]
 then
@@ -77,11 +77,6 @@
 
   MAKE=gmake ./tools/run_tests/run_tests.py -t -l $language -c $config -x report.xml $@ || true
 
-elif [ "$platform" == "interop" ]
-then
-  echo "building interop tests for language $language"
-
-  ./tools/run_tests/run_interop_tests.py --use_docker -t -l $language --cloud_to_prod --server all || true
 else
   echo "Unknown platform $platform"
   exit 1
diff --git a/tools/run_tests/post_tests_ruby.sh b/tools/run_tests/post_tests_ruby.sh
new file mode 100755
index 0000000..66a9fbc
--- /dev/null
+++ b/tools/run_tests/post_tests_ruby.sh
@@ -0,0 +1,46 @@
+#!/bin/bash
+# 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.
+
+set -ex
+
+if [ "$CONFIG" != "gcov" ] ; then exit ; fi
+
+root=$(readlink -f $(dirname $0)/../..)
+out=$root/reports/ruby_ext_coverage
+tmp1=$(mktemp)
+tmp2=$(mktemp)
+cd $root
+lcov --capture --directory . --output-file $tmp1
+lcov --extract $tmp1 "$root/src/ruby/*" --output-file $tmp2
+genhtml $tmp2 --output-directory $out
+rm $tmp2
+rm $tmp1
+
+cp -rv $root/src/ruby/coverage $root/reports/ruby
diff --git a/tools/run_tests/run_tests.py b/tools/run_tests/run_tests.py
index 2d12ade..f1595ac 100755
--- a/tools/run_tests/run_tests.py
+++ b/tools/run_tests/run_tests.py
@@ -322,7 +322,7 @@
     return [['tools/run_tests/build_ruby.sh']]
 
   def post_tests_steps(self):
-    return []
+    return [['tools/run_tests/post_tests_ruby.sh']]
 
   def makefile_name(self):
     return 'Makefile'
@@ -862,6 +862,7 @@
   port_server_port = 32767
   _start_port_server(port_server_port)
   resultset = None
+  num_test_failures = 0
   try:
     infinite_runs = runs_per_test == 0
     one_run = set(
@@ -885,7 +886,7 @@
                      else itertools.repeat(massaged_one_run, runs_per_test))
     all_runs = itertools.chain.from_iterable(runs_sequence)
 
-    number_failures, resultset = jobset.run(
+    num_test_failures, resultset = jobset.run(
         all_runs, check_cancelled, newline_on_success=newline_on_success,
         travis=args.travis, infinite_runs=infinite_runs, maxjobs=args.jobs,
         stop_on_failure=args.stop_on_failure,
@@ -902,8 +903,6 @@
               do_newline=True)
         else:
           jobset.message('PASSED', k, do_newline=True)
-    if number_failures:
-      return 2
   finally:
     for antagonist in antagonists:
       antagonist.kill()
@@ -913,8 +912,8 @@
   number_failures, _ = jobset.run(
       post_tests_steps, maxjobs=1, stop_on_failure=True,
       newline_on_success=newline_on_success, travis=args.travis)
-  if number_failures:
-    return 3
+  if num_test_failures or number_failures:
+    return 2
 
   if cache: cache.save()