Fixes #5332 - CustomProperties of an entity are deleted during restart (#5333)

This commit is contained in:
Suresh Srinivas 2022-06-07 07:47:34 -07:00 committed by GitHub
parent 581f63b157
commit b0d64d1684
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 76 additions and 29 deletions

View File

@ -21,6 +21,8 @@ import java.util.concurrent.ConcurrentHashMap;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.openmetadata.catalog.entity.Type; import org.openmetadata.catalog.entity.Type;
import org.openmetadata.catalog.entity.type.CustomProperty; import org.openmetadata.catalog.entity.type.CustomProperty;
import org.openmetadata.catalog.exception.CatalogExceptionMessage;
import org.openmetadata.catalog.exception.EntityNotFoundException;
import org.openmetadata.catalog.util.FullyQualifiedName; import org.openmetadata.catalog.util.FullyQualifiedName;
import org.openmetadata.catalog.util.JsonUtils; import org.openmetadata.catalog.util.JsonUtils;
@ -48,7 +50,6 @@ public class TypeRegistry {
public void addType(Type type) { public void addType(Type type) {
TYPES.put(type.getName(), type); TYPES.put(type.getName(), type);
LOG.info("Updated type {}\n", type.getName());
// Store custom properties added to a type // Store custom properties added to a type
for (CustomProperty property : type.getCustomProperties()) { for (CustomProperty property : type.getCustomProperties()) {
@ -58,18 +59,17 @@ public class TypeRegistry {
public void removeType(String typeName) { public void removeType(String typeName) {
TYPES.remove(typeName); TYPES.remove(typeName);
LOG.info("Deleted type {}\n", typeName); LOG.info("Deleted type {}", typeName);
// TODO cleanup custom properties // TODO cleanup custom properties
} }
private void addCustomProperty(String entityType, String propertyName, CustomProperty customProperty) { private void addCustomProperty(String entityType, String propertyName, CustomProperty customProperty) {
String customPropertyFQN = getCustomPropertyFQN(entityType, propertyName); String customPropertyFQN = getCustomPropertyFQN(entityType, propertyName);
CUSTOM_PROPERTIES.put(customPropertyFQN, customProperty); CUSTOM_PROPERTIES.put(customPropertyFQN, customProperty);
LOG.info("Adding custom property {}\n", customPropertyFQN);
JsonSchema jsonSchema = JsonUtils.getJsonSchema(TYPES.get(customProperty.getPropertyType().getName()).getSchema()); JsonSchema jsonSchema = JsonUtils.getJsonSchema(TYPES.get(customProperty.getPropertyType().getName()).getSchema());
CUSTOM_PROPERTY_SCHEMAS.put(customPropertyFQN, jsonSchema); CUSTOM_PROPERTY_SCHEMAS.put(customPropertyFQN, jsonSchema);
LOG.info("Adding json schema for {} {}\n", customProperty, jsonSchema); LOG.info("Adding custom property {} with JSON schema {}", customPropertyFQN, jsonSchema);
} }
public JsonSchema getSchema(String entityType, String propertyName) { public JsonSchema getSchema(String entityType, String propertyName) {
@ -81,9 +81,8 @@ public class TypeRegistry {
// Validate custom properties added to a type // Validate custom properties added to a type
for (CustomProperty property : listOrEmpty(type.getCustomProperties())) { for (CustomProperty property : listOrEmpty(type.getCustomProperties())) {
if (TYPES.get(property.getPropertyType().getName()) == null) { if (TYPES.get(property.getPropertyType().getName()) == null) {
throw new IllegalArgumentException( throw EntityNotFoundException.byMessage(
String.format( CatalogExceptionMessage.entityNotFound(Entity.TYPE, property.getPropertyType().getId()));
"Type %s not found for custom property %s", property.getPropertyType().getName(), property.getName()));
} }
} }
} }

View File

@ -87,11 +87,6 @@ public class TypeRepository extends EntityRepository<Type> {
TypeRegistry.instance().removeType(entity.getName()); TypeRegistry.instance().removeType(entity.getName());
} }
@Override
protected void postUpdate(Type entity) {
TypeRegistry.instance().addType(entity);
}
@Override @Override
public EntityUpdater getUpdater(Type original, Type updated, Operation operation) { public EntityUpdater getUpdater(Type original, Type updated, Operation operation) {
return new TypeUpdater(original, updated, operation); return new TypeUpdater(original, updated, operation);
@ -102,7 +97,7 @@ public class TypeRepository extends EntityRepository<Type> {
Type type = dao.findEntityById(UUID.fromString(id), Include.NON_DELETED); Type type = dao.findEntityById(UUID.fromString(id), Include.NON_DELETED);
property.setPropertyType(dao.findEntityReferenceById(property.getPropertyType().getId(), Include.NON_DELETED)); property.setPropertyType(dao.findEntityReferenceById(property.getPropertyType().getId(), Include.NON_DELETED));
if (type.getCategory().equals(Category.Field)) { if (type.getCategory().equals(Category.Field)) {
throw new IllegalArgumentException("Property types can't be extended"); throw new IllegalArgumentException("Only entity types can be extended and field types can't be extended");
} }
setFields(type, putFields); setFields(type, putFields);
@ -148,7 +143,7 @@ public class TypeRepository extends EntityRepository<Type> {
List<CustomProperty> origFields = listOrEmpty(original.getCustomProperties()); List<CustomProperty> origFields = listOrEmpty(original.getCustomProperties());
List<CustomProperty> added = new ArrayList<>(); List<CustomProperty> added = new ArrayList<>();
List<CustomProperty> deleted = new ArrayList<>(); List<CustomProperty> deleted = new ArrayList<>();
recordListChange("customProperty", origFields, updatedFields, added, deleted, EntityUtil.customFieldMatch); recordListChange("customProperties", origFields, updatedFields, added, deleted, EntityUtil.customFieldMatch);
for (CustomProperty property : added) { for (CustomProperty property : added) {
String customPropertyFQN = getCustomPropertyFQN(updated.getName(), property.getName()); String customPropertyFQN = getCustomPropertyFQN(updated.getName(), property.getName());
String customPropertyJson = JsonUtils.pojoToJson(property); String customPropertyJson = JsonUtils.pojoToJson(property);

View File

@ -56,6 +56,7 @@ import org.openmetadata.catalog.CatalogApplicationConfig;
import org.openmetadata.catalog.Entity; import org.openmetadata.catalog.Entity;
import org.openmetadata.catalog.api.CreateType; import org.openmetadata.catalog.api.CreateType;
import org.openmetadata.catalog.entity.Type; import org.openmetadata.catalog.entity.Type;
import org.openmetadata.catalog.entity.type.Category;
import org.openmetadata.catalog.entity.type.CustomProperty; import org.openmetadata.catalog.entity.type.CustomProperty;
import org.openmetadata.catalog.jdbi3.CollectionDAO; import org.openmetadata.catalog.jdbi3.CollectionDAO;
import org.openmetadata.catalog.jdbi3.ListFilter; import org.openmetadata.catalog.jdbi3.ListFilter;
@ -66,6 +67,7 @@ import org.openmetadata.catalog.security.Authorizer;
import org.openmetadata.catalog.security.SecurityUtil; import org.openmetadata.catalog.security.SecurityUtil;
import org.openmetadata.catalog.type.EntityHistory; import org.openmetadata.catalog.type.EntityHistory;
import org.openmetadata.catalog.type.Include; import org.openmetadata.catalog.type.Include;
import org.openmetadata.catalog.util.EntityUtil.Fields;
import org.openmetadata.catalog.util.JsonUtils; import org.openmetadata.catalog.util.JsonUtils;
import org.openmetadata.catalog.util.RestUtil.PutResponse; import org.openmetadata.catalog.util.RestUtil.PutResponse;
import org.openmetadata.catalog.util.ResultList; import org.openmetadata.catalog.util.ResultList;
@ -92,7 +94,7 @@ public class TypeResource extends EntityResource<Type, TypeRepository> {
@SuppressWarnings("unused") // Method used for reflection @SuppressWarnings("unused") // Method used for reflection
public void initialize(CatalogApplicationConfig config) throws IOException { public void initialize(CatalogApplicationConfig config) throws IOException {
// Find tag definitions and load tag categories from the json file, if necessary // Load types defined in OpenMetadata schemas
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
List<Type> types = JsonUtils.getTypes(); List<Type> types = JsonUtils.getTypes();
types.forEach( types.forEach(
@ -100,6 +102,17 @@ public class TypeResource extends EntityResource<Type, TypeRepository> {
type.withId(UUID.randomUUID()).withUpdatedBy("admin").withUpdatedAt(now); type.withId(UUID.randomUUID()).withUpdatedBy("admin").withUpdatedAt(now);
LOG.info("Loading type {}", type.getName()); LOG.info("Loading type {}", type.getName());
try { try {
Fields fields = getFields("customProperties");
try {
Type storedType = dao.getByName(null, type.getName(), fields);
type.setId(storedType.getId());
// If entity type already exists, then carry forward custom properties
if (storedType.getCategory().equals(Category.Entity)) {
type.setCustomProperties(storedType.getCustomProperties());
}
} catch (Exception e) {
LOG.debug("Creating entity that does not exist ", e);
}
this.dao.createOrUpdate(null, type); this.dao.createOrUpdate(null, type);
} catch (IOException e) { } catch (IOException e) {
LOG.error("Error loading type {}", type.getName(), e); LOG.error("Error loading type {}", type.getName(), e);

View File

@ -159,7 +159,7 @@ public abstract class EntityResourceTest<T extends EntityInterface, K extends Cr
protected boolean supportsFieldsQueryParam = true; protected boolean supportsFieldsQueryParam = true;
protected boolean supportsEmptyDescription = true; protected boolean supportsEmptyDescription = true;
protected boolean supportsNameWithDot = true; protected boolean supportsNameWithDot = true;
protected final boolean supportsCustomAttributes; protected final boolean supportsCustomExtension;
public static final String DATA_STEWARD_ROLE_NAME = "DataSteward"; public static final String DATA_STEWARD_ROLE_NAME = "DataSteward";
public static final String DATA_CONSUMER_ROLE_NAME = "DataConsumer"; public static final String DATA_CONSUMER_ROLE_NAME = "DataConsumer";
@ -248,7 +248,7 @@ public abstract class EntityResourceTest<T extends EntityInterface, K extends Cr
this.supportsOwner = allowedFields.contains(FIELD_OWNER); this.supportsOwner = allowedFields.contains(FIELD_OWNER);
this.supportsTags = allowedFields.contains(FIELD_TAGS); this.supportsTags = allowedFields.contains(FIELD_TAGS);
this.supportsSoftDelete = allowedFields.contains(FIELD_DELETED); this.supportsSoftDelete = allowedFields.contains(FIELD_DELETED);
this.supportsCustomAttributes = allowedFields.contains(FIELD_EXTENSION); this.supportsCustomExtension = allowedFields.contains(FIELD_EXTENSION);
} }
@BeforeAll @BeforeAll
@ -1130,38 +1130,55 @@ public abstract class EntityResourceTest<T extends EntityInterface, K extends Cr
} }
@Test @Test
void put_entityExtension(TestInfo test) throws IOException { void put_addEntityCustomAttributes(TestInfo test) throws IOException {
if (!supportsCustomAttributes) { if (!supportsCustomExtension) {
return; return;
} }
// Add valid custom fields to the entity // PUT valid custom field intA to the entity type
TypeResourceTest typeResourceTest = new TypeResourceTest(); TypeResourceTest typeResourceTest = new TypeResourceTest();
INT_TYPE = typeResourceTest.getEntityByName("integer", "", ADMIN_AUTH_HEADERS); INT_TYPE = typeResourceTest.getEntityByName("integer", "", ADMIN_AUTH_HEADERS);
STRING_TYPE = typeResourceTest.getEntityByName("string", "", ADMIN_AUTH_HEADERS); STRING_TYPE = typeResourceTest.getEntityByName("string", "", ADMIN_AUTH_HEADERS);
Type entity = typeResourceTest.getEntityByName(this.entityType, "customProperties", ADMIN_AUTH_HEADERS); Type entity = typeResourceTest.getEntityByName(this.entityType, "customProperties", ADMIN_AUTH_HEADERS);
CustomProperty fieldA = CustomProperty fieldA =
new CustomProperty().withName("intA").withDescription("intA").withPropertyType(INT_TYPE.getEntityReference()); new CustomProperty().withName("intA").withDescription("intA").withPropertyType(INT_TYPE.getEntityReference());
entity = typeResourceTest.addAndCheckCustomProperty(entity.getId(), fieldA, OK, ADMIN_AUTH_HEADERS);
final UUID id = entity.getId();
// PATCH valid custom field stringB
CustomProperty fieldB = CustomProperty fieldB =
new CustomProperty() new CustomProperty()
.withName("stringB") .withName("stringB")
.withDescription("stringB") .withDescription("stringB")
.withPropertyType(STRING_TYPE.getEntityReference()); .withPropertyType(STRING_TYPE.getEntityReference());
typeResourceTest.addCustomProperty(entity.getId(), fieldA, OK, ADMIN_AUTH_HEADERS); String json = JsonUtils.pojoToJson(entity);
typeResourceTest.addCustomProperty(entity.getId(), fieldB, OK, ADMIN_AUTH_HEADERS);
// Add invalid custom fields to the entity - custom field has invalid type ChangeDescription change = getChangeDescription(entity.getVersion());
Type INVALID_TYPE = change.getFieldsAdded().add(new FieldChange().withName("customProperties").withNewValue(Arrays.asList(fieldB)));
entity.getCustomProperties().add(fieldB);
entity = typeResourceTest.patchEntityAndCheck(entity, json, ADMIN_AUTH_HEADERS, MINOR_UPDATE, change);
// PUT invalid custom fields to the entity - custom field has invalid type
Type invalidType =
new Type().withId(UUID.randomUUID()).withName("invalid").withCategory(Category.Field).withSchema("{}"); new Type().withId(UUID.randomUUID()).withName("invalid").withCategory(Category.Field).withSchema("{}");
CustomProperty fieldInvalid = CustomProperty fieldInvalid =
new CustomProperty() new CustomProperty()
.withName("invalid") .withName("invalid")
.withDescription("invalid") .withDescription("invalid")
.withPropertyType(INVALID_TYPE.getEntityReference()); .withPropertyType(invalidType.getEntityReference());
assertResponse( assertResponse(
() -> typeResourceTest.addCustomProperty(entity.getId(), fieldInvalid, OK, ADMIN_AUTH_HEADERS), () -> typeResourceTest.addCustomProperty(id, fieldInvalid, OK, ADMIN_AUTH_HEADERS),
NOT_FOUND, NOT_FOUND,
CatalogExceptionMessage.entityNotFound(Entity.TYPE, INVALID_TYPE.getId())); CatalogExceptionMessage.entityNotFound(Entity.TYPE, invalidType.getId()));
// PATCH invalid custom fields to the entity - custom field has invalid type
String json1 = JsonUtils.pojoToJson(entity);
entity.getCustomProperties().add(fieldInvalid);
Type finalEntity = entity;
assertResponse(
() -> typeResourceTest.patchEntity(id, json1, finalEntity, ADMIN_AUTH_HEADERS),
NOT_FOUND,
CatalogExceptionMessage.entityNotFound(Entity.TYPE, invalidType.getId()));
// Now create an entity with custom field // Now create an entity with custom field
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();

View File

@ -22,6 +22,7 @@ import static org.openmetadata.common.utils.CommonUtil.listOrEmpty;
import java.io.IOException; import java.io.IOException;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import javax.ws.rs.client.WebTarget; import javax.ws.rs.client.WebTarget;
@ -42,6 +43,8 @@ import org.openmetadata.catalog.resources.EntityResourceTest;
import org.openmetadata.catalog.resources.types.TypeResource; import org.openmetadata.catalog.resources.types.TypeResource;
import org.openmetadata.catalog.resources.types.TypeResource.TypeList; import org.openmetadata.catalog.resources.types.TypeResource.TypeList;
import org.openmetadata.catalog.type.EntityReference; import org.openmetadata.catalog.type.EntityReference;
import org.openmetadata.catalog.util.EntityUtil;
import org.openmetadata.catalog.util.JsonUtils;
import org.openmetadata.catalog.util.TestUtils; import org.openmetadata.catalog.util.TestUtils;
@Slf4j @Slf4j
@ -112,7 +115,7 @@ public class TypeResourceTest extends EntityResourceTest<Type, CreateType> {
assertResponse( assertResponse(
() -> addCustomProperty(INT_TYPE.getId(), field, Status.CREATED, ADMIN_AUTH_HEADERS), () -> addCustomProperty(INT_TYPE.getId(), field, Status.CREATED, ADMIN_AUTH_HEADERS),
Status.BAD_REQUEST, Status.BAD_REQUEST,
"Property types can't be extended"); "Only entity types can be extended and field types can't be extended");
} }
@Override @Override
@ -125,6 +128,17 @@ public class TypeResourceTest extends EntityResourceTest<Type, CreateType> {
return type; return type;
} }
public Type addAndCheckCustomProperty(
UUID entityTypeId, CustomProperty customProperty, Status status, Map<String, String> authHeaders)
throws HttpResponseException {
Type updated = addCustomProperty(entityTypeId, customProperty, status, authHeaders);
assertTrue(updated.getCustomProperties().contains(customProperty));
Type get = getEntity(entityTypeId, "customProperties", authHeaders);
assertTrue(get.getCustomProperties().contains(customProperty));
return updated;
}
public Type addCustomProperty( public Type addCustomProperty(
UUID entityTypeId, CustomProperty customProperty, Status status, Map<String, String> authHeaders) UUID entityTypeId, CustomProperty customProperty, Status status, Map<String, String> authHeaders)
throws HttpResponseException { throws HttpResponseException {
@ -159,6 +173,15 @@ public class TypeResourceTest extends EntityResourceTest<Type, CreateType> {
if (expected == actual) { if (expected == actual) {
return; return;
} }
assertCommonFieldChange(fieldName, expected, actual); if (fieldName.equals("customProperties")) {
List<CustomProperty> expectedProperties = (List<CustomProperty>) expected;
List<CustomProperty> actualProperties = JsonUtils.readObjects(actual.toString(), CustomProperty.class);
assertEquals(expectedProperties.size(), actualProperties.size());
expectedProperties.sort(EntityUtil.compareCustomProperty);
actualProperties.sort(EntityUtil.compareCustomProperty);
assertEquals(expectedProperties, actualProperties);
} else {
assertCommonFieldChange(fieldName, expected, actual);
}
} }
} }