/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.nifi.processors.gcp.storage;
import com.google.cloud.storage.Blob;
import com.google.cloud.storage.BlobId;
import com.google.cloud.storage.BlobInfo;
import com.google.cloud.storage.BucketInfo;
import com.google.cloud.storage.Storage;
import com.google.cloud.storage.StorageException;
import com.google.cloud.storage.testing.RemoteStorageHelper;
import org.apache.nifi.processor.Processor;
import org.apache.nifi.processors.gcp.GCPIntegrationTests;
import org.apache.nifi.processors.gcp.credentials.service.GCPCredentialsControllerService;
import org.apache.nifi.util.TestRunner;
import org.apache.nifi.util.TestRunners;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.experimental.categories.Category;
import java.util.Arrays;
import java.util.Iterator;
import static org.junit.Assert.fail;
/**
* Base class for GCS Integration Tests. Establishes a bucket and helper methods for creating test scenarios.
* Assumes use of <a href=https://developers.google.com/identity/protocols/application-default-credentials">Application Default</a>
* credentials for running tests.
*/
@Category(GCPIntegrationTests.class)
public abstract class AbstractGCSIT {
private static final String PROJECT_ID = System.getProperty("test.gcp.project.id", "nifi-test-gcp-project");
protected static final String BUCKET = RemoteStorageHelper.generateBucketName();
protected static final String ENCRYPTION_KEY = "3gCN8OOPAGpDwRYieHAj6fR0eBSG5vloaHl9vlZ3doQ=";
protected static final Integer RETRIES = 6;
protected static RemoteStorageHelper helper;
protected static Storage storage;
@BeforeClass
public static void setUp() {
try {
helper = RemoteStorageHelper.create();
storage = helper.getOptions().getService();
if (storage.get(BUCKET) != null) {
// As the generateBucketName function uses a UUID, this should pretty much never happen
fail("Bucket " + BUCKET + " exists. Please rerun the test to generate a new bucket name.");
}
// Create the bucket
storage.create(BucketInfo.of(BUCKET));
} catch (StorageException e) {
fail("Can't create bucket " + BUCKET + ": " + e.getLocalizedMessage());
}
if (storage.get(BUCKET) == null) {
fail("Setup incomplete, tests will fail");
}
}
@AfterClass
public static void tearDown() {
try {
// Empty the bucket before deleting it.
Iterator<Blob> blobIterator = storage.list(BUCKET, Storage.BlobListOption.versions(true)).iterateAll();
while(blobIterator.hasNext()) {
Blob blob = blobIterator.next();
storage.delete(blob.getBlobId());
}
storage.delete(BUCKET);
} catch (final StorageException e) {
fail("Unable to delete bucket " + BUCKET + ": " + e.getLocalizedMessage());
}
if (storage.get(BUCKET) != null) {
fail("Incomplete teardown, subsequent tests might fail");
}
}
protected static TestRunner buildNewRunner(Processor processor) throws Exception {
final GCPCredentialsControllerService credentialsControllerService = new GCPCredentialsControllerService();
final TestRunner runner = TestRunners.newTestRunner(processor);
runner.addControllerService("gcpCredentialsControllerService", credentialsControllerService);
runner.enableControllerService(credentialsControllerService);
runner.setProperty(AbstractGCSProcessor.GCP_CREDENTIALS_PROVIDER_SERVICE, "gcpCredentialsControllerService");
runner.setProperty(AbstractGCSProcessor.PROJECT_ID, PROJECT_ID);
runner.setProperty(AbstractGCSProcessor.RETRY_COUNT, String.valueOf(RETRIES));
runner.assertValid(credentialsControllerService);
return runner;
}
/**
* Puts a test file onto Google Cloud Storage in bucket {@link AbstractGCSIT#BUCKET}.
*
* @param key Key which the file will be uploaded under
* @param bytes The content of the file to be uploaded
* @throws StorageException if the file can't be created for some reason
*/
protected void putTestFile(String key, byte[] bytes) throws StorageException {
storage.create(BlobInfo.newBuilder(BlobId.of(BUCKET, key))
.build(), bytes
);
}
/**
* Puts a test file onto Google Cloud Storage in bucket {@link AbstractGCSIT#BUCKET}. This file is encrypted with
* server-side encryption using {@link AbstractGCSIT#ENCRYPTION_KEY}.
*
* @param key Key which the file will be uploaded under
* @param bytes The content of the file to be uploaded
* @throws StorageException if the file can't be created for some reason
*/
protected void putTestFileEncrypted(String key, byte[] bytes) throws StorageException {
storage.create(BlobInfo.newBuilder(BlobId.of(BUCKET, key))
.build(), bytes, Storage.BlobTargetOption.encryptionKey(ENCRYPTION_KEY));
}
/**
* Test if the file exists in Google Cloud Storage in bucket {@link AbstractGCSIT#BUCKET}.
*
* @param key Key to check for the file
* @return true if the file exists, false if it doesn't
* @throws StorageException if there are any issues accessing the file or connecting to GCS.
*/
protected boolean fileExists(String key) throws StorageException {
return (storage.get(BlobId.of(BUCKET, key)) != null);
}
/**
* Test if the file exists in Google Cloud Storage in bucket {@link AbstractGCSIT#BUCKET}, and if the content is as
* specified.
*
* @param key Key to check for the file
* @param bytes The content to compare to the content of the file
* @return true if the file exists and the content of the file is equal to {@code bytes}, false otherwise.
* @throws StorageException if there are any issues accessing the file or connecting to GCS.
*/
protected boolean fileEquals(String key, byte[] bytes) throws StorageException {
return (fileExists(key) && Arrays.equals(storage.readAllBytes(BlobId.of(BUCKET, key)), bytes));
}
/**
* Test if the file exists in Google Cloud Storage in bucket {@link AbstractGCSIT#BUCKET}, and if the content is as
* specified. Assumes that the file is encrypted using {@link AbstractGCSIT#ENCRYPTION_KEY}.
*
* @param key Key to check for the file
* @param bytes The content to compare to the content of the file
* @return true if the file exists and the content of the file is equal to {@code bytes}, false otherwise.
* @throws StorageException if there are any issues accessing the file or connecting to GCS.
*/
protected boolean fileEqualsEncrypted(String key, byte[] bytes) throws StorageException {
return (fileExists(key) && Arrays.equals(
storage.readAllBytes(BlobId.of(BUCKET, key), Storage.BlobSourceOption.decryptionKey(ENCRYPTION_KEY)),
bytes));
}
}