use of org.apache.pulsar.functions.LocalRunner in project pulsar by apache.
the class PulsarFunctionLocalRunTest method testExitOnError.
@Test(timeOut = 20000)
public void testExitOnError() throws Throwable {
final String namespacePortion = "io";
final String replNamespace = tenant + "/" + namespacePortion;
final String sourceTopic = "persistent://" + replNamespace + "/input";
final String sinkName = "PulsarSink-test";
final String propertyKey = "key";
final String propertyValue = "value";
final String subscriptionName = "test-sub";
admin.namespaces().createNamespace(replNamespace);
Set<String> clusters = Sets.newHashSet(Lists.newArrayList("local"));
admin.namespaces().setNamespaceReplicationClusters(replNamespace, clusters);
// create a producer that creates a topic at broker
Producer<String> producer = pulsarClient.newProducer(Schema.STRING).topic(sourceTopic).create();
SinkConfig sinkConfig = createSinkConfig(tenant, namespacePortion, sinkName, sourceTopic, subscriptionName);
sinkConfig.setInputSpecs(Collections.singletonMap(sourceTopic, ConsumerConfig.builder().receiverQueueSize(1000).build()));
sinkConfig.setClassName(TestErrorSink.class.getName());
int metricsPort = FunctionCommon.findAvailablePort();
LocalRunner.LocalRunnerBuilder localRunnerBuilder = LocalRunner.builder().clientAuthPlugin(AuthenticationTls.class.getName()).clientAuthParams(String.format("tlsCertFile:%s,tlsKeyFile:%s", TLS_CLIENT_CERT_FILE_PATH, TLS_CLIENT_KEY_FILE_PATH)).useTls(true).tlsTrustCertFilePath(TLS_TRUST_CERT_FILE_PATH).tlsAllowInsecureConnection(true).tlsHostNameVerificationEnabled(false).brokerServiceUrl(pulsar.getBrokerServiceUrlTls()).connectorsDirectory(workerConfig.getConnectorsDirectory()).metricsPortStart(metricsPort).exitOnError(true);
sinkConfig.getConfigs().put("throwErrorOpen", true);
localRunnerBuilder.sinkConfig(sinkConfig);
LocalRunner localRunner = localRunnerBuilder.build();
localRunner.start(true);
sinkConfig.getConfigs().put("throwErrorWrite", true);
localRunnerBuilder.sinkConfig(sinkConfig);
localRunner = localRunnerBuilder.build();
localRunner.start(true);
}
use of org.apache.pulsar.functions.LocalRunner in project pulsar by apache.
the class PulsarFunctionLocalRunTest method testPulsarSourceLocalRun.
private void testPulsarSourceLocalRun(String jarFilePathUrl, int parallelism) throws Exception {
final String namespacePortion = "io";
final String replNamespace = tenant + "/" + namespacePortion;
final String sinkTopic = "persistent://" + replNamespace + "/output";
final String sourceName = "PulsarSource-test";
admin.namespaces().createNamespace(replNamespace);
Set<String> clusters = Sets.newHashSet(Lists.newArrayList(CLUSTER));
admin.namespaces().setNamespaceReplicationClusters(replNamespace, clusters);
SourceConfig sourceConfig = createSourceConfig(tenant, namespacePortion, sourceName, sinkTopic);
if (jarFilePathUrl == null || !jarFilePathUrl.endsWith(".nar")) {
sourceConfig.setClassName("org.apache.pulsar.io.datagenerator.DataGeneratorSource");
}
sourceConfig.setArchive(jarFilePathUrl);
sourceConfig.setParallelism(parallelism);
int metricsPort = FunctionCommon.findAvailablePort();
@Cleanup LocalRunner localRunner = LocalRunner.builder().sourceConfig(sourceConfig).clientAuthPlugin(AuthenticationTls.class.getName()).clientAuthParams(String.format("tlsCertFile:%s,tlsKeyFile:%s", TLS_CLIENT_CERT_FILE_PATH, TLS_CLIENT_KEY_FILE_PATH)).useTls(true).tlsTrustCertFilePath(TLS_TRUST_CERT_FILE_PATH).tlsAllowInsecureConnection(true).tlsHostNameVerificationEnabled(false).brokerServiceUrl(pulsar.getBrokerServiceUrlTls()).connectorsDirectory(workerConfig.getConnectorsDirectory()).metricsPortStart(metricsPort).build();
localRunner.start(false);
Assert.assertTrue(retryStrategically((test) -> {
try {
return admin.topics().getStats(sinkTopic).getPublishers().size() == parallelism;
} catch (PulsarAdminException e) {
return false;
}
}, 10, 150));
Assert.assertTrue(retryStrategically((test) -> {
try {
boolean result = false;
TopicStats sourceStats = admin.topics().getStats(sinkTopic);
if (sourceStats.getPublishers().size() == parallelism) {
for (PublisherStats publisher : sourceStats.getPublishers()) {
result = publisher.getMetadata() != null && publisher.getMetadata().containsKey("id") && publisher.getMetadata().get("id").equals(String.format("%s/%s/%s", tenant, namespacePortion, sourceName));
}
}
return result;
} catch (PulsarAdminException e) {
return false;
}
}, 50, 150));
Assert.assertTrue(retryStrategically((test) -> {
try {
return (admin.topics().getStats(sinkTopic).getPublishers().size() == parallelism) && (admin.topics().getInternalStats(sinkTopic, false).numberOfEntries > 4);
} catch (PulsarAdminException e) {
return false;
}
}, 50, 150));
assertEquals(admin.topics().getStats(sinkTopic).getPublishers().size(), parallelism);
// validate prometheus metrics
String prometheusMetrics = PulsarFunctionTestUtils.getPrometheusMetrics(metricsPort);
log.info("prometheus metrics: {}", prometheusMetrics);
Map<String, PulsarFunctionTestUtils.Metric> metricsMap = new HashMap<>();
Arrays.asList(prometheusMetrics.split("\n")).forEach(line -> {
if (line.startsWith("pulsar_source_written_total")) {
Map<String, PulsarFunctionTestUtils.Metric> metrics = PulsarFunctionTestUtils.parseMetrics(line);
assertFalse(metrics.isEmpty());
PulsarFunctionTestUtils.Metric m = metrics.get("pulsar_source_written_total");
if (m != null) {
metricsMap.put(m.tags.get("instance_id"), m);
}
}
});
Assert.assertEquals(metricsMap.size(), parallelism);
for (int i = 0; i < parallelism; i++) {
PulsarFunctionTestUtils.Metric m = metricsMap.get(String.valueOf(i));
Assert.assertNotNull(m);
assertEquals(m.tags.get("cluster"), config.getClusterName());
assertEquals(m.tags.get("instance_id"), String.valueOf(i));
assertEquals(m.tags.get("name"), sourceName);
assertEquals(m.tags.get("namespace"), String.format("%s/%s", tenant, namespacePortion));
assertEquals(m.tags.get("fqfn"), FunctionCommon.getFullyQualifiedName(tenant, namespacePortion, sourceName));
assertTrue(m.value > 0.0);
}
localRunner.stop();
Assert.assertTrue(retryStrategically((test) -> {
try {
return (admin.topics().getStats(sinkTopic).getPublishers().size() == 0);
} catch (PulsarAdminException e) {
return e.getStatusCode() == 404;
}
}, 10, 150));
try {
assertEquals(admin.topics().getStats(sinkTopic).getPublishers().size(), 0);
} catch (PulsarAdminException e) {
if (e.getStatusCode() != 404) {
fail();
}
}
}
use of org.apache.pulsar.functions.LocalRunner in project pulsar by apache.
the class PulsarFunctionLocalRunTest method testE2EPulsarFunctionLocalRun.
/**
* Validates pulsar sink e2e functionality on functions.
*
* @throws Exception
*/
private void testE2EPulsarFunctionLocalRun(String jarFilePathUrl, int parallelism) throws Exception {
final String namespacePortion = "io";
final String replNamespace = tenant + "/" + namespacePortion;
final String sourceTopic = "persistent://" + replNamespace + "/my-topic1";
final String sinkTopic = "persistent://" + replNamespace + "/output";
final String propertyKey = "key";
final String propertyValue = "value";
final String functionName = "PulsarFunction-test";
final String subscriptionName = "test-sub";
admin.namespaces().createNamespace(replNamespace);
Set<String> clusters = Sets.newHashSet(Lists.newArrayList(CLUSTER));
admin.namespaces().setNamespaceReplicationClusters(replNamespace, clusters);
// create a producer that creates a topic at broker
Producer<String> producer = pulsarClient.newProducer(Schema.STRING).topic(sourceTopic).create();
Consumer<String> consumer = pulsarClient.newConsumer(Schema.STRING).topic(sinkTopic).subscriptionName("sub").subscribe();
FunctionConfig functionConfig = createFunctionConfig(tenant, namespacePortion, functionName, sourceTopic, sinkTopic, subscriptionName);
functionConfig.setProcessingGuarantees(FunctionConfig.ProcessingGuarantees.ATLEAST_ONCE);
functionConfig.setJar(jarFilePathUrl);
functionConfig.setParallelism(parallelism);
int metricsPort = FunctionCommon.findAvailablePort();
@Cleanup LocalRunner localRunner = LocalRunner.builder().functionConfig(functionConfig).clientAuthPlugin(AuthenticationTls.class.getName()).clientAuthParams(String.format("tlsCertFile:%s,tlsKeyFile:%s", TLS_CLIENT_CERT_FILE_PATH, TLS_CLIENT_KEY_FILE_PATH)).useTls(true).tlsTrustCertFilePath(TLS_TRUST_CERT_FILE_PATH).tlsAllowInsecureConnection(true).tlsHostNameVerificationEnabled(false).metricsPortStart(metricsPort).brokerServiceUrl(pulsar.getBrokerServiceUrlTls()).build();
localRunner.start(false);
Assert.assertTrue(retryStrategically((test) -> {
try {
boolean result = false;
TopicStats topicStats = admin.topics().getStats(sourceTopic);
if (topicStats.getSubscriptions().containsKey(subscriptionName) && topicStats.getSubscriptions().get(subscriptionName).getConsumers().size() == parallelism) {
for (ConsumerStats consumerStats : topicStats.getSubscriptions().get(subscriptionName).getConsumers()) {
result = consumerStats.getAvailablePermits() == 1000 && consumerStats.getMetadata() != null && consumerStats.getMetadata().containsKey("id") && consumerStats.getMetadata().get("id").equals(String.format("%s/%s/%s", tenant, namespacePortion, functionName));
}
}
return result;
} catch (PulsarAdminException e) {
return false;
}
}, 50, 150));
// validate pulsar sink consumer has started on the topic
TopicStats stats = admin.topics().getStats(sourceTopic);
assertTrue(stats.getSubscriptions().get(subscriptionName) != null && !stats.getSubscriptions().get(subscriptionName).getConsumers().isEmpty());
int totalMsgs = 5;
for (int i = 0; i < totalMsgs; i++) {
String data = "my-message-" + i;
producer.newMessage().property(propertyKey, propertyValue).value(data).send();
}
retryStrategically((test) -> {
try {
SubscriptionStats subStats = admin.topics().getStats(sourceTopic).getSubscriptions().get(subscriptionName);
return subStats.getUnackedMessages() == 0;
} catch (PulsarAdminException e) {
return false;
}
}, 50, 150);
for (int i = 0; i < totalMsgs; i++) {
Message<String> msg = consumer.receive(5, TimeUnit.SECONDS);
String receivedPropertyValue = msg.getProperty(propertyKey);
assertEquals(propertyValue, receivedPropertyValue);
assertEquals(msg.getValue(), "my-message-" + i + "!");
}
// validate pulsar-sink consumer has consumed all messages and delivered to Pulsar sink but unacked messages
// due to publish failure
assertNotEquals(admin.topics().getStats(sourceTopic).getSubscriptions().values().iterator().next().getUnackedMessages(), totalMsgs);
// validate prometheus metrics
String prometheusMetrics = PulsarFunctionTestUtils.getPrometheusMetrics(metricsPort);
log.info("prometheus metrics: {}", prometheusMetrics);
Map<String, PulsarFunctionTestUtils.Metric> metricsMap = new HashMap<>();
Arrays.asList(prometheusMetrics.split("\n")).forEach(line -> {
if (line.startsWith("pulsar_function_processed_successfully_total")) {
Map<String, PulsarFunctionTestUtils.Metric> metrics = PulsarFunctionTestUtils.parseMetrics(line);
assertFalse(metrics.isEmpty());
PulsarFunctionTestUtils.Metric m = metrics.get("pulsar_function_processed_successfully_total");
if (m != null) {
metricsMap.put(m.tags.get("instance_id"), m);
}
}
});
Assert.assertEquals(metricsMap.size(), parallelism);
double totalMsgRecv = 0.0;
for (int i = 0; i < parallelism; i++) {
PulsarFunctionTestUtils.Metric m = metricsMap.get(String.valueOf(i));
Assert.assertNotNull(m);
assertEquals(m.tags.get("cluster"), config.getClusterName());
assertEquals(m.tags.get("instance_id"), String.valueOf(i));
assertEquals(m.tags.get("name"), functionName);
assertEquals(m.tags.get("namespace"), String.format("%s/%s", tenant, namespacePortion));
assertEquals(m.tags.get("fqfn"), FunctionCommon.getFullyQualifiedName(tenant, namespacePortion, functionName));
totalMsgRecv += m.value;
}
Assert.assertEquals(totalMsgRecv, totalMsgs);
// stop functions
localRunner.stop();
retryStrategically((test) -> {
try {
TopicStats topicStats = admin.topics().getStats(sourceTopic);
return topicStats.getSubscriptions().get(subscriptionName) != null && topicStats.getSubscriptions().get(subscriptionName).getConsumers().isEmpty();
} catch (PulsarAdminException e) {
return false;
}
}, 20, 150);
TopicStats topicStats = admin.topics().getStats(sourceTopic);
assertTrue(topicStats.getSubscriptions().get(subscriptionName) != null && topicStats.getSubscriptions().get(subscriptionName).getConsumers().isEmpty());
retryStrategically((test) -> {
try {
return (admin.topics().getStats(sinkTopic).getPublishers().size() == 0);
} catch (PulsarAdminException e) {
if (e.getStatusCode() == 404) {
return true;
}
return false;
}
}, 10, 150);
try {
assertEquals(admin.topics().getStats(sinkTopic).getPublishers().size(), 0);
} catch (PulsarAdminException e) {
if (e.getStatusCode() != 404) {
fail();
}
}
}
use of org.apache.pulsar.functions.LocalRunner in project pulsar by apache.
the class PulsarSinkE2ETest method testPulsarSinkDLQ.
@Test(timeOut = 30000)
public void testPulsarSinkDLQ() throws Exception {
final String namespacePortion = "io";
final String replNamespace = tenant + "/" + namespacePortion;
final String sourceTopic = "persistent://" + replNamespace + "/input";
final String dlqTopic = sourceTopic + "-DLQ";
final String sinkName = "PulsarSink-test";
final String propertyKey = "key";
final String propertyValue = "value";
final String subscriptionName = "test-sub";
admin.namespaces().createNamespace(replNamespace);
Set<String> clusters = Sets.newHashSet(Lists.newArrayList("use"));
admin.namespaces().setNamespaceReplicationClusters(replNamespace, clusters);
// 1 create producer、DLQ consumer
Producer<String> producer = pulsarClient.newProducer(Schema.STRING).topic(sourceTopic).create();
Consumer<String> consumer = pulsarClient.newConsumer(Schema.STRING).topic(dlqTopic).subscriptionName(subscriptionName).subscribe();
// 2 setup sink
SinkConfig sinkConfig = createSinkConfig(tenant, namespacePortion, sinkName, sourceTopic, subscriptionName);
sinkConfig.setNegativeAckRedeliveryDelayMs(1001L);
sinkConfig.setProcessingGuarantees(FunctionConfig.ProcessingGuarantees.ATLEAST_ONCE);
sinkConfig.setMaxMessageRetries(2);
sinkConfig.setDeadLetterTopic(dlqTopic);
sinkConfig.setInputSpecs(Collections.singletonMap(sourceTopic, ConsumerConfig.builder().receiverQueueSize(1000).build()));
sinkConfig.setClassName(SinkForTest.class.getName());
@Cleanup LocalRunner localRunner = LocalRunner.builder().sinkConfig(sinkConfig).clientAuthPlugin(AuthenticationTls.class.getName()).clientAuthParams(String.format("tlsCertFile:%s,tlsKeyFile:%s", TLS_CLIENT_CERT_FILE_PATH, TLS_CLIENT_KEY_FILE_PATH)).useTls(true).tlsTrustCertFilePath(TLS_TRUST_CERT_FILE_PATH).tlsAllowInsecureConnection(true).tlsHostNameVerificationEnabled(false).brokerServiceUrl(pulsar.getBrokerServiceUrlTls()).build();
localRunner.start(false);
retryStrategically((test) -> {
try {
TopicStats topicStats = admin.topics().getStats(sourceTopic);
return topicStats.getSubscriptions().containsKey(subscriptionName) && topicStats.getSubscriptions().get(subscriptionName).getConsumers().size() == 1 && topicStats.getSubscriptions().get(subscriptionName).getConsumers().get(0).getAvailablePermits() == 1000;
} catch (PulsarAdminException e) {
return false;
}
}, 50, 150);
// 3 send message
int totalMsgs = 10;
Set<String> remainingMessagesToReceive = new HashSet<>();
for (int i = 0; i < totalMsgs; i++) {
String messageBody = "fail" + i;
producer.newMessage().property(propertyKey, propertyValue).value(messageBody).send();
remainingMessagesToReceive.add(messageBody);
}
// 4 All messages should enter DLQ
for (int i = 0; i < totalMsgs; i++) {
Message<String> message = consumer.receive(10, TimeUnit.SECONDS);
assertNotNull(message);
remainingMessagesToReceive.remove(message.getValue());
}
assertEquals(remainingMessagesToReceive, Collections.emptySet());
// clean up
producer.close();
consumer.close();
}
use of org.apache.pulsar.functions.LocalRunner in project pulsar by yahoo.
the class PulsarFunctionLocalRunTest method testE2EPulsarFunctionLocalRun.
/**
* Validates pulsar sink e2e functionality on functions.
*
* @throws Exception
*/
private void testE2EPulsarFunctionLocalRun(String jarFilePathUrl, int parallelism) throws Exception {
final String namespacePortion = "io";
final String replNamespace = tenant + "/" + namespacePortion;
final String sourceTopic = "persistent://" + replNamespace + "/my-topic1";
final String sinkTopic = "persistent://" + replNamespace + "/output";
final String propertyKey = "key";
final String propertyValue = "value";
final String functionName = "PulsarFunction-test";
final String subscriptionName = "test-sub";
admin.namespaces().createNamespace(replNamespace);
Set<String> clusters = Sets.newHashSet(Lists.newArrayList(CLUSTER));
admin.namespaces().setNamespaceReplicationClusters(replNamespace, clusters);
// create a producer that creates a topic at broker
Producer<String> producer = pulsarClient.newProducer(Schema.STRING).topic(sourceTopic).create();
Consumer<String> consumer = pulsarClient.newConsumer(Schema.STRING).topic(sinkTopic).subscriptionName("sub").subscribe();
FunctionConfig functionConfig = createFunctionConfig(tenant, namespacePortion, functionName, sourceTopic, sinkTopic, subscriptionName);
functionConfig.setProcessingGuarantees(FunctionConfig.ProcessingGuarantees.ATLEAST_ONCE);
functionConfig.setJar(jarFilePathUrl);
functionConfig.setParallelism(parallelism);
int metricsPort = FunctionCommon.findAvailablePort();
@Cleanup LocalRunner localRunner = LocalRunner.builder().functionConfig(functionConfig).clientAuthPlugin(AuthenticationTls.class.getName()).clientAuthParams(String.format("tlsCertFile:%s,tlsKeyFile:%s", TLS_CLIENT_CERT_FILE_PATH, TLS_CLIENT_KEY_FILE_PATH)).useTls(true).tlsTrustCertFilePath(TLS_TRUST_CERT_FILE_PATH).tlsAllowInsecureConnection(true).tlsHostNameVerificationEnabled(false).metricsPortStart(metricsPort).brokerServiceUrl(pulsar.getBrokerServiceUrlTls()).build();
localRunner.start(false);
Assert.assertTrue(retryStrategically((test) -> {
try {
boolean result = false;
TopicStats topicStats = admin.topics().getStats(sourceTopic);
if (topicStats.getSubscriptions().containsKey(subscriptionName) && topicStats.getSubscriptions().get(subscriptionName).getConsumers().size() == parallelism) {
for (ConsumerStats consumerStats : topicStats.getSubscriptions().get(subscriptionName).getConsumers()) {
result = consumerStats.getAvailablePermits() == 1000 && consumerStats.getMetadata() != null && consumerStats.getMetadata().containsKey("id") && consumerStats.getMetadata().get("id").equals(String.format("%s/%s/%s", tenant, namespacePortion, functionName));
}
}
return result;
} catch (PulsarAdminException e) {
return false;
}
}, 50, 150));
// validate pulsar sink consumer has started on the topic
TopicStats stats = admin.topics().getStats(sourceTopic);
assertTrue(stats.getSubscriptions().get(subscriptionName) != null && !stats.getSubscriptions().get(subscriptionName).getConsumers().isEmpty());
int totalMsgs = 5;
for (int i = 0; i < totalMsgs; i++) {
String data = "my-message-" + i;
producer.newMessage().property(propertyKey, propertyValue).value(data).send();
}
retryStrategically((test) -> {
try {
SubscriptionStats subStats = admin.topics().getStats(sourceTopic).getSubscriptions().get(subscriptionName);
return subStats.getUnackedMessages() == 0;
} catch (PulsarAdminException e) {
return false;
}
}, 50, 150);
for (int i = 0; i < totalMsgs; i++) {
Message<String> msg = consumer.receive(5, TimeUnit.SECONDS);
String receivedPropertyValue = msg.getProperty(propertyKey);
assertEquals(propertyValue, receivedPropertyValue);
assertEquals(msg.getValue(), "my-message-" + i + "!");
}
// validate pulsar-sink consumer has consumed all messages and delivered to Pulsar sink but unacked messages
// due to publish failure
assertNotEquals(admin.topics().getStats(sourceTopic).getSubscriptions().values().iterator().next().getUnackedMessages(), totalMsgs);
// validate prometheus metrics
String prometheusMetrics = PulsarFunctionTestUtils.getPrometheusMetrics(metricsPort);
log.info("prometheus metrics: {}", prometheusMetrics);
Map<String, PulsarFunctionTestUtils.Metric> metricsMap = new HashMap<>();
Arrays.asList(prometheusMetrics.split("\n")).forEach(line -> {
if (line.startsWith("pulsar_function_processed_successfully_total")) {
Map<String, PulsarFunctionTestUtils.Metric> metrics = PulsarFunctionTestUtils.parseMetrics(line);
assertFalse(metrics.isEmpty());
PulsarFunctionTestUtils.Metric m = metrics.get("pulsar_function_processed_successfully_total");
if (m != null) {
metricsMap.put(m.tags.get("instance_id"), m);
}
}
});
Assert.assertEquals(metricsMap.size(), parallelism);
double totalMsgRecv = 0.0;
for (int i = 0; i < parallelism; i++) {
PulsarFunctionTestUtils.Metric m = metricsMap.get(String.valueOf(i));
Assert.assertNotNull(m);
assertEquals(m.tags.get("cluster"), config.getClusterName());
assertEquals(m.tags.get("instance_id"), String.valueOf(i));
assertEquals(m.tags.get("name"), functionName);
assertEquals(m.tags.get("namespace"), String.format("%s/%s", tenant, namespacePortion));
assertEquals(m.tags.get("fqfn"), FunctionCommon.getFullyQualifiedName(tenant, namespacePortion, functionName));
totalMsgRecv += m.value;
}
Assert.assertEquals(totalMsgRecv, totalMsgs);
// stop functions
localRunner.stop();
retryStrategically((test) -> {
try {
TopicStats topicStats = admin.topics().getStats(sourceTopic);
return topicStats.getSubscriptions().get(subscriptionName) != null && topicStats.getSubscriptions().get(subscriptionName).getConsumers().isEmpty();
} catch (PulsarAdminException e) {
return false;
}
}, 20, 150);
TopicStats topicStats = admin.topics().getStats(sourceTopic);
assertTrue(topicStats.getSubscriptions().get(subscriptionName) != null && topicStats.getSubscriptions().get(subscriptionName).getConsumers().isEmpty());
retryStrategically((test) -> {
try {
return (admin.topics().getStats(sinkTopic).getPublishers().size() == 0);
} catch (PulsarAdminException e) {
if (e.getStatusCode() == 404) {
return true;
}
return false;
}
}, 10, 150);
try {
assertEquals(admin.topics().getStats(sinkTopic).getPublishers().size(), 0);
} catch (PulsarAdminException e) {
if (e.getStatusCode() != 404) {
fail();
}
}
}
Aggregations