/* * 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.Acl; import com.google.cloud.storage.Blob; import com.google.cloud.storage.BlobInfo; import com.google.cloud.storage.Storage; import com.google.cloud.storage.StorageException; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import org.apache.nifi.flowfile.attributes.CoreAttributes; import org.apache.nifi.util.MockFlowFile; import org.apache.nifi.util.TestRunner; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import java.io.InputStream; import java.util.List; import java.util.Map; import java.util.Set; import static com.google.cloud.storage.Storage.PredefinedAcl.BUCKET_OWNER_READ; import static org.apache.nifi.processors.gcp.storage.StorageAttributes.BUCKET_ATTR; import static org.apache.nifi.processors.gcp.storage.StorageAttributes.CACHE_CONTROL_ATTR; import static org.apache.nifi.processors.gcp.storage.StorageAttributes.COMPONENT_COUNT_ATTR; import static org.apache.nifi.processors.gcp.storage.StorageAttributes.CONTENT_DISPOSITION_ATTR; import static org.apache.nifi.processors.gcp.storage.StorageAttributes.CONTENT_ENCODING_ATTR; import static org.apache.nifi.processors.gcp.storage.StorageAttributes.CONTENT_LANGUAGE_ATTR; import static org.apache.nifi.processors.gcp.storage.StorageAttributes.CRC32C_ATTR; import static org.apache.nifi.processors.gcp.storage.StorageAttributes.CREATE_TIME_ATTR; import static org.apache.nifi.processors.gcp.storage.StorageAttributes.ENCRYPTION_ALGORITHM_ATTR; import static org.apache.nifi.processors.gcp.storage.StorageAttributes.ENCRYPTION_SHA256_ATTR; import static org.apache.nifi.processors.gcp.storage.StorageAttributes.ETAG_ATTR; import static org.apache.nifi.processors.gcp.storage.StorageAttributes.GENERATED_ID_ATTR; import static org.apache.nifi.processors.gcp.storage.StorageAttributes.GENERATION_ATTR; import static org.apache.nifi.processors.gcp.storage.StorageAttributes.KEY_ATTR; import static org.apache.nifi.processors.gcp.storage.StorageAttributes.MD5_ATTR; import static org.apache.nifi.processors.gcp.storage.StorageAttributes.MEDIA_LINK_ATTR; import static org.apache.nifi.processors.gcp.storage.StorageAttributes.METAGENERATION_ATTR; import static org.apache.nifi.processors.gcp.storage.StorageAttributes.OWNER_ATTR; import static org.apache.nifi.processors.gcp.storage.StorageAttributes.OWNER_TYPE_ATTR; import static org.apache.nifi.processors.gcp.storage.StorageAttributes.SIZE_ATTR; import static org.apache.nifi.processors.gcp.storage.StorageAttributes.UPDATE_TIME_ATTR; import static org.apache.nifi.processors.gcp.storage.StorageAttributes.URI_ATTR; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.when; /** * Unit tests for {@link PutGCSObject} which do not use Google Cloud resources. */ public class PutGCSObjectTest extends AbstractGCSTest { private static final String FILENAME = "test-filename"; private static final String KEY = "test-key"; private static final String CONTENT_TYPE = "test-content-type"; private static final String MD5 = "test-md5"; private static final String CRC32C = "test-crc32c"; private static final Storage.PredefinedAcl ACL = BUCKET_OWNER_READ; private static final String ENCRYPTION_KEY = "test-encryption-key"; private static final Boolean OVERWRITE = false; private static final String CONTENT_DISPOSITION_TYPE = "inline"; private static final Long SIZE = 100L; private static final String CACHE_CONTROL = "test-cache-control"; private static final Integer COMPONENT_COUNT = 3; private static final String CONTENT_ENCODING = "test-content-encoding"; private static final String CONTENT_LANGUAGE = "test-content-language"; private static final String ENCRYPTION = "test-encryption"; private static final String ENCRYPTION_SHA256 = "test-encryption-256"; private static final String ETAG = "test-etag"; private static final String GENERATED_ID = "test-generated-id"; private static final String MEDIA_LINK = "test-media-link"; private static final Long METAGENERATION = 42L; private static final String OWNER_USER_EMAIL = "test-owner-user-email"; private static final String OWNER_GROUP_EMAIL = "test-owner-group-email"; private static final String OWNER_DOMAIN = "test-owner-domain"; private static final String OWNER_PROJECT_ID = "test-owner-project-id"; private static final String URI = "test-uri"; private static final String CONTENT_DISPOSITION = "attachment; filename=\"" + FILENAME + "\""; private static final Long CREATE_TIME = 1234L; private static final Long UPDATE_TIME = 4567L; private final static Long GENERATION = 5L; @Mock Storage storage; @Mock Blob blob; @Captor ArgumentCaptor<Storage.BlobWriteOption> blobWriteOptionArgumentCaptor; @Captor ArgumentCaptor<InputStream> inputStreamArgumentCaptor; @Captor ArgumentCaptor<BlobInfo> blobInfoArgumentCaptor; @Override public PutGCSObject getProcessor() { return new PutGCSObject() { @Override protected Storage getCloudService() { return storage; } }; } @Override protected void addRequiredPropertiesToRunner(TestRunner runner) { runner.setProperty(PutGCSObject.BUCKET, BUCKET); } @Test public void testSuccessfulPutOperationNoParameters() throws Exception { reset(storage, blob); final PutGCSObject processor = getProcessor(); final TestRunner runner = buildNewRunner(processor); addRequiredPropertiesToRunner(runner); runner.assertValid(); when(storage.create(blobInfoArgumentCaptor.capture(), inputStreamArgumentCaptor.capture(), blobWriteOptionArgumentCaptor.capture())).thenReturn(blob); runner.enqueue("test"); runner.run(); runner.assertAllFlowFilesTransferred(PutGCSObject.REL_SUCCESS); runner.assertTransferCount(PutGCSObject.REL_SUCCESS, 1); /** Can't do this any more due to the switch to Java InputStreams which close after an operation **/ /* String text; try (final Reader reader = new InputStreamReader(inputStreamArgumentCaptor.getValue())) { text = CharStreams.toString(reader); } assertEquals( "FlowFile content should be equal to the Blob content", "test", text ); */ final List<Storage.BlobWriteOption> blobWriteOptions = blobWriteOptionArgumentCaptor.getAllValues(); assertEquals("No BlobWriteOptions should be set", 0, blobWriteOptions.size()); final BlobInfo blobInfo = blobInfoArgumentCaptor.getValue(); assertNull(blobInfo.getMd5()); assertNull(blobInfo.getContentDisposition()); assertNull(blobInfo.getCrc32c()); } @Test public void testSuccessfulPutOperation() throws Exception { reset(storage, blob); final PutGCSObject processor = getProcessor(); final TestRunner runner = buildNewRunner(processor); addRequiredPropertiesToRunner(runner); runner.setProperty(PutGCSObject.KEY, KEY); runner.setProperty(PutGCSObject.CONTENT_TYPE, CONTENT_TYPE); runner.setProperty(PutGCSObject.MD5, MD5); runner.setProperty(PutGCSObject.CRC32C, CRC32C); runner.setProperty(PutGCSObject.ACL, ACL.name()); runner.setProperty(PutGCSObject.ENCRYPTION_KEY, ENCRYPTION_KEY); runner.setProperty(PutGCSObject.OVERWRITE, String.valueOf(OVERWRITE)); runner.setProperty(PutGCSObject.CONTENT_DISPOSITION_TYPE, CONTENT_DISPOSITION_TYPE); runner.assertValid(); when(storage.create(blobInfoArgumentCaptor.capture(), inputStreamArgumentCaptor.capture(), blobWriteOptionArgumentCaptor.capture())).thenReturn(blob); runner.enqueue("test", ImmutableMap.of(CoreAttributes.FILENAME.key(), FILENAME)); runner.run(); runner.assertAllFlowFilesTransferred(PutGCSObject.REL_SUCCESS); runner.assertTransferCount(PutGCSObject.REL_SUCCESS, 1); /* String text; try (final Reader reader = new InputStreamReader(inputStreamArgumentCaptor.getValue())) { text = CharStreams.toString(reader); } assertEquals( "FlowFile content should be equal to the Blob content", "test", text ); */ final BlobInfo blobInfo = blobInfoArgumentCaptor.getValue(); assertEquals( BUCKET, blobInfo.getBucket() ); assertEquals( KEY, blobInfo.getName() ); assertEquals( CONTENT_DISPOSITION_TYPE + "; filename=" + FILENAME, blobInfo.getContentDisposition() ); assertEquals( CONTENT_TYPE, blobInfo.getContentType() ); assertEquals( MD5, blobInfo.getMd5() ); assertEquals( CRC32C, blobInfo.getCrc32c() ); assertNull(blobInfo.getMetadata()); final List<Storage.BlobWriteOption> blobWriteOptions = blobWriteOptionArgumentCaptor.getAllValues(); final Set<Storage.BlobWriteOption> blobWriteOptionSet = ImmutableSet.copyOf(blobWriteOptions); assertEquals( "Each of the BlobWriteOptions should be unique", blobWriteOptions.size(), blobWriteOptionSet.size() ); assertTrue("The doesNotExist BlobWriteOption should be set if OVERWRITE is false", blobWriteOptionSet.contains(Storage.BlobWriteOption.doesNotExist())); assertTrue("The md5Match BlobWriteOption should be set if MD5 is non-null", blobWriteOptionSet.contains(Storage.BlobWriteOption.md5Match())); assertTrue("The crc32cMatch BlobWriteOption should be set if CRC32C is non-null", blobWriteOptionSet.contains(Storage.BlobWriteOption.crc32cMatch())); assertTrue("The predefinedAcl BlobWriteOption should be set if ACL is non-null", blobWriteOptionSet.contains(Storage.BlobWriteOption.predefinedAcl(ACL))); assertTrue("The encryptionKey BlobWriteOption should be set if ENCRYPTION_KEY is non-null", blobWriteOptionSet.contains(Storage.BlobWriteOption.encryptionKey(ENCRYPTION_KEY))); } @Test public void testSuccessfulPutOperationWithUserMetadata() throws Exception { reset(storage, blob); final PutGCSObject processor = getProcessor(); final TestRunner runner = buildNewRunner(processor); addRequiredPropertiesToRunner(runner); runner.setProperty( "testMetadataKey1", "testMetadataValue1" ); runner.setProperty( "testMetadataKey2", "testMetadataValue2" ); runner.assertValid(); when(storage.create(blobInfoArgumentCaptor.capture(), inputStreamArgumentCaptor.capture(), blobWriteOptionArgumentCaptor.capture())).thenReturn(blob); runner.enqueue("test"); runner.run(); runner.assertAllFlowFilesTransferred(PutGCSObject.REL_SUCCESS); runner.assertTransferCount(PutGCSObject.REL_SUCCESS, 1); /* String text; try (final Reader reader = new InputStreamReader(inputStreamArgumentCaptor.getValue())) { text = CharStreams.toString(reader); } assertEquals( "FlowFile content should be equal to the Blob content", "test", text ); */ final BlobInfo blobInfo = blobInfoArgumentCaptor.getValue(); final Map<String, String> metadata = blobInfo.getMetadata(); assertNotNull(metadata); assertEquals( 2, metadata.size() ); assertEquals( "testMetadataValue1", metadata.get("testMetadataKey1") ); assertEquals( "testMetadataValue2", metadata.get("testMetadataKey2") ); } @Test public void testAttributesSetOnSuccessfulPut() throws Exception { reset(storage, blob); final PutGCSObject processor = getProcessor(); final TestRunner runner = buildNewRunner(processor); addRequiredPropertiesToRunner(runner); runner.assertValid(); when(storage.create(any(BlobInfo.class), any(InputStream.class), any(Storage.BlobWriteOption.class))) .thenReturn(blob); when(blob.getBucket()).thenReturn(BUCKET); when(blob.getName()).thenReturn(KEY); when(blob.getSize()).thenReturn(SIZE); when(blob.getCacheControl()).thenReturn(CACHE_CONTROL); when(blob.getComponentCount()).thenReturn(COMPONENT_COUNT); when(blob.getContentDisposition()).thenReturn(CONTENT_DISPOSITION); when(blob.getContentEncoding()).thenReturn(CONTENT_ENCODING); when(blob.getContentLanguage()).thenReturn(CONTENT_LANGUAGE); when(blob.getContentType()).thenReturn(CONTENT_TYPE); when(blob.getCrc32c()).thenReturn(CRC32C); final BlobInfo.CustomerEncryption mockEncryption = mock(BlobInfo.CustomerEncryption.class); when(blob.getCustomerEncryption()).thenReturn(mockEncryption); when(mockEncryption.getEncryptionAlgorithm()).thenReturn(ENCRYPTION); when(mockEncryption.getKeySha256()).thenReturn(ENCRYPTION_SHA256); when(blob.getEtag()).thenReturn(ETAG); when(blob.getGeneratedId()).thenReturn(GENERATED_ID); when(blob.getGeneration()).thenReturn(GENERATION); when(blob.getMd5()).thenReturn(MD5); when(blob.getMediaLink()).thenReturn(MEDIA_LINK); when(blob.getMetageneration()).thenReturn(METAGENERATION); when(blob.getSelfLink()).thenReturn(URI); when(blob.getCreateTime()).thenReturn(CREATE_TIME); when(blob.getUpdateTime()).thenReturn(UPDATE_TIME); runner.enqueue("test"); runner.run(); runner.assertAllFlowFilesTransferred(PutGCSObject.REL_SUCCESS); runner.assertTransferCount(PutGCSObject.REL_SUCCESS, 1); final MockFlowFile mockFlowFile = runner.getFlowFilesForRelationship(PutGCSObject.REL_SUCCESS).get(0); mockFlowFile.assertAttributeEquals(BUCKET_ATTR, BUCKET); mockFlowFile.assertAttributeEquals(KEY_ATTR, KEY); mockFlowFile.assertAttributeEquals(SIZE_ATTR, String.valueOf(SIZE)); mockFlowFile.assertAttributeEquals(CACHE_CONTROL_ATTR, CACHE_CONTROL); mockFlowFile.assertAttributeEquals(COMPONENT_COUNT_ATTR, String.valueOf(COMPONENT_COUNT)); mockFlowFile.assertAttributeEquals(CONTENT_DISPOSITION_ATTR, CONTENT_DISPOSITION); mockFlowFile.assertAttributeEquals(CoreAttributes.FILENAME.key(), FILENAME); mockFlowFile.assertAttributeEquals(CONTENT_ENCODING_ATTR, CONTENT_ENCODING); mockFlowFile.assertAttributeEquals(CONTENT_LANGUAGE_ATTR, CONTENT_LANGUAGE); mockFlowFile.assertAttributeEquals(CoreAttributes.MIME_TYPE.key(), CONTENT_TYPE); mockFlowFile.assertAttributeEquals(CRC32C_ATTR, CRC32C); mockFlowFile.assertAttributeEquals(ENCRYPTION_ALGORITHM_ATTR, ENCRYPTION); mockFlowFile.assertAttributeEquals(ENCRYPTION_SHA256_ATTR, ENCRYPTION_SHA256); mockFlowFile.assertAttributeEquals(ETAG_ATTR, ETAG); mockFlowFile.assertAttributeEquals(GENERATED_ID_ATTR, GENERATED_ID); mockFlowFile.assertAttributeEquals(GENERATION_ATTR, String.valueOf(GENERATION)); mockFlowFile.assertAttributeEquals(MD5_ATTR, MD5); mockFlowFile.assertAttributeEquals(MEDIA_LINK_ATTR, MEDIA_LINK); mockFlowFile.assertAttributeEquals(METAGENERATION_ATTR, String.valueOf(METAGENERATION)); mockFlowFile.assertAttributeEquals(URI_ATTR, URI); mockFlowFile.assertAttributeEquals(CREATE_TIME_ATTR, String.valueOf(CREATE_TIME)); mockFlowFile.assertAttributeEquals(UPDATE_TIME_ATTR, String.valueOf(UPDATE_TIME)); } @Test public void testAclAttributeUser() throws Exception { reset(storage, blob); final PutGCSObject processor = getProcessor(); final TestRunner runner = buildNewRunner(processor); addRequiredPropertiesToRunner(runner); runner.assertValid(); when(storage.create(any(BlobInfo.class), any(InputStream.class), any(Storage.BlobWriteOption.class))) .thenReturn(blob); final Acl.User mockUser = mock(Acl.User.class); when(mockUser.getEmail()).thenReturn(OWNER_USER_EMAIL); when(blob.getOwner()).thenReturn(mockUser); runner.enqueue("test"); runner.run(); runner.assertAllFlowFilesTransferred(PutGCSObject.REL_SUCCESS); runner.assertTransferCount(PutGCSObject.REL_SUCCESS, 1); final MockFlowFile mockFlowFile = runner.getFlowFilesForRelationship(PutGCSObject.REL_SUCCESS).get(0); mockFlowFile.assertAttributeEquals(OWNER_ATTR, OWNER_USER_EMAIL); mockFlowFile.assertAttributeEquals(OWNER_TYPE_ATTR, "user"); } @Test public void testAclAttributeGroup() throws Exception { reset(storage, blob); final PutGCSObject processor = getProcessor(); final TestRunner runner = buildNewRunner(processor); addRequiredPropertiesToRunner(runner); runner.assertValid(); when(storage.create(any(BlobInfo.class), any(InputStream.class), any(Storage.BlobWriteOption.class))) .thenReturn(blob); final Acl.Group mockGroup = mock(Acl.Group.class); when(mockGroup.getEmail()).thenReturn(OWNER_GROUP_EMAIL); when(blob.getOwner()).thenReturn(mockGroup); runner.enqueue("test"); runner.run(); runner.assertAllFlowFilesTransferred(PutGCSObject.REL_SUCCESS); runner.assertTransferCount(PutGCSObject.REL_SUCCESS, 1); final MockFlowFile mockFlowFile = runner.getFlowFilesForRelationship(PutGCSObject.REL_SUCCESS).get(0); mockFlowFile.assertAttributeEquals(OWNER_ATTR, OWNER_GROUP_EMAIL); mockFlowFile.assertAttributeEquals(OWNER_TYPE_ATTR, "group"); } @Test public void testAclAttributeDomain() throws Exception { reset(storage, blob); final PutGCSObject processor = getProcessor(); final TestRunner runner = buildNewRunner(processor); addRequiredPropertiesToRunner(runner); runner.assertValid(); when(storage.create(any(BlobInfo.class), any(InputStream.class), any(Storage.BlobWriteOption.class))) .thenReturn(blob); final Acl.Domain mockDomain = mock(Acl.Domain.class); when(mockDomain.getDomain()).thenReturn(OWNER_DOMAIN); when(blob.getOwner()).thenReturn(mockDomain); runner.enqueue("test"); runner.run(); runner.assertAllFlowFilesTransferred(PutGCSObject.REL_SUCCESS); runner.assertTransferCount(PutGCSObject.REL_SUCCESS, 1); final MockFlowFile mockFlowFile = runner.getFlowFilesForRelationship(PutGCSObject.REL_SUCCESS).get(0); mockFlowFile.assertAttributeEquals(OWNER_ATTR, OWNER_DOMAIN); mockFlowFile.assertAttributeEquals(OWNER_TYPE_ATTR, "domain"); } @Test public void testAclAttributeProject() throws Exception { reset(storage, blob); final PutGCSObject processor = getProcessor(); final TestRunner runner = buildNewRunner(processor); addRequiredPropertiesToRunner(runner); runner.assertValid(); when(storage.create(any(BlobInfo.class), any(InputStream.class), any(Storage.BlobWriteOption.class))) .thenReturn(blob); final Acl.Project mockProject = mock(Acl.Project.class); when(mockProject.getProjectId()).thenReturn(OWNER_PROJECT_ID); when(blob.getOwner()).thenReturn(mockProject); runner.enqueue("test"); runner.run(); runner.assertAllFlowFilesTransferred(PutGCSObject.REL_SUCCESS); runner.assertTransferCount(PutGCSObject.REL_SUCCESS, 1); final MockFlowFile mockFlowFile = runner.getFlowFilesForRelationship(PutGCSObject.REL_SUCCESS).get(0); mockFlowFile.assertAttributeEquals(OWNER_ATTR, OWNER_PROJECT_ID); mockFlowFile.assertAttributeEquals(OWNER_TYPE_ATTR, "project"); } @Test public void testFailureHandling() throws Exception { reset(storage); final PutGCSObject processor = getProcessor(); final TestRunner runner = buildNewRunner(processor); addRequiredPropertiesToRunner(runner); runner.assertValid(); when(storage.create(any(BlobInfo.class), any(InputStream.class), any(Storage.BlobWriteOption.class))) .thenThrow(new StorageException(404, "test exception")); runner.enqueue("test"); runner.run(); runner.assertAllFlowFilesTransferred(PutGCSObject.REL_FAILURE); runner.assertTransferCount(PutGCSObject.REL_FAILURE, 1); final MockFlowFile mockFlowFile = runner.getFlowFilesForRelationship(PutGCSObject.REL_FAILURE).get(0); assertTrue(mockFlowFile.isPenalized()); } }