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

Google cloud storage support #1340

Merged
merged 3 commits into from
Jun 25, 2019
Merged

Google cloud storage support #1340

merged 3 commits into from
Jun 25, 2019

Conversation

jkobejs
Copy link
Contributor

@jkobejs jkobejs commented Nov 25, 2018

Fixes #588

Finished work that @francisdb started on #650.

  • Implemented missing apis
  • Added documentation
  • Improved java/scala dsl's
  • Added mocked http tests
  • Wrote tests for client facing apis

I didn't add documentation for all api methods, should add documentation for remaining ones?

@lightbend-cla-validator

Hi @josipgrgurica,

Thank you for your contribution! We really value the time you've taken to put this together.

Before we proceed with reviewing this pull request, please sign the Lightbend Contributors License Agreement:

http://www.lightbend.com/contribute/cla

@jkobejs
Copy link
Contributor Author

jkobejs commented Nov 25, 2018

I signed the CLA. Sorry, I totally forgot about that.

@francisdb
Copy link
Contributor

Thanks for picking this up!

@jkobejs
Copy link
Contributor Author

jkobejs commented Nov 26, 2018

@francisdb you're welcome. Thank you for doing great work on this connector 👏.

@ennru ennru added the p:new label Nov 26, 2018
@ennru
Copy link
Member

ennru commented Dec 12, 2018

Sorry for being slow on reviewing. We focused on Alpakka Kafka for a long while, but are back on this part of Alpakka now.

Copy link
Member

@ennru ennru left a comment

Choose a reason for hiding this comment

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

Looks great so far I got today. I haven't looked into the internals, yet.

We've recently tried out to use Akka Extensions for connectors that normally have a single incarnation (just as Akka Http contains Http()), that would flip the usage around to do things via methods on a CloudStorage object which implicitly takes the extension. What do you think about such a design? (see #1323 which would change S3 to that style)

Copy link
Member

@ennru ennru left a comment

Choose a reason for hiding this comment

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

Looks really great, I've nothing big to object.

}
}

private[storage] abstract class GoogleCloudStorage {
Copy link
Member

Choose a reason for hiding this comment

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

This cannot be private as it is part of the public API. You should make it a final class by passing in the client and ec.

* Gets information on a bucket
*
* @param bucketName the name of the bucket to look up
* @return a [[CompletionStage]] containing [[Bucket]] if it exists */
Copy link
Member

Choose a reason for hiding this comment

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

Scaladoc requires the full package path for classes not in the same package (importing doesn't help). But I think its enough to back-tick those as they show clickable in the message signatures.

}
}

private[storage] trait GoogleCloudStorage {
Copy link
Member

Choose a reason for hiding this comment

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

This can't be private. You should make it a final class by passing in the client.

new StorageSettings(projectId, clientEmail, privateKey)

override def toString: String =
s"StorageSettings(projectId=$projectId, clientEmail=$clientEmail, privateKey=$privateKey"
Copy link
Member

Choose a reason for hiding this comment

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

Don't print out the key by mistake...

Suggested change
s"StorageSettings(projectId=$projectId, clientEmail=$clientEmail, privateKey=$privateKey"
s"StorageSettings(projectId=$projectId, clientEmail=$clientEmail, privateKey=***)"

val chunkSize = 5 * 1024 * 1024

val uploadSink: Sink[ByteString, Future[StorageObject]] =
storage.resumableUpload(bucketName, objectName, ContentTypes.`text/plain(UTF-8)`, chunkSize)
Copy link
Member

Choose a reason for hiding this comment

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

In a perfect world, we'd pull these from sources that are run within the tests.

@josefelixh
Copy link

Is there any plans to continue working on this PR?

@jkobejs
Copy link
Contributor Author

jkobejs commented Jan 29, 2019

Is there any plans to continue working on this PR?

Yes there are, I plan to continue by the end of this week. 😄

@ennru
Copy link
Member

ennru commented May 22, 2019

Hi @josipgrgurica,

Do you still have plans to continue this? Or would you like anybody else to carry on?

@jkobejs
Copy link
Contributor Author

jkobejs commented May 28, 2019

Hi @josipgrgurica,

Do you still have plans to continue this? Or would you like anybody else to carry on?

Hi, I'm sorry about not pushing anything, I got stuck in my daily job 😞
I plan continue developing this feature 😄

I pushed code with changes for most comments.

@jkobejs
Copy link
Contributor Author

jkobejs commented May 28, 2019

Looks great so far I got today. I haven't looked into the internals, yet.

We've recently tried out to use Akka Extensions for connectors that normally have a single incarnation (just as Akka Http contains Http()), that would flip the usage around to do things via methods on a CloudStorage object which implicitly takes the extension. What do you think about such a design? (see #1323 which would change S3 to that style)

That makes sense to me, is this PR that I should use for reference #1395 ?

private def expiresSoon(g: AccessTokenExpiry): Boolean =
g.expiresAt < (tokenApi.now + 60)

def getToken()(implicit materializer: Materializer): Future[String] = {
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'm using this lib in production for few months and I noticed that this call can fail, it is transient issue. Does it make sense to retry it?

Copy link
Member

Choose a reason for hiding this comment

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

Possibly, but it is important to back off appropriately.
I'm not aware that someone had that issue with the other Google Cloud connectors, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You are right, we should be careful with back off.
What do you suggest for defaults?

@jkobejs
Copy link
Contributor Author

jkobejs commented Jun 6, 2019

@ennru I refactored code to use Akka Extensions, I took current S3 api design as a guide.

Copy link
Member

@ennru ennru left a comment

Choose a reason for hiding this comment

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

Had another look, still impressed. Great that you added the extension.

private def expiresSoon(g: AccessTokenExpiry): Boolean =
g.expiresAt < (tokenApi.now + 60)

def getToken()(implicit materializer: Materializer): Future[String] = {
Copy link
Member

Choose a reason for hiding this comment

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

Possibly, but it is important to back off appropriately.
I'm not aware that someone had that issue with the other Google Cloud connectors, though.

contentType: ContentType,
chunkSize: Int): Flow[ByteString, (HttpRequest, (MultiPartUpload, Int)), NotUsed] = {

assert(
Copy link
Member

Choose a reason for hiding this comment

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

This assertion could go all the way up to resumableUpload AFAICS.

val projectId = c.getString("project-id")
val clientEmail = c.getString("client-email")
val privateKey = c.getString("private-key")
val baseUrl = if (c.hasPath("base-url")) c.getString("base-url") else defaultBaseUrl
Copy link
Member

Choose a reason for hiding this comment

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

The idea of using config is to have all defaults in reference.conf so that they are always set.

@ennru
Copy link
Member

ennru commented Jun 13, 2019

The build fails on MiMa, you need to add .disablePlugins(MimaPlugin) to the project in build.sbt for now.

@lightbend-cla-validator

Hi @jkobejs,

Thank you for your contribution! We really value the time you've taken to put this together.

Before we proceed with reviewing this pull request, please sign the Lightbend Contributors License Agreement:

http://www.lightbend.com/contribute/cla

Copy link
Member

@2m 2m left a comment

Choose a reason for hiding this comment

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

Looking really good. Just some nitpick comments.

GCStorageStream.listBucket(bucket, Option(prefix)).asJava

/**
* Downloads object from bucket. Returns an empty Source if the object was not found.
Copy link
Member

Choose a reason for hiding this comment

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

This is contradictory with the comment in the "@return" section.

import akka.actor.{ActorSystem, ExtendedActorSystem, Extension, ExtensionId, ExtensionIdProvider}

/**
* Manages one [[S3Settings]] per `ActorSystem`.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* Manages one [[S3Settings]] per `ActorSystem`.
* Manages one [[GCStorageSettings]] per `ActorSystem`.

@@ -0,0 +1,10 @@
/*
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 should be moved to impl package, as it is only accessed from there.

@@ -0,0 +1,21 @@
/*
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 should be moved to impl package, as it is only accessed from there.


import scala.collection.immutable.Seq

final case class FailedUpload(reasons: Seq[Throwable]) extends Exception(reasons.map(_.getMessage).mkString(", "))
Copy link
Member

Choose a reason for hiding this comment

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

This should be converted to non-case class and introduced a Java API getter for reasons (as other model classes have).

@@ -0,0 +1,10 @@
/*
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 should be moved to impl package, as it is only accessed from there.

@@ -0,0 +1,22 @@
/*
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 should be moved to impl package, as it is only accessed from there.

@@ -0,0 +1,17 @@
/*
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 should be moved to impl package, as it is only accessed from there.

@@ -0,0 +1,6 @@
alpakka.googlecloud.storage {
Copy link
Member

Choose a reason for hiding this comment

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

Other Google Cloud connector (Pub/Sub) has used alpakka.google.cloud.pubsub namespace. So this one should follow suit.

@@ -22,6 +22,8 @@ object Dependencies {
val CouchbaseVersion = "2.7.2"
val CouchbaseVersionForDocs = "2.7"

val JwtCoreVersion = "2.1.0"
Copy link
Member

Choose a reason for hiding this comment

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

The 2.13 build failed because we upgraded from Scala 2.13.0-M5 to 2.13.0 in master and jwt-core 2.1.0 is not available for Scala 2.13.0. It is a bit tricky to upgrade jwt-core for libraries that are using it already (#1762 and #1763), but since this is a new connector, this can be upgraded to 3.0.1 safely.

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 updated JwtCoreVersion to 3.0.1 together with failing code.

I noticed these warnings when compiling against Scala 2.13.0

[warn] /Users/jgrgurica/Development/FOOS/alpakka/google-cloud-storage/src/main/scala/akka/stream/alpakka/googlecloud/storage/FailedUpload.scala:15:47: object JavaConverters in package collection is deprecated (since 2.13.0): Use `scala.jdk.CollectionConverters` instead
[warn]   def getReasons: java.util.List[Throwable] = reasons.asJava
[warn]                                               ^
[warn] /Users/jgrgurica/Development/FOOS/alpakka/google-cloud-storage/src/main/scala/akka/stream/alpakka/googlecloud/storage/FailedUpload.scala:23:65: object JavaConverters in package collection is deprecated (since 2.13.0): Use `scala.jdk.CollectionConverters` instead
[warn]   def create(reasons: java.util.List[Throwable]) = FailedUpload(reasons.asScala.toList)
[warn]                                                                 ^
[warn] /Users/jgrgurica/Development/FOOS/alpakka/google-cloud-storage/src/main/scala/akka/stream/alpakka/googlecloud/storage/impl/GCStorageStream.scala:598:27: value Stream in package scala is deprecated (since 2.13.0): Use LazyList instead of Stream
[warn]           .mapConcat(r => Stream.continually(r))
[warn]                           ^
[warn] three warnings found

Can you advise me how to deal with these warnings since scala.jdk.CollenctionConverters and scala.collection.immutable.LazyList doesn't exist in previous versions of Scala?

Copy link
Member

Choose a reason for hiding this comment

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

We will have to deal with warnings for now. It will be possible to get rid of those when a new scala-collection-compat release is out with scala/scala-collection-compat#217

Copy link
Member

@2m 2m left a comment

Choose a reason for hiding this comment

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

This one looks good to be merged in. I'll squash the commits to one, that contains all @francisdb work, and to another one with all @jkobejs work. And after last Travis validation run wel'll merge it.

francisdb and others added 3 commits June 21, 2019 15:44
Implement missing apis
Add documentation
Improve java/scala dsl's
Add mocked http tests
Write tests for client facing apis
Add stroage test command into travis script
Refactor api to static methods
remove VS settings, format dependencies, add project info
add reference conf, log status codes, disable mima
Bump jwt-core to 3.0.1
@2m
Copy link
Member

2m commented Jun 25, 2019

Failure was #1417

@2m 2m merged commit be3b89a into akka:master Jun 25, 2019
@2m
Copy link
Member

2m commented Jun 25, 2019

Great to have this in. Thanks a lot @francisdb and @jkobejs!

@2m 2m added this to the 1.0.3 milestone Jun 25, 2019
@francisdb
Copy link
Contributor

Great to see @jkobejs took the time to complete this, thanks!

@ennru
Copy link
Member

ennru commented Jun 25, 2019

I couldn't agree more, this is what open source is about. Work together.

@jkobejs
Copy link
Contributor Author

jkobejs commented Jun 26, 2019

@francisdb thanks for opportunity to finish your work, it has been a pleasure working on this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Google cloud storage client
6 participants