| /* |
| * 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); |
| } |
| |
| 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 |
| } |
| } |