simonbence commented on a change in pull request #5028: URL: https://github.com/apache/nifi/pull/5028#discussion_r627992870
########## File path: nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/main/java/org/apache/nifi/snmp/dto/SNMPResponse.java ########## @@ -0,0 +1,66 @@ +/* + * 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.snmp.dto; + +import org.apache.nifi.snmp.utils.SNMPUtils; +import org.snmp4j.PDU; +import org.snmp4j.Target; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class SNMPResponse { + + private final Map<String, String> attributes; + private final List<SNMPValue> variableBindings; + private final int errorStatus; + private final String errorStatusText; + private final String targetAddress; + + public SNMPResponse(final PDU responsePdu, final Target target) { + attributes = SNMPUtils.getPduAttributeMap(responsePdu); Review comment: Minor: is there any reason for not storing responsePdo and extract information on the fly (when get is being called) ########## File path: nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/main/java/org/apache/nifi/snmp/processors/GetSNMP.java ########## @@ -16,184 +16,186 @@ */ package org.apache.nifi.snmp.processors; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - import org.apache.nifi.annotation.behavior.InputRequirement; import org.apache.nifi.annotation.behavior.InputRequirement.Requirement; import org.apache.nifi.annotation.behavior.WritesAttribute; import org.apache.nifi.annotation.behavior.WritesAttributes; import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.Tags; +import org.apache.nifi.components.AllowableValue; import org.apache.nifi.components.PropertyDescriptor; import org.apache.nifi.flowfile.FlowFile; import org.apache.nifi.processor.ProcessContext; import org.apache.nifi.processor.ProcessSession; -import org.apache.nifi.processor.Processor; import org.apache.nifi.processor.Relationship; import org.apache.nifi.processor.util.StandardValidators; -import org.apache.nifi.processor.exception.ProcessException; -import org.snmp4j.PDU; -import org.snmp4j.event.ResponseEvent; -import org.snmp4j.smi.OID; -import org.snmp4j.util.TreeEvent; +import org.apache.nifi.snmp.dto.SNMPResponse; +import org.apache.nifi.snmp.dto.SNMPWalkResponse; +import org.apache.nifi.snmp.exception.SNMPException; +import org.apache.nifi.snmp.exception.SNMPWalkException; +import org.apache.nifi.snmp.utils.SNMPUtils; +import org.apache.nifi.snmp.validators.OIDValidator; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; /** * Retrieving data from configured SNMP agent which, upon each invocation of * {@link #onTrigger(ProcessContext, ProcessSession)} method, will construct a * {@link FlowFile} containing in its properties the information retrieved. * The output {@link FlowFile} won't have any content. */ -@Tags({ "snmp", "get", "oid", "walk" }) +@Tags({"snmp", "get", "oid", "walk"}) @InputRequirement(Requirement.INPUT_FORBIDDEN) @CapabilityDescription("Retrieves information from SNMP Agent and outputs a FlowFile with information in attributes and without any content") @WritesAttributes({ - @WritesAttribute(attribute=SNMPUtils.SNMP_PROP_PREFIX + "*", description="Attributes retrieved from the SNMP response. It may include:" - + " snmp$errorIndex, snmp$errorStatus, snmp$errorStatusText, snmp$nonRepeaters, snmp$requestID, snmp$type, snmp$variableBindings"), - @WritesAttribute(attribute=SNMPUtils.SNMP_PROP_PREFIX + "textualOid", description="This attribute will exist if and only if the strategy" - + " is GET and will be equal to the value given in Textual Oid property.") + @WritesAttribute(attribute = SNMPUtils.SNMP_PROP_PREFIX + "*", description = "Attributes retrieved from the SNMP response. It may include:" + + " snmp$errorIndex, snmp$errorStatus, snmp$errorStatusText, snmp$nonRepeaters, snmp$requestID, snmp$type, snmp$variableBindings"), + @WritesAttribute(attribute = SNMPUtils.SNMP_PROP_PREFIX + "textualOid", description = "This attribute will exist if and only if the strategy" + + " is GET and will be equal to the value given in Textual Oid property.") }) -public class GetSNMP extends AbstractSNMPProcessor<SNMPGetter> { +public class GetSNMP extends AbstractSNMPProcessor { + + // SNMP strategies + public static final AllowableValue GET = new AllowableValue("GET", "GET", + "A manager-to-agent request to retrieve the value of a variable. A response with the current value returned."); + public static final AllowableValue WALK = new AllowableValue("WALK", "WALK", + "A manager-to-agent request to retrieve the value of multiple variables. Snmp WALK also traverses all subnodes " + + "under the specified OID."); - /** OID to request (if walk, it is the root ID of the request) */ + // OID to request (if walk, it is the root ID of the request). public static final PropertyDescriptor OID = new PropertyDescriptor.Builder() .name("snmp-oid") .displayName("OID") - .description("The OID to request") + .description("Each OID (object identifier) identifies a variable that can be read or set via SNMP.") .required(true) - .addValidator(SNMPUtils.SNMP_OID_VALIDATOR) + .addValidator(new OIDValidator()) + .build(); + + public static final PropertyDescriptor SNMP_STRATEGY = new PropertyDescriptor.Builder() + .name("snmp-strategy") + .displayName("SNMP Strategy") + .description("SNMP strategy to use (SNMP Get or SNMP Walk)") + .required(true) + .allowableValues(GET, WALK) + .defaultValue(GET.getValue()) .build(); - /** Textual OID to request */ public static final PropertyDescriptor TEXTUAL_OID = new PropertyDescriptor.Builder() .name("snmp-textual-oid") .displayName("Textual OID") - .description("The textual OID to request") + .description("The textual OID to request.") .required(false) .addValidator(StandardValidators.NON_BLANK_VALIDATOR) .defaultValue(null) .build(); - /** SNMP strategy for SNMP Get processor : simple get or walk */ - public static final PropertyDescriptor SNMP_STRATEGY = new PropertyDescriptor.Builder() - .name("snmp-strategy") - .displayName("SNMP strategy (GET/WALK)") - .description("SNMP strategy to use (SNMP Get or SNMP Walk)") - .required(true) - .allowableValues("GET", "WALK") - .defaultValue("GET") - .build(); - - /** relationship for success */ public static final Relationship REL_SUCCESS = new Relationship.Builder() .name("success") - .description("All FlowFiles that are received from the SNMP agent are routed to this relationship") + .description("All FlowFiles that are received from the SNMP agent are routed to this relationship.") .build(); - /** relationship for failure */ public static final Relationship REL_FAILURE = new Relationship.Builder() .name("failure") - .description("All FlowFiles that cannot received from the SNMP agent are routed to this relationship") + .description("All FlowFiles that cannot received from the SNMP agent are routed to this relationship.") .build(); - /** list of property descriptors */ - private final static List<PropertyDescriptor> propertyDescriptors; - - /** list of relationships */ - private final static Set<Relationship> relationships; - - /* - * Will ensure that the list of property descriptors is build only once. - * Will also create a Set of relationships - */ - static { - List<PropertyDescriptor> _propertyDescriptors = new ArrayList<>(); - _propertyDescriptors.add(OID); - _propertyDescriptors.add(TEXTUAL_OID); - _propertyDescriptors.add(SNMP_STRATEGY); - _propertyDescriptors.addAll(descriptors); - propertyDescriptors = Collections.unmodifiableList(_propertyDescriptors); - - Set<Relationship> _relationships = new HashSet<>(); - _relationships.add(REL_SUCCESS); - _relationships.add(REL_FAILURE); - relationships = Collections.unmodifiableSet(_relationships); - } + protected static final List<PropertyDescriptor> PROPERTY_DESCRIPTORS = Collections.unmodifiableList(Arrays.asList( + AGENT_HOST, + AGENT_PORT, + SNMP_VERSION, + SNMP_COMMUNITY, + SNMP_SECURITY_LEVEL, + SNMP_SECURITY_NAME, + SNMP_AUTH_PROTOCOL, + SNMP_AUTH_PASSWORD, + SNMP_PRIVACY_PROTOCOL, + SNMP_PRIVACY_PASSWORD, + SNMP_RETRIES, + SNMP_TIMEOUT, + OID, + TEXTUAL_OID, + SNMP_STRATEGY + )); + + private static final Set<Relationship> RELATIONSHIPS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + REL_SUCCESS, + REL_FAILURE + ))); - /** - * Delegate method to supplement - * {@link #onTrigger(ProcessContext, ProcessSession)}. It is implemented by - * sub-classes to perform {@link Processor} specific functionality. - * - * @param context - * instance of {@link ProcessContext} - * @param processSession - * instance of {@link ProcessSession} - * @throws ProcessException Process exception - */ @Override - protected void onTriggerSnmp(ProcessContext context, ProcessSession processSession) throws ProcessException { - if("GET".equals(context.getProperty(SNMP_STRATEGY).getValue())) { - final ResponseEvent response = this.targetResource.get(); - if (response.getResponse() != null){ - FlowFile flowFile = processSession.create(); - PDU pdu = response.getResponse(); - flowFile = SNMPUtils.updateFlowFileAttributesWithPduProperties(pdu, flowFile, processSession); - flowFile = SNMPUtils.addAttribute(SNMPUtils.SNMP_PROP_PREFIX + "textualOid", - context.getProperty(TEXTUAL_OID).getValue(), flowFile, processSession); - processSession.getProvenanceReporter().receive(flowFile, - this.snmpTarget.getAddress().toString() + "/" + context.getProperty(OID).getValue()); - if(pdu.getErrorStatus() == PDU.noError) { - processSession.transfer(flowFile, REL_SUCCESS); - } else { - processSession.transfer(flowFile, REL_FAILURE); - } - } else { - this.getLogger().error("Get request timed out or parameters are incorrect."); - context.yield(); - } - } else if("WALK".equals(context.getProperty(SNMP_STRATEGY).getValue())) { - final List<TreeEvent> events = this.targetResource.walk(); - if((events != null) && !events.isEmpty() && (events.get(0).getVariableBindings() != null)) { - FlowFile flowFile = processSession.create(); - for (TreeEvent treeEvent : events) { - flowFile = SNMPUtils.updateFlowFileAttributesWithTreeEventProperties(treeEvent, flowFile, processSession); - } - processSession.getProvenanceReporter().receive(flowFile, - this.snmpTarget.getAddress().toString() + "/" + context.getProperty(OID).getValue()); + public void onTrigger(final ProcessContext context, final ProcessSession processSession) { + final SNMPStrategy snmpStrategy = SNMPStrategy.valueOf(context.getProperty(SNMP_STRATEGY).getValue()); + final String oid = context.getProperty(OID).getValue(); + + if (SNMPStrategy.GET == snmpStrategy) { + performSnmpGet(context, processSession, oid); + } else if (SNMPStrategy.WALK == snmpStrategy) { + performSnmpWalk(context, processSession, oid); + } + } + + private void performSnmpWalk(final ProcessContext context, final ProcessSession processSession, final String oid) { + try { + final SNMPWalkResponse response = snmpRequestHandler.walk(oid); + FlowFile flowFile = createFlowFileWithTreeEventProperties(response, processSession); + processSession.getProvenanceReporter().receive(flowFile, response.getTargetUri() + "/" + oid); + processSession.transfer(flowFile, REL_SUCCESS); + } catch (SNMPWalkException e) { + getLogger().error("Could not perform SNMP Walk: Check if SNMP agent is accessible and the specified OID exists."); + context.yield(); + } + } + + private void performSnmpGet(final ProcessContext context, final ProcessSession processSession, final String oid) { + final SNMPResponse response; + final FlowFile flowFile = processSession.create(); + try { + response = snmpRequestHandler.get(oid); + updateFlowFileWithResponseAttributes(flowFile, context, processSession, response); + processSession.getProvenanceReporter().receive(flowFile, response.getTargetAddress() + "/" + oid); + if (response.isValid()) { processSession.transfer(flowFile, REL_SUCCESS); } else { - this.getLogger().error("Get request timed out or parameters are incorrect."); - context.yield(); + processSession.transfer(flowFile, REL_FAILURE); Review comment: I think the error code should be added to the FF ########## File path: nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/main/java/org/apache/nifi/snmp/processors/SetSNMP.java ########## @@ -34,191 +25,101 @@ import org.apache.nifi.processor.ProcessContext; import org.apache.nifi.processor.ProcessSession; import org.apache.nifi.processor.Relationship; -import org.apache.nifi.processor.exception.ProcessException; -import org.snmp4j.PDU; -import org.snmp4j.ScopedPDU; -import org.snmp4j.event.ResponseEvent; -import org.snmp4j.mp.SnmpConstants; -import org.snmp4j.smi.AbstractVariable; -import org.snmp4j.smi.AssignableFromInteger; -import org.snmp4j.smi.AssignableFromLong; -import org.snmp4j.smi.AssignableFromString; -import org.snmp4j.smi.OID; -import org.snmp4j.smi.OctetString; -import org.snmp4j.smi.Variable; -import org.snmp4j.smi.VariableBinding; +import org.apache.nifi.snmp.dto.SNMPResponse; +import org.apache.nifi.snmp.exception.SNMPException; +import org.apache.nifi.snmp.utils.SNMPUtils; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; /** * Performs a SNMP Set operation based on attributes of incoming FlowFile. * Upon each invocation of {@link #onTrigger(ProcessContext, ProcessSession)} * method, it will inspect attributes of FlowFile and look for attributes with * name formatted as "snmp$OID" to set the attribute value to this OID. */ -@Tags({ "snmp", "set", "oid" }) +@Tags({"snmp", "set", "oid"}) @InputRequirement(Requirement.INPUT_REQUIRED) @CapabilityDescription("Based on incoming FlowFile attributes, the processor will execute SNMP Set requests." + - " When founding attributes with name like snmp$<OID>, the processor will atempt to set the value of" + + " When founding attributes with name like snmp$<OID>, the processor will attempt to set the value of" + " attribute to the corresponding OID given in the attribute name") -public class SetSNMP extends AbstractSNMPProcessor<SNMPSetter> { +public class SetSNMP extends AbstractSNMPProcessor { - /** relationship for success */ public static final Relationship REL_SUCCESS = new Relationship.Builder() .name("success") .description("All FlowFiles that have been successfully used to perform SNMP Set are routed to this relationship") .build(); - /** relationship for failure */ + public static final Relationship REL_FAILURE = new Relationship.Builder() .name("failure") .description("All FlowFiles that failed during the SNMP Set care routed to this relationship") .build(); - /** list of properties descriptors */ - private final static List<PropertyDescriptor> propertyDescriptors; + private static final List<PropertyDescriptor> PROPERTY_DESCRIPTORS = Collections.unmodifiableList(Arrays.asList( + AGENT_HOST, + AGENT_PORT, + SNMP_VERSION, + SNMP_COMMUNITY, + SNMP_SECURITY_LEVEL, + SNMP_SECURITY_NAME, + SNMP_AUTH_PROTOCOL, + SNMP_AUTH_PASSWORD, + SNMP_PRIVACY_PROTOCOL, + SNMP_PRIVACY_PASSWORD, + SNMP_RETRIES, + SNMP_TIMEOUT + )); - /** list of relationships */ - private final static Set<Relationship> relationships; + private static final Set<Relationship> RELATIONSHIPS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + REL_SUCCESS, + REL_FAILURE + ))); - /* - * Will ensure that the list of property descriptors is build only once. - * Will also create a Set of relationships - */ - static { - List<PropertyDescriptor> _propertyDescriptors = new ArrayList<>(); - _propertyDescriptors.addAll(descriptors); - propertyDescriptors = Collections.unmodifiableList(_propertyDescriptors); - - Set<Relationship> _relationships = new HashSet<>(); - _relationships.add(REL_SUCCESS); - _relationships.add(REL_FAILURE); - relationships = Collections.unmodifiableSet(_relationships); - } - - /** - * @see org.apache.nifi.snmp.processors.AbstractSNMPProcessor#onTriggerSnmp(org.apache.nifi.processor.ProcessContext, org.apache.nifi.processor.ProcessSession) - */ @Override - protected void onTriggerSnmp(ProcessContext context, ProcessSession processSession) throws ProcessException { - FlowFile flowFile = processSession.get(); + public void onTrigger(final ProcessContext context, final ProcessSession processSession) { + final FlowFile flowFile = processSession.get(); if (flowFile != null) { - // Create the PDU object - PDU pdu = null; - if(this.snmpTarget.getVersion() == SnmpConstants.version3) { - pdu = new ScopedPDU(); - } else { - pdu = new PDU(); - } - if(this.addVariables(pdu, flowFile.getAttributes())) { - pdu.setType(PDU.SET); - try { - ResponseEvent response = this.targetResource.set(pdu); - if(response.getResponse() == null) { - processSession.transfer(processSession.penalize(flowFile), REL_FAILURE); - this.getLogger().error("Set request timed out or parameters are incorrect."); - context.yield(); - } else if(response.getResponse().getErrorStatus() == PDU.noError) { - flowFile = SNMPUtils.updateFlowFileAttributesWithPduProperties(pdu, flowFile, processSession); - processSession.transfer(flowFile, REL_SUCCESS); - processSession.getProvenanceReporter().send(flowFile, this.snmpTarget.getAddress().toString()); - } else { - final String error = response.getResponse().getErrorStatusText(); - flowFile = SNMPUtils.addAttribute(SNMPUtils.SNMP_PROP_PREFIX + "error", error, flowFile, processSession); - processSession.transfer(processSession.penalize(flowFile), REL_FAILURE); - this.getLogger().error("Failed while executing SNMP Set [{}] via " + this.targetResource + ". Error = {}", new Object[]{response.getRequest().getVariableBindings(), error}); - } - } catch (IOException e) { - processSession.transfer(processSession.penalize(flowFile), REL_FAILURE); - this.getLogger().error("Failed while executing SNMP Set via " + this.targetResource, e); - context.yield(); - } - } else { + try { + final SNMPResponse response = snmpRequestHandler.set(flowFile); + processResponse(processSession, flowFile, response); + } catch (SNMPException | IOException e) { processSession.transfer(processSession.penalize(flowFile), REL_FAILURE); - this.getLogger().warn("No attributes found in the FlowFile to perform SNMP Set"); + getLogger().error("Could not perform SNMP Set: Request timed out or parameters are incorrect."); Review comment: Also based on some testing: the processor provides only a limited amount of information when the set is failed. It would be good to have more information on the interface. Also: I think timeout might be handled differently. It is possible "rolling back" the flow file and then yielding would be better, as possibly there is no issue with the input but we have a temporary outage in the other end ########## File path: nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/main/java/org/apache/nifi/snmp/factory/AbstractSNMPFactory.java ########## @@ -0,0 +1,91 @@ +/* + * 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.snmp.factory; + +import org.apache.nifi.snmp.configuration.SNMPConfiguration; +import org.apache.nifi.snmp.exception.CreateSNMPClientException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.snmp4j.CommunityTarget; +import org.snmp4j.Snmp; +import org.snmp4j.Target; +import org.snmp4j.UserTarget; +import org.snmp4j.security.SecurityLevel; +import org.snmp4j.smi.OctetString; +import org.snmp4j.smi.UdpAddress; +import org.snmp4j.transport.DefaultUdpTransportMapping; + +import java.io.IOException; +import java.util.Optional; + +public abstract class AbstractSNMPFactory { + + private static final Logger logger = LoggerFactory.getLogger(AbstractSNMPFactory.class); + + protected AbstractSNMPFactory() { + // hide implicit constructor + } + + protected static Snmp createSimpleSnmpClient() { Review comment: Minor: it does not have to be exposed that this is a SimplsSnmpCluent. Just SnmpClient is good. Also: "get" or something more generic term would be more appropriate as the client does not have to know that the instance has been created on demand ########## File path: nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/main/java/org/apache/nifi/snmp/logging/SLF4JLogAdapter.java ########## @@ -0,0 +1,105 @@ +/*_############################################################################ + _## + _## SNMP4J - JavaLogAdapter.java + _## + _## Copyright (C) 2003-2020 Frank Fock (SNMP4J.org) + _## + _## Licensed 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.snmp.logging; + +import org.slf4j.Logger; +import org.snmp4j.log.LogAdapter; +import org.snmp4j.log.LogLevel; + +import java.io.Serializable; +import java.util.Iterator; +import java.util.logging.Handler; + +public class SLF4JLogAdapter implements LogAdapter { + + private final Logger logger; + + public SLF4JLogAdapter(final Logger logger) { + this.logger = logger; + } + + public boolean isDebugEnabled() { + return logger.isDebugEnabled(); + } + + public boolean isInfoEnabled() { + return logger.isInfoEnabled(); + } + + public boolean isWarnEnabled() { + return logger.isWarnEnabled(); + } + + + public void debug(final Serializable message) { + if (isDebugEnabled()) { + logger.debug("{}", message); + } + } + + public void info(final CharSequence message) { + if (isInfoEnabled()) { + logger.info("{}", message); + } + } + + public void warn(final Serializable message) { + if (isWarnEnabled()) { + logger.warn("{}", message); + } + } + + public void error(final Serializable message) { + logger.error("{}", message); + } + + public void error(final CharSequence message, final Throwable t) { + logger.error("{}", message, t); + } + + public void fatal(final Object message) { + logger.error("{}", message); + } + + public void fatal(final CharSequence message, final Throwable t) { + logger.error("{}", message, t); + } + + + public LogLevel getEffectiveLogLevel() { + return LogLevel.ALL; + } + + public Iterator<Handler> getLogHandler() { + return null; Review comment: an UnsupportedOperationException might be better ########## File path: nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/main/java/org/apache/nifi/snmp/factory/AbstractSNMPFactory.java ########## @@ -0,0 +1,91 @@ +/* + * 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.snmp.factory; + +import org.apache.nifi.snmp.configuration.SNMPConfiguration; +import org.apache.nifi.snmp.exception.CreateSNMPClientException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.snmp4j.CommunityTarget; +import org.snmp4j.Snmp; +import org.snmp4j.Target; +import org.snmp4j.UserTarget; +import org.snmp4j.security.SecurityLevel; +import org.snmp4j.smi.OctetString; +import org.snmp4j.smi.UdpAddress; +import org.snmp4j.transport.DefaultUdpTransportMapping; + +import java.io.IOException; +import java.util.Optional; + +public abstract class AbstractSNMPFactory { + + private static final Logger logger = LoggerFactory.getLogger(AbstractSNMPFactory.class); + + protected AbstractSNMPFactory() { + // hide implicit constructor + } + + protected static Snmp createSimpleSnmpClient() { + final Snmp snmp; + try { + snmp = new Snmp(new DefaultUdpTransportMapping()); + snmp.listen(); + } catch (IOException e) { + final String errorMessage = "Creating SNMP client failed."; + logger.error(errorMessage, e); + throw new CreateSNMPClientException(errorMessage); + } + return snmp; Review comment: Minor: just for better readability this could go into the try block ########## File path: nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/main/java/org/apache/nifi/snmp/dto/SNMPResponse.java ########## @@ -0,0 +1,66 @@ +/* + * 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.snmp.dto; + +import org.apache.nifi.snmp.utils.SNMPUtils; +import org.snmp4j.PDU; +import org.snmp4j.Target; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class SNMPResponse { Review comment: If it's counterpart is called SNMPWalkResponse, this might be called as SNMPGetResponse (I am not 100% sure as the set uses the same, it might not worth to differentiate) ########## File path: nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/main/resources/docs/org.apache.nifi.snmp.processors.SetSNMP/additionalDetails.html ########## @@ -33,26 +33,5 @@ <h2>Summary</h2> the type of data associated to the given OID to allow a correct conversion. If there is no third element, the value is considered as a String and the value will be sent as an OctetString object. </p> -<h2>Configuration Details</h2> Review comment: Same as in case of GetSNMP ########## File path: nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/main/resources/docs/org.apache.nifi.snmp.processors.GetSNMP/additionalDetails.html ########## @@ -44,27 +44,5 @@ <h2>SNMP Properties</h2> In case of a single SNMP Get request, the following is the list of available standard SNMP properties which may come with the PDU: <i>("snmp$errorIndex", "snmp$errorStatus", "snmp$errorStatusText", "snmp$nonRepeaters", "snmp$requestID", "snmp$type")</i> </p> -<h2>Configuration Details</h2> Review comment: I do think, completely removing this piece of information is a step backward. It would be better to restructure it to follow the current code. (Something like which properties are needed for the given versions, etc.) ########## File path: nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/test/java/org/apache/nifi/snmp/operations/SNMPRequestTest.java ########## @@ -0,0 +1,118 @@ +/* + * 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.snmp.operations; + +import org.apache.nifi.snmp.dto.SNMPResponse; +import org.apache.nifi.snmp.dto.SNMPWalkResponse; +import org.apache.nifi.snmp.helper.SNMPTestUtils; +import org.apache.nifi.snmp.testagents.TestAgent; +import org.apache.nifi.util.MockFlowFile; +import org.snmp4j.CommunityTarget; +import org.snmp4j.Snmp; +import org.snmp4j.agent.mo.DefaultMOFactory; +import org.snmp4j.agent.mo.MOAccessImpl; +import org.snmp4j.smi.OID; +import org.snmp4j.smi.OctetString; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public abstract class SNMPRequestTest { + + protected static final String LOCALHOST = "127.0.0.1"; + protected static final String INVALID_HOST = "127.0.0.2"; + protected static final String READ_ONLY_OID_1 = "1.3.6.1.4.1.32437.1.5.1.4.2.0"; + protected static final String READ_ONLY_OID_2 = "1.3.6.1.4.1.32437.1.5.1.4.3.0"; + protected static final String WRITE_ONLY_OID = "1.3.6.1.4.1.32437.1.5.1.4.4.0"; + protected static final String READ_ONLY_OID_VALUE_1 = "TestOID1"; + protected static final String READ_ONLY_OID_VALUE_2 = "TestOID2"; + protected static final String WRITE_ONLY_OID_VALUE = "writeOnlyOID"; + protected static final String SNMP_PROP_DELIMITER = "$"; + protected static final String SNMP_PROP_PREFIX = "snmp" + SNMP_PROP_DELIMITER; + protected static final String NOT_WRITABLE = "Not writable"; + protected static final String NO_ACCESS = "No access"; + protected static final String SUCCESS = "Success"; + protected static final String EXPECTED_OID_VALUE = "testValue"; + protected static final Map<String, String> WALK_OID_MAP; + + static { + final Map<String, String> oidMap = new HashMap<>(); + oidMap.put(READ_ONLY_OID_1, READ_ONLY_OID_VALUE_1); + oidMap.put(READ_ONLY_OID_2, READ_ONLY_OID_VALUE_2); + WALK_OID_MAP = Collections.unmodifiableMap(oidMap); + } + + protected static void initAgent(final TestAgent agent) throws IOException { + agent.start(); + agent.registerManagedObjects( + DefaultMOFactory.getInstance().createScalar(new OID(READ_ONLY_OID_1), MOAccessImpl.ACCESS_READ_ONLY, new OctetString(READ_ONLY_OID_VALUE_1)), + DefaultMOFactory.getInstance().createScalar(new OID(READ_ONLY_OID_2), MOAccessImpl.ACCESS_READ_ONLY, new OctetString(READ_ONLY_OID_VALUE_2)), + DefaultMOFactory.getInstance().createScalar(new OID(WRITE_ONLY_OID), MOAccessImpl.ACCESS_WRITE_ONLY, new OctetString(WRITE_ONLY_OID_VALUE)) + ); + } + + protected SNMPWalkResponse getTreeEvents(final int port, final int version) throws IOException { + final Snmp snmp = SNMPTestUtils.createSnmpClient(); + final CommunityTarget target = SNMPTestUtils.createCommTarget("public", LOCALHOST + "/" + port, version); + final StandardSNMPRequestHandler standardSnmpRequestHandler = new StandardSNMPRequestHandler(snmp, target); + return standardSnmpRequestHandler.walk("1.3.6.1.4.1.32437"); + } + + protected SNMPResponse getResponseEvent(final String address, final int port, final int version, final String oid) throws IOException { + final Snmp snmp = SNMPTestUtils.createSnmpClient(); + final CommunityTarget target = SNMPTestUtils.createCommTarget("public", address + "/" + port, version); + final StandardSNMPRequestHandler standardSnmpRequestHandler = new StandardSNMPRequestHandler(snmp, target); + return standardSnmpRequestHandler.get(oid); + } + + protected SNMPResponse getSetResponse(final int port, final int version, final String oid, final String expectedOid) throws IOException { + final Snmp snmp = SNMPTestUtils.createSnmpClient(); + final CommunityTarget target = SNMPTestUtils.createCommTarget("public", LOCALHOST + "/" + port, version); + final StandardSNMPRequestHandler standardSnmpRequestHandler = new StandardSNMPRequestHandler(snmp, target); + + final MockFlowFile flowFile = new MockFlowFile(1L); + final Map<String, String> attributes = new HashMap<>(); + attributes.put(SNMP_PROP_PREFIX + oid, expectedOid); + flowFile.putAttributes(attributes); + + return standardSnmpRequestHandler.set(flowFile); + } + + protected void checkSubTreeContainsOids(SNMPWalkResponse response) { Review comment: Instead "check", the "assert" prefix would be more readable ########## File path: nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/test/java/org/apache/nifi/snmp/operations/SNMPV1RequestTest.java ########## @@ -0,0 +1,93 @@ +/* + * 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.snmp.operations; + +import org.apache.nifi.snmp.dto.SNMPResponse; +import org.apache.nifi.snmp.dto.SNMPWalkResponse; +import org.apache.nifi.snmp.exception.RequestTimeoutException; +import org.apache.nifi.snmp.testagents.TestSNMPV1Agent; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.snmp4j.MessageException; +import org.snmp4j.mp.SnmpConstants; + +import java.io.IOException; + +import static org.junit.Assert.assertEquals; + +public class SNMPV1RequestTest extends SNMPRequestTest { + + private static final String NO_SUCH_NAME = "No such name"; + private TestSNMPV1Agent snmpV1Agent; + + @Before + public void init() throws IOException { + snmpV1Agent = new TestSNMPV1Agent(LOCALHOST); + initAgent(snmpV1Agent); + } + + @After + public void tearDown() { + snmpV1Agent.stop(); + } + + @Test + public void testSuccessfulSnmpV1Get() throws IOException { Review comment: Minor: at the test method is in the SNMPV1RequestTest, it's not necessary to highlight the version in the method name as well ########## File path: nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/test/java/org/apache/nifi/snmp/operations/SNMPV2CRequestTest.java ########## @@ -0,0 +1,94 @@ +/* + * 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.snmp.operations; + +import org.apache.nifi.snmp.dto.SNMPResponse; +import org.apache.nifi.snmp.dto.SNMPWalkResponse; +import org.apache.nifi.snmp.exception.RequestTimeoutException; +import org.apache.nifi.snmp.testagents.TestSNMPV2cAgent; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.snmp4j.MessageException; +import org.snmp4j.mp.SnmpConstants; + +import java.io.IOException; + +import static org.junit.Assert.assertEquals; + +public class SNMPV2CRequestTest extends SNMPRequestTest { + + private TestSNMPV2cAgent snmpV2cAgent; + + @Before + public void init() throws IOException { Review comment: I think with using generic parameter on the class and adding a supplier method for the actual agent, these might be moved to the parent from all three test class ########## File path: nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/test/java/org/apache/nifi/snmp/factory/SNMPClientFactoryTest.java ########## @@ -0,0 +1,68 @@ +/* + * 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.snmp.factory; + +import org.apache.nifi.snmp.configuration.SNMPConfiguration; +import org.apache.nifi.snmp.configuration.SNMPConfigurationBuilder; +import org.junit.Test; +import org.snmp4j.Snmp; +import org.snmp4j.mp.SnmpConstants; +import org.snmp4j.security.UsmUser; +import org.snmp4j.smi.OID; +import org.snmp4j.smi.OctetString; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public class SNMPClientFactoryTest { + + private final SNMPConfigurationBuilder configurationBuilder = new SNMPConfigurationBuilder() + .setAgentHost("1.2.3.4") Review comment: Minor: would not it be better to use something like 127.0.0.1? ########## File path: nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/main/java/org/apache/nifi/snmp/processors/SetSNMP.java ########## @@ -34,191 +25,101 @@ import org.apache.nifi.processor.ProcessContext; import org.apache.nifi.processor.ProcessSession; import org.apache.nifi.processor.Relationship; -import org.apache.nifi.processor.exception.ProcessException; -import org.snmp4j.PDU; -import org.snmp4j.ScopedPDU; -import org.snmp4j.event.ResponseEvent; -import org.snmp4j.mp.SnmpConstants; -import org.snmp4j.smi.AbstractVariable; -import org.snmp4j.smi.AssignableFromInteger; -import org.snmp4j.smi.AssignableFromLong; -import org.snmp4j.smi.AssignableFromString; -import org.snmp4j.smi.OID; -import org.snmp4j.smi.OctetString; -import org.snmp4j.smi.Variable; -import org.snmp4j.smi.VariableBinding; +import org.apache.nifi.snmp.dto.SNMPResponse; +import org.apache.nifi.snmp.exception.SNMPException; +import org.apache.nifi.snmp.utils.SNMPUtils; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; /** * Performs a SNMP Set operation based on attributes of incoming FlowFile. * Upon each invocation of {@link #onTrigger(ProcessContext, ProcessSession)} * method, it will inspect attributes of FlowFile and look for attributes with * name formatted as "snmp$OID" to set the attribute value to this OID. */ -@Tags({ "snmp", "set", "oid" }) +@Tags({"snmp", "set", "oid"}) @InputRequirement(Requirement.INPUT_REQUIRED) @CapabilityDescription("Based on incoming FlowFile attributes, the processor will execute SNMP Set requests." + - " When founding attributes with name like snmp$<OID>, the processor will atempt to set the value of" + + " When founding attributes with name like snmp$<OID>, the processor will attempt to set the value of" + " attribute to the corresponding OID given in the attribute name") -public class SetSNMP extends AbstractSNMPProcessor<SNMPSetter> { +public class SetSNMP extends AbstractSNMPProcessor { - /** relationship for success */ public static final Relationship REL_SUCCESS = new Relationship.Builder() .name("success") .description("All FlowFiles that have been successfully used to perform SNMP Set are routed to this relationship") .build(); - /** relationship for failure */ + public static final Relationship REL_FAILURE = new Relationship.Builder() .name("failure") .description("All FlowFiles that failed during the SNMP Set care routed to this relationship") .build(); - /** list of properties descriptors */ - private final static List<PropertyDescriptor> propertyDescriptors; + private static final List<PropertyDescriptor> PROPERTY_DESCRIPTORS = Collections.unmodifiableList(Arrays.asList( + AGENT_HOST, + AGENT_PORT, + SNMP_VERSION, + SNMP_COMMUNITY, + SNMP_SECURITY_LEVEL, + SNMP_SECURITY_NAME, + SNMP_AUTH_PROTOCOL, + SNMP_AUTH_PASSWORD, + SNMP_PRIVACY_PROTOCOL, + SNMP_PRIVACY_PASSWORD, + SNMP_RETRIES, + SNMP_TIMEOUT + )); - /** list of relationships */ - private final static Set<Relationship> relationships; + private static final Set<Relationship> RELATIONSHIPS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + REL_SUCCESS, + REL_FAILURE + ))); - /* - * Will ensure that the list of property descriptors is build only once. - * Will also create a Set of relationships - */ - static { - List<PropertyDescriptor> _propertyDescriptors = new ArrayList<>(); - _propertyDescriptors.addAll(descriptors); - propertyDescriptors = Collections.unmodifiableList(_propertyDescriptors); - - Set<Relationship> _relationships = new HashSet<>(); - _relationships.add(REL_SUCCESS); - _relationships.add(REL_FAILURE); - relationships = Collections.unmodifiableSet(_relationships); - } - - /** - * @see org.apache.nifi.snmp.processors.AbstractSNMPProcessor#onTriggerSnmp(org.apache.nifi.processor.ProcessContext, org.apache.nifi.processor.ProcessSession) - */ @Override - protected void onTriggerSnmp(ProcessContext context, ProcessSession processSession) throws ProcessException { - FlowFile flowFile = processSession.get(); + public void onTrigger(final ProcessContext context, final ProcessSession processSession) { + final FlowFile flowFile = processSession.get(); if (flowFile != null) { - // Create the PDU object - PDU pdu = null; - if(this.snmpTarget.getVersion() == SnmpConstants.version3) { - pdu = new ScopedPDU(); - } else { - pdu = new PDU(); - } - if(this.addVariables(pdu, flowFile.getAttributes())) { - pdu.setType(PDU.SET); - try { - ResponseEvent response = this.targetResource.set(pdu); - if(response.getResponse() == null) { - processSession.transfer(processSession.penalize(flowFile), REL_FAILURE); - this.getLogger().error("Set request timed out or parameters are incorrect."); - context.yield(); - } else if(response.getResponse().getErrorStatus() == PDU.noError) { - flowFile = SNMPUtils.updateFlowFileAttributesWithPduProperties(pdu, flowFile, processSession); - processSession.transfer(flowFile, REL_SUCCESS); - processSession.getProvenanceReporter().send(flowFile, this.snmpTarget.getAddress().toString()); - } else { - final String error = response.getResponse().getErrorStatusText(); - flowFile = SNMPUtils.addAttribute(SNMPUtils.SNMP_PROP_PREFIX + "error", error, flowFile, processSession); - processSession.transfer(processSession.penalize(flowFile), REL_FAILURE); - this.getLogger().error("Failed while executing SNMP Set [{}] via " + this.targetResource + ". Error = {}", new Object[]{response.getRequest().getVariableBindings(), error}); - } - } catch (IOException e) { - processSession.transfer(processSession.penalize(flowFile), REL_FAILURE); - this.getLogger().error("Failed while executing SNMP Set via " + this.targetResource, e); - context.yield(); - } - } else { + try { + final SNMPResponse response = snmpRequestHandler.set(flowFile); + processResponse(processSession, flowFile, response); + } catch (SNMPException | IOException e) { processSession.transfer(processSession.penalize(flowFile), REL_FAILURE); - this.getLogger().warn("No attributes found in the FlowFile to perform SNMP Set"); + getLogger().error("Could not perform SNMP Set: Request timed out or parameters are incorrect."); Review comment: It would be good to add some details about the exception ########## File path: nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/test/java/org/apache/nifi/snmp/testagents/TestSNMPV1Agent.java ########## @@ -0,0 +1,72 @@ +/* + * 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.snmp.testagents; + +import org.snmp4j.agent.CommandProcessor; +import org.snmp4j.agent.mo.snmp.StorageType; +import org.snmp4j.agent.mo.snmp.VacmMIB; +import org.snmp4j.agent.security.MutableVACM; +import org.snmp4j.mp.MPv3; +import org.snmp4j.security.SecurityLevel; +import org.snmp4j.security.SecurityModel; +import org.snmp4j.smi.OID; +import org.snmp4j.smi.OctetString; + +import java.io.File; + +public class TestSNMPV1Agent extends TestAgent { + + public TestSNMPV1Agent(final String host) { + super(new File("target/bootCounter1.agent"), new File("target/conf1.agent"), Review comment: Will not this fail when the tests are running parallel? Using an UUID pre/postfix on the filename should solve this ########## File path: nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/main/java/org/apache/nifi/snmp/utils/SNMPUtils.java ########## @@ -0,0 +1,212 @@ +/* + * 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.snmp.utils; + +import org.apache.nifi.flowfile.FlowFile; +import org.apache.nifi.snmp.exception.InvalidAuthProtocolException; +import org.apache.nifi.snmp.exception.InvalidPrivProtocolException; +import org.apache.nifi.snmp.exception.InvalidSnmpVersionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.snmp4j.PDU; +import org.snmp4j.mp.SnmpConstants; +import org.snmp4j.security.AuthHMAC128SHA224; +import org.snmp4j.security.AuthHMAC192SHA256; +import org.snmp4j.security.AuthHMAC256SHA384; +import org.snmp4j.security.AuthHMAC384SHA512; +import org.snmp4j.security.AuthMD5; +import org.snmp4j.security.AuthSHA; +import org.snmp4j.security.Priv3DES; +import org.snmp4j.security.PrivAES128; +import org.snmp4j.security.PrivAES192; +import org.snmp4j.security.PrivAES256; +import org.snmp4j.security.PrivDES; +import org.snmp4j.smi.AbstractVariable; +import org.snmp4j.smi.AssignableFromInteger; +import org.snmp4j.smi.AssignableFromLong; +import org.snmp4j.smi.AssignableFromString; +import org.snmp4j.smi.OID; +import org.snmp4j.smi.OctetString; +import org.snmp4j.smi.Variable; +import org.snmp4j.smi.VariableBinding; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Vector; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * Utility helper class that simplifies interactions with target SNMP API and NIFI API. + */ +public final class SNMPUtils { + + public static final String SNMP_PROP_DELIMITER = "$"; + public static final String SNMP_PROP_PREFIX = "snmp" + SNMP_PROP_DELIMITER; + + private static final Logger logger = LoggerFactory.getLogger(SNMPUtils.class); + private static final String OID_PROP_PATTERN = SNMP_PROP_PREFIX + "%s" + SNMP_PROP_DELIMITER + "%s"; + private static final Pattern OID_PATTERN = Pattern.compile("[0-9+.]*"); + + private static final Map<String, OID> AUTH_MAP; + private static final Map<String, OID> PRIV_MAP; + private static final Map<String, Integer> VERSION_MAP; + + static { + final Map<String, OID> map = new HashMap<>(); + map.put("DES", PrivDES.ID); + map.put("3DES", Priv3DES.ID); + map.put("AES128", PrivAES128.ID); + map.put("AES192", PrivAES192.ID); + map.put("AES256", PrivAES256.ID); + PRIV_MAP = Collections.unmodifiableMap(map); + } + + static { + final Map<String, OID> map = new HashMap<>(); + map.put("SHA", AuthSHA.ID); + map.put("MD5", AuthMD5.ID); + map.put("HMAC128SHA224", AuthHMAC128SHA224.ID); + map.put("HMAC192SHA256", AuthHMAC192SHA256.ID); + map.put("HMAC256SHA384", AuthHMAC256SHA384.ID); + map.put("HMAC384SHA512", AuthHMAC384SHA512.ID); + AUTH_MAP = Collections.unmodifiableMap(map); + } + + static { + final Map<String, Integer> map = new HashMap<>(); + map.put("SNMPv1", SnmpConstants.version1); + map.put("SNMPv2c", SnmpConstants.version2c); + map.put("SNMPv3", SnmpConstants.version3); + VERSION_MAP = Collections.unmodifiableMap(map); + } + + public static Map<String, String> getPduAttributeMap(final PDU response) { + final Vector<? extends VariableBinding> variableBindings = response.getVariableBindings(); + final Map<String, String> attributes = variableBindings.stream() + .collect(Collectors.toMap(k -> String.format(OID_PROP_PATTERN, k.getOid(), k.getSyntax()), + v -> v.getVariable().toString() + )); + + attributes.computeIfAbsent(SNMP_PROP_PREFIX + "errorIndex", v -> String.valueOf(response.getErrorIndex())); + attributes.computeIfAbsent(SNMP_PROP_PREFIX + "errorStatus", v -> String.valueOf(response.getErrorStatus())); + attributes.computeIfAbsent(SNMP_PROP_PREFIX + "errorStatusText", v -> response.getErrorStatusText()); + attributes.computeIfAbsent(SNMP_PROP_PREFIX + "nonRepeaters", v -> String.valueOf(response.getNonRepeaters())); + attributes.computeIfAbsent(SNMP_PROP_PREFIX + "requestID", v -> String.valueOf(response.getRequestID())); + attributes.computeIfAbsent(SNMP_PROP_PREFIX + "type", v -> String.valueOf(response.getType())); + + return attributes; + } + + /** + * Method to construct {@link FlowFile} attributes from a vector of {@link VariableBinding} + * + * @param variableBindings list of {@link VariableBinding} + * @return the attributes map + */ + public static Map<String, String> createWalkOidValuesMap(final List<VariableBinding> variableBindings) { + final Map<String, String> attributes = new HashMap<>(); + variableBindings.forEach(vb -> addAttributeFromVariable(vb, attributes)); + return attributes; + } + + public static OID getPriv(final String privProtocol) { + if (PRIV_MAP.containsKey(privProtocol)) { + return PRIV_MAP.get(privProtocol); + } + throw new InvalidPrivProtocolException("Invalid privacy protocol provided."); + } + + + public static OID getAuth(final String authProtocol) { + if (AUTH_MAP.containsKey(authProtocol)) { + return AUTH_MAP.get(authProtocol); + } + throw new InvalidAuthProtocolException("Invalid authentication protocol provided."); + } + + public static int getVersion(final String snmpVersion) { + return Optional.ofNullable(VERSION_MAP.get(snmpVersion)) + .orElseThrow(() -> new InvalidSnmpVersionException("Invalid SNMP version provided.")); + } + + /** + * Method to construct {@link VariableBinding} based on {@link FlowFile} + * attributes in order to update the {@link PDU} that is going to be sent to + * the SNMP Agent. + * + * @param pdu {@link PDU} to be sent + * @param attributes {@link FlowFile} attributes + * @return true if at least one {@link VariableBinding} has been created, false otherwise + */ + public static boolean addVariables(final PDU pdu, final Map<String, String> attributes) { + boolean result = false; + for (Map.Entry<String, String> attributeEntry : attributes.entrySet()) { + if (attributeEntry.getKey().startsWith(SNMPUtils.SNMP_PROP_PREFIX)) { + final String[] splits = attributeEntry.getKey().split("\\" + SNMPUtils.SNMP_PROP_DELIMITER); + final String snmpPropName = splits[1]; + final String snmpPropValue = attributeEntry.getValue(); + if (SNMPUtils.OID_PATTERN.matcher(snmpPropName).matches()) { + final Optional<Variable> var; + if (splits.length == 2) { // no SMI syntax defined + var = Optional.of(new OctetString(snmpPropValue)); + } else { + final int smiSyntax = Integer.parseInt(splits[2]); + var = SNMPUtils.stringToVariable(snmpPropValue, smiSyntax); + } + if (var.isPresent()) { + final VariableBinding varBind = new VariableBinding(new OID(snmpPropName), var.get()); + pdu.add(varBind); + result = true; + } + } + } + } + return result; + } + + private static void addAttributeFromVariable(final VariableBinding variableBinding, final Map<String, String> attributes) { + attributes.put(SNMP_PROP_PREFIX + variableBinding.getOid() + SNMP_PROP_DELIMITER + variableBinding.getVariable().getSyntax(), variableBinding.getVariable().toString()); + } + + private static Optional<Variable> stringToVariable(final String value, final int smiSyntax) { + Variable var = AbstractVariable.createFromSyntax(smiSyntax); + try { + if (var instanceof AssignableFromString) { + ((AssignableFromString) var).setValue(value); + } else if (var instanceof AssignableFromInteger) { + ((AssignableFromInteger) var).setValue(Integer.parseInt(value)); + } else if (var instanceof AssignableFromLong) { + ((AssignableFromLong) var).setValue(Long.parseLong(value)); + } else { + logger.error("Unsupported conversion of [ {} ] to ", var.getSyntaxString()); + var = null; + } + } catch (IllegalArgumentException e) { + logger.error("Unsupported conversion of [ {} ] to ", var.getSyntaxString(), e); + var = null; + } + return Optional.ofNullable(var); + } + + private SNMPUtils() { Review comment: Please put this before the methods ########## File path: nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/main/java/org/apache/nifi/snmp/processors/AbstractSNMPProcessor.java ########## @@ -16,413 +16,257 @@ */ package org.apache.nifi.snmp.processors; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - +import org.apache.nifi.annotation.behavior.RequiresInstanceClassLoading; +import org.apache.nifi.annotation.lifecycle.OnScheduled; import org.apache.nifi.annotation.lifecycle.OnStopped; +import org.apache.nifi.components.AllowableValue; import org.apache.nifi.components.PropertyDescriptor; -import org.apache.nifi.components.ValidationContext; -import org.apache.nifi.components.ValidationResult; +import org.apache.nifi.flowfile.FlowFile; import org.apache.nifi.processor.AbstractProcessor; import org.apache.nifi.processor.ProcessContext; import org.apache.nifi.processor.ProcessSession; -import org.apache.nifi.processor.Processor; -import org.apache.nifi.processor.exception.ProcessException; import org.apache.nifi.processor.util.StandardValidators; -import org.snmp4j.AbstractTarget; -import org.snmp4j.CommunityTarget; -import org.snmp4j.Snmp; -import org.snmp4j.TransportMapping; -import org.snmp4j.UserTarget; -import org.snmp4j.mp.MPv3; -import org.snmp4j.mp.SnmpConstants; -import org.snmp4j.security.SecurityModels; -import org.snmp4j.security.SecurityProtocols; -import org.snmp4j.security.USM; -import org.snmp4j.security.UsmUser; -import org.snmp4j.smi.OctetString; -import org.snmp4j.smi.UdpAddress; -import org.snmp4j.transport.DefaultUdpTransportMapping; +import org.apache.nifi.reporting.InitializationException; +import org.apache.nifi.snmp.configuration.SNMPConfiguration; +import org.apache.nifi.snmp.configuration.SNMPConfigurationBuilder; +import org.apache.nifi.snmp.logging.SLF4JLogFactory; +import org.apache.nifi.snmp.operations.SNMPRequestHandler; +import org.apache.nifi.snmp.operations.SNMPRequestHandlerFactory; +import org.apache.nifi.snmp.utils.SNMPUtils; +import org.snmp4j.log.LogFactory; + +import java.util.HashMap; +import java.util.Map; /** - * Base processor that uses SNMP4J client API + * Base processor that uses SNMP4J client API. * (http://www.snmp4j.org/) - * - * @param <T> the type of {@link SNMPWorker}. Please see {@link SNMPSetter} - * and {@link SNMPGetter} */ -abstract class AbstractSNMPProcessor<T extends SNMPWorker> extends AbstractProcessor { +@RequiresInstanceClassLoading +abstract class AbstractSNMPProcessor extends AbstractProcessor { + + static { + LogFactory.setLogFactory(new SLF4JLogFactory()); + } - /** property to define host of the SNMP agent */ - public static final PropertyDescriptor HOST = new PropertyDescriptor.Builder() + private static final String SHA_2_BASED_AUTHENTICATION = "SHA-2 based authentication"; + private static final String SHA_2_ALGORITHM = "Provides authentication based on the HMAC-SHA-2 algorithm."; + + // SNMP versions + public static final AllowableValue SNMP_V1 = new AllowableValue("SNMPv1", "v1", "SNMP version 1"); + public static final AllowableValue SNMP_V2C = new AllowableValue("SNMPv2c", "v2c", "SNMP version 2c"); + public static final AllowableValue SNMP_V3 = new AllowableValue("SNMPv3", "v3", "SNMP version 3 with improved security"); + + // SNMPv3 privacy protocols + public static final AllowableValue NO_AUTH_NO_PRIV = new AllowableValue("noAuthNoPriv", "No authentication or encryption", + "No authentication or encryption."); + public static final AllowableValue AUTH_NO_PRIV = new AllowableValue("authNoPriv", "Authentication without encryption", + "Authentication without encryption."); + public static final AllowableValue AUTH_PRIV = new AllowableValue("authPriv", "Authentication and encryption", + "Authentication and encryption."); + + // SNMPv3 authentication protocols + public static final AllowableValue MD5 = new AllowableValue("MD5", "MD5 based authentication", + "Provides authentication based on the HMAC-MD5 algorithm."); + public static final AllowableValue SHA = new AllowableValue("SHA", "SHA based authentication", + "Provides authentication based on the HMAC-SHA algorithm."); + public static final AllowableValue HMAC128SHA224 = new AllowableValue("HMAC128SHA224", SHA_2_BASED_AUTHENTICATION, + SHA_2_ALGORITHM); + public static final AllowableValue HMAC192SHA256 = new AllowableValue("HMAC192SHA256", SHA_2_BASED_AUTHENTICATION, + SHA_2_ALGORITHM); + public static final AllowableValue HMAC256SHA384 = new AllowableValue("HMAC256SHA384", SHA_2_BASED_AUTHENTICATION, + SHA_2_ALGORITHM); + public static final AllowableValue HMAC384SHA512 = new AllowableValue("HMAC384SHA512", SHA_2_BASED_AUTHENTICATION, + SHA_2_ALGORITHM); + + // SNMPv3 encryption + public static final AllowableValue DES = new AllowableValue("DES", "DES", + "Symmetric-key algorithm for the encryption of digital data. DES has been considered insecure" + + "because of the feasilibity of brute-force attacks. We recommend using the AES encryption protocol."); + public static final AllowableValue DES3 = new AllowableValue("3DES", "3DES", + "Symmetric-key block cipher, which applies the DES cipher algorithm three times to each data block." + + " 3DES has been considered insecure has been deprecated by NIST in 2017. We recommend using the AES encryption protocol."); + + private static final String AES_DESCRIPTION = "AES is a symmetric algorithm which uses the same 128, 192, or 256 bit" + + " key for both encryption and decryption (the security of an AES system increases exponentially with key length)."; + + public static final AllowableValue AES128 = new AllowableValue("AES128", "AES128", AES_DESCRIPTION); + public static final AllowableValue AES192 = new AllowableValue("AES192", "AES192", AES_DESCRIPTION); + public static final AllowableValue AES256 = new AllowableValue("AES256", "AES256", AES_DESCRIPTION); + + public static final PropertyDescriptor AGENT_HOST = new PropertyDescriptor.Builder() .name("snmp-hostname") - .displayName("Host Name") - .description("Network address of SNMP Agent (e.g., localhost)") + .displayName("SNMP Agent Hostname") + .description("Network address of the SNMP Agent.") .required(true) .defaultValue("localhost") .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .build(); - /** property to define port of the SNMP agent */ - public static final PropertyDescriptor PORT = new PropertyDescriptor.Builder() + public static final PropertyDescriptor AGENT_PORT = new PropertyDescriptor.Builder() .name("snmp-port") - .displayName("Port") - .description("Numeric value identifying Port of SNMP Agent (e.g., 161)") + .displayName("SNMP Agent Port") + .description("Numeric value identifying the port of SNMP Agent.") .required(true) .defaultValue("161") .addValidator(StandardValidators.PORT_VALIDATOR) .build(); - /** property to define SNMP version to use */ public static final PropertyDescriptor SNMP_VERSION = new PropertyDescriptor.Builder() .name("snmp-version") .displayName("SNMP Version") - .description("SNMP Version to use") + .description("Three significant versions of SNMP have been developed and deployed. " + + "SNMPv1 is the original version of the protocol. More recent versions, " + + "SNMPv2c and SNMPv3, feature improvements in performance, flexibility and security.") .required(true) - .allowableValues("SNMPv1", "SNMPv2c", "SNMPv3") - .defaultValue("SNMPv1") + .allowableValues(SNMP_V1, SNMP_V2C, SNMP_V3) + .defaultValue(SNMP_V1.getValue()) .build(); - /** property to define SNMP community to use */ public static final PropertyDescriptor SNMP_COMMUNITY = new PropertyDescriptor.Builder() .name("snmp-community") - .displayName("SNMP Community (v1 & v2c)") - .description("SNMP Community to use (e.g., public)") - .required(false) + .displayName("SNMP Community") + .description("SNMPv1 and SNMPv2 use communities to establish trust between managers and agents." + + " Most agents support three community names, one each for read-only, read-write and trap." + + " These three community strings control different types of activities. The read-only community" + + " applies to get requests. The read-write community string applies to set requests. The trap" + + " community string applies to receipt of traps.") + .required(true) + .sensitive(true) .defaultValue("public") .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) + .dependsOn(SNMP_VERSION, SNMP_V1, SNMP_V2C) .build(); - /** property to define SNMP security level to use */ public static final PropertyDescriptor SNMP_SECURITY_LEVEL = new PropertyDescriptor.Builder() .name("snmp-security-level") .displayName("SNMP Security Level") - .description("SNMP Security Level to use") + .description("SNMP version 3 provides extra security with User Based Security Model (USM). The three levels of security is " + + "1. Communication without authentication and encryption (NoAuthNoPriv). " + + "2. Communication with authentication and without encryption (AuthNoPriv). " + + "3. Communication with authentication and encryption (AuthPriv).") .required(true) - .allowableValues("noAuthNoPriv", "authNoPriv", "authPriv") - .defaultValue("authPriv") + .allowableValues(NO_AUTH_NO_PRIV, AUTH_NO_PRIV, AUTH_PRIV) + .defaultValue(NO_AUTH_NO_PRIV.getValue()) + .dependsOn(SNMP_VERSION, SNMP_V3) .build(); - /** property to define SNMP security name to use */ public static final PropertyDescriptor SNMP_SECURITY_NAME = new PropertyDescriptor.Builder() .name("snmp-security-name") - .displayName("SNMP Security name / user name") - .description("Security name used for SNMP exchanges") - .required(false) + .displayName("SNMP Security Name") + .description("User name used for SNMP v3 Authentication.") + .required(true) .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) + .dependsOn(SNMP_VERSION, SNMP_V3) .build(); - /** property to define SNMP authentication protocol to use */ public static final PropertyDescriptor SNMP_AUTH_PROTOCOL = new PropertyDescriptor.Builder() .name("snmp-authentication-protocol") .displayName("SNMP Authentication Protocol") - .description("SNMP Authentication Protocol to use") + .description("Hash based authentication protocol for secure authentication.") .required(true) - .allowableValues("MD5", "SHA", "") - .defaultValue("") + .allowableValues(MD5, SHA, HMAC128SHA224, HMAC192SHA256, HMAC256SHA384, HMAC384SHA512) + .dependsOn(SNMP_SECURITY_LEVEL, AUTH_NO_PRIV, AUTH_PRIV) .build(); - /** property to define SNMP authentication password to use */ public static final PropertyDescriptor SNMP_AUTH_PASSWORD = new PropertyDescriptor.Builder() .name("snmp-authentication-passphrase") - .displayName("SNMP Authentication pass phrase") - .description("Pass phrase used for SNMP authentication protocol") - .required(false) + .displayName("SNMP Authentication Passphrase") + .description("Passphrase used for SNMP authentication protocol.") + .required(true) .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .sensitive(true) + .dependsOn(SNMP_SECURITY_LEVEL, AUTH_NO_PRIV, AUTH_PRIV) .build(); - /** property to define SNMP private protocol to use */ - public static final PropertyDescriptor SNMP_PRIV_PROTOCOL = new PropertyDescriptor.Builder() + public static final PropertyDescriptor SNMP_PRIVACY_PROTOCOL = new PropertyDescriptor.Builder() .name("snmp-private-protocol") - .displayName("SNMP Private Protocol") - .description("SNMP Private Protocol to use") + .displayName("SNMP Privacy Protocol") + .description("Privacy allows for encryption of SNMP v3 messages to ensure confidentiality of data.") .required(true) - .allowableValues("DES", "3DES", "AES128", "AES192", "AES256", "") - .defaultValue("") + .allowableValues(DES, DES3, AES128, AES192, AES256) + .dependsOn(SNMP_SECURITY_LEVEL, AUTH_PRIV) .build(); - /** property to define SNMP private password to use */ - public static final PropertyDescriptor SNMP_PRIV_PASSWORD = new PropertyDescriptor.Builder() + public static final PropertyDescriptor SNMP_PRIVACY_PASSWORD = new PropertyDescriptor.Builder() .name("snmp-private-protocol-passphrase") - .displayName("SNMP Private protocol pass phrase") - .description("Pass phrase used for SNMP private protocol") - .required(false) + .displayName("SNMP Privacy Passphrase") + .description("Passphrase used for SNMP privacy protocol.") + .required(true) .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .sensitive(true) + .dependsOn(SNMP_SECURITY_LEVEL, AUTH_PRIV) .build(); - /** property to define the number of SNMP retries when requesting the SNMP Agent */ public static final PropertyDescriptor SNMP_RETRIES = new PropertyDescriptor.Builder() .name("snmp-retries") - .displayName("Number of retries") - .description("Set the number of retries when requesting the SNMP Agent") - .required(true) + .displayName("Number of Retries") + .description("Set the number of retries when requesting the SNMP Agent.") + .required(false) .defaultValue("0") - .addValidator(StandardValidators.INTEGER_VALIDATOR) + .addValidator(StandardValidators.NON_NEGATIVE_INTEGER_VALIDATOR) .build(); - /** property to define the timeout when requesting the SNMP Agent */ public static final PropertyDescriptor SNMP_TIMEOUT = new PropertyDescriptor.Builder() .name("snmp-timeout") .displayName("Timeout (ms)") - .description("Set the timeout (in milliseconds) when requesting the SNMP Agent") - .required(true) + .description("Set the timeout (in milliseconds) when requesting the SNMP Agent.") + .required(false) .defaultValue("5000") - .addValidator(StandardValidators.INTEGER_VALIDATOR) + .addValidator(StandardValidators.NON_NEGATIVE_INTEGER_VALIDATOR) .build(); - /** list of property descriptors */ - static List<PropertyDescriptor> descriptors = new ArrayList<>(); - /* - * Will ensure that list of PropertyDescriptors is build only once, since - * all other life cycle methods are invoked multiple times. - */ - static { - descriptors.add(HOST); - descriptors.add(PORT); - descriptors.add(SNMP_VERSION); - descriptors.add(SNMP_COMMUNITY); - descriptors.add(SNMP_SECURITY_LEVEL); - descriptors.add(SNMP_SECURITY_NAME); - descriptors.add(SNMP_AUTH_PROTOCOL); - descriptors.add(SNMP_AUTH_PASSWORD); - descriptors.add(SNMP_PRIV_PROTOCOL); - descriptors.add(SNMP_PRIV_PASSWORD); - descriptors.add(SNMP_RETRIES); - descriptors.add(SNMP_TIMEOUT); - } - - /** SNMP target */ - protected volatile AbstractTarget snmpTarget; - - /** transport mapping */ - protected volatile TransportMapping transportMapping; + protected volatile SNMPRequestHandler snmpRequestHandler; - /** SNMP */ - protected volatile Snmp snmp; - - /** target resource */ - protected volatile T targetResource; - - /** - * Will builds target resource upon first invocation and will delegate to the - * implementation of {@link #onTriggerSnmp(ProcessContext, ProcessSession)} method for - * further processing. - */ - @Override - public void onTrigger(ProcessContext context, ProcessSession session) throws ProcessException { - synchronized (this) { - this.buildTargetResource(context); + @OnScheduled + public void initSnmpClient(final ProcessContext context) throws InitializationException { + final int version = SNMPUtils.getVersion(context.getProperty(SNMP_VERSION).getValue()); + SNMPConfiguration configuration; + try { + configuration = new SNMPConfigurationBuilder() + .setAgentHost(context.getProperty(AGENT_HOST).getValue()) + .setAgentPort(context.getProperty(AGENT_PORT).toString()) + .setRetries(context.getProperty(SNMP_RETRIES).asInteger()) + .setTimeout(context.getProperty(SNMP_TIMEOUT).asInteger()) + .setVersion(version) + .setAuthProtocol(context.getProperty(SNMP_AUTH_PROTOCOL).getValue()) + .setAuthPassphrase(context.getProperty(SNMP_AUTH_PASSWORD).getValue()) + .setPrivacyProtocol(context.getProperty(SNMP_PRIVACY_PROTOCOL).getValue()) + .setPrivacyPassphrase(context.getProperty(SNMP_PRIVACY_PASSWORD).getValue()) + .setSecurityName(context.getProperty(SNMP_SECURITY_NAME).getValue()) + .setSecurityLevel(context.getProperty(SNMP_SECURITY_LEVEL).getValue()) + .setCommunityString(context.getProperty(SNMP_COMMUNITY).getValue()) + .build(); + } catch (IllegalStateException e) { + throw new InitializationException(e); } - this.onTriggerSnmp(context, session); + snmpRequestHandler = SNMPRequestHandlerFactory.createStandardRequestHandler(configuration); } /** - * Will close current SNMP mapping. + * Closes the current SNMP mapping. */ @OnStopped public void close() { - try { - if (this.targetResource != null) { - this.targetResource.close(); - } - } catch (Exception e) { - this.getLogger().warn("Failure while closing target resource " + this.targetResource, e); - } - this.targetResource = null; - - try { - if (this.transportMapping != null) { - this.transportMapping.close(); - } - } catch (IOException e) { - this.getLogger().warn("Failure while closing UDP transport mapping", e); - } - this.transportMapping = null; - - try { - if (this.snmp != null) { - this.snmp.close(); - } - } catch (IOException e) { - this.getLogger().warn("Failure while closing UDP transport mapping", e); + if (snmpRequestHandler != null) { + snmpRequestHandler.close(); Review comment: Set it null as well after closing would makes sense (releasing objects, etc.) ########## File path: nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/main/java/org/apache/nifi/snmp/processors/GetSNMP.java ########## @@ -16,184 +16,186 @@ */ package org.apache.nifi.snmp.processors; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - import org.apache.nifi.annotation.behavior.InputRequirement; import org.apache.nifi.annotation.behavior.InputRequirement.Requirement; import org.apache.nifi.annotation.behavior.WritesAttribute; import org.apache.nifi.annotation.behavior.WritesAttributes; import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.Tags; +import org.apache.nifi.components.AllowableValue; import org.apache.nifi.components.PropertyDescriptor; import org.apache.nifi.flowfile.FlowFile; import org.apache.nifi.processor.ProcessContext; import org.apache.nifi.processor.ProcessSession; -import org.apache.nifi.processor.Processor; import org.apache.nifi.processor.Relationship; import org.apache.nifi.processor.util.StandardValidators; -import org.apache.nifi.processor.exception.ProcessException; -import org.snmp4j.PDU; -import org.snmp4j.event.ResponseEvent; -import org.snmp4j.smi.OID; -import org.snmp4j.util.TreeEvent; +import org.apache.nifi.snmp.dto.SNMPResponse; +import org.apache.nifi.snmp.dto.SNMPWalkResponse; +import org.apache.nifi.snmp.exception.SNMPException; +import org.apache.nifi.snmp.exception.SNMPWalkException; +import org.apache.nifi.snmp.utils.SNMPUtils; +import org.apache.nifi.snmp.validators.OIDValidator; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; /** * Retrieving data from configured SNMP agent which, upon each invocation of * {@link #onTrigger(ProcessContext, ProcessSession)} method, will construct a * {@link FlowFile} containing in its properties the information retrieved. * The output {@link FlowFile} won't have any content. */ -@Tags({ "snmp", "get", "oid", "walk" }) +@Tags({"snmp", "get", "oid", "walk"}) @InputRequirement(Requirement.INPUT_FORBIDDEN) @CapabilityDescription("Retrieves information from SNMP Agent and outputs a FlowFile with information in attributes and without any content") @WritesAttributes({ - @WritesAttribute(attribute=SNMPUtils.SNMP_PROP_PREFIX + "*", description="Attributes retrieved from the SNMP response. It may include:" - + " snmp$errorIndex, snmp$errorStatus, snmp$errorStatusText, snmp$nonRepeaters, snmp$requestID, snmp$type, snmp$variableBindings"), - @WritesAttribute(attribute=SNMPUtils.SNMP_PROP_PREFIX + "textualOid", description="This attribute will exist if and only if the strategy" - + " is GET and will be equal to the value given in Textual Oid property.") + @WritesAttribute(attribute = SNMPUtils.SNMP_PROP_PREFIX + "*", description = "Attributes retrieved from the SNMP response. It may include:" + + " snmp$errorIndex, snmp$errorStatus, snmp$errorStatusText, snmp$nonRepeaters, snmp$requestID, snmp$type, snmp$variableBindings"), + @WritesAttribute(attribute = SNMPUtils.SNMP_PROP_PREFIX + "textualOid", description = "This attribute will exist if and only if the strategy" + + " is GET and will be equal to the value given in Textual Oid property.") }) -public class GetSNMP extends AbstractSNMPProcessor<SNMPGetter> { +public class GetSNMP extends AbstractSNMPProcessor { + + // SNMP strategies + public static final AllowableValue GET = new AllowableValue("GET", "GET", + "A manager-to-agent request to retrieve the value of a variable. A response with the current value returned."); + public static final AllowableValue WALK = new AllowableValue("WALK", "WALK", + "A manager-to-agent request to retrieve the value of multiple variables. Snmp WALK also traverses all subnodes " + + "under the specified OID."); - /** OID to request (if walk, it is the root ID of the request) */ + // OID to request (if walk, it is the root ID of the request). public static final PropertyDescriptor OID = new PropertyDescriptor.Builder() .name("snmp-oid") .displayName("OID") - .description("The OID to request") + .description("Each OID (object identifier) identifies a variable that can be read or set via SNMP.") .required(true) - .addValidator(SNMPUtils.SNMP_OID_VALIDATOR) + .addValidator(new OIDValidator()) + .build(); + + public static final PropertyDescriptor SNMP_STRATEGY = new PropertyDescriptor.Builder() + .name("snmp-strategy") + .displayName("SNMP Strategy") + .description("SNMP strategy to use (SNMP Get or SNMP Walk)") + .required(true) + .allowableValues(GET, WALK) + .defaultValue(GET.getValue()) .build(); - /** Textual OID to request */ public static final PropertyDescriptor TEXTUAL_OID = new PropertyDescriptor.Builder() .name("snmp-textual-oid") .displayName("Textual OID") - .description("The textual OID to request") + .description("The textual OID to request.") .required(false) .addValidator(StandardValidators.NON_BLANK_VALIDATOR) .defaultValue(null) .build(); - /** SNMP strategy for SNMP Get processor : simple get or walk */ - public static final PropertyDescriptor SNMP_STRATEGY = new PropertyDescriptor.Builder() - .name("snmp-strategy") - .displayName("SNMP strategy (GET/WALK)") - .description("SNMP strategy to use (SNMP Get or SNMP Walk)") - .required(true) - .allowableValues("GET", "WALK") - .defaultValue("GET") - .build(); - - /** relationship for success */ public static final Relationship REL_SUCCESS = new Relationship.Builder() .name("success") - .description("All FlowFiles that are received from the SNMP agent are routed to this relationship") + .description("All FlowFiles that are received from the SNMP agent are routed to this relationship.") .build(); - /** relationship for failure */ public static final Relationship REL_FAILURE = new Relationship.Builder() .name("failure") - .description("All FlowFiles that cannot received from the SNMP agent are routed to this relationship") + .description("All FlowFiles that cannot received from the SNMP agent are routed to this relationship.") .build(); - /** list of property descriptors */ - private final static List<PropertyDescriptor> propertyDescriptors; - - /** list of relationships */ - private final static Set<Relationship> relationships; - - /* - * Will ensure that the list of property descriptors is build only once. - * Will also create a Set of relationships - */ - static { - List<PropertyDescriptor> _propertyDescriptors = new ArrayList<>(); - _propertyDescriptors.add(OID); - _propertyDescriptors.add(TEXTUAL_OID); - _propertyDescriptors.add(SNMP_STRATEGY); - _propertyDescriptors.addAll(descriptors); - propertyDescriptors = Collections.unmodifiableList(_propertyDescriptors); - - Set<Relationship> _relationships = new HashSet<>(); - _relationships.add(REL_SUCCESS); - _relationships.add(REL_FAILURE); - relationships = Collections.unmodifiableSet(_relationships); - } + protected static final List<PropertyDescriptor> PROPERTY_DESCRIPTORS = Collections.unmodifiableList(Arrays.asList( + AGENT_HOST, + AGENT_PORT, + SNMP_VERSION, + SNMP_COMMUNITY, + SNMP_SECURITY_LEVEL, + SNMP_SECURITY_NAME, + SNMP_AUTH_PROTOCOL, + SNMP_AUTH_PASSWORD, + SNMP_PRIVACY_PROTOCOL, + SNMP_PRIVACY_PASSWORD, + SNMP_RETRIES, + SNMP_TIMEOUT, + OID, + TEXTUAL_OID, + SNMP_STRATEGY + )); + + private static final Set<Relationship> RELATIONSHIPS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + REL_SUCCESS, + REL_FAILURE + ))); - /** - * Delegate method to supplement - * {@link #onTrigger(ProcessContext, ProcessSession)}. It is implemented by - * sub-classes to perform {@link Processor} specific functionality. - * - * @param context - * instance of {@link ProcessContext} - * @param processSession - * instance of {@link ProcessSession} - * @throws ProcessException Process exception - */ @Override - protected void onTriggerSnmp(ProcessContext context, ProcessSession processSession) throws ProcessException { - if("GET".equals(context.getProperty(SNMP_STRATEGY).getValue())) { - final ResponseEvent response = this.targetResource.get(); - if (response.getResponse() != null){ - FlowFile flowFile = processSession.create(); - PDU pdu = response.getResponse(); - flowFile = SNMPUtils.updateFlowFileAttributesWithPduProperties(pdu, flowFile, processSession); - flowFile = SNMPUtils.addAttribute(SNMPUtils.SNMP_PROP_PREFIX + "textualOid", - context.getProperty(TEXTUAL_OID).getValue(), flowFile, processSession); - processSession.getProvenanceReporter().receive(flowFile, - this.snmpTarget.getAddress().toString() + "/" + context.getProperty(OID).getValue()); - if(pdu.getErrorStatus() == PDU.noError) { - processSession.transfer(flowFile, REL_SUCCESS); - } else { - processSession.transfer(flowFile, REL_FAILURE); - } - } else { - this.getLogger().error("Get request timed out or parameters are incorrect."); - context.yield(); - } - } else if("WALK".equals(context.getProperty(SNMP_STRATEGY).getValue())) { - final List<TreeEvent> events = this.targetResource.walk(); - if((events != null) && !events.isEmpty() && (events.get(0).getVariableBindings() != null)) { - FlowFile flowFile = processSession.create(); - for (TreeEvent treeEvent : events) { - flowFile = SNMPUtils.updateFlowFileAttributesWithTreeEventProperties(treeEvent, flowFile, processSession); - } - processSession.getProvenanceReporter().receive(flowFile, - this.snmpTarget.getAddress().toString() + "/" + context.getProperty(OID).getValue()); + public void onTrigger(final ProcessContext context, final ProcessSession processSession) { + final SNMPStrategy snmpStrategy = SNMPStrategy.valueOf(context.getProperty(SNMP_STRATEGY).getValue()); + final String oid = context.getProperty(OID).getValue(); + + if (SNMPStrategy.GET == snmpStrategy) { + performSnmpGet(context, processSession, oid); + } else if (SNMPStrategy.WALK == snmpStrategy) { + performSnmpWalk(context, processSession, oid); + } + } + + private void performSnmpWalk(final ProcessContext context, final ProcessSession processSession, final String oid) { + try { + final SNMPWalkResponse response = snmpRequestHandler.walk(oid); + FlowFile flowFile = createFlowFileWithTreeEventProperties(response, processSession); + processSession.getProvenanceReporter().receive(flowFile, response.getTargetUri() + "/" + oid); + processSession.transfer(flowFile, REL_SUCCESS); + } catch (SNMPWalkException e) { + getLogger().error("Could not perform SNMP Walk: Check if SNMP agent is accessible and the specified OID exists."); Review comment: It would be good to add some details about the exception ########## File path: nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/main/java/org/apache/nifi/snmp/processors/GetSNMP.java ########## @@ -16,184 +16,186 @@ */ package org.apache.nifi.snmp.processors; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - import org.apache.nifi.annotation.behavior.InputRequirement; import org.apache.nifi.annotation.behavior.InputRequirement.Requirement; import org.apache.nifi.annotation.behavior.WritesAttribute; import org.apache.nifi.annotation.behavior.WritesAttributes; import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.Tags; +import org.apache.nifi.components.AllowableValue; import org.apache.nifi.components.PropertyDescriptor; import org.apache.nifi.flowfile.FlowFile; import org.apache.nifi.processor.ProcessContext; import org.apache.nifi.processor.ProcessSession; -import org.apache.nifi.processor.Processor; import org.apache.nifi.processor.Relationship; import org.apache.nifi.processor.util.StandardValidators; -import org.apache.nifi.processor.exception.ProcessException; -import org.snmp4j.PDU; -import org.snmp4j.event.ResponseEvent; -import org.snmp4j.smi.OID; -import org.snmp4j.util.TreeEvent; +import org.apache.nifi.snmp.dto.SNMPResponse; +import org.apache.nifi.snmp.dto.SNMPWalkResponse; +import org.apache.nifi.snmp.exception.SNMPException; +import org.apache.nifi.snmp.exception.SNMPWalkException; +import org.apache.nifi.snmp.utils.SNMPUtils; +import org.apache.nifi.snmp.validators.OIDValidator; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; /** * Retrieving data from configured SNMP agent which, upon each invocation of * {@link #onTrigger(ProcessContext, ProcessSession)} method, will construct a * {@link FlowFile} containing in its properties the information retrieved. * The output {@link FlowFile} won't have any content. */ -@Tags({ "snmp", "get", "oid", "walk" }) +@Tags({"snmp", "get", "oid", "walk"}) @InputRequirement(Requirement.INPUT_FORBIDDEN) @CapabilityDescription("Retrieves information from SNMP Agent and outputs a FlowFile with information in attributes and without any content") @WritesAttributes({ - @WritesAttribute(attribute=SNMPUtils.SNMP_PROP_PREFIX + "*", description="Attributes retrieved from the SNMP response. It may include:" - + " snmp$errorIndex, snmp$errorStatus, snmp$errorStatusText, snmp$nonRepeaters, snmp$requestID, snmp$type, snmp$variableBindings"), - @WritesAttribute(attribute=SNMPUtils.SNMP_PROP_PREFIX + "textualOid", description="This attribute will exist if and only if the strategy" - + " is GET and will be equal to the value given in Textual Oid property.") + @WritesAttribute(attribute = SNMPUtils.SNMP_PROP_PREFIX + "*", description = "Attributes retrieved from the SNMP response. It may include:" + + " snmp$errorIndex, snmp$errorStatus, snmp$errorStatusText, snmp$nonRepeaters, snmp$requestID, snmp$type, snmp$variableBindings"), + @WritesAttribute(attribute = SNMPUtils.SNMP_PROP_PREFIX + "textualOid", description = "This attribute will exist if and only if the strategy" + + " is GET and will be equal to the value given in Textual Oid property.") }) -public class GetSNMP extends AbstractSNMPProcessor<SNMPGetter> { +public class GetSNMP extends AbstractSNMPProcessor { + + // SNMP strategies + public static final AllowableValue GET = new AllowableValue("GET", "GET", + "A manager-to-agent request to retrieve the value of a variable. A response with the current value returned."); + public static final AllowableValue WALK = new AllowableValue("WALK", "WALK", + "A manager-to-agent request to retrieve the value of multiple variables. Snmp WALK also traverses all subnodes " + + "under the specified OID."); - /** OID to request (if walk, it is the root ID of the request) */ + // OID to request (if walk, it is the root ID of the request). public static final PropertyDescriptor OID = new PropertyDescriptor.Builder() .name("snmp-oid") .displayName("OID") - .description("The OID to request") + .description("Each OID (object identifier) identifies a variable that can be read or set via SNMP.") .required(true) - .addValidator(SNMPUtils.SNMP_OID_VALIDATOR) + .addValidator(new OIDValidator()) + .build(); + + public static final PropertyDescriptor SNMP_STRATEGY = new PropertyDescriptor.Builder() + .name("snmp-strategy") + .displayName("SNMP Strategy") + .description("SNMP strategy to use (SNMP Get or SNMP Walk)") + .required(true) + .allowableValues(GET, WALK) + .defaultValue(GET.getValue()) .build(); - /** Textual OID to request */ public static final PropertyDescriptor TEXTUAL_OID = new PropertyDescriptor.Builder() .name("snmp-textual-oid") .displayName("Textual OID") - .description("The textual OID to request") + .description("The textual OID to request.") .required(false) .addValidator(StandardValidators.NON_BLANK_VALIDATOR) .defaultValue(null) .build(); - /** SNMP strategy for SNMP Get processor : simple get or walk */ - public static final PropertyDescriptor SNMP_STRATEGY = new PropertyDescriptor.Builder() - .name("snmp-strategy") - .displayName("SNMP strategy (GET/WALK)") - .description("SNMP strategy to use (SNMP Get or SNMP Walk)") - .required(true) - .allowableValues("GET", "WALK") - .defaultValue("GET") - .build(); - - /** relationship for success */ public static final Relationship REL_SUCCESS = new Relationship.Builder() .name("success") - .description("All FlowFiles that are received from the SNMP agent are routed to this relationship") + .description("All FlowFiles that are received from the SNMP agent are routed to this relationship.") .build(); - /** relationship for failure */ public static final Relationship REL_FAILURE = new Relationship.Builder() .name("failure") - .description("All FlowFiles that cannot received from the SNMP agent are routed to this relationship") + .description("All FlowFiles that cannot received from the SNMP agent are routed to this relationship.") .build(); - /** list of property descriptors */ - private final static List<PropertyDescriptor> propertyDescriptors; - - /** list of relationships */ - private final static Set<Relationship> relationships; - - /* - * Will ensure that the list of property descriptors is build only once. - * Will also create a Set of relationships - */ - static { - List<PropertyDescriptor> _propertyDescriptors = new ArrayList<>(); - _propertyDescriptors.add(OID); - _propertyDescriptors.add(TEXTUAL_OID); - _propertyDescriptors.add(SNMP_STRATEGY); - _propertyDescriptors.addAll(descriptors); - propertyDescriptors = Collections.unmodifiableList(_propertyDescriptors); - - Set<Relationship> _relationships = new HashSet<>(); - _relationships.add(REL_SUCCESS); - _relationships.add(REL_FAILURE); - relationships = Collections.unmodifiableSet(_relationships); - } + protected static final List<PropertyDescriptor> PROPERTY_DESCRIPTORS = Collections.unmodifiableList(Arrays.asList( + AGENT_HOST, + AGENT_PORT, + SNMP_VERSION, + SNMP_COMMUNITY, + SNMP_SECURITY_LEVEL, + SNMP_SECURITY_NAME, + SNMP_AUTH_PROTOCOL, + SNMP_AUTH_PASSWORD, + SNMP_PRIVACY_PROTOCOL, + SNMP_PRIVACY_PASSWORD, + SNMP_RETRIES, + SNMP_TIMEOUT, + OID, + TEXTUAL_OID, + SNMP_STRATEGY + )); + + private static final Set<Relationship> RELATIONSHIPS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + REL_SUCCESS, + REL_FAILURE + ))); - /** - * Delegate method to supplement - * {@link #onTrigger(ProcessContext, ProcessSession)}. It is implemented by - * sub-classes to perform {@link Processor} specific functionality. - * - * @param context - * instance of {@link ProcessContext} - * @param processSession - * instance of {@link ProcessSession} - * @throws ProcessException Process exception - */ @Override - protected void onTriggerSnmp(ProcessContext context, ProcessSession processSession) throws ProcessException { - if("GET".equals(context.getProperty(SNMP_STRATEGY).getValue())) { - final ResponseEvent response = this.targetResource.get(); - if (response.getResponse() != null){ - FlowFile flowFile = processSession.create(); - PDU pdu = response.getResponse(); - flowFile = SNMPUtils.updateFlowFileAttributesWithPduProperties(pdu, flowFile, processSession); - flowFile = SNMPUtils.addAttribute(SNMPUtils.SNMP_PROP_PREFIX + "textualOid", - context.getProperty(TEXTUAL_OID).getValue(), flowFile, processSession); - processSession.getProvenanceReporter().receive(flowFile, - this.snmpTarget.getAddress().toString() + "/" + context.getProperty(OID).getValue()); - if(pdu.getErrorStatus() == PDU.noError) { - processSession.transfer(flowFile, REL_SUCCESS); - } else { - processSession.transfer(flowFile, REL_FAILURE); - } - } else { - this.getLogger().error("Get request timed out or parameters are incorrect."); - context.yield(); - } - } else if("WALK".equals(context.getProperty(SNMP_STRATEGY).getValue())) { - final List<TreeEvent> events = this.targetResource.walk(); - if((events != null) && !events.isEmpty() && (events.get(0).getVariableBindings() != null)) { - FlowFile flowFile = processSession.create(); - for (TreeEvent treeEvent : events) { - flowFile = SNMPUtils.updateFlowFileAttributesWithTreeEventProperties(treeEvent, flowFile, processSession); - } - processSession.getProvenanceReporter().receive(flowFile, - this.snmpTarget.getAddress().toString() + "/" + context.getProperty(OID).getValue()); + public void onTrigger(final ProcessContext context, final ProcessSession processSession) { + final SNMPStrategy snmpStrategy = SNMPStrategy.valueOf(context.getProperty(SNMP_STRATEGY).getValue()); + final String oid = context.getProperty(OID).getValue(); + + if (SNMPStrategy.GET == snmpStrategy) { + performSnmpGet(context, processSession, oid); + } else if (SNMPStrategy.WALK == snmpStrategy) { + performSnmpWalk(context, processSession, oid); + } + } + + private void performSnmpWalk(final ProcessContext context, final ProcessSession processSession, final String oid) { + try { + final SNMPWalkResponse response = snmpRequestHandler.walk(oid); + FlowFile flowFile = createFlowFileWithTreeEventProperties(response, processSession); + processSession.getProvenanceReporter().receive(flowFile, response.getTargetUri() + "/" + oid); + processSession.transfer(flowFile, REL_SUCCESS); + } catch (SNMPWalkException e) { + getLogger().error("Could not perform SNMP Walk: Check if SNMP agent is accessible and the specified OID exists."); + context.yield(); + } + } + + private void performSnmpGet(final ProcessContext context, final ProcessSession processSession, final String oid) { + final SNMPResponse response; + final FlowFile flowFile = processSession.create(); + try { + response = snmpRequestHandler.get(oid); + updateFlowFileWithResponseAttributes(flowFile, context, processSession, response); + processSession.getProvenanceReporter().receive(flowFile, response.getTargetAddress() + "/" + oid); + if (response.isValid()) { processSession.transfer(flowFile, REL_SUCCESS); } else { - this.getLogger().error("Get request timed out or parameters are incorrect."); - context.yield(); + processSession.transfer(flowFile, REL_FAILURE); Review comment: Based on running the processor, there are some attributes added, like snmp$errorStatus and snmp$errorIndex. Please use the "." dot as separator as usual in the attributes and also it would be good to add the WritesAttribute for every single attribute and document it's meaning. Also there was a non-documented attribute "snmp$1.3$5" with null value. ########## File path: nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/main/java/org/apache/nifi/snmp/processors/GetSNMP.java ########## @@ -16,184 +16,186 @@ */ package org.apache.nifi.snmp.processors; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - import org.apache.nifi.annotation.behavior.InputRequirement; import org.apache.nifi.annotation.behavior.InputRequirement.Requirement; import org.apache.nifi.annotation.behavior.WritesAttribute; import org.apache.nifi.annotation.behavior.WritesAttributes; import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.Tags; +import org.apache.nifi.components.AllowableValue; import org.apache.nifi.components.PropertyDescriptor; import org.apache.nifi.flowfile.FlowFile; import org.apache.nifi.processor.ProcessContext; import org.apache.nifi.processor.ProcessSession; -import org.apache.nifi.processor.Processor; import org.apache.nifi.processor.Relationship; import org.apache.nifi.processor.util.StandardValidators; -import org.apache.nifi.processor.exception.ProcessException; -import org.snmp4j.PDU; -import org.snmp4j.event.ResponseEvent; -import org.snmp4j.smi.OID; -import org.snmp4j.util.TreeEvent; +import org.apache.nifi.snmp.dto.SNMPResponse; +import org.apache.nifi.snmp.dto.SNMPWalkResponse; +import org.apache.nifi.snmp.exception.SNMPException; +import org.apache.nifi.snmp.exception.SNMPWalkException; +import org.apache.nifi.snmp.utils.SNMPUtils; +import org.apache.nifi.snmp.validators.OIDValidator; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; /** * Retrieving data from configured SNMP agent which, upon each invocation of * {@link #onTrigger(ProcessContext, ProcessSession)} method, will construct a * {@link FlowFile} containing in its properties the information retrieved. * The output {@link FlowFile} won't have any content. */ -@Tags({ "snmp", "get", "oid", "walk" }) +@Tags({"snmp", "get", "oid", "walk"}) @InputRequirement(Requirement.INPUT_FORBIDDEN) @CapabilityDescription("Retrieves information from SNMP Agent and outputs a FlowFile with information in attributes and without any content") @WritesAttributes({ - @WritesAttribute(attribute=SNMPUtils.SNMP_PROP_PREFIX + "*", description="Attributes retrieved from the SNMP response. It may include:" - + " snmp$errorIndex, snmp$errorStatus, snmp$errorStatusText, snmp$nonRepeaters, snmp$requestID, snmp$type, snmp$variableBindings"), - @WritesAttribute(attribute=SNMPUtils.SNMP_PROP_PREFIX + "textualOid", description="This attribute will exist if and only if the strategy" - + " is GET and will be equal to the value given in Textual Oid property.") + @WritesAttribute(attribute = SNMPUtils.SNMP_PROP_PREFIX + "*", description = "Attributes retrieved from the SNMP response. It may include:" + + " snmp$errorIndex, snmp$errorStatus, snmp$errorStatusText, snmp$nonRepeaters, snmp$requestID, snmp$type, snmp$variableBindings"), + @WritesAttribute(attribute = SNMPUtils.SNMP_PROP_PREFIX + "textualOid", description = "This attribute will exist if and only if the strategy" + + " is GET and will be equal to the value given in Textual Oid property.") }) -public class GetSNMP extends AbstractSNMPProcessor<SNMPGetter> { +public class GetSNMP extends AbstractSNMPProcessor { + + // SNMP strategies + public static final AllowableValue GET = new AllowableValue("GET", "GET", + "A manager-to-agent request to retrieve the value of a variable. A response with the current value returned."); + public static final AllowableValue WALK = new AllowableValue("WALK", "WALK", + "A manager-to-agent request to retrieve the value of multiple variables. Snmp WALK also traverses all subnodes " + + "under the specified OID."); - /** OID to request (if walk, it is the root ID of the request) */ + // OID to request (if walk, it is the root ID of the request). public static final PropertyDescriptor OID = new PropertyDescriptor.Builder() .name("snmp-oid") .displayName("OID") - .description("The OID to request") + .description("Each OID (object identifier) identifies a variable that can be read or set via SNMP.") .required(true) - .addValidator(SNMPUtils.SNMP_OID_VALIDATOR) + .addValidator(new OIDValidator()) + .build(); + + public static final PropertyDescriptor SNMP_STRATEGY = new PropertyDescriptor.Builder() + .name("snmp-strategy") + .displayName("SNMP Strategy") + .description("SNMP strategy to use (SNMP Get or SNMP Walk)") + .required(true) + .allowableValues(GET, WALK) + .defaultValue(GET.getValue()) .build(); - /** Textual OID to request */ public static final PropertyDescriptor TEXTUAL_OID = new PropertyDescriptor.Builder() .name("snmp-textual-oid") .displayName("Textual OID") - .description("The textual OID to request") + .description("The textual OID to request.") .required(false) .addValidator(StandardValidators.NON_BLANK_VALIDATOR) .defaultValue(null) .build(); - /** SNMP strategy for SNMP Get processor : simple get or walk */ - public static final PropertyDescriptor SNMP_STRATEGY = new PropertyDescriptor.Builder() - .name("snmp-strategy") - .displayName("SNMP strategy (GET/WALK)") - .description("SNMP strategy to use (SNMP Get or SNMP Walk)") - .required(true) - .allowableValues("GET", "WALK") - .defaultValue("GET") - .build(); - - /** relationship for success */ public static final Relationship REL_SUCCESS = new Relationship.Builder() .name("success") - .description("All FlowFiles that are received from the SNMP agent are routed to this relationship") + .description("All FlowFiles that are received from the SNMP agent are routed to this relationship.") .build(); - /** relationship for failure */ public static final Relationship REL_FAILURE = new Relationship.Builder() .name("failure") - .description("All FlowFiles that cannot received from the SNMP agent are routed to this relationship") + .description("All FlowFiles that cannot received from the SNMP agent are routed to this relationship.") .build(); - /** list of property descriptors */ - private final static List<PropertyDescriptor> propertyDescriptors; - - /** list of relationships */ - private final static Set<Relationship> relationships; - - /* - * Will ensure that the list of property descriptors is build only once. - * Will also create a Set of relationships - */ - static { - List<PropertyDescriptor> _propertyDescriptors = new ArrayList<>(); - _propertyDescriptors.add(OID); - _propertyDescriptors.add(TEXTUAL_OID); - _propertyDescriptors.add(SNMP_STRATEGY); - _propertyDescriptors.addAll(descriptors); - propertyDescriptors = Collections.unmodifiableList(_propertyDescriptors); - - Set<Relationship> _relationships = new HashSet<>(); - _relationships.add(REL_SUCCESS); - _relationships.add(REL_FAILURE); - relationships = Collections.unmodifiableSet(_relationships); - } + protected static final List<PropertyDescriptor> PROPERTY_DESCRIPTORS = Collections.unmodifiableList(Arrays.asList( + AGENT_HOST, + AGENT_PORT, + SNMP_VERSION, + SNMP_COMMUNITY, + SNMP_SECURITY_LEVEL, + SNMP_SECURITY_NAME, + SNMP_AUTH_PROTOCOL, + SNMP_AUTH_PASSWORD, + SNMP_PRIVACY_PROTOCOL, + SNMP_PRIVACY_PASSWORD, + SNMP_RETRIES, + SNMP_TIMEOUT, + OID, + TEXTUAL_OID, + SNMP_STRATEGY + )); + + private static final Set<Relationship> RELATIONSHIPS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + REL_SUCCESS, + REL_FAILURE + ))); - /** - * Delegate method to supplement - * {@link #onTrigger(ProcessContext, ProcessSession)}. It is implemented by - * sub-classes to perform {@link Processor} specific functionality. - * - * @param context - * instance of {@link ProcessContext} - * @param processSession - * instance of {@link ProcessSession} - * @throws ProcessException Process exception - */ @Override - protected void onTriggerSnmp(ProcessContext context, ProcessSession processSession) throws ProcessException { - if("GET".equals(context.getProperty(SNMP_STRATEGY).getValue())) { - final ResponseEvent response = this.targetResource.get(); - if (response.getResponse() != null){ - FlowFile flowFile = processSession.create(); - PDU pdu = response.getResponse(); - flowFile = SNMPUtils.updateFlowFileAttributesWithPduProperties(pdu, flowFile, processSession); - flowFile = SNMPUtils.addAttribute(SNMPUtils.SNMP_PROP_PREFIX + "textualOid", - context.getProperty(TEXTUAL_OID).getValue(), flowFile, processSession); - processSession.getProvenanceReporter().receive(flowFile, - this.snmpTarget.getAddress().toString() + "/" + context.getProperty(OID).getValue()); - if(pdu.getErrorStatus() == PDU.noError) { - processSession.transfer(flowFile, REL_SUCCESS); - } else { - processSession.transfer(flowFile, REL_FAILURE); - } - } else { - this.getLogger().error("Get request timed out or parameters are incorrect."); - context.yield(); - } - } else if("WALK".equals(context.getProperty(SNMP_STRATEGY).getValue())) { - final List<TreeEvent> events = this.targetResource.walk(); - if((events != null) && !events.isEmpty() && (events.get(0).getVariableBindings() != null)) { - FlowFile flowFile = processSession.create(); - for (TreeEvent treeEvent : events) { - flowFile = SNMPUtils.updateFlowFileAttributesWithTreeEventProperties(treeEvent, flowFile, processSession); - } - processSession.getProvenanceReporter().receive(flowFile, - this.snmpTarget.getAddress().toString() + "/" + context.getProperty(OID).getValue()); + public void onTrigger(final ProcessContext context, final ProcessSession processSession) { + final SNMPStrategy snmpStrategy = SNMPStrategy.valueOf(context.getProperty(SNMP_STRATEGY).getValue()); + final String oid = context.getProperty(OID).getValue(); + + if (SNMPStrategy.GET == snmpStrategy) { + performSnmpGet(context, processSession, oid); + } else if (SNMPStrategy.WALK == snmpStrategy) { + performSnmpWalk(context, processSession, oid); + } + } + + private void performSnmpWalk(final ProcessContext context, final ProcessSession processSession, final String oid) { + try { + final SNMPWalkResponse response = snmpRequestHandler.walk(oid); + FlowFile flowFile = createFlowFileWithTreeEventProperties(response, processSession); + processSession.getProvenanceReporter().receive(flowFile, response.getTargetUri() + "/" + oid); + processSession.transfer(flowFile, REL_SUCCESS); + } catch (SNMPWalkException e) { + getLogger().error("Could not perform SNMP Walk: Check if SNMP agent is accessible and the specified OID exists."); + context.yield(); + } + } + + private void performSnmpGet(final ProcessContext context, final ProcessSession processSession, final String oid) { + final SNMPResponse response; + final FlowFile flowFile = processSession.create(); + try { + response = snmpRequestHandler.get(oid); + updateFlowFileWithResponseAttributes(flowFile, context, processSession, response); + processSession.getProvenanceReporter().receive(flowFile, response.getTargetAddress() + "/" + oid); + if (response.isValid()) { processSession.transfer(flowFile, REL_SUCCESS); } else { - this.getLogger().error("Get request timed out or parameters are incorrect."); - context.yield(); + processSession.transfer(flowFile, REL_FAILURE); } + } catch (SNMPException e) { + getLogger().error("Could not perform SNMP Get: Request timed out."); Review comment: It would be good to add some details about the exception (same below) -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. For queries about this service, please contact Infrastructure at: us...@infra.apache.org