Commit 8348f221 by Ashutosh Mestry Committed by Madhan Neethiraj

ATLAS-2120: Import API updated to detect attribute-type change

parent 636a6213
...@@ -68,12 +68,12 @@ To use the option, set the contents of _importOptions.json_ to: ...@@ -68,12 +68,12 @@ To use the option, set the contents of _importOptions.json_ to:
} }
</verbatim> </verbatim>
To use _startIndex_, use the following in the _importOptions.json_: To use _startPosition_, use the following in the _importOptions.json_:
<verbatim> <verbatim>
{ {
"options": { "options": {
"startIndex": "332" "startPosition": "332"
} }
} }
</verbatim> </verbatim>
...@@ -89,7 +89,7 @@ Steps to use the behavior: ...@@ -89,7 +89,7 @@ Steps to use the behavior:
The output of Export has _atlas-typedef.json_ that contains the type definitions for the entities exported. The output of Export has _atlas-typedef.json_ that contains the type definitions for the entities exported.
By default (that is if no options is specified), the type definitions are imported and applied to the system. The entity import is then performed. By default (that is if no options is specified), the type definitions are imported and applied to the system being imported to. The entity import is performed after this.
In some cases, you would not want to modify the type definitions. Import may be better off failing than the types be modified. In some cases, you would not want to modify the type definitions. Import may be better off failing than the types be modified.
...@@ -100,7 +100,7 @@ Table below enumerates the conditions that get addressed as part of type definit ...@@ -100,7 +100,7 @@ Table below enumerates the conditions that get addressed as part of type definit
|*Condition*|*Action*| |*Condition*|*Action*|
| Incoming type does not exist in target system | Type is created. | | Incoming type does not exist in target system | Type is created. |
|Type to be imported and type in target system are same | No change | |Type to be imported and type in target system are same | No change |
|Type to be imported and type in target system differ by some attributes| Target system type is updated to the attributes present in the source. It is possible that the target system will have attributes in addition to the one present in the source. In that case, the target system's type attributes will be an union of the attributes. (Attributes in target system will not be deleted to match the source.)| |Type to be imported and type in target system differ by some attributes| Target system type is updated to the attributes present in the source. It is possible that the target system will have attributes in addition to the one present in the source. In that case, the target system's type attributes will be an union of the attributes. Attributes in target system will not be deleted to match the source. If the type of the attribute differ, import process will be aborted and exception logged.|
To use the option, set the contents of _importOptions.json_ to: To use the option, set the contents of _importOptions.json_ to:
<verbatim> <verbatim>
......
...@@ -92,8 +92,9 @@ public enum AtlasErrorCode { ...@@ -92,8 +92,9 @@ public enum AtlasErrorCode {
RELATIONSHIP_INVALID_ENDTYPE(400, "ATLAS-400-00-045", "Invalid entity-type for relationship attribute ‘{0}’: entity specified (guid={1}) is of type ‘{2}’, but expected type is ‘{3}’"), RELATIONSHIP_INVALID_ENDTYPE(400, "ATLAS-400-00-045", "Invalid entity-type for relationship attribute ‘{0}’: entity specified (guid={1}) is of type ‘{2}’, but expected type is ‘{3}’"),
UNKNOWN_CLASSIFICATION(400, "ATLAS-400-00-046", "{0}: Unknown/invalid classification"), UNKNOWN_CLASSIFICATION(400, "ATLAS-400-00-046", "{0}: Unknown/invalid classification"),
INVALID_SEARCH_PARAMS(400, "ATLAS-400-00-047", "No search parameter was found. One of the following MUST be specified in the request; typeName, classification or queryText"), INVALID_SEARCH_PARAMS(400, "ATLAS-400-00-047", "No search parameter was found. One of the following MUST be specified in the request; typeName, classification or queryText"),
INVALID_RELATIONSHIP_ATTRIBUTE(400, "ATLAS-400-00-048", "Expected attribute {0} to be a relationship but found type {}"), INVALID_RELATIONSHIP_ATTRIBUTE(400, "ATLAS-400-00-048", "Expected attribute {0} to be a relationship but found type {1}"),
INVALID_RELATIONSHIP_TYPE(400, "ATLAS-400-00-049", "Invalid entity type '{0}', guid '{1}' in relationship search"), INVALID_RELATIONSHIP_TYPE(400, "ATLAS-400-00-049", "Invalid entity type '{0}', guid '{1}' in relationship search"),
INVALID_IMPORT_ATTRIBUTE_TYPE_CHANGED(400, "ATLAS-400-00-050", "Attribute {0}.{1} is of type {2}. Import has this attribute type as {3}"),
// All Not found enums go here // All Not found enums go here
TYPE_NAME_NOT_FOUND(404, "ATLAS-404-00-001", "Given typename {0} was invalid"), TYPE_NAME_NOT_FOUND(404, "ATLAS-404-00-001", "Given typename {0} was invalid"),
......
...@@ -6,9 +6,9 @@ ...@@ -6,9 +6,9 @@
* to you under the Apache License, Version 2.0 (the * to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance * "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at * with the License. You may obtain a copy of the License at
* * <p>
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* * <p>
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
*/ */
package org.apache.atlas.repository.impexp; package org.apache.atlas.repository.impexp;
import org.apache.atlas.AtlasErrorCode;
import org.apache.atlas.exception.AtlasBaseException; import org.apache.atlas.exception.AtlasBaseException;
import org.apache.atlas.model.impexp.AtlasImportResult; import org.apache.atlas.model.impexp.AtlasImportResult;
import org.apache.atlas.model.typedef.AtlasClassificationDef; import org.apache.atlas.model.typedef.AtlasClassificationDef;
...@@ -26,11 +27,15 @@ import org.apache.atlas.model.typedef.AtlasStructDef; ...@@ -26,11 +27,15 @@ import org.apache.atlas.model.typedef.AtlasStructDef;
import org.apache.atlas.model.typedef.AtlasTypesDef; import org.apache.atlas.model.typedef.AtlasTypesDef;
import org.apache.atlas.store.AtlasTypeDefStore; import org.apache.atlas.store.AtlasTypeDefStore;
import org.apache.atlas.type.AtlasTypeRegistry; import org.apache.atlas.type.AtlasTypeRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class TypeAttributeDifference { public class TypeAttributeDifference {
private static final Logger LOG = LoggerFactory.getLogger(TypeAttributeDifference.class);
private final AtlasTypeDefStore typeDefStore; private final AtlasTypeDefStore typeDefStore;
private final AtlasTypeRegistry typeRegistry; private final AtlasTypeRegistry typeRegistry;
...@@ -48,9 +53,9 @@ public class TypeAttributeDifference { ...@@ -48,9 +53,9 @@ public class TypeAttributeDifference {
} }
private void updateEntityDef(AtlasTypesDef typeDefinitionMap, AtlasImportResult result) throws AtlasBaseException { private void updateEntityDef(AtlasTypesDef typeDefinitionMap, AtlasImportResult result) throws AtlasBaseException {
for (AtlasEntityDef def: typeDefinitionMap.getEntityDefs()) { for (AtlasEntityDef def : typeDefinitionMap.getEntityDefs()) {
AtlasEntityDef existing = typeRegistry.getEntityDefByName(def.getName()); AtlasEntityDef existing = typeRegistry.getEntityDefByName(def.getName());
if(existing != null && addAttributes(existing, def)) { if (existing != null && addAttributes(existing, def)) {
typeDefStore.updateEntityDefByName(existing.getName(), existing); typeDefStore.updateEntityDefByName(existing.getName(), existing);
result.incrementMeticsCounter("typedef:entitydef:update"); result.incrementMeticsCounter("typedef:entitydef:update");
} }
...@@ -58,9 +63,9 @@ public class TypeAttributeDifference { ...@@ -58,9 +63,9 @@ public class TypeAttributeDifference {
} }
private void updateClassificationDef(AtlasTypesDef typeDefinitionMap, AtlasImportResult result) throws AtlasBaseException { private void updateClassificationDef(AtlasTypesDef typeDefinitionMap, AtlasImportResult result) throws AtlasBaseException {
for (AtlasClassificationDef def: typeDefinitionMap.getClassificationDefs()) { for (AtlasClassificationDef def : typeDefinitionMap.getClassificationDefs()) {
AtlasClassificationDef existing = typeRegistry.getClassificationDefByName(def.getName()); AtlasClassificationDef existing = typeRegistry.getClassificationDefByName(def.getName());
if(existing != null && addAttributes(existing, def)) { if (existing != null && addAttributes(existing, def)) {
typeDefStore.updateClassificationDefByName(existing.getName(), existing); typeDefStore.updateClassificationDefByName(existing.getName(), existing);
result.incrementMeticsCounter("typedef:classification:update"); result.incrementMeticsCounter("typedef:classification:update");
} }
...@@ -68,9 +73,9 @@ public class TypeAttributeDifference { ...@@ -68,9 +73,9 @@ public class TypeAttributeDifference {
} }
private void updateEnumDef(AtlasTypesDef typeDefinitionMap, AtlasImportResult result) throws AtlasBaseException { private void updateEnumDef(AtlasTypesDef typeDefinitionMap, AtlasImportResult result) throws AtlasBaseException {
for (AtlasEnumDef def: typeDefinitionMap.getEnumDefs()) { for (AtlasEnumDef def : typeDefinitionMap.getEnumDefs()) {
AtlasEnumDef existing = typeRegistry.getEnumDefByName(def.getName()); AtlasEnumDef existing = typeRegistry.getEnumDefByName(def.getName());
if(existing != null && addElements(existing, def)) { if (existing != null && addElements(existing, def)) {
typeDefStore.updateEnumDefByName(existing.getName(), existing); typeDefStore.updateEnumDefByName(existing.getName(), existing);
result.incrementMeticsCounter("typedef:enum:update"); result.incrementMeticsCounter("typedef:enum:update");
} }
...@@ -78,45 +83,62 @@ public class TypeAttributeDifference { ...@@ -78,45 +83,62 @@ public class TypeAttributeDifference {
} }
private void updateStructDef(AtlasTypesDef typeDefinitionMap, AtlasImportResult result) throws AtlasBaseException { private void updateStructDef(AtlasTypesDef typeDefinitionMap, AtlasImportResult result) throws AtlasBaseException {
for (AtlasStructDef def: typeDefinitionMap.getStructDefs()) { for (AtlasStructDef def : typeDefinitionMap.getStructDefs()) {
AtlasStructDef existing = typeRegistry.getStructDefByName(def.getName()); AtlasStructDef existing = typeRegistry.getStructDefByName(def.getName());
if(existing != null && addAttributes(existing, def)) { if (existing != null && addAttributes(existing, def)) {
typeDefStore.updateStructDefByName(existing.getName(), existing); typeDefStore.updateStructDefByName(existing.getName(), existing);
result.incrementMeticsCounter("typedef:struct:update"); result.incrementMeticsCounter("typedef:struct:update");
} }
} }
} }
private boolean addElements(AtlasEnumDef existing, AtlasEnumDef incoming) { private boolean addElements(AtlasEnumDef existing, AtlasEnumDef incoming) throws AtlasBaseException {
return addElements(existing, getElementsAbsentInExisting(existing, incoming)); return addElements(existing, getElementsAbsentInExisting(existing, incoming));
} }
private boolean addAttributes(AtlasStructDef existing, AtlasStructDef incoming) { private boolean addAttributes(AtlasStructDef existing, AtlasStructDef incoming) throws AtlasBaseException {
return addAttributes(existing, getElementsAbsentInExisting(existing, incoming)); return addAttributes(existing, getElementsAbsentInExisting(existing, incoming));
} }
private List<AtlasStructDef.AtlasAttributeDef> getElementsAbsentInExisting(AtlasStructDef existing, AtlasStructDef incoming) { private List<AtlasStructDef.AtlasAttributeDef> getElementsAbsentInExisting(AtlasStructDef existing, AtlasStructDef incoming) throws AtlasBaseException {
List<AtlasStructDef.AtlasAttributeDef> difference = new ArrayList<>(); List<AtlasStructDef.AtlasAttributeDef> difference = new ArrayList<>();
for (AtlasStructDef.AtlasAttributeDef attr : incoming.getAttributeDefs()) { for (AtlasStructDef.AtlasAttributeDef attr : incoming.getAttributeDefs()) {
if(existing.getAttribute(attr.getName()) == null) { updateCollectionWithDifferingAttributes(difference, existing, attr);
difference.add(attr);
}
} }
return difference; return difference;
} }
private List<AtlasEnumDef.AtlasEnumElementDef> getElementsAbsentInExisting(AtlasEnumDef existing, AtlasEnumDef incoming) { private void updateCollectionWithDifferingAttributes(List<AtlasStructDef.AtlasAttributeDef> difference,
AtlasStructDef existing,
AtlasStructDef.AtlasAttributeDef incoming) throws AtlasBaseException {
AtlasStructDef.AtlasAttributeDef existingAttribute = existing.getAttribute(incoming.getName());
if (existingAttribute == null) {
difference.add(incoming);
} else {
if (!existingAttribute.getTypeName().equals(incoming.getTypeName())) {
LOG.error("Attribute definition difference found: {}, {}", existingAttribute, incoming);
throw new AtlasBaseException(AtlasErrorCode.INVALID_IMPORT_ATTRIBUTE_TYPE_CHANGED, existing.getName(), existingAttribute.getName(), existingAttribute.getTypeName(), incoming.getTypeName());
}
}
}
private List<AtlasEnumDef.AtlasEnumElementDef> getElementsAbsentInExisting(AtlasEnumDef existing, AtlasEnumDef incoming) throws AtlasBaseException {
List<AtlasEnumDef.AtlasEnumElementDef> difference = new ArrayList<>(); List<AtlasEnumDef.AtlasEnumElementDef> difference = new ArrayList<>();
for (AtlasEnumDef.AtlasEnumElementDef ed : incoming.getElementDefs()) { for (AtlasEnumDef.AtlasEnumElementDef ed : incoming.getElementDefs()) {
if(existing.getElement(ed.getValue()) == null) { updateCollectionWithDifferingAttributes(existing, difference, ed);
difference.add(ed);
}
} }
return difference; return difference;
} }
private void updateCollectionWithDifferingAttributes(AtlasEnumDef existing, List<AtlasEnumDef.AtlasEnumElementDef> difference, AtlasEnumDef.AtlasEnumElementDef ed) throws AtlasBaseException {
AtlasEnumDef.AtlasEnumElementDef existingElement = existing.getElement(ed.getValue());
if (existingElement == null) {
difference.add(ed);
}
}
private boolean addAttributes(AtlasStructDef def, List<AtlasStructDef.AtlasAttributeDef> list) { private boolean addAttributes(AtlasStructDef def, List<AtlasStructDef.AtlasAttributeDef> list) {
for (AtlasStructDef.AtlasAttributeDef ad : list) { for (AtlasStructDef.AtlasAttributeDef ad : list) {
def.addAttribute(ad); def.addAttribute(ad);
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
package org.apache.atlas.repository.impexp; package org.apache.atlas.repository.impexp;
import com.google.inject.Inject; import com.google.inject.Inject;
import org.apache.atlas.AtlasErrorCode;
import org.apache.atlas.RequestContextV1; import org.apache.atlas.RequestContextV1;
import org.apache.atlas.TestModules; import org.apache.atlas.TestModules;
import org.apache.atlas.TestUtilsV2; import org.apache.atlas.TestUtilsV2;
...@@ -25,6 +26,7 @@ import org.apache.atlas.exception.AtlasBaseException; ...@@ -25,6 +26,7 @@ import org.apache.atlas.exception.AtlasBaseException;
import org.apache.atlas.model.impexp.AtlasImportRequest; import org.apache.atlas.model.impexp.AtlasImportRequest;
import org.apache.atlas.repository.store.graph.AtlasEntityStore; import org.apache.atlas.repository.store.graph.AtlasEntityStore;
import org.apache.atlas.store.AtlasTypeDefStore; import org.apache.atlas.store.AtlasTypeDefStore;
import org.apache.atlas.type.AtlasClassificationType;
import org.apache.atlas.type.AtlasTypeRegistry; import org.apache.atlas.type.AtlasTypeRegistry;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -41,6 +43,7 @@ import java.util.Map; ...@@ -41,6 +43,7 @@ import java.util.Map;
import static org.apache.atlas.repository.impexp.ZipFileResourceTestUtils.*; import static org.apache.atlas.repository.impexp.ZipFileResourceTestUtils.*;
import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
@Guice(modules = TestModules.TestOnlyModule.class) @Guice(modules = TestModules.TestOnlyModule.class)
public class ImportServiceTest { public class ImportServiceTest {
...@@ -156,6 +159,29 @@ public class ImportServiceTest { ...@@ -156,6 +159,29 @@ public class ImportServiceTest {
runImportWithNoParameters(getImportService(), zipSource); runImportWithNoParameters(getImportService(), zipSource);
} }
@DataProvider(name = "hdfs_path1")
public static Object[][] getDataFromHdfsPath1(ITestContext context) throws IOException {
return getZipSource("hdfs_path1.zip");
}
@Test(dataProvider = "hdfs_path1", expectedExceptions = AtlasBaseException.class)
public void importHdfs_path1(ZipSource zipSource) throws IOException, AtlasBaseException {
loadModelFromJson("0000-Area0/0010-base_model.json", typeDefStore, typeRegistry);
loadModelFromJson("1000-Hadoop/1020-fs_model.json", typeDefStore, typeRegistry);
loadModelFromResourcesJson("tag1.json", typeDefStore, typeRegistry);
try {
runImportWithNoParameters(getImportService(), zipSource);
} catch (AtlasBaseException e) {
assertEquals(e.getAtlasErrorCode(), AtlasErrorCode.INVALID_IMPORT_ATTRIBUTE_TYPE_CHANGED);
AtlasClassificationType tag1 = typeRegistry.getClassificationTypeByName("tag1");
assertNotNull(tag1);
assertEquals(tag1.getAllAttributes().size(), 2);
throw e;
}
}
private ImportService getImportService() { private ImportService getImportService() {
return new ImportService(typeDefStore, entityStore, typeRegistry); return new ImportService(typeDefStore, entityStore, typeRegistry);
} }
......
...@@ -49,8 +49,7 @@ public class ZipFileResourceTestUtils { ...@@ -49,8 +49,7 @@ public class ZipFileResourceTestUtils {
public static final Logger LOG = LoggerFactory.getLogger(ZipFileResourceTestUtils.class); public static final Logger LOG = LoggerFactory.getLogger(ZipFileResourceTestUtils.class);
public static FileInputStream getFileInputStream(String fileName) { public static FileInputStream getFileInputStream(String fileName) {
final String userDir = System.getProperty("user.dir"); String filePath = getFileFromResources(fileName);
String filePath = getFilePath(userDir, fileName);
File f = new File(filePath); File f = new File(filePath);
FileInputStream fs = null; FileInputStream fs = null;
try { try {
...@@ -61,6 +60,11 @@ public class ZipFileResourceTestUtils { ...@@ -61,6 +60,11 @@ public class ZipFileResourceTestUtils {
return fs; return fs;
} }
private static String getFileFromResources(String fileName) {
final String userDir = System.getProperty("user.dir");
return getFilePath(userDir, fileName);
}
private static String getFilePath(String startPath, String fileName) { private static String getFilePath(String startPath, String fileName) {
return startPath + "/src/test/resources/" + fileName; return startPath + "/src/test/resources/" + fileName;
} }
...@@ -75,6 +79,15 @@ public class ZipFileResourceTestUtils { ...@@ -75,6 +79,15 @@ public class ZipFileResourceTestUtils {
return s; return s;
} }
public static String getModelJsonFromResources(String fileName) throws IOException {
String filePath = getFileFromResources(fileName);
File f = new File(filePath);
String s = FileUtils.readFileToString(f);
assertFalse(StringUtils.isEmpty(s), "Model file read correctly from resources!");
return s;
}
public static Object[][] getZipSource(String fileName) throws IOException { public static Object[][] getZipSource(String fileName) throws IOException {
FileInputStream fs = ZipFileResourceTestUtils.getFileInputStream(fileName); FileInputStream fs = ZipFileResourceTestUtils.getFileInputStream(fileName);
...@@ -119,6 +132,11 @@ public class ZipFileResourceTestUtils { ...@@ -119,6 +132,11 @@ public class ZipFileResourceTestUtils {
createTypesAsNeeded(typesFromJson, typeDefStore, typeRegistry); createTypesAsNeeded(typesFromJson, typeDefStore, typeRegistry);
} }
public static void loadModelFromResourcesJson(String fileName, AtlasTypeDefStore typeDefStore, AtlasTypeRegistry typeRegistry) throws IOException, AtlasBaseException {
AtlasTypesDef typesFromJson = getAtlasTypesDefFromResourceFile(fileName);
createTypesAsNeeded(typesFromJson, typeDefStore, typeRegistry);
}
private static void createTypesAsNeeded(AtlasTypesDef typesFromJson, AtlasTypeDefStore typeDefStore, AtlasTypeRegistry typeRegistry) throws AtlasBaseException { private static void createTypesAsNeeded(AtlasTypesDef typesFromJson, AtlasTypeDefStore typeDefStore, AtlasTypeRegistry typeRegistry) throws AtlasBaseException {
AtlasTypesDef typesToCreate = AtlasTypeDefStoreInitializer.getTypesToCreate(typesFromJson, typeRegistry); AtlasTypesDef typesToCreate = AtlasTypeDefStoreInitializer.getTypesToCreate(typesFromJson, typeRegistry);
...@@ -132,6 +150,11 @@ public class ZipFileResourceTestUtils { ...@@ -132,6 +150,11 @@ public class ZipFileResourceTestUtils {
return AtlasType.fromJson(sampleTypes, AtlasTypesDef.class); return AtlasType.fromJson(sampleTypes, AtlasTypesDef.class);
} }
private static AtlasTypesDef getAtlasTypesDefFromResourceFile(String fileName) throws IOException {
String sampleTypes = getModelJsonFromResources(fileName);
return AtlasType.fromJson(sampleTypes, AtlasTypesDef.class);
}
public static AtlasImportRequest getDefaultImportRequest() { public static AtlasImportRequest getDefaultImportRequest() {
return new AtlasImportRequest(); return new AtlasImportRequest();
} }
......
{
"enumDefs": [],
"structDefs": [],
"classificationDefs": [
{
"category": "CLASSIFICATION",
"guid": "03f95af3-3415-4522-8f4c-95fadcb59636",
"createdBy": "admin",
"updatedBy": "admin",
"createTime": 1504887556449,
"updateTime": 1504887556449,
"version": 1,
"name": "tag1",
"description": "tag1",
"typeVersion": "1.0",
"attributeDefs": [
{
"name": "attrib1",
"typeName": "date",
"isOptional": true,
"cardinality": "SINGLE",
"valuesMinCount": 0,
"valuesMaxCount": 1,
"isUnique": false,
"isIndexable": false
},
{
"name": "attrib3",
"typeName": "int",
"isOptional": true,
"cardinality": "SINGLE",
"valuesMinCount": 0,
"valuesMaxCount": 1,
"isUnique": false,
"isIndexable": false
}
],
"superTypes": []
}
],
"entityDefs": [],
"relationshipDefs": []
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment