Skip to content

Commit 2bea12a

Browse files
authored
Merge branch 'master' into remove-page-size
2 parents 7e525bc + ecf6c8c commit 2bea12a

File tree

58 files changed

+1490
-302
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+1490
-302
lines changed

.github/workflows/dagster-plugin.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ jobs:
3131
DATAHUB_TELEMETRY_ENABLED: false
3232
strategy:
3333
matrix:
34-
python-version: ["3.8", "3.10"]
34+
python-version: ["3.9", "3.10"]
3535
include:
36-
- python-version: "3.8"
36+
- python-version: "3.9"
3737
extraPythonRequirement: "dagster>=1.3.3"
3838
- python-version: "3.10"
3939
extraPythonRequirement: "dagster>=1.3.3"

build.gradle

+7-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ buildscript {
3434
// Releases: https://github.com/linkedin/rest.li/blob/master/CHANGELOG.md
3535
ext.pegasusVersion = '29.57.0'
3636
ext.mavenVersion = '3.6.3'
37+
ext.versionGradle = '8.11.1'
3738
ext.springVersion = '6.1.13'
3839
ext.springBootVersion = '3.2.9'
3940
ext.springKafkaVersion = '3.1.6'
@@ -78,7 +79,7 @@ buildscript {
7879

7980
plugins {
8081
id 'com.gorylenko.gradle-git-properties' version '2.4.1'
81-
id 'com.github.johnrengelman.shadow' version '8.1.1' apply false
82+
id 'com.gradleup.shadow' version '8.3.5' apply false
8283
id 'com.palantir.docker' version '0.35.0' apply false
8384
id 'com.avast.gradle.docker-compose' version '0.17.6'
8485
id "com.diffplug.spotless" version "6.23.3"
@@ -499,3 +500,8 @@ subprojects {
499500
}
500501
}
501502
}
503+
504+
wrapper {
505+
gradleVersion = project.versionGradle
506+
distributionType = Wrapper.DistributionType.ALL
507+
}

datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsGraphQLEngine.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -1318,7 +1318,8 @@ private void configureMutationResolvers(final RuntimeWiring.Builder builder) {
13181318
.dataFetcher("updateQuery", new UpdateQueryResolver(this.queryService))
13191319
.dataFetcher("deleteQuery", new DeleteQueryResolver(this.queryService))
13201320
.dataFetcher(
1321-
"createDataProduct", new CreateDataProductResolver(this.dataProductService))
1321+
"createDataProduct",
1322+
new CreateDataProductResolver(this.dataProductService, this.entityService))
13221323
.dataFetcher(
13231324
"updateDataProduct", new UpdateDataProductResolver(this.dataProductService))
13241325
.dataFetcher(

datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/dataproduct/CreateDataProductResolver.java

+6
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@
1010
import com.linkedin.datahub.graphql.exception.AuthorizationException;
1111
import com.linkedin.datahub.graphql.generated.CreateDataProductInput;
1212
import com.linkedin.datahub.graphql.generated.DataProduct;
13+
import com.linkedin.datahub.graphql.generated.OwnerEntityType;
14+
import com.linkedin.datahub.graphql.resolvers.mutate.util.OwnerUtils;
1315
import com.linkedin.datahub.graphql.types.dataproduct.mappers.DataProductMapper;
1416
import com.linkedin.entity.EntityResponse;
17+
import com.linkedin.metadata.entity.EntityService;
1518
import com.linkedin.metadata.service.DataProductService;
1619
import graphql.schema.DataFetcher;
1720
import graphql.schema.DataFetchingEnvironment;
@@ -24,6 +27,7 @@
2427
public class CreateDataProductResolver implements DataFetcher<CompletableFuture<DataProduct>> {
2528

2629
private final DataProductService _dataProductService;
30+
private final EntityService _entityService;
2731

2832
@Override
2933
public CompletableFuture<DataProduct> get(final DataFetchingEnvironment environment)
@@ -56,6 +60,8 @@ public CompletableFuture<DataProduct> get(final DataFetchingEnvironment environm
5660
context.getOperationContext(),
5761
dataProductUrn,
5862
UrnUtils.getUrn(input.getDomainUrn()));
63+
OwnerUtils.addCreatorAsOwner(
64+
context, dataProductUrn.toString(), OwnerEntityType.CORP_USER, _entityService);
5965
EntityResponse response =
6066
_dataProductService.getDataProductEntityResponse(
6167
context.getOperationContext(), dataProductUrn);

docker/profiles/docker-compose.gms.yml

-2
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@ x-datahub-gms-service: &datahub-gms-service
9999
- ${DATAHUB_LOCAL_GMS_ENV:-empty2.env}
100100
environment: &datahub-gms-env
101101
<<: [*primary-datastore-mysql-env, *graph-datastore-search-env, *search-datastore-env, *datahub-quickstart-telemetry-env, *kafka-env]
102-
ELASTICSEARCH_QUERY_CUSTOM_CONFIG_FILE: ${ELASTICSEARCH_QUERY_CUSTOM_CONFIG_FILE:-search_config.yaml}
103102
ALTERNATE_MCP_VALIDATION: ${ALTERNATE_MCP_VALIDATION:-true}
104103
STRICT_URN_VALIDATION_ENABLED: ${STRICT_URN_VALIDATION_ENABLED:-true}
105104
healthcheck:
@@ -126,7 +125,6 @@ x-datahub-gms-service-dev: &datahub-gms-service-dev
126125
- ${DATAHUB_LOCAL_GMS_ENV:-empty2.env}
127126
environment: &datahub-gms-dev-env
128127
<<: [*datahub-dev-telemetry-env, *datahub-gms-env]
129-
ELASTICSEARCH_QUERY_CUSTOM_CONFIG_FILE: ${ELASTICSEARCH_QUERY_CUSTOM_CONFIG_FILE:-search_config.yaml}
130128
SKIP_ELASTICSEARCH_CHECK: false
131129
JAVA_TOOL_OPTIONS: '-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5001'
132130
BOOTSTRAP_SYSTEM_UPDATE_WAIT_FOR_SYSTEM_UPDATE: false

docs/advanced/mcp-mcl.md

+3
Original file line numberDiff line numberDiff line change
@@ -218,3 +218,6 @@ Another form of conditional writes which considers the existence of an aspect or
218218

219219
`CREATE_ENTITY` - Create the aspect if no aspects exist for the entity.
220220

221+
By default, a validation exception is thrown if the `CREATE`/`CREATE_ENTITY` constraint is violated. If the write operation
222+
should be dropped without considering it an exception, then add the following header: `If-None-Match: *` to the MCP.
223+

docs/plugins.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,14 @@ The sample authenticator implementation can be found at [Authenticator Sample](.
6565
3. Use `getResourceAsStream` to read files: If your plugin read any configuration file like properties or YAML or JSON or xml then use `this.getClass().getClassLoader().getResourceAsStream("<file-name>")` to read that file from DataHub GMS plugin's class-path. For DataHub GMS resource look-up behavior please refer [Plugin Installation](#plugin-installation) section. Sample code of `getResourceAsStream` is available in sample Authenticator plugin [TestAuthenticator.java](../metadata-service/plugin/src/test/sample-test-plugins/src/main/java/com/datahub/plugins/test/TestAuthenticator.java).
6666

6767

68-
4. Bundle your Jar: Use `com.github.johnrengelman.shadow` gradle plugin to create an uber jar.
68+
4. Bundle your Jar: Use `com.gradleup.shadow` gradle plugin to create an uber jar.
6969

7070
To see an example of building an uber jar, check out the `build.gradle` file for the apache-ranger-plugin file of [Apache Ranger Plugin](https://github.com/acryldata/datahub-ranger-auth-plugin/tree/main/apache-ranger-plugin) for reference.
7171

7272
Exclude signature files as shown in below `shadowJar` task.
7373

7474
```groovy
75-
apply plugin: 'com.github.johnrengelman.shadow';
75+
apply plugin: 'com.gradleup.shadow';
7676
shadowJar {
7777
// Exclude com.datahub.plugins package and files related to jar signature
7878
exclude "META-INF/*.RSA", "META-INF/*.SF","META-INF/*.DSA"
@@ -152,14 +152,14 @@ The sample authorizer implementation can be found at [Authorizer Sample](https:/
152152

153153
3. Use `getResourceAsStream` to read files: If your plugin read any configuration file like properties or YAML or JSON or xml then use `this.getClass().getClassLoader().getResourceAsStream("<file-name>")` to read that file from DataHub GMS plugin's class-path. For DataHub GMS resource look-up behavior please refer [Plugin Installation](#plugin-installation) section. Sample code of `getResourceAsStream` is available in sample Authenticator plugin [TestAuthenticator.java](../metadata-service/plugin/src/test/sample-test-plugins/src/main/java/com/datahub/plugins/test/TestAuthenticator.java).
154154

155-
4. Bundle your Jar: Use `com.github.johnrengelman.shadow` gradle plugin to create an uber jar.
155+
4. Bundle your Jar: Use `com.gradleup.shadow` gradle plugin to create an uber jar.
156156

157157
To see an example of building an uber jar, check out the `build.gradle` file for the apache-ranger-plugin file of [Apache Ranger Plugin](https://github.com/acryldata/datahub-ranger-auth-plugin/tree/main/apache-ranger-plugin) for reference.
158158

159159
Exclude signature files as shown in below `shadowJar` task.
160160

161161
```groovy
162-
apply plugin: 'com.github.johnrengelman.shadow';
162+
apply plugin: 'com.gradleup.shadow';
163163
shadowJar {
164164
// Exclude com.datahub.plugins package and files related to jar signature
165165
exclude "META-INF/*.RSA", "META-INF/*.SF","META-INF/*.DSA"

entity-registry/src/main/java/com/linkedin/metadata/aspect/plugins/validation/AspectValidationException.java

+20-22
Original file line numberDiff line numberDiff line change
@@ -18,45 +18,39 @@ public static AspectValidationException forItem(BatchItem item, String msg) {
1818
}
1919

2020
public static AspectValidationException forItem(BatchItem item, String msg, Exception e) {
21-
return new AspectValidationException(
22-
item.getChangeType(), item.getUrn(), item.getAspectName(), msg, SubType.VALIDATION, e);
21+
return new AspectValidationException(item, msg, SubType.VALIDATION, e);
2322
}
2423

2524
public static AspectValidationException forPrecondition(BatchItem item, String msg) {
2625
return forPrecondition(item, msg, null);
2726
}
2827

28+
public static AspectValidationException forFilter(BatchItem item, String msg) {
29+
return new AspectValidationException(item, msg, SubType.FILTER);
30+
}
31+
2932
public static AspectValidationException forPrecondition(BatchItem item, String msg, Exception e) {
30-
return new AspectValidationException(
31-
item.getChangeType(), item.getUrn(), item.getAspectName(), msg, SubType.PRECONDITION, e);
33+
return new AspectValidationException(item, msg, SubType.PRECONDITION, e);
3234
}
3335

36+
@Nonnull BatchItem item;
3437
@Nonnull ChangeType changeType;
3538
@Nonnull Urn entityUrn;
3639
@Nonnull String aspectName;
3740
@Nonnull SubType subType;
3841
@Nullable String msg;
3942

40-
public AspectValidationException(
41-
@Nonnull ChangeType changeType,
42-
@Nonnull Urn entityUrn,
43-
@Nonnull String aspectName,
44-
String msg,
45-
SubType subType) {
46-
this(changeType, entityUrn, aspectName, msg, subType, null);
43+
public AspectValidationException(@Nonnull BatchItem item, String msg, SubType subType) {
44+
this(item, msg, subType, null);
4745
}
4846

4947
public AspectValidationException(
50-
@Nonnull ChangeType changeType,
51-
@Nonnull Urn entityUrn,
52-
@Nonnull String aspectName,
53-
@Nonnull String msg,
54-
@Nullable SubType subType,
55-
Exception e) {
48+
@Nonnull BatchItem item, @Nonnull String msg, @Nullable SubType subType, Exception e) {
5649
super(msg, e);
57-
this.changeType = changeType;
58-
this.entityUrn = entityUrn;
59-
this.aspectName = aspectName;
50+
this.item = item;
51+
this.changeType = item.getChangeType();
52+
this.entityUrn = item.getUrn();
53+
this.aspectName = item.getAspectName();
6054
this.msg = msg;
6155
this.subType = subType != null ? subType : SubType.VALIDATION;
6256
}
@@ -65,8 +59,12 @@ public Pair<Urn, String> getAspectGroup() {
6559
return Pair.of(entityUrn, aspectName);
6660
}
6761

68-
public static enum SubType {
62+
public enum SubType {
63+
// A validation exception is thrown
6964
VALIDATION,
70-
PRECONDITION
65+
// A failed precondition is thrown if the header constraints are not met
66+
PRECONDITION,
67+
// Exclude from processing further
68+
FILTER
7169
}
7270
}

entity-registry/src/main/java/com/linkedin/metadata/aspect/plugins/validation/ValidationExceptionCollection.java

+22-4
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,38 @@
1515
public class ValidationExceptionCollection
1616
extends HashMap<Pair<Urn, String>, Set<AspectValidationException>> {
1717

18+
private final Set<Integer> failedHashCodes;
19+
private final Set<Integer> filteredHashCodes;
20+
21+
public ValidationExceptionCollection() {
22+
super();
23+
this.failedHashCodes = new HashSet<>();
24+
this.filteredHashCodes = new HashSet<>();
25+
}
26+
27+
public boolean hasFatalExceptions() {
28+
return !failedHashCodes.isEmpty();
29+
}
30+
1831
public static ValidationExceptionCollection newCollection() {
1932
return new ValidationExceptionCollection();
2033
}
2134

2235
public void addException(AspectValidationException exception) {
2336
super.computeIfAbsent(exception.getAspectGroup(), key -> new HashSet<>()).add(exception);
37+
if (!AspectValidationException.SubType.FILTER.equals(exception.getSubType())) {
38+
failedHashCodes.add(exception.getItem().hashCode());
39+
} else {
40+
filteredHashCodes.add(exception.getItem().hashCode());
41+
}
2442
}
2543

2644
public void addException(BatchItem item, String message) {
2745
addException(item, message, null);
2846
}
2947

3048
public void addException(BatchItem item, String message, Exception ex) {
31-
super.computeIfAbsent(Pair.of(item.getUrn(), item.getAspectName()), key -> new HashSet<>())
32-
.add(AspectValidationException.forItem(item, message, ex));
49+
addException(AspectValidationException.forItem(item, message, ex));
3350
}
3451

3552
public Stream<AspectValidationException> streamAllExceptions() {
@@ -41,15 +58,16 @@ public <T extends BatchItem> Collection<T> successful(Collection<T> items) {
4158
}
4259

4360
public <T extends BatchItem> Stream<T> streamSuccessful(Stream<T> items) {
44-
return items.filter(i -> !this.containsKey(Pair.of(i.getUrn(), i.getAspectName())));
61+
return items.filter(
62+
i -> !failedHashCodes.contains(i.hashCode()) && !filteredHashCodes.contains(i.hashCode()));
4563
}
4664

4765
public <T extends BatchItem> Collection<T> exceptions(Collection<T> items) {
4866
return streamExceptions(items.stream()).collect(Collectors.toList());
4967
}
5068

5169
public <T extends BatchItem> Stream<T> streamExceptions(Stream<T> items) {
52-
return items.filter(i -> this.containsKey(Pair.of(i.getUrn(), i.getAspectName())));
70+
return items.filter(i -> failedHashCodes.contains(i.hashCode()));
5371
}
5472

5573
@Override

entity-registry/src/main/java/com/linkedin/metadata/aspect/validation/CreateIfNotExistsValidator.java

+27-7
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
@Getter
2626
@Accessors(chain = true)
2727
public class CreateIfNotExistsValidator extends AspectPayloadValidator {
28+
public static final String FILTER_EXCEPTION_HEADER = "If-None-Match";
29+
public static final String FILTER_EXCEPTION_VALUE = "*";
2830

2931
@Nonnull private AspectPluginConfig config;
3032

@@ -49,22 +51,34 @@ protected Stream<AspectValidationException> validatePreCommitAspects(
4951
.filter(item -> ChangeType.CREATE_ENTITY.equals(item.getChangeType()))
5052
.collect(Collectors.toSet())) {
5153
// if the key aspect is missing in the batch, the entity exists and CREATE_ENTITY should be
52-
// denied
54+
// denied or dropped
5355
if (!entityKeyMap.containsKey(createEntityItem.getUrn())) {
54-
exceptions.addException(
55-
createEntityItem,
56-
"Cannot perform CREATE_ENTITY if not exists since the entity key already exists.");
56+
if (isPrecondition(createEntityItem)) {
57+
exceptions.addException(
58+
AspectValidationException.forFilter(
59+
createEntityItem, "Dropping write per precondition header If-None-Match: *"));
60+
} else {
61+
exceptions.addException(
62+
createEntityItem,
63+
"Cannot perform CREATE_ENTITY if not exists since the entity key already exists.");
64+
}
5765
}
5866
}
5967

6068
for (ChangeMCP createItem :
6169
changeMCPs.stream()
6270
.filter(item -> ChangeType.CREATE.equals(item.getChangeType()))
6371
.collect(Collectors.toSet())) {
64-
// if a CREATE item has a previous value, should be denied
72+
// if a CREATE item has a previous value, should be denied or dropped
6573
if (createItem.getPreviousRecordTemplate() != null) {
66-
exceptions.addException(
67-
createItem, "Cannot perform CREATE since the aspect already exists.");
74+
if (isPrecondition(createItem)) {
75+
exceptions.addException(
76+
AspectValidationException.forFilter(
77+
createItem, "Dropping write per precondition header If-None-Match: *"));
78+
} else {
79+
exceptions.addException(
80+
createItem, "Cannot perform CREATE since the aspect already exists.");
81+
}
6882
}
6983
}
7084

@@ -77,4 +91,10 @@ protected Stream<AspectValidationException> validateProposedAspects(
7791
@Nonnull RetrieverContext retrieverContext) {
7892
return Stream.empty();
7993
}
94+
95+
private static boolean isPrecondition(ChangeMCP item) {
96+
return item.getHeader(FILTER_EXCEPTION_HEADER)
97+
.map(FILTER_EXCEPTION_VALUE::equals)
98+
.orElse(false);
99+
}
80100
}

entity-registry/src/testFixtures/java/com/linkedin/test/metadata/aspect/batch/TestMCP.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.linkedin.test.metadata.aspect.TestEntityRegistry;
2222
import java.net.URISyntaxException;
2323
import java.util.Collection;
24+
import java.util.Collections;
2425
import java.util.Map;
2526
import java.util.Objects;
2627
import java.util.Optional;
@@ -140,7 +141,7 @@ public Map<String, String> getHeaders() {
140141
mcp ->
141142
mcp.getHeaders().entrySet().stream()
142143
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)))
143-
.orElse(headers);
144+
.orElse(headers != null ? headers : Collections.emptyMap());
144145
}
145146

146147
@Override

gradle/wrapper/gradle-wrapper.jar

-17.6 KB
Binary file not shown.
+3-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
3+
distributionSha256Sum=89d4e70e4e84e2d2dfbb63e4daa53e21b25017cc70c37e4eea31ee51fb15098a
4+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
45
networkTimeout=10000
6+
validateDistributionUrl=true
57
zipStoreBase=GRADLE_USER_HOME
68
zipStorePath=wrapper/dists

0 commit comments

Comments
 (0)