Github user ijokarumawak commented on a diff in the pull request: https://github.com/apache/nifi/pull/2335#discussion_r157217462 --- Diff: nifi-nar-bundles/nifi-atlas-bundle/nifi-atlas-reporting-task/src/main/java/org/apache/nifi/atlas/reporting/AtlasNiFiFlowLineage.java --- @@ -0,0 +1,714 @@ +/* + * 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.atlas.reporting; + +import com.sun.jersey.api.client.ClientResponse; +import org.apache.atlas.AtlasServiceException; +import org.apache.commons.lang3.StringUtils; +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.common.config.SslConfigs; +import org.apache.nifi.annotation.behavior.DynamicProperty; +import org.apache.nifi.annotation.behavior.RequiresInstanceClassLoading; +import org.apache.nifi.annotation.behavior.Stateful; +import org.apache.nifi.annotation.documentation.CapabilityDescription; +import org.apache.nifi.annotation.documentation.Tags; +import org.apache.nifi.annotation.lifecycle.OnScheduled; +import org.apache.nifi.annotation.lifecycle.OnUnscheduled; +import org.apache.nifi.atlas.NiFIAtlasHook; +import org.apache.nifi.atlas.NiFiAtlasClient; +import org.apache.nifi.atlas.NiFiFlow; +import org.apache.nifi.atlas.NiFiFlowAnalyzer; +import org.apache.nifi.atlas.provenance.AnalysisContext; +import org.apache.nifi.atlas.provenance.StandardAnalysisContext; +import org.apache.nifi.atlas.provenance.lineage.CompleteFlowPathLineage; +import org.apache.nifi.atlas.provenance.lineage.LineageStrategy; +import org.apache.nifi.atlas.provenance.lineage.SimpleFlowPathLineage; +import org.apache.nifi.atlas.resolver.ClusterResolver; +import org.apache.nifi.atlas.resolver.ClusterResolvers; +import org.apache.nifi.atlas.resolver.RegexClusterResolver; +import org.apache.nifi.atlas.security.AtlasAuthN; +import org.apache.nifi.atlas.security.Basic; +import org.apache.nifi.atlas.security.Kerberos; +import org.apache.nifi.components.AllowableValue; +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.PropertyValue; +import org.apache.nifi.components.ValidationContext; +import org.apache.nifi.components.ValidationResult; +import org.apache.nifi.components.state.Scope; +import org.apache.nifi.context.PropertyContext; +import org.apache.nifi.controller.ConfigurationContext; +import org.apache.nifi.controller.status.ProcessGroupStatus; +import org.apache.nifi.processor.exception.ProcessException; +import org.apache.nifi.processor.util.StandardValidators; +import org.apache.nifi.provenance.ProvenanceEventRecord; +import org.apache.nifi.provenance.ProvenanceRepository; +import org.apache.nifi.reporting.AbstractReportingTask; +import org.apache.nifi.reporting.EventAccess; +import org.apache.nifi.reporting.ReportingContext; +import org.apache.nifi.reporting.util.provenance.ProvenanceEventConsumer; +import org.apache.nifi.ssl.SSLContextService; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import static org.apache.commons.lang3.StringUtils.isEmpty; +import static org.apache.nifi.reporting.util.provenance.ProvenanceEventConsumer.PROVENANCE_BATCH_SIZE; +import static org.apache.nifi.reporting.util.provenance.ProvenanceEventConsumer.PROVENANCE_START_POSITION; + +@Tags({"atlas", "lineage"}) +@CapabilityDescription("Publishes NiFi flow data set level lineage to Apache Atlas." + + " By reporting flow information to Atlas, an end-to-end Process and DataSet lineage such as across NiFi environments and other systems" + + " connected by technologies, for example NiFi Site-to-Site, Kafka topic or Hive tables." + + " There are limitations and required configurations for both NiFi and Atlas. See 'Additional Details' for further description.") +@Stateful(scopes = Scope.LOCAL, description = "Stores the Reporting Task's last event Id so that on restart the task knows where it left off.") +@DynamicProperty(name = "hostnamePattern.<ClusterName>", value = "hostname Regex patterns", description = RegexClusterResolver.PATTERN_PROPERTY_PREFIX_DESC) +// In order for each reporting task instance to have its own static objects such as KafkaNotification. +@RequiresInstanceClassLoading +public class AtlasNiFiFlowLineage extends AbstractReportingTask { + + static final PropertyDescriptor ATLAS_URLS = new PropertyDescriptor.Builder() + .name("atlas-urls") + .displayName("Atlas URLs") + .description("Comma separated URL of Atlas Servers" + + " (e.g. http://atlas-server-hostname:21000 or https://atlas-server-hostname:21443)." + + " For accessing Atlas behind Knox gateway, specify Knox gateway URL" + + " (e.g. https://knox-hostname:8443/gateway/{topology-name}/atlas).") + .required(true) + .expressionLanguageSupported(true) + .addValidator(StandardValidators.NON_BLANK_VALIDATOR) + .build(); + + static final AllowableValue ATLAS_AUTHN_BASIC = new AllowableValue("basic", "Basic", "Use username and password."); + static final AllowableValue ATLAS_AUTHN_KERBEROS = new AllowableValue("kerberos", "Kerberos", "Use Kerberos keytab file."); + static final PropertyDescriptor ATLAS_AUTHN_METHOD = new PropertyDescriptor.Builder() + .name("atlas-authentication-method") + .displayName("Atlas Authentication Method") + .description("Specify how to authenticate this reporting task to Atlas server.") + .required(true) + .allowableValues(ATLAS_AUTHN_BASIC, ATLAS_AUTHN_KERBEROS) + .defaultValue(ATLAS_AUTHN_BASIC.getValue()) + .build(); + + public static final PropertyDescriptor ATLAS_USER = new PropertyDescriptor.Builder() + .name("atlas-username") + .displayName("Atlas Username") + .description("User name to communicate with Atlas.") + .required(false) + .expressionLanguageSupported(true) + .addValidator(StandardValidators.NON_BLANK_VALIDATOR) + .build(); + + public static final PropertyDescriptor ATLAS_PASSWORD = new PropertyDescriptor.Builder() + .name("atlas-password") + .displayName("Atlas Password") + .description("Password to communicate with Atlas.") + .required(false) + .sensitive(true) + .expressionLanguageSupported(true) + .addValidator(StandardValidators.NON_BLANK_VALIDATOR) + .build(); + + static final PropertyDescriptor ATLAS_CONF_DIR = new PropertyDescriptor.Builder() + .name("atlas-conf-dir") + .displayName("Atlas Configuration Directory") + .description("Directory path that contains 'atlas-application.properties' file." + + " If not specified and 'Create Atlas Configuration File' is disabled," + + " then, 'atlas-application.properties' file under root classpath is used.") + .required(false) + .expressionLanguageSupported(true) + .addValidator(StandardValidators.NON_BLANK_VALIDATOR) + .build(); + + public static final PropertyDescriptor ATLAS_NIFI_URL = new PropertyDescriptor.Builder() + .name("atlas-nifi-url") + .displayName("NiFi URL for Atlas") + .description("NiFi URL is used in Atlas to represent this NiFi cluster (or standalone instance)." + + " It is recommended to use one that can be accessible remotely instead of using 'localhost'.") + .required(true) + .expressionLanguageSupported(true) + .addValidator(StandardValidators.URL_VALIDATOR) + .build(); + + public static final PropertyDescriptor ATLAS_DEFAULT_CLUSTER_NAME = new PropertyDescriptor.Builder() + .name("atlas-default-cluster-name") + .displayName("Atlas Default Cluster Name") + .description("Cluster name for Atlas entities reported by this ReportingTask." + + " If not specified, 'atlas.cluster.name' in Atlas Configuration File is used." + + " Cluster name mappings can be configured by user defined properties." + + " See additional detail for detail.") + .required(false) + .expressionLanguageSupported(true) + .addValidator(StandardValidators.NON_BLANK_VALIDATOR) + .build(); + + static final PropertyDescriptor ATLAS_CONF_CREATE = new PropertyDescriptor.Builder() + .name("atlas-conf-create") + .displayName("Create Atlas Configuration File") + .description("If enabled, 'atlas-application.properties' file will be created in 'Atlas Configuration Directory'" + + " automatically when this processor starts." + + " Note that the existing configuration file will be overwritten.") + .required(true) + .expressionLanguageSupported(false) + .allowableValues("true", "false") + .defaultValue("false") + .build(); + + static final PropertyDescriptor SSL_CONTEXT_SERVICE = new PropertyDescriptor.Builder() + .name("ssl-context-service") + .displayName("SSL Context Service") + .description("Specifies the SSL Context Service to use for communicating with Atlas and Kafka.") + .required(false) + .identifiesControllerService(SSLContextService.class) + .build(); + + static final PropertyDescriptor KAFKA_BOOTSTRAP_SERVERS = new PropertyDescriptor.Builder() + .name("kafka-bootstrap-servers") + .displayName("Kafka Bootstrap Servers") + .description("Kafka Bootstrap Servers to send Atlas hook notification messages based on NiFi provenance events." + + " E.g. 'localhost:9092'" + + " NOTE: Once this reporting task has started, restarting NiFi is required to changed this property" + + " as Atlas library holds a unmodifiable static reference to Kafka client.") + .required(false) + .expressionLanguageSupported(true) + .addValidator(StandardValidators.NON_BLANK_VALIDATOR) + .build(); + + static final AllowableValue SEC_PLAINTEXT = new AllowableValue("PLAINTEXT", "PLAINTEXT", "PLAINTEXT"); + static final AllowableValue SEC_SSL = new AllowableValue("SSL", "SSL", "SSL"); + static final AllowableValue SEC_SASL_PLAINTEXT = new AllowableValue("SASL_PLAINTEXT", "SASL_PLAINTEXT", "SASL_PLAINTEXT"); + static final AllowableValue SEC_SASL_SSL = new AllowableValue("SASL_SSL", "SASL_SSL", "SASL_SSL"); + static final PropertyDescriptor KAFKA_SECURITY_PROTOCOL = new PropertyDescriptor.Builder() + .name("kafka-security-protocol") + .displayName("Kafka Security Protocol") + .description("Protocol used to communicate with Kafka brokers to send Atlas hook notification messages." + + " Corresponds to Kafka's 'security.protocol' property.") + .required(true) + .expressionLanguageSupported(false) + .allowableValues(SEC_PLAINTEXT, SEC_SSL, SEC_SASL_PLAINTEXT, SEC_SASL_SSL) + .defaultValue(SEC_PLAINTEXT.getValue()) + .build(); + + public static final PropertyDescriptor NIFI_KERBEROS_PRINCIPAL = new PropertyDescriptor.Builder() + .name("nifi-kerberos-principal") + .displayName("NiFi Kerberos Principal") + .description("The Kerberos principal for this NiFi instance to access Atlas API and Kafka brokers." + + " If not set, it is expected to set a JAAS configuration file in the JVM properties defined in the bootstrap.conf file." + + " This principal will be set into 'sasl.jaas.config' Kafka's property.") + .required(false) + .addValidator(StandardValidators.NON_BLANK_VALIDATOR) + .expressionLanguageSupported(true) + .build(); + public static final PropertyDescriptor NIFI_KERBEROS_KEYTAB = new PropertyDescriptor.Builder() + .name("nifi-kerberos-keytab") + .displayName("NiFi Kerberos Keytab") + .description("The Kerberos keytab for this NiFi instance to access Atlas API and Kafka brokers." + + " If not set, it is expected to set a JAAS configuration file in the JVM properties defined in the bootstrap.conf file." + + " This principal will be set into 'sasl.jaas.config' Kafka's property.") + .required(false) + .addValidator(StandardValidators.FILE_EXISTS_VALIDATOR) + .expressionLanguageSupported(true) + .build(); + + static final PropertyDescriptor KAFKA_KERBEROS_SERVICE_NAME = new PropertyDescriptor.Builder() + .name("kafka-kerberos-service-name-kafka") + .displayName("Kafka Kerberos Service Name") + .description("The Kerberos principal name that Kafka runs for Atlas notification." + + " This can be defined either in Kafka's JAAS config or in Kafka's config." + + " Corresponds to Kafka's 'security.protocol' property." + + " It is ignored unless one of the SASL options of the <Security Protocol> are selected.") + .required(false) + .addValidator(StandardValidators.NON_BLANK_VALIDATOR) + .expressionLanguageSupported(true) + .defaultValue("kafka") + .build(); + + static final AllowableValue LINEAGE_STRATEGY_SIMPLE_PATH = new AllowableValue("SimplePath", "Simple Path", + "Map NiFi provenance events and target Atlas DataSets to statically created 'nifi_flow_path' Atlas Processes." + + " See also 'Additional Details'."); + static final AllowableValue LINEAGE_STRATEGY_COMPLETE_PATH = new AllowableValue("CompletePath", "Complete Path", + "Create separate 'nifi_flow_path' Atlas Processes for each distinct input and output DataSet combinations" + + " by looking at the complete route for a given FlowFile. See also 'Additional Details."); + + static final PropertyDescriptor NIFI_LINEAGE_STRATEGY = new PropertyDescriptor.Builder() + .name("nifi-lineage-strategy") + .displayName("NiFi Lineage Strategy") + .description("Specifies granularity on how NiFi data flow should be reported to Atlas.") + .required(true) + .allowableValues(LINEAGE_STRATEGY_SIMPLE_PATH, LINEAGE_STRATEGY_COMPLETE_PATH) + .defaultValue(LINEAGE_STRATEGY_SIMPLE_PATH.getValue()) + .build(); + + private static final String ATLAS_PROPERTIES_FILENAME = "atlas-application.properties"; + private static final String ATLAS_PROPERTY_CLUSTER_NAME = "atlas.cluster.name"; + private static final String ATLAS_PROPERTY_ENABLE_TLS = "atlas.enableTLS"; + private static final String ATLAS_KAFKA_PREFIX = "atlas.kafka."; + private static final String ATLAS_PROPERTY_KAFKA_BOOTSTRAP_SERVERS = ATLAS_KAFKA_PREFIX + "bootstrap.servers"; + private static final String ATLAS_PROPERTY_KAFKA_CLIENT_ID = ATLAS_KAFKA_PREFIX + ProducerConfig.CLIENT_ID_CONFIG; + private final ServiceLoader<ClusterResolver> clusterResolverLoader = ServiceLoader.load(ClusterResolver.class); + private volatile NiFiAtlasClient atlasClient; + private volatile Properties atlasProperties; + private volatile boolean isTypeDefCreated = false; + private volatile String defaultClusterName; + + private volatile ProvenanceEventConsumer consumer; + private volatile ClusterResolvers clusterResolvers; + private volatile NiFIAtlasHook nifiAtlasHook; + private volatile LineageStrategy lineageStrategy; + + @Override + protected List<PropertyDescriptor> getSupportedPropertyDescriptors() { + final List<PropertyDescriptor> properties = new ArrayList<>(); + properties.add(ATLAS_URLS); + properties.add(ATLAS_AUTHN_METHOD); + properties.add(ATLAS_USER); + properties.add(ATLAS_PASSWORD); + properties.add(ATLAS_CONF_DIR); + properties.add(ATLAS_NIFI_URL); + properties.add(ATLAS_DEFAULT_CLUSTER_NAME); + properties.add(NIFI_LINEAGE_STRATEGY); + properties.add(PROVENANCE_START_POSITION); + properties.add(PROVENANCE_BATCH_SIZE); + properties.add(SSL_CONTEXT_SERVICE); + + // Following properties are required if ATLAS_CONF_CREATE is enabled. + // Otherwise should be left blank. + properties.add(ATLAS_CONF_CREATE); + properties.add(NIFI_KERBEROS_PRINCIPAL); + properties.add(NIFI_KERBEROS_KEYTAB); + properties.add(KAFKA_KERBEROS_SERVICE_NAME); + properties.add(KAFKA_BOOTSTRAP_SERVERS); + properties.add(KAFKA_SECURITY_PROTOCOL); + + return properties; + } + + @Override + protected PropertyDescriptor getSupportedDynamicPropertyDescriptor(String propertyDescriptorName) { + for (ClusterResolver resolver : clusterResolverLoader) { + final PropertyDescriptor propertyDescriptor = resolver.getSupportedDynamicPropertyDescriptor(propertyDescriptorName); + if(propertyDescriptor != null) { + return propertyDescriptor; + } + } + return null; + } + + private void parseAtlasUrls(final PropertyValue atlasUrlsProp, final Consumer<String> urlStrConsumer) { + final String atlasUrlsStr = atlasUrlsProp.evaluateAttributeExpressions().getValue(); + if (atlasUrlsStr != null && !atlasUrlsStr.isEmpty()) { + Arrays.stream(atlasUrlsStr.split(",")) + .map(String::trim) + .forEach(urlStrConsumer); + } + } + + @Override + protected Collection<ValidationResult> customValidate(ValidationContext context) { + final Collection<ValidationResult> results = new ArrayList<>(); + + final boolean isSSLContextServiceSet = context.getProperty(SSL_CONTEXT_SERVICE).isSet(); + final ValidationResult.Builder invalidSSLService = new ValidationResult.Builder() + .subject(SSL_CONTEXT_SERVICE.getDisplayName()).valid(false); + parseAtlasUrls(context.getProperty(ATLAS_URLS), input -> { + final ValidationResult.Builder builder = new ValidationResult.Builder().subject(ATLAS_URLS.getDisplayName()).input(input); + try { + final URL url = new URL(input); + if ("https".equalsIgnoreCase(url.getProtocol()) && !isSSLContextServiceSet) { + results.add(invalidSSLService.explanation("required by HTTPS Atlas access").build()); + } else { + results.add(builder.explanation("Valid URI").valid(true).build()); + } + } catch (Exception e) { + results.add(builder.explanation("Contains invalid URI: " + e).valid(false).build()); + } + }); + + final String atlasAuthNMethod = context.getProperty(ATLAS_AUTHN_METHOD).getValue(); + final AtlasAuthN atlasAuthN = getAtlasAuthN(atlasAuthNMethod); + results.addAll(atlasAuthN.validate(context)); + + + clusterResolverLoader.forEach(resolver -> results.addAll(resolver.validate(context))); + + if (context.getProperty(ATLAS_CONF_CREATE).asBoolean()) { + + Stream.of(ATLAS_CONF_DIR, ATLAS_DEFAULT_CLUSTER_NAME, KAFKA_BOOTSTRAP_SERVERS) + .filter(p -> !context.getProperty(p).isSet()) + .forEach(p -> results.add(new ValidationResult.Builder() + .subject(p.getDisplayName()) + .explanation("required to create Atlas configuration file.") + .valid(false).build())); + + validateKafkaProperties(context, results, isSSLContextServiceSet, invalidSSLService); + } + + return results; + } + + private void validateKafkaProperties(ValidationContext context, Collection<ValidationResult> results, boolean isSSLContextServiceSet, ValidationResult.Builder invalidSSLService) { + final String kafkaSecurityProtocol = context.getProperty(KAFKA_SECURITY_PROTOCOL).getValue(); + if ((SEC_SSL.equals(kafkaSecurityProtocol) || SEC_SASL_SSL.equals(kafkaSecurityProtocol)) + && !isSSLContextServiceSet) { + results.add(invalidSSLService.explanation("required by SSL Kafka connection").build()); + } + + if (SEC_SASL_PLAINTEXT.equals(kafkaSecurityProtocol) || SEC_SASL_SSL.equals(kafkaSecurityProtocol)) { + Stream.of(NIFI_KERBEROS_PRINCIPAL, NIFI_KERBEROS_KEYTAB, KAFKA_KERBEROS_SERVICE_NAME) + .filter(p -> !context.getProperty(p).isSet()) + .forEach(p -> results.add(new ValidationResult.Builder() + .subject(p.getDisplayName()) + .explanation("required by Kafka SASL authentication.") + .valid(false).build())); + } + } + + @OnScheduled + public void setup(ConfigurationContext context) throws IOException { + // initAtlasClient has to be done first as it loads AtlasProperty. + initAtlasClient(context); + initLineageStrategy(context); + initClusterResolvers(context); + } + + private void initLineageStrategy(ConfigurationContext context) throws IOException { + nifiAtlasHook = new NiFIAtlasHook(atlasClient); + + final String strategy = context.getProperty(NIFI_LINEAGE_STRATEGY).getValue(); + if (LINEAGE_STRATEGY_SIMPLE_PATH.equals(strategy)) { + lineageStrategy = new SimpleFlowPathLineage(); + } else if (LINEAGE_STRATEGY_COMPLETE_PATH.equals(strategy)) { + lineageStrategy = new CompleteFlowPathLineage(); + } + + lineageStrategy.setLineageContext(nifiAtlasHook); + initProvenanceConsumer(context); + } + + private void initClusterResolvers(ConfigurationContext context) { + final Set<ClusterResolver> loadedClusterResolvers = new LinkedHashSet<>(); + clusterResolverLoader.forEach(resolver -> { + resolver.configure(context); + loadedClusterResolvers.add(resolver); + }); + clusterResolvers = new ClusterResolvers(Collections.unmodifiableSet(loadedClusterResolvers), defaultClusterName); + } + + + private void initAtlasClient(ConfigurationContext context) throws IOException { + List<String> urls = new ArrayList<>(); + parseAtlasUrls(context.getProperty(ATLAS_URLS), urls::add); + final boolean isAtlasApiSecure = urls.stream().anyMatch(url -> url.toLowerCase().startsWith("https")); + final String atlasAuthNMethod = context.getProperty(ATLAS_AUTHN_METHOD).getValue(); + + final String confDirStr = context.getProperty(ATLAS_CONF_DIR).evaluateAttributeExpressions().getValue(); + final File confDir = confDirStr != null && !confDirStr.isEmpty() ? new File(confDirStr) : null; + + atlasProperties = new Properties(); + final File atlasPropertiesFile = new File(confDir, ATLAS_PROPERTIES_FILENAME); + + final Boolean createAtlasConf = context.getProperty(ATLAS_CONF_CREATE).asBoolean(); + if (!createAtlasConf) { + // Load existing properties file. + if (atlasPropertiesFile.isFile()) { + getLogger().info("Loading {}", new Object[]{atlasPropertiesFile}); + try (InputStream in = new FileInputStream(atlasPropertiesFile)) { + atlasProperties.load(in); + } + } else { + final String fileInClasspath = "/" + ATLAS_PROPERTIES_FILENAME; + try (InputStream in = AtlasNiFiFlowLineage.class.getResourceAsStream(fileInClasspath)) { + getLogger().info("Loading {} from classpath", new Object[]{fileInClasspath}); + if (in == null) { + throw new ProcessException(String.format("Could not find %s in classpath." + + " Please add it to classpath," + + " or specify %s a directory containing Atlas properties file," + + " or enable %s to generate it.", + fileInClasspath, ATLAS_CONF_DIR.getDisplayName(), ATLAS_CONF_CREATE.getDisplayName())); + } + atlasProperties.load(in); + } + } + } + + // Resolve default cluster name. + defaultClusterName = context.getProperty(ATLAS_DEFAULT_CLUSTER_NAME).evaluateAttributeExpressions().getValue(); + if (defaultClusterName == null || defaultClusterName.isEmpty()) { + // If default cluster name is not specified by processor configuration, then load it from Atlas config. + defaultClusterName = atlasProperties.getProperty(ATLAS_PROPERTY_CLUSTER_NAME); + } + + // If default cluster name is still not defined, processor should not be able to start. + if (defaultClusterName == null || defaultClusterName.isEmpty()) { + throw new ProcessException("Default cluster name is not defined."); + } + + final AtlasAuthN atlasAuthN = getAtlasAuthN(atlasAuthNMethod); + atlasAuthN.configure(context); + + // Create Atlas configuration file if necessary. + if (createAtlasConf) { + + atlasProperties.put(ATLAS_PROPERTY_CLUSTER_NAME, defaultClusterName); + atlasProperties.put(ATLAS_PROPERTY_ENABLE_TLS, String.valueOf(isAtlasApiSecure)); + + setKafkaConfig(atlasProperties, context); + + atlasAuthN.populateProperties(atlasProperties); + + try (FileOutputStream fos = new FileOutputStream(atlasPropertiesFile)) { + String ts = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX") + .withZone(ZoneOffset.UTC) + .format(Instant.now()); + atlasProperties.store(fos, "Generated by Apache NiFi AtlasNiFiFlowLineage ReportingTask at " + ts); + } + } + + + atlasClient = NiFiAtlasClient.getInstance(); + try { + atlasClient.initialize(urls.toArray(new String[]{}), atlasAuthN, confDir); + } catch (final NullPointerException e) { --- End diff -- Yeah, this is odd to me, too. However, AtlasClient throws NullPointerException when it can not find atlas properties file with following stack trace. Probably we should report this to Atlas project, too. Thanks. ``` 2017-12-15 23:46:36,607 ERROR [StandardProcessScheduler Thread-4] o.a.n.c.s.StandardProcessScheduler Failed to invoke the On-Scheduled Lifecycle methods of [AtlasNiFiFlowLineage[id=5a7835aa-0160-1000-974d-14d95e0c38e9], java.lang.reflect.InvocationTargetException, 30 sec] due to {}; administratively yielding this ReportingTask and will attempt to schedule it again after {} java.lang.reflect.InvocationTargetException: null at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.apache.nifi.util.ReflectionUtils.invokeMethodsWithAnnotations(ReflectionUtils.java:137) at org.apache.nifi.util.ReflectionUtils.invokeMethodsWithAnnotations(ReflectionUtils.java:125) at org.apache.nifi.util.ReflectionUtils.invokeMethodsWithAnnotations(ReflectionUtils.java:70) at org.apache.nifi.util.ReflectionUtils.invokeMethodsWithAnnotation(ReflectionUtils.java:47) at org.apache.nifi.controller.scheduling.StandardProcessScheduler$2.run(StandardProcessScheduler.java:210) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745) Caused by: org.apache.nifi.processor.exception.ProcessException: Failed to initialize Atlas client due to java.lang.NullPointerException. Make sure 'atlas-application.properties' is in the directory specified with Atlas Configuration Directory or under root classpath if not specified. at org.apache.nifi.atlas.reporting.AtlasNiFiFlowLineage.initAtlasClient(AtlasNiFiFlowLineage.java:505) at org.apache.nifi.atlas.reporting.AtlasNiFiFlowLineage.setup(AtlasNiFiFlowLineage.java:402) ... 16 common frames omitted Caused by: java.lang.NullPointerException: null at org.apache.atlas.AtlasBaseClient.getClient(AtlasBaseClient.java:171) at org.apache.atlas.AtlasBaseClient.initializeState(AtlasBaseClient.java:154) at org.apache.atlas.AtlasBaseClient.initializeState(AtlasBaseClient.java:149) at org.apache.atlas.AtlasBaseClient.<init>(AtlasBaseClient.java:96) at org.apache.atlas.AtlasClientV2.<init>(AtlasClientV2.java:115) at org.apache.nifi.atlas.security.Basic.createClient(Basic.java:62) at org.apache.nifi.atlas.NiFiAtlasClient.initialize(NiFiAtlasClient.java:119) at org.apache.nifi.atlas.reporting.AtlasNiFiFlowLineage.initAtlasClient(AtlasNiFiFlowLineage.java:503) ... 17 common frames omitted ```
---