diff options
author | Ashlee Young <ashlee@onosfw.com> | 2015-09-09 22:15:21 -0700 |
---|---|---|
committer | Ashlee Young <ashlee@onosfw.com> | 2015-09-09 22:15:21 -0700 |
commit | 13d05bc8458758ee39cb829098241e89616717ee (patch) | |
tree | 22a4d1ce65f15952f07a3df5af4b462b4697cb3a /framework/src/onos/drivers | |
parent | 6139282e1e93c2322076de4b91b1c85d0bc4a8b3 (diff) |
ONOS checkin based on commit tag e796610b1f721d02f9b0e213cf6f7790c10ecd60
Change-Id: Ife8810491034fe7becdba75dda20de4267bd15cd
Diffstat (limited to 'framework/src/onos/drivers')
27 files changed, 6513 insertions, 0 deletions
diff --git a/framework/src/onos/drivers/features.xml b/framework/src/onos/drivers/features.xml new file mode 100644 index 00000000..a7144492 --- /dev/null +++ b/framework/src/onos/drivers/features.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<!-- + ~ Copyright 2015 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. + --> +<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${project.version}"> + <repository>mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features</repository> + <feature name="${project.artifactId}" version="${project.version}" + description="${project.description}"> + <feature>onos-api</feature> + <bundle>mvn:${project.groupId}/${project.artifactId}/${project.version}</bundle> + <bundle>mvn:${project.groupId}/onos-of-api/${project.version}</bundle> + + <bundle>mvn:${project.groupId}/onos-ovsdb-api/${project.version}</bundle> + <bundle>mvn:${project.groupId}/onos-ovsdb-rfc/${project.version}</bundle> + </feature> +</features> diff --git a/framework/src/onos/drivers/pom.xml b/framework/src/onos/drivers/pom.xml new file mode 100644 index 00000000..b86e2bc2 --- /dev/null +++ b/framework/src/onos/drivers/pom.xml @@ -0,0 +1,93 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2014 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. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onos</artifactId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>onos-drivers</artifactId> + <packaging>bundle</packaging> + + <description>Builtin device drivers</description> + + <properties> + <onos.app.name>org.onosproject.drivers</onos.app.name> + </properties> + + <dependencies> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.compendium</artifactId> + </dependency> + + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-api</artifactId> + </dependency> + + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-of-api</artifactId> + </dependency> + + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-core-serializers</artifactId> + <version>1.3.0-SNAPSHOT</version> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-ovsdb-api</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.easymock</groupId> + <artifactId>easymock</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.scr.annotations</artifactId> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-scr-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.onosproject</groupId> + <artifactId>onos-maven-plugin</artifactId> + </plugin> + </plugins> + </build> +</project> diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/DefaultDrivers.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/DefaultDrivers.java new file mode 100644 index 00000000..83adcde7 --- /dev/null +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/DefaultDrivers.java @@ -0,0 +1,69 @@ +/* + * Copyright 2015 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.driver; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.onosproject.net.driver.DefaultDriverProviderService; +import org.onosproject.net.driver.DriverAdminService; +import org.onosproject.net.driver.DriverProvider; +import org.onosproject.net.driver.XmlDriverLoader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.InputStream; + +/** + * Bootstrap for built in drivers. + */ +@Service +@Component(immediate = false) +public class DefaultDrivers implements DefaultDriverProviderService { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + private static final String DRIVERS_XML = "/onos-drivers.xml"; + + private DriverProvider provider; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DriverAdminService driverAdminService; + + @Activate + protected void activate() { + ClassLoader classLoader = getClass().getClassLoader(); + try { + InputStream stream = classLoader.getResourceAsStream(DRIVERS_XML); + provider = new XmlDriverLoader(classLoader) + .loadDrivers(stream, driverAdminService); + driverAdminService.registerProvider(provider); + } catch (Exception e) { + log.error("Unable to load default drivers", e); + } + log.info("Started"); + } + + @Deactivate + protected void deactivate() { + driverAdminService.unregisterProvider(provider); + log.info("Stopped"); + } + +} diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/handshaker/CalientFiberSwitchHandshaker.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/handshaker/CalientFiberSwitchHandshaker.java new file mode 100644 index 00000000..df4dfa53 --- /dev/null +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/handshaker/CalientFiberSwitchHandshaker.java @@ -0,0 +1,181 @@ +/* + * Copyright 2015 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.driver.handshaker; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import org.onosproject.net.Device; +import org.onosproject.openflow.controller.OpenFlowOpticalSwitch; +import org.onosproject.openflow.controller.PortDescPropertyType; +import org.onosproject.openflow.controller.driver.AbstractOpenFlowSwitch; +import org.onosproject.openflow.controller.driver.SwitchDriverSubHandshakeAlreadyStarted; +import org.onosproject.openflow.controller.driver.SwitchDriverSubHandshakeCompleted; +import org.onosproject.openflow.controller.driver.SwitchDriverSubHandshakeNotStarted; +import org.projectfloodlight.openflow.protocol.OFCalientFlowStatsRequest; +import org.projectfloodlight.openflow.protocol.OFCalientPortDescStatsEntry; +import org.projectfloodlight.openflow.protocol.OFCalientPortDescStatsReply; +import org.projectfloodlight.openflow.protocol.OFCalientPortDescStatsRequest; +import org.projectfloodlight.openflow.protocol.OFFlowStatsRequest; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFObject; +import org.projectfloodlight.openflow.protocol.OFStatsReplyFlags; +import org.projectfloodlight.openflow.protocol.OFStatsRequest; +import org.projectfloodlight.openflow.protocol.OFType; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.TableId; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +public class CalientFiberSwitchHandshaker extends AbstractOpenFlowSwitch implements OpenFlowOpticalSwitch { + + private final AtomicBoolean driverHandshakeComplete = new AtomicBoolean(false); + private List<OFCalientPortDescStatsEntry> fiberPorts = new ArrayList<>(); + + + @Override + public Boolean supportNxRole() { + return false; + } + + @Override + public void startDriverHandshake() { + log.warn("Starting driver handshake for sw {}", getStringId()); + if (startDriverHandshakeCalled) { + throw new SwitchDriverSubHandshakeAlreadyStarted(); + } + startDriverHandshakeCalled = true; + try { + sendHandshakeOFExperimenterPortDescRequest(); + } catch (IOException e) { + log.error("Exception while sending experimenter port desc:", e.getMessage()); + e.printStackTrace(); + } + + } + + private void sendHandshakeOFExperimenterPortDescRequest() throws IOException { + // send multi part message for port description for optical switches + OFCalientPortDescStatsRequest portsRequest = factory() + .buildCalientPortDescStatsRequest() + .build(); + log.warn("Sending experimenter port description message {}", + portsRequest.toString()); + this.sendHandshakeMessage(portsRequest); + } + + @Override + public boolean isDriverHandshakeComplete() { + return driverHandshakeComplete.get(); + } + + @Override + public void processDriverHandshakeMessage(OFMessage m) { + if (!startDriverHandshakeCalled) { + throw new SwitchDriverSubHandshakeNotStarted(); + } + if (driverHandshakeComplete.get()) { + throw new SwitchDriverSubHandshakeCompleted(m); + } + + switch (m.getType()) { + case BARRIER_REPLY: + break; + case ERROR: + log.error("Switch Error {} {}", getStringId(), m); + break; + case FEATURES_REPLY: + break; + case FLOW_REMOVED: + break; + case GET_ASYNC_REPLY: + break; + case PACKET_IN: + break; + case PORT_STATUS: + break; + case QUEUE_GET_CONFIG_REPLY: + break; + case ROLE_REPLY: + break; + case STATS_REPLY: + log.warn("Received port desc reply"); + OFCalientPortDescStatsReply descStatsReply = (OFCalientPortDescStatsReply) m; + fiberPorts.addAll(descStatsReply.getPortDesc()); + // Multi-part message + if (!descStatsReply.getFlags().contains(OFStatsReplyFlags.REPLY_MORE)) { + driverHandshakeComplete.set(true); + } + break; + default: + log.warn("Received message {} during switch-driver " + + "subhandshake " + "from switch {} ... " + + "Ignoring message", m, + getStringId()); + + } + } + + @Override + public Device.Type deviceType() { + return Device.Type.FIBER_SWITCH; + } + + @Override + public List<? extends OFObject> getPortsOf(PortDescPropertyType type) { + return ImmutableList.copyOf(fiberPorts); + } + + @Override + public Set<PortDescPropertyType> getPortTypes() { + return ImmutableSet.of(PortDescPropertyType.OPTICAL_TRANSPORT); + } + + @Override + public final void sendMsg(OFMessage m) { + OFMessage newMsg = m; + + if (m.getType() == OFType.STATS_REQUEST) { + OFStatsRequest sr = (OFStatsRequest) m; + log.debug("Rebuilding stats request type {}", sr.getStatsType()); + switch (sr.getStatsType()) { + case FLOW: + OFCalientFlowStatsRequest request = this.factory().buildCalientFlowStatsRequest() + .setCookie(((OFFlowStatsRequest) sr).getCookie()) + .setCookieMask(((OFFlowStatsRequest) sr).getCookieMask()) + .setMatch(this.factory().matchWildcardAll()) + .setOutGroup(((OFFlowStatsRequest) sr).getOutGroup().getGroupNumber()) + .setOutPort(OFPort.ANY) + .setTableId(TableId.ALL) + .setXid(sr.getXid()) + .setFlags(sr.getFlags()) + .build(); + newMsg = request; + break; + case PORT: + // TODO + break; + default: + break; + } + } + + super.sendMsg(newMsg); + } +} diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/handshaker/CorsaSwitchHandshaker.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/handshaker/CorsaSwitchHandshaker.java new file mode 100644 index 00000000..376b3985 --- /dev/null +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/handshaker/CorsaSwitchHandshaker.java @@ -0,0 +1,92 @@ +/* + * Copyright 2015 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.driver.handshaker; + +import org.onosproject.openflow.controller.driver.AbstractOpenFlowSwitch; +import org.onosproject.openflow.controller.driver.SwitchDriverSubHandshakeAlreadyStarted; +import org.onosproject.openflow.controller.driver.SwitchDriverSubHandshakeCompleted; +import org.onosproject.openflow.controller.driver.SwitchDriverSubHandshakeNotStarted; +import org.projectfloodlight.openflow.protocol.OFBarrierRequest; +import org.projectfloodlight.openflow.protocol.OFFlowMod; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFType; +import org.projectfloodlight.openflow.types.OFGroup; +import org.projectfloodlight.openflow.types.TableId; + +import java.util.Collections; +import java.util.concurrent.atomic.AtomicBoolean; + + +/** + * Corsa switch handshaker. + */ +public class CorsaSwitchHandshaker extends AbstractOpenFlowSwitch { + + private AtomicBoolean handshakeComplete = new AtomicBoolean(false); + + private int barrierXid; + + + @Override + public Boolean supportNxRole() { + return false; + } + + @Override + public void startDriverHandshake() { + if (startDriverHandshakeCalled) { + throw new SwitchDriverSubHandshakeAlreadyStarted(); + } + startDriverHandshakeCalled = true; + OFFlowMod fm = factory().buildFlowDelete() + .setTableId(TableId.ALL) + .setOutGroup(OFGroup.ANY) + .build(); + + sendMsg(Collections.singletonList(fm)); + + barrierXid = getNextTransactionId(); + OFBarrierRequest barrier = factory().buildBarrierRequest() + .setXid(barrierXid).build(); + + + sendHandshakeMessage(barrier); + + } + + @Override + public boolean isDriverHandshakeComplete() { + if (!startDriverHandshakeCalled) { + throw new SwitchDriverSubHandshakeAlreadyStarted(); + } + return handshakeComplete.get(); + } + + @Override + public void processDriverHandshakeMessage(OFMessage m) { + if (!startDriverHandshakeCalled) { + throw new SwitchDriverSubHandshakeNotStarted(); + } + if (handshakeComplete.get()) { + throw new SwitchDriverSubHandshakeCompleted(m); + } + if (m.getType() == OFType.BARRIER_REPLY && + m.getXid() == barrierXid) { + handshakeComplete.set(true); + } + } + +} diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/handshaker/DefaultSwitchHandshaker.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/handshaker/DefaultSwitchHandshaker.java new file mode 100644 index 00000000..fb789440 --- /dev/null +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/handshaker/DefaultSwitchHandshaker.java @@ -0,0 +1,67 @@ +/* + * Copyright 2015 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.driver.handshaker; + +import org.onosproject.openflow.controller.driver.AbstractOpenFlowSwitch; +import org.projectfloodlight.openflow.protocol.OFFlowAdd; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.protocol.OFVersion; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Default driver to fallback on if no other driver is available. + */ +public class DefaultSwitchHandshaker extends AbstractOpenFlowSwitch { + + private static final int LOWEST_PRIORITY = 0; + + @Override + public Boolean supportNxRole() { + return false; + } + + @Override + public void startDriverHandshake() { + if (factory().getVersion() == OFVersion.OF_10) { + OFFlowAdd.Builder fmBuilder = factory().buildFlowAdd(); + fmBuilder.setPriority(LOWEST_PRIORITY); + sendHandshakeMessage(fmBuilder.build()); + } + } + + @Override + public void processDriverHandshakeMessage(OFMessage m) {} + + @Override + public boolean isDriverHandshakeComplete() { + return true; + } + + @Override + public List<OFPortDesc> getPorts() { + if (this.factory().getVersion() == OFVersion.OF_10) { + return Collections.unmodifiableList(features.getPorts()); + } else { + return Collections.unmodifiableList( + ports.stream().flatMap(p -> p.getEntries().stream()) + .collect(Collectors.toList())); + } + } +} diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/handshaker/NiciraSwitchHandshaker.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/handshaker/NiciraSwitchHandshaker.java new file mode 100644 index 00000000..4f61361f --- /dev/null +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/handshaker/NiciraSwitchHandshaker.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015 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.driver.handshaker; + +import org.projectfloodlight.openflow.protocol.OFVersion; + +/** + * Default driver to fallback on if no other driver is available. + */ +public class NiciraSwitchHandshaker extends DefaultSwitchHandshaker { + + @Override + public Boolean supportNxRole() { + if (this.factory().getVersion() == OFVersion.OF_10) { + return true; + } + return false; + } + +} diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/handshaker/OFOpticalSwitchImplLINC13.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/handshaker/OFOpticalSwitchImplLINC13.java new file mode 100644 index 00000000..7faee374 --- /dev/null +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/handshaker/OFOpticalSwitchImplLINC13.java @@ -0,0 +1,261 @@ +/* + * Copyright 2015 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.driver.handshaker; + +import org.onosproject.net.Device; +import com.google.common.collect.ImmutableSet; +import org.onosproject.openflow.controller.OpenFlowOpticalSwitch; +import org.onosproject.openflow.controller.PortDescPropertyType; +import org.onosproject.openflow.controller.driver.AbstractOpenFlowSwitch; +import org.onosproject.openflow.controller.driver.SwitchDriverSubHandshakeAlreadyStarted; +import org.onosproject.openflow.controller.driver.SwitchDriverSubHandshakeCompleted; +import org.onosproject.openflow.controller.driver.SwitchDriverSubHandshakeNotStarted; +import org.projectfloodlight.openflow.protocol.OFCircuitPortStatus; +import org.projectfloodlight.openflow.protocol.OFCircuitPortsReply; +import org.projectfloodlight.openflow.protocol.OFCircuitPortsRequest; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFObject; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.protocol.OFPortDescPropOpticalTransport; +import org.projectfloodlight.openflow.protocol.OFPortDescStatsReply; +import org.projectfloodlight.openflow.protocol.OFPortOptical; +import org.projectfloodlight.openflow.protocol.OFStatsReply; +import org.projectfloodlight.openflow.protocol.OFStatsType; +import org.projectfloodlight.openflow.types.OFPort; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * LINC-OE Optical Emulator switch class. + * + * The LINC ROADM emulator exposes two types of ports: OCh ports connect to ports in the packet layer, + * while OMS ports connect to an OMS port on a neighbouring ROADM. + * + * LINC sends the tap ports (OCh for our purposes) in the regular port desc stats reply, + * while it sends *all* ports (both tap and WDM ports, i.e., OCh and OMS) in the experimenter port desc stats reply. + * + */ +public class OFOpticalSwitchImplLINC13 + extends AbstractOpenFlowSwitch implements OpenFlowOpticalSwitch { + + private final AtomicBoolean driverHandshakeComplete = new AtomicBoolean(false); + private long barrierXidToWaitFor = -1; + + private List<OFPortOptical> opticalPorts; + + @Override + public void startDriverHandshake() { + log.warn("Starting driver handshake for sw {}", getStringId()); + if (startDriverHandshakeCalled) { + throw new SwitchDriverSubHandshakeAlreadyStarted(); + } + startDriverHandshakeCalled = true; + try { + sendHandshakeOFExperimenterPortDescRequest(); + } catch (IOException e) { + log.error("LINC-OE exception while sending experimenter port desc:", + e.getMessage()); + e.printStackTrace(); + } + } + + @Override + public boolean isDriverHandshakeComplete() { + return driverHandshakeComplete.get(); + } + + @Override + public void processDriverHandshakeMessage(OFMessage m) { + if (!startDriverHandshakeCalled) { + throw new SwitchDriverSubHandshakeNotStarted(); + } + if (driverHandshakeComplete.get()) { + throw new SwitchDriverSubHandshakeCompleted(m); + } + + switch (m.getType()) { + case BARRIER_REPLY: + if (m.getXid() == barrierXidToWaitFor) { + log.debug("LINC-OE Received barrier response"); + } + break; + case ERROR: + log.error("Switch {} Error {}", getStringId(), m); + break; + case FEATURES_REPLY: + break; + case FLOW_REMOVED: + break; + case GET_ASYNC_REPLY: + break; + case PACKET_IN: + break; + case PORT_STATUS: + log.warn("****LINC-OE Port Status {} {}", getStringId(), m); + processOFPortStatus((OFCircuitPortStatus) m); + break; + case QUEUE_GET_CONFIG_REPLY: + break; + case ROLE_REPLY: + break; + case STATS_REPLY: + OFStatsReply stats = (OFStatsReply) m; + if (stats.getStatsType() == OFStatsType.EXPERIMENTER) { + log.warn("LINC-OE : Received stats reply message {}", m); + createOpticalPortList((OFCircuitPortsReply) m); + driverHandshakeComplete.set(true); + } + break; + default: + log.warn("Received message {} during switch-driver " + + "subhandshake " + "from switch {} ... " + + "Ignoring message", m, + getStringId()); + + } + } + + public void processOFPortStatus(OFCircuitPortStatus ps) { + log.debug("LINC-OE ..OF Port Status :", ps); + } + + private void sendHandshakeOFExperimenterPortDescRequest() throws + IOException { + // send multi part message for port description for optical switches + OFCircuitPortsRequest circuitPortsRequest = factory() + .buildCircuitPortsRequest().setXid(getNextTransactionId()) + .build(); + log.warn("LINC-OE : Sending experimented circuit port stats " + + "message " + + "{}", + circuitPortsRequest.toString()); + this.sendHandshakeMessage(circuitPortsRequest); + } + + @Override + /** + * Returns a list of standard (Ethernet) ports. + * + * @return List of ports + */ + public List<OFPortDesc> getPorts() { + return Collections.EMPTY_LIST; + } + + + @Override + public Boolean supportNxRole() { + return false; + } + + @Override + public Device.Type deviceType() { + return Device.Type.ROADM; + } + + /** + * Checks if given port is also part of the regular port desc stats, i.e., is the port a tap port. + * + * @param port given OF port + * @return true if the port is a tap (OCh), false otherwise (OMS port) + */ + private boolean hasPort(OFPort port) { + for (OFPortDescStatsReply reply : this.ports) { + for (OFPortDesc p : reply.getEntries()) { + if (p.getPortNo().equals(port)) { + return true; + } + } + } + + return false; + } + + /** + * Creates an OpenFlow optical port based on the given port and transport type. + * + * @param port OpenFlow optical port + * @param type transport type + * @return OpenFlow optical port + */ + private OFPortOptical createOpticalPort(OFPortOptical port, short type) { + List<OFPortDescPropOpticalTransport> descList = new ArrayList<>(port.getDesc().size()); + + for (OFPortDescPropOpticalTransport desc : port.getDesc()) { + OFPortDescPropOpticalTransport newDesc = desc.createBuilder() + .setType(desc.getType()) + .setPortSignalType(type) + .setPortType(desc.getPortType()) + .setReserved(desc.getReserved()) + .build(); + descList.add(newDesc); + } + + OFPortOptical newPort = port.createBuilder() + .setConfig(port.getConfig()) + .setDesc(descList) + .setHwAddr(port.getHwAddr()) + .setName(port.getName()) + .setPortNo(port.getPortNo()) + .setState(port.getState()) + .build(); + + return newPort; + } + + /** + * Builds list of OFPortOptical ports based on the multi-part circuit ports reply. + * + * Ensure the optical transport port's signal type is configured correctly. + * + * @param wPorts OF reply with circuit ports + */ + private void createOpticalPortList(OFCircuitPortsReply wPorts) { + opticalPorts = new ArrayList<>(wPorts.getEntries().size()); + + for (OFPortOptical p : wPorts.getEntries()) { + short signalType; + + // FIXME: use constants once loxi has full optical extensions + if (hasPort(p.getPortNo())) { + signalType = 5; // OCH port + } else { + signalType = 2; // OMS port + } + + opticalPorts.add(createOpticalPort(p, signalType)); + } + } + + @Override + public List<? extends OFObject> getPortsOf(PortDescPropertyType type) { + if (!type.equals(PortDescPropertyType.OPTICAL_TRANSPORT)) { + return Collections.EMPTY_LIST; + } + + return opticalPorts; + } + + @Override + public Set<PortDescPropertyType> getPortTypes() { + return ImmutableSet.of(PortDescPropertyType.OPTICAL_TRANSPORT); + } +} diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/handshaker/package-info.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/handshaker/package-info.java new file mode 100644 index 00000000..754309eb --- /dev/null +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/handshaker/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 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. + */ + +/** + * Implementations of the OpenFlow handshake driver behaviours. + */ +package org.onosproject.driver.handshaker;
\ No newline at end of file diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/ovsdb/OvsdbBridgeConfig.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/ovsdb/OvsdbBridgeConfig.java new file mode 100644 index 00000000..524163a1 --- /dev/null +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/ovsdb/OvsdbBridgeConfig.java @@ -0,0 +1,165 @@ +/* + * Copyright 2015 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.driver.ovsdb; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.onlab.packet.IpAddress; +import org.onosproject.net.DeviceId; +import org.onosproject.net.PortNumber; +import org.onosproject.net.behaviour.BridgeConfig; +import org.onosproject.net.behaviour.BridgeDescription; +import org.onosproject.net.behaviour.BridgeName; +import org.onosproject.net.behaviour.DefaultBridgeDescription; +import org.onosproject.net.device.DefaultPortDescription; +import org.onosproject.net.device.PortDescription; +import org.onosproject.net.driver.AbstractHandlerBehaviour; +import org.onosproject.net.driver.DriverHandler; +import org.onosproject.ovsdb.controller.OvsdbBridge; +import org.onosproject.ovsdb.controller.OvsdbClientService; +import org.onosproject.ovsdb.controller.OvsdbController; +import org.onosproject.ovsdb.controller.OvsdbNodeId; +import org.onosproject.ovsdb.controller.OvsdbPort; + +/** + * The implementation of BridageConfig. + */ +public class OvsdbBridgeConfig extends AbstractHandlerBehaviour + implements BridgeConfig { + + @Override + public void addBridge(BridgeName bridgeName) { + DriverHandler handler = handler(); + OvsdbClientService clientService = getOvsdbClientService(handler); + clientService.createBridge(bridgeName.name()); + } + + @Override + public void deleteBridge(BridgeName bridgeName) { + DriverHandler handler = handler(); + OvsdbClientService clientService = getOvsdbClientService(handler); + clientService.dropBridge(bridgeName.name()); + } + + @Override + public Collection<BridgeDescription> getBridges() { + DriverHandler handler = handler(); + DeviceId deviceId = handler.data().deviceId(); + OvsdbClientService clientService = getOvsdbClientService(handler); + Set<OvsdbBridge> bridges = clientService.getBridges(); + + return bridges.stream() + .map(x -> new DefaultBridgeDescription( + BridgeName.bridgeName(x.bridgeName().value()), + deviceId, + DeviceId.deviceId("of:" + x.datapathId().value()) + ) + ) + .collect(Collectors.toSet()); + } + + @Override + public void addPort(PortDescription port) { + DriverHandler handler = handler(); + OvsdbClientService clientService = getOvsdbClientService(handler); + Set<OvsdbBridge> ovsdbSet = clientService.getBridges(); + if (ovsdbSet != null && ovsdbSet.size() > 0) { + OvsdbBridge bridge = ovsdbSet.iterator().next(); + clientService.createPort(bridge.bridgeName().toString(), port + .portNumber().toString()); + } + } + + @Override + public void deletePort(PortDescription port) { + DriverHandler handler = handler(); + OvsdbClientService clientService = getOvsdbClientService(handler); + Set<OvsdbBridge> ovsdbSet = clientService.getBridges(); + if (ovsdbSet != null && ovsdbSet.size() > 0) { + OvsdbBridge bridge = ovsdbSet.iterator().next(); + clientService.dropPort(bridge.bridgeName().toString(), port + .portNumber().toString()); + } + } + + @Override + public Collection<PortDescription> getPorts() { + DriverHandler handler = handler(); + OvsdbClientService clientService = getOvsdbClientService(handler); + Set<OvsdbPort> ports = clientService.getPorts(); + + return ports.stream() + .map(x -> new DefaultPortDescription( + PortNumber.portNumber(x.portNumber().value()), + true + ) + ) + .collect(Collectors.toSet()); + } + + // OvsdbNodeId(IP:port) is used in the adaptor while DeviceId(ovsdb:IP:port) + // is used in the core. So DeviceId need be changed to OvsdbNodeId. + private OvsdbNodeId changeDeviceIdToNodeId(DeviceId deviceId) { + int lastColon = deviceId.toString().lastIndexOf(":"); + int fistColon = deviceId.toString().indexOf(":"); + String ip = deviceId.toString().substring(fistColon + 1, lastColon); + String port = deviceId.toString().substring(lastColon + 1); + IpAddress ipAddress = IpAddress.valueOf(ip); + long portL = Long.parseLong(port); + return new OvsdbNodeId(ipAddress, portL); + } + + // Used for getting OvsdbClientService. + private OvsdbClientService getOvsdbClientService(DriverHandler handler) { + OvsdbController ovsController = handler.get(OvsdbController.class); + DeviceId deviceId = handler.data().deviceId(); + OvsdbNodeId nodeId = changeDeviceIdToNodeId(deviceId); + return ovsController.getOvsdbClient(nodeId); + } + + @Override + public Set<PortNumber> getPortNumbers() { + DriverHandler handler = handler(); + OvsdbClientService clientService = getOvsdbClientService(handler); + Set<OvsdbPort> ports = clientService.getPorts(); + + return ports.stream() + .map(x -> PortNumber.portNumber( + x.portNumber().value(), + x.portName().value() + ) + ) + .collect(Collectors.toSet()); + } + + @Override + public List<PortNumber> getLocalPorts(Iterable<String> ifaceIds) { + List<PortNumber> ports = new ArrayList<>(); + DriverHandler handler = handler(); + OvsdbClientService clientService = getOvsdbClientService(handler); + Set<OvsdbPort> ovsdbSet = clientService.getLocalPorts(ifaceIds); + ovsdbSet.forEach(o -> { + PortNumber port = PortNumber.portNumber(o.portNumber().value(), + o.portName().value()); + ports.add(port); + }); + return ports; + } +} diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/ovsdb/OvsdbTunnelConfig.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/ovsdb/OvsdbTunnelConfig.java new file mode 100644 index 00000000..d32fb6be --- /dev/null +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/ovsdb/OvsdbTunnelConfig.java @@ -0,0 +1,123 @@ +/* + * Copyright 2015 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.driver.ovsdb; + +import java.util.Collection; + +import java.util.Set; +import java.util.stream.Collectors; + +import org.onlab.packet.IpAddress; +import org.onosproject.net.DeviceId; +import org.onosproject.net.behaviour.DefaultTunnelDescription; +import org.onosproject.net.behaviour.IpTunnelEndPoint; +import org.onosproject.net.behaviour.TunnelConfig; +import org.onosproject.net.behaviour.TunnelDescription; +import org.onosproject.net.behaviour.TunnelName; +import org.onosproject.net.driver.AbstractHandlerBehaviour; +import org.onosproject.net.driver.DriverHandler; +import org.onosproject.ovsdb.controller.OvsdbClientService; +import org.onosproject.ovsdb.controller.OvsdbController; +import org.onosproject.ovsdb.controller.OvsdbNodeId; +import org.onosproject.ovsdb.controller.OvsdbTunnel; + +/** + * OVSDB-based implementation of tunnel config behaviour. + */ +public class OvsdbTunnelConfig extends AbstractHandlerBehaviour + implements TunnelConfig { + + private static final String DEFAULT_ADDRESS = "0.0.0.0"; + + @Override + public void createTunnel(TunnelDescription tunnel) { + DriverHandler handler = handler(); + OvsdbClientService ovsdbNode = getOvsdbNode(handler); + IpTunnelEndPoint ipSrc = IpTunnelEndPoint.ipTunnelPoint(IpAddress + .valueOf(DEFAULT_ADDRESS)); + IpTunnelEndPoint ipDst = IpTunnelEndPoint.ipTunnelPoint(IpAddress + .valueOf(DEFAULT_ADDRESS)); + if (tunnel.src() instanceof IpTunnelEndPoint) { + ipSrc = (IpTunnelEndPoint) tunnel.src(); + } + if (tunnel.dst() instanceof IpTunnelEndPoint) { + ipDst = (IpTunnelEndPoint) tunnel.dst(); + } + //Even if source point ip or destination point ip equals 0:0:0:0, it is still work-in-progress. + ovsdbNode.createTunnel(ipSrc.ip(), ipDst.ip()); + } + + @Override + public void removeTunnel(TunnelDescription tunnel) { + DriverHandler handler = handler(); + OvsdbClientService ovsdbNode = getOvsdbNode(handler); + IpTunnelEndPoint ipSrc = IpTunnelEndPoint.ipTunnelPoint(IpAddress + .valueOf(DEFAULT_ADDRESS)); + IpTunnelEndPoint ipDst = IpTunnelEndPoint.ipTunnelPoint(IpAddress + .valueOf(DEFAULT_ADDRESS)); + if (tunnel.src() instanceof IpTunnelEndPoint) { + ipSrc = (IpTunnelEndPoint) tunnel.src(); + } + if (tunnel.dst() instanceof IpTunnelEndPoint) { + ipDst = (IpTunnelEndPoint) tunnel.dst(); + } + //Even if source point ip or destination point ip equals 0:0:0:0, it is still work-in-progress. + ovsdbNode.dropTunnel(ipSrc.ip(), ipDst.ip()); + } + + @Override + public void updateTunnel(TunnelDescription tunnel) { + // TODO Auto-generated method stub + + } + + @Override + public Collection<TunnelDescription> getTunnels() { + DriverHandler handler = handler(); + OvsdbClientService ovsdbNode = getOvsdbNode(handler); + Set<OvsdbTunnel> tunnels = ovsdbNode.getTunnels(); + + return tunnels.stream() + .map(x -> + new DefaultTunnelDescription( + IpTunnelEndPoint.ipTunnelPoint(x.localIp()), + IpTunnelEndPoint.ipTunnelPoint(x.remoteIp()), + TunnelDescription.Type.VXLAN, + TunnelName.tunnelName(x.tunnelName().toString()) + ) + ) + .collect(Collectors.toSet()); + } + + // OvsdbNodeId(IP:port) is used in the adaptor while DeviceId(ovsdb:IP:port) + // is used in the core. So DeviceId need be changed to OvsdbNodeId. + private OvsdbNodeId changeDeviceIdToNodeId(DeviceId deviceId) { + int lastColon = deviceId.toString().lastIndexOf(":"); + int fistColon = deviceId.toString().indexOf(":"); + String ip = deviceId.toString().substring(fistColon + 1, lastColon); + String port = deviceId.toString().substring(lastColon + 1); + IpAddress ipAddress = IpAddress.valueOf(ip); + long portL = Long.parseLong(port); + return new OvsdbNodeId(ipAddress, portL); + } + + private OvsdbClientService getOvsdbNode(DriverHandler handler) { + OvsdbController ovsController = handler.get(OvsdbController.class); + DeviceId deviceId = handler.data().deviceId(); + OvsdbNodeId nodeId = changeDeviceIdToNodeId(deviceId); + return ovsController.getOvsdbClient(nodeId); + } +} diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/ovsdb/package-info.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/ovsdb/package-info.java new file mode 100644 index 00000000..8d878a50 --- /dev/null +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/ovsdb/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 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. + */ + +/** + * Implementations of OVSDB protocol configurations. + */ +package org.onosproject.driver.ovsdb;
\ No newline at end of file diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/package-info.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/package-info.java new file mode 100644 index 00000000..41a6a02f --- /dev/null +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 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. + */ + +/** + * Bootstrap of built-in device drivers. + */ +package org.onosproject.driver;
\ No newline at end of file diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/CentecV350Pipeline.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/CentecV350Pipeline.java new file mode 100644 index 00000000..ed9794c0 --- /dev/null +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/CentecV350Pipeline.java @@ -0,0 +1,625 @@ +package org.onosproject.driver.pipeline; + + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.RemovalCause; +import com.google.common.cache.RemovalNotification; + +import org.onlab.osgi.ServiceDirectory; +import org.onlab.packet.Ethernet; +import org.onlab.packet.IPv4; +import org.onlab.packet.TpPort; +import org.onlab.packet.VlanId; +import org.onlab.util.KryoNamespace; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.net.DeviceId; +import org.onosproject.net.PortNumber; +import org.onosproject.net.behaviour.NextGroup; +import org.onosproject.net.behaviour.Pipeliner; +import org.onosproject.net.behaviour.PipelinerContext; +import org.onosproject.net.driver.AbstractHandlerBehaviour; +import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.FlowRuleOperations; +import org.onosproject.net.flow.FlowRuleOperationsContext; +import org.onosproject.net.flow.FlowRuleService; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.criteria.Criterion; +import org.onosproject.net.flow.criteria.Criteria; +import org.onosproject.net.flow.criteria.EthCriterion; +import org.onosproject.net.flow.criteria.PortCriterion; +import org.onosproject.net.flow.criteria.EthTypeCriterion; +import org.onosproject.net.flow.criteria.IPCriterion; +import org.onosproject.net.flow.criteria.VlanIdCriterion; +import org.onosproject.net.flow.instructions.Instruction; +import org.onosproject.net.flow.instructions.L2ModificationInstruction; +import org.onosproject.net.flowobjective.FilteringObjective; +import org.onosproject.net.flowobjective.FlowObjectiveStore; +import org.onosproject.net.flowobjective.ForwardingObjective; +import org.onosproject.net.flowobjective.NextObjective; +import org.onosproject.net.flowobjective.Objective; +import org.onosproject.net.flowobjective.ObjectiveError; +import org.onosproject.net.group.DefaultGroupBucket; +import org.onosproject.net.group.DefaultGroupDescription; +import org.onosproject.net.group.DefaultGroupKey; +import org.onosproject.net.group.Group; +import org.onosproject.net.group.GroupBucket; +import org.onosproject.net.group.GroupBuckets; +import org.onosproject.net.group.GroupDescription; +import org.onosproject.net.group.GroupEvent; +import org.onosproject.net.group.GroupKey; +import org.onosproject.net.group.GroupListener; +import org.onosproject.net.group.GroupService; +import org.slf4j.Logger; + +import java.util.Collection; +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static org.onlab.util.Tools.groupedThreads; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Driver for Centec's V350 switches. + */ +public class CentecV350Pipeline extends AbstractHandlerBehaviour implements Pipeliner { + + protected static final int PORT_VLAN_TABLE = 0; + protected static final int FILTER_TABLE = 1; + // TMAC is configured in MAC Table to redirect packets to ROUTE_TABLE. + protected static final int MAC_TABLE = 2; + protected static final int ROUTE_TABLE = 3; + + private static final long DEFAULT_METADATA = 100; + private static final long DEFAULT_METADATA_MASK = 0xffffffffffffffffL; + + // Priority used in PORT_VLAN Table, the only priority accepted is PORT_VLAN_TABLE_PRIORITY. + // The packet passed PORT+VLAN check will goto FILTER Table. + private static final int PORT_VLAN_TABLE_PRIORITY = 0xffff; + + // Priority used in Filter Table. + private static final int FILTER_TABLE_CONTROLLER_PRIORITY = 500; + // TMAC priority should be lower than controller. + private static final int FILTER_TABLE_TMAC_PRIORITY = 200; + private static final int FILTER_TABLE_HIGHEST_PRIORITY = 0xffff; + + // Priority used in MAC Table. + // We do exact matching for DMAC+metadata, so priority is ignored and required to be set to 0xffff. + private static final int MAC_TABLE_PRIORITY = 0xffff; + + // Priority used in Route Table. + // We do LPM matching in Route Table, so priority is ignored and required to be set to 0xffff. + private static final int ROUTE_TABLE_PRIORITY = 0xffff; + + private static final short BGP_PORT = 179; + + private final Logger log = getLogger(getClass()); + + private ServiceDirectory serviceDirectory; + private FlowRuleService flowRuleService; + private CoreService coreService; + private GroupService groupService; + private FlowObjectiveStore flowObjectiveStore; + private DeviceId deviceId; + private ApplicationId appId; + + private KryoNamespace appKryo = new KryoNamespace.Builder() + .register(GroupKey.class) + .register(DefaultGroupKey.class) + .register(CentecV350Group.class) + .register(byte[].class) + .build(); + + private Cache<GroupKey, NextObjective> pendingGroups; + + private ScheduledExecutorService groupChecker = + Executors.newScheduledThreadPool(2, groupedThreads("onos/pipeliner", + "centec-V350-%d")); + + @Override + public void init(DeviceId deviceId, PipelinerContext context) { + this.serviceDirectory = context.directory(); + this.deviceId = deviceId; + + pendingGroups = CacheBuilder.newBuilder() + .expireAfterWrite(20, TimeUnit.SECONDS) + .removalListener((RemovalNotification<GroupKey, NextObjective> notification) -> { + if (notification.getCause() == RemovalCause.EXPIRED) { + fail(notification.getValue(), ObjectiveError.GROUPINSTALLATIONFAILED); + } + }).build(); + + groupChecker.scheduleAtFixedRate(new GroupChecker(), 0, 500, TimeUnit.MILLISECONDS); + + coreService = serviceDirectory.get(CoreService.class); + flowRuleService = serviceDirectory.get(FlowRuleService.class); + groupService = serviceDirectory.get(GroupService.class); + flowObjectiveStore = context.store(); + + groupService.addListener(new InnerGroupListener()); + + appId = coreService.registerApplication( + "org.onosproject.driver.CentecV350Pipeline"); + + initializePipeline(); + } + + @Override + public void filter(FilteringObjective filteringObjective) { + if (filteringObjective.type() == FilteringObjective.Type.PERMIT) { + processFilter(filteringObjective, + filteringObjective.op() == Objective.Operation.ADD, + filteringObjective.appId()); + } else { + fail(filteringObjective, ObjectiveError.UNSUPPORTED); + } + } + + @Override + public void forward(ForwardingObjective fwd) { + Collection<FlowRule> rules; + FlowRuleOperations.Builder flowBuilder = FlowRuleOperations.builder(); + + rules = processForward(fwd); + switch (fwd.op()) { + case ADD: + rules.stream() + .filter(rule -> rule != null) + .forEach(flowBuilder::add); + break; + case REMOVE: + rules.stream() + .filter(rule -> rule != null) + .forEach(flowBuilder::remove); + break; + default: + fail(fwd, ObjectiveError.UNKNOWN); + log.warn("Unknown forwarding type {}", fwd.op()); + } + + + flowRuleService.apply(flowBuilder.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + pass(fwd); + } + + @Override + public void onError(FlowRuleOperations ops) { + fail(fwd, ObjectiveError.FLOWINSTALLATIONFAILED); + } + })); + + } + + @Override + public void next(NextObjective nextObjective) { + switch (nextObjective.type()) { + case SIMPLE: + Collection<TrafficTreatment> treatments = nextObjective.next(); + if (treatments.size() == 1) { + TrafficTreatment treatment = treatments.iterator().next(); + + // Since we do not support strip_vlan in PORT_VLAN table, we use mod_vlan + // to modify the packet to desired vlan. + // Note: if we use push_vlan here, the switch will add a second VLAN tag to the outgoing + // packet, which is not what we want. + TrafficTreatment.Builder treatmentWithoutPushVlan = DefaultTrafficTreatment.builder(); + VlanId modVlanId; + for (Instruction ins : treatment.allInstructions()) { + if (ins.type() == Instruction.Type.L2MODIFICATION) { + L2ModificationInstruction l2ins = (L2ModificationInstruction) ins; + switch (l2ins.subtype()) { + case ETH_DST: + treatmentWithoutPushVlan.setEthDst( + ((L2ModificationInstruction.ModEtherInstruction) l2ins).mac()); + break; + case ETH_SRC: + treatmentWithoutPushVlan.setEthSrc( + ((L2ModificationInstruction.ModEtherInstruction) l2ins).mac()); + break; + case VLAN_ID: + modVlanId = ((L2ModificationInstruction.ModVlanIdInstruction) l2ins).vlanId(); + treatmentWithoutPushVlan.setVlanId(modVlanId); + break; + default: + break; + } + } else if (ins.type() == Instruction.Type.OUTPUT) { + //long portNum = ((Instructions.OutputInstruction) ins).port().toLong(); + treatmentWithoutPushVlan.add(ins); + } else { + // Ignore the vlan_pcp action since it's does matter much. + log.warn("Driver does not handle this type of TrafficTreatment" + + " instruction in nextObjectives: {}", ins.type()); + } + } + + GroupBucket bucket = + DefaultGroupBucket.createIndirectGroupBucket(treatmentWithoutPushVlan.build()); + final GroupKey key = new DefaultGroupKey(appKryo.serialize(nextObjective.id())); + GroupDescription groupDescription + = new DefaultGroupDescription(deviceId, + GroupDescription.Type.INDIRECT, + new GroupBuckets(Collections + .singletonList(bucket)), + key, + null, // let group service determine group id + nextObjective.appId()); + groupService.addGroup(groupDescription); + pendingGroups.put(key, nextObjective); + } + break; + case HASHED: + case BROADCAST: + case FAILOVER: + fail(nextObjective, ObjectiveError.UNSUPPORTED); + log.warn("Unsupported next objective type {}", nextObjective.type()); + break; + default: + fail(nextObjective, ObjectiveError.UNKNOWN); + log.warn("Unknown next objective type {}", nextObjective.type()); + } + + } + + private Collection<FlowRule> processForward(ForwardingObjective fwd) { + switch (fwd.flag()) { + case SPECIFIC: + return processSpecific(fwd); + case VERSATILE: + return processVersatile(fwd); + default: + fail(fwd, ObjectiveError.UNKNOWN); + log.warn("Unknown forwarding flag {}", fwd.flag()); + } + return Collections.emptySet(); + } + + private Collection<FlowRule> processVersatile(ForwardingObjective fwd) { + log.warn("Driver does not support versatile forwarding objective"); + fail(fwd, ObjectiveError.UNSUPPORTED); + return Collections.emptySet(); + } + + private Collection<FlowRule> processSpecific(ForwardingObjective fwd) { + log.debug("Processing specific forwarding objective"); + TrafficSelector selector = fwd.selector(); + EthTypeCriterion ethType = + (EthTypeCriterion) selector.getCriterion(Criterion.Type.ETH_TYPE); + if (ethType == null || ethType.ethType().toShort() != Ethernet.TYPE_IPV4) { + fail(fwd, ObjectiveError.UNSUPPORTED); + return Collections.emptySet(); + } + + // Must have metadata as key. + TrafficSelector filteredSelector = + DefaultTrafficSelector.builder() + .matchEthType(Ethernet.TYPE_IPV4) + .matchMetadata(DEFAULT_METADATA) + .matchIPDst( + ((IPCriterion) + selector.getCriterion(Criterion.Type.IPV4_DST)).ip()) + .build(); + + TrafficTreatment.Builder tb = DefaultTrafficTreatment.builder(); + + if (fwd.nextId() != null) { + NextGroup next = flowObjectiveStore.getNextGroup(fwd.nextId()); + GroupKey key = appKryo.deserialize(next.data()); + Group group = groupService.getGroup(deviceId, key); + if (group == null) { + log.warn("The group left!"); + fail(fwd, ObjectiveError.GROUPMISSING); + return Collections.emptySet(); + } + tb.group(group.id()); + } + + FlowRule.Builder ruleBuilder = DefaultFlowRule.builder() + .fromApp(fwd.appId()) + .withPriority(ROUTE_TABLE_PRIORITY) + .forDevice(deviceId) + .withSelector(filteredSelector) + .withTreatment(tb.build()); + + if (fwd.permanent()) { + ruleBuilder.makePermanent(); + } else { + ruleBuilder.makeTemporary(fwd.timeout()); + } + + ruleBuilder.forTable(ROUTE_TABLE); + + return Collections.singletonList(ruleBuilder.build()); + + } + + private void processFilter(FilteringObjective filt, boolean install, + ApplicationId applicationId) { + PortCriterion p; + if (!filt.key().equals(Criteria.dummy()) && + filt.key().type() == Criterion.Type.IN_PORT) { + p = (PortCriterion) filt.key(); + } else { + log.warn("No key defined in filtering objective from app: {}. Not" + + "processing filtering objective", applicationId); + fail(filt, ObjectiveError.UNKNOWN); + return; + } + + // Convert filtering conditions for switch-intfs into flow rules. + FlowRuleOperations.Builder ops = FlowRuleOperations.builder(); + + for (Criterion c : filt.conditions()) { + // Here we do a trick to install 2 flow rules to MAC_TABLE and ROUTE_TABLE. + if (c.type() == Criterion.Type.ETH_DST) { + EthCriterion e = (EthCriterion) c; + + // Install TMAC flow rule. + log.debug("adding rule for Termination MAC in Filter Table: {}", e.mac()); + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder(); + selector.matchEthDst(e.mac()); + // Add IPv4 matching explicitly since we will redirect it to ROUTE Table + // through MAC table. + selector.matchEthType(Ethernet.TYPE_IPV4); + treatment.transition(MAC_TABLE); + FlowRule rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(FILTER_TABLE_TMAC_PRIORITY) + .fromApp(applicationId) + .makePermanent() + .forTable(FILTER_TABLE).build(); + ops = install ? ops.add(rule) : ops.remove(rule); + + // Must install another rule to direct the IPv4 packets that hit TMAC to + // Route table. + log.debug("adding rule for Termination MAC in MAC Table: {}", e.mac()); + selector = DefaultTrafficSelector.builder(); + treatment = DefaultTrafficTreatment.builder(); + selector.matchEthDst(e.mac()); + // MAC_Table must have metadata matching configured, use the default metadata. + selector.matchMetadata(DEFAULT_METADATA); + treatment.transition(ROUTE_TABLE); + rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(MAC_TABLE_PRIORITY) + .fromApp(applicationId) + .makePermanent() + .forTable(MAC_TABLE).build(); + ops = install ? ops.add(rule) : ops.remove(rule); + } else if (c.type() == Criterion.Type.VLAN_VID) { + VlanIdCriterion v = (VlanIdCriterion) c; + log.debug("adding rule for VLAN: {}", v.vlanId()); + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder(); + selector.matchVlanId(v.vlanId()); + selector.matchInPort(p.port()); + // Although the accepted packets will be sent to filter table, we must + // explicitly set goto_table instruction here. + treatment.writeMetadata(DEFAULT_METADATA, DEFAULT_METADATA_MASK); + // set default metadata written by PORT_VLAN Table. + treatment.transition(FILTER_TABLE); + // We do not support strip vlan here, treatment.deferred().popVlan(); + // PORT_VLAN table only accept 0xffff priority since it does exact match only. + FlowRule rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(PORT_VLAN_TABLE_PRIORITY) + .fromApp(applicationId) + .makePermanent() + .forTable(PORT_VLAN_TABLE).build(); + ops = install ? ops.add(rule) : ops.remove(rule); + } else if (c.type() == Criterion.Type.IPV4_DST) { + IPCriterion ipaddr = (IPCriterion) c; + log.debug("adding IP filtering rules in FILTER table: {}", ipaddr.ip()); + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder(); + selector.matchEthType(Ethernet.TYPE_IPV4); + selector.matchIPDst(ipaddr.ip()); // router IPs to the controller + treatment.setOutput(PortNumber.CONTROLLER); + FlowRule rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(FILTER_TABLE_CONTROLLER_PRIORITY) + .fromApp(applicationId) + .makePermanent() + .forTable(FILTER_TABLE).build(); + ops = install ? ops.add(rule) : ops.remove(rule); + } else { + log.warn("Driver does not currently process filtering condition" + + " of type: {}", c.type()); + fail(filt, ObjectiveError.UNSUPPORTED); + } + } + + // apply filtering flow rules + flowRuleService.apply(ops.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + pass(filt); + log.info("Applied filtering rules"); + } + + @Override + public void onError(FlowRuleOperations ops) { + fail(filt, ObjectiveError.FLOWINSTALLATIONFAILED); + log.info("Failed to apply filtering rules"); + } + })); + } + + private void pass(Objective obj) { + if (obj.context().isPresent()) { + obj.context().get().onSuccess(obj); + } + } + + private void fail(Objective obj, ObjectiveError error) { + if (obj.context().isPresent()) { + obj.context().get().onError(obj, error); + } + } + + private void initializePipeline() { + // CENTEC_V350: PORT_VLAN_TABLE->FILTER_TABLE->MAC_TABLE(TMAC)->ROUTE_TABLE. + processPortVlanTable(true); + processFilterTable(true); + } + + private void processPortVlanTable(boolean install) { + // By default the packet are dropped, need install port+vlan by some ways. + + // XXX can we add table-miss-entry to drop? Code says drops by default + // XXX TTP description says default goes to table1. + // It also says that match is only on vlan -- not port-vlan -- which one is true? + } + + private void processFilterTable(boolean install) { + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder treatment = DefaultTrafficTreatment + .builder(); + FlowRuleOperations.Builder ops = FlowRuleOperations.builder(); + FlowRule rule; + + // Punt ARP packets to controller by default. + selector.matchEthType(Ethernet.TYPE_ARP); + treatment.punt(); + rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(FILTER_TABLE_CONTROLLER_PRIORITY) + .fromApp(appId) + .makePermanent() + .forTable(FILTER_TABLE).build(); + ops = install ? ops.add(rule) : ops.remove(rule); + + // Punt BGP packets to controller directly. + selector = DefaultTrafficSelector.builder(); + treatment = DefaultTrafficTreatment.builder(); + selector.matchEthType(Ethernet.TYPE_IPV4) + .matchIPProtocol(IPv4.PROTOCOL_TCP) + .matchTcpSrc(TpPort.tpPort(BGP_PORT)); + treatment.punt(); + rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withPriority(FILTER_TABLE_HIGHEST_PRIORITY) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .fromApp(appId) + .makePermanent() + .forTable(FILTER_TABLE).build(); + ops = install ? ops.add(rule) : ops.remove(rule); + + selector = DefaultTrafficSelector.builder(); + treatment = DefaultTrafficTreatment.builder(); + selector.matchEthType(Ethernet.TYPE_IPV4) + .matchIPProtocol(IPv4.PROTOCOL_TCP) + .matchTcpDst(TpPort.tpPort(BGP_PORT)); + treatment.punt(); + rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withPriority(FILTER_TABLE_HIGHEST_PRIORITY) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .fromApp(appId) + .makePermanent() + .forTable(FILTER_TABLE).build(); + + ops = install ? ops.add(rule) : ops.remove(rule); + + // Packet will be discard in PORT_VLAN table, no need to install rule in + // filter table. + + // XXX does not tell me if packets are going to be dropped by default in + // filter table or not? TTP says it will be dropped by default + + flowRuleService.apply(ops.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + log.info("Provisioned filter table"); + } + + @Override + public void onError(FlowRuleOperations ops) { + log.info("Failed to provision filter table"); + } + })); + } + + private class InnerGroupListener implements GroupListener { + @Override + public void event(GroupEvent event) { + if (event.type() == GroupEvent.Type.GROUP_ADDED) { + GroupKey key = event.subject().appCookie(); + + NextObjective obj = pendingGroups.getIfPresent(key); + if (obj != null) { + flowObjectiveStore.putNextGroup(obj.id(), new CentecV350Group(key)); + pass(obj); + pendingGroups.invalidate(key); + } + } + } + } + + + private class GroupChecker implements Runnable { + + @Override + public void run() { + Set<GroupKey> keys = pendingGroups.asMap().keySet().stream() + .filter(key -> groupService.getGroup(deviceId, key) != null) + .collect(Collectors.toSet()); + + keys.stream().forEach(key -> { + NextObjective obj = pendingGroups.getIfPresent(key); + if (obj == null) { + return; + } + pass(obj); + pendingGroups.invalidate(key); + log.info("Heard back from group service for group {}. " + + "Applying pending forwarding objectives", obj.id()); + flowObjectiveStore.putNextGroup(obj.id(), new CentecV350Group(key)); + }); + } + } + + private class CentecV350Group implements NextGroup { + + private final GroupKey key; + + public CentecV350Group(GroupKey key) { + this.key = key; + } + + @SuppressWarnings("unused") + public GroupKey key() { + return key; + } + + @Override + public byte[] data() { + return appKryo.serialize(key); + } + + } +} diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/CorsaPipeline.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/CorsaPipeline.java new file mode 100644 index 00000000..0c957319 --- /dev/null +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/CorsaPipeline.java @@ -0,0 +1,61 @@ +package org.onosproject.driver.pipeline; + +import static org.slf4j.LoggerFactory.getLogger; + +import org.onlab.packet.Ethernet; +import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.FlowRuleOperations; +import org.onosproject.net.flow.FlowRuleOperationsContext; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.slf4j.Logger; + +/** + * Driver for Corsa TTP. + * + */ +public class CorsaPipeline extends OVSCorsaPipeline { + + private final Logger log = getLogger(getClass()); + + @Override + protected void processVlanMplsTable(boolean install) { + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder treatment = DefaultTrafficTreatment + .builder(); + FlowRuleOperations.Builder ops = FlowRuleOperations.builder(); + FlowRule rule; + // corsa uses non-OF-standard way to match on presence of VLAN tags + selector.matchEthType(Ethernet.TYPE_VLAN); + treatment.transition(VLAN_TABLE); + + rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(CONTROLLER_PRIORITY) + .fromApp(appId) + .makePermanent() + .forTable(VLAN_MPLS_TABLE).build(); + + ops = install ? ops.add(rule) : ops.remove(rule); + + flowRuleService.apply(ops.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + log.info("Provisioned vlan/mpls table"); + } + + @Override + public void onError(FlowRuleOperations ops) { + log.info( + "Failed to provision vlan/mpls table"); + } + })); + + } + +} diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/CpqdOFDPA1Pipeline.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/CpqdOFDPA1Pipeline.java new file mode 100644 index 00000000..b338719c --- /dev/null +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/CpqdOFDPA1Pipeline.java @@ -0,0 +1,186 @@ +package org.onosproject.driver.pipeline; + +import static org.slf4j.LoggerFactory.getLogger; + +import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.FlowRuleOperations; +import org.onosproject.net.flow.FlowRuleOperationsContext; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.slf4j.Logger; + + +/** + * Driver for software switch emulation of the OFDPA 1.0 pipeline. + * The software switch is the CPqD OF 1.3 switch. + */ +public class CpqdOFDPA1Pipeline extends OFDPA1Pipeline { + + private final Logger log = getLogger(getClass()); + + @Override + protected void initializePipeline() { + processPortTable(); + //processVlanTable(); + processTmacTable(); + processIpTable(); + //processMcastTable(); + processBridgingTable(); + processAclTable(); + //processGroupTable(); + } + + @Override + protected void processPortTable() { + FlowRuleOperations.Builder ops = FlowRuleOperations.builder(); + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder(); + treatment.transition(VLAN_TABLE); + FlowRule tmisse = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(LOWEST_PRIORITY) + .fromApp(driverId) + .makePermanent() + .forTable(PORT_TABLE).build(); + ops = ops.add(tmisse); + + flowRuleService.apply(ops.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + log.info("Initialized port table"); + } + + @Override + public void onError(FlowRuleOperations ops) { + log.info("Failed to initialize port table"); + } + })); + } + + @Override + protected void processTmacTable() { + //table miss entry + FlowRuleOperations.Builder ops = FlowRuleOperations.builder(); + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder(); + selector = DefaultTrafficSelector.builder(); + treatment = DefaultTrafficTreatment.builder(); + treatment.transition(BRIDGING_TABLE); + FlowRule rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(LOWEST_PRIORITY) + .fromApp(driverId) + .makePermanent() + .forTable(TMAC_TABLE).build(); + ops = ops.add(rule); + flowRuleService.apply(ops.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + log.info("Initialized tmac table"); + } + + @Override + public void onError(FlowRuleOperations ops) { + log.info("Failed to initialize tmac table"); + } + })); + } + + @Override + protected void processIpTable() { + //table miss entry + FlowRuleOperations.Builder ops = FlowRuleOperations.builder(); + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder(); + selector = DefaultTrafficSelector.builder(); + treatment = DefaultTrafficTreatment.builder(); + treatment.transition(ACL_TABLE); + FlowRule rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(LOWEST_PRIORITY) + .fromApp(driverId) + .makePermanent() + .forTable(UNICAST_ROUTING_TABLE).build(); + ops = ops.add(rule); + flowRuleService.apply(ops.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + log.info("Initialized IP table"); + } + + @Override + public void onError(FlowRuleOperations ops) { + log.info("Failed to initialize unicast IP table"); + } + })); + } + + private void processBridgingTable() { + //table miss entry + FlowRuleOperations.Builder ops = FlowRuleOperations.builder(); + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder(); + selector = DefaultTrafficSelector.builder(); + treatment = DefaultTrafficTreatment.builder(); + treatment.transition(ACL_TABLE); + FlowRule rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(LOWEST_PRIORITY) + .fromApp(driverId) + .makePermanent() + .forTable(BRIDGING_TABLE).build(); + ops = ops.add(rule); + flowRuleService.apply(ops.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + log.info("Initialized Bridging table"); + } + + @Override + public void onError(FlowRuleOperations ops) { + log.info("Failed to initialize Bridging table"); + } + })); + } + + private void processAclTable() { + //table miss entry - catch all to executed action-set + FlowRuleOperations.Builder ops = FlowRuleOperations.builder(); + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder(); + selector = DefaultTrafficSelector.builder(); + treatment = DefaultTrafficTreatment.builder(); + FlowRule rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(LOWEST_PRIORITY) + .fromApp(driverId) + .makePermanent() + .forTable(ACL_TABLE).build(); + ops = ops.add(rule); + flowRuleService.apply(ops.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + log.info("Initialized Acl table"); + } + + @Override + public void onError(FlowRuleOperations ops) { + log.info("Failed to initialize Acl table"); + } + })); + } + +} diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/DefaultSingleTablePipeline.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/DefaultSingleTablePipeline.java new file mode 100644 index 00000000..d42650f5 --- /dev/null +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/DefaultSingleTablePipeline.java @@ -0,0 +1,129 @@ +/* + * Copyright 2015 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.driver.pipeline; + +import org.onlab.osgi.ServiceDirectory; +import org.onosproject.net.DeviceId; +import org.onosproject.net.behaviour.Pipeliner; +import org.onosproject.net.behaviour.PipelinerContext; +import org.onosproject.net.driver.AbstractHandlerBehaviour; +import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.FlowRuleOperations; +import org.onosproject.net.flow.FlowRuleOperationsContext; +import org.onosproject.net.flow.FlowRuleService; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.instructions.Instructions; +import org.onosproject.net.flowobjective.FilteringObjective; +import org.onosproject.net.flowobjective.ForwardingObjective; +import org.onosproject.net.flowobjective.NextObjective; +import org.onosproject.net.flowobjective.ObjectiveError; +import org.slf4j.Logger; + +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Simple single table pipeline abstraction. + */ +public class DefaultSingleTablePipeline extends AbstractHandlerBehaviour implements Pipeliner { + + private final Logger log = getLogger(getClass()); + + private ServiceDirectory serviceDirectory; + private FlowRuleService flowRuleService; + private DeviceId deviceId; + + @Override + public void init(DeviceId deviceId, PipelinerContext context) { + this.serviceDirectory = context.directory(); + this.deviceId = deviceId; + + flowRuleService = serviceDirectory.get(FlowRuleService.class); + } + + @Override + public void filter(FilteringObjective filter) {} + + @Override + public void forward(ForwardingObjective fwd) { + FlowRuleOperations.Builder flowBuilder = FlowRuleOperations.builder(); + + if (fwd.flag() != ForwardingObjective.Flag.VERSATILE) { + throw new UnsupportedOperationException( + "Only VERSATILE is supported."); + } + + TrafficSelector selector = fwd.selector(); + TrafficTreatment treatment = fwd.treatment(); + if ((fwd.treatment().deferred().size() == 0) && + (fwd.treatment().immediate().size() == 0) && + (fwd.treatment().tableTransition() == null) && + (!fwd.treatment().clearedDeferred())) { + TrafficTreatment.Builder flowTreatment = DefaultTrafficTreatment.builder(); + flowTreatment.add(Instructions.createDrop()); + treatment = flowTreatment.build(); + } + + FlowRule.Builder ruleBuilder = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector) + .withTreatment(treatment) + .fromApp(fwd.appId()) + .withPriority(fwd.priority()); + + if (fwd.permanent()) { + ruleBuilder.makePermanent(); + } else { + ruleBuilder.makeTemporary(fwd.timeout()); + } + + + switch (fwd.op()) { + + case ADD: + flowBuilder.add(ruleBuilder.build()); + break; + case REMOVE: + flowBuilder.remove(ruleBuilder.build()); + break; + default: + log.warn("Unknown operation {}", fwd.op()); + } + + flowRuleService.apply(flowBuilder.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + if (fwd.context().isPresent()) { + fwd.context().get().onSuccess(fwd); + } + } + + @Override + public void onError(FlowRuleOperations ops) { + if (fwd.context().isPresent()) { + fwd.context().get().onError(fwd, ObjectiveError.FLOWINSTALLATIONFAILED); + } + } + })); + + } + + @Override + public void next(NextObjective nextObjective) {} + +} diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/OFDPA1Pipeline.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/OFDPA1Pipeline.java new file mode 100644 index 00000000..f17309e1 --- /dev/null +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/OFDPA1Pipeline.java @@ -0,0 +1,887 @@ +/* + * Copyright 2015 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.driver.pipeline; + +import static org.onlab.util.Tools.groupedThreads; +import static org.slf4j.LoggerFactory.getLogger; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import org.onlab.osgi.ServiceDirectory; +import org.onlab.packet.Ethernet; +import org.onlab.packet.MplsLabel; +import org.onlab.packet.VlanId; +import org.onlab.util.KryoNamespace; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.core.DefaultGroupId; +import org.onosproject.net.DeviceId; +import org.onosproject.net.PortNumber; +import org.onosproject.net.behaviour.NextGroup; +import org.onosproject.net.behaviour.Pipeliner; +import org.onosproject.net.behaviour.PipelinerContext; +import org.onosproject.net.driver.AbstractHandlerBehaviour; +import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.FlowRuleOperations; +import org.onosproject.net.flow.FlowRuleOperationsContext; +import org.onosproject.net.flow.FlowRuleService; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.criteria.Criteria; +import org.onosproject.net.flow.criteria.Criterion; +import org.onosproject.net.flow.criteria.EthCriterion; +import org.onosproject.net.flow.criteria.EthTypeCriterion; +import org.onosproject.net.flow.criteria.IPCriterion; +import org.onosproject.net.flow.criteria.PortCriterion; +import org.onosproject.net.flow.criteria.VlanIdCriterion; +import org.onosproject.net.flow.instructions.Instruction; +import org.onosproject.net.flow.instructions.Instructions.OutputInstruction; +import org.onosproject.net.flow.instructions.L2ModificationInstruction; +import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModEtherInstruction; +import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModVlanIdInstruction; +import org.onosproject.net.flowobjective.FilteringObjective; +import org.onosproject.net.flowobjective.FlowObjectiveStore; +import org.onosproject.net.flowobjective.ForwardingObjective; +import org.onosproject.net.flowobjective.NextObjective; +import org.onosproject.net.flowobjective.Objective; +import org.onosproject.net.flowobjective.ObjectiveError; +import org.onosproject.net.group.DefaultGroupBucket; +import org.onosproject.net.group.DefaultGroupDescription; +import org.onosproject.net.group.DefaultGroupKey; +import org.onosproject.net.group.Group; +import org.onosproject.net.group.GroupBucket; +import org.onosproject.net.group.GroupBuckets; +import org.onosproject.net.group.GroupDescription; +import org.onosproject.net.group.GroupEvent; +import org.onosproject.net.group.GroupKey; +import org.onosproject.net.group.GroupListener; +import org.onosproject.net.group.GroupService; +import org.onosproject.store.serializers.KryoNamespaces; +import org.slf4j.Logger; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.RemovalCause; +import com.google.common.cache.RemovalNotification; + +/** + * Driver for Broadcom's OF-DPA v1.0 TTP. + * + */ +public class OFDPA1Pipeline extends AbstractHandlerBehaviour implements Pipeliner { + + protected static final int PORT_TABLE = 0; + protected static final int VLAN_TABLE = 10; + protected static final int TMAC_TABLE = 20; + protected static final int UNICAST_ROUTING_TABLE = 30; + protected static final int MULTICAST_ROUTING_TABLE = 40; + protected static final int BRIDGING_TABLE = 50; + protected static final int ACL_TABLE = 60; + protected static final int MAC_LEARNING_TABLE = 254; + + private static final int HIGHEST_PRIORITY = 0xffff; + private static final int DEFAULT_PRIORITY = 0x8000; + protected static final int LOWEST_PRIORITY = 0x0; + + /* + * Group keys are normally generated by using the next Objective id. In the + * case of a next objective resulting in a group chain, each group derives a + * group key from the next objective id in the following way: + * The upper 4 bits of the group-key are used to denote the position of the + * group in the group chain. For example, in the chain + * group0 --> group1 --> group2 --> port + * group0's group key would have the upper 4 bits as 0, group1's upper four + * bits would be 1, and so on + */ + private static final int GROUP0MASK = 0x0; + private static final int GROUP1MASK = 0x10000000; + + /* + * OFDPA requires group-id's to have a certain form. + * L2 Interface Groups have <4bits-0><12bits-vlanid><16bits-portid> + * L3 Unicast Groups have <4bits-2><28bits-index> + */ + private static final int L2INTERFACEMASK = 0x0; + private static final int L3UNICASTMASK = 0x20000000; + + private final Logger log = getLogger(getClass()); + private ServiceDirectory serviceDirectory; + protected FlowRuleService flowRuleService; + private CoreService coreService; + private GroupService groupService; + private FlowObjectiveStore flowObjectiveStore; + protected DeviceId deviceId; + protected ApplicationId driverId; + + private KryoNamespace appKryo = new KryoNamespace.Builder() + .register(KryoNamespaces.API) + .register(GroupKey.class) + .register(DefaultGroupKey.class) + .register(OfdpaGroupChain.class) + .register(byte[].class) + .build(); + + private Cache<GroupKey, OfdpaGroupChain> pendingNextObjectives; + private ConcurrentHashMap<GroupKey, GroupChainElem> pendingGroups; + + private ScheduledExecutorService groupChecker = + Executors.newScheduledThreadPool(2, groupedThreads("onos/pipeliner", + "ofdpa1-%d")); + + @Override + public void init(DeviceId deviceId, PipelinerContext context) { + this.serviceDirectory = context.directory(); + this.deviceId = deviceId; + + pendingNextObjectives = CacheBuilder.newBuilder() + .expireAfterWrite(20, TimeUnit.SECONDS) + .removalListener((RemovalNotification<GroupKey, OfdpaGroupChain> notification) -> { + if (notification.getCause() == RemovalCause.EXPIRED) { + fail(notification.getValue().nextObjective(), + ObjectiveError.GROUPINSTALLATIONFAILED); + } + }).build(); + + groupChecker.scheduleAtFixedRate(new GroupChecker(), 0, 500, TimeUnit.MILLISECONDS); + pendingGroups = new ConcurrentHashMap<GroupKey, GroupChainElem>(); + + coreService = serviceDirectory.get(CoreService.class); + flowRuleService = serviceDirectory.get(FlowRuleService.class); + groupService = serviceDirectory.get(GroupService.class); + flowObjectiveStore = context.store(); + + groupService.addListener(new InnerGroupListener()); + + driverId = coreService.registerApplication( + "org.onosproject.driver.OFDPA1Pipeline"); + + initializePipeline(); + + } + + @Override + public void filter(FilteringObjective filteringObjective) { + if (filteringObjective.type() == FilteringObjective.Type.PERMIT) { + processFilter(filteringObjective, + filteringObjective.op() == Objective.Operation.ADD, + filteringObjective.appId()); + } else { + fail(filteringObjective, ObjectiveError.UNSUPPORTED); + } + } + + @Override + public void forward(ForwardingObjective fwd) { + Collection<FlowRule> rules; + FlowRuleOperations.Builder flowOpsBuilder = FlowRuleOperations.builder(); + + rules = processForward(fwd); + switch (fwd.op()) { + case ADD: + rules.stream() + .filter(rule -> rule != null) + .forEach(flowOpsBuilder::add); + break; + case REMOVE: + rules.stream() + .filter(rule -> rule != null) + .forEach(flowOpsBuilder::remove); + break; + default: + fail(fwd, ObjectiveError.UNKNOWN); + log.warn("Unknown forwarding type {}", fwd.op()); + } + + + flowRuleService.apply(flowOpsBuilder.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + pass(fwd); + } + + @Override + public void onError(FlowRuleOperations ops) { + fail(fwd, ObjectiveError.FLOWINSTALLATIONFAILED); + } + })); + + } + + @Override + public void next(NextObjective nextObjective) { + switch (nextObjective.type()) { + case SIMPLE: + Collection<TrafficTreatment> treatments = nextObjective.next(); + if (treatments.size() != 1) { + log.error("Next Objectives of type Simple should only have a " + + "single Traffic Treatment. Next Objective Id:{}", nextObjective.id()); + fail(nextObjective, ObjectiveError.BADPARAMS); + return; + } + processSimpleNextObjective(nextObjective); + break; + case HASHED: + case BROADCAST: + case FAILOVER: + fail(nextObjective, ObjectiveError.UNSUPPORTED); + log.warn("Unsupported next objective type {}", nextObjective.type()); + break; + default: + fail(nextObjective, ObjectiveError.UNKNOWN); + log.warn("Unknown next objective type {}", nextObjective.type()); + } + } + + /** + * As per OFDPA 1.0 TTP, filtering of VLAN ids, MAC addresses (for routing) + * and IP addresses configured on switch ports happen in different tables. + * Note that IP filtering rules need to be added to the ACL table, as there + * is no mechanism to send to controller via IP table. + * + * @param filt + * @param install + * @param applicationId + */ + private void processFilter(FilteringObjective filt, + boolean install, ApplicationId applicationId) { + // This driver only processes filtering criteria defined with switch + // ports as the key + PortCriterion p = null; EthCriterion e = null; VlanIdCriterion v = null; + Collection<IPCriterion> ips = new ArrayList<IPCriterion>(); + if (!filt.key().equals(Criteria.dummy()) && + filt.key().type() == Criterion.Type.IN_PORT) { + p = (PortCriterion) filt.key(); + } else { + log.warn("No key defined in filtering objective from app: {}. Not" + + "processing filtering objective", applicationId); + fail(filt, ObjectiveError.UNKNOWN); + return; + } + // convert filtering conditions for switch-intfs into flowrules + FlowRuleOperations.Builder ops = FlowRuleOperations.builder(); + for (Criterion c : filt.conditions()) { + if (c.type() == Criterion.Type.ETH_DST) { + e = (EthCriterion) c; + } else if (c.type() == Criterion.Type.VLAN_VID) { + v = (VlanIdCriterion) c; + } else if (c.type() == Criterion.Type.IPV4_DST) { + ips.add((IPCriterion) c); + } else { + log.error("Unsupported filter {}", c); + fail(filt, ObjectiveError.UNSUPPORTED); + return; + } + } + + log.debug("adding VLAN filtering rule in VLAN table: {}", e.mac()); + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder(); + selector.matchInPort(p.port()); + selector.matchVlanId(v.vlanId()); + treatment.transition(TMAC_TABLE); + FlowRule rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(DEFAULT_PRIORITY) + .fromApp(applicationId) + .makePermanent() + .forTable(VLAN_TABLE).build(); + ops = ops.add(rule); + + log.debug("adding MAC filtering rules in TMAC table: {}", e.mac()); + selector = DefaultTrafficSelector.builder(); + treatment = DefaultTrafficTreatment.builder(); + selector.matchInPort(p.port()); + selector.matchVlanId(v.vlanId()); + selector.matchEthType(Ethernet.TYPE_IPV4); + selector.matchEthDst(e.mac()); + treatment.transition(UNICAST_ROUTING_TABLE); + rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(DEFAULT_PRIORITY) + .fromApp(applicationId) + .makePermanent() + .forTable(TMAC_TABLE).build(); + ops = ops.add(rule); + + log.debug("adding IP filtering rules in ACL table"); + for (IPCriterion ipaddr : ips) { + selector = DefaultTrafficSelector.builder(); + treatment = DefaultTrafficTreatment.builder(); + selector.matchEthType(Ethernet.TYPE_IPV4); + selector.matchIPDst(ipaddr.ip()); + treatment.setOutput(PortNumber.CONTROLLER); + rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(HIGHEST_PRIORITY) + .fromApp(applicationId) + .makePermanent() + .forTable(ACL_TABLE).build(); + ops = ops.add(rule); + } + + ops = install ? ops.add(rule) : ops.remove(rule); + // apply filtering flow rules + flowRuleService.apply(ops.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + log.info("Applied filtering rules"); + pass(filt); + } + + @Override + public void onError(FlowRuleOperations ops) { + log.info("Failed to apply filtering rules"); + fail(filt, ObjectiveError.FLOWINSTALLATIONFAILED); + } + })); + + } + + + /** + * As per the OFDPA 1.0 TTP, packets are sent out of ports by using + * a chain of groups, namely an L3 Unicast Group that points to an L2 Interface + * Group which in turns points to an output port. The Next Objective passed + * in by the application has to be broken up into a group chain + * to satisfy this TTP. + * + * @param nextObj the nextObjective of type SIMPLE + */ + private void processSimpleNextObjective(NextObjective nextObj) { + // break up simple next objective to GroupChain objects + TrafficTreatment treatment = nextObj.next().iterator().next(); + // for the l2interface group, get vlan and port info + // for the l3unicast group, get the src/dst mac and vlan info + TrafficTreatment.Builder l3utt = DefaultTrafficTreatment.builder(); + TrafficTreatment.Builder l2itt = DefaultTrafficTreatment.builder(); + VlanId vlanid = null; + long portNum = 0; + for (Instruction ins : treatment.allInstructions()) { + if (ins.type() == Instruction.Type.L2MODIFICATION) { + L2ModificationInstruction l2ins = (L2ModificationInstruction) ins; + switch (l2ins.subtype()) { + case ETH_DST: + l3utt.setEthDst(((ModEtherInstruction) l2ins).mac()); + break; + case ETH_SRC: + l3utt.setEthSrc(((ModEtherInstruction) l2ins).mac()); + break; + case VLAN_ID: + vlanid = ((ModVlanIdInstruction) l2ins).vlanId(); + l3utt.setVlanId(vlanid); + break; + case DEC_MPLS_TTL: + case MPLS_LABEL: + case MPLS_POP: + case MPLS_PUSH: + case VLAN_PCP: + case VLAN_POP: + case VLAN_PUSH: + default: + break; + } + } else if (ins.type() == Instruction.Type.OUTPUT) { + portNum = ((OutputInstruction) ins).port().toLong(); + l2itt.add(ins); + } else { + log.warn("Driver does not handle this type of TrafficTreatment" + + " instruction in nextObjectives: {}", ins.type()); + } + } + + // assemble information for ofdpa l2interface group + int l2gk = nextObj.id() | GROUP1MASK; + final GroupKey l2groupkey = new DefaultGroupKey(appKryo.serialize(l2gk)); + Integer l2groupId = L2INTERFACEMASK | (vlanid.toShort() << 16) | (int) portNum; + + // assemble information for ofdpa l3unicast group + int l3gk = nextObj.id() | GROUP0MASK; + final GroupKey l3groupkey = new DefaultGroupKey(appKryo.serialize(l3gk)); + Integer l3groupId = L3UNICASTMASK | (int) portNum; + l3utt.group(new DefaultGroupId(l2groupId)); + GroupChainElem gce = new GroupChainElem(l3groupkey, l3groupId, + l3utt.build(), nextObj.appId()); + + // create object for local and distributed storage + List<GroupKey> gkeys = new ArrayList<GroupKey>(); + gkeys.add(l3groupkey); // group0 in chain + gkeys.add(l2groupkey); // group1 in chain + OfdpaGroupChain ofdpaGrp = new OfdpaGroupChain(gkeys, nextObj); + + // store l2groupkey with the groupChainElem for the l3group that depends on it + pendingGroups.put(l2groupkey, gce); + + // store l3groupkey with the ofdpaGroupChain for the nextObjective that depends on it + pendingNextObjectives.put(l3groupkey, ofdpaGrp); + + // create group description for the ofdpa l2interfacegroup and send to groupservice + GroupBucket bucket = + DefaultGroupBucket.createIndirectGroupBucket(l2itt.build()); + GroupDescription groupDescription = new DefaultGroupDescription(deviceId, + GroupDescription.Type.INDIRECT, + new GroupBuckets(Collections.singletonList(bucket)), + l2groupkey, + l2groupId, + nextObj.appId()); + groupService.addGroup(groupDescription); + } + + /** + * Processes next element of a group chain. Assumption is that if this + * group points to another group, the latter has already been created + * and this driver has received notification for it. A second assumption is + * that if there is another group waiting for this group then the appropriate + * stores already have the information to act upon the notification for the + * creating of this group. + * + * @param gce the group chain element to be processed next + */ + private void processGroupChain(GroupChainElem gce) { + GroupBucket bucket = DefaultGroupBucket + .createIndirectGroupBucket(gce.getBucketActions()); + GroupDescription groupDesc = new DefaultGroupDescription(deviceId, + GroupDescription.Type.INDIRECT, + new GroupBuckets(Collections.singletonList(bucket)), + gce.getGkey(), + gce.getGivenGroupId(), + gce.getAppId()); + groupService.addGroup(groupDesc); + } + + private Collection<FlowRule> processForward(ForwardingObjective fwd) { + switch (fwd.flag()) { + case SPECIFIC: + return processSpecific(fwd); + case VERSATILE: + return processVersatile(fwd); + default: + fail(fwd, ObjectiveError.UNKNOWN); + log.warn("Unknown forwarding flag {}", fwd.flag()); + } + return Collections.emptySet(); + } + + /** + * In the OF-DPA 1.0 pipeline, versatile forwarding objectives go to the + * ACL table. + * @param fwd the forwarding objective of type 'versatile' + * @return a collection of flow rules to be sent to the switch. An empty + * collection may be returned if there is a problem in processing + * the flow rule + */ + private Collection<FlowRule> processVersatile(ForwardingObjective fwd) { + log.info("Processing versatile forwarding objective"); + TrafficSelector selector = fwd.selector(); + + EthTypeCriterion ethType = + (EthTypeCriterion) selector.getCriterion(Criterion.Type.ETH_TYPE); + if (ethType == null) { + log.error("Versatile forwarding objective must include ethType"); + fail(fwd, ObjectiveError.UNKNOWN); + return Collections.emptySet(); + } + if (ethType.ethType().toShort() == Ethernet.TYPE_ARP) { + log.warn("Installing ARP rule to table 60"); + + // currently need to punt from ACL table should use: + // OF apply-actions-instruction + // To use OF write-actions-instruction + /*TrafficTreatment.Builder tb = DefaultTrafficTreatment.builder(); + fwd.treatment().allInstructions().stream() + .forEach(ti -> tb.deferred().add(ti));*/ + + FlowRule.Builder ruleBuilder = DefaultFlowRule.builder() + .fromApp(fwd.appId()) + .withPriority(fwd.priority()) + .forDevice(deviceId) + .withSelector(fwd.selector()) + .withTreatment(fwd.treatment()) + .makePermanent() + .forTable(ACL_TABLE); + + return Collections.singletonList(ruleBuilder.build()); + } + + // XXX not handling other versatile flows yet + return Collections.emptySet(); + } + + /** + * In the OF-DPA 1.0 pipeline, specific forwarding refers to the IP table + * (unicast or multicast) or the L2 table (mac + vlan). + * + * @param fwd the forwarding objective of type 'specific' + * @return a collection of flow rules. Typically there will be only one + * for this type of forwarding objective. An empty set may be + * returned if there is an issue in processing the objective. + */ + private Collection<FlowRule> processSpecific(ForwardingObjective fwd) { + log.debug("Processing specific forwarding objective"); + TrafficSelector selector = fwd.selector(); + EthTypeCriterion ethType = + (EthTypeCriterion) selector.getCriterion(Criterion.Type.ETH_TYPE); + // XXX currently supporting only the L3 unicast table + if (ethType == null || ethType.ethType().toShort() != Ethernet.TYPE_IPV4) { + fail(fwd, ObjectiveError.UNSUPPORTED); + return Collections.emptySet(); + } + + TrafficSelector filteredSelector = + DefaultTrafficSelector.builder() + .matchEthType(Ethernet.TYPE_IPV4) + .matchIPDst( + ((IPCriterion) + selector.getCriterion(Criterion.Type.IPV4_DST)).ip()) + .build(); + + TrafficTreatment.Builder tb = DefaultTrafficTreatment.builder(); + + if (fwd.nextId() != null) { + NextGroup next = flowObjectiveStore.getNextGroup(fwd.nextId()); + List<GroupKey> gkeys = appKryo.deserialize(next.data()); + Group group = groupService.getGroup(deviceId, gkeys.get(0)); + if (group == null) { + log.warn("The group left!"); + fail(fwd, ObjectiveError.GROUPMISSING); + return Collections.emptySet(); + } + tb.deferred().group(group.id()); + } + tb.transition(ACL_TABLE); + FlowRule.Builder ruleBuilder = DefaultFlowRule.builder() + .fromApp(fwd.appId()) + .withPriority(fwd.priority()) + .forDevice(deviceId) + .withSelector(filteredSelector) + .withTreatment(tb.build()); + + if (fwd.permanent()) { + ruleBuilder.makePermanent(); + } else { + ruleBuilder.makeTemporary(fwd.timeout()); + } + + ruleBuilder.forTable(UNICAST_ROUTING_TABLE); + return Collections.singletonList(ruleBuilder.build()); + } + + private void pass(Objective obj) { + if (obj.context().isPresent()) { + obj.context().get().onSuccess(obj); + } + } + + + private void fail(Objective obj, ObjectiveError error) { + if (obj.context().isPresent()) { + obj.context().get().onError(obj, error); + } + } + + + protected void initializePipeline() { + processPortTable(); + processVlanTable(); + processTmacTable(); + processIpTable(); + //processMcastTable(); + //processBridgingTable(); + //processAclTable(); + //processGroupTable(); + //processMplsTable(); + } + + protected void processMplsTable() { + FlowRuleOperations.Builder ops = FlowRuleOperations.builder(); + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + selector.matchEthType(Ethernet.MPLS_UNICAST); + selector.matchMplsLabel(MplsLabel.mplsLabel(0xff)); + selector.matchMplsBos(true); + TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder(); + treatment.popMpls(Ethernet.TYPE_IPV4); + treatment.transition(ACL_TABLE); + FlowRule test = DefaultFlowRule.builder().forDevice(deviceId) + .withSelector(selector.build()).withTreatment(treatment.build()) + .withPriority(LOWEST_PRIORITY).fromApp(driverId).makePermanent() + .forTable(25).build(); + ops = ops.add(test); + + flowRuleService.apply(ops.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + log.info("Initialized mpls table"); + } + + @Override + public void onError(FlowRuleOperations ops) { + log.info("Failed to initialize mpls table"); + } + })); + + } + + protected void processPortTable() { + //XXX is table miss entry enough or do we need to do the maskable in-port 0? + FlowRuleOperations.Builder ops = FlowRuleOperations.builder(); + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + selector.matchInPort(PortNumber.portNumber(0)); // should be maskable? + TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder(); + treatment.transition(VLAN_TABLE); + FlowRule tmisse = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(LOWEST_PRIORITY) + .fromApp(driverId) + .makePermanent() + .forTable(PORT_TABLE).build(); + /*ops = ops.add(tmisse); + + flowRuleService.apply(ops.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + log.info("Initialized port table"); + } + + @Override + public void onError(FlowRuleOperations ops) { + log.info("Failed to initialize port table"); + } + }));*/ + + } + + private void processVlanTable() { + // Table miss entry is not required as ofdpa default is to drop + // In OF terms, the absence of a t.m.e. also implies drop + } + + + protected void processTmacTable() { + //table miss entry + FlowRuleOperations.Builder ops = FlowRuleOperations.builder(); + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder(); + selector = DefaultTrafficSelector.builder(); + treatment = DefaultTrafficTreatment.builder(); + treatment.transition(BRIDGING_TABLE); + FlowRule rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(LOWEST_PRIORITY) + .fromApp(driverId) + .makePermanent() + .forTable(TMAC_TABLE).build(); + /*ops = ops.add(rule); // XXX bug in ofdpa + flowRuleService.apply(ops.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + log.info("Initialized tmac table"); + } + + @Override + public void onError(FlowRuleOperations ops) { + log.info("Failed to initialize tmac table"); + } + }));*/ + } + + protected void processIpTable() { + //table miss entry + FlowRuleOperations.Builder ops = FlowRuleOperations.builder(); + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder(); + selector = DefaultTrafficSelector.builder(); + treatment = DefaultTrafficTreatment.builder(); + treatment.transition(ACL_TABLE); + FlowRule rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(LOWEST_PRIORITY) + .fromApp(driverId) + .makePermanent() + .forTable(UNICAST_ROUTING_TABLE).build(); + /*ops = ops.add(rule); + flowRuleService.apply(ops.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + log.info("Initialized IP table"); + } + + @Override + public void onError(FlowRuleOperations ops) { + log.info("Failed to initialize unicast IP table"); + } + }));*/ + } + + private class GroupChecker implements Runnable { + @Override + public void run() { + Set<GroupKey> keys = pendingGroups.keySet().stream() + .filter(key -> groupService.getGroup(deviceId, key) != null) + .collect(Collectors.toSet()); + Set<GroupKey> otherkeys = pendingNextObjectives.asMap().keySet().stream() + .filter(otherkey -> groupService.getGroup(deviceId, otherkey) != null) + .collect(Collectors.toSet()); + keys.addAll(otherkeys); + + keys.stream().forEach(key -> { + //first check for group chain + GroupChainElem gce = pendingGroups.remove(key); + if (gce != null) { + log.info("Group service processed group key {}. Processing next " + + "group in group chain with group key {}", + appKryo.deserialize(key.key()), + appKryo.deserialize(gce.getGkey().key())); + processGroupChain(gce); + } else { + OfdpaGroupChain obj = pendingNextObjectives.getIfPresent(key); + log.info("Group service processed group key {}. Done implementing " + + "next objective: {}", appKryo.deserialize(key.key()), + obj.nextObjective().id()); + if (obj != null) { + pass(obj.nextObjective()); + pendingNextObjectives.invalidate(key); + flowObjectiveStore.putNextGroup(obj.nextObjective().id(), obj); + } + } + }); + } + } + + private class InnerGroupListener implements GroupListener { + @Override + public void event(GroupEvent event) { + log.debug("received group event of type {}", event.type()); + if (event.type() == GroupEvent.Type.GROUP_ADDED) { + GroupKey key = event.subject().appCookie(); + // first check for group chain + GroupChainElem gce = pendingGroups.remove(key); + if (gce != null) { + log.info("group ADDED with group key {} .. " + + "Processing next group in group chain with group key {}", + appKryo.deserialize(key.key()), + appKryo.deserialize(gce.getGkey().key())); + processGroupChain(gce); + } else { + OfdpaGroupChain obj = pendingNextObjectives.getIfPresent(key); + if (obj != null) { + log.info("group ADDED with key {}.. Done implementing next " + + "objective: {}", + appKryo.deserialize(key.key()), obj.nextObjective().id()); + pass(obj.nextObjective()); + pendingNextObjectives.invalidate(key); + flowObjectiveStore.putNextGroup(obj.nextObjective().id(), obj); + } + } + } + } + } + + /** + * Represents a group-chain that implements a Next-Objective from + * the application. Includes information about the next objective Id, and the + * group keys for the groups in the group chain. The chain is expected to + * look like group0 --> group 1 --> outPort. Information about the groups + * themselves can be fetched from the Group Service using the group keys from + * objects instantiating this class. + */ + private class OfdpaGroupChain implements NextGroup { + private final NextObjective nextObj; + private final List<GroupKey> gkeys; + + /** expected group chain: group0 --> group1 --> port. */ + public OfdpaGroupChain(List<GroupKey> gkeys, NextObjective nextObj) { + this.gkeys = gkeys; + this.nextObj = nextObj; + } + + @SuppressWarnings("unused") + public List<GroupKey> groupKeys() { + return gkeys; + } + + public NextObjective nextObjective() { + return nextObj; + } + + @Override + public byte[] data() { + return appKryo.serialize(gkeys); + } + + } + + /** + * Represents a group element that is part of a chain of groups. + * Stores enough information to create a Group Description to add the group + * to the switch by requesting the Group Service. Objects instantiating this + * class are meant to be temporary and live as long as it is needed to wait for + * preceding groups in the group chain to be created. + */ + private class GroupChainElem { + private TrafficTreatment bucketActions; + private Integer givenGroupId; + private GroupKey gkey; + private ApplicationId appId; + + public GroupChainElem(GroupKey gkey, Integer givenGroupId, + TrafficTreatment tr, ApplicationId appId) { + this.bucketActions = tr; + this.givenGroupId = givenGroupId; + this.gkey = gkey; + this.appId = appId; + } + + public TrafficTreatment getBucketActions() { + return bucketActions; + } + + public Integer getGivenGroupId() { + return givenGroupId; + } + + public GroupKey getGkey() { + return gkey; + } + + public ApplicationId getAppId() { + return appId; + } + + } +} diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/OLTPipeline.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/OLTPipeline.java new file mode 100644 index 00000000..eebb2e22 --- /dev/null +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/OLTPipeline.java @@ -0,0 +1,249 @@ +/* + * Copyright 2015 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.driver.pipeline; + +import org.onlab.osgi.ServiceDirectory; +import org.onlab.packet.EthType; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.net.DefaultAnnotations; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.MastershipRole; +import org.onosproject.net.PortNumber; +import org.onosproject.net.behaviour.Pipeliner; +import org.onosproject.net.behaviour.PipelinerContext; +import org.onosproject.net.device.DefaultDeviceDescription; +import org.onosproject.net.device.DeviceDescription; +import org.onosproject.net.device.DeviceProvider; +import org.onosproject.net.device.DeviceProviderRegistry; +import org.onosproject.net.device.DeviceProviderService; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.driver.AbstractHandlerBehaviour; +import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.FlowRuleOperations; +import org.onosproject.net.flow.FlowRuleOperationsContext; +import org.onosproject.net.flow.FlowRuleService; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.instructions.Instructions; +import org.onosproject.net.flowobjective.FilteringObjective; +import org.onosproject.net.flowobjective.ForwardingObjective; +import org.onosproject.net.flowobjective.NextObjective; +import org.onosproject.net.flowobjective.ObjectiveError; +import org.onosproject.net.packet.PacketPriority; +import org.onosproject.net.provider.AbstractProvider; +import org.onosproject.net.provider.ProviderId; +import org.slf4j.Logger; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Pipeliner for OLT device. + */ +public class OLTPipeline extends AbstractHandlerBehaviour implements Pipeliner { + + private final Logger log = getLogger(getClass()); + + static final ProviderId PID = new ProviderId("olt", "org.onosproject.olt", true); + + static final String DEVICE = "isAccess"; + static final String OLT = "true"; + + private ServiceDirectory serviceDirectory; + private FlowRuleService flowRuleService; + private DeviceId deviceId; + private CoreService coreService; + + private ApplicationId appId; + + private DeviceProvider provider = new AnnotationProvider(); + + + @Override + public void init(DeviceId deviceId, PipelinerContext context) { + this.serviceDirectory = context.directory(); + this.deviceId = deviceId; + DeviceProviderRegistry registry = + serviceDirectory.get(DeviceProviderRegistry.class); + flowRuleService = serviceDirectory.get(FlowRuleService.class); + coreService = serviceDirectory.get(CoreService.class); + + try { + DeviceProviderService providerService = registry.register(provider); + providerService.deviceConnected(deviceId, + description(deviceId, DEVICE, OLT)); + } finally { + registry.unregister(provider); + } + + appId = coreService.registerApplication( + "org.onosproject.driver.OLTPipeline"); + + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchEthType(EthType.EtherType.EAPOL.ethType().toShort()) + .build(); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .punt() + .build(); + + FlowRule flowRule = new DefaultFlowRule(deviceId, selector, treatment, + PacketPriority.CONTROL.priorityValue(), + appId, 0, true, null); + + flowRuleService.applyFlowRules(flowRule); + } + + @Override + public void filter(FilteringObjective filter) { + throw new UnsupportedOperationException("Single table does not filter."); + } + + @Override + public void forward(ForwardingObjective fwd) { + FlowRuleOperations.Builder flowBuilder = FlowRuleOperations.builder(); + + if (fwd.flag() != ForwardingObjective.Flag.VERSATILE) { + throw new UnsupportedOperationException( + "Only VERSATILE is supported."); + } + + boolean isPunt = fwd.treatment().immediate().stream().anyMatch(i -> { + if (i instanceof Instructions.OutputInstruction) { + Instructions.OutputInstruction out = (Instructions.OutputInstruction) i; + return out.port().equals(PortNumber.CONTROLLER); + } + return false; + }); + + if (isPunt) { + return; + } + + TrafficSelector selector = fwd.selector(); + TrafficTreatment treatment = fwd.treatment(); + if ((fwd.treatment().deferred().size() == 0) && + (fwd.treatment().immediate().size() == 0) && + (fwd.treatment().tableTransition() == null) && + (!fwd.treatment().clearedDeferred())) { + TrafficTreatment.Builder flowTreatment = DefaultTrafficTreatment.builder(); + flowTreatment.add(Instructions.createDrop()); + treatment = flowTreatment.build(); + } + + FlowRule.Builder ruleBuilder = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector) + .withTreatment(fwd.treatment()) + .fromApp(fwd.appId()) + .withPriority(fwd.priority()); + + if (fwd.permanent()) { + ruleBuilder.makePermanent(); + } else { + ruleBuilder.makeTemporary(fwd.timeout()); + } + + + switch (fwd.op()) { + + case ADD: + flowBuilder.add(ruleBuilder.build()); + break; + case REMOVE: + flowBuilder.remove(ruleBuilder.build()); + break; + default: + log.warn("Unknown operation {}", fwd.op()); + } + + flowRuleService.apply(flowBuilder.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + if (fwd.context().isPresent()) { + fwd.context().get().onSuccess(fwd); + } + } + + @Override + public void onError(FlowRuleOperations ops) { + if (fwd.context().isPresent()) { + fwd.context().get().onError(fwd, ObjectiveError.FLOWINSTALLATIONFAILED); + } + } + })); + + } + + @Override + public void next(NextObjective nextObjective) { + throw new UnsupportedOperationException("Single table does not next hop."); + } + + /** + * Build a device description. + * @param deviceId a deviceId + * @param key the key of the annotation + * @param value the value for the annotation + * @return a device description + */ + private DeviceDescription description(DeviceId deviceId, String key, String value) { + DeviceService deviceService = serviceDirectory.get(DeviceService.class); + Device device = deviceService.getDevice(deviceId); + + checkNotNull(device, "Device not found in device service."); + + DefaultAnnotations.Builder builder = DefaultAnnotations.builder(); + if (value != null) { + builder.set(key, value); + } else { + builder.remove(key); + } + return new DefaultDeviceDescription(device.id().uri(), device.type(), + device.manufacturer(), device.hwVersion(), + device.swVersion(), device.serialNumber(), + device.chassisId(), builder.build()); + } + + /** + * Simple ancillary provider used to annotate device. + */ + private static final class AnnotationProvider + extends AbstractProvider implements DeviceProvider { + private AnnotationProvider() { + super(PID); + } + + @Override + public void triggerProbe(DeviceId deviceId) { + } + + @Override + public void roleChanged(DeviceId deviceId, MastershipRole newRole) { + } + + @Override + public boolean isReachable(DeviceId deviceId) { + return false; + } + } + +} diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/OVSCorsaPipeline.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/OVSCorsaPipeline.java new file mode 100644 index 00000000..5993d96e --- /dev/null +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/OVSCorsaPipeline.java @@ -0,0 +1,829 @@ +/* + * Copyright 2015 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.driver.pipeline; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.RemovalCause; +import com.google.common.cache.RemovalNotification; + +import org.onlab.osgi.ServiceDirectory; +import org.onlab.packet.Ethernet; +import org.onlab.packet.IPv4; +import org.onlab.packet.MacAddress; +import org.onlab.packet.VlanId; +import org.onlab.util.KryoNamespace; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.net.DeviceId; +import org.onosproject.net.behaviour.NextGroup; +import org.onosproject.net.behaviour.Pipeliner; +import org.onosproject.net.behaviour.PipelinerContext; +import org.onosproject.net.driver.AbstractHandlerBehaviour; +import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.FlowRuleOperations; +import org.onosproject.net.flow.FlowRuleOperationsContext; +import org.onosproject.net.flow.FlowRuleService; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.criteria.Criteria; +import org.onosproject.net.flow.criteria.Criterion; +import org.onosproject.net.flow.criteria.EthCriterion; +import org.onosproject.net.flow.criteria.EthTypeCriterion; +import org.onosproject.net.flow.criteria.IPCriterion; +import org.onosproject.net.flow.criteria.IPProtocolCriterion; +import org.onosproject.net.flow.criteria.PortCriterion; +import org.onosproject.net.flow.criteria.VlanIdCriterion; +import org.onosproject.net.flowobjective.FilteringObjective; +import org.onosproject.net.flowobjective.FlowObjectiveStore; +import org.onosproject.net.flowobjective.ForwardingObjective; +import org.onosproject.net.flowobjective.NextObjective; +import org.onosproject.net.flowobjective.Objective; +import org.onosproject.net.flowobjective.ObjectiveError; +import org.onosproject.net.group.DefaultGroupBucket; +import org.onosproject.net.group.DefaultGroupDescription; +import org.onosproject.net.group.DefaultGroupKey; +import org.onosproject.net.group.Group; +import org.onosproject.net.group.GroupBucket; +import org.onosproject.net.group.GroupBuckets; +import org.onosproject.net.group.GroupDescription; +import org.onosproject.net.group.GroupEvent; +import org.onosproject.net.group.GroupKey; +import org.onosproject.net.group.GroupListener; +import org.onosproject.net.group.GroupService; +import org.slf4j.Logger; + +import java.util.Collection; +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static org.onlab.util.Tools.groupedThreads; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * OpenvSwitch emulation of the Corsa pipeline handler. + */ +public class OVSCorsaPipeline extends AbstractHandlerBehaviour implements Pipeliner { + + protected static final int MAC_TABLE = 0; + protected static final int VLAN_MPLS_TABLE = 1; + protected static final int VLAN_TABLE = 2; + //protected static final int MPLS_TABLE = 3; + protected static final int ETHER_TABLE = 4; + protected static final int COS_MAP_TABLE = 5; + protected static final int FIB_TABLE = 6; + protected static final int LOCAL_TABLE = 9; + + + protected static final int CONTROLLER_PRIORITY = 255; + private static final int DROP_PRIORITY = 0; + private static final int HIGHEST_PRIORITY = 0xffff; + + private final Logger log = getLogger(getClass()); + + private ServiceDirectory serviceDirectory; + protected FlowRuleService flowRuleService; + private CoreService coreService; + private GroupService groupService; + private FlowObjectiveStore flowObjectiveStore; + protected DeviceId deviceId; + protected ApplicationId appId; + + private KryoNamespace appKryo = new KryoNamespace.Builder() + .register(GroupKey.class) + .register(DefaultGroupKey.class) + .register(CorsaGroup.class) + .register(byte[].class) + .build(); + + private Cache<GroupKey, NextObjective> pendingGroups; + + private ScheduledExecutorService groupChecker = + Executors.newScheduledThreadPool(2, groupedThreads("onos/pipeliner", + "ovs-corsa-%d")); + + @Override + public void init(DeviceId deviceId, PipelinerContext context) { + this.serviceDirectory = context.directory(); + this.deviceId = deviceId; + + pendingGroups = CacheBuilder.newBuilder() + .expireAfterWrite(20, TimeUnit.SECONDS) + .removalListener((RemovalNotification<GroupKey, NextObjective> notification) -> { + if (notification.getCause() == RemovalCause.EXPIRED) { + fail(notification.getValue(), ObjectiveError.GROUPINSTALLATIONFAILED); + } + }).build(); + + groupChecker.scheduleAtFixedRate(new GroupChecker(), 0, 500, TimeUnit.MILLISECONDS); + + coreService = serviceDirectory.get(CoreService.class); + flowRuleService = serviceDirectory.get(FlowRuleService.class); + groupService = serviceDirectory.get(GroupService.class); + flowObjectiveStore = context.store(); + + groupService.addListener(new InnerGroupListener()); + + appId = coreService.registerApplication( + "org.onosproject.driver.OVSCorsaPipeline"); + + initializePipeline(); + } + + @Override + public void filter(FilteringObjective filteringObjective) { + if (filteringObjective.type() == FilteringObjective.Type.PERMIT) { + processFilter(filteringObjective, + filteringObjective.op() == Objective.Operation.ADD, + filteringObjective.appId()); + } else { + fail(filteringObjective, ObjectiveError.UNSUPPORTED); + } + } + + @Override + public void forward(ForwardingObjective fwd) { + Collection<FlowRule> rules; + FlowRuleOperations.Builder flowBuilder = FlowRuleOperations.builder(); + + rules = processForward(fwd); + switch (fwd.op()) { + case ADD: + rules.stream() + .filter(rule -> rule != null) + .forEach(flowBuilder::add); + break; + case REMOVE: + rules.stream() + .filter(rule -> rule != null) + .forEach(flowBuilder::remove); + break; + default: + fail(fwd, ObjectiveError.UNKNOWN); + log.warn("Unknown forwarding type {}", fwd.op()); + } + + + flowRuleService.apply(flowBuilder.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + pass(fwd); + } + + @Override + public void onError(FlowRuleOperations ops) { + fail(fwd, ObjectiveError.FLOWINSTALLATIONFAILED); + } + })); + + } + + @Override + public void next(NextObjective nextObjective) { + switch (nextObjective.type()) { + case SIMPLE: + Collection<TrafficTreatment> treatments = nextObjective.next(); + if (treatments.size() == 1) { + TrafficTreatment treatment = treatments.iterator().next(); + GroupBucket bucket = + DefaultGroupBucket.createIndirectGroupBucket(treatment); + final GroupKey key = new DefaultGroupKey(appKryo.serialize(nextObjective.id())); + GroupDescription groupDescription + = new DefaultGroupDescription(deviceId, + GroupDescription.Type.INDIRECT, + new GroupBuckets(Collections + .singletonList(bucket)), + key, + null, // let group service determine group id + nextObjective.appId()); + groupService.addGroup(groupDescription); + pendingGroups.put(key, nextObjective); + } + break; + case HASHED: + case BROADCAST: + case FAILOVER: + fail(nextObjective, ObjectiveError.UNSUPPORTED); + log.warn("Unsupported next objective type {}", nextObjective.type()); + break; + default: + fail(nextObjective, ObjectiveError.UNKNOWN); + log.warn("Unknown next objective type {}", nextObjective.type()); + } + + } + + private Collection<FlowRule> processForward(ForwardingObjective fwd) { + switch (fwd.flag()) { + case SPECIFIC: + return processSpecific(fwd); + case VERSATILE: + return processVersatile(fwd); + default: + fail(fwd, ObjectiveError.UNKNOWN); + log.warn("Unknown forwarding flag {}", fwd.flag()); + } + return Collections.emptySet(); + } + + private Collection<FlowRule> processVersatile(ForwardingObjective fwd) { + log.debug("Processing versatile forwarding objective"); + TrafficSelector selector = fwd.selector(); + + EthTypeCriterion ethType = + (EthTypeCriterion) selector.getCriterion(Criterion.Type.ETH_TYPE); + if (ethType == null) { + log.error("Versatile forwarding objective must include ethType"); + fail(fwd, ObjectiveError.UNKNOWN); + return Collections.emptySet(); + } + if (ethType.ethType().toShort() == Ethernet.TYPE_ARP) { + log.warn("Driver automatically handles ARP packets by punting to controller " + + " from ETHER table"); + pass(fwd); + return Collections.emptySet(); + } else if (ethType.ethType().toShort() == Ethernet.TYPE_LLDP || + ethType.ethType().toShort() == Ethernet.TYPE_BSN) { + log.warn("Driver currently does not currently handle LLDP packets"); + fail(fwd, ObjectiveError.UNSUPPORTED); + return Collections.emptySet(); + } else if (ethType.ethType().toShort() == Ethernet.TYPE_IPV4) { + IPCriterion ipSrc = (IPCriterion) selector + .getCriterion(Criterion.Type.IPV4_SRC); + IPCriterion ipDst = (IPCriterion) selector + .getCriterion(Criterion.Type.IPV4_DST); + IPProtocolCriterion ipProto = (IPProtocolCriterion) selector + .getCriterion(Criterion.Type.IP_PROTO); + if (ipSrc != null) { + log.warn("Driver does not currently handle matching Src IP"); + fail(fwd, ObjectiveError.UNSUPPORTED); + return Collections.emptySet(); + } + if (ipDst != null) { + log.error("Driver handles Dst IP matching as specific forwarding " + + "objective, not versatile"); + fail(fwd, ObjectiveError.UNSUPPORTED); + return Collections.emptySet(); + } + if (ipProto != null && ipProto.protocol() == IPv4.PROTOCOL_TCP) { + log.warn("Driver automatically punts all packets reaching the " + + "LOCAL table to the controller"); + pass(fwd); + return Collections.emptySet(); + } + } + + log.warn("Driver does not support given versatile forwarding objective"); + fail(fwd, ObjectiveError.UNSUPPORTED); + return Collections.emptySet(); + } + + private Collection<FlowRule> processSpecific(ForwardingObjective fwd) { + log.debug("Processing specific forwarding objective"); + TrafficSelector selector = fwd.selector(); + EthTypeCriterion ethType = + (EthTypeCriterion) selector.getCriterion(Criterion.Type.ETH_TYPE); + if (ethType == null || ethType.ethType().toShort() != Ethernet.TYPE_IPV4) { + fail(fwd, ObjectiveError.UNSUPPORTED); + return Collections.emptySet(); + } + + TrafficSelector filteredSelector = + DefaultTrafficSelector.builder() + .matchEthType(Ethernet.TYPE_IPV4) + .matchIPDst( + ((IPCriterion) + selector.getCriterion(Criterion.Type.IPV4_DST)).ip()) + .build(); + + TrafficTreatment.Builder tb = DefaultTrafficTreatment.builder(); + + if (fwd.nextId() != null) { + NextGroup next = flowObjectiveStore.getNextGroup(fwd.nextId()); + GroupKey key = appKryo.deserialize(next.data()); + Group group = groupService.getGroup(deviceId, key); + if (group == null) { + log.warn("The group left!"); + fail(fwd, ObjectiveError.GROUPMISSING); + return Collections.emptySet(); + } + tb.group(group.id()); + } + + FlowRule.Builder ruleBuilder = DefaultFlowRule.builder() + .fromApp(fwd.appId()) + .withPriority(fwd.priority()) + .forDevice(deviceId) + .withSelector(filteredSelector) + .withTreatment(tb.build()); + + if (fwd.permanent()) { + ruleBuilder.makePermanent(); + } else { + ruleBuilder.makeTemporary(fwd.timeout()); + } + + ruleBuilder.forTable(FIB_TABLE); + + + return Collections.singletonList(ruleBuilder.build()); + + } + + private void processFilter(FilteringObjective filt, boolean install, + ApplicationId applicationId) { + // This driver only processes filtering criteria defined with switch + // ports as the key + PortCriterion p; + if (!filt.key().equals(Criteria.dummy()) && + filt.key().type() == Criterion.Type.IN_PORT) { + p = (PortCriterion) filt.key(); + } else { + log.warn("No key defined in filtering objective from app: {}. Not" + + "processing filtering objective", applicationId); + fail(filt, ObjectiveError.UNKNOWN); + return; + } + // convert filtering conditions for switch-intfs into flowrules + FlowRuleOperations.Builder ops = FlowRuleOperations.builder(); + for (Criterion c : filt.conditions()) { + if (c.type() == Criterion.Type.ETH_DST) { + EthCriterion e = (EthCriterion) c; + log.debug("adding rule for MAC: {}", e.mac()); + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder(); + selector.matchEthDst(e.mac()); + treatment.transition(VLAN_MPLS_TABLE); + FlowRule rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(CONTROLLER_PRIORITY) + .fromApp(applicationId) + .makePermanent() + .forTable(MAC_TABLE).build(); + ops = install ? ops.add(rule) : ops.remove(rule); + } else if (c.type() == Criterion.Type.VLAN_VID) { + VlanIdCriterion v = (VlanIdCriterion) c; + log.debug("adding rule for VLAN: {}", v.vlanId()); + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder(); + selector.matchVlanId(v.vlanId()); + selector.matchInPort(p.port()); + treatment.transition(ETHER_TABLE); + treatment.deferred().popVlan(); + FlowRule rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(CONTROLLER_PRIORITY) + .fromApp(applicationId) + .makePermanent() + .forTable(VLAN_TABLE).build(); + ops = install ? ops.add(rule) : ops.remove(rule); + } else if (c.type() == Criterion.Type.IPV4_DST) { + IPCriterion ip = (IPCriterion) c; + log.debug("adding rule for IP: {}", ip.ip()); + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder(); + selector.matchEthType(Ethernet.TYPE_IPV4); + selector.matchIPDst(ip.ip()); + treatment.transition(LOCAL_TABLE); + FlowRule rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(HIGHEST_PRIORITY) + .fromApp(applicationId) + .makePermanent() + .forTable(FIB_TABLE).build(); + + ops = install ? ops.add(rule) : ops.remove(rule); + } else { + log.warn("Driver does not currently process filtering condition" + + " of type: {}", c.type()); + fail(filt, ObjectiveError.UNSUPPORTED); + } + } + // apply filtering flow rules + flowRuleService.apply(ops.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + pass(filt); + log.info("Applied filtering rules"); + } + + @Override + public void onError(FlowRuleOperations ops) { + fail(filt, ObjectiveError.FLOWINSTALLATIONFAILED); + log.info("Failed to apply filtering rules"); + } + })); + } + + private void pass(Objective obj) { + if (obj.context().isPresent()) { + obj.context().get().onSuccess(obj); + } + } + + private void fail(Objective obj, ObjectiveError error) { + if (obj.context().isPresent()) { + obj.context().get().onError(obj, error); + } + } + + private void initializePipeline() { + processMacTable(true); + processVlanMplsTable(true); + processVlanTable(true); + processEtherTable(true); + processCosTable(true); + processFibTable(true); + processLocalTable(true); + } + + private void processMacTable(boolean install) { + TrafficSelector.Builder selector; + TrafficTreatment.Builder treatment; + + // Bcast rule + selector = DefaultTrafficSelector.builder(); + treatment = DefaultTrafficTreatment.builder(); + + selector.matchEthDst(MacAddress.BROADCAST); + treatment.transition(VLAN_MPLS_TABLE); + + FlowRule rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(CONTROLLER_PRIORITY) + .fromApp(appId) + .makePermanent() + .forTable(MAC_TABLE).build(); + + + FlowRuleOperations.Builder ops = FlowRuleOperations.builder(); + + ops = install ? ops.add(rule) : ops.remove(rule); + + + //Drop rule + selector = DefaultTrafficSelector.builder(); + treatment = DefaultTrafficTreatment.builder(); + + treatment.drop(); + + rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(DROP_PRIORITY) + .fromApp(appId) + .makePermanent() + .forTable(MAC_TABLE).build(); + + + ops = install ? ops.add(rule) : ops.remove(rule); + + flowRuleService.apply(ops.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + log.info("Provisioned mac table"); + } + + @Override + public void onError(FlowRuleOperations ops) { + log.info("Failed to provision mac table"); + } + })); + + } + + protected void processVlanMplsTable(boolean install) { + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder treatment = DefaultTrafficTreatment + .builder(); + FlowRuleOperations.Builder ops = FlowRuleOperations.builder(); + FlowRule rule; + + selector.matchVlanId(VlanId.ANY); + treatment.transition(VLAN_TABLE); + + rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(CONTROLLER_PRIORITY) + .fromApp(appId) + .makePermanent() + .forTable(VLAN_MPLS_TABLE).build(); + + + ops = install ? ops.add(rule) : ops.remove(rule); + + flowRuleService.apply(ops.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + log.info("Provisioned vlan/mpls table"); + } + + @Override + public void onError(FlowRuleOperations ops) { + log.info( + "Failed to provision vlan/mpls table"); + } + })); + + } + + private void processVlanTable(boolean install) { + TrafficSelector.Builder selector; + TrafficTreatment.Builder treatment; + FlowRuleOperations.Builder ops = FlowRuleOperations.builder(); + FlowRule rule; + + + //Drop rule + selector = DefaultTrafficSelector.builder(); + treatment = DefaultTrafficTreatment.builder(); + + treatment.drop(); + + rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(DROP_PRIORITY) + .fromApp(appId) + .makePermanent() + .forTable(VLAN_TABLE).build(); + + ops = install ? ops.add(rule) : ops.remove(rule); + + flowRuleService.apply(ops.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + log.info("Provisioned vlan table"); + } + + @Override + public void onError(FlowRuleOperations ops) { + log.info("Failed to provision vlan table"); + } + })); + } + + private void processEtherTable(boolean install) { + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder treatment = DefaultTrafficTreatment + .builder(); + FlowRuleOperations.Builder ops = FlowRuleOperations.builder(); + FlowRule rule; + + selector.matchEthType(Ethernet.TYPE_ARP); + treatment.punt(); + + rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(CONTROLLER_PRIORITY) + .fromApp(appId) + .makePermanent() + .forTable(ETHER_TABLE).build(); + + ops = install ? ops.add(rule) : ops.remove(rule); + + selector = DefaultTrafficSelector.builder(); + treatment = DefaultTrafficTreatment.builder(); + + selector.matchEthType(Ethernet.TYPE_IPV4); + treatment.transition(COS_MAP_TABLE); + + rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withPriority(CONTROLLER_PRIORITY) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .fromApp(appId) + .makePermanent() + .forTable(ETHER_TABLE).build(); + + ops = install ? ops.add(rule) : ops.remove(rule); + + //Drop rule + selector = DefaultTrafficSelector.builder(); + treatment = DefaultTrafficTreatment.builder(); + + treatment.drop(); + + rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(DROP_PRIORITY) + .fromApp(appId) + .makePermanent() + .forTable(ETHER_TABLE).build(); + + + ops = install ? ops.add(rule) : ops.remove(rule); + + flowRuleService.apply(ops.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + log.info("Provisioned ether table"); + } + + @Override + public void onError(FlowRuleOperations ops) { + log.info("Failed to provision ether table"); + } + })); + + } + + private void processCosTable(boolean install) { + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder treatment = DefaultTrafficTreatment + .builder(); + FlowRuleOperations.Builder ops = FlowRuleOperations.builder(); + FlowRule rule; + + treatment.transition(FIB_TABLE); + + rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(DROP_PRIORITY) + .fromApp(appId) + .makePermanent() + .forTable(COS_MAP_TABLE).build(); + + ops = install ? ops.add(rule) : ops.remove(rule); + + flowRuleService.apply(ops.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + log.info("Provisioned cos table"); + } + + @Override + public void onError(FlowRuleOperations ops) { + log.info("Failed to provision cos table"); + } + })); + + } + + private void processFibTable(boolean install) { + TrafficSelector.Builder selector; + TrafficTreatment.Builder treatment; + FlowRuleOperations.Builder ops = FlowRuleOperations.builder(); + FlowRule rule; + + //Drop rule + selector = DefaultTrafficSelector.builder(); + treatment = DefaultTrafficTreatment.builder(); + + treatment.drop(); + + rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(DROP_PRIORITY) + .fromApp(appId) + .makePermanent() + .forTable(FIB_TABLE).build(); + + ops = install ? ops.add(rule) : ops.remove(rule); + + flowRuleService.apply(ops.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + log.info("Provisioned FIB table"); + } + + @Override + public void onError(FlowRuleOperations ops) { + log.info("Failed to provision FIB table"); + } + })); + } + + private void processLocalTable(boolean install) { + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder treatment = DefaultTrafficTreatment + .builder(); + FlowRuleOperations.Builder ops = FlowRuleOperations.builder(); + FlowRule rule; + + treatment.punt(); + + rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(CONTROLLER_PRIORITY) + .fromApp(appId) + .makePermanent() + .forTable(LOCAL_TABLE).build(); + + ops = install ? ops.add(rule) : ops.remove(rule); + + flowRuleService.apply(ops.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + log.info("Provisioned Local table"); + } + + @Override + public void onError(FlowRuleOperations ops) { + log.info("Failed to provision Local table"); + } + })); + } + + private class InnerGroupListener implements GroupListener { + @Override + public void event(GroupEvent event) { + if (event.type() == GroupEvent.Type.GROUP_ADDED) { + GroupKey key = event.subject().appCookie(); + + NextObjective obj = pendingGroups.getIfPresent(key); + if (obj != null) { + flowObjectiveStore.putNextGroup(obj.id(), new CorsaGroup(key)); + pass(obj); + pendingGroups.invalidate(key); + } + } + } + } + + + private class GroupChecker implements Runnable { + + @Override + public void run() { + Set<GroupKey> keys = pendingGroups.asMap().keySet().stream() + .filter(key -> groupService.getGroup(deviceId, key) != null) + .collect(Collectors.toSet()); + + keys.stream().forEach(key -> { + NextObjective obj = pendingGroups.getIfPresent(key); + if (obj == null) { + return; + } + pass(obj); + pendingGroups.invalidate(key); + log.info("Heard back from group service for group {}. " + + "Applying pending forwarding objectives", obj.id()); + flowObjectiveStore.putNextGroup(obj.id(), new CorsaGroup(key)); + }); + } + } + + private class CorsaGroup implements NextGroup { + + private final GroupKey key; + + public CorsaGroup(GroupKey key) { + this.key = key; + } + + @SuppressWarnings("unused") + public GroupKey key() { + return key; + } + + @Override + public byte[] data() { + return appKryo.serialize(key); + } + + } +} diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/OpenVSwitchPipeline.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/OpenVSwitchPipeline.java new file mode 100644 index 00000000..270e76a2 --- /dev/null +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/OpenVSwitchPipeline.java @@ -0,0 +1,183 @@ +/* + * Copyright 2015 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.driver.pipeline; + +import static org.slf4j.LoggerFactory.getLogger; + +import java.util.Collection; +import java.util.Collections; + +import org.onlab.osgi.ServiceDirectory; +import org.onosproject.core.CoreService; +import org.onosproject.net.DeviceId; +import org.onosproject.net.behaviour.Pipeliner; +import org.onosproject.net.behaviour.PipelinerContext; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.FlowRuleOperations; +import org.onosproject.net.flow.FlowRuleOperationsContext; +import org.onosproject.net.flow.FlowRuleService; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.criteria.Criterion.Type; +import org.onosproject.net.flow.instructions.Instructions; +import org.onosproject.net.flowobjective.FilteringObjective; +import org.onosproject.net.flowobjective.FlowObjectiveStore; +import org.onosproject.net.flowobjective.ForwardingObjective; +import org.onosproject.net.flowobjective.NextObjective; +import org.onosproject.net.flowobjective.Objective; +import org.onosproject.net.flowobjective.ObjectiveError; +import org.slf4j.Logger; + +/** + * Driver for standard OpenVSwitch. + */ +public class OpenVSwitchPipeline extends DefaultSingleTablePipeline + implements Pipeliner { + + private static final String VTN_APP_ID = "org.onosproject.app.vtn"; + private final Logger log = getLogger(getClass()); + private CoreService coreService; + private ServiceDirectory serviceDirectory; + protected FlowObjectiveStore flowObjectiveStore; + protected DeviceId deviceId; + protected FlowRuleService flowRuleService; + protected DeviceService deviceService; + private static final int TIME_OUT = 0; + private static final int MAC_TABLE = 40; + private static final int PORT_TABLE = 0; + + @Override + public void init(DeviceId deviceId, PipelinerContext context) { + super.init(deviceId, context); + this.serviceDirectory = context.directory(); + this.deviceId = deviceId; + + coreService = serviceDirectory.get(CoreService.class); + flowRuleService = serviceDirectory.get(FlowRuleService.class); + flowObjectiveStore = context.store(); + coreService + .registerApplication("org.onosproject.driver.OpenVSwitchPipeline"); + + } + + @Override + public void filter(FilteringObjective filteringObjective) { + super.filter(filteringObjective); + } + + @Override + public void forward(ForwardingObjective fwd) { + if (!VTN_APP_ID.equals(fwd.appId().name())) { + super.forward(fwd); + return; + } + Collection<FlowRule> rules; + FlowRuleOperations.Builder flowOpsBuilder = FlowRuleOperations + .builder(); + + rules = processForward(fwd); + switch (fwd.op()) { + case ADD: + rules.stream().filter(rule -> rule != null) + .forEach(flowOpsBuilder::add); + break; + case REMOVE: + rules.stream().filter(rule -> rule != null) + .forEach(flowOpsBuilder::remove); + break; + default: + fail(fwd, ObjectiveError.UNKNOWN); + log.warn("Unknown forwarding type {}", fwd.op()); + } + + flowRuleService.apply(flowOpsBuilder + .build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + pass(fwd); + } + + @Override + public void onError(FlowRuleOperations ops) { + fail(fwd, ObjectiveError.FLOWINSTALLATIONFAILED); + } + })); + } + + @Override + public void next(NextObjective nextObjective) { + super.next(nextObjective); + } + + private Collection<FlowRule> processForward(ForwardingObjective fwd) { + switch (fwd.flag()) { + case SPECIFIC: + return processSpecific(fwd); + case VERSATILE: + return processVersatile(fwd); + default: + fail(fwd, ObjectiveError.UNKNOWN); + log.warn("Unknown forwarding flag {}", fwd.flag()); + } + return Collections.emptySet(); + } + + private Collection<FlowRule> processVersatile(ForwardingObjective fwd) { + log.debug("Processing versatile forwarding objective"); + return Collections.emptyList(); + } + + private Collection<FlowRule> processSpecific(ForwardingObjective fwd) { + log.debug("Processing specific forwarding objective"); + TrafficSelector selector = fwd.selector(); + TrafficTreatment tb = fwd.treatment(); + FlowRule.Builder ruleBuilder = DefaultFlowRule.builder() + .fromApp(fwd.appId()).withPriority(fwd.priority()) + .forDevice(deviceId).withSelector(selector) + .withTreatment(tb).makeTemporary(TIME_OUT); + ruleBuilder.withPriority(fwd.priority()); + if (fwd.permanent()) { + ruleBuilder.makePermanent(); + } + if (selector.getCriterion(Type.ETH_DST) != null + || tb.allInstructions().contains(Instructions.createDrop())) { + ruleBuilder.withTreatment(tb); + ruleBuilder.forTable(MAC_TABLE); + } else { + TrafficTreatment.Builder newTraffic = DefaultTrafficTreatment.builder(); + tb.allInstructions().forEach(t -> newTraffic.add(t)); + newTraffic.transition(MAC_TABLE); + ruleBuilder.withTreatment(newTraffic.build()); + ruleBuilder.forTable(PORT_TABLE); + } + return Collections.singletonList(ruleBuilder.build()); + } + + private void fail(Objective obj, ObjectiveError error) { + if (obj.context().isPresent()) { + obj.context().get().onError(obj, error); + } + } + + private void pass(Objective obj) { + if (obj.context().isPresent()) { + obj.context().get().onSuccess(obj); + } + } +} diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/PicaPipeline.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/PicaPipeline.java new file mode 100644 index 00000000..32d6b103 --- /dev/null +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/PicaPipeline.java @@ -0,0 +1,543 @@ +/* + * Copyright 2015 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.driver.pipeline; + +import org.onlab.osgi.ServiceDirectory; +import org.onlab.packet.Ethernet; +import org.onlab.packet.MacAddress; +import org.onlab.packet.VlanId; +import org.onlab.util.KryoNamespace; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.net.DeviceId; +import org.onosproject.net.PortNumber; +import org.onosproject.net.behaviour.NextGroup; +import org.onosproject.net.behaviour.Pipeliner; +import org.onosproject.net.behaviour.PipelinerContext; +import org.onosproject.net.driver.AbstractHandlerBehaviour; +import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.FlowRuleOperations; +import org.onosproject.net.flow.FlowRuleOperationsContext; +import org.onosproject.net.flow.FlowRuleService; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.criteria.Criteria; +import org.onosproject.net.flow.criteria.Criterion; +import org.onosproject.net.flow.criteria.EthCriterion; +import org.onosproject.net.flow.criteria.EthTypeCriterion; +import org.onosproject.net.flow.criteria.IPCriterion; +import org.onosproject.net.flow.criteria.PortCriterion; +import org.onosproject.net.flow.criteria.VlanIdCriterion; +import org.onosproject.net.flow.instructions.Instruction; +import org.onosproject.net.flow.instructions.L2ModificationInstruction; +import org.onosproject.net.flowobjective.FilteringObjective; +import org.onosproject.net.flowobjective.FlowObjectiveStore; +import org.onosproject.net.flowobjective.ForwardingObjective; +import org.onosproject.net.flowobjective.NextObjective; +import org.onosproject.net.flowobjective.Objective; +import org.onosproject.net.flowobjective.ObjectiveError; +import org.onosproject.store.serializers.KryoNamespaces; +import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Pica pipeline handler. + */ +public class PicaPipeline extends AbstractHandlerBehaviour implements Pipeliner { + + protected static final int IP_UNICAST_TABLE = 252; + protected static final int ACL_TABLE = 0; + + //private static final int CONTROLLER_PRIORITY = 255; + private static final int DROP_PRIORITY = 0; + private static final int HIGHEST_PRIORITY = 0xffff; + + private final Logger log = getLogger(getClass()); + + private ServiceDirectory serviceDirectory; + private FlowRuleService flowRuleService; + private CoreService coreService; + private FlowObjectiveStore flowObjectiveStore; + private DeviceId deviceId; + private ApplicationId appId; + private Collection<Filter> filters; + private Collection<ForwardingObjective> pendingVersatiles; + + private KryoNamespace appKryo = new KryoNamespace.Builder() + .register(KryoNamespaces.API) + .register(PicaGroup.class) + .register(byte[].class) + .build(); + + @Override + public void init(DeviceId deviceId, PipelinerContext context) { + this.serviceDirectory = context.directory(); + this.deviceId = deviceId; + + coreService = serviceDirectory.get(CoreService.class); + flowRuleService = serviceDirectory.get(FlowRuleService.class); + flowObjectiveStore = context.store(); + filters = Collections.newSetFromMap(new ConcurrentHashMap<Filter, Boolean>()); + pendingVersatiles = Collections.newSetFromMap( + new ConcurrentHashMap<ForwardingObjective, Boolean>()); + appId = coreService.registerApplication( + "org.onosproject.driver.OVSPicaPipeline"); + + initializePipeline(); + } + + @Override + public void filter(FilteringObjective filteringObjective) { + if (filteringObjective.type() == FilteringObjective.Type.PERMIT) { + processFilter(filteringObjective, + filteringObjective.op() == Objective.Operation.ADD, + filteringObjective.appId()); + } else { + fail(filteringObjective, ObjectiveError.UNSUPPORTED); + } + } + + @Override + public void forward(ForwardingObjective fwd) { + Collection<FlowRule> rules; + FlowRuleOperations.Builder flowBuilder = FlowRuleOperations.builder(); + + rules = processForward(fwd); + switch (fwd.op()) { + case ADD: + rules.stream() + .filter(rule -> rule != null) + .forEach(flowBuilder::add); + break; + case REMOVE: + rules.stream() + .filter(rule -> rule != null) + .forEach(flowBuilder::remove); + break; + default: + fail(fwd, ObjectiveError.UNKNOWN); + log.warn("Unknown forwarding type {}", fwd.op()); + } + + + flowRuleService.apply(flowBuilder.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + pass(fwd); + } + + @Override + public void onError(FlowRuleOperations ops) { + fail(fwd, ObjectiveError.FLOWINSTALLATIONFAILED); + } + })); + + } + + @Override + public void next(NextObjective nextObjective) { + switch (nextObjective.type()) { + case SIMPLE: + Collection<TrafficTreatment> treatments = nextObjective.next(); + if (treatments.size() != 1) { + log.error("Next Objectives of type Simple should only have a " + + "single Traffic Treatment. Next Objective Id:{}", nextObjective.id()); + fail(nextObjective, ObjectiveError.BADPARAMS); + return; + } + TrafficTreatment treatment = treatments.iterator().next(); + TrafficTreatment.Builder filteredTreatment = DefaultTrafficTreatment.builder(); + VlanId modVlanId; + for (Instruction ins : treatment.allInstructions()) { + if (ins.type() == Instruction.Type.L2MODIFICATION) { + L2ModificationInstruction l2ins = (L2ModificationInstruction) ins; + switch (l2ins.subtype()) { + case ETH_DST: + filteredTreatment.setEthDst( + ((L2ModificationInstruction.ModEtherInstruction) l2ins).mac()); + break; + case ETH_SRC: + filteredTreatment.setEthSrc( + ((L2ModificationInstruction.ModEtherInstruction) l2ins).mac()); + break; + case VLAN_ID: + modVlanId = ((L2ModificationInstruction.ModVlanIdInstruction) l2ins).vlanId(); + filteredTreatment.setVlanId(modVlanId); + break; + default: + break; + } + } else if (ins.type() == Instruction.Type.OUTPUT) { + //long portNum = ((Instructions.OutputInstruction) ins).port().toLong(); + filteredTreatment.add(ins); + } else { + // Ignore the vlan_pcp action since it's does matter much. + log.warn("Driver does not handle this type of TrafficTreatment" + + " instruction in nextObjectives: {}", ins.type()); + } + } + // store for future use + flowObjectiveStore.putNextGroup(nextObjective.id(), + new PicaGroup(filteredTreatment.build())); + break; + case HASHED: + case BROADCAST: + case FAILOVER: + fail(nextObjective, ObjectiveError.UNSUPPORTED); + log.warn("Unsupported next objective type {}", nextObjective.type()); + break; + default: + fail(nextObjective, ObjectiveError.UNKNOWN); + log.warn("Unknown next objective type {}", nextObjective.type()); + } + + } + + private Collection<FlowRule> processForward(ForwardingObjective fwd) { + switch (fwd.flag()) { + case SPECIFIC: + return processSpecific(fwd); + case VERSATILE: + return processVersatile(fwd); + default: + fail(fwd, ObjectiveError.UNKNOWN); + log.warn("Unknown forwarding flag {}", fwd.flag()); + } + return Collections.emptySet(); + } + + private Collection<FlowRule> processVersatile(ForwardingObjective fwd) { + log.debug("Processing versatile forwarding objective"); + TrafficSelector selector = fwd.selector(); + TrafficTreatment treatment = fwd.treatment(); + Collection<FlowRule> flowrules = new ArrayList<FlowRule>(); + + // first add this rule for basic single-table operation + // or non-ARP related multi-table operation + FlowRule rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector) + .withTreatment(treatment) + .withPriority(fwd.priority()) + .fromApp(fwd.appId()) + .makePermanent() + .forTable(ACL_TABLE).build(); + flowrules.add(rule); + + EthTypeCriterion ethType = + (EthTypeCriterion) selector.getCriterion(Criterion.Type.ETH_TYPE); + if (ethType == null) { + log.warn("No ethType in versatile forwarding obj. Not processing further."); + return flowrules; + } + + // now deal with possible mix of ARP with filtering objectives + // in multi-table scenarios + if (ethType.ethType().toShort() == Ethernet.TYPE_ARP) { + if (filters.isEmpty()) { + pendingVersatiles.add(fwd); + return flowrules; + } + for (Filter filter : filters) { + flowrules.addAll(processVersatilesWithFilters(filter, fwd)); + } + } + return flowrules; + } + + private Collection<FlowRule> processVersatilesWithFilters( + Filter filt, ForwardingObjective fwd) { + Collection<FlowRule> flows = new ArrayList<FlowRule>(); + + // rule for ARP replies + log.debug("adding ARP rule in ACL table"); + TrafficSelector.Builder sel = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder treat = DefaultTrafficTreatment.builder(); + sel.matchInPort(filt.port()); + sel.matchVlanId(filt.vlanId()); + sel.matchEthDst(filt.mac()); + sel.matchEthType(Ethernet.TYPE_ARP); + treat.setOutput(PortNumber.CONTROLLER); + FlowRule rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(sel.build()) + .withTreatment(treat.build()) + .withPriority(HIGHEST_PRIORITY) + .fromApp(appId) + .makePermanent() + .forTable(ACL_TABLE).build(); + flows.add(rule); + + // rule for ARP Broadcast + sel = DefaultTrafficSelector.builder(); + treat = DefaultTrafficTreatment.builder(); + sel.matchInPort(filt.port()); + sel.matchVlanId(filt.vlanId()); + sel.matchEthDst(MacAddress.BROADCAST); + sel.matchEthType(Ethernet.TYPE_ARP); + treat.setOutput(PortNumber.CONTROLLER); + rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(sel.build()) + .withTreatment(treat.build()) + .withPriority(HIGHEST_PRIORITY) + .fromApp(appId) + .makePermanent() + .forTable(ACL_TABLE).build(); + flows.add(rule); + + return flows; + } + + + private Collection<FlowRule> processSpecific(ForwardingObjective fwd) { + log.debug("Processing specific forwarding objective"); + TrafficSelector selector = fwd.selector(); + EthTypeCriterion ethType = + (EthTypeCriterion) selector.getCriterion(Criterion.Type.ETH_TYPE); + if (ethType == null || ethType.ethType().toShort() != Ethernet.TYPE_IPV4) { + fail(fwd, ObjectiveError.UNSUPPORTED); + return Collections.emptySet(); + } + + List<FlowRule> ipflows = new ArrayList<FlowRule>(); + for (Filter f: filters) { + TrafficSelector filteredSelector = + DefaultTrafficSelector.builder() + .matchEthType(Ethernet.TYPE_IPV4) + .matchIPDst( + ((IPCriterion) + selector.getCriterion(Criterion.Type.IPV4_DST)).ip()) + .matchEthDst(f.mac()) + .matchVlanId(f.vlanId()) + .build(); + TrafficTreatment tt = null; + if (fwd.nextId() != null) { + NextGroup next = flowObjectiveStore.getNextGroup(fwd.nextId()); + if (next == null) { + log.error("next-id {} does not exist in store", fwd.nextId()); + return Collections.emptySet(); + } + tt = appKryo.deserialize(next.data()); + if (tt == null) { + log.error("Error in deserializing next-id {}", fwd.nextId()); + return Collections.emptySet(); + } + } + + FlowRule.Builder ruleBuilder = DefaultFlowRule.builder() + .fromApp(fwd.appId()) + .withPriority(fwd.priority()) + .forDevice(deviceId) + .withSelector(filteredSelector) + .withTreatment(tt); + if (fwd.permanent()) { + ruleBuilder.makePermanent(); + } else { + ruleBuilder.makeTemporary(fwd.timeout()); + } + ruleBuilder.forTable(IP_UNICAST_TABLE); + ipflows.add(ruleBuilder.build()); + } + + return ipflows; + } + + private void processFilter(FilteringObjective filt, boolean install, + ApplicationId applicationId) { + // This driver only processes filtering criteria defined with switch + // ports as the key + PortCriterion p; + if (!filt.key().equals(Criteria.dummy()) && + filt.key().type() == Criterion.Type.IN_PORT) { + p = (PortCriterion) filt.key(); + } else { + log.warn("No key defined in filtering objective from app: {}. Not" + + "processing filtering objective", applicationId); + fail(filt, ObjectiveError.UNKNOWN); + return; + } + + EthCriterion e = null; VlanIdCriterion v = null; + Collection<IPCriterion> ips = new ArrayList<IPCriterion>(); + // convert filtering conditions for switch-intfs into flowrules + FlowRuleOperations.Builder ops = FlowRuleOperations.builder(); + for (Criterion c : filt.conditions()) { + if (c.type() == Criterion.Type.ETH_DST) { + e = (EthCriterion) c; + } else if (c.type() == Criterion.Type.VLAN_VID) { + v = (VlanIdCriterion) c; + } else if (c.type() == Criterion.Type.IPV4_DST) { + ips.add((IPCriterion) c); + } else { + log.error("Unsupported filter {}", c); + fail(filt, ObjectiveError.UNSUPPORTED); + return; + } + } + + // cache for later use + Filter filter = new Filter(p, e, v, ips); + filters.add(filter); + + // apply any pending versatile forwarding objectives + for (ForwardingObjective fwd : pendingVersatiles) { + Collection<FlowRule> ret = processVersatilesWithFilters(filter, fwd); + for (FlowRule fr : ret) { + ops.add(fr); + } + } + + for (IPCriterion ipaddr : ips) { + log.debug("adding IP filtering rules in ACL table: {}", ipaddr.ip()); + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder(); + selector.matchInPort(p.port()); + selector.matchVlanId(v.vlanId()); + selector.matchEthDst(e.mac()); + selector.matchEthType(Ethernet.TYPE_IPV4); + selector.matchIPDst(ipaddr.ip()); // router IPs to the controller + treatment.setOutput(PortNumber.CONTROLLER); + FlowRule rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(HIGHEST_PRIORITY) + .fromApp(applicationId) + .makePermanent() + .forTable(ACL_TABLE).build(); + ops = ops.add(rule); + } + + // apply filtering flow rules + flowRuleService.apply(ops.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + pass(filt); + log.info("Applied filtering rules"); + } + + @Override + public void onError(FlowRuleOperations ops) { + fail(filt, ObjectiveError.FLOWINSTALLATIONFAILED); + log.info("Failed to apply filtering rules"); + } + })); + } + + private void pass(Objective obj) { + if (obj.context().isPresent()) { + obj.context().get().onSuccess(obj); + } + } + + private void fail(Objective obj, ObjectiveError error) { + if (obj.context().isPresent()) { + obj.context().get().onError(obj, error); + } + } + + private void initializePipeline() { + //processIpUnicastTable(true); + processACLTable(true); + } + + private void processACLTable(boolean install) { + TrafficSelector.Builder selector; + TrafficTreatment.Builder treatment; + FlowRuleOperations.Builder ops = FlowRuleOperations.builder(); + FlowRule rule; + + //Drop rule + selector = DefaultTrafficSelector.builder(); + treatment = DefaultTrafficTreatment.builder(); + + rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(DROP_PRIORITY) + .fromApp(appId) + .makePermanent() + .forTable(ACL_TABLE).build(); + + ops = install ? ops.add(rule) : ops.remove(rule); + + flowRuleService.apply(ops.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + log.info("Provisioned ACL table"); + } + + @Override + public void onError(FlowRuleOperations ops) { + log.info("Failed to provision ACL table"); + } + })); + } + + private class Filter { + private PortCriterion port; + private VlanIdCriterion vlan; + private EthCriterion eth; + + @SuppressWarnings("unused") + private Collection<IPCriterion> ips; + + public Filter(PortCriterion p, EthCriterion e, VlanIdCriterion v, + Collection<IPCriterion> ips) { + this.eth = e; + this.port = p; + this.vlan = v; + this.ips = ips; + } + + public PortNumber port() { + return port.port(); + } + + public VlanId vlanId() { + return vlan.vlanId(); + } + + public MacAddress mac() { + return eth.mac(); + } + } + + private class PicaGroup implements NextGroup { + TrafficTreatment nextActions; + + public PicaGroup(TrafficTreatment next) { + this.nextActions = next; + } + + @Override + public byte[] data() { + return appKryo.serialize(nextActions); + } + } +} diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/SoftRouterPipeline.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/SoftRouterPipeline.java new file mode 100644 index 00000000..220be864 --- /dev/null +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/SoftRouterPipeline.java @@ -0,0 +1,529 @@ +package org.onosproject.driver.pipeline; + +import static org.slf4j.LoggerFactory.getLogger; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.ConcurrentHashMap; + +import org.onlab.osgi.ServiceDirectory; +import org.onlab.packet.Ethernet; +import org.onlab.packet.MacAddress; +import org.onlab.packet.VlanId; +import org.onlab.util.KryoNamespace; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.net.DeviceId; +import org.onosproject.net.PortNumber; +import org.onosproject.net.behaviour.NextGroup; +import org.onosproject.net.behaviour.Pipeliner; +import org.onosproject.net.behaviour.PipelinerContext; +import org.onosproject.net.driver.AbstractHandlerBehaviour; +import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.FlowRuleOperations; +import org.onosproject.net.flow.FlowRuleOperationsContext; +import org.onosproject.net.flow.FlowRuleService; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.criteria.Criteria; +import org.onosproject.net.flow.criteria.Criterion; +import org.onosproject.net.flow.criteria.EthCriterion; +import org.onosproject.net.flow.criteria.EthTypeCriterion; +import org.onosproject.net.flow.criteria.IPCriterion; +import org.onosproject.net.flow.criteria.PortCriterion; +import org.onosproject.net.flow.criteria.VlanIdCriterion; +import org.onosproject.net.flowobjective.FilteringObjective; +import org.onosproject.net.flowobjective.FlowObjectiveStore; +import org.onosproject.net.flowobjective.ForwardingObjective; +import org.onosproject.net.flowobjective.NextObjective; +import org.onosproject.net.flowobjective.Objective; +import org.onosproject.net.flowobjective.ObjectiveError; +import org.slf4j.Logger; +import org.onosproject.store.serializers.KryoNamespaces; + +/** + * Simple 2-Table Pipeline for Software/NPU based routers. This pipeline + * does not forward IP traffic to next-hop groups. Instead it forwards traffic + * using OF FlowMod actions. + */ +public class SoftRouterPipeline extends AbstractHandlerBehaviour implements Pipeliner { + + protected static final int FILTER_TABLE = 0; + protected static final int FIB_TABLE = 1; + + private static final int DROP_PRIORITY = 0; + private static final int DEFAULT_PRIORITY = 0x8000; + private static final int HIGHEST_PRIORITY = 0xffff; + + private ServiceDirectory serviceDirectory; + protected FlowRuleService flowRuleService; + private CoreService coreService; + private FlowObjectiveStore flowObjectiveStore; + protected DeviceId deviceId; + protected ApplicationId appId; + private ApplicationId driverId; + private Collection<Filter> filters; + private Collection<ForwardingObjective> pendingVersatiles; + + private KryoNamespace appKryo = new KryoNamespace.Builder() + .register(DummyGroup.class) + .register(KryoNamespaces.API) + .register(byte[].class) + .build(); + + private final Logger log = getLogger(getClass()); + + @Override + public void init(DeviceId deviceId, PipelinerContext context) { + this.serviceDirectory = context.directory(); + this.deviceId = deviceId; + coreService = serviceDirectory.get(CoreService.class); + flowRuleService = serviceDirectory.get(FlowRuleService.class); + flowObjectiveStore = context.store(); + driverId = coreService.registerApplication( + "org.onosproject.driver.OVSCorsaPipeline"); + filters = Collections.newSetFromMap(new ConcurrentHashMap<Filter, Boolean>()); + pendingVersatiles = Collections.newSetFromMap( + new ConcurrentHashMap<ForwardingObjective, Boolean>()); + initializePipeline(); + } + + @Override + public void filter(FilteringObjective filteringObjective) { + if (filteringObjective.type() == FilteringObjective.Type.PERMIT) { + processFilter(filteringObjective, + filteringObjective.op() == Objective.Operation.ADD, + filteringObjective.appId()); + } else { + fail(filteringObjective, ObjectiveError.UNSUPPORTED); + } + } + + @Override + public void forward(ForwardingObjective fwd) { + Collection<FlowRule> rules; + FlowRuleOperations.Builder flowOpsBuilder = FlowRuleOperations.builder(); + + rules = processForward(fwd); + switch (fwd.op()) { + case ADD: + rules.stream() + .filter(rule -> rule != null) + .forEach(flowOpsBuilder::add); + break; + case REMOVE: + rules.stream() + .filter(rule -> rule != null) + .forEach(flowOpsBuilder::remove); + break; + default: + fail(fwd, ObjectiveError.UNKNOWN); + log.warn("Unknown forwarding type {}", fwd.op()); + } + + + flowRuleService.apply(flowOpsBuilder.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + pass(fwd); + } + + @Override + public void onError(FlowRuleOperations ops) { + fail(fwd, ObjectiveError.FLOWINSTALLATIONFAILED); + } + })); + + } + + @Override + public void next(NextObjective nextObjective) { + switch (nextObjective.type()) { + case SIMPLE: + Collection<TrafficTreatment> treatments = nextObjective.next(); + if (treatments.size() != 1) { + log.error("Next Objectives of type Simple should only have a " + + "single Traffic Treatment. Next Objective Id:{}", nextObjective.id()); + fail(nextObjective, ObjectiveError.BADPARAMS); + return; + } + processSimpleNextObjective(nextObjective); + break; + case HASHED: + case BROADCAST: + case FAILOVER: + fail(nextObjective, ObjectiveError.UNSUPPORTED); + log.warn("Unsupported next objective type {}", nextObjective.type()); + break; + default: + fail(nextObjective, ObjectiveError.UNKNOWN); + log.warn("Unknown next objective type {}", nextObjective.type()); + } + } + + private void pass(Objective obj) { + if (obj.context().isPresent()) { + obj.context().get().onSuccess(obj); + } + } + + private void fail(Objective obj, ObjectiveError error) { + if (obj.context().isPresent()) { + obj.context().get().onError(obj, error); + } + } + + + private void initializePipeline() { + //Drop rules for both tables + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder(); + FlowRuleOperations.Builder ops = FlowRuleOperations.builder(); + + treatment.drop(); + + FlowRule rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(DROP_PRIORITY) + .fromApp(driverId) + .makePermanent() + .forTable(FILTER_TABLE) + .build(); + ops = ops.add(rule); + + rule = DefaultFlowRule.builder().forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(DROP_PRIORITY) + .fromApp(driverId) + .makePermanent() + .forTable(FIB_TABLE) + .build(); + ops = ops.add(rule); + + flowRuleService.apply(ops.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + log.info("Provisioned drop rules in both tables"); + } + + @Override + public void onError(FlowRuleOperations ops) { + log.info("Failed to provision drop rules"); + } + })); + } + + private void processFilter(FilteringObjective filt, boolean install, + ApplicationId applicationId) { + // This driver only processes filtering criteria defined with switch + // ports as the key + PortCriterion p; EthCriterion e = null; VlanIdCriterion v = null; + Collection<IPCriterion> ips = new ArrayList<IPCriterion>(); + if (!filt.key().equals(Criteria.dummy()) && + filt.key().type() == Criterion.Type.IN_PORT) { + p = (PortCriterion) filt.key(); + } else { + log.warn("No key defined in filtering objective from app: {}. Not" + + "processing filtering objective", applicationId); + fail(filt, ObjectiveError.UNKNOWN); + return; + } + + // convert filtering conditions for switch-intfs into flowrules + FlowRuleOperations.Builder ops = FlowRuleOperations.builder(); + for (Criterion c : filt.conditions()) { + if (c.type() == Criterion.Type.ETH_DST) { + e = (EthCriterion) c; + } else if (c.type() == Criterion.Type.VLAN_VID) { + v = (VlanIdCriterion) c; + } else if (c.type() == Criterion.Type.IPV4_DST) { + ips.add((IPCriterion) c); + } else { + log.error("Unsupported filter {}", c); + fail(filt, ObjectiveError.UNSUPPORTED); + return; + } + } + + log.debug("adding Port/VLAN/MAC filtering rules in filter table: {}/{}/{}", + p.port(), v.vlanId(), e.mac()); + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder(); + selector.matchInPort(p.port()); + selector.matchVlanId(v.vlanId()); + selector.matchEthDst(e.mac()); + selector.matchEthType(Ethernet.TYPE_IPV4); + treatment.popVlan(); + treatment.transition(FIB_TABLE); // all other IPs to the FIB table + FlowRule rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(DEFAULT_PRIORITY) + .fromApp(applicationId) + .makePermanent() + .forTable(FILTER_TABLE).build(); + ops = ops.add(rule); + + for (IPCriterion ipaddr : ips) { + log.debug("adding IP filtering rules in FILTER table: {}", ipaddr.ip()); + selector = DefaultTrafficSelector.builder(); + treatment = DefaultTrafficTreatment.builder(); + selector.matchInPort(p.port()); + selector.matchVlanId(v.vlanId()); + selector.matchEthDst(e.mac()); + selector.matchEthType(Ethernet.TYPE_IPV4); + selector.matchIPDst(ipaddr.ip()); // router IPs to the controller + treatment.setOutput(PortNumber.CONTROLLER); + rule = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(HIGHEST_PRIORITY) + .fromApp(applicationId) + .makePermanent() + .forTable(FILTER_TABLE).build(); + ops = ops.add(rule); + } + + // cache for later use + Filter filter = new Filter(p, e, v, ips); + filters.add(filter); + // apply any pending versatile forwarding objectives + for (ForwardingObjective fwd : pendingVersatiles) { + Collection<FlowRule> ret = processVersatilesWithFilters(filter, fwd); + for (FlowRule fr : ret) { + ops.add(fr); + } + } + + ops = install ? ops.add(rule) : ops.remove(rule); + // apply filtering flow rules + flowRuleService.apply(ops.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + log.info("Applied filtering rules"); + pass(filt); + } + + @Override + public void onError(FlowRuleOperations ops) { + log.info("Failed to apply filtering rules"); + fail(filt, ObjectiveError.FLOWINSTALLATIONFAILED); + } + })); + } + + private Collection<FlowRule> processForward(ForwardingObjective fwd) { + switch (fwd.flag()) { + case SPECIFIC: + return processSpecific(fwd); + case VERSATILE: + return processVersatile(fwd); + default: + fail(fwd, ObjectiveError.UNKNOWN); + log.warn("Unknown forwarding flag {}", fwd.flag()); + } + return Collections.emptySet(); + } + + /** + * SoftRouter has a single versatile table - the filter table. All versatile + * flow rules must include the filtering rules. + * + * @param fwd The forwarding objective of type versatile + * @return A collection of flow rules meant to be delivered to the flowrule + * subsystem. May return empty collection in case of failures. + */ + private Collection<FlowRule> processVersatile(ForwardingObjective fwd) { + if (filters.isEmpty()) { + pendingVersatiles.add(fwd); + return Collections.emptySet(); + } + Collection<FlowRule> flowrules = new ArrayList<FlowRule>(); + for (Filter filter : filters) { + flowrules.addAll(processVersatilesWithFilters(filter, fwd)); + } + return flowrules; + } + + private Collection<FlowRule> processVersatilesWithFilters( + Filter filt, ForwardingObjective fwd) { + log.info("Processing versatile forwarding objective"); + Collection<FlowRule> flows = new ArrayList<FlowRule>(); + TrafficSelector match = fwd.selector(); + EthTypeCriterion ethType = + (EthTypeCriterion) match.getCriterion(Criterion.Type.ETH_TYPE); + if (ethType == null) { + log.error("Versatile forwarding objective must include ethType"); + fail(fwd, ObjectiveError.UNKNOWN); + return Collections.emptySet(); + } + + if (ethType.ethType().toShort() == Ethernet.TYPE_ARP) { + // need to install ARP request & reply flow rules for each interface filter + + // rule for ARP replies + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + selector.matchInPort(filt.port()); + selector.matchVlanId(filt.vlanId()); + selector.matchEthDst(filt.mac()); + selector.matchEthType(Ethernet.TYPE_ARP); + FlowRule.Builder ruleBuilder = DefaultFlowRule.builder() + .fromApp(fwd.appId()) + .withPriority(fwd.priority()) + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(fwd.treatment()) + .makePermanent() + .forTable(FILTER_TABLE); + flows.add(ruleBuilder.build()); + + //rule for ARP requests + selector = DefaultTrafficSelector.builder(); + selector.matchInPort(filt.port()); + selector.matchVlanId(filt.vlanId()); + selector.matchEthDst(MacAddress.BROADCAST); + selector.matchEthType(Ethernet.TYPE_ARP); + ruleBuilder = DefaultFlowRule.builder() + .fromApp(fwd.appId()) + .withPriority(fwd.priority()) + .forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(fwd.treatment()) + .makePermanent() + .forTable(FILTER_TABLE); + flows.add(ruleBuilder.build()); + + return flows; + } + // not handling other versatile flows + return Collections.emptySet(); + } + + /** + * SoftRouter has a single specific table - the FIB Table. It emulates + * LPM matching of dstIP by using higher priority flows for longer prefixes. + * Flows are forwarded using flow-actions + * + * @param fwd The forwarding objective of type simple + * @return A collection of flow rules meant to be delivered to the flowrule + * subsystem. Typically the returned collection has a single flowrule. + * May return empty collection in case of failures. + * + */ + private Collection<FlowRule> processSpecific(ForwardingObjective fwd) { + log.debug("Processing specific forwarding objective"); + TrafficSelector selector = fwd.selector(); + EthTypeCriterion ethType = + (EthTypeCriterion) selector.getCriterion(Criterion.Type.ETH_TYPE); + // XXX currently supporting only the L3 unicast table + if (ethType == null || ethType.ethType().toShort() != Ethernet.TYPE_IPV4) { + fail(fwd, ObjectiveError.UNSUPPORTED); + return Collections.emptySet(); + } + + TrafficSelector filteredSelector = + DefaultTrafficSelector.builder() + .matchEthType(Ethernet.TYPE_IPV4) + .matchIPDst(((IPCriterion) + selector.getCriterion(Criterion.Type.IPV4_DST)).ip()) + .build(); + + TrafficTreatment tt = null; + if (fwd.nextId() != null) { + NextGroup next = flowObjectiveStore.getNextGroup(fwd.nextId()); + if (next == null) { + log.error("next-id {} does not exist in store", fwd.nextId()); + return Collections.emptySet(); + } + tt = appKryo.deserialize(next.data()); + if (tt == null) { + log.error("Error in deserializing next-id {}", fwd.nextId()); + return Collections.emptySet(); + } + } + + FlowRule.Builder ruleBuilder = DefaultFlowRule.builder() + .fromApp(fwd.appId()) + .withPriority(fwd.priority()) + .forDevice(deviceId) + .withSelector(filteredSelector) + .withTreatment(tt); + + if (fwd.permanent()) { + ruleBuilder.makePermanent(); + } else { + ruleBuilder.makeTemporary(fwd.timeout()); + } + + ruleBuilder.forTable(FIB_TABLE); + return Collections.singletonList(ruleBuilder.build()); + } + + /** + * Next Objectives are stored as dummy groups for retrieval later + * when Forwarding Objectives reference the next objective id. At that point + * the dummy group is fetched from the distributed store and the enclosed + * treatment is applied as a flow rule action. + * + * @param nextObj the next objective of type simple + */ + private void processSimpleNextObjective(NextObjective nextObj) { + // Simple next objective has a single treatment (not a collection) + TrafficTreatment treatment = nextObj.next().iterator().next(); + flowObjectiveStore.putNextGroup(nextObj.id(), + new DummyGroup(treatment)); + } + + private class Filter { + private PortCriterion port; + private VlanIdCriterion vlan; + private EthCriterion eth; + + @SuppressWarnings("unused") + private Collection<IPCriterion> ips; + + public Filter(PortCriterion p, EthCriterion e, VlanIdCriterion v, + Collection<IPCriterion> ips) { + this.eth = e; + this.port = p; + this.vlan = v; + this.ips = ips; + } + + public PortNumber port() { + return port.port(); + } + + public VlanId vlanId() { + return vlan.vlanId(); + } + + public MacAddress mac() { + return eth.mac(); + } + } + + private class DummyGroup implements NextGroup { + TrafficTreatment nextActions; + + public DummyGroup(TrafficTreatment next) { + this.nextActions = next; + } + + @Override + public byte[] data() { + return appKryo.serialize(nextActions); + } + + } + +} diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTP.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTP.java new file mode 100644 index 00000000..c02ba3ca --- /dev/null +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTP.java @@ -0,0 +1,775 @@ +/* + * Copyright 2015 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.driver.pipeline; + +import static org.onlab.util.Tools.groupedThreads; +import static org.slf4j.LoggerFactory.getLogger; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.RemovalCause; +import com.google.common.cache.RemovalNotification; + +import org.onlab.osgi.ServiceDirectory; +import org.onlab.packet.Ethernet; +import org.onlab.packet.VlanId; +import org.onlab.util.KryoNamespace; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.net.DeviceId; +import org.onosproject.net.PortNumber; +import org.onosproject.net.behaviour.NextGroup; +import org.onosproject.net.behaviour.Pipeliner; +import org.onosproject.net.behaviour.PipelinerContext; +import org.onosproject.net.driver.AbstractHandlerBehaviour; +import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.FlowRuleOperations; +import org.onosproject.net.flow.FlowRuleOperationsContext; +import org.onosproject.net.flow.FlowRuleService; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.criteria.Criteria; +import org.onosproject.net.flow.criteria.Criterion; +import org.onosproject.net.flow.criteria.EthCriterion; +import org.onosproject.net.flow.criteria.EthTypeCriterion; +import org.onosproject.net.flow.criteria.IPCriterion; +import org.onosproject.net.flow.criteria.MplsCriterion; +import org.onosproject.net.flow.criteria.PortCriterion; +import org.onosproject.net.flow.criteria.VlanIdCriterion; +import org.onosproject.net.flow.instructions.Instruction; +import org.onosproject.net.flowobjective.FilteringObjective; +import org.onosproject.net.flowobjective.FlowObjectiveStore; +import org.onosproject.net.flowobjective.ForwardingObjective; +import org.onosproject.net.flowobjective.NextObjective; +import org.onosproject.net.flowobjective.Objective; +import org.onosproject.net.flowobjective.ObjectiveError; +import org.onosproject.net.group.DefaultGroupBucket; +import org.onosproject.net.group.DefaultGroupDescription; +import org.onosproject.net.group.DefaultGroupKey; +import org.onosproject.net.group.Group; +import org.onosproject.net.group.GroupBucket; +import org.onosproject.net.group.GroupBuckets; +import org.onosproject.net.group.GroupDescription; +import org.onosproject.net.group.GroupEvent; +import org.onosproject.net.group.GroupKey; +import org.onosproject.net.group.GroupListener; +import org.onosproject.net.group.GroupService; +import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * Driver for SPRING-OPEN pipeline. + */ +public class SpringOpenTTP extends AbstractHandlerBehaviour + implements Pipeliner { + + // Default table ID - compatible with CpqD switch + private static final int TABLE_VLAN = 0; + private static final int TABLE_TMAC = 1; + private static final int TABLE_IPV4_UNICAST = 2; + private static final int TABLE_MPLS = 3; + private static final int TABLE_ACL = 5; + + /** + * Set the default values. These variables will get overwritten based on the + * switch vendor type + */ + protected int vlanTableId = TABLE_VLAN; + protected int tmacTableId = TABLE_TMAC; + protected int ipv4UnicastTableId = TABLE_IPV4_UNICAST; + protected int mplsTableId = TABLE_MPLS; + protected int aclTableId = TABLE_ACL; + + protected final Logger log = getLogger(getClass()); + + private ServiceDirectory serviceDirectory; + private FlowRuleService flowRuleService; + private CoreService coreService; + protected GroupService groupService; + protected FlowObjectiveStore flowObjectiveStore; + protected DeviceId deviceId; + private ApplicationId appId; + + private Cache<GroupKey, NextObjective> pendingGroups; + + private ScheduledExecutorService groupChecker = Executors + .newScheduledThreadPool(2, + groupedThreads("onos/pipeliner", + "spring-open-%d")); + protected KryoNamespace appKryo = new KryoNamespace.Builder() + .register(GroupKey.class).register(DefaultGroupKey.class) + .register(SegmentRoutingGroup.class).register(byte[].class).build(); + + @Override + public void init(DeviceId deviceId, PipelinerContext context) { + this.serviceDirectory = context.directory(); + this.deviceId = deviceId; + + pendingGroups = CacheBuilder + .newBuilder() + .expireAfterWrite(20, TimeUnit.SECONDS) + .removalListener((RemovalNotification<GroupKey, NextObjective> notification) -> { + if (notification.getCause() == RemovalCause.EXPIRED) { + fail(notification.getValue(), + ObjectiveError.GROUPINSTALLATIONFAILED); + } + }).build(); + + groupChecker.scheduleAtFixedRate(new GroupChecker(), 0, 500, + TimeUnit.MILLISECONDS); + + coreService = serviceDirectory.get(CoreService.class); + flowRuleService = serviceDirectory.get(FlowRuleService.class); + groupService = serviceDirectory.get(GroupService.class); + flowObjectiveStore = context.store(); + + groupService.addListener(new InnerGroupListener()); + + appId = coreService + .registerApplication("org.onosproject.driver.SpringOpenTTP"); + + setTableMissEntries(); + log.info("Spring Open TTP driver initialized"); + } + + @Override + public void filter(FilteringObjective filteringObjective) { + if (filteringObjective.type() == FilteringObjective.Type.PERMIT) { + log.debug("processing PERMIT filter objective"); + processFilter(filteringObjective, + filteringObjective.op() == Objective.Operation.ADD, + filteringObjective.appId()); + } else { + log.debug("filter objective other than PERMIT not supported"); + fail(filteringObjective, ObjectiveError.UNSUPPORTED); + } + } + + @Override + public void forward(ForwardingObjective fwd) { + Collection<FlowRule> rules; + FlowRuleOperations.Builder flowBuilder = FlowRuleOperations.builder(); + + rules = processForward(fwd); + switch (fwd.op()) { + case ADD: + rules.stream().filter(rule -> rule != null) + .forEach(flowBuilder::add); + break; + case REMOVE: + rules.stream().filter(rule -> rule != null) + .forEach(flowBuilder::remove); + break; + default: + fail(fwd, ObjectiveError.UNKNOWN); + log.warn("Unknown forwarding type {}", fwd.op()); + } + + flowRuleService.apply(flowBuilder + .build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + pass(fwd); + log.debug("Provisioned tables in {} with " + + "forwarding rules for segment " + + "router", deviceId); + } + + @Override + public void onError(FlowRuleOperations ops) { + fail(fwd, ObjectiveError.FLOWINSTALLATIONFAILED); + log.warn("Failed to provision tables in {} with " + + "forwarding rules for segment router", + deviceId); + } + })); + + } + + @Override + public void next(NextObjective nextObjective) { + + log.debug("Processing NextObjective id{} op{}", nextObjective.id(), + nextObjective.op()); + if (nextObjective.op() == Objective.Operation.REMOVE) { + if (nextObjective.next().isEmpty()) { + removeGroup(nextObjective); + } else { + removeBucketFromGroup(nextObjective); + } + } else if (nextObjective.op() == Objective.Operation.ADD) { + NextGroup nextGroup = flowObjectiveStore.getNextGroup(nextObjective.id()); + if (nextGroup != null) { + addBucketToGroup(nextObjective); + } else { + addGroup(nextObjective); + } + } else { + log.warn("Unsupported operation {}", nextObjective.op()); + } + + } + + private void removeGroup(NextObjective nextObjective) { + log.debug("removeGroup in {}: for next objective id {}", + deviceId, nextObjective.id()); + final GroupKey key = new DefaultGroupKey( + appKryo.serialize(nextObjective.id())); + groupService.removeGroup(deviceId, key, appId); + } + + private void addGroup(NextObjective nextObjective) { + log.debug("addGroup with type{} for nextObjective id {}", + nextObjective.type(), nextObjective.id()); + switch (nextObjective.type()) { + case SIMPLE: + log.debug("processing SIMPLE next objective"); + Collection<TrafficTreatment> treatments = nextObjective.next(); + if (treatments.size() == 1) { + TrafficTreatment treatment = treatments.iterator().next(); + GroupBucket bucket = DefaultGroupBucket + .createIndirectGroupBucket(treatment); + final GroupKey key = new DefaultGroupKey( + appKryo.serialize(nextObjective + .id())); + GroupDescription groupDescription = new DefaultGroupDescription( + deviceId, + GroupDescription.Type.INDIRECT, + new GroupBuckets( + Collections.singletonList(bucket)), + key, + null, + nextObjective.appId()); + log.debug("Creating SIMPLE group for next objective id {}", + nextObjective.id()); + groupService.addGroup(groupDescription); + pendingGroups.put(key, nextObjective); + } + break; + case HASHED: + log.debug("processing HASHED next objective"); + List<GroupBucket> buckets = nextObjective + .next() + .stream() + .map((treatment) -> DefaultGroupBucket + .createSelectGroupBucket(treatment)) + .collect(Collectors.toList()); + if (!buckets.isEmpty()) { + final GroupKey key = new DefaultGroupKey( + appKryo.serialize(nextObjective + .id())); + GroupDescription groupDescription = new DefaultGroupDescription( + deviceId, + GroupDescription.Type.SELECT, + new GroupBuckets(buckets), + key, + null, + nextObjective.appId()); + log.debug("Creating HASHED group for next objective id {}", + nextObjective.id()); + groupService.addGroup(groupDescription); + pendingGroups.put(key, nextObjective); + } + break; + case BROADCAST: + case FAILOVER: + log.debug("BROADCAST and FAILOVER next objectives not supported"); + fail(nextObjective, ObjectiveError.UNSUPPORTED); + log.warn("Unsupported next objective type {}", nextObjective.type()); + break; + default: + fail(nextObjective, ObjectiveError.UNKNOWN); + log.warn("Unknown next objective type {}", nextObjective.type()); + } + } + + private void addBucketToGroup(NextObjective nextObjective) { + log.debug("addBucketToGroup in {}: for next objective id {}", + deviceId, nextObjective.id()); + Collection<TrafficTreatment> treatments = nextObjective.next(); + TrafficTreatment treatment = treatments.iterator().next(); + final GroupKey key = new DefaultGroupKey( + appKryo.serialize(nextObjective + .id())); + Group group = groupService.getGroup(deviceId, key); + if (group == null) { + log.warn("Group is not found in {} for {}", deviceId, key); + return; + } + GroupBucket bucket; + if (group.type() == GroupDescription.Type.INDIRECT) { + bucket = DefaultGroupBucket.createIndirectGroupBucket(treatment); + } else if (group.type() == GroupDescription.Type.SELECT) { + bucket = DefaultGroupBucket.createSelectGroupBucket(treatment); + } else { + log.warn("Unsupported Group type {}", group.type()); + return; + } + GroupBuckets bucketsToAdd = new GroupBuckets(Collections.singletonList(bucket)); + log.debug("Adding buckets to group id {} of next objective id {} in device {}", + group.id(), nextObjective.id(), deviceId); + groupService.addBucketsToGroup(deviceId, key, bucketsToAdd, key, appId); + } + + private void removeBucketFromGroup(NextObjective nextObjective) { + log.debug("removeBucketFromGroup in {}: for next objective id {}", + deviceId, nextObjective.id()); + NextGroup nextGroup = flowObjectiveStore.getNextGroup(nextObjective.id()); + if (nextGroup != null) { + Collection<TrafficTreatment> treatments = nextObjective.next(); + TrafficTreatment treatment = treatments.iterator().next(); + final GroupKey key = new DefaultGroupKey( + appKryo.serialize(nextObjective + .id())); + Group group = groupService.getGroup(deviceId, key); + if (group == null) { + log.warn("Group is not found in {} for {}", deviceId, key); + return; + } + GroupBucket bucket; + if (group.type() == GroupDescription.Type.INDIRECT) { + bucket = DefaultGroupBucket.createIndirectGroupBucket(treatment); + } else if (group.type() == GroupDescription.Type.SELECT) { + bucket = DefaultGroupBucket.createSelectGroupBucket(treatment); + } else { + log.warn("Unsupported Group type {}", group.type()); + return; + } + GroupBuckets removeBuckets = new GroupBuckets(Collections.singletonList(bucket)); + log.debug("Removing buckets from group id {} of next objective id {} in device {}", + group.id(), nextObjective.id(), deviceId); + groupService.removeBucketsFromGroup(deviceId, key, removeBuckets, key, appId); + } + } + + private Collection<FlowRule> processForward(ForwardingObjective fwd) { + switch (fwd.flag()) { + case SPECIFIC: + return processSpecific(fwd); + case VERSATILE: + return processVersatile(fwd); + default: + fail(fwd, ObjectiveError.UNKNOWN); + log.warn("Unknown forwarding flag {}", fwd.flag()); + } + return Collections.emptySet(); + } + + private Collection<FlowRule> processVersatile(ForwardingObjective fwd) { + log.debug("Processing versatile forwarding objective"); + TrafficSelector selector = fwd.selector(); + + EthTypeCriterion ethType = + (EthTypeCriterion) selector.getCriterion(Criterion.Type.ETH_TYPE); + if (ethType == null) { + log.error("Versatile forwarding objective must include ethType"); + fail(fwd, ObjectiveError.UNKNOWN); + return Collections.emptySet(); + } + + TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment + .builder(); + treatmentBuilder.wipeDeferred(); + + if (fwd.nextId() != null) { + NextGroup next = flowObjectiveStore.getNextGroup(fwd.nextId()); + + if (next != null) { + GroupKey key = appKryo.deserialize(next.data()); + + Group group = groupService.getGroup(deviceId, key); + + if (group == null) { + log.warn("The group left!"); + fail(fwd, ObjectiveError.GROUPMISSING); + return Collections.emptySet(); + } + treatmentBuilder.deferred().group(group.id()); + log.debug("Adding OUTGROUP action"); + } + } else { + log.warn("VERSATILE forwarding objective need next objective ID."); + return Collections.emptySet(); + } + + TrafficTreatment treatment = treatmentBuilder.build(); + + FlowRule.Builder ruleBuilder = DefaultFlowRule.builder() + .fromApp(fwd.appId()).withPriority(fwd.priority()) + .forDevice(deviceId).withSelector(fwd.selector()) + .withTreatment(treatment); + + if (fwd.permanent()) { + ruleBuilder.makePermanent(); + } else { + ruleBuilder.makeTemporary(fwd.timeout()); + } + + ruleBuilder.forTable(aclTableId); + return Collections.singletonList(ruleBuilder.build()); + } + + protected Collection<FlowRule> processSpecific(ForwardingObjective fwd) { + log.debug("Processing specific"); + TrafficSelector selector = fwd.selector(); + EthTypeCriterion ethType = (EthTypeCriterion) selector + .getCriterion(Criterion.Type.ETH_TYPE); + if ((ethType == null) || + (ethType.ethType().toShort() != Ethernet.TYPE_IPV4) && + (ethType.ethType().toShort() != Ethernet.MPLS_UNICAST)) { + log.warn("processSpecific: Unsupported " + + "forwarding objective criteraia"); + fail(fwd, ObjectiveError.UNSUPPORTED); + return Collections.emptySet(); + } + + TrafficSelector.Builder filteredSelectorBuilder = + DefaultTrafficSelector.builder(); + int forTableId = -1; + if (ethType.ethType().toShort() == Ethernet.TYPE_IPV4) { + filteredSelectorBuilder = filteredSelectorBuilder + .matchEthType(Ethernet.TYPE_IPV4) + .matchIPDst(((IPCriterion) selector + .getCriterion(Criterion.Type.IPV4_DST)) + .ip()); + forTableId = ipv4UnicastTableId; + log.debug("processing IPv4 specific forwarding objective"); + } else { + filteredSelectorBuilder = filteredSelectorBuilder + .matchEthType(Ethernet.MPLS_UNICAST) + .matchMplsLabel(((MplsCriterion) + selector.getCriterion(Criterion.Type.MPLS_LABEL)).label()); + //TODO: Add Match for BoS + //if (selector.getCriterion(Criterion.Type.MPLS_BOS) != null) { + //} + forTableId = mplsTableId; + log.debug("processing MPLS specific forwarding objective"); + } + + TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment + .builder(); + if (fwd.treatment() != null) { + for (Instruction i : fwd.treatment().allInstructions()) { + treatmentBuilder.add(i); + } + } + + //TODO: Analyze the forwarding objective here to make + //device specific decision such as no ECMP groups in Dell + //switches. + if (fwd.nextId() != null) { + NextGroup next = flowObjectiveStore.getNextGroup(fwd.nextId()); + + if (next != null) { + GroupKey key = appKryo.deserialize(next.data()); + + Group group = groupService.getGroup(deviceId, key); + + if (group == null) { + log.warn("The group left!"); + fail(fwd, ObjectiveError.GROUPMISSING); + return Collections.emptySet(); + } + treatmentBuilder.deferred().group(group.id()); + log.debug("Adding OUTGROUP action"); + } else { + log.warn("processSpecific: No associated next objective object"); + fail(fwd, ObjectiveError.GROUPMISSING); + return Collections.emptySet(); + } + } + + TrafficSelector filteredSelector = filteredSelectorBuilder.build(); + TrafficTreatment treatment = treatmentBuilder.transition(aclTableId) + .build(); + + FlowRule.Builder ruleBuilder = DefaultFlowRule.builder() + .fromApp(fwd.appId()).withPriority(fwd.priority()) + .forDevice(deviceId).withSelector(filteredSelector) + .withTreatment(treatment); + + if (fwd.permanent()) { + ruleBuilder.makePermanent(); + } else { + ruleBuilder.makeTemporary(fwd.timeout()); + } + + ruleBuilder.forTable(forTableId); + return Collections.singletonList(ruleBuilder.build()); + + } + + protected List<FlowRule> processEthDstFilter(Criterion c, + FilteringObjective filt, + ApplicationId applicationId) { + List<FlowRule> rules = new ArrayList<FlowRule>(); + EthCriterion e = (EthCriterion) c; + TrafficSelector.Builder selectorIp = DefaultTrafficSelector + .builder(); + TrafficTreatment.Builder treatmentIp = DefaultTrafficTreatment + .builder(); + selectorIp.matchEthDst(e.mac()); + selectorIp.matchEthType(Ethernet.TYPE_IPV4); + treatmentIp.transition(ipv4UnicastTableId); + FlowRule ruleIp = DefaultFlowRule.builder().forDevice(deviceId) + .withSelector(selectorIp.build()) + .withTreatment(treatmentIp.build()) + .withPriority(filt.priority()).fromApp(applicationId) + .makePermanent().forTable(tmacTableId).build(); + log.debug("adding IP ETH rule for MAC: {}", e.mac()); + rules.add(ruleIp); + + TrafficSelector.Builder selectorMpls = DefaultTrafficSelector + .builder(); + TrafficTreatment.Builder treatmentMpls = DefaultTrafficTreatment + .builder(); + selectorMpls.matchEthDst(e.mac()); + selectorMpls.matchEthType(Ethernet.MPLS_UNICAST); + treatmentMpls.transition(mplsTableId); + FlowRule ruleMpls = DefaultFlowRule.builder() + .forDevice(deviceId).withSelector(selectorMpls.build()) + .withTreatment(treatmentMpls.build()) + .withPriority(filt.priority()).fromApp(applicationId) + .makePermanent().forTable(tmacTableId).build(); + log.debug("adding MPLS ETH rule for MAC: {}", e.mac()); + rules.add(ruleMpls); + + return rules; + } + + protected List<FlowRule> processVlanIdFilter(Criterion c, + FilteringObjective filt, + ApplicationId applicationId) { + List<FlowRule> rules = new ArrayList<FlowRule>(); + VlanIdCriterion v = (VlanIdCriterion) c; + log.debug("adding rule for VLAN: {}", v.vlanId()); + TrafficSelector.Builder selector = DefaultTrafficSelector + .builder(); + TrafficTreatment.Builder treatment = DefaultTrafficTreatment + .builder(); + PortCriterion p = (PortCriterion) filt.key(); + if (v.vlanId() != VlanId.NONE) { + selector.matchVlanId(v.vlanId()); + selector.matchInPort(p.port()); + treatment.deferred().popVlan(); + } + treatment.transition(tmacTableId); + FlowRule rule = DefaultFlowRule.builder().forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(filt.priority()).fromApp(applicationId) + .makePermanent().forTable(vlanTableId).build(); + rules.add(rule); + + return rules; + } + + private void processFilter(FilteringObjective filt, boolean install, + ApplicationId applicationId) { + // This driver only processes filtering criteria defined with switch + // ports as the key + if (filt.key().equals(Criteria.dummy()) + || filt.key().type() != Criterion.Type.IN_PORT) { + log.warn("No key defined in filtering objective from app: {}. Not" + + "processing filtering objective", applicationId); + fail(filt, ObjectiveError.UNKNOWN); + return; + } + // convert filtering conditions for switch-intfs into flowrules + FlowRuleOperations.Builder ops = FlowRuleOperations.builder(); + for (Criterion c : filt.conditions()) { + if (c.type() == Criterion.Type.ETH_DST) { + for (FlowRule rule : processEthDstFilter(c, + filt, + applicationId)) { + ops = install ? ops.add(rule) : ops.remove(rule); + } + } else if (c.type() == Criterion.Type.VLAN_VID) { + for (FlowRule rule : processVlanIdFilter(c, + filt, + applicationId)) { + ops = install ? ops.add(rule) : ops.remove(rule); + } + } else if (c.type() == Criterion.Type.IPV4_DST) { + IPCriterion ip = (IPCriterion) c; + log.debug("adding rule for IP: {}", ip.ip()); + TrafficSelector.Builder selector = DefaultTrafficSelector + .builder(); + TrafficTreatment.Builder treatment = DefaultTrafficTreatment + .builder(); + selector.matchEthType(Ethernet.TYPE_IPV4); + selector.matchIPDst(ip.ip()); + treatment.transition(aclTableId); + FlowRule rule = DefaultFlowRule.builder().forDevice(deviceId) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .withPriority(filt.priority()).fromApp(applicationId) + .makePermanent().forTable(ipv4UnicastTableId).build(); + ops = install ? ops.add(rule) : ops.remove(rule); + } else { + log.warn("Driver does not currently process filtering condition" + + " of type: {}", c.type()); + fail(filt, ObjectiveError.UNSUPPORTED); + } + } + // apply filtering flow rules + flowRuleService.apply(ops.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + pass(filt); + log.debug("Provisioned tables in {} with fitering " + + "rules for segment router", deviceId); + } + + @Override + public void onError(FlowRuleOperations ops) { + fail(filt, ObjectiveError.FLOWINSTALLATIONFAILED); + log.warn("Failed to provision tables in {} with " + + "fitering rules for segment router", deviceId); + } + })); + } + + protected void setTableMissEntries() { + // set all table-miss-entries + populateTableMissEntry(vlanTableId, true, false, false, -1); + populateTableMissEntry(tmacTableId, true, false, false, -1); + populateTableMissEntry(ipv4UnicastTableId, false, true, true, + aclTableId); + populateTableMissEntry(mplsTableId, false, true, true, aclTableId); + populateTableMissEntry(aclTableId, false, false, false, -1); + } + + protected void populateTableMissEntry(int tableToAdd, + boolean toControllerNow, + boolean toControllerWrite, + boolean toTable, int tableToSend) { + TrafficSelector selector = DefaultTrafficSelector.builder().build(); + TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder(); + + if (toControllerNow) { + tBuilder.setOutput(PortNumber.CONTROLLER); + } + + if (toControllerWrite) { + tBuilder.deferred().setOutput(PortNumber.CONTROLLER); + } + + if (toTable) { + tBuilder.transition(tableToSend); + } + + FlowRule flow = DefaultFlowRule.builder().forDevice(deviceId) + .withSelector(selector).withTreatment(tBuilder.build()) + .withPriority(0).fromApp(appId).makePermanent() + .forTable(tableToAdd).build(); + + flowRuleService.applyFlowRules(flow); + } + + private void pass(Objective obj) { + if (obj.context().isPresent()) { + obj.context().get().onSuccess(obj); + } + } + + protected void fail(Objective obj, ObjectiveError error) { + if (obj.context().isPresent()) { + obj.context().get().onError(obj, error); + } + } + + private class InnerGroupListener implements GroupListener { + @Override + public void event(GroupEvent event) { + if (event.type() == GroupEvent.Type.GROUP_ADDED) { + log.debug("InnerGroupListener: Group ADDED " + + "event received in device {}", deviceId); + GroupKey key = event.subject().appCookie(); + + NextObjective obj = pendingGroups.getIfPresent(key); + if (obj != null) { + flowObjectiveStore + .putNextGroup(obj.id(), + new SegmentRoutingGroup(key)); + pass(obj); + pendingGroups.invalidate(key); + } + } else if (event.type() == GroupEvent.Type.GROUP_ADD_FAILED) { + log.warn("InnerGroupListener: Group ADD " + + "failed event received in device {}", deviceId); + } + } + } + + private class GroupChecker implements Runnable { + + @Override + public void run() { + Set<GroupKey> keys = pendingGroups + .asMap() + .keySet() + .stream() + .filter(key -> groupService.getGroup(deviceId, key) != null) + .collect(Collectors.toSet()); + + keys.stream() + .forEach(key -> { + NextObjective obj = pendingGroups + .getIfPresent(key); + if (obj == null) { + return; + } + pass(obj); + pendingGroups.invalidate(key); + flowObjectiveStore.putNextGroup(obj.id(), + new SegmentRoutingGroup( + key)); + }); + } + } + + private class SegmentRoutingGroup implements NextGroup { + + private final GroupKey key; + + public SegmentRoutingGroup(GroupKey key) { + this.key = key; + } + + public GroupKey key() { + return key; + } + + @Override + public byte[] data() { + return appKryo.serialize(key); + } + + } +} diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTPDell.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTPDell.java new file mode 100644 index 00000000..3267d550 --- /dev/null +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTPDell.java @@ -0,0 +1,200 @@ +/* + * Copyright 2015 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.driver.pipeline; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.onlab.packet.Ethernet; +import org.onlab.packet.MacAddress; +import org.onosproject.core.ApplicationId; +import org.onosproject.net.behaviour.NextGroup; +import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.criteria.Criterion; +import org.onosproject.net.flow.criteria.EthCriterion; +import org.onosproject.net.flow.criteria.EthTypeCriterion; +import org.onosproject.net.flow.criteria.IPCriterion; +import org.onosproject.net.flow.criteria.MplsCriterion; +import org.onosproject.net.flow.instructions.Instruction; +import org.onosproject.net.flowobjective.FilteringObjective; +import org.onosproject.net.flowobjective.ForwardingObjective; +import org.onosproject.net.flowobjective.ObjectiveError; +import org.onosproject.net.group.Group; +import org.onosproject.net.group.GroupKey; + +/** + * Spring-open driver implementation for Dell hardware switches. + */ +public class SpringOpenTTPDell extends SpringOpenTTP { + + /* Table IDs to be used for Dell Open Segment Routers*/ + private static final int DELL_TABLE_VLAN = 17; + private static final int DELL_TABLE_TMAC = 18; + private static final int DELL_TABLE_IPV4_UNICAST = 30; + private static final int DELL_TABLE_MPLS = 25; + private static final int DELL_TABLE_ACL = 40; + + //TODO: Store this info in the distributed store. + private MacAddress deviceTMac = null; + + public SpringOpenTTPDell() { + super(); + vlanTableId = DELL_TABLE_VLAN; + tmacTableId = DELL_TABLE_TMAC; + ipv4UnicastTableId = DELL_TABLE_IPV4_UNICAST; + mplsTableId = DELL_TABLE_MPLS; + aclTableId = DELL_TABLE_ACL; + } + + @Override + protected void setTableMissEntries() { + // No need to set table-miss-entries in Dell switches + return; + } + + @Override + //Dell switches need ETH_DST based match condition in all IP table entries. + //So this method overrides the default spring-open behavior and adds + //ETH_DST match condition while pushing IP table flow rules + protected Collection<FlowRule> processSpecific(ForwardingObjective fwd) { + log.debug("Processing specific"); + TrafficSelector selector = fwd.selector(); + EthTypeCriterion ethType = (EthTypeCriterion) selector + .getCriterion(Criterion.Type.ETH_TYPE); + if ((ethType == null) || + (ethType.ethType().toShort() != Ethernet.TYPE_IPV4) && + (ethType.ethType().toShort() != Ethernet.MPLS_UNICAST)) { + log.debug("processSpecific: Unsupported " + + "forwarding objective criteraia"); + fail(fwd, ObjectiveError.UNSUPPORTED); + return Collections.emptySet(); + } + + TrafficSelector.Builder filteredSelectorBuilder = + DefaultTrafficSelector.builder(); + int forTableId = -1; + if (ethType.ethType().toShort() == Ethernet.TYPE_IPV4) { + if (deviceTMac == null) { + log.debug("processSpecific: ETH_DST filtering " + + "objective is not set which is required " + + "before sending a IPv4 forwarding objective"); + //TODO: Map the error to more appropriate error code. + fail(fwd, ObjectiveError.DEVICEMISSING); + return Collections.emptySet(); + } + filteredSelectorBuilder = filteredSelectorBuilder + .matchEthType(Ethernet.TYPE_IPV4) + .matchEthDst(deviceTMac) + .matchIPDst(((IPCriterion) selector + .getCriterion(Criterion.Type.IPV4_DST)) + .ip()); + forTableId = ipv4UnicastTableId; + log.debug("processing IPv4 specific forwarding objective"); + } else { + filteredSelectorBuilder = filteredSelectorBuilder + .matchEthType(Ethernet.MPLS_UNICAST) + .matchMplsLabel(((MplsCriterion) + selector.getCriterion(Criterion.Type.MPLS_LABEL)).label()); + //TODO: Add Match for BoS + //if (selector.getCriterion(Criterion.Type.MPLS_BOS) != null) { + //} + forTableId = mplsTableId; + log.debug("processing MPLS specific forwarding objective"); + } + + TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment + .builder(); + if (fwd.treatment() != null) { + for (Instruction i : fwd.treatment().allInstructions()) { + treatmentBuilder.add(i); + } + } + + if (fwd.nextId() != null) { + NextGroup next = flowObjectiveStore.getNextGroup(fwd.nextId()); + + if (next != null) { + GroupKey key = appKryo.deserialize(next.data()); + + Group group = groupService.getGroup(deviceId, key); + + if (group == null) { + log.warn("The group left!"); + fail(fwd, ObjectiveError.GROUPMISSING); + return Collections.emptySet(); + } + treatmentBuilder.group(group.id()); + log.debug("Adding OUTGROUP action"); + } else { + log.warn("processSpecific: No associated next objective object"); + fail(fwd, ObjectiveError.GROUPMISSING); + return Collections.emptySet(); + } + } + + TrafficSelector filteredSelector = filteredSelectorBuilder.build(); + TrafficTreatment treatment = treatmentBuilder.transition(aclTableId) + .build(); + + FlowRule.Builder ruleBuilder = DefaultFlowRule.builder() + .fromApp(fwd.appId()).withPriority(fwd.priority()) + .forDevice(deviceId).withSelector(filteredSelector) + .withTreatment(treatment); + + if (fwd.permanent()) { + ruleBuilder.makePermanent(); + } else { + ruleBuilder.makeTemporary(fwd.timeout()); + } + + ruleBuilder.forTable(forTableId); + return Collections.singletonList(ruleBuilder.build()); + + } + + @Override + //Dell switches need ETH_DST based match condition in all IP table entries. + //So while processing the ETH_DST based filtering objective, store + //the device MAC to be used locally to use it while pushing the IP rules. + protected List<FlowRule> processEthDstFilter(Criterion c, + FilteringObjective filt, + ApplicationId applicationId) { + // Store device termination Mac to be used in IP flow entries + EthCriterion e = (EthCriterion) c; + deviceTMac = e.mac(); + + log.debug("For now not adding any TMAC rules " + + "into Dell switches as it is ignoring"); + + return Collections.emptyList(); + } + + @Override + protected List<FlowRule> processVlanIdFilter(Criterion c, + FilteringObjective filt, + ApplicationId applicationId) { + log.debug("For now not adding any VLAN rules " + + "into Dell switches as it is ignoring"); + + return Collections.emptyList(); + } +}
\ No newline at end of file diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/package-info.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/package-info.java new file mode 100644 index 00000000..880acf5d --- /dev/null +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 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. + */ + +/** + * Implementations of the pipeline driver behaviours. + */ +package org.onosproject.driver.pipeline;
\ No newline at end of file diff --git a/framework/src/onos/drivers/src/main/resources/onos-drivers.xml b/framework/src/onos/drivers/src/main/resources/onos-drivers.xml new file mode 100644 index 00000000..ac307c28 --- /dev/null +++ b/framework/src/onos/drivers/src/main/resources/onos-drivers.xml @@ -0,0 +1,124 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2015 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. + --> +<drivers> + <driver name="default" + manufacturer="ON.Lab" hwVersion="0.0.1" swVersion="0.0.1"> + <behaviour api="org.onosproject.net.behaviour.Pipeliner" + impl="org.onosproject.driver.pipeline.DefaultSingleTablePipeline"/> + <behaviour api="org.onosproject.openflow.controller.driver.OpenFlowSwitchDriver" + impl="org.onosproject.driver.handshaker.DefaultSwitchHandshaker"/> + <behaviour api="org.onosproject.net.behaviour.TunnelConfig" + impl="org.onosproject.driver.ovsdb.OvsdbTunnelConfig"/> + <behaviour api="org.onosproject.net.behaviour.BridgeConfig" + impl="org.onosproject.driver.ovsdb.OvsdbBridgeConfig"/> + </driver> + <driver name="ovs" extends="default" + manufacturer="Nicira, Inc\." hwVersion="Open vSwitch" swVersion="2\..*"> + <behaviour api="org.onosproject.openflow.controller.driver.OpenFlowSwitchDriver" + impl="org.onosproject.driver.handshaker.NiciraSwitchHandshaker"/> + </driver> + <driver name="ovs-corsa" extends="ovs" + manufacturer="Corsa" hwVersion="emulation" swVersion="0.0.0"> + <behaviour api="org.onosproject.net.behaviour.Pipeliner" + impl="org.onosproject.driver.pipeline.OVSCorsaPipeline"/> + </driver> + <driver name="spring-open-cpqd" extends="default" + manufacturer="Stanford University, Ericsson Research and CPqD Research" + hwVersion="OpenFlow 1.3 Reference Userspace Switch" swVersion=".*"> + <behaviour api="org.onosproject.net.behaviour.Pipeliner" + impl="org.onosproject.driver.pipeline.SpringOpenTTP"/> + </driver> + <driver name="spring-open" extends="default" + manufacturer="Dell " hwVersion="OpenFlow switch HW ver. 1.0" + swVersion="OpenFlow switch SW ver. 1.0 and 1.3"> + <behaviour api="org.onosproject.net.behaviour.Pipeliner" + impl="org.onosproject.driver.pipeline.SpringOpenTTPDell"/> + </driver> + <driver name="linc-oe" extends="default" + manufacturer="FlowForwarding.org" hwVersion="Unknown" + swVersion="LINC-OE OpenFlow Software Switch 1.1"> + <behaviour api="org.onosproject.openflow.controller.driver.OpenFlowSwitchDriver" + impl="org.onosproject.driver.handshaker.OFOpticalSwitchImplLINC13"/> + </driver> + <driver name="corsa" + manufacturer="Corsa" hwVersion="Corsa Element" swVersion="2.3.1"> + <behaviour api="org.onosproject.net.behaviour.Pipeliner" + impl="org.onosproject.driver.pipeline.CorsaPipeline"/> + <behaviour api="org.onosproject.openflow.controller.driver.OpenFlowSwitchDriver" + impl="org.onosproject.driver.handshaker.CorsaSwitchHandshaker"/> + </driver> + <driver name="ofdpa" extends="default" + manufacturer="Broadcom Corp." hwVersion="OF-DPA.*" swVersion="OF-DPA.*"> + <behaviour api="org.onosproject.net.behaviour.Pipeliner" + impl="org.onosproject.driver.pipeline.OFDPA1Pipeline"/> + </driver> + <driver name="pmc-olt" extends="default" + manufacturer="Big Switch Networks" hwVersion="ivs 0.5" swVersion="ivs 0.5"> + <behaviour api="org.onosproject.net.behaviour.Pipeliner" + impl="org.onosproject.driver.pipeline.OLTPipeline"/> + </driver> + <driver name="g.fast" extends="default" + manufacturer="TEST1" hwVersion="TEST2" swVersion="TEST3"> + <behaviour api="org.onosproject.net.behaviour.Pipeliner" + impl="org.onosproject.driver.pipeline.OLTPipeline"/> + </driver> + <!-- The SoftRouter driver is meant to be used by any software/NPU based + ~ switch that wishes to implement a simple 2-table router. To use this + ~ driver, configure ONOS with the dpid of the device, or extend the + ~ driver declaration with the manufacturer/hwVersion/swVersion of the + ~ device (see 'noviflow' example). + --> + <driver name="softrouter" extends="default" + manufacturer="Various" hwVersion="various" swVersion="0.0.0"> + <behaviour api="org.onosproject.net.behaviour.Pipeliner" + impl="org.onosproject.driver.pipeline.SoftRouterPipeline"/> + </driver> + <driver name="centec-V350" extends="default" + manufacturer=".*Centec.*" hwVersion=".*" swVersion="3.1.*"> + <behaviour api="org.onosproject.net.behaviour.Pipeliner" + impl="org.onosproject.driver.pipeline.CentecV350Pipeline"/> + </driver> + <driver name="pica" extends="default" + manufacturer="Pica8, Inc." hwVersion=".*" swVersion="PicOS 2.6"> + <behaviour api="org.onosproject.net.behaviour.Pipeliner" + impl="org.onosproject.driver.pipeline.PicaPipeline"/> + </driver> + <driver name="noviflow" extends="softrouter" + manufacturer="NoviFlow Inc" hwVersion="NS.*" swVersion="NW.*"> + </driver> + <!-- Emulation of the ofdpa pipeline using a CPqD OF 1.3 software switch. + ~ To use this driver, configure ONOS with the dpid of the device. + --> + <driver name="ofdpa-cpqd" extends="default" + manufacturer="ONF" + hwVersion="OF1.3 Software Switch from CPqD" swVersion="for Group Chaining"> + <behaviour api="org.onosproject.net.behaviour.Pipeliner" + impl="org.onosproject.driver.pipeline.CpqdOFDPA1Pipeline"/> + </driver> + <driver name="calient" extends="default" + manufacturer="calient inc" hwVersion="calient hardware" + swVersion="ocs switch"> + <behaviour api="org.onosproject.openflow.controller.driver.OpenFlowSwitchDriver" + impl="org.onosproject.driver.handshaker.CalientFiberSwitchHandshaker"/> + </driver> + <driver name="onosfw" extends="default" + manufacturer="" hwVersion="" swVersion=""> + <behaviour api="org.onosproject.net.behaviour.Pipeliner" + impl="org.onosproject.driver.pipeline.OpenVSwitchPipeline"/> + </driver> +</drivers> + |