package net.floodlightcontroller.util;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.projectfloodlight.openflow.protocol.OFFactories;
import org.projectfloodlight.openflow.protocol.OFFlowMod;
import org.projectfloodlight.openflow.protocol.OFInstructionType;
import org.projectfloodlight.openflow.protocol.OFOxsList;
import org.projectfloodlight.openflow.protocol.OFStatTriggerFlags;
import org.projectfloodlight.openflow.protocol.OFVersion;
import org.projectfloodlight.openflow.protocol.instruction.OFInstruction;
import org.projectfloodlight.openflow.protocol.instruction.OFInstructionApplyActions;
import org.projectfloodlight.openflow.protocol.instruction.OFInstructionClearActions;
import org.projectfloodlight.openflow.protocol.instruction.OFInstructionExperimenter;
import org.projectfloodlight.openflow.protocol.instruction.OFInstructionGotoTable;
import org.projectfloodlight.openflow.protocol.instruction.OFInstructionMeter;
import org.projectfloodlight.openflow.protocol.instruction.OFInstructionStatTrigger;
import org.projectfloodlight.openflow.protocol.instruction.OFInstructionWriteActions;
import org.projectfloodlight.openflow.protocol.instruction.OFInstructionWriteMetadata;
import org.projectfloodlight.openflow.protocol.oxs.OFOxs;
import org.projectfloodlight.openflow.protocol.oxs.OFOxsByteCount;
import org.projectfloodlight.openflow.protocol.oxs.OFOxsDuration;
import org.projectfloodlight.openflow.protocol.oxs.OFOxsFlowCount;
import org.projectfloodlight.openflow.protocol.oxs.OFOxsIdleTime;
import org.projectfloodlight.openflow.protocol.oxs.OFOxsPacketCount;
import org.projectfloodlight.openflow.protocol.stat.StatField;
import org.projectfloodlight.openflow.types.TableId;
import org.projectfloodlight.openflow.types.U32;
import org.projectfloodlight.openflow.types.U64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.JsonGenerator.Feature;
/**
* Convert OFInstructions to and from dpctl/ofctl-style strings.
* Used primarily by the static flow pusher to store and retreive
* flow entries.
*
* @author Ryan Izard <ryan.izard@bigswitch.com, rizard@g.clemson.edu>
*
*/
public class InstructionUtils {
private static final Logger log = LoggerFactory.getLogger(InstructionUtils.class);
public static final String STR_GOTO_TABLE = "instruction_goto_table";
public static final String STR_WRITE_METADATA = "instruction_write_metadata";
public static final String STR_WRITE_ACTIONS = "instruction_write_actions";
public static final String STR_APPLY_ACTIONS = "instruction_apply_actions";
public static final String STR_CLEAR_ACTIONS = "instruction_clear_actions";
public static final String STR_GOTO_METER = "instruction_goto_meter";
public static final String STR_EXPERIMENTER = "instruction_experimenter";
public static final String STR_DEPRECATED = "instruction_deprecated";
public static final String STR_STAT_TRIGGER = "instruction_stat_trigger";
private static final JsonFactory jsonFactory = new JsonFactory();
private static final String JSON_EMPTY_OBJECT = "{}";
/**
* Adds the instructions to the list of OFInstructions in the OFFlowMod. Any pre-existing
* instruction of the same type is replaced with OFInstruction inst.
* @param fmb, the flow mod to append the instruction to
* @param inst, the instuction to append
*/
public static void appendInstruction(OFFlowMod.Builder fmb, OFInstruction inst) {
List<OFInstruction> newIl = new ArrayList<OFInstruction>();
List<OFInstruction> oldIl = fmb.getInstructions();
if (oldIl != null) { // keep any existing instructions that were added earlier
newIl.addAll(fmb.getInstructions());
}
for (OFInstruction i : newIl) { // remove any duplicates. Only one of each instruction.
if (i.getType() == inst.getType()) {
newIl.remove(i);
}
}
newIl.add(inst);
fmb.setInstructions(newIl);
}
/**
* Get string name of OFInstructionType
* @param t
* @return
*/
public static String getInstructionName(OFInstructionType t) {
switch (t) {
case APPLY_ACTIONS:
return STR_APPLY_ACTIONS;
case CLEAR_ACTIONS:
return STR_CLEAR_ACTIONS;
case DEPRECATED:
return STR_DEPRECATED;
case EXPERIMENTER:
return STR_EXPERIMENTER;
case GOTO_TABLE:
return STR_GOTO_TABLE;
case METER:
return STR_GOTO_METER;
case STAT_TRIGGER:
return STR_STAT_TRIGGER;
case WRITE_ACTIONS:
return STR_WRITE_ACTIONS;
case WRITE_METADATA:
return STR_WRITE_METADATA;
}
return "";
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Convert an OFInstructionGotoTable to string form. The string will be formatted
* in a dpctl/ofctl-style syntax.
* @param inst; The instruction to convert to a string
* @return
*/
public static String gotoTableToString(OFInstructionGotoTable inst) {
return Short.toString(inst.getTableId().getValue());
}
/**
* Convert the string representation of an OFInstructionGotoTable to
* an OFInstructionGotoTable. The instruction will be set within the
* OFFlowMod.Builder provided. Notice nothing is returned, but the
* side effect is the addition of an instruction in the OFFlowMod.Builder.
* @param fmb; The FMB in which to append the new instruction
* @param instStr; The string to parse the instruction from
*/
public static void gotoTableFromString(OFFlowMod.Builder fmb, String inst) {
if (inst == null || inst.equals("")) {
return;
}
if (fmb.getVersion().compareTo(OFVersion.OF_11) < 0) {
log.error("Goto Table Instruction not supported in OpenFlow 1.0");
return;
}
OFInstructionGotoTable.Builder ib = OFFactories.getFactory(fmb.getVersion()).instructions().buildGotoTable();
// Get the table ID
ib.setTableId(TableId.of(ParseUtils.parseHexOrDecInt(inst)));
log.debug("Appending GotoTable instruction: {}", ib.build());
appendInstruction(fmb, ib.build());
log.debug("All instructions after append: {}", fmb.getInstructions());
}
/**
* Convert an OFInstructionMetadata to string form. The string will be formatted
* in a dpctl/ofctl-style syntax.
* @param inst; The instruction to convert to a string
* @return
*/
public static String writeMetadataToString(OFInstructionWriteMetadata inst) {
/*
* U64.toString() formats with a leading 0x
*/
if (inst.getMetadataMask().equals(U64.NO_MASK)) { // don't give the mask if it's all 1's --> omit "/"
return inst.getMetadata().toString();
} else {
return inst.getMetadata().toString() + "/" + inst.getMetadataMask().toString();
}
}
/**
* Convert the string representation of an OFInstructionMetadata to
* an OFInstructionMetadata. The instruction will be set within the
* OFFlowMod.Builder provided. Notice nothing is returned, but the
* side effect is the addition of an instruction in the OFFlowMod.Builder.
* @param fmb; The FMB in which to append the new instruction
* @param instStr; The string to parse the instruction from
*/
public static void writeMetadataFromString(OFFlowMod.Builder fmb, String inst) {
if (inst == null || inst.equals("")) {
return;
}
if (fmb.getVersion().compareTo(OFVersion.OF_11) < 0) {
log.error("Write Metadata Instruction not supported in OpenFlow 1.0");
return;
}
OFInstructionWriteMetadata.Builder ib = OFFactories.getFactory(fmb.getVersion()).instructions().buildWriteMetadata();
// Process tokens (should be metadata or its mask)
String[] keyValue = inst.split("/");
if (keyValue.length > 2) {
throw new IllegalArgumentException("[Metadata, Mask] " + keyValue + " does not have form 'metadata/mask' or 'metadata' for parsing " + inst);
} else if (keyValue.length == 1) {
log.debug("No mask detected in OFInstructionWriteMetaData string.");
} else if (keyValue.length == 2) {
log.debug("Detected mask in OFInstructionWriteMetaData string.");
}
// Get the metadata
ib.setMetadata(U64.of(ParseUtils.parseHexOrDecLong(keyValue[0])));
// Get the optional mask
if (keyValue.length == 2) {
ib.setMetadataMask(U64.of(ParseUtils.parseHexOrDecLong(keyValue[1])));
} else {
ib.setMetadataMask(U64.NO_MASK);
}
log.debug("Appending WriteMetadata instruction: {}", ib.build());
appendInstruction(fmb, ib.build());
log.debug("All instructions after append: {}", fmb.getInstructions());
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Convert an OFInstructionWriteActions to string form. The string will be formatted
* in a dpctl/ofctl-style syntax.
* @param inst; The instruction to convert to a string
* @return
*/
public static String writeActionsToString(OFInstructionWriteActions inst) throws Exception {
return ActionUtils.actionsToString(inst.getActions());
}
/**
* Convert the string representation of an OFInstructionWriteActions to
* an OFInstructionWriteActions. The instruction will be set within the
* OFFlowMod.Builder provided. Notice nothing is returned, but the
* side effect is the addition of an instruction in the OFFlowMod.Builder.
* @param fmb; The FMB in which to append the new instruction
* @param instStr; The string to parse the instruction from
*/
public static void writeActionsFromString(OFFlowMod.Builder fmb, String inst) {
if (fmb.getVersion().compareTo(OFVersion.OF_11) < 0) {
log.error("Write Actions Instruction not supported in OpenFlow 1.0");
return;
}
OFFlowMod.Builder tmpFmb = OFFactories.getFactory(fmb.getVersion()).buildFlowModify(); // ActionUtils.fromString() will use setActions(), which should not be used for OF1.3; use temp to avoid overwriting any applyActions data
OFInstructionWriteActions.Builder ib = OFFactories.getFactory(fmb.getVersion()).instructions().buildWriteActions();
ActionUtils.fromString(tmpFmb, inst);
ib.setActions(tmpFmb.getActions());
log.debug("Appending WriteActions instruction: {}", ib.build());
appendInstruction(fmb, ib.build());
log.debug("All instructions after append: {}", fmb.getInstructions()); }
///////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Convert an OFInstructionApplyActions to string form. The string will be formatted
* in a dpctl/ofctl-style syntax.
* @param inst; The instruction to convert to a string
* @return
*/
public static String applyActionsToString(OFInstructionApplyActions inst) throws Exception {
return ActionUtils.actionsToString(inst.getActions());
}
/**
* Convert the string representation of an OFInstructionApplyActions to
* an OFInstructionApplyActions. The instruction will be set within the
* OFFlowMod.Builder provided. Notice nothing is returned, but the
* side effect is the addition of an instruction in the OFFlowMod.Builder.
* @param fmb; The FMB in which to append the new instruction
* @param instStr; The string to parse the instruction from
*/
public static void applyActionsFromString(OFFlowMod.Builder fmb, String inst) {
if (fmb.getVersion().compareTo(OFVersion.OF_11) < 0) {
log.error("Apply Actions Instruction not supported in OpenFlow 1.0");
return;
}
OFFlowMod.Builder tmpFmb = OFFactories.getFactory(fmb.getVersion()).buildFlowModify();
OFInstructionApplyActions.Builder ib = OFFactories.getFactory(fmb.getVersion()).instructions().buildApplyActions();
ActionUtils.fromString(tmpFmb, inst);
ib.setActions(tmpFmb.getActions());
log.debug("Appending ApplyActions instruction: {}", ib.build());
appendInstruction(fmb, ib.build());
log.debug("All instructions after append: {}", fmb.getInstructions()); }
///////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Convert an OFInstructionClearActions to string form. The string will be formatted
* in a dpctl/ofctl-style syntax.
* @param inst; The instruction to convert to a string
* @return
*/
public static String clearActionsToString(OFInstructionClearActions inst) {
return ""; // No data for this instruction. The presence of it's key indicates it is to be applied.
}
/**
* Convert the string representation of an OFInstructionClearActions to
* an OFInstructionClearActions. The instruction will be set within the
* OFFlowMod.Builder provided. Notice nothing is returned, but the
* side effect is the addition of an instruction in the OFFlowMod.Builder.
* @param fmb; The FMB in which to append the new instruction
* @param instStr; The string to parse the instruction from
*/
public static void clearActionsFromString(OFFlowMod.Builder fmb, String inst) {
if (fmb.getVersion().compareTo(OFVersion.OF_11) < 0) {
log.error("Clear Actions Instruction not supported in OpenFlow 1.0");
return;
}
if (inst != null && inst.trim().isEmpty()) { /* Allow the empty string, since this is what specifies clear (i.e. key clear does not have any defined values). */
OFInstructionClearActions i = OFFactories.getFactory(fmb.getVersion()).instructions().clearActions();
log.debug("Appending ClearActions instruction: {}", i);
appendInstruction(fmb, i);
log.debug("All instructions after append: {}", fmb.getInstructions());
} else {
log.error("Got non-empty or null string, but ClearActions should not have any String sub-fields: {}", inst);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Convert an OFInstructionMeter to string form. The string will be formatted
* in a dpctl/ofctl-style syntax.
* @param inst; The instruction to convert to a string
* @return
*/
public static String meterToString(OFInstructionMeter inst) {
return Long.toString(inst.getMeterId());
}
/**
* Convert the string representation of an OFInstructionMeter to
* an OFInstructionMeter. The instruction will be set within the
* OFFlowMod.Builder provided. Notice nothing is returned, but the
* side effect is the addition of an instruction in the OFFlowMod.Builder.
* @param fmb; The FMB in which to append the new instruction
* @param instStr; The string to parse the instruction from
*/
public static void meterFromString(OFFlowMod.Builder fmb, String inst) {
if (inst == null || inst.isEmpty()) {
return;
}
if (fmb.getVersion().compareTo(OFVersion.OF_13) < 0) {
log.error("Goto Meter Instruction not supported in OpenFlow 1.0, 1.1, or 1.2");
return;
}
OFInstructionMeter.Builder ib = OFFactories.getFactory(fmb.getVersion()).instructions().buildMeter();
ib.setMeterId(ParseUtils.parseHexOrDecLong(inst));
log.debug("Appending (Goto)Meter instruction: {}", ib.build());
appendInstruction(fmb, ib.build());
log.debug("All instructions after append: {}", fmb.getInstructions());
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Convert an OFInstructionExperimenter to string form. The string will be formatted
* in a dpctl/ofctl-style syntax.
* @param inst; The instruction to convert to a string
* @return
*/
public static String experimenterToString(OFInstructionExperimenter inst) {
return Long.toString(inst.getExperimenter());
}
/**
* Convert the string representation of an OFInstructionExperimenter to
* an OFInstructionExperimenter. The instruction will be set within the
* OFFlowMod.Builder provided. Notice nothing is returned, but the
* side effect is the addition of an instruction in the OFFlowMod.Builder.
* @param fmb; The FMB in which to append the new instruction
* @param instStr; The string to parse the instruction from
*/
public static void experimenterFromString(OFFlowMod.Builder fmb, String inst) {
log.warn("OFInstructionExperimenter from-string not implemented");
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Convert an deprecated (OF1.5+) OFInstructionMeter to string form.
* @param inst; The instruction to convert to a string
* @return
*/
public static String deprecatedToString(OFInstruction inst) {
log.warn("OFInstructionDeprecated is being used. Did you mean to use OF1.5+ OFActionMeter?");
return "";
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Convert JSON string OFInstructionStatTrigger object.
* @param fmb; Where the instruction will be placed
* @param json; The string to convert to an OFInstructionStatTrigger
* @return
*/
public static boolean statTriggerFromJsonString(OFFlowMod.Builder fmb, String json) {
OFInstructionStatTrigger i = statTriggerFromJsonString(fmb.getVersion(), json);
if (i != null) {
appendInstruction(fmb, i);
return true;
} else {
return false;
}
}
/**
* Convert JSON string OFInstructionStatTrigger object.
* @param json; The string to convert to an OFInstructionStatTrigger
* @return
*/
public static OFInstructionStatTrigger statTriggerFromJsonString(OFVersion v, String json) {
if (json == null) {
throw new IllegalArgumentException("JSON string cannot be null");
}
final Set<OFStatTriggerFlags> flags = new HashSet<OFStatTriggerFlags>();
final Set<OFOxs<?>> thresholds = new HashSet<OFOxs<?>>();
final JsonParser jp;
try {
jp = jsonFactory.createParser(json);
} catch (IOException e) {
log.error("Could not create JSON parser for OFFlowMod.Builder {}", json);
return null;
}
try {
if (jp.nextToken() != JsonToken.START_OBJECT) {
throw new IOException("Expected START_OBJECT");
}
while (jp.nextToken() != JsonToken.END_OBJECT) {
String key = jp.getCurrentName().toLowerCase().trim();
if (jp.nextToken() != JsonToken.START_ARRAY) {
throw new IOException("Expected START_ARRAY");
}
switch (key) {
case "flags":
while (jp.nextToken() != JsonToken.END_ARRAY) {
boolean flagSet = false;
for (OFStatTriggerFlags f : OFStatTriggerFlags.values()) {
if (f.toString().equalsIgnoreCase(jp.getText().trim())) { /* case-insensitive, unlike enum.valueOf() */
flags.add(f);
flagSet = true;
break;
}
}
if (!flagSet) {
log.warn("Unknown flag {}", jp.getText());
}
}
break;
case "thresholds":
while (jp.nextToken() != JsonToken.END_ARRAY) {
if (jp.getCurrentToken() != JsonToken.START_OBJECT) {
throw new IOException("Expected START_OBJECT");
}
OFOxs.Builder<?> threshold = null;
U64 value = null;
while (jp.nextToken() != JsonToken.END_OBJECT) {
switch (jp.getCurrentName().toLowerCase().trim()) {
case "oxs_type":
String type = jp.getText().toLowerCase().trim();
if (type.equals(StatField.BYTE_COUNT.getName())) {
threshold = OFFactories.getFactory(v).oxss().buildByteCount();
} else if (type.equals(StatField.DURATION.getName())) {
threshold = OFFactories.getFactory(v).oxss().buildDuration();
} else if (type.equals(StatField.FLOW_COUNT.getName())) {
threshold = OFFactories.getFactory(v).oxss().buildFlowCount();
} else if (type.equals(StatField.IDLE_TIME.getName())) {
threshold = OFFactories.getFactory(v).oxss().buildIdleTime();
} else if (type.equals(StatField.PACKET_COUNT.getName())) {
threshold = OFFactories.getFactory(v).oxss().buildPacketCount();
} else {
log.warn("Unexpected OXS threshold type {}", type);
}
break;
case "value":
value = U64.of(ParseUtils.parseHexOrDecLong(jp.getText()));
break;
default:
log.warn("Unexpected OXS threshold key {}", jp.getCurrentName());
break;
}
}
if (threshold == null || value == null) {
log.error("Must specify both OXS type and threshold value. Got {} and {}, respectively", threshold, value);
return null;
} else {
if (threshold.getStatField().getName().equals(StatField.BYTE_COUNT.getName())) {
thresholds.add(((OFOxsByteCount.Builder) threshold).setValue(value).build());
} else if (threshold.getStatField().getName().equals(StatField.DURATION.getName())) {
thresholds.add(((OFOxsDuration.Builder) threshold).setValue(value).build());
} else if (threshold.getStatField().getName().equals(StatField.FLOW_COUNT.getName())) {
thresholds.add(((OFOxsFlowCount.Builder) threshold).setValue(U32.of(value.getValue())).build());
} else if (threshold.getStatField().getName().equals(StatField.IDLE_TIME.getName())) {
thresholds.add(((OFOxsIdleTime.Builder) threshold).setValue(value).build());
} else if (threshold.getStatField().getName().equals(StatField.PACKET_COUNT.getName())) {
thresholds.add(((OFOxsPacketCount.Builder) threshold).setValue(value).build());
} else {
log.warn("Unexpected OXS threshold type {}", threshold.getStatField().getName());
}
}
}
break;
default:
log.warn("Unexpected OFInstructionStatTrigger key {}", key);
break;
}
}
} catch (IOException e) {
log.error("Could not parse: {}", json);
log.error("JSON parse error message: {}", e.getMessage());
return null;
}
return OFFactories.getFactory(v).instructions().statTrigger(flags, OFOxsList.ofList(thresholds));
}
/**
* Append OFInstructionStatsTrigger object to an existing JsonGenerator.
* This method assumes the field name of the instruction has been
* written already, if required. The appended data will
* be formatted as follows:
* {
* "flags":[
* f1, f2, f3, ..., fn
* ],
* "thresholds":[
* {
* "oxs_type":"l",
* "value":"v"
* },
* {
* "oxs_type":"m",
* "value":"v"
* },
* ...,
* {
* "oxs_type":"n",
* "value":"v"
* }
* ]
* }
* @param jsonGen
* @param s
*/
public static void statTriggerToJsonString(JsonGenerator jsonGen, OFInstructionStatTrigger s) {
jsonGen.configure(Feature.WRITE_NUMBERS_AS_STRINGS, true);
try {
jsonGen.writeStartObject();
jsonGen.writeArrayFieldStart("flags");
for (OFStatTriggerFlags f : s.getFlags()) {
jsonGen.writeString(f.toString());
}
jsonGen.writeEndArray();
jsonGen.writeArrayFieldStart("thresholds");
for (OFOxs<?> o : s.getThresholds()) {
jsonGen.writeStartObject();
if (o instanceof OFOxsDuration) {
OFOxsDuration t = (OFOxsDuration) o;
jsonGen.writeStringField("oxs_type", t.getStatField().getName());
jsonGen.writeStringField("value", t.getValue().toString());
} else if (o instanceof OFOxsByteCount) {
OFOxsByteCount t = (OFOxsByteCount) o;
jsonGen.writeStringField("oxs_type", t.getStatField().getName());
jsonGen.writeStringField("value", t.getValue().toString());
} else if (o instanceof OFOxsFlowCount) {
OFOxsFlowCount t = (OFOxsFlowCount) o;
jsonGen.writeStringField("oxs_type", t.getStatField().getName());
jsonGen.writeStringField("value", t.getValue().toString());
} else if (o instanceof OFOxsIdleTime) {
OFOxsIdleTime t = (OFOxsIdleTime) o;
jsonGen.writeStringField("oxs_type", t.getStatField().getName());
jsonGen.writeStringField("value", t.getValue().toString());
} else if (o instanceof OFOxsPacketCount) {
OFOxsPacketCount t = (OFOxsPacketCount) o;
jsonGen.writeStringField("oxs_type", t.getStatField().getName());
jsonGen.writeStringField("value", t.getValue().toString());
} else {
log.warn("Skipping unknown OXS type {}", o.getStatField().getName());
}
jsonGen.writeEndObject();
}
jsonGen.writeEndArray();
jsonGen.close();
} catch (IOException e) {
log.error("Error composing OFInstructionStatTrigger JSON object. {}", e.getMessage());
}
}
/**
* Create OFInstructionStatsTrigger JSON object string.
* This method assumes the field name of the instruction will be
* written externally, if required. The appended JSON string will
* be formatted as follows:
* {
* "flags":[
* f1, f2, f3, ..., fn
* ],
* "thresholds":[
* {
* "oxs_type":"l",
* "value":"v"
* },
* {
* "oxs_type":"m",
* "value":"v"
* },
* ...,
* {
* "oxs_type":"n",
* "value":"v"
* }
* ]
* }
* @param s
*/
public static String statTriggerToJsonString(OFInstructionStatTrigger s) {
Writer w = new StringWriter();
JsonGenerator jsonGen;
try {
jsonGen = jsonFactory.createGenerator(w);
} catch (IOException e) {
log.error("Could not instantiate JSON Generator. {}", e.getMessage());
return JSON_EMPTY_OBJECT;
}
statTriggerToJsonString(jsonGen, s);
return w.toString(); /* overridden impl returns contents of Writer's StringBuffer */
}
}