/*
* 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.processors;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.nifi.annotation.behavior.InputRequirement;
import org.apache.nifi.annotation.behavior.InputRequirement.Requirement;
import org.apache.nifi.annotation.documentation.CapabilityDescription;
import org.apache.nifi.annotation.documentation.Tags;
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.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;
/**
* 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" })
@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" +
" attribute to the corresponding OID given in the attribute name")
public class SetSNMP extends AbstractSNMPProcessor<SNMPSetter> {
/** 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;
/** 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.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();
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 {
processSession.transfer(processSession.penalize(flowFile), REL_FAILURE);
this.getLogger().warn("No attributes found in the FlowFile to perform SNMP Set");
}
}
}
/**
* 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
*/
private boolean addVariables(PDU pdu, Map<String, String> attributes) {
boolean result = false;
for (Entry<String, String> attributeEntry : attributes.entrySet()) {
if (attributeEntry.getKey().startsWith(SNMPUtils.SNMP_PROP_PREFIX)) {
String[] splits = attributeEntry.getKey().split("\\" + SNMPUtils.SNMP_PROP_DELIMITER);
String snmpPropName = splits[1];
String snmpPropValue = attributeEntry.getValue();
if(SNMPUtils.OID_PATTERN.matcher(snmpPropName).matches()) {
Variable var = null;
if (splits.length == 2) { // no SMI syntax defined
var = new OctetString(snmpPropValue);
} else {
int smiSyntax = Integer.valueOf(splits[2]);
var = this.stringToVariable(snmpPropValue, smiSyntax);
}
if(var != null) {
VariableBinding varBind = new VariableBinding(new OID(snmpPropName), var);
pdu.add(varBind);
result = true;
}
}
}
}
return result;
}
/**
* Method to create the variable from the attribute value and the given SMI syntax value
* @param value attribute value
* @param smiSyntax attribute SMI Syntax
* @return variable
*/
private Variable stringToVariable(String value, 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.valueOf(value));
} else if (var instanceof AssignableFromLong) {
((AssignableFromLong) var).setValue(Long.valueOf(value));
} else {
this.getLogger().error("Unsupported conversion of [" + value +"] to " + var.getSyntaxString());
var = null;
}
} catch (IllegalArgumentException e) {
this.getLogger().error("Unsupported conversion of [" + value +"] to " + var.getSyntaxString(), e);
var = null;
}
return var;
}
/**
* @see org.apache.nifi.components.AbstractConfigurableComponent#getSupportedPropertyDescriptors()
*/
@Override
protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
return propertyDescriptors;
}
/**
* @see org.apache.nifi.processor.AbstractSessionFactoryProcessor#getRelationships()
*/
@Override
public Set<Relationship> getRelationships() {
return relationships;
}
/**
* @see org.apache.nifi.snmp.processors.AbstractSNMPProcessor#finishBuildingTargetResource(org.apache.nifi.processor.ProcessContext)
*/
@Override
protected SNMPSetter finishBuildingTargetResource(ProcessContext context) {
return new SNMPSetter(this.snmp, this.snmpTarget);
}
}