Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(gcs): Fix startup error when bucketLocation is not specified (#934)
* test(gcs): Add some implementations to FakeStorageRpc In an upcoming commit, we'll need to also support getting a bucket; to prepare for that let's update the FakeStorageRpc to: * Keep track of what bucket objects are in (rather than now where it assumes they are all in the same bucket) * Handle creating and getting a bucket We'll add an additional Map around the blobs data structure that maps the bucket to a Map of blobs. (This is a bit of a complex data structure that in production code might be better expressed as classes, but for this single fake in a test it's porbably fine.) Creating a bucket initializes its blobs to an empty map; we know if a bucket exists by whether it has an entry in the map. I used the documentation on the interface we're mocking to decide how to handle a missing bucket; in some cases we just return null (when getting an object or a bucket) while in others we propagate the 404. This required one small change to the existing tests, which is that we'll need to create the bucket before we use them in the tests. * test(gcs): Add test to demonstrate NPE on missing bucketLocation This commit adds a broken test to demostrate the NPE that occurs on startup when the bucketLocation is not specified. * fix(gcs): Fix startup error when bucketLocation is not specified There was a regression in 1.22 where omitting bucketLocation from one's GCS config now causes an error on startup. Prior to the rewrite of GcsStorageService, we accepted either null or empty string as the bucket location; now that GcsStorageService is in kotlin and does not use a String? for the field, an error occurs on trying to create the GcsStorageService. The fix is just to default the string to "" in the config properties. * style(gcs): Use Bucket.of intead of builder * test(gcs): Replace nested map with classes To simplify the nested map, define a Buckets class and a BucketContents class, with operation names that make it more clear what is happening. Also, I realized that before my changes we were appending the bucket name to the object name before putting it into the map, but now we don't need to do that anymore because we have a separate map for each bucket, so remove some unecessary calls to fullPath (which is now only used in printing error messages). * style(gcs): Fix typo * style(gcs): Fix constructor There's no need to accept the map in a private constructor then separately define a no-arg constructor that passes an empty map, just make the map a val in the class.
- Loading branch information
Showing
6 changed files
with
165 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
49 changes: 49 additions & 0 deletions
49
front50-gcs/src/test/kotlin/com/netflix/spinnaker/front50/model/GcsIntegrationTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
/* | ||
* Copyright 2020 Google, LLC | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
*/ | ||
|
||
package com.netflix.spinnnaker.front50.model | ||
|
||
import com.netflix.spinnaker.front50.config.GcsConfig | ||
import com.netflix.spinnaker.front50.model.GcsIntegrationTestConfiguration | ||
import com.netflix.spinnaker.front50.model.GcsStorageService | ||
import com.netflix.spinnaker.front50.model.ObjectType | ||
import com.netflix.spinnaker.front50.model.pipeline.Pipeline | ||
import org.junit.jupiter.api.Test | ||
import org.junit.jupiter.api.extension.ExtendWith | ||
import org.springframework.beans.factory.annotation.Autowired | ||
import org.springframework.boot.test.context.ConfigFileApplicationContextInitializer | ||
import org.springframework.test.context.ContextConfiguration | ||
import org.springframework.test.context.TestPropertySource | ||
import org.springframework.test.context.junit.jupiter.SpringExtension | ||
import strikt.api.expectThat | ||
import strikt.assertions.hasSize | ||
import strikt.assertions.isEmpty | ||
|
||
@ExtendWith(SpringExtension::class) | ||
@ContextConfiguration( | ||
classes = [GcsConfig::class, GcsIntegrationTestConfiguration::class], | ||
initializers = [ConfigFileApplicationContextInitializer::class] | ||
) | ||
@TestPropertySource(properties = ["spring.config.location=classpath:minimal-gcs-account.yml"]) | ||
class GcsIntegrationTest { | ||
@Test | ||
fun startupTest(@Autowired storageService: GcsStorageService) { | ||
expectThat(storageService.listObjectKeys(ObjectType.PIPELINE)).isEmpty() | ||
storageService.storeObject(ObjectType.PIPELINE, "my-key", Pipeline()) | ||
expectThat(storageService.listObjectKeys(ObjectType.PIPELINE)).hasSize(1) | ||
} | ||
} |
58 changes: 58 additions & 0 deletions
58
...cs/src/test/kotlin/com/netflix/spinnaker/front50/model/GcsIntegrationTestConfiguration.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/* | ||
* Copyright 2020 Google, LLC | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License") | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.netflix.spinnaker.front50.model | ||
|
||
import com.google.auth.Credentials | ||
import com.google.cloud.storage.Storage | ||
import com.google.cloud.storage.StorageOptions | ||
import com.netflix.spectator.api.NoopRegistry | ||
import com.netflix.spectator.api.Registry | ||
import com.netflix.spinnaker.front50.config.GcsProperties | ||
import com.netflix.spinnaker.front50.config.StorageServiceConfigurationProperties | ||
import com.netflix.spinnnaker.front50.model.FakeStorageRpcFactory | ||
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry | ||
import io.github.resilience4j.circuitbreaker.internal.InMemoryCircuitBreakerRegistry | ||
import io.mockk.mockk | ||
import java.time.Clock | ||
import java.time.Instant | ||
import java.time.ZoneOffset | ||
import org.springframework.beans.factory.annotation.Qualifier | ||
import org.springframework.boot.context.properties.EnableConfigurationProperties | ||
import org.springframework.boot.test.context.TestConfiguration | ||
import org.springframework.context.annotation.Bean | ||
import org.springframework.context.annotation.Primary | ||
|
||
@EnableConfigurationProperties(StorageServiceConfigurationProperties::class) | ||
@TestConfiguration | ||
class GcsIntegrationTestConfiguration { | ||
private val clock = Clock.fixed(Instant.ofEpochSecond(629528400L), ZoneOffset.UTC) | ||
|
||
@Bean | ||
fun noopRegistry(): Registry = NoopRegistry() | ||
|
||
@Bean | ||
@Primary | ||
@Qualifier("gcsCredentials") fun gcsCredentials(): Credentials = mockk() | ||
|
||
@Bean | ||
fun circuitBreakerRegistry(): CircuitBreakerRegistry = InMemoryCircuitBreakerRegistry() | ||
|
||
@Bean | ||
fun storage(properties: GcsProperties): Storage { | ||
return StorageOptions.newBuilder().setServiceRpcFactory(FakeStorageRpcFactory(clock)).build().service | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
spinnaker: | ||
gcs: | ||
enabled: true | ||
project: my-project | ||
bucket: my-bucket |