/* * Copyright (c) 2013 Big Switch Networks, Inc. * * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html * * 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.sdnplatform.netvirt.web; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.codehaus.jackson.annotate.JsonProperty; import org.openflow.util.HexString; import org.restlet.resource.Get; import org.restlet.resource.ServerResource; import org.sdnplatform.flowcache.BetterFlowCache; import org.sdnplatform.flowcache.FlowCacheObj; import org.sdnplatform.flowcache.IFlowCacheService; import org.sdnplatform.flowcache.BetterFlowCache.BfcDb; import org.sdnplatform.flowcache.FlowCacheObj.FCEntry; import org.sdnplatform.packet.IPv4; import com.google.common.collect.MinMaxPriorityQueue; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** * @author subrata * */ public class FlowCacheMgrResource extends ServerResource { public static final int MAX_ENTRIES_TO_RETURN = 1000; /** * A dummy class to flatten flow cache entry and then serialize them * for the REST API. * This is a rather ugly hack but given the current state of FlowCache * it's the best we can do at this time and we'll throw it away once we * actually fix FC. * */ @SuppressFBWarnings(value="URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD", justification="Fields are serialized by Jackson") public static class BetterFlowCacheRestEntry implements Comparable<BetterFlowCacheRestEntry>{ @JsonProperty("Appl") public String applName; // These properties are from the HashMap structures @JsonProperty("VLAN") public short vlan; @JsonProperty("AppInst") public String appInstName; @JsonProperty("DestMAC") public String dstMAC; @JsonProperty("SrcMAC") public String srcMAC; // These properties are from FlowCacheObj @JsonProperty("Cookie") public long cookie; @JsonProperty("Time") public Long installTimeMs; // These properties are from FCEntry @JsonProperty("EtherType") public String etherType; @JsonProperty("PCP") public int pcp; @JsonProperty("SrcIPAddr") public String srcIpAddr; @JsonProperty("DstIPAddr") public String dstIpAddr; @JsonProperty("Protocol") public int protocol; @JsonProperty("TOS") public int tos; @JsonProperty("SrcPort") public int srcPort; @JsonProperty("DstPort") public int dstPort; @JsonProperty("Source-Switch") public String srcSwitch; @JsonProperty("InputPort") public int inputPort; @JsonProperty("Wildcards") public String wildcards; @JsonProperty("OFPri") public int ofPriority; @JsonProperty("Action") public String fcAction; @JsonProperty("State") public String fcState; @JsonProperty("SC") public byte scanCount; /** * Creates a new entry with the given fields. * @param appInstName * @param vlan * @param dstMAC * @param srcMAC * @param cookie * @param installTimeNs * @param fce */ @SuppressFBWarnings(value="URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD", justification="Fields are serialized by Jackson") public BetterFlowCacheRestEntry(String appInstName, short vlan, long dstMAC, long srcMAC, long cookie, long installTimeNs, FCEntry fce) { this.applName = "netVirt"; this.appInstName = appInstName; this.vlan = vlan; this.dstMAC = HexString.toHexString(dstMAC, 6); this.srcMAC = HexString.toHexString(srcMAC, 6); this.cookie = cookie; long curTimeNs = System.nanoTime(); long elapsedMs=(curTimeNs - installTimeNs)/1000000; this.installTimeMs=System.currentTimeMillis()-elapsedMs; this.etherType = String.format("0x%04x", fce.getEtherType()); this.pcp = fce.getPcp(); this.srcIpAddr = IPv4.fromIPv4Address(fce.getSrcIpAddr()); this.dstIpAddr = IPv4.fromIPv4Address(fce.getDestIpAddr()); this.protocol = fce.getProtocol(); this.tos = fce.getNwTos(); this.srcPort = fce.getSrcL4Port(); this.dstPort = fce.getDestL4Port(); this.srcSwitch = HexString.toHexString(fce.getSrcSwitchDpid(), 8); this.inputPort = fce.getInputPort(); // wildcards field is 22 bits as per OF 1.0 this.wildcards = String.format("0x%06x", fce.getWildcards()); this.ofPriority = fce.getOfPri(); if (fce.getAction() == FlowCacheObj.FCActionPERMIT) { this.fcAction = "PERMIT"; } else { this.fcAction = "DENY"; } if (fce.getState() == FlowCacheObj.FCStateACTIVE) { this.fcState = "ACTIVE"; } else { this.fcState = "INACTIVE"; } this.scanCount = fce.getScanCount(); } @Override public int compareTo(BetterFlowCacheRestEntry o) { return -installTimeMs.compareTo(o.installTimeMs); } } /** * A dummy helper class representing the counters and stats of the * flow cache to return via REST. * @author gregor * */ @SuppressFBWarnings(value="URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD", justification="Fields are serialized by Jackson") public static class BetterFlowCacheRestCounters { public BetterFlowCacheRestCounters(BfcDb bfcCore) { applName = bfcCore.getApplName(); maxFlows = bfcCore.getMaxFlows(); activeCnt = bfcCore.getActiveCnt(); inactiveCnt = bfcCore.getInactiveCnt(); addCnt = bfcCore.getAddCnt(); delCnt = bfcCore.getDelCnt(); activatedCnt = bfcCore.getActivatedCnt(); deactivatedCnt = bfcCore.getDeactivatedCnt(); cacheHitCnt = bfcCore.getCacheHitCnt(); missCnt = bfcCore.getMissCnt(); flowModRemovalMsgLossCnt = bfcCore.getFlowModRemovalMsgLossCnt(); notStoredFullCnt = bfcCore.getNotStoredFullCnt(); fcObjFreedCnt = bfcCore.getFcObjFreedCnt(); unknownOperCnt = bfcCore.getUnknownOperCnt(); flowCacheAlmostFull = bfcCore.isFlowCacheAlmostFull(); } public String applName; public long maxFlows; public long activeCnt; public long inactiveCnt; public long addCnt; public long delCnt; public long activatedCnt; public long deactivatedCnt; public long cacheHitCnt; public long missCnt; public long flowModRemovalMsgLossCnt; public long notStoredFullCnt; public long fcObjFreedCnt; public long unknownOperCnt; public boolean flowCacheAlmostFull; } /** * A dummy class that represents the return value for flow cache * rest calls. * */ @SuppressFBWarnings(value="URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD", justification="Fields are serialized by Jackson") public static class BetterFlowCacheRestData { public MinMaxPriorityQueue<BetterFlowCacheRestEntry> flows; public BetterFlowCacheRestCounters counters; public String description; public String status; /** * A helper method. Populate flow entries to the "flows" list for * a single app instance name. * * @param appInstName The appInstanceName * @param vlanEntriesMap The map for this app instance name (from * BfcDB) */ private void addAllEntriesForOneAppInstance( String appInstName, ConcurrentHashMap<Short, ConcurrentHashMap<Long, ConcurrentHashMap<Long, FlowCacheObj>>> vlanEntriesMap) { for(Map.Entry<Short, ConcurrentHashMap<Long, ConcurrentHashMap<Long, FlowCacheObj>>> vlanEntry: vlanEntriesMap.entrySet()) { Short vlan = vlanEntry.getKey(); for(Map.Entry<Long, ConcurrentHashMap<Long, FlowCacheObj>> dstMacEntry: vlanEntry.getValue().entrySet()) { Long dstMac = dstMacEntry.getKey(); for(Map.Entry<Long, FlowCacheObj> srcMacEntry: dstMacEntry.getValue().entrySet()) { Long srcMac = srcMacEntry.getKey(); FlowCacheObj fcObj = srcMacEntry.getValue(); addAllEntriesForOneFCObj(appInstName, vlan, dstMac, srcMac, fcObj); } } } } /** * A helper method. Populate flow entries to the "flows" kust for * a particular FlowCacheObject * @param appInstName * @param vlan * @param dstMac * @param srcMac * @param fcObj */ private void addAllEntriesForOneFCObj(String appInstName, Short vlan, Long dstMac, Long srcMac, FlowCacheObj fcObj) { if (fcObj.fce != null) { flows.add(new BetterFlowCacheRestEntry(appInstName, vlan, dstMac, srcMac, fcObj.cookie, fcObj.installTimeNs, fcObj.fce)); } if (fcObj.fceList != null && fcObj.fceList.size() > 0) { for(FCEntry fce: fcObj.fceList) { flows.add(new BetterFlowCacheRestEntry(appInstName, vlan, dstMac, srcMac, fcObj.cookie, fcObj.installTimeNs, fce)); } } } private BetterFlowCacheRestData() { super(); } private BetterFlowCacheRestData(BfcDb bfcCore) { super(); this.counters = new BetterFlowCacheRestCounters(bfcCore); this.description = "Flow Cache for application=" + this.counters.applName + " application instance=all" + " query type=counters"; this.status = "OK"; this.flows = MinMaxPriorityQueue .maximumSize(MAX_ENTRIES_TO_RETURN) .create(); } /** * Perform a query for all flows in the cache and return * {@link BetterFlowCacheRestData} instance representing the result * @param bfcCore * @return */ public static BetterFlowCacheRestData queryAllFlows(BfcDb bfcCore) { BetterFlowCacheRestData rv = new BetterFlowCacheRestData(bfcCore); rv.description = "Flow Cache for application=" + rv.counters.applName + " application instance=all" + " query type=all"; ConcurrentHashMap<String, ConcurrentHashMap<Short, ConcurrentHashMap<Long, ConcurrentHashMap<Long, FlowCacheObj>>>> cache = bfcCore.getFlowCache(); for (Map.Entry<String, ConcurrentHashMap<Short, ConcurrentHashMap<Long, ConcurrentHashMap<Long, FlowCacheObj>>>> netVirtEntry: cache.entrySet()) { String appInstName = netVirtEntry.getKey(); rv.addAllEntriesForOneAppInstance(appInstName, netVirtEntry.getValue()); } return rv; } /** * Perform a query for just the counters and return a new * {@link BetterFlowCacheRestData} instance representing the result * @param bfcCore * @return */ public static BetterFlowCacheRestData queryCounters(BfcDb bfcCore) { BetterFlowCacheRestData rv = new BetterFlowCacheRestData(bfcCore); return rv; } /** * Perform a query for all flows for a single app instance in the * cache and return {@link BetterFlowCacheRestData} instance representing the * result * @param bfcCore * @return */ public static BetterFlowCacheRestData queryOneAppInst(BfcDb bfcCore, String appInstName) { BetterFlowCacheRestData rv = new BetterFlowCacheRestData(bfcCore); rv.description = "Flow Cache for application=" + rv.counters.applName + " application instance=" + appInstName + " query type=all"; ConcurrentHashMap<Short, ConcurrentHashMap<Long, ConcurrentHashMap<Long, FlowCacheObj>>> entries = bfcCore.getFlowCache().get(appInstName); rv.addAllEntriesForOneAppInstance(appInstName, entries); return rv; } /** * Return a {@link BetterFlowCacheRestData} representing a query error * @param description * @param status * @return */ public static BetterFlowCacheRestData queryError(String description, String status) { BetterFlowCacheRestData rv = new BetterFlowCacheRestData(); rv.description = description; rv.status = status; return rv; } } @Get("json") public BetterFlowCacheRestData handleApiQuery() { String applName = (String)getRequestAttributes().get("applName"); String applInstName= (String)getRequestAttributes().get("applInstName"); String queryType = (String)getRequestAttributes().get("querytype"); if (applInstName.contains("%7C")) { applInstName=applInstName.replace("%7C", "|"); } String errorDescription = "Flow Cache for application=" + applName + " application instance=" + applInstName + " query type=" + queryType; // Currently flow cache for netVirt application only is supported if (!applName.equalsIgnoreCase("netVirt")) { String status = "ERROR: Application "+applName+" not found" ; return BetterFlowCacheRestData.queryError(errorDescription, status); } // Get the flow cache object BetterFlowCache bfc = (BetterFlowCache)getContext().getAttributes(). get(IFlowCacheService.class.getCanonicalName()); BfcDb bfcCore = bfc.getBfcCore(); // If the querytype is counters then appl name and instance are ignored if (queryType.equals("counters")) { return BetterFlowCacheRestData.queryCounters(bfcCore); } else { // Check queryType, it should be "all" if (!queryType.equalsIgnoreCase("all")) { String status = "ERROR: Unknown query type: " + queryType; return BetterFlowCacheRestData.queryError(errorDescription, status); } if (applInstName.equalsIgnoreCase("all")) { return BetterFlowCacheRestData.queryAllFlows(bfcCore); } else { // Only return flows for the given appl. instance name if (bfcCore.getFlowCache().containsKey(applInstName)) { return BetterFlowCacheRestData.queryAllFlows(bfcCore); } else { // FIXME: we shouldn't return an error here but well. // More things are broken here. String status = "ERROR: Application instance " + applInstName + " not found"; return BetterFlowCacheRestData.queryError(errorDescription, status); } } } } }