This is an automated email from the ASF dual-hosted git repository. jamesnetherton pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git
The following commit(s) were added to refs/heads/main by this push: new ee6cadb250 Snmp: Extend test coverage #4797 ee6cadb250 is described below commit ee6cadb250713b78e8a3e45a5bee3bd508d04ca2 Author: JiriOndrusek <ondrusek.j...@gmail.com> AuthorDate: Thu Apr 20 15:31:01 2023 +0200 Snmp: Extend test coverage #4797 --- integration-tests-jvm/snmp/pom.xml | 5 + .../quarkus/component/snmp/it/SnmpResource.java | 120 ++++++++++++++++-- .../camel/quarkus/component/snmp/it/SnmpRoute.java | 64 ++++++++++ .../camel/quarkus/component/snmp/it/SnmpTest.java | 67 +++++++++- .../component/snmp/it/SnmpTestResource.java | 135 +++++++++++++++++++++ 5 files changed, 375 insertions(+), 16 deletions(-) diff --git a/integration-tests-jvm/snmp/pom.xml b/integration-tests-jvm/snmp/pom.xml index b64813cbfb..effa7b4291 100644 --- a/integration-tests-jvm/snmp/pom.xml +++ b/integration-tests-jvm/snmp/pom.xml @@ -46,6 +46,11 @@ <artifactId>quarkus-junit5</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>org.awaitility</groupId> + <artifactId>awaitility</artifactId> + <scope>test</scope> + </dependency> <dependency> <groupId>io.rest-assured</groupId> <artifactId>rest-assured</artifactId> diff --git a/integration-tests-jvm/snmp/src/main/java/org/apache/camel/quarkus/component/snmp/it/SnmpResource.java b/integration-tests-jvm/snmp/src/main/java/org/apache/camel/quarkus/component/snmp/it/SnmpResource.java index 8ff7bcda6f..5b573a6f1d 100644 --- a/integration-tests-jvm/snmp/src/main/java/org/apache/camel/quarkus/component/snmp/it/SnmpResource.java +++ b/integration-tests-jvm/snmp/src/main/java/org/apache/camel/quarkus/component/snmp/it/SnmpResource.java @@ -16,35 +16,129 @@ */ package org.apache.camel.quarkus.component.snmp.it; +import java.util.Deque; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import jakarta.inject.Named; import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; -import org.apache.camel.CamelContext; -import org.jboss.logging.Logger; +import org.apache.camel.ProducerTemplate; +import org.apache.camel.component.snmp.SnmpMessage; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.snmp4j.PDU; +import org.snmp4j.PDUv1; +import org.snmp4j.mp.SnmpConstants; +import org.snmp4j.smi.OID; +import org.snmp4j.smi.OctetString; +import org.snmp4j.smi.TimeTicks; +import org.snmp4j.smi.Variable; +import org.snmp4j.smi.VariableBinding; @Path("/snmp") @ApplicationScoped public class SnmpResource { - private static final Logger LOG = Logger.getLogger(SnmpResource.class); + @ConfigProperty(name = "snmpListenAddress") + String snmpListenAddress; + + @Inject + @Named("snmpTrapResults") + Map<String, Deque<SnmpMessage>> snmpResults; - private static final String COMPONENT_SNMP = "snmp"; @Inject - CamelContext context; + ProducerTemplate producerTemplate; - @Path("/load/component/snmp") + @Path("/producePDU") @GET @Produces(MediaType.TEXT_PLAIN) - public Response loadComponentSnmp() throws Exception { - /* This is an autogenerated test */ - if (context.getComponent(COMPONENT_SNMP) != null) { - return Response.ok().build(); - } - LOG.warnf("Could not load [%s] from the Camel context", COMPONENT_SNMP); - return Response.status(500, COMPONENT_SNMP + " could not be loaded from the Camel context").build(); + public Response producePDU() { + String url = String.format("snmp://%s?retries=1", snmpListenAddress); + SnmpMessage pdu = producerTemplate.requestBody(url, "", SnmpMessage.class); + + String response = pdu.getSnmpMessage().getVariableBindings().stream() + .filter(vb -> vb.getOid().equals(SnmpConstants.sysDescr)) + .map(vb -> vb.getVariable().toString()) + .collect(Collectors.joining()); + + return Response.ok(response).build(); + } + + @Path("/sendPoll") + @GET + @Produces(MediaType.TEXT_PLAIN) + public Response sendPoll() { + SnmpMessage pdu = producerTemplate.requestBody("direct:producePoll", "", SnmpMessage.class); + + String response = pdu.getSnmpMessage().getVariableBindings().stream() + .filter(vb -> vb.getOid().equals(SnmpConstants.sysDescr)) + .map(vb -> vb.getVariable().toString()) + .collect(Collectors.joining()); + + return Response.ok(response).build(); + } + + @Path("/getNext") + @POST + @Produces(MediaType.TEXT_PLAIN) + public Response getNext(String payload) { + String url = String.format("snmp://%s?type=GET_NEXT&retries=1&protocol=udp&oids=%s", snmpListenAddress, + SnmpConstants.sysDescr); + List<SnmpMessage> pdu = producerTemplate.requestBody(url, "", List.class); + + String response = pdu.stream() + .flatMap(m -> m.getSnmpMessage().getVariableBindings().stream()) + .filter(vb -> vb.getOid().equals(SnmpConstants.sysDescr)) + .map(vb -> vb.getVariable().toString()) + .collect(Collectors.joining(",")); + + return Response.ok(response).build(); + } + + @Path("/produceTrap") + @POST + @Produces(MediaType.TEXT_PLAIN) + public Response sendTrap(String payload) { + String url = "snmp:127.0.0.1:1662?protocol=udp&type=TRAP&snmpVersion=0)"; + PDU trap = createTrap(payload); + + producerTemplate.sendBody(url, trap); + + return Response.ok().build(); + } + + @Path("/results") + @POST + @Produces(MediaType.TEXT_PLAIN) + public Response results(String from) throws Exception { + OID oid = "trap".equals(from) ? new OID("1.2.3.4.5") : SnmpConstants.sysDescr; + String result = snmpResults.get(from).stream().map(m -> m.getSnmpMessage().getVariable(oid).toString()) + .collect(Collectors.joining(",")); + + return Response.ok(result).build(); + } + + public PDU createTrap(String payload) { + PDUv1 trap = new PDUv1(); + trap.setGenericTrap(PDUv1.ENTERPRISE_SPECIFIC); + trap.setSpecificTrap(1); + + OID oid = new OID("1.2.3.4.5"); + trap.add(new VariableBinding(SnmpConstants.snmpTrapOID, oid)); + trap.add(new VariableBinding(SnmpConstants.sysUpTime, new TimeTicks(5000))); // put your uptime here + trap.add(new VariableBinding(SnmpConstants.sysDescr, new OctetString("System Description"))); + trap.setEnterprise(oid); + + //Add Payload + Variable var = new OctetString(payload); + trap.add(new VariableBinding(oid, var)); + return trap; } } diff --git a/integration-tests-jvm/snmp/src/main/java/org/apache/camel/quarkus/component/snmp/it/SnmpRoute.java b/integration-tests-jvm/snmp/src/main/java/org/apache/camel/quarkus/component/snmp/it/SnmpRoute.java new file mode 100644 index 0000000000..445d6a456f --- /dev/null +++ b/integration-tests-jvm/snmp/src/main/java/org/apache/camel/quarkus/component/snmp/it/SnmpRoute.java @@ -0,0 +1,64 @@ +/* + * 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.camel.quarkus.component.snmp.it; + +import java.util.Deque; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedDeque; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import jakarta.inject.Singleton; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.snmp.SnmpMessage; +import org.eclipse.microprofile.config.inject.ConfigProperty; + +@ApplicationScoped +public class SnmpRoute extends RouteBuilder { + + @ConfigProperty(name = "snmpListenAddress") + String snmpListenAddress; + + @Inject + @Named("snmpTrapResults") + Map<String, Deque<SnmpMessage>> snmpResults; + + @Override + public void configure() { + //TRAP consumer + from("snmp:0.0.0.0:1662?protocol=udp&type=TRAP&snmpVersion=0") + .process(e -> snmpResults.get("trap").add(e.getIn().getBody(SnmpMessage.class))); + + //POLL consumer + from("snmp://" + snmpListenAddress + "?protocol=udp&type=POLL&snmpVersion=0&oids=1.3.6.1.2.1.1.5.0") + .process(e -> snmpResults.get("poll").add(e.getIn().getBody(SnmpMessage.class))); + } + + static class Producers { + @jakarta.enterprise.inject.Produces + @Singleton + @Named("snmpTrapResults") + Map<String, Deque<SnmpMessage>> snmpResults() { + Map<String, Deque<SnmpMessage>> map = new ConcurrentHashMap<>(); + map.put("trap", new ConcurrentLinkedDeque()); + map.put("poll", new ConcurrentLinkedDeque()); + return map; + } + } +} diff --git a/integration-tests-jvm/snmp/src/test/java/org/apache/camel/quarkus/component/snmp/it/SnmpTest.java b/integration-tests-jvm/snmp/src/test/java/org/apache/camel/quarkus/component/snmp/it/SnmpTest.java index 046ed5b620..83194f0a89 100644 --- a/integration-tests-jvm/snmp/src/test/java/org/apache/camel/quarkus/component/snmp/it/SnmpTest.java +++ b/integration-tests-jvm/snmp/src/test/java/org/apache/camel/quarkus/component/snmp/it/SnmpTest.java @@ -16,19 +16,80 @@ */ package org.apache.camel.quarkus.component.snmp.it; +import java.util.concurrent.TimeUnit; + +import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; +import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; +import static org.awaitility.Awaitility.await; + +/** + * There is a responder defined in the test resource. Which returns 2 responses without a delay and the third one + * with delay longer then default timeout. This means following behavior: + * - send PDU will receive 1 response + * - get_next will receive 2 responses (the third one reaches timeout) + * - poll returns unending stream of responses + */ @QuarkusTest +@QuarkusTestResource(SnmpTestResource.class) class SnmpTest { @Test - public void loadComponentSnmp() { - /* A simple autogenerated test */ - RestAssured.get("/snmp/load/component/snmp") + public void testSendReceiveTrap() throws Exception { + + RestAssured.given() + .body("TEXT") + .post("/snmp/produceTrap") .then() .statusCode(200); + + await().atMost(10L, TimeUnit.SECONDS).pollDelay(100, TimeUnit.MILLISECONDS).until(() -> { + String result = RestAssured.given() + .body("trap") + .post("/snmp/results") + .then() + .statusCode(200) + .extract().body().asString(); + + return result.contains("TEXT"); + }); + } + + @Test + public void testPoll() throws Exception { + await().atMost(10L, TimeUnit.SECONDS).pollDelay(100, TimeUnit.MILLISECONDS).until(() -> { + String result = RestAssured.given() + .body("poll") + .post("/snmp/results") + .then() + .statusCode(200) + .extract().body().asString(); + + return result.startsWith("Response from the test #1,Response from the test #2,Response from the test #3"); + }); } + @Test + public void testProducePDU() { + + RestAssured + .get("/snmp/producePDU") + .then() + .statusCode(200) + .body(Matchers.equalTo("Response from the test #1")); + } + + @Test + public void testGetNext() { + + RestAssured.given() + .body("TEXT") + .post("/snmp/getNext") + .then() + .statusCode(200) + .body(Matchers.equalTo("Response from the test #1,Response from the test #2")); + } } diff --git a/integration-tests-jvm/snmp/src/test/java/org/apache/camel/quarkus/component/snmp/it/SnmpTestResource.java b/integration-tests-jvm/snmp/src/test/java/org/apache/camel/quarkus/component/snmp/it/SnmpTestResource.java new file mode 100644 index 0000000000..504e4f6250 --- /dev/null +++ b/integration-tests-jvm/snmp/src/test/java/org/apache/camel/quarkus/component/snmp/it/SnmpTestResource.java @@ -0,0 +1,135 @@ +/* + * 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.camel.quarkus.component.snmp.it; + +import java.io.IOException; +import java.util.Map; +import java.util.Optional; +import java.util.Vector; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; +import org.apache.camel.util.CollectionHelper; +import org.junit.jupiter.api.Assertions; +import org.snmp4j.CommandResponder; +import org.snmp4j.CommandResponderEvent; +import org.snmp4j.MessageException; +import org.snmp4j.PDU; +import org.snmp4j.Snmp; +import org.snmp4j.mp.SnmpConstants; +import org.snmp4j.mp.StatusInformation; +import org.snmp4j.smi.OID; +import org.snmp4j.smi.OctetString; +import org.snmp4j.smi.UdpAddress; +import org.snmp4j.smi.VariableBinding; +import org.snmp4j.transport.DefaultUdpTransportMapping; + +public class SnmpTestResource implements QuarkusTestResourceLifecycleManager { + + public static final String LISTEN_ADDRESS = "snmpListenAddress"; + public static final String LOCAL_ADDRESS = "127.0.0.1/0"; + + Snmp snmpResponder; + + @Override + public Map<String, String> start() { + DefaultUdpTransportMapping udpTransportMapping; + try { + udpTransportMapping = new DefaultUdpTransportMapping(new UdpAddress(LOCAL_ADDRESS)); + snmpResponder = new Snmp(udpTransportMapping); + + TestCommandResponder responder = new TestCommandResponder(snmpResponder); + snmpResponder.addCommandResponder(responder); + + snmpResponder.listen(); + } catch (Exception e) { + throw new RuntimeException(e); + } + + return CollectionHelper.mapOf(LISTEN_ADDRESS, udpTransportMapping.getListenAddress().toString().replaceFirst("/", ":")); + } + + @Override + public void stop() { + if (snmpResponder != null) { + try { + snmpResponder.close(); + } catch (IOException e) { + //do nothing + } + } + } + + static class TestCommandResponder implements CommandResponder { + + private final Snmp commandResponder; + private final Map<String, Integer> counts = new ConcurrentHashMap<>(); + + public TestCommandResponder(Snmp commandResponder) { + this.commandResponder = commandResponder; + } + + @Override + public synchronized void processPdu(CommandResponderEvent event) { + PDU pdu = event.getPDU(); + Vector<? extends VariableBinding> vbs = Optional.ofNullable(pdu.getVariableBindings()).orElse(new Vector<>(0)); + String key = vbs.stream().sequential().map(vb -> vb.getOid().toString()).collect(Collectors.joining(",")); + int numberOfSent = counts.getOrDefault(key, 0); + + //if 3 responses were already sent for the OID, do not respond anymore + if (numberOfSent > 3) { + return; + } + //first 2 responses are quick, the third response takes 3000ms (so there is a timeout with default 1500ms) -> + // getNext producer will receive only 2 messages + // poll consumer should receive all of them + if (numberOfSent % 3 == 2) { + try { + Thread.sleep(3000); + } catch (InterruptedException e) { + //nothing + } + } + + PDU response = makeResponse(++numberOfSent, SnmpConstants.version1); + if (response != null) { + try { + response.setRequestID(pdu.getRequestID()); + commandResponder.getMessageDispatcher().returnResponsePdu( + event.getMessageProcessingModel(), event.getSecurityModel(), + event.getSecurityName(), event.getSecurityLevel(), + response, event.getMaxSizeResponsePDU(), + event.getStateReference(), new StatusInformation()); + } catch (MessageException e) { + Assertions.assertNull(e); + } + counts.put(key, numberOfSent); + } + } + + private PDU makeResponse(int counter, int version) { + PDU responsePDU = new PDU(); + responsePDU.setType(PDU.RESPONSE); + responsePDU.setErrorStatus(PDU.noError); + responsePDU.setErrorIndex(0); + responsePDU.add(new VariableBinding(new OID(SnmpConstants.sysDescr), + new OctetString("Response from the test #" + counter))); + return responsePDU; + } + } +}