| /* |
| * Copyright 2014-2016 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.base.Preconditions; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Maps; |
| import com.google.common.collect.Sets; |
| import org.onosproject.bmv2.api.runtime.Bmv2ExtensionSelector; |
| import org.onosproject.bmv2.api.runtime.Bmv2ExtensionTreatment; |
| import org.onosproject.bmv2.api.runtime.Bmv2TableEntry; |
| import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException; |
| import org.onosproject.bmv2.ctl.Bmv2ThriftClient; |
| 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.onosproject.net.flow.criteria.Criterion; |
| import org.onosproject.net.flow.criteria.ExtensionCriterion; |
| import org.onosproject.net.flow.criteria.ExtensionSelector; |
| import org.onosproject.net.flow.criteria.ExtensionSelectorType; |
| import org.onosproject.net.flow.instructions.ExtensionTreatment; |
| import org.onosproject.net.flow.instructions.ExtensionTreatmentType; |
| import org.onosproject.net.flow.instructions.Instruction; |
| import org.onosproject.net.flow.instructions.Instructions; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| public class Bmv2FlowRuleDriver extends AbstractHandlerBehaviour |
| implements FlowRuleProgrammable { |
| |
| private final Logger log = |
| LoggerFactory.getLogger(this.getClass()); |
| |
| // Bmv2 doesn't support proper table dump, use a local store |
| // FIXME: synchronize entries with device |
| private final Map<FlowRule, FlowEntry> deviceEntriesMap = Maps.newHashMap(); |
| private final Map<Integer, Set<FlowRule>> tableRulesMap = Maps.newHashMap(); |
| private final Map<FlowRule, Long> tableEntryIdsMap = Maps.newHashMap(); |
| |
| @Override |
| public Collection<FlowEntry> getFlowEntries() { |
| return Collections.unmodifiableCollection( |
| deviceEntriesMap.values()); |
| } |
| |
| @Override |
| public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) { |
| Bmv2ThriftClient deviceClient; |
| try { |
| deviceClient = getDeviceClient(); |
| } catch (Bmv2RuntimeException e) { |
| return Collections.emptyList(); |
| } |
| |
| List<FlowRule> appliedFlowRules = Lists.newArrayList(); |
| |
| for (FlowRule rule : rules) { |
| |
| Bmv2TableEntry entry; |
| |
| try { |
| entry = parseFlowRule(rule); |
| } catch (IllegalStateException e) { |
| log.error("Unable to parse flow rule", e); |
| continue; |
| } |
| |
| // Instantiate flowrule set for table if it does not exist |
| if (!tableRulesMap.containsKey(rule.tableId())) { |
| tableRulesMap.put(rule.tableId(), Sets.newHashSet()); |
| } |
| |
| if (tableRulesMap.get(rule.tableId()).contains(rule)) { |
| /* Rule is already installed in the table */ |
| long entryId = tableEntryIdsMap.get(rule); |
| |
| try { |
| deviceClient.modifyTableEntry( |
| entry.tableName(), entryId, entry.action()); |
| |
| // Replace stored rule as treatment, etc. might have changed |
| // Java Set doesn't replace on add, remove first |
| tableRulesMap.get(rule.tableId()).remove(rule); |
| tableRulesMap.get(rule.tableId()).add(rule); |
| tableEntryIdsMap.put(rule, entryId); |
| deviceEntriesMap.put(rule, new DefaultFlowEntry( |
| rule, FlowEntry.FlowEntryState.ADDED, 0, 0, 0)); |
| } catch (Bmv2RuntimeException e) { |
| log.error("Unable to update flow rule", e); |
| continue; |
| } |
| |
| } else { |
| /* Rule is new */ |
| try { |
| long entryId = deviceClient.addTableEntry(entry); |
| |
| tableRulesMap.get(rule.tableId()).add(rule); |
| tableEntryIdsMap.put(rule, entryId); |
| deviceEntriesMap.put(rule, new DefaultFlowEntry( |
| rule, FlowEntry.FlowEntryState.ADDED, 0, 0, 0)); |
| } catch (Bmv2RuntimeException e) { |
| log.error("Unable to add flow rule", e); |
| continue; |
| } |
| } |
| |
| appliedFlowRules.add(rule); |
| } |
| |
| return Collections.unmodifiableCollection(appliedFlowRules); |
| } |
| |
| @Override |
| public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) { |
| Bmv2ThriftClient deviceClient; |
| try { |
| deviceClient = getDeviceClient(); |
| } catch (Bmv2RuntimeException e) { |
| return Collections.emptyList(); |
| } |
| |
| List<FlowRule> removedFlowRules = Lists.newArrayList(); |
| |
| for (FlowRule rule : rules) { |
| |
| if (tableEntryIdsMap.containsKey(rule)) { |
| long entryId = tableEntryIdsMap.get(rule); |
| String tableName = parseTableName(rule.tableId()); |
| |
| try { |
| deviceClient.deleteTableEntry(tableName, entryId); |
| } catch (Bmv2RuntimeException e) { |
| log.error("Unable to delete flow rule", e); |
| continue; |
| } |
| |
| /* remove from local store */ |
| tableEntryIdsMap.remove(rule); |
| tableRulesMap.get(rule.tableId()).remove(rule); |
| deviceEntriesMap.remove(rule); |
| |
| removedFlowRules.add(rule); |
| } |
| } |
| |
| return Collections.unmodifiableCollection(removedFlowRules); |
| } |
| |
| private Bmv2TableEntry parseFlowRule(FlowRule flowRule) { |
| |
| // TODO make it pipeline dependant, i.e. implement mapping |
| |
| Bmv2TableEntry.Builder entryBuilder = Bmv2TableEntry.builder(); |
| |
| // Check selector |
| ExtensionCriterion ec = |
| (ExtensionCriterion) flowRule |
| .selector().getCriterion(Criterion.Type.EXTENSION); |
| Preconditions.checkState( |
| flowRule.selector().criteria().size() == 1 |
| && ec != null, |
| "Selector must have only 1 criterion of type EXTENSION"); |
| ExtensionSelector es = ec.extensionSelector(); |
| Preconditions.checkState( |
| es.type() == ExtensionSelectorType.ExtensionSelectorTypes.P4_BMV2_MATCH_KEY.type(), |
| "ExtensionSelectorType must be P4_BMV2_MATCH_KEY"); |
| |
| // Selector OK, get Bmv2MatchKey |
| entryBuilder.withMatchKey(((Bmv2ExtensionSelector) es).matchKey()); |
| |
| // Check treatment |
| Instruction inst = flowRule.treatment().allInstructions().get(0); |
| Preconditions.checkState( |
| flowRule.treatment().allInstructions().size() == 1 |
| && inst.type() == Instruction.Type.EXTENSION, |
| "Treatment must have only 1 instruction of type EXTENSION"); |
| ExtensionTreatment et = |
| ((Instructions.ExtensionInstructionWrapper) inst) |
| .extensionInstruction(); |
| |
| Preconditions.checkState( |
| et.type() == ExtensionTreatmentType.ExtensionTreatmentTypes.P4_BMV2_ACTION.type(), |
| "ExtensionTreatmentType must be P4_BMV2_ACTION"); |
| |
| // Treatment OK, get Bmv2Action |
| entryBuilder.withAction(((Bmv2ExtensionTreatment) et).getAction()); |
| |
| // Table name |
| entryBuilder.withTableName(parseTableName(flowRule.tableId())); |
| |
| if (!flowRule.isPermanent()) { |
| entryBuilder.withTimeout(flowRule.timeout()); |
| } |
| |
| entryBuilder.withPriority(flowRule.priority()); |
| |
| return entryBuilder.build(); |
| } |
| |
| private String parseTableName(int tableId) { |
| // TODO: map tableId with tableName according to P4 JSON |
| return "table" + String.valueOf(tableId); |
| } |
| |
| private Bmv2ThriftClient getDeviceClient() throws Bmv2RuntimeException { |
| try { |
| return Bmv2ThriftClient.of(handler().data().deviceId()); |
| } catch (Bmv2RuntimeException e) { |
| log.error("Failed to connect to Bmv2 device", e); |
| throw e; |
| } |
| } |
| } |