grpc-ecosystem / grpc-spring

Spring Boot starter module for gRPC framework.
https://grpc-ecosystem.github.io/grpc-spring/
Apache License 2.0
3.52k stars 826 forks source link

Changing server address at runtime based on user input #817

Closed Philipp-p closed 1 year ago

Philipp-p commented 1 year ago

The context

I develop a CLI client that needs to change the target server at runtime based on a user input.

The question

I implemented based on #519 a custom NameResolverProvider that is supposed to target only one server based on user input and did not specify any server in my application.properties:

public class FixedStaticNameResolverProvider extends NameResolverProvider {
  private static final String SCHEME = "static";
  private static String DEFAULT_HOST;
  private final StaticNameResolverProvider provider = new StaticNameResolverProvider();

  public FixedStaticNameResolverProvider(String defaultHost) {
    this.DEFAULT_HOST = "static://" + defaultHost + ":6565";
  }

  @Override
  public NameResolver newNameResolver(URI targetUri, Args args) {
    try {
      System.out.println("targetUri: " + targetUri);
      System.out.println("args: " + args);

      NameResolver dnsNameResolver = provider.newNameResolver(new URI(DEFAULT_HOST), args);
      return dnsNameResolver;
    } catch (URISyntaxException e) {
      log.error("NameResolver creation failed", e);
      return null;
    }
  }

  @Override
  protected boolean isAvailable() {
    return true;
  }

  @Override
  protected int priority() {
    return 10; // higher than everything else
  }

  @Override
  public String getDefaultScheme() {
    return SCHEME;
  }
}

Here StaticNameResolverProvider is StaticNameResolverProvider.

Then I create and register it based on https://yidongnan.github.io/grpc-spring-boot-starter/en/client/configuration.html#custom-nameresolverprovider as a bean with my application context, where grpcServerAddress is my user input (for my test case localhost):

appContext.registerBean("tmpServer", NameResolverProvider.class, () -> new FixedStaticNameResolverProvider(grpcServerAddress));

I then make an gRPC call. It does never execute my prints and therefore never invokes newNameResolver. Instead it throws this exception:

io.grpc.StatusRuntimeException: UNAVAILABLE: Unable to resolve host grpc-server

The same happens if I follow the documentation of grpc-java and register my NameResolverProvider like in the code below and execute a gRPC call.

NameResolverProvider nameResolverProvider = new FixedStaticNameResolverProvider(grpcServerAddress);
NameResolverRegistry.getDefaultRegistry().register(nameResolverProvider);

My question is if I am simply overlooking something or if I have to achieve this functionality by hot reloading my spring application properties and then refresh my application context? I have also seen #753 and #793, is this the way and I have to create a new channel for every request, since the Discovery used in #793 seems not to be the right solution for my problem?

The application's environment

Which versions do you use?

Additional information

The CLI client works fine if a server address is specified by the application.properties file. Even if the code above is included, it still targets the server stated in the application.properties file and everything works, but also in this case my prints in FixedStaticNameResolverProvider are never executed.

Any input or help is highly appreciated!

ST-DDT commented 1 year ago

There are two potential causes for this:

The registry doesnt accept your name resolver for whatever reason, you might want to debug into the name resolver registry for more insights.

The second possibility is that the bean is registered to late and thus is never considered for the resolution. Please check the bean initialization order in comparison to the grpc channel factory/injection.

Philipp-p commented 1 year ago

Thanks for the quick answer! I'll look into this tomorrow.

Philipp-p commented 1 year ago

There are two potential causes for this:

The registry doesnt accept your name resolver for whatever reason, you might want to debug into the name resolver registry for more insights.

I debugged into the registry (using the approach from grpc-java), it does accept the resolver and changes the default scheme for "dns" to "static" automatically and so on.

The second possibility is that the bean is registered to late and thus is never considered for the resolution. Please check the bean initialization order in comparison to the grpc channel factory/injection.

The bean is definitely way later then the grpc channel factory/injection. I need to boot up my CLI and if the user decides, for some reason, to contact another server during the use of the CLI I need to change the target server of the CLI. It looks like a restart of the CLI with a changed application.properties seems the way here or is the solution from #753 the right way?

ST-DDT commented 1 year ago

The second possibility is that the bean is registered to late and thus is never considered for the resolution. Please check the bean initialization order in comparison to the grpc channel factory/injection.

The bean is definitely way later then the grpc channel factory/injection.

The name resolver has to be registered by the time the NameResolverFactory returned/powered by the NameResolverRegistry is invoked first. (Which I think happens during the first use of the stub/channel)

There are two ways to achieve that:

Please make sure that you set the correct target to the NameResolver created by your NameResolverFactory. https://github.com/yidongnan/grpc-spring-boot-starter/blob/aa230f84207b2407fd45a9ff7a509647830f4e94/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/nameresolver/StaticNameResolver.java#L81

I need to boot up my CLI and if the user decides, for some reason, to contact another server during the use of the CLI I need to change the target server of the CLI.

Creating the stub on demand sounds like a good idea as well, since this allows you to use all registered schemes to resolve grpc server addresses (instead of only static/ips). e.g. consul/... service registries Please note that dns:/// supports DNS+SVC/TXT records as well as grpc service config depending on the present libraries whereas static:// does not.

Philipp-p commented 1 year ago

Thanks for the insights! I'll try it out!

ST-DDT commented 1 year ago

Just for FFR: I plan to simplify the injection of grpc channels/stubs in the future by providing a specialized injection/provider bean:

https://github.com/yidongnan/grpc-spring-boot-starter/issues/741#issuecomment-1247148603

Not sure when I have time for the rewrite though.