Added E-CORD metro path provisioner.

- add copyright
- use Commons Pair
- logs to help debugging

Change-Id: Ie7e59da18396d5edc68f2740e998ea1b9750c493
diff --git a/ecord/metro/pom.xml b/ecord/metro/pom.xml
new file mode 100644
index 0000000..c3f2dfb
--- /dev/null
+++ b/ecord/metro/pom.xml
@@ -0,0 +1,175 @@
+<?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">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <artifactId>onos-app-samples</artifactId>
+        <groupId>org.onosproject</groupId>
+        <version>1.6.0-SNAPSHOT</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>onos-app-ecord-metro</artifactId>
+    <version>1.6.0-SNAPSHOT</version>
+    <packaging>bundle</packaging>
+
+    <description>Enterprise CORD for metro network</description>
+    <url>http://onosproject.org</url>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <onos.version>1.6.0-SNAPSHOT</onos.version>
+        <onos.app.name>org.onosproject.ecord.metro</onos.app.name>
+        <!-- TODO App dependency not working? -->
+        <onos.app.requires>org.onosproject.incubator.rpc,org.onosproject.incubator.rpc.grpc</onos.app.requires>
+        <onos.app.origin>Open Networking Lab</onos.app.origin>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-api</artifactId>
+            <version>${onos.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-core-dist</artifactId>
+            <version>${onos.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.12</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-api</artifactId>
+            <version>${onos.version}</version>
+            <scope>test</scope>
+            <classifier>tests</classifier>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.scr.annotations</artifactId>
+            <version>1.9.12</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+            <version>4.3.1</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.karaf.shell</groupId>
+            <artifactId>org.apache.karaf.shell.console</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-cli</artifactId>
+            <version>${onos.version}</version>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>org.apache.karaf.tooling</groupId>
+                    <artifactId>karaf-maven-plugin</artifactId>
+                    <version>3.0.5</version>
+                    <extensions>true</extensions>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <version>2.5.3</version>
+                <extensions>true</extensions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>2.5.1</version>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-scr-plugin</artifactId>
+                <version>1.20.0</version>
+                <executions>
+                    <execution>
+                        <id>generate-scr-srcdescriptor</id>
+                        <goals>
+                            <goal>scr</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <supportedProjectTypes>
+                        <supportedProjectType>bundle</supportedProjectType>
+                        <supportedProjectType>war</supportedProjectType>
+                    </supportedProjectTypes>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.onosproject</groupId>
+                <artifactId>onos-maven-plugin</artifactId>
+                <version>1.5</version>
+                <executions>
+                    <execution>
+                        <id>cfg</id>
+                        <phase>generate-resources</phase>
+                        <goals>
+                            <goal>cfg</goal>
+                        </goals>
+                    </execution>
+                    <execution>
+                        <id>swagger</id>
+                        <phase>generate-sources</phase>
+                        <goals>
+                            <goal>swagger</goal>
+                        </goals>
+                    </execution>
+                    <execution>
+                        <id>app</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>app</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/ecord/metro/src/main/java/org/onosproject/ecord/metro/CeVlanConfig.java b/ecord/metro/src/main/java/org/onosproject/ecord/metro/CeVlanConfig.java
new file mode 100644
index 0000000..c2f4576
--- /dev/null
+++ b/ecord/metro/src/main/java/org/onosproject/ecord/metro/CeVlanConfig.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.ecord.metro;
+
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.config.Config;
+
+import java.util.Optional;
+
+/**
+ * Configuration information for edge connect point and corresponding VLAN tag.
+ */
+public class CeVlanConfig extends Config<ConnectPoint> {
+    public static final String CONFIG_KEY = "ceVlan";
+
+    public static final String CE_VLAN_TAG_KEY = "tag";
+
+    public Optional<VlanId> ceVlanId() {
+        String s = get(CE_VLAN_TAG_KEY, null);
+        if (s == null) {
+            return Optional.empty();
+        }
+        return Optional.of(VlanId.vlanId(Short.valueOf(s)));
+    }
+
+    public CeVlanConfig ceVlanId(VlanId vlanId) {
+        if (vlanId == null) {
+            return (CeVlanConfig) setOrClear(CE_VLAN_TAG_KEY, (String) null);
+        }
+        return (CeVlanConfig) setOrClear(CE_VLAN_TAG_KEY, String.valueOf(vlanId.toShort()));
+    }
+}
diff --git a/ecord/metro/src/main/java/org/onosproject/ecord/metro/CoPathProvisioner.java b/ecord/metro/src/main/java/org/onosproject/ecord/metro/CoPathProvisioner.java
new file mode 100644
index 0000000..6e7b168
--- /dev/null
+++ b/ecord/metro/src/main/java/org/onosproject/ecord/metro/CoPathProvisioner.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.ecord.metro;
+
+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.onlab.packet.VlanId;
+import org.onlab.util.Bandwidth;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Main component to configure CO.
+ */
+@Component(immediate = true)
+public class CoPathProvisioner {
+    protected static final Logger log = LoggerFactory.getLogger(CoPathProvisioner.class);
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    @Activate
+    protected void activate() {
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        log.info("Stopped");
+    }
+
+    public boolean setupCoConnectivity(DeviceId bigSw, ConnectPoint uni, ConnectPoint egress,
+                                       VlanId cevlan, Bandwidth bandwidth) {
+        // TODO implement
+        return false;
+    }
+
+}
diff --git a/ecord/metro/src/main/java/org/onosproject/ecord/metro/MetroConnectivity.java b/ecord/metro/src/main/java/org/onosproject/ecord/metro/MetroConnectivity.java
new file mode 100644
index 0000000..7b86cc9
--- /dev/null
+++ b/ecord/metro/src/main/java/org/onosproject/ecord/metro/MetroConnectivity.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.ecord.metro;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import org.onlab.util.Bandwidth;
+import org.onosproject.ecord.metro.api.MetroConnectivityId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.intent.IntentId;
+
+import java.time.Duration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Entity to store metro connectivity request and related information.
+ */
+public class MetroConnectivity {
+
+    private final MetroConnectivityId id;
+    private final List<Link> links;
+    private final Bandwidth requestBandwidth;
+    private final Duration requestLatency;
+
+    // Bandwidth capacity of optical layer
+    private Bandwidth opticalCapacity;
+
+    private final Set<PacketLinkRealizedByOptical> realizingLinks = new HashSet<>();
+
+    // TODO This IntentId is used only to reserve bandwidth resource.
+    //      If ResourceManager can accept app-defined ResourceConsumer (not IntentId),
+    //      this Intent should be replaced with MetroConnectivityId.
+    private IntentId intentId;
+
+    private State state = State.CREATED;
+
+    public enum State {
+        CREATED,
+        INSTALLING,
+        INSTALLED,
+        WITHDRAWING,
+        WITHDRAWN,
+        FAILED
+    }
+
+    public MetroConnectivity(MetroConnectivityId id, Path path, Bandwidth requestBandwidth,
+                             Duration requestLatency) {
+        this.id = id;
+        this.links = ImmutableList.copyOf(path.links());
+        this.requestBandwidth = requestBandwidth;
+        this.requestLatency = requestLatency;
+    }
+
+    public void setLinkEstablished(ConnectPoint src, ConnectPoint dst) {
+        realizingLinks.stream().filter(l -> l.equals(src, dst))
+                .findAny()
+                .ifPresent(l -> l.setEstablished(true));
+    }
+
+    public void setLinkRemoved(ConnectPoint src, ConnectPoint dst) {
+        realizingLinks.stream().filter(l -> l.equals(src, dst))
+                .findAny()
+                .ifPresent(l -> l.setEstablished(false));
+    }
+
+    public boolean isAllRealizingLinkEstablished() {
+        return realizingLinks.stream().allMatch(PacketLinkRealizedByOptical::isEstablished);
+    }
+
+    public boolean isAllRealizingLinkNotEstablished() {
+        return !realizingLinks.stream().anyMatch(PacketLinkRealizedByOptical::isEstablished);
+    }
+
+    public MetroConnectivityId id() {
+        return id;
+    }
+
+    public List<Link> links() {
+        return links;
+    }
+
+    public Bandwidth bandwidth() {
+        return requestBandwidth;
+    }
+
+    public Duration latency() {
+        return requestLatency;
+    }
+
+    public State state() {
+        return state;
+    }
+
+    public boolean state(State state) {
+        boolean valid = true;
+        // reject invalid state transition
+        switch (this.state) {
+            case CREATED:
+                valid = (state == State.INSTALLING || state == State.FAILED);
+                break;
+            case INSTALLING:
+                valid = (state == State.INSTALLED || state == State.FAILED);
+                break;
+            case INSTALLED:
+                valid = (state == State.WITHDRAWING || state == State.FAILED);
+                break;
+            case WITHDRAWING:
+                valid = (state == State.WITHDRAWN || state == State.FAILED);
+                break;
+            case FAILED:
+                valid = (state == State.INSTALLING || state == State.WITHDRAWING || state == State.FAILED);
+                break;
+            default:
+                break;
+        }
+
+        if (valid) {
+            this.state = state;
+        }
+
+        return valid;
+    }
+
+    public Bandwidth getOpticalCapacity() {
+        return opticalCapacity;
+    }
+
+    public void setOpticalCapacity(Bandwidth opticalCapacity) {
+        this.opticalCapacity = opticalCapacity;
+    }
+
+    public void addRealizingLink(PacketLinkRealizedByOptical link) {
+        checkNotNull(link);
+        realizingLinks.add(link);
+    }
+
+    public void removeRealizingLink(PacketLinkRealizedByOptical link) {
+        checkNotNull(link);
+        realizingLinks.remove(link);
+    }
+
+    public Set<PacketLinkRealizedByOptical> getRealizingLinks() {
+        return ImmutableSet.copyOf(realizingLinks);
+    }
+
+    public IntentId getIntentId() {
+        return intentId;
+    }
+
+    public void setIntentId(IntentId intentId) {
+        this.intentId = intentId;
+    }
+}
diff --git a/ecord/metro/src/main/java/org/onosproject/ecord/metro/MetroPathProvisioner.java b/ecord/metro/src/main/java/org/onosproject/ecord/metro/MetroPathProvisioner.java
new file mode 100644
index 0000000..7fa0092
--- /dev/null
+++ b/ecord/metro/src/main/java/org/onosproject/ecord/metro/MetroPathProvisioner.java
@@ -0,0 +1,726 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.ecord.metro;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+
+import org.apache.commons.lang3.tuple.Pair;
+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.onlab.packet.VlanId;
+import org.onlab.util.Bandwidth;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.ecord.metro.api.MetroConnectivityId;
+import org.onosproject.ecord.metro.api.MetroPathEvent;
+import org.onosproject.ecord.metro.api.MetroPathListener;
+import org.onosproject.ecord.metro.api.MetroPathService;
+import org.onosproject.event.AbstractListenerManager;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.CltSignalType;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.OchPort;
+import org.onosproject.net.OduCltPort;
+import org.onosproject.net.OduSignalType;
+import org.onosproject.net.Path;
+import org.onosproject.net.Port;
+import org.onosproject.net.config.ConfigFactory;
+import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.config.NetworkConfigListener;
+import org.onosproject.net.config.NetworkConfigRegistry;
+import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentEvent;
+import org.onosproject.net.intent.IntentId;
+import org.onosproject.net.intent.IntentListener;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.intent.Key;
+import org.onosproject.net.intent.OpticalCircuitIntent;
+import org.onosproject.net.intent.OpticalConnectivityIntent;
+import org.onosproject.net.intent.PointToPointIntent;
+import org.onosproject.net.link.LinkService;
+import org.onosproject.net.resource.BandwidthCapacity;
+import org.onosproject.net.resource.ContinuousResource;
+import org.onosproject.net.resource.Resource;
+import org.onosproject.net.resource.ResourceAllocation;
+import org.onosproject.net.resource.ResourceService;
+import org.onosproject.net.resource.Resources;
+import org.onosproject.net.topology.LinkWeight;
+import org.onosproject.net.topology.PathService;
+import org.onosproject.net.topology.TopologyEdge;
+import org.onosproject.store.service.AtomicCounter;
+import org.onosproject.store.service.StorageService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.time.Duration;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.config.basics.SubjectFactories.CONNECT_POINT_SUBJECT_FACTORY;
+
+/**
+ * Main component to configure metro area connectivity.
+ */
+@Service
+@Component(immediate = true)
+public class MetroPathProvisioner
+        extends AbstractListenerManager<MetroPathEvent, MetroPathListener>
+        implements MetroPathService {
+    protected static final Logger log = LoggerFactory.getLogger(MetroPathProvisioner.class);
+
+    private static final String METRO_CONNECTIVITY_ID_COUNTER = "ecord-metro-connectivity-id";
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentService intentService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PathService pathService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected LinkService linkService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected MastershipService mastershipService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ClusterService clusterService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected StorageService storageService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected NetworkConfigRegistry cfgRegistry;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected NetworkConfigService networkConfigService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ResourceService resourceService;
+
+    private final List<ConfigFactory<?, ?>> factories = ImmutableList.of(
+            new ConfigFactory<ConnectPoint, CeVlanConfig>(CONNECT_POINT_SUBJECT_FACTORY,
+                    CeVlanConfig.class, CeVlanConfig.CONFIG_KEY) {
+                @Override
+                public CeVlanConfig createConfig() {
+                    return new CeVlanConfig();
+                }
+            });
+
+    private ApplicationId appId;
+
+    private AtomicCounter idCounter;
+
+    private IntentListener intentListener = new InternalIntentListener();
+    private NetworkConfigListener netcfgListener = new InternalNetworkConfigListener();
+
+    private Map<PacketLinkRealizedByOptical, MetroConnectivity> linkPathMap = new ConcurrentHashMap<>();
+
+    // TODO this should be stored to distributed store
+    private Map<MetroConnectivityId, MetroConnectivity> connectivities = new ConcurrentHashMap<>();
+
+    // TODO this should be stored to distributed store
+    // Map of cross connect link and installed path which uses the link
+    private Set<Link> usedCrossConnectLinks = Sets.newConcurrentHashSet();
+
+    // Map of connect points and corresponding VLAN tag
+    private Map<ConnectPoint, VlanId> portVlanMap = new ConcurrentHashMap<>();
+
+    @Activate
+    protected void activate() {
+        factories.forEach(cfgRegistry::registerConfigFactory);
+        appId = coreService.registerApplication("org.onosproject.ecord.metro");
+
+        idCounter = storageService.atomicCounterBuilder()
+                .withName(METRO_CONNECTIVITY_ID_COUNTER)
+                .withMeteringDisabled()
+                .build()
+                .asAtomicCounter();
+
+        eventDispatcher.addSink(MetroPathEvent.class, listenerRegistry);
+        intentService.addListener(intentListener);
+        networkConfigService.addListener(netcfgListener);
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        networkConfigService.removeListener(netcfgListener);
+        intentService.removeListener(intentListener);
+
+        factories.forEach(cfgRegistry::unregisterConfigFactory);
+
+        log.info("Stopped");
+    }
+
+    @Override
+    public MetroConnectivityId setupConnectivity(ConnectPoint ingress, ConnectPoint egress,
+                                              Bandwidth bandwidth, Duration latency) {
+        checkNotNull(ingress);
+        checkNotNull(egress);
+        log.info("setupConnectivity({}, {}, {}, {})", ingress, egress, bandwidth, latency);
+
+        bandwidth = (bandwidth == null) ? Bandwidth.bps(0) : bandwidth;
+
+        Set<Path> paths = pathService.getPaths(ingress.deviceId(), egress.deviceId(),
+                new BandwidthLinkWeight(bandwidth));
+        if (paths.isEmpty()) {
+            log.warn("Unable to find multi-layer path.");
+            return null;
+        }
+
+        // Search path with available cross connect points
+        for (Path path : paths) {
+            MetroConnectivityId id = setupPath(path, bandwidth, latency);
+            if (id != null) {
+                log.info("Assigned MetroConnectivityId: {}", id);
+                return id;
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    public MetroConnectivityId setupPath(Path path, Bandwidth bandwidth, Duration latency) {
+        checkNotNull(path);
+        log.info("setupPath({}, {}, {})", path, bandwidth, latency);
+
+        // validate optical path
+        List<Pair<ConnectPoint, ConnectPoint>> xcPointPairs = getCrossConnectPoints(path);
+        if (!checkXcPoints(xcPointPairs)) {
+            // Can't setup path if cross connect points are mismatched
+            return null;
+        }
+
+        MetroConnectivity connectivity = createConnectivity(path, bandwidth, latency);
+
+        // create intents from cross connect points and set connectivity information
+        List<Intent> intents = createIntents(xcPointPairs, connectivity);
+
+        // store cross connect port usage
+        path.links().stream().filter(this::isCrossConnectLink)
+                .forEach(usedCrossConnectLinks::add);
+
+        // Submit the intents
+        for (Intent i : intents) {
+            intentService.submit(i);
+            log.debug("Submitted an intent: {}", i);
+        }
+
+        return connectivity.id();
+    }
+
+    private MetroConnectivity createConnectivity(Path path, Bandwidth bandwidth, Duration latency) {
+        MetroConnectivityId id = MetroConnectivityId.valueOf(idCounter.getAndIncrement());
+        MetroConnectivity connectivity = new MetroConnectivity(id, path, bandwidth, latency);
+
+        ConnectPoint ingress = path.src();
+        ConnectPoint egress = path.dst();
+
+        Intent pktIntent = PointToPointIntent.builder()
+                .appId(appId)
+                .ingressPoint(ingress)
+                .egressPoint(egress)
+                .key(Key.of(id.value(), appId))
+                .build();
+
+        connectivity.setIntentId(pktIntent.id());
+
+        // store connectivity information
+        connectivities.put(connectivity.id(), connectivity);
+
+        return connectivity;
+    }
+
+    @Override
+    public boolean removeConnectivity(MetroConnectivityId id) {
+        log.info("removeConnectivity({})", id);
+        MetroConnectivity connectivity = connectivities.remove(id);
+
+        // TODO withdraw intent only if all of connectivities that use the optical path are withdrawn
+        connectivity.getRealizingLinks().forEach(l -> {
+            Intent intent = intentService.getIntent(l.realizingIntentKey());
+            intentService.withdraw(intent);
+        });
+
+        return connectivity != null;
+    }
+
+    @Override
+    public List<Link> getPath(MetroConnectivityId id) {
+        MetroConnectivity connectivity = connectivities.get(id);
+        if (connectivity == null) {
+            return null;
+        }
+
+        return ImmutableList.copyOf(connectivity.links());
+    }
+
+    @Override
+    public Optional<VlanId> getVlanId(MetroConnectivityId id) {
+        checkNotNull(id);
+
+        MetroConnectivity connectivity = connectivities.get(id);
+        if (connectivity != null) {
+            return getVlanTag(connectivity.links());
+        }
+
+        return Optional.empty();
+    }
+
+    /**
+     * Returns list of (optical, packet) pairs of cross connection points of missing optical path sections.
+     *
+     * Scans the given multi-layer path and looks for sections that use cross connect links.
+     * The ingress and egress points in the optical layer are combined to the packet layer ports, and
+     * are returned in a list.
+     *
+     * @param path the multi-layer path
+     * @return List of cross connect link's (packet port, optical port) pairs
+     */
+    private List<Pair<ConnectPoint, ConnectPoint>> getCrossConnectPoints(Path path) {
+        List<Pair<ConnectPoint, ConnectPoint>> xcPointPairs = new LinkedList<>();
+        boolean scanning = false;
+
+        for (Link link : path.links()) {
+            if (!isCrossConnectLink(link)) {
+                continue;
+            }
+
+            if (scanning) {
+                // link.src() is packet, link.dst() is optical
+                xcPointPairs.add(Pair.of(checkNotNull(link.src()), checkNotNull(link.dst())));
+                scanning = false;
+            } else {
+                // link.src() is optical, link.dst() is packet
+                xcPointPairs.add(Pair.of(checkNotNull(link.dst()), checkNotNull(link.src())));
+                scanning = true;
+            }
+        }
+
+        return xcPointPairs;
+    }
+
+    /**
+     * Checks if optical cross connect points are of same type.
+     *
+     * @param xcPointPairs list of cross connection points
+     * @return true if cross connect point pairs are of same type, false otherwise
+     */
+    private boolean checkXcPoints(List<Pair<ConnectPoint, ConnectPoint>> xcPointPairs) {
+        checkArgument(xcPointPairs.size() % 2 == 0);
+
+        Iterator<Pair<ConnectPoint, ConnectPoint>> itr = xcPointPairs.iterator();
+
+        while (itr.hasNext()) {
+            // checkArgument at start ensures we'll always have pairs of connect points
+            Pair<ConnectPoint, ConnectPoint> src = itr.next();
+            Pair<ConnectPoint, ConnectPoint> dst = itr.next();
+
+            Device.Type srcType = deviceService.getDevice(src.getKey().deviceId()).type();
+            Device.Type dstType = deviceService.getDevice(dst.getKey().deviceId()).type();
+
+            // Only support connections between identical port types
+            if (srcType != dstType) {
+                log.warn("Unsupported mix of cross connect points");
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Scans the list of cross connection points and returns a list of optical connectivity intents.
+     * During the process, store intent ID and its realizing link information to given connectivity object.
+     *
+     * @param xcPointPairs list of cross connection points
+     * @return list of optical connectivity intents
+     */
+    private List<Intent> createIntents(List<Pair<ConnectPoint, ConnectPoint>> xcPointPairs,
+                                       MetroConnectivity connectivity) {
+        checkArgument(xcPointPairs.size() % 2 == 0);
+
+        List<Intent> intents = new LinkedList<>();
+        Iterator<Pair<ConnectPoint, ConnectPoint>> itr = xcPointPairs.iterator();
+
+        while (itr.hasNext()) {
+            // checkArgument at start ensures we'll always have pairs of connect points
+            Pair<ConnectPoint, ConnectPoint> src = itr.next();
+            Pair<ConnectPoint, ConnectPoint> dst = itr.next();
+
+            Port srcPort = deviceService.getPort(src.getKey().deviceId(), src.getKey().port());
+            Port dstPort = deviceService.getPort(dst.getKey().deviceId(), dst.getKey().port());
+
+            if (srcPort instanceof OduCltPort && dstPort instanceof OduCltPort) {
+                // Create OTN circuit
+                OpticalCircuitIntent circuitIntent = OpticalCircuitIntent.builder()
+                        .appId(appId)
+                        .src(src.getKey())
+                        .dst(dst.getKey())
+                        .signalType(CltSignalType.CLT_10GBE)
+                        .bidirectional(true)
+                        .build();
+                intents.add(circuitIntent);
+                PacketLinkRealizedByOptical pLink = PacketLinkRealizedByOptical.create(src.getValue(), dst.getValue(),
+                        circuitIntent);
+                connectivity.addRealizingLink(pLink);
+                linkPathMap.put(pLink, connectivity);
+            } else if (srcPort instanceof OchPort && dstPort instanceof OchPort) {
+                // Create lightpath
+                // FIXME: hardcoded ODU signal type
+                OpticalConnectivityIntent opticalIntent = OpticalConnectivityIntent.builder()
+                        .appId(appId)
+                        .src(src.getKey())
+                        .dst(dst.getKey())
+                        .signalType(OduSignalType.ODU4)
+                        .bidirectional(true)
+                        .build();
+                intents.add(opticalIntent);
+                PacketLinkRealizedByOptical pLink = PacketLinkRealizedByOptical.create(src.getValue(), dst.getValue(),
+                        opticalIntent);
+                connectivity.addRealizingLink(pLink);
+                linkPathMap.put(pLink, connectivity);
+            } else {
+                log.warn("Unsupported cross connect point types {} {}", srcPort.type(), dstPort.type());
+                return Collections.emptyList();
+            }
+        }
+
+        return intents;
+    }
+
+    /**
+     * Verifies if given device type is in packet layer, i.e., ROADM, OTN or ROADM_OTN device.
+     *
+     * @param type device type
+     * @return true if in packet layer, false otherwise
+     */
+    private boolean isPacketLayer(Device.Type type) {
+        return type == Device.Type.SWITCH || type == Device.Type.ROUTER || type == Device.Type.VIRTUAL;
+    }
+
+    /**
+     * Verifies if given device type is in packet layer, i.e., switch or router device.
+     *
+     * @param type device type
+     * @return true if in packet layer, false otherwise
+     */
+    private boolean isTransportLayer(Device.Type type) {
+        return type == Device.Type.ROADM || type == Device.Type.OTN || type == Device.Type.ROADM_OTN;
+    }
+
+    /**
+     * Verifies if given link forms a cross-connection between packet and optical layer.
+     *
+     * @param link the link
+     * @return true if the link is a cross-connect link, false otherwise
+     */
+    private boolean isCrossConnectLink(Link link) {
+        if (link.type() != Link.Type.OPTICAL) {
+            return false;
+        }
+
+        Device.Type src = deviceService.getDevice(link.src().deviceId()).type();
+        Device.Type dst = deviceService.getDevice(link.dst().deviceId()).type();
+
+        return src != dst &&
+                ((isPacketLayer(src) && isTransportLayer(dst)) || (isPacketLayer(dst) && isTransportLayer(src)));
+    }
+
+    /**
+     * Updates bandwidth resource of given connect point.
+     * @param cp Connect point
+     * @param bandwidth New bandwidth
+     */
+    private void updatePortBandwidth(ConnectPoint cp, Bandwidth bandwidth) {
+        NodeId localNode = clusterService.getLocalNode().id();
+        NodeId sourceMaster = mastershipService.getMasterFor(cp.deviceId());
+        if (localNode.equals(sourceMaster)) {
+            log.debug("update Port {} Bandwidth {}", cp, bandwidth);
+            BandwidthCapacity bwCapacity = networkConfigService.addConfig(cp, BandwidthCapacity.class);
+            bwCapacity.capacity(bandwidth).apply();
+        }
+    }
+
+    /**
+     * Updates usage information of bandwidth based on connectivity which is established.
+     * @param connectivity Metro connectivity
+     */
+    private void updateBandwidthUsage(MetroConnectivity connectivity) {
+        IntentId intentId = connectivity.getIntentId();
+        if (intentId == null) {
+            return;
+        }
+
+        List<Link> links = connectivity.links();
+
+        List<Resource> resources = links.stream().flatMap(l -> Stream.of(l.src(), l.dst()))
+                .filter(cp -> !isTransportLayer(deviceService.getDevice(cp.deviceId()).type()))
+                .map(cp -> Resources.continuous(cp.deviceId(), cp.port(),
+                        Bandwidth.class).resource(connectivity.bandwidth().bps()))
+                .collect(Collectors.toList());
+
+        log.debug("allocating bandwidth for {} : {}", connectivity.getIntentId(), resources);
+        List<ResourceAllocation> allocations = resourceService.allocate(intentId, resources);
+        if (allocations.isEmpty()) {
+            log.warn("Failed to allocate bandwidth {} to {}",
+                    connectivity.bandwidth().bps(), resources);
+            // TODO any recovery?
+        }
+        log.debug("Done allocating bandwidth for {}", connectivity.getIntentId());
+    }
+
+    /**
+     * Release bandwidth allocated by given connectivity.
+     * @param connectivity Metro connectivity
+     */
+    private void releaseBandwidthUsage(MetroConnectivity connectivity) {
+        IntentId intentId = connectivity.getIntentId();
+        if (intentId == null) {
+            return;
+        }
+
+        log.debug("releasing bandwidth allocated to {}", connectivity.getIntentId());
+        if (!resourceService.release(connectivity.getIntentId())) {
+            log.warn("Failed to release bandwidth allocated to {}",
+                    connectivity.getIntentId());
+            // TODO any recovery?
+        }
+        log.debug("DONE releasing bandwidth for {}", connectivity.getIntentId());
+    }
+
+    /**
+     * Returns VLAN tag assigned to given path.
+     * @param links Path
+     * @return VLAN tag if found any. empty if not found.
+     */
+    private Optional<VlanId> getVlanTag(List<Link> links) {
+        Optional<ConnectPoint> edge = links.stream().flatMap(l -> Stream.of(l.src(), l.dst()))
+                .filter(portVlanMap::containsKey)
+                .findAny();
+
+        if (edge.isPresent()) {
+            return Optional.of(portVlanMap.get(edge.get()));
+        }
+        return Optional.empty();
+    }
+
+    private class BandwidthLinkWeight implements LinkWeight {
+        private Bandwidth bandwidth = null;
+
+        public BandwidthLinkWeight(Bandwidth bandwidth) {
+            this.bandwidth = bandwidth;
+        }
+
+        @Override
+        public double weight(TopologyEdge edge) {
+            Link l = edge.link();
+
+            // Ignore inactive links
+            if (l.state() == Link.State.INACTIVE) {
+                return -1.0;
+            }
+
+            // Ignore cross connect links with used ports
+            if (isCrossConnectLink(l) && usedCrossConnectLinks.contains(l)) {
+                return -1.0;
+            }
+
+            // Check availability of bandwidth
+            if (bandwidth != null) {
+                if (hasEnoughBandwidth(l.src()) && hasEnoughBandwidth(l.dst())) {
+                    return 1.0;
+                } else {
+                    return -1.0;
+                }
+            } else {
+                // TODO needs to differentiate optical and packet?
+                if (l.type() == Link.Type.OPTICAL) {
+                    // Transport links
+                    return 1.0;
+                } else {
+                    // Packet links
+                    return 1.0;
+                }
+            }
+        }
+
+        private boolean hasEnoughBandwidth(ConnectPoint cp) {
+            if (cp.elementId() instanceof DeviceId) {
+                Device.Type type = deviceService.getDevice(cp.deviceId()).type();
+                if (isTransportLayer(type)) {
+                    // Optical ports are assumed to have enough bandwidth
+                    // TODO should look up physical limit?
+                    return true;
+                }
+
+                ContinuousResource resource = Resources.continuous(cp.deviceId(), cp.port(), Bandwidth.class)
+                        .resource(bandwidth.bps());
+
+                return resourceService.isAvailable(resource);
+            }
+            return false;
+        }
+    }
+
+
+    public class InternalIntentListener implements IntentListener {
+        @Override
+        public void event(IntentEvent event) {
+            switch (event.type()) {
+                case INSTALLED:
+                    log.info("Intent {} installed.", event.subject());
+                    updateCrossConnectLink(event.subject());
+                    break;
+                case WITHDRAWN:
+                    log.info("Intent {} withdrawn.", event.subject());
+                    removeCrossConnectLinks(event.subject());
+                    break;
+                case FAILED:
+                    log.info("Intent {} failed.", event.subject());
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        private void updateCrossConnectLink(Intent intent) {
+            linkPathMap.entrySet().stream()
+                    .filter(e -> e.getKey().realizingIntentKey().equals(intent.key()))
+                    .forEach(e -> {
+                        ConnectPoint packetSrc = e.getKey().src();
+                        ConnectPoint packetDst = e.getKey().dst();
+                        Bandwidth bw = e.getKey().bandwidth();
+                        // Updates bandwidth of packet ports
+                        updatePortBandwidth(packetSrc, bw);
+                        updatePortBandwidth(packetDst, bw);
+
+                        MetroConnectivity connectivity = e.getValue();
+                        connectivity.setLinkEstablished(packetSrc, packetDst);
+
+                        if (e.getValue().isAllRealizingLinkEstablished()) {
+                            updateBandwidthUsage(connectivity);
+
+                            // Notifies listeners if all links are established
+                            post(new MetroPathEvent(MetroPathEvent.Type.PATH_INSTALLED, e.getValue().id()));
+                        }
+                    });
+        }
+
+        private void removeCrossConnectLinks(Intent intent) {
+            ConnectPoint src, dst;
+
+            if (intent instanceof OpticalCircuitIntent) {
+                OpticalCircuitIntent circuit = (OpticalCircuitIntent) intent;
+                src = circuit.getSrc();
+                dst = circuit.getDst();
+            } else if (intent instanceof OpticalConnectivityIntent) {
+                OpticalConnectivityIntent conn = (OpticalConnectivityIntent) intent;
+                src = conn.getSrc();
+                dst = conn.getDst();
+            } else {
+                return;
+            }
+
+            removeXcLinkUsage(src);
+            removeXcLinkUsage(dst);
+
+            // Set bandwidth of 0 to cross connect ports
+            Bandwidth bw = Bandwidth.bps(0);
+            linkPathMap.entrySet().stream()
+                    .filter(e -> e.getKey().realizingIntentKey().equals(intent.key()))
+                    .forEach(e -> {
+                        ConnectPoint packetSrc = e.getKey().src();
+                        ConnectPoint packetDst = e.getKey().dst();
+                        // Updates bandwidth of packet ports
+                        updatePortBandwidth(packetSrc, bw);
+                        updatePortBandwidth(packetDst, bw);
+                        MetroConnectivity connectivity = e.getValue();
+                        connectivity.setLinkRemoved(packetSrc, packetDst);
+
+                        // Notifies listeners if all links are gone
+                        if (e.getValue().isAllRealizingLinkNotEstablished()) {
+                            releaseBandwidthUsage(connectivity);
+                            post(new MetroPathEvent(MetroPathEvent.Type.PATH_REMOVED, e.getValue().id()));
+                        }
+                    });
+        }
+
+        private void removeXcLinkUsage(ConnectPoint cp) {
+            Optional<Link> link = linkService.getLinks(cp).stream()
+                    .filter(usedCrossConnectLinks::contains)
+                    .findAny();
+
+            if (!link.isPresent()) {
+                log.warn("Cross connect point {} has no cross connect link.", cp);
+                return;
+            }
+
+            usedCrossConnectLinks.remove(link.get());
+        }
+    }
+
+    private class InternalNetworkConfigListener implements NetworkConfigListener {
+
+        @Override
+        public void event(NetworkConfigEvent event) {
+            if (event.configClass() != CeVlanConfig.class) {
+                return;
+            }
+
+            ConnectPoint cp = (ConnectPoint) event.subject();
+            CeVlanConfig config = networkConfigService.getConfig(cp, CeVlanConfig.class);
+            if (config != null && config.ceVlanId().isPresent()) {
+                log.info("VLAN tag {} is assigned to port {}", config.ceVlanId().get(), cp);
+                portVlanMap.put(cp, config.ceVlanId().get());
+            } else {
+                portVlanMap.remove(cp);
+            }
+        }
+
+    }
+}
+
diff --git a/ecord/metro/src/main/java/org/onosproject/ecord/metro/PacketLinkRealizedByOptical.java b/ecord/metro/src/main/java/org/onosproject/ecord/metro/PacketLinkRealizedByOptical.java
new file mode 100644
index 0000000..9159b96
--- /dev/null
+++ b/ecord/metro/src/main/java/org/onosproject/ecord/metro/PacketLinkRealizedByOptical.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.ecord.metro;
+
+import org.onlab.util.Bandwidth;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.intent.Key;
+import org.onosproject.net.intent.OpticalCircuitIntent;
+import org.onosproject.net.intent.OpticalConnectivityIntent;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Entity to represent packet link realized by optical intent.
+ */
+public class PacketLinkRealizedByOptical {
+    private final ConnectPoint src, dst;
+    private final Bandwidth bandwidth;
+    // TODO should be list of Intent Key?
+    private final Key realizingIntentKey;
+    // established=false represents that this (packet) link is expected to be
+    // discovered after underlying (optical) path has been provisioned.
+    private boolean established;
+
+    public PacketLinkRealizedByOptical(ConnectPoint src, ConnectPoint dst,
+                                       Key realizingIntentKey, Bandwidth bandwidth) {
+        this.src = src;
+        this.dst = dst;
+        this.realizingIntentKey = realizingIntentKey;
+        this.bandwidth = bandwidth;
+        this.established = false;
+    }
+
+    public static PacketLinkRealizedByOptical create(ConnectPoint src, ConnectPoint dst,
+                                                     OpticalCircuitIntent intent) {
+        checkNotNull(src);
+        checkNotNull(dst);
+        checkNotNull(intent);
+
+        long rate = intent.getSignalType().bitRate();
+        return new PacketLinkRealizedByOptical(src, dst, intent.key(), Bandwidth.bps(rate));
+    }
+
+    public static PacketLinkRealizedByOptical create(ConnectPoint src, ConnectPoint dst,
+                                                     OpticalConnectivityIntent intent) {
+        checkNotNull(src);
+        checkNotNull(dst);
+        checkNotNull(intent);
+
+        long rate = intent.getSignalType().bitRate();
+        return new PacketLinkRealizedByOptical(src, dst, intent.key(), Bandwidth.bps(rate));
+    }
+
+    public ConnectPoint src() {
+        return src;
+    }
+
+    public ConnectPoint dst() {
+        return dst;
+    }
+
+    public Bandwidth bandwidth() {
+        return bandwidth;
+    }
+
+    public Key realizingIntentKey() {
+        return realizingIntentKey;
+    }
+
+    public boolean isEstablished() {
+        return established;
+    }
+
+    public void setEstablished(boolean established) {
+        this.established = established;
+    }
+
+    public boolean equals(ConnectPoint src, ConnectPoint dst) {
+        return (this.src.equals(src) && this.dst.equals(dst));
+    }
+
+}
diff --git a/ecord/metro/src/main/java/org/onosproject/ecord/metro/api/MetroConnectivityId.java b/ecord/metro/src/main/java/org/onosproject/ecord/metro/api/MetroConnectivityId.java
new file mode 100644
index 0000000..6c02464
--- /dev/null
+++ b/ecord/metro/src/main/java/org/onosproject/ecord/metro/api/MetroConnectivityId.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.ecord.metro.api;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * ID for metro connectivity request.
+ */
+public final class MetroConnectivityId {
+    private final long value;
+
+    public static MetroConnectivityId valueOf(long value) {
+        return new MetroConnectivityId(value);
+    }
+
+    MetroConnectivityId(long value) {
+        this.value = value;
+    }
+
+    public long value() {
+        return value;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        MetroConnectivityId that = (MetroConnectivityId) o;
+
+        return value == that.value;
+
+    }
+
+    @Override
+    public int hashCode() {
+        return (int) (value ^ (value >>> 32));
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("value", value)
+                .toString();
+    }
+}
diff --git a/ecord/metro/src/main/java/org/onosproject/ecord/metro/api/MetroPathEvent.java b/ecord/metro/src/main/java/org/onosproject/ecord/metro/api/MetroPathEvent.java
new file mode 100644
index 0000000..4a5f38a
--- /dev/null
+++ b/ecord/metro/src/main/java/org/onosproject/ecord/metro/api/MetroPathEvent.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.ecord.metro.api;
+
+import org.onosproject.event.AbstractEvent;
+
+/**
+ * Event related to metro domain path setup.
+ */
+public class MetroPathEvent extends AbstractEvent<MetroPathEvent.Type, MetroConnectivityId> {
+    public enum Type {
+        PATH_INSTALLED,
+        PATH_REMOVED
+    }
+
+    public MetroPathEvent(Type type, MetroConnectivityId subject) {
+        super(type, subject);
+    }
+
+}
diff --git a/ecord/metro/src/main/java/org/onosproject/ecord/metro/api/MetroPathListener.java b/ecord/metro/src/main/java/org/onosproject/ecord/metro/api/MetroPathListener.java
new file mode 100644
index 0000000..9d8e894
--- /dev/null
+++ b/ecord/metro/src/main/java/org/onosproject/ecord/metro/api/MetroPathListener.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.ecord.metro.api;
+
+import org.onosproject.event.EventListener;
+
+/**
+ * Entity capable of receiving metro path related events.
+ */
+public interface MetroPathListener extends EventListener<MetroPathEvent> {
+}
diff --git a/ecord/metro/src/main/java/org/onosproject/ecord/metro/api/MetroPathService.java b/ecord/metro/src/main/java/org/onosproject/ecord/metro/api/MetroPathService.java
new file mode 100644
index 0000000..2377b7f
--- /dev/null
+++ b/ecord/metro/src/main/java/org/onosproject/ecord/metro/api/MetroPathService.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.ecord.metro.api;
+
+import com.google.common.annotations.Beta;
+import org.onlab.packet.VlanId;
+import org.onlab.util.Bandwidth;
+import org.onosproject.event.ListenerService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Service to setup metro domain connectivity.
+ */
+@Beta
+public interface MetroPathService extends ListenerService<MetroPathEvent, MetroPathListener> {
+
+    /**
+     * Calculates optical path between connect points and sets up connectivity.
+     *
+     * @param ingress   ingress port
+     * @param egress    egress port
+     * @param bandwidth required bandwidth. No bandwidth is assured if null.
+     * @param latency   required latency. No latency is assured if null.
+     * @return ID of created connectivity if successful. null otherwise.
+     */
+    MetroConnectivityId setupConnectivity(ConnectPoint ingress, ConnectPoint egress,
+                                       Bandwidth bandwidth, Duration latency);
+
+    /**
+     * Sets up connectivity along given optical path.
+     *
+     * @param path      path along which connectivity will be set up
+     * @param bandwidth required bandwidth. No bandwidth is assured if null.
+     * @param latency   required latency. No latency is assured if null.
+     * @return true if successful. false otherwise.
+     */
+    MetroConnectivityId setupPath(Path path, Bandwidth bandwidth, Duration latency);
+
+    /**
+     * Removes connectivity with given ID.
+     *
+     * @param id ID of connectivity
+     * @return true if succeed. false if failed.
+     */
+    boolean removeConnectivity(MetroConnectivityId id);
+
+    /**
+     * Returns path assigned to given ID.
+     * @param id ID of connectivity
+     * @return list of link that compose a path. null if ID is invalid.
+     */
+    List<Link> getPath(MetroConnectivityId id);
+
+    // FIXME This is for ONS2016 demo use only
+    Optional<VlanId> getVlanId(MetroConnectivityId id);
+}
diff --git a/ecord/metro/src/main/java/org/onosproject/ecord/metro/api/package-info.java b/ecord/metro/src/main/java/org/onosproject/ecord/metro/api/package-info.java
new file mode 100644
index 0000000..866eb9b
--- /dev/null
+++ b/ecord/metro/src/main/java/org/onosproject/ecord/metro/api/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * API to access E-CORD metro service.
+ */
+package org.onosproject.ecord.metro.api;
\ No newline at end of file
diff --git a/ecord/metro/src/main/java/org/onosproject/ecord/metro/cli/AddMetroConnectivityCommand.java b/ecord/metro/src/main/java/org/onosproject/ecord/metro/cli/AddMetroConnectivityCommand.java
new file mode 100644
index 0000000..05fc378
--- /dev/null
+++ b/ecord/metro/src/main/java/org/onosproject/ecord/metro/cli/AddMetroConnectivityCommand.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.ecord.metro.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.packet.VlanId;
+import org.onlab.util.Bandwidth;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.ecord.metro.api.MetroConnectivityId;
+import org.onosproject.ecord.metro.api.MetroPathService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+
+import java.util.Optional;
+
+@Command(scope = "onos", name = "add-metro-connectivity",
+        description = "Configure metro domain connectivity")
+public class AddMetroConnectivityCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "ingress", description = "Ingress connect point",
+            required = true, multiValued = false)
+    String ingressStr = null;
+
+    @Argument(index = 1, name = "egress", description = "Egress connect point",
+            required = true, multiValued = false)
+    String egressStr = null;
+
+    @Argument(index = 2, name = "bandwidth", description = "Bandwidth",
+            required = false, multiValued = false)
+    String bandwidthStr = null;
+
+    @Argument(index = 3, name = "latency", description = "Latency",
+            required = true, multiValued = false)
+    String latencyStr = null;
+
+
+    @Override
+    protected void execute() {
+        MetroPathService metroPathService = get(MetroPathService.class);
+
+        ConnectPoint ingress = readConnectPoint(ingressStr);
+        ConnectPoint egress = readConnectPoint(egressStr);
+        if (ingress == null || egress == null) {
+            print("Invalid connect points: %s, %s", ingressStr, egressStr);
+            return;
+        }
+
+        Bandwidth bandwidth = (bandwidthStr == null || bandwidthStr.isEmpty()) ? null :
+                Bandwidth.bps(Long.valueOf(bandwidthStr));
+
+        print("Trying to setup connectivity between %s and %s.", ingress, egress);
+        MetroConnectivityId id = metroPathService.setupConnectivity(ingress, egress, bandwidth, null);
+        if (id == null) {
+            print("Failed.");
+            return;
+        }
+        print("Metro path ID : %s", id.value());
+        Optional<VlanId> vlanId = metroPathService.getVlanId(id);
+        if (vlanId.isPresent()) {
+            print("  -- VLAN ID %d was assigned.", vlanId.get().toShort());
+        } else {
+            print("  -- No VLAN ID was assigned.");
+        }
+    }
+
+    private ConnectPoint readConnectPoint(String str) {
+        String[] strings = str.split("/");
+        if (strings.length != 2) {
+            return null;
+        }
+
+        DeviceId devId = DeviceId.deviceId(strings[0]);
+        PortNumber port = PortNumber.portNumber(strings[1]);
+
+        return new ConnectPoint(devId, port);
+    }
+
+}
diff --git a/ecord/metro/src/main/java/org/onosproject/ecord/metro/cli/RemoveMetroConnectivityCommand.java b/ecord/metro/src/main/java/org/onosproject/ecord/metro/cli/RemoveMetroConnectivityCommand.java
new file mode 100644
index 0000000..ed6d2c8
--- /dev/null
+++ b/ecord/metro/src/main/java/org/onosproject/ecord/metro/cli/RemoveMetroConnectivityCommand.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.ecord.metro.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.ecord.metro.api.MetroConnectivityId;
+import org.onosproject.ecord.metro.api.MetroPathService;
+
+@Command(scope = "onos", name = "remove-metro-connectivity",
+        description = "Remove metro domain connectivity")
+public class RemoveMetroConnectivityCommand extends AbstractShellCommand {
+    @Argument(index = 0, name = "id", description = "ID of metro connectivity",
+            required = true, multiValued = false)
+    String idStr = null;
+
+    @Override
+    protected void execute() {
+        MetroPathService metroPathService = get(MetroPathService.class);
+
+        MetroConnectivityId id = MetroConnectivityId.valueOf(Long.valueOf(idStr));
+
+        print("Trying to remove connectivity with id %s.", idStr);
+        if (metroPathService.removeConnectivity(id)) {
+            print(" -- success");
+        } else {
+            print(" -- failed");
+        }
+
+    }
+}
diff --git a/ecord/metro/src/main/java/org/onosproject/ecord/metro/cli/package-info.java b/ecord/metro/src/main/java/org/onosproject/ecord/metro/cli/package-info.java
new file mode 100644
index 0000000..8907956
--- /dev/null
+++ b/ecord/metro/src/main/java/org/onosproject/ecord/metro/cli/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * CLI to control E-CORD metro service.
+ */
+package org.onosproject.ecord.metro.cli;
\ No newline at end of file
diff --git a/ecord/metro/src/main/java/org/onosproject/ecord/metro/package-info.java b/ecord/metro/src/main/java/org/onosproject/ecord/metro/package-info.java
new file mode 100644
index 0000000..e197e5c
--- /dev/null
+++ b/ecord/metro/src/main/java/org/onosproject/ecord/metro/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Module to control E-CORD metro domain.
+ */
+package org.onosproject.ecord.metro;
\ No newline at end of file
diff --git a/ecord/metro/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/ecord/metro/src/main/resources/OSGI-INF/blueprint/shell-config.xml
new file mode 100644
index 0000000..df45cf5
--- /dev/null
+++ b/ecord/metro/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -0,0 +1,27 @@
+<!--
+  ~ 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.
+  -->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
+
+    <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
+        <command>
+            <action class="org.onosproject.ecord.metro.cli.AddMetroConnectivityCommand"/>
+        </command>
+        <command>
+            <action class="org.onosproject.ecord.metro.cli.RemoveMetroConnectivityCommand"/>
+        </command>
+    </command-bundle>
+
+</blueprint>
diff --git a/ecord/metro/src/main/resources/config-examples/network-cfg.json b/ecord/metro/src/main/resources/config-examples/network-cfg.json
new file mode 100644
index 0000000..d5da00d
--- /dev/null
+++ b/ecord/metro/src/main/resources/config-examples/network-cfg.json
@@ -0,0 +1,14 @@
+{
+  "ports": {
+    "of:0000ffffffffff01/10": {
+      "ceVlan": {
+        "tag" : 100
+      }
+    },
+    "of:0000ffffffffff01/11": {
+      "ceVlan": {
+        "tag" : 200
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 09d2932..aa196fe 100644
--- a/pom.xml
+++ b/pom.xml
@@ -39,6 +39,7 @@
         <module>tvue</module>
         <module>uiref</module>
         <module>ecord/co</module>
+        <module>ecord/metro</module>
   </modules>
 
     <properties>