/** * Copyright 2016 Hortonworks. * * 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.hortonworks.registries.schemaregistry.avro; import com.hortonworks.registries.common.catalog.CatalogResponse; import com.hortonworks.registries.common.test.IntegrationTest; import com.hortonworks.registries.schemaregistry.SchemaCompatibility; import com.hortonworks.registries.schemaregistry.SchemaFieldQuery; import com.hortonworks.registries.schemaregistry.SchemaIdVersion; import com.hortonworks.registries.schemaregistry.SchemaMetadata; import com.hortonworks.registries.schemaregistry.SchemaMetadataInfo; import com.hortonworks.registries.schemaregistry.SchemaVersion; import com.hortonworks.registries.schemaregistry.SchemaVersionInfo; import com.hortonworks.registries.schemaregistry.SchemaVersionKey; import com.hortonworks.registries.schemaregistry.SerDesInfo; import com.hortonworks.registries.schemaregistry.SerDesPair; import com.hortonworks.registries.schemaregistry.client.SchemaRegistryClient; import com.hortonworks.registries.schemaregistry.errors.IncompatibleSchemaException; import com.hortonworks.registries.schemaregistry.errors.InvalidSchemaException; import com.hortonworks.registries.schemaregistry.serdes.avro.AvroSnapshotDeserializer; import com.hortonworks.registries.schemaregistry.serdes.avro.AvroSnapshotSerializer; import org.apache.commons.io.IOUtils; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; import javax.ws.rs.BadRequestException; import javax.ws.rs.core.Response; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; import static com.hortonworks.registries.common.catalog.CatalogResponse.ResponseMessage.BAD_REQUEST_PARAM_MISSING; import static com.hortonworks.registries.common.catalog.CatalogResponse.ResponseMessage.UNSUPPORTED_SCHEMA_TYPE; import static javax.ws.rs.core.Response.Status.BAD_REQUEST; @Category(IntegrationTest.class) public class AvroSchemaRegistryClientTest extends AbstractAvroSchemaRegistryCientTest { private static final String INVALID_SCHEMA_PROVIDER_TYPE = "invalid-schema-provider-type"; /** Class to describe schema create operation failure scenarios */ private static class SchemaCreateFailureScenario { private final String test; private final String name; private final String group; private final String type; private final Response.Status expectedHttpResponse; private final CatalogResponse.ResponseMessage expectedCatalogResponse; SchemaCreateFailureScenario(String test, String name, String group, String type, Response.Status httpResponse, CatalogResponse.ResponseMessage catalogResponse) { this.test = test; this.name = name; this.group = group; this.type = type; expectedHttpResponse = httpResponse; expectedCatalogResponse = catalogResponse; } /** Return true if the schema creation failed as expected */ private void testCreate(SchemaRegistryClient client) { boolean failedAsExpected = false; SchemaMetadata schemaMetadata = new SchemaMetadata.Builder(name).type(type).schemaGroup(group). description("description").build(); try { client.registerSchemaMetadata(schemaMetadata); } catch (BadRequestException ex) { Response resp = ex.getResponse(); Assert.assertEquals(test + " - http response unexpected", expectedHttpResponse.getStatusCode(), resp.getStatus()); CatalogResponse catalogResponse = SchemaRegistryClient.readCatalogResponse(resp.readEntity(String.class)); Assert.assertEquals(test + " - catalog response unexpected", expectedCatalogResponse.getCode(), catalogResponse.getResponseCode()); failedAsExpected = true; } Assert.assertTrue(test + " - did not fail as expected", failedAsExpected); } } /** * Tests for various schema create failure scenarios */ private static SchemaCreateFailureScenario[] createFailureScenarios = { // No schema type specified new SchemaCreateFailureScenario("Test empty schema type", "name", "group", "", BAD_REQUEST, BAD_REQUEST_PARAM_MISSING), // Schema type is white spaces new SchemaCreateFailureScenario("Test empty schema white spaces", "name", "group", " ", BAD_REQUEST, BAD_REQUEST_PARAM_MISSING), // Invalid schema type new SchemaCreateFailureScenario("Test invalid schema type", "name", "group", "invalid", BAD_REQUEST, UNSUPPORTED_SCHEMA_TYPE), // No schema name new SchemaCreateFailureScenario("Test empty schema name", "", "group", AvroSchemaProvider.TYPE, BAD_REQUEST, BAD_REQUEST_PARAM_MISSING), // Schema name is white spaces new SchemaCreateFailureScenario("Test schema name white spaces", " ", "group", AvroSchemaProvider.TYPE, BAD_REQUEST, BAD_REQUEST_PARAM_MISSING) }; @Test public void testSchemaCreateFailures() throws Exception { // Run through all the tests related to schema create failure scenarios for (SchemaCreateFailureScenario scenario : createFailureScenarios) { scenario.testCreate(schemaRegistryClient); } } @Test public void testSchemaOps() throws Exception { SchemaMetadata schemaMetadata = createSchemaMetadata(TEST_NAME_RULE.getMethodName(), SchemaCompatibility.BOTH); Long id = schemaRegistryClient.registerSchemaMetadata(schemaMetadata); Assert.assertNotNull(id); // registering a new schema String schemaName = schemaMetadata.getName(); String schema1 = getSchema("/schema-1.avsc"); SchemaIdVersion v1 = schemaRegistryClient.addSchemaVersion(schemaName, new SchemaVersion(schema1, "Initial version of the schema")); Assert.assertNotNull(v1.getSchemaMetadataId()); Assert.assertEquals(1, v1.getVersion().intValue()); SchemaMetadataInfo schemaMetadataInfoForId = schemaRegistryClient.getSchemaMetadataInfo(v1.getSchemaMetadataId()); SchemaMetadataInfo schemaMetadataInfoForName = schemaRegistryClient.getSchemaMetadataInfo(schemaName); Assert.assertEquals(schemaMetadataInfoForId, schemaMetadataInfoForName); // adding a new version of the schema using uploadSchemaVersion API SchemaIdVersion v2 = schemaRegistryClient.uploadSchemaVersion(schemaMetadata.getName(), "second version", AvroSchemaRegistryClientTest.class.getResourceAsStream("/schema-2.avsc")); Assert.assertEquals(v1.getVersion() + 1, v2.getVersion().intValue()); SchemaVersionInfo schemaVersionInfo = schemaRegistryClient.getSchemaVersionInfo(new SchemaVersionKey(schemaName, v2.getVersion())); SchemaVersionInfo latest = schemaRegistryClient.getLatestSchemaVersionInfo(schemaName); Assert.assertEquals(latest, schemaVersionInfo); Collection<SchemaVersionInfo> allVersions = schemaRegistryClient.getAllVersions(schemaName); Assert.assertEquals(2, allVersions.size()); // receive the same version as earlier without adding a new schema entry as it exists in the same schema group. SchemaIdVersion version = schemaRegistryClient.addSchemaVersion(schemaMetadata, new SchemaVersion(schema1, "already added schema")); Assert.assertEquals(version, v1); Collection<SchemaVersionKey> md5SchemaVersionKeys = schemaRegistryClient.findSchemasByFields(new SchemaFieldQuery.Builder().name("md5").build()); Assert.assertEquals(2, md5SchemaVersionKeys.size()); Collection<SchemaVersionKey> txidSchemaVersionKeys = schemaRegistryClient.findSchemasByFields(new SchemaFieldQuery.Builder().name("txid").build()); Assert.assertEquals(1, txidSchemaVersionKeys.size()); } @Test(expected = InvalidSchemaException.class) public void testInvalidSchema() throws Exception { String schema = "--- invalid schema ---"; SchemaMetadata schemaMetadata = createSchemaMetadata(TEST_NAME_RULE.getMethodName(), SchemaCompatibility.BACKWARD); schemaRegistryClient.addSchemaVersion(schemaMetadata, new SchemaVersion(schema, "Initial version of the schema")); } @Test(expected = IncompatibleSchemaException.class) public void testIncompatibleSchemas() throws Exception { String schema = getSchema("/device.avsc"); String incompatSchema = getSchema("/device-incompat.avsc"); SchemaMetadata schemaMetadata = createSchemaMetadata(TEST_NAME_RULE.getMethodName(), SchemaCompatibility.BACKWARD); // registering a new schema schemaRegistryClient.addSchemaVersion(schemaMetadata, new SchemaVersion(schema, "Initial version of the schema")); // adding a new version of the schema SchemaVersion incompatSchemaInfo = new SchemaVersion(incompatSchema, "second version"); schemaRegistryClient.addSchemaVersion(schemaMetadata, incompatSchemaInfo); } private SchemaMetadata createSchemaMetadata(String schemaDesc, SchemaCompatibility compatibility) { return new SchemaMetadata.Builder(schemaDesc + "-schema") .type(AvroSchemaProvider.TYPE) .schemaGroup(schemaDesc + "-group") .description("Schema for " + schemaDesc) .compatibility(compatibility) .build(); } @Test public void testDefaultSerDes() throws Exception { Object defaultSerializer = schemaRegistryClient.getDefaultSerializer(AvroSchemaProvider.TYPE); Object defaultDeserializer = schemaRegistryClient.getDefaultDeserializer(AvroSchemaProvider.TYPE); Assert.assertEquals(AvroSnapshotDeserializer.class , defaultDeserializer.getClass()); Assert.assertEquals(AvroSnapshotSerializer.class, defaultSerializer.getClass()); } @Test(expected = Exception.class) public void testInvalidTypeForDefaultSer() throws Exception { schemaRegistryClient.getDefaultSerializer(INVALID_SCHEMA_PROVIDER_TYPE); } @Test(expected = Exception.class) public void testInvalidTypeForDefaultDes() throws Exception { schemaRegistryClient.getDefaultDeserializer(INVALID_SCHEMA_PROVIDER_TYPE); } @Test public void testAvroSerDeGenericObj() throws Exception { Map<String, String> config = Collections.singletonMap(SchemaRegistryClient.Configuration.SCHEMA_REGISTRY_URL.name(), rootUrl); AvroSnapshotSerializer avroSnapshotSerializer = new AvroSnapshotSerializer(); avroSnapshotSerializer.init(config); AvroSnapshotDeserializer avroSnapshotDeserializer = new AvroSnapshotDeserializer(); avroSnapshotDeserializer.init(config); String deviceSchema = getSchema("/device.avsc"); SchemaMetadata schemaMetadata = createSchemaMetadata(TEST_NAME_RULE.getMethodName(), SchemaCompatibility.BOTH); SchemaIdVersion v1 = schemaRegistryClient.addSchemaVersion(schemaMetadata, new SchemaVersion(deviceSchema, "Initial version of the schema")); Assert.assertNotNull(v1); Object deviceObject = createGenericRecordForDevice(); byte[] serializedData = avroSnapshotSerializer.serialize(deviceObject, schemaMetadata); Object deserializedObj = avroSnapshotDeserializer.deserialize(new ByteArrayInputStream(serializedData), null); Assert.assertEquals(deviceObject, deserializedObj); } @Test public void testAvroSerDePrimitives() throws Exception { Map<String, String> config = Collections.singletonMap(SchemaRegistryClient.Configuration.SCHEMA_REGISTRY_URL.name(), rootUrl); AvroSnapshotSerializer avroSnapshotSerializer = new AvroSnapshotSerializer(); avroSnapshotSerializer.init(config); AvroSnapshotDeserializer avroSnapshotDeserializer = new AvroSnapshotDeserializer(); avroSnapshotDeserializer.init(config); Object[] objects = generatePrimitivePayloads(); for (Object obj : objects) { String name = obj != null ? obj.getClass().getName() : Void.TYPE.getName(); SchemaMetadata schemaMetadata = createSchemaMetadata(name, SchemaCompatibility.BOTH); byte[] serializedData = avroSnapshotSerializer.serialize(obj, schemaMetadata); Object deserializedObj = avroSnapshotDeserializer.deserialize(new ByteArrayInputStream(serializedData), null); if (obj instanceof byte[]) { Assert.assertArrayEquals((byte[]) obj, (byte[]) deserializedObj); } else { Assert.assertEquals(obj, deserializedObj); } } } @Test public void testSerializerOps() throws Exception { String fileId = uploadFile(); SchemaMetadata schemaMetadata = createSchemaMetadata(TEST_NAME_RULE.getMethodName(), SchemaCompatibility.BOTH); schemaRegistryClient.addSchemaVersion(schemaMetadata, new SchemaVersion(getSchema("/device.avsc"), "Initial version of the schema")); SerDesPair serializerInfo = createSerDesInfo(fileId); Long serializerId = schemaRegistryClient.addSerDes(serializerInfo); String schemaName = schemaMetadata.getName(); schemaRegistryClient.mapSchemaWithSerDes(schemaName, serializerId); Collection<SerDesInfo> serializers = schemaRegistryClient.getSerDes(schemaName); Assert.assertTrue(serializers.stream() .map(x -> x.getSerDesPair()) .collect(Collectors.toList()) .contains(serializerInfo)); } private SerDesPair createSerDesInfo(String fileId) { return new SerDesPair( "avro serializer", "avro serializer", fileId, "con.hwx.registries.serializer.AvroSnapshotSerializer", "con.hwx.registries.serializer.AvroSnapshotDeserializer" ); } private String uploadFile() throws IOException { // upload a dummy file. File tmpJarFile = Files.createTempFile("foo", ".jar").toFile(); tmpJarFile.deleteOnExit(); try (FileOutputStream fileOutputStream = new FileOutputStream(tmpJarFile)) { IOUtils.write(("Some random stuff: " + UUID.randomUUID()).getBytes(), fileOutputStream); } InputStream inputStream = new FileInputStream(tmpJarFile); return schemaRegistryClient.uploadFile(inputStream); } }