Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HealthCheck endpoint and service #630

Merged
merged 15 commits into from
Aug 5, 2019
Merged

HealthCheck endpoint and service #630

merged 15 commits into from
Aug 5, 2019

Conversation

ememmr
Copy link
Contributor

@ememmr ememmr commented Jul 24, 2019

What this does?

It imitates gRPC's health checking protocol .
Due to some problems of accessibility at grpc health check module, a new scala version has been developed.

It contains:

  • module health-check-unary for unary calls
  • module health-check-fs2 for streaming calls with fs2 library
  • module health-check-monix for streaming calls with monix library
  • example for health check fs2 streaming case
  • example for health check monix streaming case

Related to #626

mrtmmr added 5 commits July 17, 2019 15:50
On one terminal:
 sbt health-server/run

On other terminal:
 sbt health-client/run

Fails with exception:
com.google.protobuf.InvalidProtocolBufferException: While parsing a
protocol message, the input ended unexpectedly in the middle of a
field.  This could mean either that the input has been truncated or
that an embedded message misreported its own length.
 - Due to there’s a bug on protobuf services, we can’t use enum types.
Instead of them, we use ServerStatus as case class.
 - Empty.type doesn’t work on cleanAll and checkAll methods. Pending to
fix
 -
Pending: refactor unary calls into a common part
build.sbt Outdated Show resolved Hide resolved
@codecov
Copy link

codecov bot commented Jul 24, 2019

Codecov Report

Merging #630 into master will increase coverage by 0.47%.
The diff coverage is 88.88%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #630      +/-   ##
==========================================
+ Coverage   82.45%   82.93%   +0.47%     
==========================================
  Files          62       69       +7     
  Lines         969      996      +27     
  Branches       11       11              
==========================================
+ Hits          799      826      +27     
  Misses        170      170
Impacted Files Coverage Δ
...herkindness/mu/rpc/healthcheck/unary/service.scala 0% <0%> (ø)
...ndness/mu/rpc/healthcheck/monix/serviceMonix.scala 0% <0%> (ø)
...erkindness/mu/rpc/healthcheck/fs2/serviceFS2.scala 0% <0%> (ø)
...pc/healthcheck/fs2/handler/HealthServiceImpl.scala 100% <100%> (ø)
...erkindness/mu/rpc/healthcheck/unary/ordering.scala 100% <100%> (ø)
.../healthcheck/monix/handler/HealthServiceImpl.scala 100% <100%> (ø)
.../healthcheck/unary/handler/HealthServiceImpl.scala 100% <100%> (ø)
... and 5 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update dce6b79...ec14467. Read the comment docs.

 + application.conf of todolist example restored
Copy link
Member

@juanpedromoreno juanpedromoreno left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @mrtmmr! This is looking neat, I left some comments though.

Stream.eval(watchTopic.publish1(newStatus)).compile.drain

def watch(service: HealthCheck): Stream[F, HealthStatus] =
watchTopic.subscribe(20).filter(hs => hs.hc == service)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why 20?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought 20 was enough elements to enqueue before blocking the subscription to the stream. I guess it depends on the project in which is used

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense if we can provide it as a new function argument?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More or less, there is only one stream providing status information to everyone who ask for it. It just filters depending on the service you ask, so the number you pass will apply to every status (of every service added). The user needs beware of this to choose the correct number.

import higherkindness.mu.rpc.protocol.{service, Empty, Protobuf}
object service {

@service(Protobuf)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about providing this protocols as IDL files (.proto files), however, what we are providing here is also the implementation, so at the end, you will be adding the binary dependencies in any case. Am I right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but I think is better to provide it

mrtmmr added 3 commits July 26, 2019 12:04
- Monix:
 Debug messages dropped
 ExecutionContext dropped
 Effects fixed
- FS2:
 Debug messages dropped
 Effects fixed

Pending: refactor health-check modules into existing
Service:
- unary service:  server module
- fs2 service: fs2 module
- monix service: monix module

Example:
- health-client: all clients
- health-server-fs2: fs2 server
- health-server-monix: monix server
Copy link
Contributor

@fedefernandez fedefernandez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking good, thanks @mrtmmr

build.sbt Outdated Show resolved Hide resolved
build.sbt Outdated
.dependsOn(fs2 % "test->test")
.dependsOn(`internal-monix`% "test->test")
.dependsOn(`internal-fs2`% "test->test")
.dependsOn(channel)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could it be .dependsOn(channel % "test->test")?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need it for the unary service creation :/

Copy link
Contributor

@fedefernandez fedefernandez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some more comments, LGTM once addressed or answered

Ref.of[F, Map[String, ServerStatus]](Map.empty[String, ServerStatus])

checkRef.map(c => new HealthCheckServiceUnaryImpl[F](c))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  def buildInstance[F[_]: Sync](implicit s: Scheduler): F[HealthCheckServiceUnary[F]] =
      Ref.of[F, Map[String, ServerStatus]](Map.empty[String, ServerStatus])
        .map(new HealthCheckServiceUnaryImpl[F](_))


class HealthCheckServiceMonixImpl[F[_]: Sync](
checkStatus: Ref[F, Map[String, ServerStatus]],
pipe: (Observer.Sync[HealthStatus], Observable[HealthStatus]))(implicit s: Scheduler)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd change this to receive the types in two params:

object HealthServiceMonix {

  def buildInstance[F[_]: Sync](implicit s: Scheduler): F[HealthCheckServiceMonix[F]] = {

    val (observer, observable) =
      Pipe(
        MulticastStrategy.behavior(
          HealthStatus(new HealthCheck("FirstStatus"), ServerStatus("UNKNOWN"))))
        .concurrent(s)

    Ref.of[F, Map[String, ServerStatus]](Map.empty[String, ServerStatus])
      .map(new HealthCheckServiceMonixImpl[F](_, observer, observable))
  }
}

class HealthCheckServiceMonixImpl[F[_]: Sync](
    checkStatus: Ref[F, Map[String, ServerStatus]],
    observer: Observer.Sync[HealthStatus],
    observable: Observable[HealthStatus])(implicit s: Scheduler)
    extends AbstractHealthService[F](checkStatus)
    with HealthCheckServiceMonix[F] {

  override def setStatus(newStatus: HealthStatus): F[Unit] =
    checkStatus.update(_ + (newStatus.hc.nameService -> newStatus.status)) <*
      Sync[F].delay(observer.onNext(newStatus))

  override def watch(service: HealthCheck): Observable[HealthStatus] =
    observable.filter(_.hc.nameService == service.nameService)

}

Sync[F].delay(pipe._1.onNext(newStatus))

override def watch(service: HealthCheck): Observable[HealthStatus] =
pipe._2.filter(_.hc == service)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking about creating an implicit instance of cats.kernel.Order for the type HealthCheck, for example in a package object (mu/modules/channel/src/main/scala/higherkindness/mu/rpc/healthcheck/package.scala)

package higherkindness.mu.rpc

import cats.kernel.Order
import cats.instances.string._

package object healthcheck {

  implicit val orderForHealthCheck: Order[HealthCheck] = new HealthCheckOrder

  class HealthCheckOrder(implicit O: Order[String]) extends Order[HealthCheck] {

    override def eqv(x: HealthCheck, y: HealthCheck): Boolean =
      O.eqv(x.nameService, y.nameService)

    def compare(x: HealthCheck, y: HealthCheck): Int =
      O.compare(x.nameService, y.nameService)
  }

}

An then use the triple equals here:

pipe._2.filter(_.hc === service)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would this prevent? I'm not sure...

Copy link
Contributor

@fedefernandez fedefernandez Jul 31, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the triple equals will check the types at compilation time, avoiding some bugs when this code is evolved. i.e. if in the future the param service is not of type HealthCheck, the compilation will fail.

@fedefernandez
Copy link
Contributor

fedefernandez commented Jul 31, 2019

Now I'm seeing the errors in the build it looks we need to provide the protocol in a different module.

We could have the following modules:

  • health-check-protocol-unary: With the current unary @service
  • health-check-service-unary: Depending on channel and health-check-protocol-unary-scala with the % provided

We could also include the avdl and proto files in the channel resources, providing instructions about how to add the sbt settings for generating your protocol definition.

We'd need something similar for monix and fs2. @mrtmmr @juanpedromoreno thoughts?

@juanpedromoreno
Copy link
Member

Sounds good to me @fedefernandez @mrtmmr :)

@fedefernandez
Copy link
Contributor

@juanpedromoreno after a talk with @mrtmmr we could create the unary service in its own single module, right now only with Protobuf and create an issue for giving support for both, avro and proto.

build.sbt Outdated
lazy val `health-check-unary` = project
.in(file("modules/health-check-unary"))
.dependsOn(channel)
.dependsOn(server)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you don't need server

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right, I need it just on the examples

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ops

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happen?

build.sbt Outdated
.dependsOn(channel)
.dependsOn(server)
.settings(healthCheckSettings)
.settings(moduleName := "mu-rpc-example-health-check-unary")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.settings(moduleName := "mu-rpc-example-health-check-unary")
.settings(moduleName := "mu-rpc-health-check-unary")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ops, too much changes on build.sbt

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file seems to be related to your IDE. Could we remove it and ignore it from the .gitignore file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, no problem. I can't understand why it didn't happen before.

What should I do with code coverage test? It fails

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for this file

Copy link
Member

@juanpedromoreno juanpedromoreno left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @mrtmmr ! Outstanding job 👍


}

}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice

Copy link
Contributor

@fedefernandez fedefernandez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏 👏 👏


class OrderingTest extends WordSpec with Matchers {
"Ordering" should {
"works" in {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"works" in {
"work" in {

assert(new HealthCheck("example") === new HealthCheck("example"))
assert(new HealthCheck("example") !== new HealthCheck("not example"))
}
"works with boolean comparison" in {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"works with boolean comparison" in {
"work with boolean comparison" in {

@ememmr ememmr merged commit 0a0bac5 into master Aug 5, 2019
@ememmr ememmr deleted the mmm/626-healthcheckpoint branch August 6, 2019 13:20
@fedefernandez fedefernandez changed the title mmm/626 healthcheckpoint HealthCheck endpoint and service Aug 19, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants