/* * Copyright 2016-present Open Networking Laboratory * * 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.onosproject.drivers.bmv2; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.apache.commons.lang3.tuple.Pair; import org.onlab.osgi.ServiceNotFoundException; import org.onosproject.bmv2.api.context.Bmv2Configuration; import org.onosproject.bmv2.api.context.Bmv2DeviceContext; import org.onosproject.bmv2.api.context.Bmv2FlowRuleTranslator; import org.onosproject.bmv2.api.context.Bmv2FlowRuleTranslatorException; import org.onosproject.bmv2.api.context.Bmv2Interpreter; import org.onosproject.bmv2.api.context.Bmv2TableModel; import org.onosproject.bmv2.api.runtime.Bmv2DeviceAgent; import org.onosproject.bmv2.api.runtime.Bmv2FlowRuleWrapper; import org.onosproject.bmv2.api.runtime.Bmv2MatchKey; import org.onosproject.bmv2.api.runtime.Bmv2ParsedTableEntry; import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException; import org.onosproject.bmv2.api.runtime.Bmv2TableEntry; import org.onosproject.bmv2.api.runtime.Bmv2TableEntryReference; import org.onosproject.bmv2.api.service.Bmv2Controller; import org.onosproject.bmv2.api.service.Bmv2DeviceContextService; import org.onosproject.bmv2.api.service.Bmv2TableEntryService; import org.onosproject.net.DeviceId; import org.onosproject.net.driver.AbstractHandlerBehaviour; import org.onosproject.net.flow.DefaultFlowEntry; import org.onosproject.net.flow.FlowEntry; import org.onosproject.net.flow.FlowRule; import org.onosproject.net.flow.FlowRuleProgrammable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import static org.onosproject.bmv2.api.runtime.Bmv2RuntimeException.Code.*; import static org.onosproject.net.flow.FlowEntry.FlowEntryState.ADDED; /** * Implementation of the flow rule programmable behaviour for BMv2. */ public class Bmv2FlowRuleProgrammable extends AbstractHandlerBehaviour implements FlowRuleProgrammable { private final Logger log = LoggerFactory.getLogger(this.getClass()); // Needed to synchronize operations over the same table entry. private static final ConcurrentMap<Bmv2TableEntryReference, Lock> ENTRY_LOCKS = Maps.newConcurrentMap(); private Bmv2Controller controller; private Bmv2TableEntryService tableEntryService; private Bmv2DeviceContextService contextService; private boolean init() { try { controller = handler().get(Bmv2Controller.class); tableEntryService = handler().get(Bmv2TableEntryService.class); contextService = handler().get(Bmv2DeviceContextService.class); return true; } catch (ServiceNotFoundException e) { log.warn(e.getMessage()); return false; } } @Override public Collection<FlowEntry> getFlowEntries() { if (!init()) { return Collections.emptyList(); } DeviceId deviceId = handler().data().deviceId(); Bmv2DeviceAgent deviceAgent; try { deviceAgent = controller.getAgent(deviceId); } catch (Bmv2RuntimeException e) { log.error("Failed to get BMv2 device agent: {}", e.explain()); return Collections.emptyList(); } Bmv2DeviceContext context = contextService.getContext(deviceId); if (context == null) { log.warn("Unable to get device context for {}", deviceId); return Collections.emptyList(); } Bmv2Interpreter interpreter = context.interpreter(); Bmv2Configuration configuration = context.configuration(); List<FlowEntry> entryList = Lists.newArrayList(); for (Bmv2TableModel table : configuration.tables()) { // For each table in the configuration AND exposed by the interpreter. if (!interpreter.tableIdMap().inverse().containsKey(table.name())) { continue; // next table } List<Bmv2ParsedTableEntry> installedEntries; try { installedEntries = deviceAgent.getTableEntries(table.name()); } catch (Bmv2RuntimeException e) { log.warn("Failed to get table entries of table {} of {}: {}", table.name(), deviceId, e.explain()); continue; // next table } for (Bmv2ParsedTableEntry parsedEntry : installedEntries) { Bmv2TableEntryReference entryRef = new Bmv2TableEntryReference(deviceId, table.name(), parsedEntry.matchKey()); Lock lock = ENTRY_LOCKS.computeIfAbsent(entryRef, key -> new ReentrantLock()); lock.lock(); try { Bmv2FlowRuleWrapper frWrapper = tableEntryService.lookup(entryRef); if (frWrapper == null) { log.debug("Missing reference from table entry service. Deleting it. BUG? " + "deviceId={}, tableName={}, matchKey={}", deviceId, table.name(), entryRef.matchKey()); try { doRemove(deviceAgent, table.name(), parsedEntry.entryId(), parsedEntry.matchKey()); } catch (Bmv2RuntimeException e) { log.warn("Unable to remove inconsistent flow rule: {}", e.explain()); } continue; // next entry } long remoteEntryId = parsedEntry.entryId(); long localEntryId = frWrapper.entryId(); if (remoteEntryId != localEntryId) { log.debug("getFlowEntries(): inconsistent entry id! BUG? Updating it... remote={}, local={}", remoteEntryId, localEntryId); frWrapper = new Bmv2FlowRuleWrapper(frWrapper.rule(), remoteEntryId, frWrapper.installedOnMillis()); tableEntryService.bind(entryRef, frWrapper); } long bytes = 0L; long packets = 0L; if (table.hasCounters()) { // Read counter values from device. try { Pair<Long, Long> counterValue = deviceAgent.readTableEntryCounter(table.name(), remoteEntryId); bytes = counterValue.getLeft(); packets = counterValue.getRight(); } catch (Bmv2RuntimeException e) { log.warn("Unable to get counters for entry {}/{} of device {}: {}", table.name(), remoteEntryId, deviceId, e.explain()); } } FlowEntry entry = new DefaultFlowEntry(frWrapper.rule(), ADDED, frWrapper.lifeInSeconds(), packets, bytes); entryList.add(entry); } finally { lock.unlock(); } } } return Collections.unmodifiableCollection(entryList); } @Override public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) { return processFlowRules(rules, Operation.APPLY); } @Override public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) { return processFlowRules(rules, Operation.REMOVE); } private Collection<FlowRule> processFlowRules(Collection<FlowRule> rules, Operation operation) { if (!init()) { return Collections.emptyList(); } DeviceId deviceId = handler().data().deviceId(); Bmv2DeviceAgent deviceAgent; try { deviceAgent = controller.getAgent(deviceId); } catch (Bmv2RuntimeException e) { log.error("Failed to get BMv2 device agent: {}", e.explain()); return Collections.emptyList(); } Bmv2DeviceContext context = contextService.getContext(deviceId); if (context == null) { log.error("Unable to get device context for {}", deviceId); return Collections.emptyList(); } Bmv2FlowRuleTranslator translator = tableEntryService.getFlowRuleTranslator(); List<FlowRule> processedFlowRules = Lists.newArrayList(); for (FlowRule rule : rules) { Bmv2TableEntry bmv2Entry; try { bmv2Entry = translator.translate(rule, context); } catch (Bmv2FlowRuleTranslatorException e) { log.warn("Unable to translate flow rule: {} - {}", e.getMessage(), rule); continue; // next rule } String tableName = bmv2Entry.tableName(); Bmv2TableEntryReference entryRef = new Bmv2TableEntryReference(deviceId, tableName, bmv2Entry.matchKey()); Lock lock = ENTRY_LOCKS.computeIfAbsent(entryRef, k -> new ReentrantLock()); lock.lock(); try { // Get from store Bmv2FlowRuleWrapper frWrapper = tableEntryService.lookup(entryRef); try { if (operation == Operation.APPLY) { // Apply entry long entryId; if (frWrapper != null) { // Existing entry. entryId = frWrapper.entryId(); // Tentatively delete entry before re-adding. // It might not exist on device due to inconsistencies. silentlyRemove(deviceAgent, entryRef.tableName(), entryId); } // Add entry. entryId = doAddEntry(deviceAgent, bmv2Entry); frWrapper = new Bmv2FlowRuleWrapper(rule, entryId, System.currentTimeMillis()); } else { // Remove entry if (frWrapper == null) { // Entry not found in map, how come? forceRemove(deviceAgent, entryRef.tableName(), entryRef.matchKey()); } else { long entryId = frWrapper.entryId(); doRemove(deviceAgent, entryRef.tableName(), entryId, entryRef.matchKey()); } frWrapper = null; } // If here, no exceptions... things went well :) processedFlowRules.add(rule); } catch (Bmv2RuntimeException e) { log.warn("Unable to {} flow rule: {}", operation.name(), e.explain()); } // Update entryRef binding in table entry service. if (frWrapper != null) { tableEntryService.bind(entryRef, frWrapper); } else { tableEntryService.unbind(entryRef); } } finally { lock.unlock(); } } return processedFlowRules; } private long doAddEntry(Bmv2DeviceAgent agent, Bmv2TableEntry entry) throws Bmv2RuntimeException { try { return agent.addTableEntry(entry); } catch (Bmv2RuntimeException e) { if (e.getCode().equals(TABLE_DUPLICATE_ENTRY)) { forceRemove(agent, entry.tableName(), entry.matchKey()); return agent.addTableEntry(entry); } else { throw e; } } } private void doRemove(Bmv2DeviceAgent agent, String tableName, long entryId, Bmv2MatchKey matchKey) throws Bmv2RuntimeException { try { agent.deleteTableEntry(tableName, entryId); } catch (Bmv2RuntimeException e) { if (e.getCode().equals(TABLE_INVALID_HANDLE) || e.getCode().equals(TABLE_EXPIRED_HANDLE)) { // entry is not there with the declared ID, try with a forced remove. forceRemove(agent, tableName, matchKey); } else { throw e; } } } private void forceRemove(Bmv2DeviceAgent agent, String tableName, Bmv2MatchKey matchKey) throws Bmv2RuntimeException { // Find the entryID (expensive call!) for (Bmv2ParsedTableEntry pEntry : agent.getTableEntries(tableName)) { if (pEntry.matchKey().equals(matchKey)) { // Remove entry and drop exceptions. silentlyRemove(agent, tableName, pEntry.entryId()); break; } } } private void silentlyRemove(Bmv2DeviceAgent agent, String tableName, long entryId) { try { agent.deleteTableEntry(tableName, entryId); } catch (Bmv2RuntimeException e) { // do nothing } } private enum Operation { APPLY, REMOVE } }