SNMP based device and flow rule provider for Lumentum SDN ROADMs.

ONOS-3690 and ONOS-3842

Change-Id: If00ba70fa26e01924c225596c52a5ffb24987cc2
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/ConfigGetter.java b/core/api/src/main/java/org/onosproject/net/behaviour/ConfigGetter.java
index e77183b..89180ff 100644
--- a/core/api/src/main/java/org/onosproject/net/behaviour/ConfigGetter.java
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/ConfigGetter.java
@@ -29,7 +29,7 @@
 
     /**
      * Returns the string representation of a device configuration, returns a
-     * failure string if the configuration cannot be retreived.
+     * failure string if the configuration cannot be retrieved.
      * @param type the type of configuration to get (i.e. running).
      * @return string representation of the configuration or an error string.
      */
diff --git a/drivers/lumentum/features.xml b/drivers/lumentum/features.xml
new file mode 100644
index 0000000..17413d9
--- /dev/null
+++ b/drivers/lumentum/features.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--
+  ~ Copyright 2016 Open Networking Laboratory
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${project.version}">
+    <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-restsb-api/${project.version}</bundle>
+
+        <bundle>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.snmp4j/2.3.4_1</bundle>
+
+    </feature>
+</features>
diff --git a/drivers/lumentum/pom.xml b/drivers/lumentum/pom.xml
new file mode 100644
index 0000000..3375e2f
--- /dev/null
+++ b/drivers/lumentum/pom.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2016 Open Networking Laboratory
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<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/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>onos-drivers-general</artifactId>
+        <groupId>org.onosproject</groupId>
+        <version>1.5.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>onos-drivers-lumentum</artifactId>
+    <packaging>bundle</packaging>
+
+    <description>Lumentum device drivers</description>
+
+    <properties>
+        <onos.app.name>org.onosproject.drivers.lumentum</onos.app.name>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.servicemix.bundles</groupId>
+            <artifactId>org.apache.servicemix.bundles.snmp4j</artifactId>
+            <version>2.3.4_1</version>
+        </dependency>
+    </dependencies>
+</project>
\ No newline at end of file
diff --git a/drivers/lumentum/src/main/java/org/onosproject/drivers/lumentum/CrossConnect.java b/drivers/lumentum/src/main/java/org/onosproject/drivers/lumentum/CrossConnect.java
new file mode 100644
index 0000000..42937d0
--- /dev/null
+++ b/drivers/lumentum/src/main/java/org/onosproject/drivers/lumentum/CrossConnect.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2016 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.drivers.lumentum;
+
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.FlowRule;
+
+/**
+ * Interface for cross connects as common in optical networking.
+ */
+public interface CrossConnect extends FlowRule {
+    /**
+     * Returns the add/drop port of the cross connect.
+     *
+     * @return port number
+     */
+    PortNumber addDrop();
+
+    /**
+     * Returns the wavelength of the cross connect.
+     *
+     * @return OCh signal
+     */
+    OchSignal ochSignal();
+
+    /**
+     * Returns true if cross connect is adding traffic.
+     *
+     * @return true if add rule, false if drop rule
+     */
+    boolean isAddRule();
+}
diff --git a/drivers/lumentum/src/main/java/org/onosproject/drivers/lumentum/CrossConnectCache.java b/drivers/lumentum/src/main/java/org/onosproject/drivers/lumentum/CrossConnectCache.java
new file mode 100644
index 0000000..6d013fb
--- /dev/null
+++ b/drivers/lumentum/src/main/java/org/onosproject/drivers/lumentum/CrossConnectCache.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2016 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.drivers.lumentum;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.onosproject.net.flow.FlowId;
+
+/**
+ * Simple interface to cache flow ID and priority of cross connect flows.
+ */
+interface CrossConnectCache {
+    /**
+     * Returns the flow ID and priority corresponding to the flow hash.
+     *
+     * @param hash flow hash
+     * @return flow ID and priority, null if not in cache
+     */
+    Pair<FlowId, Integer> get(int hash);
+
+    /**
+     * Stores the flow ID and priority corresponding to the flow hash.
+     *
+     * @param hash flow hash
+     * @param flowId flow ID
+     * @param priority flow priority
+     */
+    void set(int hash, FlowId flowId, int priority);
+
+    /**
+     * Removes the given hash from the cache.
+     *
+     * @param hash flow hash
+     */
+    void remove(int hash);
+}
diff --git a/drivers/lumentum/src/main/java/org/onosproject/drivers/lumentum/CrossConnectFlowRule.java b/drivers/lumentum/src/main/java/org/onosproject/drivers/lumentum/CrossConnectFlowRule.java
new file mode 100644
index 0000000..b6a51d7
--- /dev/null
+++ b/drivers/lumentum/src/main/java/org/onosproject/drivers/lumentum/CrossConnectFlowRule.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2016 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.drivers.lumentum;
+
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.OchSignalCriterion;
+import org.onosproject.net.flow.criteria.OchSignalTypeCriterion;
+import org.onosproject.net.flow.criteria.PortCriterion;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+
+import java.util.List;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Cross connect abstraction based on a flow rule.
+ */
+public class CrossConnectFlowRule extends DefaultFlowRule implements CrossConnect {
+    private PortNumber addDrop;
+    private OchSignal ochSignal;
+    private boolean isAddRule;
+
+    public CrossConnectFlowRule(FlowRule rule, List<PortNumber> linePorts) {
+        super(rule);
+
+        Set<Criterion> criteria = rule.selector().criteria();
+        List<Instruction> instructions = rule.treatment().immediate();
+
+        // Proper cross connect has criteria for input port, OChSignal and OCh signal type.
+        // Instruction must be output to port.
+        checkArgument(criteria.size() == 3, "Wrong size of flow rule criteria for cross connect.");
+        checkArgument(instructions.size() == 1, "Wrong size of flow rule instructions for cross connect.");
+        // FIXME: Ensure criteria has exactly one of each match type
+        criteria.forEach(
+                c -> checkArgument(c instanceof OchSignalCriterion ||
+                        c instanceof OchSignalTypeCriterion ||
+                        c instanceof PortCriterion,
+                        "Incompatible flow rule criteria for cross connect: " + criteria
+                )
+        );
+        checkArgument(instructions.get(0).type() == Instruction.Type.OUTPUT,
+                "Incompatible flow rule instructions for cross connect: " + instructions);
+
+        ochSignal = criteria.stream()
+                .filter(c -> c instanceof OchSignalCriterion)
+                .map(c -> ((OchSignalCriterion) c).lambda())
+                .findAny()
+                .orElse(null);
+
+        // Add or drop rule?
+        Instructions.OutputInstruction outInstruction = (Instructions.OutputInstruction) instructions.get(0);
+        if (linePorts.contains(outInstruction.port())) {
+            addDrop = criteria.stream()
+                    .filter(c -> c instanceof PortCriterion)
+                    .map(c -> ((PortCriterion) c).port())
+                    .findAny()
+                    .orElse(null);
+            isAddRule = true;
+        } else {
+            addDrop = outInstruction.port();
+            isAddRule = false;
+        }
+    }
+
+    @Override
+    public PortNumber addDrop() {
+        return addDrop;
+    }
+
+    @Override
+    public OchSignal ochSignal() {
+        return ochSignal;
+    }
+
+    @Override
+    public boolean isAddRule() {
+        return isAddRule;
+    }
+}
diff --git a/drivers/lumentum/src/main/java/org/onosproject/drivers/lumentum/DefaultCrossConnectCache.java b/drivers/lumentum/src/main/java/org/onosproject/drivers/lumentum/DefaultCrossConnectCache.java
new file mode 100644
index 0000000..849add1
--- /dev/null
+++ b/drivers/lumentum/src/main/java/org/onosproject/drivers/lumentum/DefaultCrossConnectCache.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2016 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.drivers.lumentum;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.onosproject.net.flow.FlowId;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Component(immediate = true, enabled = true)
+@Service
+public class DefaultCrossConnectCache implements CrossConnectCache {
+    private final Map<Integer, Pair<FlowId, Integer>> cache = new HashMap<>();
+
+    @Override
+    public Pair<FlowId, Integer> get(int hash) {
+        return cache.get(hash);
+    }
+
+    @Override
+    public void set(int hash, FlowId flowId, int priority) {
+        cache.put(hash, Pair.of(flowId, priority));
+    }
+
+    @Override
+    public void remove(int hash) {
+        cache.remove(hash);
+    }
+}
diff --git a/drivers/lumentum/src/main/java/org/onosproject/drivers/lumentum/LambdaQueryLumentumRoadm.java b/drivers/lumentum/src/main/java/org/onosproject/drivers/lumentum/LambdaQueryLumentumRoadm.java
new file mode 100644
index 0000000..36ad521
--- /dev/null
+++ b/drivers/lumentum/src/main/java/org/onosproject/drivers/lumentum/LambdaQueryLumentumRoadm.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2016 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.drivers.lumentum;
+
+import org.onlab.util.Frequency;
+import org.onlab.util.Spectrum;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.behaviour.LambdaQuery;
+import org.onosproject.net.driver.AbstractHandlerBehaviour;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+/**
+ * Implementation of lambda query interface for Lumentum SDN ROADMs.
+ *
+ * Device supports 96 wavelengths of 50 GHz, between center frequencies 191.350 THz and 196.075 THz.
+ */
+public class LambdaQueryLumentumRoadm extends AbstractHandlerBehaviour implements LambdaQuery {
+    private static final int LAMBDA_COUNT = 96;
+
+    @Override
+    public Set<OchSignal> queryLambdas(PortNumber port) {
+        int startMultiplier = (int) (LumentumSnmpDevice.START_CENTER_FREQ.subtract(Spectrum.CENTER_FREQUENCY).asHz()
+                / Frequency.ofGHz(50).asHz());
+
+        return IntStream.range(0, LAMBDA_COUNT)
+                .mapToObj(x -> new OchSignal(LumentumSnmpDevice.GRID_TYPE,
+                        LumentumSnmpDevice.CHANNEL_SPACING,
+                        startMultiplier + x,
+                        4))
+                .collect(Collectors.toSet());
+    }
+}
diff --git a/drivers/lumentum/src/main/java/org/onosproject/drivers/lumentum/LumentumDriversLoader.java b/drivers/lumentum/src/main/java/org/onosproject/drivers/lumentum/LumentumDriversLoader.java
new file mode 100644
index 0000000..c398019
--- /dev/null
+++ b/drivers/lumentum/src/main/java/org/onosproject/drivers/lumentum/LumentumDriversLoader.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.drivers.lumentum;
+
+import org.apache.felix.scr.annotations.Component;
+import org.onosproject.net.driver.AbstractDriverLoader;
+
+/**
+ * Loader for Lumentum device drivers from specific xml.
+ */
+@Component(immediate = true)
+public class LumentumDriversLoader extends AbstractDriverLoader {
+
+    public LumentumDriversLoader() {
+        super("/lumentum-drivers.xml");
+    }
+}
diff --git a/drivers/lumentum/src/main/java/org/onosproject/drivers/lumentum/LumentumFlowRuleDriver.java b/drivers/lumentum/src/main/java/org/onosproject/drivers/lumentum/LumentumFlowRuleDriver.java
new file mode 100644
index 0000000..c1a5fe8
--- /dev/null
+++ b/drivers/lumentum/src/main/java/org/onosproject/drivers/lumentum/LumentumFlowRuleDriver.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright 2016 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.drivers.lumentum;
+
+import com.google.common.collect.Lists;
+import org.apache.commons.lang3.tuple.Pair;
+import org.onosproject.net.ChannelSpacing;
+import org.onosproject.net.GridType;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.OchSignalType;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.driver.AbstractHandlerBehaviour;
+import org.onosproject.net.flow.DefaultFlowEntry;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowId;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleProgrammable;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criteria;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.snmp4j.PDU;
+import org.snmp4j.event.ResponseEvent;
+import org.snmp4j.smi.Integer32;
+import org.snmp4j.smi.OID;
+import org.snmp4j.smi.UnsignedInteger32;
+import org.snmp4j.smi.VariableBinding;
+import org.snmp4j.util.TreeEvent;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+// TODO: need to convert between OChSignal and XC channel number
+public class LumentumFlowRuleDriver extends AbstractHandlerBehaviour implements FlowRuleProgrammable {
+
+    private static final Logger log =
+            LoggerFactory.getLogger(LumentumFlowRuleDriver.class);
+    private static final int DEFAULT_ATTENUATION = 160;
+    private static final int OUT_OF_SERVICE = 1;
+    private static final int IN_SERVICE = 2;
+    private static final String CTRL_AMP_MODULE_SERVICE_STATE_PREAMP = ".1.3.6.1.4.1.46184.1.4.4.1.2.1";
+    private static final String CTRL_AMP_MODULE_SERVICE_STATE_BOOSTER = ".1.3.6.1.4.1.46184.1.4.4.1.2.2";
+    private static final String CTRL_CHANNEL_STATE = ".1.3.6.1.4.1.46184.1.4.2.1.3.";
+    private static final String CTRL_CHANNEL_ADD_DROP_PORT_INDEX = ".1.3.6.1.4.1.46184.1.4.2.1.13.";
+    private static final String CTRL_CHANNEL_ABSOLUTE_ATTENUATION = ".1.3.6.1.4.1.46184.1.4.2.1.5.";
+
+    private LumentumSnmpDevice snmp;
+
+    @Override
+    public Collection<FlowEntry> getFlowEntries() {
+        try {
+            snmp = new LumentumSnmpDevice(handler().data().deviceId());
+        } catch (IOException e) {
+            log.error("Failed to connect to device: ", e);
+            return Collections.emptyList();
+        }
+
+        // Line in is last but one port, line out is last
+        DeviceService deviceService = this.handler().get(DeviceService.class);
+        List<Port> ports = deviceService.getPorts(data().deviceId());
+        if (ports.size() < 2) {
+            return Collections.emptyList();
+        }
+        PortNumber lineIn = ports.get(ports.size() - 2).number();
+        PortNumber lineOut = ports.get(ports.size() - 1).number();
+
+        Collection<FlowEntry> entries = Lists.newLinkedList();
+
+        // Add rules
+        OID addOid = new OID(CTRL_CHANNEL_STATE + "1");
+        entries.addAll(
+                fetchRules(addOid, true, lineOut).stream()
+                        .map(fr -> new DefaultFlowEntry(fr, FlowEntry.FlowEntryState.ADDED, 0, 0, 0))
+                        .collect(Collectors.toList())
+        );
+
+        // Drop rules
+        OID dropOid = new OID(CTRL_CHANNEL_STATE + "2");
+        entries.addAll(
+                fetchRules(dropOid, false, lineIn).stream()
+                        .map(fr -> new DefaultFlowEntry(fr, FlowEntry.FlowEntryState.ADDED, 0, 0, 0))
+                        .collect(Collectors.toList())
+        );
+
+        return entries;
+    }
+
+    @Override
+    public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
+        try {
+            snmp = new LumentumSnmpDevice(data().deviceId());
+        } catch (IOException e) {
+            log.error("Failed to connect to device: ", e);
+        }
+
+        // Line ports
+        DeviceService deviceService = this.handler().get(DeviceService.class);
+        List<Port> ports = deviceService.getPorts(data().deviceId());
+        List<PortNumber> linePorts = ports.subList(ports.size() - 2, ports.size()).stream()
+                .map(p -> p.number())
+                .collect(Collectors.toList());
+
+        // Apply the valid rules on the device
+        Collection<FlowRule> added = rules.stream()
+                .map(r -> new CrossConnectFlowRule(r, linePorts))
+                .filter(xc -> installCrossConnect(xc))
+                .collect(Collectors.toList());
+
+        // Cache the cookie/priority
+        CrossConnectCache cache = this.handler().get(CrossConnectCache.class);
+        added.stream()
+                .forEach(xc -> cache.set(
+                        Objects.hash(data().deviceId(), xc.selector(), xc.treatment()),
+                        xc.id(),
+                        xc.priority()));
+
+        return added;
+    }
+
+    @Override
+    public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
+        try {
+            snmp = new LumentumSnmpDevice(data().deviceId());
+        } catch (IOException e) {
+            log.error("Failed to connect to device: ", e);
+        }
+
+        // Line ports
+        DeviceService deviceService = this.handler().get(DeviceService.class);
+        List<Port> ports = deviceService.getPorts(data().deviceId());
+        List<PortNumber> linePorts = ports.subList(ports.size() - 2, ports.size()).stream()
+                .map(p -> p.number())
+                .collect(Collectors.toList());
+
+        // Apply the valid rules on the device
+        Collection<FlowRule> removed = rules.stream()
+                .map(r -> new CrossConnectFlowRule(r, linePorts))
+                .filter(xc -> removeCrossConnect(xc))
+                .collect(Collectors.toList());
+
+        // Remove flow rule from cache
+        CrossConnectCache cache = this.handler().get(CrossConnectCache.class);
+        removed.stream()
+                .forEach(xc -> cache.remove(
+                        Objects.hash(data().deviceId(), xc.selector(), xc.treatment())));
+
+        return removed;
+    }
+
+    // Installs cross connect on device
+    private boolean installCrossConnect(CrossConnectFlowRule xc) {
+
+        int channel = toChannel(xc.ochSignal());
+        long addDrop = xc.addDrop().toLong();
+
+        // Create the PDU object
+        PDU pdu = new PDU();
+        pdu.setType(PDU.SET);
+
+        // Enable preamp & booster for service
+        List<OID> oids = Arrays.asList(new OID(CTRL_AMP_MODULE_SERVICE_STATE_PREAMP),
+                new OID(CTRL_AMP_MODULE_SERVICE_STATE_BOOSTER));
+        oids.forEach(
+                oid -> pdu.add(new VariableBinding(oid, new Integer32(IN_SERVICE)))
+        );
+
+        // Enable the channel
+        OID ctrlChannelState = new OID(CTRL_CHANNEL_STATE + (xc.isAddRule() ? "1." : "2.") + channel);
+        pdu.add(new VariableBinding(ctrlChannelState, new Integer32(IN_SERVICE)));
+
+        // Make cross connect
+        OID ctrlChannelAddDropPortIndex = new OID(CTRL_CHANNEL_ADD_DROP_PORT_INDEX +
+                (xc.isAddRule() ? "1." : "2.") + channel);
+        pdu.add(new VariableBinding(ctrlChannelAddDropPortIndex, new UnsignedInteger32(addDrop)));
+
+        // Set attenuation to zero
+        OID ctrlChannelAbsoluteAttenuation = new OID(CTRL_CHANNEL_ABSOLUTE_ATTENUATION +
+                (xc.isAddRule() ? "1." : "2.") + channel);
+        pdu.add(new VariableBinding(ctrlChannelAbsoluteAttenuation, new UnsignedInteger32(0)));
+
+        try {
+            ResponseEvent response = snmp.set(pdu);
+
+             // TODO: parse response
+        } catch (IOException e) {
+            log.error("Failed to create cross connect, unable to connect to device: ", e);
+        }
+
+        return true;
+    }
+
+    // Removes cross connect on device
+    private boolean removeCrossConnect(CrossConnectFlowRule xc) {
+
+        int channel = toChannel(xc.ochSignal());
+
+        // Create the PDU object
+        PDU pdu = new PDU();
+        pdu.setType(PDU.SET);
+
+        // Disable the channel
+        OID ctrlChannelState = new OID(CTRL_CHANNEL_STATE + (xc.isAddRule() ? "1." : "2.") + channel);
+        pdu.add(new VariableBinding(ctrlChannelState, new Integer32(OUT_OF_SERVICE)));
+
+        // Put cross connect back into default port 1
+        OID ctrlChannelAddDropPortIndex = new OID(CTRL_CHANNEL_ADD_DROP_PORT_INDEX +
+                (xc.isAddRule() ? "1." : "2.") + channel);
+        pdu.add(new VariableBinding(ctrlChannelAddDropPortIndex, new UnsignedInteger32(OUT_OF_SERVICE)));
+
+        // Set attenuation to default value
+        OID ctrlChannelAbsoluteAttenuation = new OID(CTRL_CHANNEL_ABSOLUTE_ATTENUATION +
+                (xc.isAddRule() ? "1." : "2.") + channel);
+        pdu.add(new VariableBinding(ctrlChannelAbsoluteAttenuation, new UnsignedInteger32(DEFAULT_ATTENUATION))
+        );
+
+        try {
+            ResponseEvent response = snmp.set(pdu);
+
+            // TODO: parse response
+        } catch (IOException e) {
+            log.error("Failed to remove cross connect, unable to connect to device: ", e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Convert OCh signal to Lumentum channel ID.
+     *
+     * @param ochSignal OCh signal
+     * @return Lumentum channel ID
+     */
+    public static int toChannel(OchSignal ochSignal) {
+        // FIXME: move to cross connect validation
+        checkArgument(ochSignal.channelSpacing() == ChannelSpacing.CHL_50GHZ);
+        checkArgument(LumentumSnmpDevice.START_CENTER_FREQ.compareTo(ochSignal.centralFrequency()) <= 0);
+        checkArgument(LumentumSnmpDevice.END_CENTER_FREQ.compareTo(ochSignal.centralFrequency()) >= 0);
+
+        return ochSignal.spacingMultiplier() + LumentumSnmpDevice.MULTIPLIER_SHIFT;
+    }
+
+    /**
+     * Convert Lumentum channel ID to OCh signal.
+     *
+     * @param channel Lumentum channel ID
+     * @return OCh signal
+     */
+    public static OchSignal toOchSignal(int channel) {
+        checkArgument(1 <= channel);
+        checkArgument(channel <= 96);
+
+        return new OchSignal(GridType.DWDM, ChannelSpacing.CHL_50GHZ,
+                channel - LumentumSnmpDevice.MULTIPLIER_SHIFT, 4);
+    }
+
+    // Returns the currently configured add/drop port for the given channel.
+    private PortNumber getAddDropPort(int channel, boolean isAddPort) {
+        OID oid = new OID(CTRL_CHANNEL_ADD_DROP_PORT_INDEX + (isAddPort ? "1" : "2"));
+
+        for (TreeEvent event : snmp.get(oid)) {
+            if (event == null) {
+                return null;
+            }
+
+            VariableBinding[] varBindings = event.getVariableBindings();
+
+            for (VariableBinding varBinding : varBindings) {
+                if (varBinding.getOid().last() == channel) {
+                    int port = varBinding.getVariable().toInt();
+                    return PortNumber.portNumber(port);
+
+                }
+            }
+
+        }
+
+        return null;
+    }
+
+    // Returns the currently installed flow entries on the device.
+    private List<FlowRule> fetchRules(OID oid, boolean isAdd, PortNumber linePort) {
+        List<FlowRule> rules = new LinkedList<>();
+
+        for (TreeEvent event : snmp.get(oid)) {
+            if (event == null) {
+                continue;
+            }
+
+            VariableBinding[] varBindings = event.getVariableBindings();
+            for (VariableBinding varBinding : varBindings) {
+                CrossConnectCache cache = this.handler().get(CrossConnectCache.class);
+
+                if (varBinding.getVariable().toInt() == IN_SERVICE) {
+                    int channel = varBinding.getOid().removeLast();
+
+                    PortNumber addDropPort = getAddDropPort(channel, isAdd);
+                    if (addDropPort == null) {
+                        continue;
+                    }
+
+                    TrafficSelector selector = DefaultTrafficSelector.builder()
+                            .matchInPort(isAdd ? addDropPort : linePort)
+                            .add(Criteria.matchOchSignalType(OchSignalType.FIXED_GRID))
+                            .add(Criteria.matchLambda(toOchSignal(channel)))
+                            .build();
+                    TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                            .setOutput(isAdd ? linePort : addDropPort)
+                            .build();
+
+                    // Lookup flow ID and priority
+                    int hash = Objects.hash(data().deviceId(), selector, treatment);
+                    Pair<FlowId, Integer> lookup = cache.get(hash);
+                    if (lookup == null) {
+                        continue;
+                    }
+
+                    FlowRule fr = DefaultFlowRule.builder()
+                            .forDevice(data().deviceId())
+                            .makePermanent()
+                            .withSelector(selector)
+                            .withTreatment(treatment)
+                            .withPriority(lookup.getRight())
+                            .withCookie(lookup.getLeft().value())
+                            .build();
+                    rules.add(fr);
+                }
+            }
+        }
+
+        return rules;
+    }
+}
diff --git a/drivers/lumentum/src/main/java/org/onosproject/drivers/lumentum/LumentumSnmpDevice.java b/drivers/lumentum/src/main/java/org/onosproject/drivers/lumentum/LumentumSnmpDevice.java
new file mode 100644
index 0000000..f0138f1
--- /dev/null
+++ b/drivers/lumentum/src/main/java/org/onosproject/drivers/lumentum/LumentumSnmpDevice.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2016 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.drivers.lumentum;
+
+import com.google.common.base.Preconditions;
+import org.onlab.util.Frequency;
+import org.onosproject.net.ChannelSpacing;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.GridType;
+import org.snmp4j.CommunityTarget;
+import org.snmp4j.PDU;
+import org.snmp4j.Snmp;
+import org.snmp4j.TransportMapping;
+import org.snmp4j.event.ResponseEvent;
+import org.snmp4j.mp.SnmpConstants;
+import org.snmp4j.smi.Address;
+import org.snmp4j.smi.GenericAddress;
+import org.snmp4j.smi.OID;
+import org.snmp4j.smi.OctetString;
+import org.snmp4j.transport.DefaultUdpTransportMapping;
+import org.snmp4j.util.DefaultPDUFactory;
+import org.snmp4j.util.TreeEvent;
+import org.snmp4j.util.TreeUtils;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Quick and dirty device abstraction for SNMP-based Lumentum devices.
+ *
+ * TODO: Refactor once SnmpDevice is finished
+ */
+public class LumentumSnmpDevice {
+
+    private static final int MAX_SIZE_RESPONSE_PDU = 65535;
+    private static final int MAX_REPETITIONS = 50;      // Only 42 directed ports on our devices
+
+    public static final GridType GRID_TYPE = GridType.DWDM;
+    public static final ChannelSpacing CHANNEL_SPACING = ChannelSpacing.CHL_50GHZ;
+    public static final Frequency START_CENTER_FREQ = Frequency.ofGHz(191_350);
+    public static final Frequency END_CENTER_FREQ = Frequency.ofGHz(196_100);
+
+    // Lumentum SDN ROADM has shifted channel plan.
+    // Channel 36 corresponds to ITU-T center frequency, which has spacing multiplier 0.
+    public static final int MULTIPLIER_SHIFT = 36;
+
+    private Snmp snmp;
+    private CommunityTarget target;
+
+    public LumentumSnmpDevice(DeviceId did) throws IOException {
+        String[] deviceComponents = did.toString().split(":");
+        Preconditions.checkArgument(deviceComponents.length > 1);
+
+        String ipAddress = deviceComponents[1];
+        String port = deviceComponents[2];
+
+        Address targetAddress = GenericAddress.parse("udp:" + ipAddress + "/" + port);
+        TransportMapping transport = new DefaultUdpTransportMapping();
+        transport.listen();
+        snmp = new Snmp(transport);
+
+        // setting up target
+        target = new CommunityTarget();
+        target.setCommunity(new OctetString("public"));
+        target.setAddress(targetAddress);
+        target.setRetries(3);
+        target.setTimeout(1000 * 3);
+        target.setVersion(SnmpConstants.version2c);
+        target.setMaxSizeRequestPDU(MAX_SIZE_RESPONSE_PDU);
+    }
+
+    public ResponseEvent set(PDU pdu) throws IOException {
+        return snmp.set(pdu, target);
+    }
+
+    public List<TreeEvent> get(OID oid) {
+        TreeUtils treeUtils = new TreeUtils(snmp, new DefaultPDUFactory());
+        treeUtils.setMaxRepetitions(MAX_REPETITIONS);
+        return treeUtils.getSubtree(target, oid);
+    }
+}
diff --git a/drivers/lumentum/src/main/java/org/onosproject/drivers/lumentum/PortDiscoveryLumentumRoadm.java b/drivers/lumentum/src/main/java/org/onosproject/drivers/lumentum/PortDiscoveryLumentumRoadm.java
new file mode 100644
index 0000000..9d250c0
--- /dev/null
+++ b/drivers/lumentum/src/main/java/org/onosproject/drivers/lumentum/PortDiscoveryLumentumRoadm.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2016 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.drivers.lumentum;
+
+import com.google.common.collect.Lists;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.SparseAnnotations;
+import org.onosproject.net.behaviour.PortDiscovery;
+import org.onosproject.net.device.OmsPortDescription;
+import org.onosproject.net.device.PortDescription;
+import org.onosproject.net.driver.AbstractHandlerBehaviour;
+import org.slf4j.Logger;
+import org.snmp4j.smi.OID;
+import org.snmp4j.smi.VariableBinding;
+import org.snmp4j.util.TreeEvent;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Discovers the ports of a Lumentum SDN ROADM device using SNMP.
+ */
+public class PortDiscoveryLumentumRoadm extends AbstractHandlerBehaviour
+        implements PortDiscovery {
+
+    private final Logger log = getLogger(PortDiscoveryLumentumRoadm.class);
+
+    private static final String CTRL_PORT_STATE = ".1.3.6.1.4.1.46184.1.4.1.1.3.";
+
+    private LumentumSnmpDevice snmp;
+
+    @Override
+    public List<PortDescription> getPorts() {
+        try {
+            snmp = new LumentumSnmpDevice(handler().data().deviceId());
+        } catch (IOException e) {
+            log.error("Failed to connect to device: ", e);
+
+            return Collections.emptyList();
+        }
+
+        List<PortDescription> ports = Lists.newLinkedList();
+
+        OID[] oids = {
+                new OID(CTRL_PORT_STATE + "1"),
+                new OID(CTRL_PORT_STATE + "2")
+        };
+
+        for (OID oid : oids) {
+
+            for (TreeEvent event : snmp.get(oid)) {
+                if (event != null) {
+                    VariableBinding[] varBindings = event.getVariableBindings();
+                    for (VariableBinding varBinding : varBindings) {
+                        if (varBinding.getVariable().toInt() == 1) {
+                            int portNumber = varBinding.getOid().removeLast();
+                            int portDirection = varBinding.getOid().removeLast();
+                            SparseAnnotations ann = DefaultAnnotations.builder()
+                                    .set(AnnotationKeys.PORT_NAME, portDirection + "-" + portNumber)
+                                    .build();
+                            PortDescription p = new OmsPortDescription(
+                                    PortNumber.portNumber(ports.size() + 1),
+                                    true,
+                                    LumentumSnmpDevice.START_CENTER_FREQ,
+                                    LumentumSnmpDevice.END_CENTER_FREQ,
+                                    LumentumSnmpDevice.CHANNEL_SPACING.frequency(),
+                                    ann);
+                            ports.add(p);
+                        }
+                    }
+                }
+            }
+        }
+
+        // Create LINE IN and LINE OUT ports as these are not reported through SNMP
+        SparseAnnotations annLineIn = DefaultAnnotations.builder()
+                .set(AnnotationKeys.PORT_NAME, "LINE IN")
+                .build();
+        ports.add(new OmsPortDescription(
+                PortNumber.portNumber(ports.size() + 1),
+                true,
+                LumentumSnmpDevice.START_CENTER_FREQ,
+                LumentumSnmpDevice.END_CENTER_FREQ,
+                LumentumSnmpDevice.CHANNEL_SPACING.frequency(),
+                annLineIn
+        ));
+
+        SparseAnnotations annLineOut = DefaultAnnotations.builder()
+                .set(AnnotationKeys.PORT_NAME, "LINE OUT")
+                .build();
+        ports.add(new OmsPortDescription(
+                PortNumber.portNumber(ports.size() + 1),
+                true,
+                LumentumSnmpDevice.START_CENTER_FREQ,
+                LumentumSnmpDevice.END_CENTER_FREQ,
+                LumentumSnmpDevice.CHANNEL_SPACING.frequency(),
+                annLineOut
+        ));
+
+        return ports;
+    }
+}
+
+
diff --git a/drivers/lumentum/src/main/java/org/onosproject/drivers/lumentum/package-info.java b/drivers/lumentum/src/main/java/org/onosproject/drivers/lumentum/package-info.java
new file mode 100644
index 0000000..0c34578
--- /dev/null
+++ b/drivers/lumentum/src/main/java/org/onosproject/drivers/lumentum/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Package for Lumentum device drivers.
+ */
+package org.onosproject.drivers.lumentum;
\ No newline at end of file
diff --git a/drivers/lumentum/src/main/resources/lumentum-drivers.xml b/drivers/lumentum/src/main/resources/lumentum-drivers.xml
new file mode 100644
index 0000000..0465fe0
--- /dev/null
+++ b/drivers/lumentum/src/main/resources/lumentum-drivers.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2016 Open Networking Laboratory
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<drivers>
+    <driver name="lumentum" manufacturer="Lumentum" hwVersion="SDN ROADM" swVersion="1.0">
+        <behaviour api="org.onosproject.net.behaviour.PortDiscovery"
+                   impl="org.onosproject.drivers.lumentum.PortDiscoveryLumentumRoadm"/>
+        <behaviour api="org.onosproject.net.behaviour.LambdaQuery"
+                   impl="org.onosproject.drivers.lumentum.LambdaQueryLumentumRoadm"/>
+        <behaviour api="org.onosproject.net.flow.FlowRuleProgrammable"
+                   impl="org.onosproject.drivers.lumentum.LumentumFlowRuleDriver"/>
+    </driver>
+</drivers>
+
diff --git a/drivers/netconf/src/main/java/org/onosproject/drivers/netconf/NetconfControllerConfig.java b/drivers/netconf/src/main/java/org/onosproject/drivers/netconf/NetconfControllerConfig.java
index 1a6f31e..7c0f1b0 100644
--- a/drivers/netconf/src/main/java/org/onosproject/drivers/netconf/NetconfControllerConfig.java
+++ b/drivers/netconf/src/main/java/org/onosproject/drivers/netconf/NetconfControllerConfig.java
@@ -58,7 +58,7 @@
             controllers.addAll(XmlConfigParser.parseStreamControllers(XmlConfigParser.
                     loadXml(new ByteArrayInputStream(reply.getBytes(StandardCharsets.UTF_8)))));
         } catch (IOException e) {
-            log.error("Cannot comunicate to device {} ", ofDeviceId);
+            log.error("Cannot communicate with device {} ", ofDeviceId);
         }
         return controllers;
     }
diff --git a/drivers/pom.xml b/drivers/pom.xml
index 290d165..560eb24 100644
--- a/drivers/pom.xml
+++ b/drivers/pom.xml
@@ -39,6 +39,7 @@
         <module>netconf</module>
         <module>ovsdb</module>
         <module>utilities</module>
+        <module>lumentum</module>
     </modules>
 
     <!--<properties>
diff --git a/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/AbstractOpenFlowSwitch.java b/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/AbstractOpenFlowSwitch.java
index 3a999a2..1613303 100644
--- a/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/AbstractOpenFlowSwitch.java
+++ b/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/AbstractOpenFlowSwitch.java
@@ -16,8 +16,6 @@
 
 package org.onosproject.openflow.controller.driver;
 
-import static org.onlab.util.Tools.groupedThreads;
-
 import com.google.common.collect.Lists;
 import org.jboss.netty.channel.Channel;
 import org.onlab.packet.IpAddress;
@@ -26,23 +24,21 @@
 import org.onosproject.openflow.controller.Dpid;
 import org.onosproject.openflow.controller.OpenFlowEventListener;
 import org.onosproject.openflow.controller.RoleState;
-
 import org.projectfloodlight.openflow.protocol.OFDescStatsReply;
-import org.projectfloodlight.openflow.protocol.OFFeaturesReply;
-import org.projectfloodlight.openflow.protocol.OFPortDescStatsReply;
-import org.projectfloodlight.openflow.protocol.OFVersion;
-import org.projectfloodlight.openflow.protocol.OFMessage;
-import org.projectfloodlight.openflow.protocol.OFType;
-import org.projectfloodlight.openflow.protocol.OFFactories;
-import org.projectfloodlight.openflow.protocol.OFPortDesc;
-import org.projectfloodlight.openflow.protocol.OFExperimenter;
 import org.projectfloodlight.openflow.protocol.OFErrorMsg;
-import org.projectfloodlight.openflow.protocol.OFRoleRequest;
-import org.projectfloodlight.openflow.protocol.OFNiciraControllerRoleRequest;
-import org.projectfloodlight.openflow.protocol.OFPortStatus;
+import org.projectfloodlight.openflow.protocol.OFExperimenter;
+import org.projectfloodlight.openflow.protocol.OFFactories;
 import org.projectfloodlight.openflow.protocol.OFFactory;
+import org.projectfloodlight.openflow.protocol.OFFeaturesReply;
+import org.projectfloodlight.openflow.protocol.OFMessage;
+import org.projectfloodlight.openflow.protocol.OFNiciraControllerRoleRequest;
+import org.projectfloodlight.openflow.protocol.OFPortDesc;
+import org.projectfloodlight.openflow.protocol.OFPortDescStatsReply;
+import org.projectfloodlight.openflow.protocol.OFPortStatus;
 import org.projectfloodlight.openflow.protocol.OFRoleReply;
-
+import org.projectfloodlight.openflow.protocol.OFRoleRequest;
+import org.projectfloodlight.openflow.protocol.OFType;
+import org.projectfloodlight.openflow.protocol.OFVersion;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -60,6 +56,8 @@
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Collectors;
 
+import static org.onlab.util.Tools.groupedThreads;
+
 /**
  * An abstract representation of an OpenFlow switch. Can be extended by others
  * to serve as a base for their vendor specific representation of a switch.
@@ -200,7 +198,8 @@
     }
 
     @Override
-    public final void sendHandshakeMessage(OFMessage message) {
+    public final void
+    sendHandshakeMessage(OFMessage message) {
         if (!this.isDriverHandshakeComplete()) {
             sendMsgsOnChannel(Collections.singletonList(message));
         }
diff --git a/providers/openflow/device/src/main/java/org/onosproject/provider/of/device/impl/OpenFlowDeviceProvider.java b/providers/openflow/device/src/main/java/org/onosproject/provider/of/device/impl/OpenFlowDeviceProvider.java
index f639dcf..6a2f9d7 100644
--- a/providers/openflow/device/src/main/java/org/onosproject/provider/of/device/impl/OpenFlowDeviceProvider.java
+++ b/providers/openflow/device/src/main/java/org/onosproject/provider/of/device/impl/OpenFlowDeviceProvider.java
@@ -15,24 +15,10 @@
  */
 package org.onosproject.provider.of.device.impl;
 
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Strings.isNullOrEmpty;
-import static org.onlab.util.Tools.get;
-import static org.onosproject.net.DeviceId.deviceId;
-import static org.onosproject.net.Port.Type.COPPER;
-import static org.onosproject.net.Port.Type.FIBER;
-import static org.onosproject.openflow.controller.Dpid.dpid;
-import static org.onosproject.openflow.controller.Dpid.uri;
-import static org.slf4j.LoggerFactory.getLogger;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Dictionary;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -82,6 +68,8 @@
 import org.onosproject.openflow.controller.PortDescPropertyType;
 import org.onosproject.openflow.controller.RoleState;
 import org.osgi.service.component.ComponentContext;
+import org.projectfloodlight.openflow.protocol.OFCalientPortDescProp;
+import org.projectfloodlight.openflow.protocol.OFCalientPortDescPropOptical;
 import org.projectfloodlight.openflow.protocol.OFCalientPortDescStatsEntry;
 import org.projectfloodlight.openflow.protocol.OFExpPort;
 import org.projectfloodlight.openflow.protocol.OFExpPortDescPropOpticalTransport;
@@ -108,10 +96,23 @@
 import org.projectfloodlight.openflow.types.PortSpeed;
 import org.slf4j.Logger;
 
-import com.google.common.base.Strings;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static org.onlab.util.Tools.get;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.Port.Type.COPPER;
+import static org.onosproject.net.Port.Type.FIBER;
+import static org.onosproject.openflow.controller.Dpid.dpid;
+import static org.onosproject.openflow.controller.Dpid.uri;
+import static org.slf4j.LoggerFactory.getLogger;
 
 /**
  * Provider which uses an OpenFlow controller to detect network
@@ -691,9 +692,19 @@
         private PortDescription buildPortDescription(OFCalientPortDescStatsEntry port) {
             PortNumber portNo = PortNumber.portNumber(port.getPortNo().getPortNumber());
 
+            // Use the alias name if it's available
+            String name = port.getName();
+            List<OFCalientPortDescProp> props = port.getProperties();
+            if (props != null && props.size() > 0) {
+                OFCalientPortDescPropOptical propOptical = (OFCalientPortDescPropOptical) props.get(0);
+                if (propOptical != null) {
+                    name = propOptical.getInAlias();
+                }
+            }
+
             // FIXME when Calient OF agent reports port status
             boolean enabled = true;
-            SparseAnnotations annotations = makePortAnnotation(port.getName(), port.getHwAddr().toString());
+            SparseAnnotations annotations = makePortAnnotation(name, port.getHwAddr().toString());
 
             // S160 data sheet
             // Wavelength range: 1260 - 1630 nm, grid is irrelevant for this type of switch
diff --git a/providers/snmp/device/src/main/java/org/onosproject/provider/snmp/device/impl/Bti7000DeviceDescriptionProvider.java b/providers/snmp/device/src/main/java/org/onosproject/provider/snmp/device/impl/Bti7000DeviceDescriptionProvider.java
index b47b534..8726673 100644
--- a/providers/snmp/device/src/main/java/org/onosproject/provider/snmp/device/impl/Bti7000DeviceDescriptionProvider.java
+++ b/providers/snmp/device/src/main/java/org/onosproject/provider/snmp/device/impl/Bti7000DeviceDescriptionProvider.java
@@ -19,14 +19,16 @@
 import com.btisystems.pronx.ems.core.model.IClassRegistry;
 import com.btisystems.pronx.ems.core.model.NetworkDevice;
 import com.btisystems.pronx.ems.core.snmp.ISnmpSession;
-import java.io.IOException;
-import java.util.Arrays;
 import org.onosproject.net.device.DefaultDeviceDescription;
 import org.onosproject.net.device.DeviceDescription;
 import org.slf4j.Logger;
-import static org.slf4j.LoggerFactory.getLogger;
 import org.snmp4j.smi.OID;
 
+import java.io.IOException;
+import java.util.Arrays;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
 /**
  * A vendor-specific implementation supporting BTI Systems BTI-7000 equipment.
  * @deprecated 1.5.0 Falcon, not compliant with ONOS SB and driver architecture.
@@ -55,7 +57,7 @@
                 String[] systemComponents = systemTree.getSysDescr().split(";");
                 return new DefaultDeviceDescription(description.deviceUri(), description.type(),
                         systemComponents[0], systemComponents[2], systemComponents[3],
-                        UNKNOWN, description.chassisId());
+                        UNKNOWN, description.chassisId(), description.annotations());
             }
         } catch (IOException ex) {
             log.error("Error reading details for device {}.", session.getAddress(), ex);
diff --git a/providers/snmp/device/src/main/java/org/onosproject/provider/snmp/device/impl/LumentumDeviceDescriptionProvider.java b/providers/snmp/device/src/main/java/org/onosproject/provider/snmp/device/impl/LumentumDeviceDescriptionProvider.java
new file mode 100644
index 0000000..70286b6
--- /dev/null
+++ b/providers/snmp/device/src/main/java/org/onosproject/provider/snmp/device/impl/LumentumDeviceDescriptionProvider.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.provider.snmp.device.impl;
+
+import com.btisystems.pronx.ems.core.snmp.ISnmpSession;
+import org.onosproject.net.Device;
+import org.onosproject.net.device.DefaultDeviceDescription;
+import org.onosproject.net.device.DeviceDescription;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Device description provider for Lumentum SDN ROADMs.
+ * @deprecated 1.5.0 Falcon, not compliant with ONOS SB and driver architecture.
+ */
+@Deprecated
+public class LumentumDeviceDescriptionProvider implements SnmpDeviceDescriptionProvider {
+
+    private static final Logger log = LoggerFactory.getLogger(LumentumDeviceDescriptionProvider.class);
+
+    @Override
+    public DeviceDescription populateDescription(ISnmpSession session, DeviceDescription description) {
+        return new DefaultDeviceDescription(description.deviceUri(), Device.Type.ROADM,
+                "Lumentum", "SDN ROADM", "1.0", "v1", description.chassisId(), description.annotations());
+    }
+}
diff --git a/providers/snmp/device/src/main/java/org/onosproject/provider/snmp/device/impl/NetSnmpDeviceDescriptionProvider.java b/providers/snmp/device/src/main/java/org/onosproject/provider/snmp/device/impl/NetSnmpDeviceDescriptionProvider.java
index 1d9c7ff..a84a43c 100644
--- a/providers/snmp/device/src/main/java/org/onosproject/provider/snmp/device/impl/NetSnmpDeviceDescriptionProvider.java
+++ b/providers/snmp/device/src/main/java/org/onosproject/provider/snmp/device/impl/NetSnmpDeviceDescriptionProvider.java
@@ -19,15 +19,17 @@
 import com.btisystems.pronx.ems.core.model.IClassRegistry;
 import com.btisystems.pronx.ems.core.model.NetworkDevice;
 import com.btisystems.pronx.ems.core.snmp.ISnmpSession;
-import java.io.IOException;
-import java.util.Arrays;
 import org.apache.commons.lang.StringUtils;
 import org.onosproject.net.device.DefaultDeviceDescription;
 import org.onosproject.net.device.DeviceDescription;
 import org.slf4j.Logger;
-import static org.slf4j.LoggerFactory.getLogger;
 import org.snmp4j.smi.OID;
 
+import java.io.IOException;
+import java.util.Arrays;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
 /**
  * A agent-specific implementation supporting NET-SNMP agents.
  * @deprecated 1.5.0 Falcon, not compliant with ONOS SB and driver architecture.
@@ -57,7 +59,7 @@
                 // so cut it here until supported in prop displayer
                 String manufacturer = StringUtils.abbreviate(systemTree.getSysContact(), 20);
                 return new DefaultDeviceDescription(description.deviceUri(), description.type(), manufacturer,
-                        UNKNOWN, UNKNOWN, UNKNOWN, description.chassisId());
+                        UNKNOWN, UNKNOWN, UNKNOWN, description.chassisId(), description.annotations());
             }
         } catch (IOException ex) {
             log.error("Error reading details for device {}.", session.getAddress(), ex);
diff --git a/providers/snmp/device/src/main/java/org/onosproject/provider/snmp/device/impl/SnmpDeviceDescriptionProvider.java b/providers/snmp/device/src/main/java/org/onosproject/provider/snmp/device/impl/SnmpDeviceDescriptionProvider.java
index 5167a44..4b8fb34 100644
--- a/providers/snmp/device/src/main/java/org/onosproject/provider/snmp/device/impl/SnmpDeviceDescriptionProvider.java
+++ b/providers/snmp/device/src/main/java/org/onosproject/provider/snmp/device/impl/SnmpDeviceDescriptionProvider.java
@@ -32,5 +32,4 @@
      */
     @Deprecated
     DeviceDescription populateDescription(ISnmpSession session, DeviceDescription description);
-
 }
diff --git a/providers/snmp/device/src/main/java/org/onosproject/provider/snmp/device/impl/SnmpDeviceProvider.java b/providers/snmp/device/src/main/java/org/onosproject/provider/snmp/device/impl/SnmpDeviceProvider.java
index 316b3ba..56e3b6a 100644
--- a/providers/snmp/device/src/main/java/org/onosproject/provider/snmp/device/impl/SnmpDeviceProvider.java
+++ b/providers/snmp/device/src/main/java/org/onosproject/provider/snmp/device/impl/SnmpDeviceProvider.java
@@ -32,9 +32,13 @@
 import org.onlab.packet.ChassisId;
 import org.onosproject.cfg.ComponentConfigService;
 import org.onosproject.cluster.ClusterService;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.DefaultAnnotations;
 import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.MastershipRole;
+import org.onosproject.net.SparseAnnotations;
+import org.onosproject.net.behaviour.PortDiscovery;
 import org.onosproject.net.device.DefaultDeviceDescription;
 import org.onosproject.net.device.DeviceDescription;
 import org.onosproject.net.device.DeviceProvider;
@@ -120,6 +124,7 @@
         //TODO refactor, no hardcoding in provider, device information should be in drivers
         providers.put("1.3.6.1.4.1.18070.2.2", new Bti7000DeviceDescriptionProvider());
         providers.put("1.3.6.1.4.1.20408", new NetSnmpDeviceDescriptionProvider());
+        providers.put("1.3.6.1.4.562.73.6", new LumentumDeviceDescriptionProvider());
     }
 
     @Activate
@@ -341,9 +346,12 @@
                 DeviceId did = getDeviceId();
                 ChassisId cid = new ChassisId();
 
+                SparseAnnotations annotations = DefaultAnnotations.builder()
+                        .set(AnnotationKeys.PROTOCOL, SCHEME.toUpperCase())
+                        .build();
 
                 DeviceDescription desc = new DefaultDeviceDescription(
-                        did.uri(), Device.Type.OTHER, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, cid);
+                        did.uri(), Device.Type.OTHER, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, cid, annotations);
 
                 desc = populateDescriptionFromDevice(did, desc);
 
@@ -353,6 +361,18 @@
                 providerService.deviceConnected(did, desc);
                 log.info("Done with Device Info Creation on ONOS core. Device Info: "
                         + device.deviceInfo() + " " + did.uri().toString());
+
+                // Do port discovery if driver supports it
+                Device d = deviceService.getDevice(did);
+                if (d.is(PortDiscovery.class)) {
+                    PortDiscovery portConfig = d.as(PortDiscovery.class);
+                    if (portConfig != null) {
+                        providerService.updatePorts(did, portConfig.getPorts());
+                    }
+                } else {
+                    log.warn("No port discovery behaviour for device {}", did);
+                }
+
                 delay(EVENTINTERVAL);
             } catch (URISyntaxException e) {
                 log.error("Syntax Error while creating URI for the device: "
@@ -373,8 +393,8 @@
                 String ipAddress = deviceComponents[1];
                 String port = deviceComponents[2];
 
-           ISnmpConfiguration config = new V2cSnmpConfiguration();
-            config.setPort(Integer.parseInt(port));
+                ISnmpConfiguration config = new V2cSnmpConfiguration();
+                config.setPort(Integer.parseInt(port));
 
                 try (ISnmpSession session = sessionFactory.createSession(config, ipAddress)) {
                     // Each session will be auto-closed.
diff --git a/tools/package/etc/samples/org.onosproject.provider.snmp.device.impl.SnmpDeviceProvider.cfg b/tools/package/etc/samples/org.onosproject.provider.snmp.device.impl.SnmpDeviceProvider.cfg
index ac20d36..b31cfe6 100644
--- a/tools/package/etc/samples/org.onosproject.provider.snmp.device.impl.SnmpDeviceProvider.cfg
+++ b/tools/package/etc/samples/org.onosproject.provider.snmp.device.impl.SnmpDeviceProvider.cfg
@@ -1,3 +1,4 @@
+
 #
 # devices which support SNMP, these may support SNMP fault-management.
 # demo.snmplabs.com is a publically available SNMP agent-simulator accessible via the internet, see http://snmpsim.sourceforge.net/public-snmp-simulator.html