Initial import of CFM and SOAM api

Change-Id: Icf5cc2d5fb34b75460e80e8cced0d70265bcd33b
diff --git a/apps/cfm/BUCK b/apps/cfm/BUCK
new file mode 100644
index 0000000..7c8cb71
--- /dev/null
+++ b/apps/cfm/BUCK
@@ -0,0 +1,28 @@
+COMPILE_DEPS = [
+    '//lib:CORE_DEPS',
+    '//lib:javax.ws.rs-api',
+    '//utils/rest:onlab-rest',
+    '//lib:JACKSON',
+    '//cli:onos-cli',
+    '//lib:org.apache.karaf.shell.console',
+    '//incubator/api:onos-incubator-api'
+]
+
+TEST_DEPS = [
+    '//lib:TEST_REST',
+    '//utils/osgi:onlab-osgi-tests',
+    '//web/api:onos-rest-tests',
+]
+
+osgi_jar_with_tests (
+    deps = COMPILE_DEPS,
+    test_deps = TEST_DEPS,
+    web_context = '/onos/cfm',
+)
+
+onos_app (
+    title = 'Layer 2 Monitoring CFM Application',
+    category = 'default',
+    url = 'http://onosproject.org',
+    description = 'Layer 2 Monitoring Connectivity Fault Management App',
+)
diff --git a/apps/cfm/README.md b/apps/cfm/README.md
new file mode 100644
index 0000000..c2c27fa
--- /dev/null
+++ b/apps/cfm/README.md
@@ -0,0 +1,3 @@
+# Layer 2 Monitoring with CFM and SOAM in ONOS
+Please see the README.md file in the API at
+incubator/api/src/main/java/org/onosproject/incubator/net/l2monitoring/README.md
\ No newline at end of file
diff --git a/apps/cfm/pom.xml b/apps/cfm/pom.xml
new file mode 100644
index 0000000..ad63ddd
--- /dev/null
+++ b/apps/cfm/pom.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- ~ Copyright 2017 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>
+        <groupId>org.onosproject</groupId>
+        <artifactId>onos-apps</artifactId>
+        <version>1.12.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>onos-app-cfm</artifactId>
+    <packaging>bundle</packaging>
+    <version>1.12.0-SNAPSHOT</version>
+
+    <description>ONOS OSGi bundle archetype</description>
+    <url>http://onosproject.org</url>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <web.context>/onos/cfm</web.context>
+        <api.description>Layer 2 Monitoring CFM REST API</api.description>
+        <api.version>1.0.0</api.version>
+        <api.package>org.onosproject.cfm</api.package>
+        <api.title>Layer 2 Monitoring CFM REST API</api.title>
+        <onos.app.name>org.onosproject.cfm</onos.app.name>
+        <onos.app.title>Layer 2 Monitoring CFM App</onos.app.title>
+        <onos.app.origin>Microsemi Inc</onos.app.origin>
+        <onos.app.category>default</onos.app.category>
+        <onos.app.url>http://onosproject.org</onos.app.url>
+        <onos.app.readme>Layer 2 Monitoring Connectivity Fault Management App</onos.app.readme>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-incubator-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-core-common</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-cli</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.karaf.shell</groupId>
+            <artifactId>org.apache.karaf.shell.console</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onlab-junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <_wab>src/main/webapp/</_wab>
+                        <Include-Resource>WEB-INF/classes/apidoc/swagger.json=target/swagger.json,
+                            {maven-resources}</Include-Resource>
+                        <Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
+                        <Import-Package>*,org.glassfish.jersey.servlet</Import-Package>
+                        <Web-ContextPath>${web.context}</Web-ContextPath>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/CfmWebComponent.java b/apps/cfm/src/main/java/org/onosproject/cfm/CfmWebComponent.java
new file mode 100644
index 0000000..6ead985
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/CfmWebComponent.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm;
+
+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.onosproject.cfm.web.ComponentCodec;
+import org.onosproject.cfm.web.FngAddressCodec;
+import org.onosproject.cfm.web.MaintenanceAssociationCodec;
+import org.onosproject.cfm.web.MaintenanceDomainCodec;
+import org.onosproject.cfm.web.MepCodec;
+import org.onosproject.cfm.web.MepEntryCodec;
+import org.onosproject.cfm.web.MepLbCreateCodec;
+import org.onosproject.cfm.web.MepLbEntryCodec;
+import org.onosproject.cfm.web.MepLtCreateCodec;
+import org.onosproject.cfm.web.RemoteMepEntryCodec;
+import org.onosproject.cfm.web.VidCodec;
+import org.onosproject.codec.CodecService;
+import org.onosproject.incubator.net.l2monitoring.cfm.MaintenanceAssociation;
+import org.onosproject.incubator.net.l2monitoring.cfm.MaintenanceDomain;
+import org.onosproject.incubator.net.l2monitoring.cfm.Mep;
+import org.onosproject.incubator.net.l2monitoring.cfm.Mep.FngAddress;
+import org.onosproject.incubator.net.l2monitoring.cfm.MepEntry;
+import org.onosproject.incubator.net.l2monitoring.cfm.MepLbCreate;
+import org.onosproject.incubator.net.l2monitoring.cfm.MepLbEntry;
+import org.onosproject.incubator.net.l2monitoring.cfm.MepLtCreate;
+import org.onosproject.incubator.net.l2monitoring.cfm.RemoteMepEntry;
+import org.onosproject.incubator.net.l2monitoring.soam.StartTime;
+import org.onosproject.incubator.net.l2monitoring.soam.StopTime;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementCreate;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementCreate.MeasurementOption;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementEntry;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementStat;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementStatCurrent;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementStatHistory;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossAvailabilityStat;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossAvailabilityStatCurrent;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossAvailabilityStatHistory;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementCreate;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementCreate.CounterOption;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementEntry;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementStat;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementStatCurrent;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementStatHistory;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementThreshold;
+import org.onosproject.soam.web.DelayMeasurementStatCodec;
+import org.onosproject.soam.web.DelayMeasurementStatCurrentCodec;
+import org.onosproject.soam.web.DelayMeasurementStatHistoryCodec;
+import org.onosproject.soam.web.DmCreateCodec;
+import org.onosproject.soam.web.DmEntryCodec;
+import org.onosproject.soam.web.DmMeasurementOptionCodec;
+import org.onosproject.soam.web.LmCounterOptionCodec;
+import org.onosproject.soam.web.LmCreateCodec;
+import org.onosproject.soam.web.LmEntryCodec;
+import org.onosproject.soam.web.LmThresholdOptionCodec;
+import org.onosproject.soam.web.LossAvailabilityStatCodec;
+import org.onosproject.soam.web.LossAvailabilityStatCurrentCodec;
+import org.onosproject.soam.web.LossAvailabilityStatHistoryCodec;
+import org.onosproject.soam.web.LossMeasurementStatCodec;
+import org.onosproject.soam.web.LossMeasurementStatCurrentCodec;
+import org.onosproject.soam.web.LossMeasurementStatHistoryCodec;
+import org.onosproject.soam.web.LossMeasurementThresholdCodec;
+import org.onosproject.soam.web.StartTimeCodec;
+import org.onosproject.soam.web.StopTimeCodec;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementThreshold.*;
+
+/**
+ * Enables the CFM REST Web Service component at /onos/cfm.
+ * Each codec for the rest interfaces should be registered here.
+ */
+@Component(immediate = true)
+public class CfmWebComponent {
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CodecService codecService;
+
+    @Activate
+    public void activate() {
+        codecService.registerCodec(MaintenanceDomain.class,
+                                        new MaintenanceDomainCodec());
+        codecService.registerCodec(MaintenanceAssociation.class,
+                                        new MaintenanceAssociationCodec());
+        codecService.registerCodec(org.onosproject.incubator.net.l2monitoring.cfm.Component.class,
+                                        new ComponentCodec());
+        codecService.registerCodec(VlanId.class, new VidCodec());
+        codecService.registerCodec(Mep.class, new MepCodec());
+        codecService.registerCodec(MepEntry.class, new MepEntryCodec());
+        codecService.registerCodec(MepLbCreate.class, new MepLbCreateCodec());
+        codecService.registerCodec(MepLbEntry.class, new MepLbEntryCodec());
+        codecService.registerCodec(MepLtCreate.class, new MepLtCreateCodec());
+        codecService.registerCodec(RemoteMepEntry.class, new RemoteMepEntryCodec());
+        codecService.registerCodec(FngAddress.class, new FngAddressCodec());
+
+
+        codecService.registerCodec(DelayMeasurementCreate.class,
+                                        new DmCreateCodec());
+        codecService.registerCodec(DelayMeasurementEntry.class,
+                                        new DmEntryCodec());
+        codecService.registerCodec(DelayMeasurementStat.class,
+                                        new DelayMeasurementStatCodec());
+        codecService.registerCodec(DelayMeasurementStatCurrent.class,
+                                        new DelayMeasurementStatCurrentCodec());
+        codecService.registerCodec(DelayMeasurementStatHistory.class,
+                                        new DelayMeasurementStatHistoryCodec());
+        codecService.registerCodec(MeasurementOption.class,
+                                        new DmMeasurementOptionCodec());
+
+        codecService.registerCodec(LossMeasurementCreate.class,
+                                        new LmCreateCodec());
+        codecService.registerCodec(LossMeasurementThreshold.class,
+                                        new LossMeasurementThresholdCodec());
+        codecService.registerCodec(LossMeasurementEntry.class,
+                                        new LmEntryCodec());
+        codecService.registerCodec(LossMeasurementStat.class,
+                                        new LossMeasurementStatCodec());
+        codecService.registerCodec(LossMeasurementStatCurrent.class,
+                                        new LossMeasurementStatCurrentCodec());
+        codecService.registerCodec(LossMeasurementStatHistory.class,
+                                        new LossMeasurementStatHistoryCodec());
+        codecService.registerCodec(LossAvailabilityStat.class,
+                                        new LossAvailabilityStatCodec());
+        codecService.registerCodec(LossAvailabilityStatCurrent.class,
+                                        new LossAvailabilityStatCurrentCodec());
+        codecService.registerCodec(LossAvailabilityStatHistory.class,
+                                        new LossAvailabilityStatHistoryCodec());
+        codecService.registerCodec(CounterOption.class,
+                                        new LmCounterOptionCodec());
+        codecService.registerCodec(ThresholdOption.class,
+                                        new LmThresholdOptionCodec());
+
+        codecService.registerCodec(StartTime.class, new StartTimeCodec());
+        codecService.registerCodec(StopTime.class, new StopTimeCodec());
+
+        log.info("CFM Web Component Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        log.info("CFM Web Component Stopped");
+        codecService.unregisterCodec(MaintenanceDomain.class);
+        codecService.unregisterCodec(MaintenanceAssociation.class);
+        codecService.unregisterCodec(org.onosproject.incubator.net.l2monitoring.cfm.Component.class);
+        codecService.unregisterCodec(VlanId.class);
+        codecService.unregisterCodec(Mep.class);
+        codecService.unregisterCodec(MepEntry.class);
+        codecService.unregisterCodec(MepLbCreate.class);
+        codecService.unregisterCodec(MepLbEntry.class);
+        codecService.unregisterCodec(MepLtCreate.class);
+        codecService.unregisterCodec(RemoteMepEntry.class);
+        codecService.unregisterCodec(FngAddress.class);
+
+        codecService.unregisterCodec(DelayMeasurementCreate.class);
+        codecService.unregisterCodec(DelayMeasurementEntry.class);
+        codecService.unregisterCodec(DelayMeasurementStat.class);
+        codecService.unregisterCodec(DelayMeasurementStatCurrent.class);
+        codecService.unregisterCodec(DelayMeasurementStatHistory.class);
+        codecService.unregisterCodec(MeasurementOption.class);
+        codecService.unregisterCodec(StartTime.class);
+        codecService.unregisterCodec(StopTime.class);
+
+        codecService.unregisterCodec(LossMeasurementCreate.class);
+        codecService.unregisterCodec(LossMeasurementThreshold.class);
+        codecService.unregisterCodec(LossMeasurementEntry.class);
+        codecService.unregisterCodec(LossMeasurementStat.class);
+        codecService.unregisterCodec(LossMeasurementStatCurrent.class);
+        codecService.unregisterCodec(LossMeasurementStatHistory.class);
+        codecService.unregisterCodec(LossAvailabilityStat.class);
+        codecService.unregisterCodec(LossAvailabilityStatCurrent.class);
+        codecService.unregisterCodec(LossAvailabilityStatHistory.class);
+        codecService.unregisterCodec(CounterOption.class);
+        codecService.unregisterCodec(ThresholdOption.class);
+
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/cli/CfmMaAddCommand.java b/apps/cfm/src/main/java/org/onosproject/cfm/cli/CfmMaAddCommand.java
new file mode 100644
index 0000000..d449cce
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/cli/CfmMaAddCommand.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.packet.VlanId;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.l2monitoring.cfm.Component;
+import org.onosproject.incubator.net.l2monitoring.cfm.DefaultComponent;
+import org.onosproject.incubator.net.l2monitoring.cfm.DefaultMaintenanceAssociation;
+import org.onosproject.incubator.net.l2monitoring.cfm.MaintenanceAssociation;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaId2Octet;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdIccY1731;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdPrimaryVid;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdRfc2685VpnId;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdShort;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdId;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdDomainName;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdMacUint;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdNone;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MepId;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmConfigException;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmMdService;
+
+/**
+ * Adds a Maintenance Association to a Maintenance Domain.
+ */
+@Command(scope = "onos", name = "cfm-ma-add",
+        description = "Add a CFM Maintenance Association to a Maintenance Domain.")
+public class CfmMaAddCommand extends AbstractShellCommand {
+    @Argument(index = 0, name = "name",
+            description = "Maintenance Domain name and type (in brackets)",
+            required = true, multiValued = false)
+    String mdName = null;
+
+    @Argument(index = 1, name = "name-type",
+            description = "Maintenance Assocation name type",
+            required = true, multiValued = false)
+    String nameType = null;
+
+    @Argument(index = 2, name = "name",
+            description = "Maintenance Assocation name. Restrictions apply depending " +
+                    "on name-type",
+            required = true, multiValued = false)
+    String name = null;
+
+    @Argument(index = 3, name = "ccm-interval",
+            description = "CCM Interval values from list",
+            required = true, multiValued = false)
+    String ccmInterval = null;
+
+    @Argument(index = 4, name = "numeric-id",
+            description = "An optional numeric id for Maintenance Association [1-65535]",
+            required = false, multiValued = false)
+    short numericId = 0;
+
+    @Argument(index = 5, name = "component-id",
+            description = "An id for a Component in the Component List [1-65535]." +
+                    "The CLI allows creation of only 1 component. If more are " +
+                    "required use the REST interface",
+            required = false, multiValued = false)
+    short componentId = 0;
+
+    @Argument(index = 6, name = "component-tag-type",
+            description = "Tag Type value for the component",
+            required = false, multiValued = false)
+    String tagType = null;
+
+    @Argument(index = 7, name = "component-mhf-creation",
+            description = "MEP Half function creation type for the component",
+            required = false, multiValued = false)
+    String mhfCreationType = null;
+
+    @Argument(index = 8, name = "component-vid",
+            description = "A VID for the component [1-4095]. This CLI allows " +
+                    "only the specification of 1 VID. If more are required use " +
+                    "the REST interface",
+            required = false, multiValued = false)
+    short vid = 0;
+
+    @Argument(index = 9, name = "rmep",
+            description = "Remote Mep numeric identifier [1-8192]",
+            required = true, multiValued = true)
+    String[] rmepArray = null;
+
+    @Override
+    protected void execute() {
+        CfmMdService service = get(CfmMdService.class);
+
+        String[] mdNameParts = mdName.split("[()]");
+        if (mdNameParts.length != 2) {
+            throw new IllegalArgumentException("Invalid name format. " +
+                    "Must be in the format of <identifier(name-type)>");
+        }
+
+        MdId mdId = null;
+        MdId.MdNameType mdNameTypeEnum = MdId.MdNameType.valueOf(mdNameParts[1]);
+        switch (mdNameTypeEnum) {
+            case DOMAINNAME:
+                mdId = MdIdDomainName.asMdId(mdNameParts[0]);
+                break;
+            case MACANDUINT:
+                mdId = MdIdMacUint.asMdId(mdNameParts[0]);
+                break;
+            case NONE:
+                mdId = MdIdNone.asMdId();
+                break;
+            case CHARACTERSTRING:
+            default:
+                mdId = MdIdCharStr.asMdId(mdNameParts[0]);
+        }
+
+        MaIdShort maId = null;
+        MaIdShort.MaIdType maNameTypeEnum = MaIdShort.MaIdType.valueOf(nameType);
+        switch (maNameTypeEnum) {
+            case TWOOCTET:
+                maId = MaId2Octet.asMaId(name);
+                break;
+            case ICCY1731:
+                maId = MaIdIccY1731.asMaId(name);
+                break;
+            case PRIMARYVID:
+                maId = MaIdPrimaryVid.asMaId(name);
+                break;
+            case RFC2685VPNID:
+                maId = MaIdRfc2685VpnId.asMaIdHex(name);
+                break;
+            case CHARACTERSTRING:
+            default:
+                maId = MaIdCharStr.asMaId(name);
+        }
+
+        MaintenanceAssociation.MaBuilder builder = null;
+        try {
+            builder = DefaultMaintenanceAssociation.builder(maId, mdId.getNameLength());
+            if (ccmInterval != null && !ccmInterval.isEmpty()) {
+                builder = builder.ccmInterval(MaintenanceAssociation.CcmInterval.valueOf(ccmInterval));
+            }
+            for (String rmep:rmepArray) {
+                builder = builder.addToRemoteMepIdList(MepId.valueOf(Short.parseShort(rmep)));
+            }
+            if (numericId > 0) {
+                builder = builder.maNumericId(numericId);
+            }
+            if (componentId > 0) {
+                Component.ComponentBuilder compBuilder =
+                        DefaultComponent.builder(componentId);
+                if (tagType != null && !tagType.isEmpty()) {
+                    compBuilder = compBuilder.tagType(
+                            Component.TagType.valueOf(tagType));
+                }
+                if (mhfCreationType != null && !mhfCreationType.isEmpty()) {
+                    compBuilder = compBuilder.mhfCreationType(
+                            Component.MhfCreationType.valueOf(mhfCreationType));
+                }
+                if (vid > 0) {
+                    compBuilder = compBuilder.addToVidList(VlanId.vlanId(vid));
+                }
+                builder = builder.addToComponentList(compBuilder.build());
+            }
+            boolean created = service.createMaintenanceAssociation(mdId, builder.build());
+            print("Maintenance Association %s is successfully %s on MD %s",
+                    maId, created ? "updated" : "created", mdId);
+        } catch (CfmConfigException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/cli/CfmMaDeleteCommand.java b/apps/cfm/src/main/java/org/onosproject/cfm/cli/CfmMaDeleteCommand.java
new file mode 100644
index 0000000..4228b78
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/cli/CfmMaDeleteCommand.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaId2Octet;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdIccY1731;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdPrimaryVid;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdRfc2685VpnId;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdShort;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdId;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdDomainName;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdMacUint;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdNone;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmConfigException;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmMdService;
+
+/**
+ * Delete a Maintenance Association from the existing list of a Maintenance Domain.
+ */
+@Command(scope = "onos", name = "cfm-ma-delete",
+        description = "Delete a CFM Maintenance Association and its children.")
+public class CfmMaDeleteCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "name",
+            description = "Maintenance Domain name and type (in brackets) " +
+                    "and the Maintenance Association name and type (in brackets)",
+            required = true, multiValued = false)
+    String name = null;
+
+    @Override
+    protected void execute() {
+        CfmMdService service = get(CfmMdService.class);
+
+        String[] nameParts = name.split("[()]");
+        if (nameParts.length != 4) {
+            throw new IllegalArgumentException("Invalid name format. Must be in " +
+                    "the format of <identifier(name-type)identifier(name-type)>");
+        }
+
+        MdId mdId = null;
+        MdId.MdNameType nameTypeEnum = MdId.MdNameType.valueOf(nameParts[1]);
+        switch (nameTypeEnum) {
+            case DOMAINNAME:
+                mdId = MdIdDomainName.asMdId(nameParts[0]);
+                break;
+            case MACANDUINT:
+                mdId = MdIdMacUint.asMdId(nameParts[0]);
+                break;
+            case NONE:
+                mdId = MdIdNone.asMdId();
+                break;
+            case CHARACTERSTRING:
+            default:
+                mdId = MdIdCharStr.asMdId(nameParts[0]);
+        }
+
+        MaIdShort maId = null;
+        MaIdShort.MaIdType maNameTypeEnum = MaIdShort.MaIdType.valueOf(nameParts[3]);
+        switch (maNameTypeEnum) {
+            case TWOOCTET:
+                maId = MaId2Octet.asMaId(nameParts[2]);
+                break;
+            case ICCY1731:
+                maId = MaIdIccY1731.asMaId(nameParts[2]);
+                break;
+            case PRIMARYVID:
+                maId = MaIdPrimaryVid.asMaId(nameParts[2]);
+                break;
+            case RFC2685VPNID:
+                maId = MaIdRfc2685VpnId.asMaIdHex(nameParts[2]);
+                break;
+            case CHARACTERSTRING:
+            default:
+                maId = MaIdCharStr.asMaId(nameParts[2]);
+        }
+
+        try {
+            boolean deleted = service.deleteMaintenanceAssociation(mdId, maId);
+            print("Maintenance Association %s-%s is %ssuccessfully deleted.",
+                    mdId, maId, deleted ? "" : "NOT ");
+        } catch (CfmConfigException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/cli/CfmMdAddCommand.java b/apps/cfm/src/main/java/org/onosproject/cfm/cli/CfmMdAddCommand.java
new file mode 100644
index 0000000..922ed64
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/cli/CfmMdAddCommand.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.cli;
+
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Argument;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.l2monitoring.cfm.DefaultMaintenanceDomain;
+import org.onosproject.incubator.net.l2monitoring.cfm.MaintenanceDomain;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdId;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdDomainName;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdMacUint;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdNone;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmConfigException;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmMdService;
+
+import java.util.Optional;
+
+/**
+ * Adds a Maintenance Domain to the existing list.
+ */
+@Command(scope = "onos", name = "cfm-md-add",
+        description = "Add a CFM Maintenance Domain.")
+public class CfmMdAddCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "name-type",
+            description = "Maintenance Domain name type",
+            required = true, multiValued = false)
+    String nameType = null;
+
+    @Argument(index = 1, name = "name",
+            description = "Maintenance Domain name. Restrictions apply depending " +
+                    "on name-type. Leave empty if name type is none",
+            required = true, multiValued = false)
+    String name = null;
+
+    @Argument(index = 2, name = "level",
+            description = "Maintenance Domain level LEVEL0-LEVEL7",
+            required = true, multiValued = false)
+    String level = null;
+
+    @Argument(index = 3, name = "numeric-id",
+            description = "An optional numeric id for Maintenance Domain [1-65535]",
+            required = false, multiValued = false)
+    short numericId = 0;
+
+    @Override
+    protected void execute() {
+        CfmMdService service = get(CfmMdService.class);
+        MdId mdId = null;
+        MdId.MdNameType nameTypeEnum = MdId.MdNameType.valueOf(nameType);
+        switch (nameTypeEnum) {
+            case DOMAINNAME:
+                mdId = MdIdDomainName.asMdId(name);
+                break;
+            case MACANDUINT:
+                mdId = MdIdMacUint.asMdId(name);
+                break;
+            case NONE:
+                mdId = MdIdNone.asMdId();
+                break;
+            case CHARACTERSTRING:
+            default:
+                mdId = MdIdCharStr.asMdId(name);
+        }
+        MaintenanceDomain.MdLevel levelEnum =
+                MaintenanceDomain.MdLevel.valueOf(level);
+        Optional<Short> numericIdOpt = Optional.empty();
+        if (numericId > 0) {
+            numericIdOpt = Optional.of(numericId);
+        }
+
+        MaintenanceDomain.MdBuilder builder = null;
+        try {
+            builder = DefaultMaintenanceDomain.builder(mdId).mdLevel(levelEnum);
+            if (numericIdOpt.isPresent()) {
+                builder = builder.mdNumericId(numericIdOpt.get());
+            }
+            boolean created = service.createMaintenanceDomain(builder.build());
+            print("Maintenance Domain with id %s is successfully %s.",
+                    mdId, created ? "updated" : "created");
+        } catch (CfmConfigException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/cli/CfmMdDeleteCommand.java b/apps/cfm/src/main/java/org/onosproject/cfm/cli/CfmMdDeleteCommand.java
new file mode 100644
index 0000000..3cf3c26
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/cli/CfmMdDeleteCommand.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdId;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdDomainName;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdMacUint;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdNone;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmConfigException;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmMdService;
+
+/**
+ * Deletes a Maintenance Domain from the existing list.
+ */
+@Command(scope = "onos", name = "cfm-md-delete",
+        description = "Delete a CFM Maintenance Domain and its children.")
+public class CfmMdDeleteCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "name",
+            description = "Maintenance Domain name and type (in brackets)",
+            required = true, multiValued = false)
+    String name = null;
+
+    @Override
+    protected void execute() {
+        CfmMdService service = get(CfmMdService.class);
+
+        String[] nameParts = name.split("[()]");
+        if (nameParts.length != 2) {
+            throw new IllegalArgumentException("Invalid name format. " +
+                    "Must be in the format of <identifier(name-type)>");
+        }
+
+        MdId mdId = null;
+        MdId.MdNameType nameTypeEnum = MdId.MdNameType.valueOf(nameParts[1]);
+        switch (nameTypeEnum) {
+            case DOMAINNAME:
+                mdId = MdIdDomainName.asMdId(nameParts[0]);
+                break;
+            case MACANDUINT:
+                mdId = MdIdMacUint.asMdId(nameParts[0]);
+                break;
+            case NONE:
+                mdId = MdIdNone.asMdId();
+                break;
+            case CHARACTERSTRING:
+            default:
+                mdId = MdIdCharStr.asMdId(nameParts[0]);
+        }
+
+        try {
+            boolean deleted = service.deleteMaintenanceDomain(mdId);
+            print("Maintenance Domain %s is %ssuccessfully deleted.",
+                    mdId, deleted ? "" : "NOT ");
+        } catch (CfmConfigException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/cli/CfmMdListMdCommand.java b/apps/cfm/src/main/java/org/onosproject/cfm/cli/CfmMdListMdCommand.java
new file mode 100644
index 0000000..341bd7d
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/cli/CfmMdListMdCommand.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.l2monitoring.cfm.MaintenanceAssociation;
+import org.onosproject.incubator.net.l2monitoring.cfm.MaintenanceDomain;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdId;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdDomainName;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdMacUint;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdNone;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmMdService;
+
+import java.util.Optional;
+
+/**
+ * Lists a particular Maintenance Domain.
+ */
+@Command(scope = "onos", name = "cfm-md-list",
+        description = "Lists a single CFM Maintenance Domain or all if none specified.")
+public class CfmMdListMdCommand extends AbstractShellCommand {
+    @Argument(index = 0, name = "name",
+            description = "Maintenance Domain name and type (in brackets)",
+            required = false, multiValued = false)
+    String name = null;
+
+    @Override
+    protected void execute() {
+        CfmMdService service = get(CfmMdService.class);
+
+        if (name != null) {
+            String[] nameParts = name.split("[()]");
+            if (nameParts.length != 2) {
+                throw new IllegalArgumentException("Invalid name format. " +
+                        "Must be in the format of <identifier(name-type)>");
+            }
+
+            MdId mdId = null;
+            MdId.MdNameType nameTypeEnum = MdId.MdNameType.valueOf(nameParts[1]);
+            switch (nameTypeEnum) {
+                case DOMAINNAME:
+                    mdId = MdIdDomainName.asMdId(nameParts[0]);
+                    break;
+                case MACANDUINT:
+                    mdId = MdIdMacUint.asMdId(nameParts[0]);
+                    break;
+                case NONE:
+                    mdId = MdIdNone.asMdId();
+                    break;
+                case CHARACTERSTRING:
+                default:
+                    mdId = MdIdCharStr.asMdId(nameParts[0]);
+            }
+
+            print("Maintenance Domain:");
+            Optional<MaintenanceDomain> md = service.getMaintenanceDomain(mdId);
+            print(printMd(md));
+            md.get().maintenanceAssociationList().forEach(ma -> printMa(Optional.of(ma)));
+        } else {
+            service.getAllMaintenanceDomain().forEach(md -> {
+                print(printMd(Optional.of(md)));
+            });
+        }
+    }
+
+    public static String printMd(Optional<MaintenanceDomain> md) {
+        if (!md.isPresent()) {
+            return new String("MD not found");
+        } else {
+            StringBuffer sb = new StringBuffer("\tMD: ");
+            sb.append(md.get().mdId().mdName());
+            sb.append("(" + md.get().mdId().nameType());
+            sb.append(") Lvl:" + md.get().mdLevel().ordinal());
+            sb.append(", Num: " + md.get().mdNumericId());
+
+            md.get().maintenanceAssociationList().
+                    forEach(ma -> sb.append(printMa(Optional.of(ma))));
+            return sb.toString();
+        }
+    }
+
+    public static String printMa(Optional<MaintenanceAssociation> ma) {
+        if (!ma.isPresent()) {
+            return "\n\tNo MA found";
+        }
+
+        StringBuilder sb = new StringBuilder("\n\t\tMA: ");
+        sb.append(ma.get().maId().maName());
+        sb.append("(");
+        sb.append(ma.get().maId().nameType());
+        sb.append(") CCM: ");
+        sb.append(ma.get().ccmInterval());
+        sb.append(" Num: ");
+        sb.append(ma.get().maNumericId());
+
+        ma.get().remoteMepIdList().forEach(rmep -> {
+                    sb.append("\n\t\t\tRmep: ");
+                    sb.append(rmep);
+                });
+        ma.get().componentList().forEach(comp -> {
+            sb.append("\n\t\t\tComponent: ");
+            sb.append(comp.componentId());
+            sb.append(" Perm: ");
+            sb.append(comp.idPermission());
+            sb.append(" MHF: ");
+            sb.append(comp.mhfCreationType());
+            sb.append(" Tag: ");
+            sb.append(comp.tagType());
+
+            comp.vidList().forEach(vid -> {
+                    sb.append("\n\t\t\t\tVID: ");
+                    sb.append(vid);
+            });
+        });
+
+        return sb.toString();
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/cli/completer/CfmCompMhfCompleter.java b/apps/cfm/src/main/java/org/onosproject/cfm/cli/completer/CfmCompMhfCompleter.java
new file mode 100644
index 0000000..499b84d
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/cli/completer/CfmCompMhfCompleter.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.cli.completer;
+
+import org.onosproject.cli.AbstractChoicesCompleter;
+import org.onosproject.incubator.net.l2monitoring.cfm.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * CLI completer for Component MEP Half Function creation.
+ */
+public class CfmCompMhfCompleter extends AbstractChoicesCompleter {
+    @Override
+    public List<String> choices() {
+        List<String> choices = new ArrayList<>();
+
+        for (Component.MhfCreationType mhfCreationType: Component.MhfCreationType.values()) {
+            choices.add(String.valueOf(mhfCreationType.toString()));
+        }
+
+        return choices;
+    }
+
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/cli/completer/CfmCompTagTypeCompleter.java b/apps/cfm/src/main/java/org/onosproject/cfm/cli/completer/CfmCompTagTypeCompleter.java
new file mode 100644
index 0000000..d919780
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/cli/completer/CfmCompTagTypeCompleter.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.cli.completer;
+
+import org.onosproject.cli.AbstractChoicesCompleter;
+import org.onosproject.incubator.net.l2monitoring.cfm.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * CLI completer for Component TagType creation.
+ */
+public class CfmCompTagTypeCompleter extends AbstractChoicesCompleter {
+    @Override
+    public List<String> choices() {
+        List<String> choices = new ArrayList<>();
+
+        for (Component.TagType tagType: Component.TagType.values()) {
+            choices.add(String.valueOf(tagType.toString()));
+        }
+
+        return choices;
+    }
+
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/cli/completer/CfmMaCcmIntervalCompleter.java b/apps/cfm/src/main/java/org/onosproject/cfm/cli/completer/CfmMaCcmIntervalCompleter.java
new file mode 100644
index 0000000..6599ed3
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/cli/completer/CfmMaCcmIntervalCompleter.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.cli.completer;
+
+import org.onosproject.cli.AbstractChoicesCompleter;
+import org.onosproject.incubator.net.l2monitoring.cfm.MaintenanceAssociation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * CLI completer for Ccm Interval creation.
+ */
+public class CfmMaCcmIntervalCompleter extends AbstractChoicesCompleter {
+    @Override
+    public List<String> choices() {
+        List<String> choices = new ArrayList<>();
+
+        for (MaintenanceAssociation.CcmInterval ccmInterval: MaintenanceAssociation.CcmInterval.values()) {
+            choices.add(ccmInterval.toString());
+        }
+
+        return choices;
+    }
+
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/cli/completer/CfmMaNameCompleter.java b/apps/cfm/src/main/java/org/onosproject/cfm/cli/completer/CfmMaNameCompleter.java
new file mode 100644
index 0000000..b484212
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/cli/completer/CfmMaNameCompleter.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.cli.completer;
+
+import org.onosproject.cli.AbstractChoicesCompleter;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmMdService;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.onosproject.cli.AbstractShellCommand.get;
+
+/**
+ * CLI completer for MA Name creation.
+ */
+public class CfmMaNameCompleter extends AbstractChoicesCompleter {
+    @Override
+    public List<String> choices() {
+        List<String> choices = new ArrayList<>();
+
+        CfmMdService service = get(CfmMdService.class);
+        service.getAllMaintenanceDomain().forEach(md ->
+            md.maintenanceAssociationList().forEach(ma ->
+                choices.add(new StringBuilder(md.mdId().mdName())
+                .append("(")
+                .append(md.mdId().nameType())
+                .append(")")
+                .append(ma.maId().maName())
+                .append("(")
+                .append(ma.maId().nameType())
+                .append(")").toString())
+            )
+        );
+
+        return choices;
+    }
+
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/cli/completer/CfmMaNameTypeCompleter.java b/apps/cfm/src/main/java/org/onosproject/cfm/cli/completer/CfmMaNameTypeCompleter.java
new file mode 100644
index 0000000..d59a5c8
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/cli/completer/CfmMaNameTypeCompleter.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.cli.completer;
+
+import org.apache.karaf.shell.console.Completer;
+import org.apache.karaf.shell.console.completer.StringsCompleter;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdShort;
+
+import java.util.List;
+import java.util.SortedSet;
+
+/**
+ * CLI completer for MA name type creation.
+ */
+public class CfmMaNameTypeCompleter implements Completer {
+    @Override
+    public int complete(String buffer, int cursor, List<String> candidates) {
+        // Delegate string completer
+        StringsCompleter delegate = new StringsCompleter();
+        SortedSet<String> strings = delegate.getStrings();
+
+        for (MaIdShort.MaIdType nameType : MaIdShort.MaIdType.values()) {
+            strings.add(nameType.toString());
+        }
+
+        // Now let the completer do the work for figuring out what to offer.
+        return delegate.complete(buffer, cursor, candidates);
+    }
+
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/cli/completer/CfmMdLevelCompleter.java b/apps/cfm/src/main/java/org/onosproject/cfm/cli/completer/CfmMdLevelCompleter.java
new file mode 100644
index 0000000..374e549
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/cli/completer/CfmMdLevelCompleter.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.cli.completer;
+
+import org.onosproject.cli.AbstractChoicesCompleter;
+import org.onosproject.incubator.net.l2monitoring.cfm.MaintenanceDomain;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * CLI completer for MD Level creation.
+ */
+public class CfmMdLevelCompleter extends AbstractChoicesCompleter {
+    @Override
+    public List<String> choices() {
+        List<String> choices = new ArrayList<>();
+
+        for (MaintenanceDomain.MdLevel mdLevel:MaintenanceDomain.MdLevel.values()) {
+            choices.add(String.valueOf(mdLevel.toString()));
+        }
+
+        return choices;
+    }
+
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/cli/completer/CfmMdNameCompleter.java b/apps/cfm/src/main/java/org/onosproject/cfm/cli/completer/CfmMdNameCompleter.java
new file mode 100644
index 0000000..7a336e7
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/cli/completer/CfmMdNameCompleter.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.cli.completer;
+
+import org.onosproject.cli.AbstractChoicesCompleter;
+import static org.onosproject.cli.AbstractShellCommand.get;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmMdService;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * CLI completer for MD Name creation.
+ */
+public class CfmMdNameCompleter extends AbstractChoicesCompleter {
+    @Override
+    public List<String> choices() {
+        List<String> choices = new ArrayList<>();
+
+        CfmMdService service = get(CfmMdService.class);
+        service.getAllMaintenanceDomain().forEach(md ->
+                choices.add(md.mdId().mdName() + "(" + md.mdId().nameType() + ")"));
+
+        return choices;
+    }
+
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/cli/completer/CfmMdNameTypeCompleter.java b/apps/cfm/src/main/java/org/onosproject/cfm/cli/completer/CfmMdNameTypeCompleter.java
new file mode 100644
index 0000000..6e24fc7
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/cli/completer/CfmMdNameTypeCompleter.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.cli.completer;
+
+import org.apache.karaf.shell.console.Completer;
+import org.apache.karaf.shell.console.completer.StringsCompleter;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdId;
+
+import java.util.List;
+import java.util.SortedSet;
+
+/**
+ * CLI completer for MD Name Type creation.
+ */
+public class CfmMdNameTypeCompleter implements Completer {
+    @Override
+    public int complete(String buffer, int cursor, List<String> candidates) {
+        // Delegate string completer
+        StringsCompleter delegate = new StringsCompleter();
+        SortedSet<String> strings = delegate.getStrings();
+
+        for (MdId.MdNameType nameType : MdId.MdNameType.values()) {
+            strings.add(nameType.toString());
+        }
+
+        // Now let the completer do the work for figuring out what to offer.
+        return delegate.complete(buffer, cursor, candidates);
+    }
+
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/cli/completer/package-info.java b/apps/cfm/src/main/java/org/onosproject/cfm/cli/completer/package-info.java
new file mode 100644
index 0000000..419084d
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/cli/completer/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.
+ */
+/**
+ * Completion of Commands for creating and deleting Maintenance Domains and Maintenance Associations.
+ */
+package org.onosproject.cfm.cli.completer;
\ No newline at end of file
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/cli/package-info.java b/apps/cfm/src/main/java/org/onosproject/cfm/cli/package-info.java
new file mode 100644
index 0000000..4c57e22
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/cli/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.
+ */
+/**
+ * Commands for creating and deleting Maintenance Domains and Maintenance Associations.
+ */
+package org.onosproject.cfm.cli;
\ No newline at end of file
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/impl/CfmWebApplication.java b/apps/cfm/src/main/java/org/onosproject/cfm/impl/CfmWebApplication.java
new file mode 100644
index 0000000..f21b66d
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/impl/CfmWebApplication.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.impl;
+
+import java.util.Set;
+
+import org.onlab.rest.AbstractWebApplication;
+import org.onosproject.soam.impl.DmWebResource;
+import org.onosproject.soam.impl.LmWebResource;
+
+/**
+ * CFM REST API web application.
+ */
+public class CfmWebApplication extends AbstractWebApplication {
+    @Override
+    public Set<Class<?>> getClasses() {
+        return getClasses(
+                MdWebResource.class,
+                MaWebResource.class,
+                MepWebResource.class,
+                DmWebResource.class,
+                LmWebResource.class);
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/impl/MaWebResource.java b/apps/cfm/src/main/java/org/onosproject/cfm/impl/MaWebResource.java
new file mode 100644
index 0000000..c861314
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/impl/MaWebResource.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.impl;
+
+import java.io.InputStream;
+import java.net.URI;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.onosproject.cfm.web.MaintenanceAssociationCodec;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.cfm.MaintenanceAssociation;
+import org.onosproject.incubator.net.l2monitoring.cfm.MaintenanceDomain;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdShort;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdId;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmConfigException;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmMdService;
+import org.onosproject.rest.AbstractWebResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Layer 2 CFM Maintenance Association web resource.
+ */
+@Path("md/{md_name}/ma")
+public class MaWebResource extends AbstractWebResource {
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    /**
+     * Get Maintenance Association by MD and MA name.
+     *
+     * @param mdName The name of a Maintenance Domain
+     * @param maName The name of a Maintenance Association belonging to the MD
+     * @return 200 OK with details of MA or 500 on Error
+     */
+    @GET
+    @Path("{ma_name}")
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response getMa(@PathParam("md_name") String mdName,
+                          @PathParam("ma_name") String maName) {
+        log.debug("GET called for MA {}/{}", mdName, maName);
+        try {
+            MdId mdId = MdIdCharStr.asMdId(mdName);
+            MaIdShort maId = MaIdCharStr.asMaId(maName);
+            MaintenanceAssociation ma = get(CfmMdService.class)
+                .getMaintenanceAssociation(mdId, maId)
+                .orElseThrow(() -> new IllegalArgumentException(
+                        "MA " + maName + " not Found"));
+            ObjectNode node = mapper().createObjectNode();
+            node.set("ma", codec(MaintenanceAssociation.class).encode(ma, this));
+            return ok(node).build();
+        } catch (IllegalArgumentException e) {
+            log.error("Get MA {} failed", mdName + "/" + maName, e);
+            return Response.serverError()
+                    .entity("{ \"failure\":\"" + e.toString() + "\" }").build();
+        }
+    }
+
+    /**
+     * Delete the Maintenance Association by MD and MA name.
+     *
+     * @param mdName The name of a Maintenance Domain
+     * @param maName The name of a Maintenance Association belonging to the MD
+     * @return 200 OK if removed, 304 if item was not found or 500 on Error
+     */
+    @DELETE
+    @Path("{ma_name}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response deleteMa(@PathParam("md_name") String mdName,
+                             @PathParam("ma_name") String maName) {
+        log.debug("DELETE called for MA {}/{}", mdName, maName);
+        try {
+            MdId mdId = MdIdCharStr.asMdId(mdName);
+            MaIdShort maId = MaIdCharStr.asMaId(maName);
+            boolean deleted = get(CfmMdService.class)
+                                        .deleteMaintenanceAssociation(mdId, maId);
+            if (!deleted) {
+                return Response.notModified(mdName + "/"
+                                            + maName + " did not exist").build();
+            } else {
+                return ok("{ \"success\":\"deleted " + mdName
+                                                + "/" + maName + "\" }").build();
+            }
+        } catch (CfmConfigException e) {
+            log.error("Delete Maintenance Association {} failed",
+                    mdName + "/" + maName, e);
+            return Response.serverError().entity("{ \"failure\":\"" +
+                                                e.toString() + "\" }").build();
+        }
+    }
+
+    /**
+     * Create Maintenance Association by MD and MA name.
+     *
+     * @param mdName The name of a Maintenance Domain
+     * @param input A JSON formatted input stream specifying the MA parameters
+     * @return 200 OK or 500 on error
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createMaintenanceAssociation(@PathParam("md_name") String mdName,
+                                                 InputStream input) {
+        log.debug("POST called to Create MA");
+        try {
+            MdId mdId = MdIdCharStr.asMdId(mdName);
+            MaintenanceDomain md = get(CfmMdService.class)
+                                            .getMaintenanceDomain(mdId).get();
+            if (md == null) {
+                return Response.serverError().entity("{ \"failure\":\"md "
+                                        + mdName + " does not exist\" }").build();
+            }
+
+            ObjectMapper mapper = new ObjectMapper();
+            JsonNode cfg = mapper.readTree(input);
+            JsonCodec<MaintenanceAssociation> maCodec =
+                                            codec(MaintenanceAssociation.class);
+
+            MaintenanceAssociation ma;
+            try {
+                ma = ((MaintenanceAssociationCodec) maCodec)
+                        .decode((ObjectNode) cfg, this, mdId.getNameLength());
+            } catch (Exception e) {
+                log.error("Create MaintenanceAssociation on MD {} failed", mdName, e);
+                return Response.serverError()
+                        .entity("{ \"failure\":\"" + e.toString() + "\" }")
+                        .build();
+            }
+
+            Boolean alreadyExists = get(CfmMdService.class)
+                                        .createMaintenanceAssociation(mdId, ma);
+            if (alreadyExists) {
+                return Response.notModified(mdName + "/" + ma.maId() +
+                                                    " already exists").build();
+            }
+            return Response
+                    .created(new URI("md/" + mdName + "/ma/" + ma.maId()))
+                    .entity("{ \"success\":\"" + mdName + "/" + ma.maId() + " created\" }")
+                    .build();
+
+        } catch (Exception | CfmConfigException e) {
+            log.error("Create MaintenanceAssociation on MD {} failed", mdName, e);
+            return Response.serverError()
+                    .entity("{ \"failure\":\"" + e.toString() + "\" }")
+                    .build();
+        }
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/impl/MdWebResource.java b/apps/cfm/src/main/java/org/onosproject/cfm/impl/MdWebResource.java
new file mode 100644
index 0000000..ecf1fe9
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/impl/MdWebResource.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.impl;
+
+import java.io.InputStream;
+import java.net.URI;
+import java.util.Collection;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.onosproject.incubator.net.l2monitoring.cfm.MaintenanceDomain;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdId;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmConfigException;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmMdService;
+import org.onosproject.rest.AbstractWebResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Layer 2 CFM Maintenance Domain web resource.
+ */
+@Path("md")
+public class MdWebResource extends AbstractWebResource {
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    public static final String JSON_NOT_NULL = "JsonNode can not be null";
+
+    /**
+     * Get all Maintenance Domains.
+     *
+     * @return 200 OK with a list of MDs and their children
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response getMds() {
+        log.debug("GET called for all MDs");
+        Collection<MaintenanceDomain> mdMap =
+                                get(CfmMdService.class).getAllMaintenanceDomain();
+        ArrayNode arrayNode = mapper().createArrayNode();
+        arrayNode.add(codec(MaintenanceDomain.class).encode(mdMap, this));
+        return ok(mapper().createObjectNode().set("mds", arrayNode)).build();
+    }
+
+    /**
+     * Get Maintenance Domain by name.
+     *
+     * @param mdName The name of a Maintenance Domain
+     * @return 200 OK with the details of the MD and its children or 500 on error
+     */
+    @GET
+    @Path("{md_name}")
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response getMd(@PathParam("md_name") String mdName) {
+        log.debug("GET called for MD {}", mdName);
+        try {
+            MaintenanceDomain md = get(CfmMdService.class)
+                    .getMaintenanceDomain(MdIdCharStr.asMdId(mdName))
+                    .orElseThrow(() -> new IllegalArgumentException(
+                            "MD " + mdName + " not Found"));
+            ObjectNode result = mapper().createObjectNode();
+            result.set("md", codec(MaintenanceDomain.class).encode(md, this));
+            return ok(result.toString()).build();
+        } catch (IllegalArgumentException e) {
+            log.error("Get MD {} failed", mdName, e);
+            return Response.serverError()
+                    .entity("{ \"failure\":\"" + e.toString() + "\" }").build();
+        }
+    }
+
+    /**
+     * Delete Maintenance Domain by name.
+     *
+     * @param mdName The name of a Maintenance Domain
+     * @return 200 OK, or 304 if not found or 500 on error
+     */
+    @DELETE
+    @Path("{md_name}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response deleteMd(@PathParam("md_name") String mdName) {
+        log.debug("DELETE called for MD {}", mdName);
+        try {
+            MdId mdId = MdIdCharStr.asMdId(mdName);
+            boolean deleted = get(CfmMdService.class).deleteMaintenanceDomain(mdId);
+            if (!deleted) {
+                return Response.notModified(mdName + " did not exist").build();
+            } else {
+                return ok("{ \"success\":\"deleted " + mdName + "\" }").build();
+            }
+        } catch (CfmConfigException e) {
+            log.error("Delete Maintenance Domain {} failed", mdName, e);
+            return Response.serverError()
+                    .entity("{ \"failure\":\"" + e.toString() + "\" }").build();
+        }
+    }
+
+    /**
+     * Create Maintenance Domain.
+     *
+     * @param input A JSON formatted input stream specifying the MA parameters
+     * @return 200 OK, 304 if MD already exists or 500 on error
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createMaintenanceDomain(InputStream input) {
+        log.debug("POST called to Create MD");
+        try {
+            ObjectMapper mapper = new ObjectMapper();
+            JsonNode cfg = mapper.readTree(input);
+            MaintenanceDomain md = codec(MaintenanceDomain.class).decode((ObjectNode) cfg, this);
+
+            if (get(CfmMdService.class).createMaintenanceDomain(md)) {
+                return Response.notModified(md.mdId().toString() + " already exists").build();
+            }
+            return Response
+                    .created(new URI("md/" + md.mdId()))
+                    .entity("{ \"success\":\"" + md.mdId() + " created\" }")
+                    .build();
+
+        } catch (Exception | CfmConfigException e) {
+            log.error("Create MaintenanceDomain", e);
+            return Response.serverError()
+                    .entity("{ \"failure\":\"" + e.toString() + "\" }")
+                    .build();
+        }
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/impl/MepWebResource.java b/apps/cfm/src/main/java/org/onosproject/cfm/impl/MepWebResource.java
new file mode 100644
index 0000000..2486df0
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/impl/MepWebResource.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.impl;
+
+import java.io.InputStream;
+import java.net.URI;
+import java.util.Collection;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.onosproject.cfm.web.MepCodec;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.cfm.MaintenanceAssociation;
+import org.onosproject.incubator.net.l2monitoring.cfm.MaintenanceDomain;
+import org.onosproject.incubator.net.l2monitoring.cfm.Mep;
+import org.onosproject.incubator.net.l2monitoring.cfm.MepEntry;
+import org.onosproject.incubator.net.l2monitoring.cfm.MepLbCreate;
+import org.onosproject.incubator.net.l2monitoring.cfm.MepLtCreate;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdShort;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdId;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MepId;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmConfigException;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmMdService;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmMepService;
+import org.onosproject.rest.AbstractWebResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Layer 2 CFM Maintenance Association Endpoint (MEP) web resource.
+ */
+@Path("md/{md_name}/ma/{ma_name}/mep")
+public class MepWebResource extends AbstractWebResource {
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    /**
+     * Get all MEPs by MD name, MA name.
+     *
+     * @param mdName The name of a Maintenance Domain
+     * @param maName The name of a Maintenance Association belonging to the MD
+     * @return 200 OK with a list of MEPS or 500 on error
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response getAllMepsForMa(@PathParam("md_name") String mdName,
+            @PathParam("ma_name") String maName) {
+        log.debug("GET all Meps called for MA {}", mdName + "/" + maName);
+        try {
+            MdId mdId = MdIdCharStr.asMdId(mdName);
+            MaIdShort maId = MaIdCharStr.asMaId(maName);
+            Collection<MepEntry> mepCollection = get(CfmMepService.class).getAllMeps(mdId, maId);
+            ArrayNode an = mapper().createArrayNode();
+            an.add(codec(MepEntry.class).encode(mepCollection, this));
+            return ok(mapper().createObjectNode().set("meps", an)).build();
+        } catch (CfmConfigException e) {
+            log.error("Get all Meps on {} failed because of exception",
+                    mdName + "/" + maName, e);
+            return Response.serverError().entity("{ \"failure\":\"" + e.toString() + "\" }").build();
+        }
+
+    }
+
+    /**
+     * Get MEP by MD name, MA name and Mep Id.
+     *
+     * @param mdName The name of a Maintenance Domain
+     * @param maName The name of a Maintenance Association belonging to the MD
+     * @param mepId The Id of the MEP
+     * @return 200 OK with details of the MEP or 500 on error
+     */
+    @GET
+    @Path("{mep_id}")
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response getMep(@PathParam("md_name") String mdName,
+            @PathParam("ma_name") String maName,
+            @PathParam("mep_id") short mepId) {
+        log.debug("GET called for MEP {}", mdName + "/" + maName + "/" + mepId);
+        try {
+            MdId mdId = MdIdCharStr.asMdId(mdName);
+            MaIdShort maId = MaIdCharStr.asMaId(maName);
+            MepEntry mepEntry = get(CfmMepService.class)
+                    .getMep(mdId, maId, MepId.valueOf(mepId));
+            if (mepEntry == null) {
+                return Response.serverError().entity("{ \"failure\":\"MEP " +
+                        mdName + "/" + maName + "/" + mepId + " not found\" }").build();
+            }
+            ObjectNode node = mapper().createObjectNode();
+            node.set("mep", codec(MepEntry.class).encode(mepEntry, this));
+            return ok(node).build();
+        } catch (CfmConfigException e) {
+            log.error("Get Mep {} failed because of exception",
+                    mdName + "/" + maName + "/" + mepId, e);
+            return Response.serverError().entity("{ \"failure\":\"" + e.toString() + "\" }").build();
+        }
+    }
+
+    /**
+     * Delete MEP by MD name, MA name and Mep Id.
+     *
+     * @param mdName The name of a Maintenance Domain
+     * @param maName The name of a Maintenance Association belonging to the MD
+     * @param mepIdShort The Id of the MEP
+     * @return 200 OK or 304 if not found, or 500 on error
+     */
+    @DELETE
+    @Path("{mep_id}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response deleteMep(@PathParam("md_name") String mdName,
+            @PathParam("ma_name") String maName,
+            @PathParam("mep_id") short mepIdShort) {
+        log.debug("DELETE called for MEP " + mdName + "/" + maName + "/" + mepIdShort);
+        try {
+            MdId mdId = MdIdCharStr.asMdId(mdName);
+            MaIdShort maId = MaIdCharStr.asMaId(maName);
+            boolean deleted = get(CfmMepService.class)
+                    .deleteMep(mdId, maId, MepId.valueOf(mepIdShort));
+            if (!deleted) {
+                return Response.notModified(mdName + "/" + maName + "/" +
+                        mepIdShort + " did not exist").build();
+            } else {
+                return ok("{ \"success\":\"deleted " + mdName + "/" + maName +
+                        "/" + mepIdShort + "\" }").build();
+            }
+        } catch (CfmConfigException e) {
+            log.error("Delete Mep {} failed because of exception ",
+                    mdName + "/" + maName + "/" + mepIdShort, e);
+            return Response.serverError().entity("{ \"failure\":\"" +
+                    e.toString() + "\" }").build();
+        }
+    }
+
+    /**
+     * Create MEP with MD name, MA name and Mep Json.
+     *
+     * @param mdName The name of a Maintenance Domain
+     * @param maName The name of a Maintenance Association belonging to the MD
+     * @param input A JSON formatted input stream specifying the Mep parameters
+     * @return 201 Created or 304 if already exists or 500 on error
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createMep(@PathParam("md_name") String mdName,
+                              @PathParam("ma_name") String maName, InputStream input) {
+        log.debug("POST called to Create Mep");
+        try {
+            MdId mdId = MdIdCharStr.asMdId(mdName);
+            MaIdShort maId = MaIdCharStr.asMaId(maName);
+            MaintenanceAssociation ma = get(CfmMdService.class).getMaintenanceAssociation(mdId, maId).get();
+
+            ObjectMapper mapper = new ObjectMapper();
+            JsonNode cfg = mapper.readTree(input);
+            JsonCodec<Mep> mepCodec = codec(Mep.class);
+
+            Mep mep = ((MepCodec) mepCodec).decode((ObjectNode) cfg, this, mdName, maName);
+
+            Boolean issuccess = get(CfmMepService.class).createMep(mdId, maId, mep);
+            if (!issuccess) {
+                return Response.notModified(mdName + "/" + ma.maId() + "/" + mep.mepId() +
+                        " already exists").build();
+            }
+            return Response
+                    .created(new URI("md/" + mdName + "/ma/" + ma.maId() + "/mep/" + mep.mepId()))
+                    .entity("{ \"success\":\"mep " + mdName + "/" + ma.maId() + "/" + mep.mepId() + " created\" }")
+                    .build();
+        } catch (Exception | CfmConfigException e) {
+            log.error("Create Mep on " + mdName + "/" + maName + " failed because of exception {}",
+                      e.toString());
+            return Response.serverError()
+                    .entity("{ \"failure\":\"" + e.toString() + "\" }")
+                    .build();
+        }
+    }
+
+    /**
+     * Transmit Loopback on MEP with MD name, MA name and Mep Id.
+     *
+     * @param mdName The name of a Maintenance Domain
+     * @param maName The name of a Maintenance Association belonging to the MD
+     * @param mepIdShort The id of a MEP belonging to the MA
+     * @param input A JSON formatted input stream specifying the Mep parameters
+     * @return 202 Received with success message or 500 on error
+     */
+    @PUT
+    @Path("{mep_id}/transmit-loopback")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response transmitLoopback(
+            @PathParam("md_name") String mdName,
+            @PathParam("ma_name") String maName,
+            @PathParam("mep_id") short mepIdShort,
+            InputStream input) {
+        log.debug("PUT called to Transmit Loopback on Mep");
+
+        MdId mdId = MdIdCharStr.asMdId(mdName);
+        MaIdShort maId = MaIdCharStr.asMaId(maName);
+        MaintenanceDomain md = get(CfmMdService.class).getMaintenanceDomain(mdId).get();
+        MaintenanceAssociation ma = get(CfmMdService.class)
+                .getMaintenanceAssociation(mdId, maId).get();
+
+        MepId mepId = MepId.valueOf(mepIdShort);
+
+        try {
+            ObjectMapper mapper = new ObjectMapper();
+            JsonNode cfg = mapper.readTree(input);
+            JsonCodec<MepLbCreate> mepLbCreateCodec = codec(MepLbCreate.class);
+
+            MepLbCreate lbCreate = mepLbCreateCodec.decode((ObjectNode) cfg, this);
+            get(CfmMepService.class).transmitLoopback(md.mdId(), ma.maId(), mepId, lbCreate);
+        } catch (Exception | CfmConfigException e) {
+            log.error("Transmit Loopback on " + mdName + "/" + maName +
+                    "/{} failed", String.valueOf(mepIdShort), e);
+            return Response.serverError()
+                  .entity("{ \"failure\":\"" + e.toString() + "\" }")
+                  .build();
+        }
+
+        return Response.accepted()
+            .entity("{ \"success\":\"Loopback on MEP " + mdName + "/" + ma.maId() + "/"
+                    + mepId.id() + " started\" }").build();
+    }
+
+    /**
+     * Abort Loopback on MEP with MD name, MA name and Mep Id.
+     *
+     * @param mdName The name of a Maintenance Domain
+     * @param maName The name of a Maintenance Association belonging to the MD
+     * @param mepIdShort The id of a MEP belonging to the MA
+     * @return 202 Received with success message or 500 on error
+     */
+    @PUT
+    @Path("{mep_id}/abort-loopback")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response abortLoopback(
+            @PathParam("md_name") String mdName,
+            @PathParam("ma_name") String maName,
+            @PathParam("mep_id") short mepIdShort) {
+        log.debug("PUT called to Abort Loopback on Mep");
+
+        MdId mdId = MdIdCharStr.asMdId(mdName);
+        MaIdShort maId = MaIdCharStr.asMaId(maName);
+        MaintenanceDomain md = get(CfmMdService.class).getMaintenanceDomain(mdId).get();
+        MaintenanceAssociation ma = get(CfmMdService.class)
+                .getMaintenanceAssociation(mdId, maId).get();
+
+        MepId mepId = MepId.valueOf(mepIdShort);
+
+        try {
+            get(CfmMepService.class).abortLoopback(md.mdId(), ma.maId(), mepId);
+        } catch (CfmConfigException e) {
+            log.error("Abort Loopback on " + mdName + "/" + maName +
+                    "/{} failed", String.valueOf(mepIdShort), e);
+            return Response.serverError()
+                  .entity("{ \"failure\":\"" + e.toString() + "\" }")
+                  .build();
+        }
+
+        return Response.accepted()
+            .entity("{ \"success\":\"Loopback on MEP " + mdName + "/" + ma.maId() + "/"
+                    + mepId.id() + " aborted\" }").build();
+    }
+
+    /**
+     * Transmit Linktrace on MEP with MD name, MA name and Mep Id.
+     *
+     * @param mdName The name of a Maintenance Domain
+     * @param maName The name of a Maintenance Association belonging to the MD
+     * @param mepIdShort The id of a MEP belonging to the MA
+     * @param input A JSON formatted input stream specifying the Linktrace parameters
+     * @return 202 Received with success message or 500 on error
+     */
+    @PUT
+    @Path("{mep_id}/transmit-linktrace")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response transmitLinktrace(
+            @PathParam("md_name") String mdName,
+            @PathParam("ma_name") String maName,
+            @PathParam("mep_id") short mepIdShort,
+            InputStream input) {
+        log.debug("PUT called to Transmit Linktrace on Mep");
+
+        MdId mdId = MdIdCharStr.asMdId(mdName);
+        MaIdShort maId = MaIdCharStr.asMaId(maName);
+        MaintenanceDomain md = get(CfmMdService.class).getMaintenanceDomain(mdId).get();
+        MaintenanceAssociation ma = get(CfmMdService.class)
+                .getMaintenanceAssociation(mdId, maId).get();
+
+        MepId mepId = MepId.valueOf(mepIdShort);
+
+        try {
+            ObjectMapper mapper = new ObjectMapper();
+            JsonNode cfg = mapper.readTree(input);
+            JsonCodec<MepLtCreate> mepLtCreateCodec = codec(MepLtCreate.class);
+
+            MepLtCreate ltCreate = mepLtCreateCodec.decode((ObjectNode) cfg, this);
+            get(CfmMepService.class).transmitLinktrace(md.mdId(), ma.maId(), mepId, ltCreate);
+        } catch (Exception | CfmConfigException e) {
+            log.error("Transmit Linktrace on " + mdName + "/" + maName +
+                    "/{} failed", String.valueOf(mepIdShort), e);
+            return Response.serverError()
+                    .entity("{ \"failure\":\"" + e.toString() + "\" }")
+                    .build();
+        }
+
+        return Response.accepted()
+                .entity("{ \"success\":\"Linktrace on MEP " + mdName + "/" + ma.maId() + "/"
+                        + mepId.id() + " started\" }").build();
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/impl/package-info.java b/apps/cfm/src/main/java/org/onosproject/cfm/impl/package-info.java
new file mode 100644
index 0000000..798317d
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/impl/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.
+ */
+/**
+ * REST Web Application for CFM.
+ */
+package org.onosproject.cfm.impl;
\ No newline at end of file
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/package-info.java b/apps/cfm/src/main/java/org/onosproject/cfm/package-info.java
new file mode 100644
index 0000000..2522fb4
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.
+ */
+/**
+ * REST Web Component for CFM and SOAM.
+ */
+package org.onosproject.cfm;
\ No newline at end of file
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/web/ComponentCodec.java b/apps/cfm/src/main/java/org/onosproject/cfm/web/ComponentCodec.java
new file mode 100644
index 0000000..6a53497
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/web/ComponentCodec.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.web;
+
+import static org.onlab.util.Tools.nullIsIllegal;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.onlab.packet.VlanId;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.cfm.Component;
+import org.onosproject.incubator.net.l2monitoring.cfm.DefaultComponent;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Encode and decode to/from JSON to Component object.
+ */
+public class ComponentCodec extends JsonCodec<Component> {
+
+    private static final String COMPONENT_ID = "component-id";
+    private static final String COMPONENT = "component";
+    private static final String VID_LIST = "vid-list";
+    private static final String TAG_TYPE = "tag-type";
+    private static final String MHF_CREATION_TYPE = "mhf-creation-type";
+    private static final String ID_PERMISSION = "id-permission";
+
+    @Override
+    public ObjectNode encode(Component component, CodecContext context) {
+
+        ObjectNode node = context.mapper().createObjectNode()
+                .put(COMPONENT_ID, component.componentId());
+
+        node.set(VID_LIST, new VidCodec().encode(component.vidList(), context));
+
+        if (component.mhfCreationType() != null) {
+            node.put(MHF_CREATION_TYPE, component.mhfCreationType().name());
+        }
+        if (component.idPermission() != null) {
+            node.put(ID_PERMISSION, component.idPermission().name());
+        }
+        if (component.tagType() != null) {
+            node.put(TAG_TYPE, component.tagType().name());
+        }
+
+        return (ObjectNode) context.mapper().createObjectNode().set(COMPONENT, node);
+    }
+
+    @Override
+    public ArrayNode encode(Iterable<Component> components, CodecContext context) {
+        ArrayNode an = context.mapper().createArrayNode();
+        components.forEach(component -> {
+            an.add(encode(component, context));
+        });
+        return an;
+    }
+
+    @Override
+    public Component decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        JsonNode componentNode = json.get(COMPONENT);
+
+        int componentId = nullIsIllegal(componentNode.get(COMPONENT_ID),
+                "component-id is required").asInt();
+        Component.ComponentBuilder componentBuilder =
+                DefaultComponent.builder(componentId);
+
+        List<VlanId> vidList = (new VidCodec()).decode((ArrayNode)
+                nullIsIllegal(componentNode.get(VID_LIST), "vid-list is required"), context);
+        if (vidList == null || vidList.size() < 1) {
+            throw new IllegalArgumentException("A least one VID is required in component: " + componentId);
+        }
+        for (VlanId vid:vidList) {
+            componentBuilder = componentBuilder.addToVidList(vid);
+        }
+
+        if (componentNode.get(TAG_TYPE) != null) {
+            componentBuilder = componentBuilder
+                    .tagType(Component.TagType.valueOf(
+                            componentNode.get(TAG_TYPE).asText()));
+        }
+
+        if (componentNode.get(MHF_CREATION_TYPE) != null) {
+            componentBuilder = componentBuilder
+                    .mhfCreationType(Component.MhfCreationType.valueOf(
+                            componentNode.get(MHF_CREATION_TYPE).asText()));
+        }
+        if (componentNode.get(ID_PERMISSION) != null) {
+            componentBuilder = componentBuilder
+                    .idPermission(Component.IdPermissionType.valueOf(
+                            componentNode.get(ID_PERMISSION).asText()));
+        }
+
+        return componentBuilder.build();
+    }
+
+    @Override
+    public List<Component> decode(ArrayNode json, CodecContext context) {
+        List<Component> componentList = new ArrayList<>();
+        json.forEach(node -> componentList.add(decode((ObjectNode) node, context)));
+        return componentList;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/web/FngAddressCodec.java b/apps/cfm/src/main/java/org/onosproject/cfm/web/FngAddressCodec.java
new file mode 100644
index 0000000..47cc9b6
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/web/FngAddressCodec.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.web;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpAddress;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.cfm.Mep;
+import org.onosproject.incubator.net.l2monitoring.cfm.Mep.FngAddress;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.nullIsIllegal;
+
+/**
+ * Encode and decode to/from JSON to FngAddress object.
+ */
+public class FngAddressCodec extends JsonCodec<FngAddress> {
+
+    @Override
+    public ObjectNode encode(FngAddress fngAddress, CodecContext context) {
+        checkNotNull(fngAddress, "FngAddress cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put("address-type", fngAddress.addressType().name());
+
+        if (fngAddress.addressType().equals(Mep.FngAddressType.IPV4) ||
+                fngAddress.addressType().equals(Mep.FngAddressType.IPV6)) {
+            result.put("ip-address", fngAddress.ipAddress().toString());
+        }
+
+        return result;
+    }
+
+    @Override
+    public FngAddress decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        JsonNode node = json.get("fng-address");
+
+        String addressType = nullIsIllegal(node.get("address-type"),
+                            "address type is required").asText();
+        Mep.FngAddressType type = Mep.FngAddressType.valueOf(addressType.toUpperCase());
+        JsonNode ipAddressNode = node.get("ipAddress");
+
+        switch (type) {
+            case IPV4:
+                return FngAddress.ipV4Address(Ip4Address.valueOf(ipAddressNode.asText()));
+            case IPV6:
+                return FngAddress.ipV6Address(Ip6Address.valueOf(ipAddressNode.asText()));
+            case NOT_TRANSMITTED:
+                return FngAddress.notTransmitted(IpAddress.valueOf(ipAddressNode.asText()));
+            case NOT_SPECIFIED:
+            default:
+                return FngAddress.notSpecified();
+        }
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/web/MaintenanceAssociationCodec.java b/apps/cfm/src/main/java/org/onosproject/cfm/web/MaintenanceAssociationCodec.java
new file mode 100644
index 0000000..62be35d
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/web/MaintenanceAssociationCodec.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.web;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.nullIsIllegal;
+
+import java.util.List;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.cfm.Component;
+import org.onosproject.incubator.net.l2monitoring.cfm.DefaultMaintenanceAssociation;
+import org.onosproject.incubator.net.l2monitoring.cfm.MaintenanceAssociation;
+import org.onosproject.incubator.net.l2monitoring.cfm.MaintenanceAssociation.CcmInterval;
+import org.onosproject.incubator.net.l2monitoring.cfm.MaintenanceAssociation.MaBuilder;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaId2Octet;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdIccY1731;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdPrimaryVid;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdRfc2685VpnId;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdShort;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MepId;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmConfigException;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Encode and decode to/from JSON to MaintenanceAssociation object.
+ */
+public class MaintenanceAssociationCodec extends JsonCodec<MaintenanceAssociation> {
+
+    private static final String MA_NAME_TYPE = "maNameType";
+    private static final String MA_NUMERIC_ID = "maNumericId";
+    private static final String MA_NAME = "maName";
+    private static final String CCM_INTERVAL = "ccm-interval";
+    private static final String COMPONENT_LIST = "component-list";
+    private static final String RMEP_LIST = "rmep-list";
+    private static final String MA = "ma";
+
+    @Override
+    public ObjectNode encode(MaintenanceAssociation ma, CodecContext context) {
+        checkNotNull(ma, "Maintenance Association cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put(MA_NAME, ma.maId().toString())
+                .put(MA_NAME_TYPE, ma.maId().nameType().name());
+        if (ma.maNumericId() > 0) {
+            result = result.put(MA_NUMERIC_ID, ma.maNumericId());
+        }
+        if (ma.ccmInterval() != null) {
+            result = result.put(CCM_INTERVAL, ma.ccmInterval().name());
+        }
+
+        result.set(COMPONENT_LIST, new ComponentCodec().encode(ma.componentList(), context));
+        result.set(RMEP_LIST, new RMepCodec().encode(ma.remoteMepIdList(), context));
+
+        return result;
+    }
+
+    public MaintenanceAssociation decode(ObjectNode json, CodecContext context, int mdNameLen) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        JsonNode maNode = json.get(MA);
+
+        String maName = nullIsIllegal(maNode.get(MA_NAME), "maName is required").asText();
+        String maNameType = MaIdShort.MaIdType.CHARACTERSTRING.name();
+        if (maNode.get(MA_NAME_TYPE) != null) {
+            maNameType = maNode.get(MA_NAME_TYPE).asText();
+        }
+
+        try {
+            MaIdShort maId = null;
+            MaIdShort.MaIdType maIdType = MaIdShort.MaIdType.valueOf(maNameType);
+            switch (maIdType) {
+                case PRIMARYVID:
+                    maId = MaIdPrimaryVid.asMaId(maName);
+                    break;
+                case TWOOCTET:
+                    maId = MaId2Octet.asMaId(maName);
+                    break;
+                case RFC2685VPNID:
+                    maId = MaIdRfc2685VpnId.asMaIdHex(maName);
+                    break;
+                case ICCY1731:
+                    maId = MaIdIccY1731.asMaId(maName);
+                    break;
+                case CHARACTERSTRING:
+                default:
+                    maId = MaIdCharStr.asMaId(maName);
+            }
+            MaBuilder builder =
+                    DefaultMaintenanceAssociation.builder(maId, mdNameLen);
+
+            JsonNode maNumericIdNode = maNode.get(MA_NUMERIC_ID);
+            if (maNumericIdNode != null) {
+                short mdNumericId = (short) maNumericIdNode.asInt();
+                builder = builder.maNumericId(mdNumericId);
+            }
+            if (maNode.get(CCM_INTERVAL) != null) {
+                builder.ccmInterval(CcmInterval.valueOf(maNode.get(CCM_INTERVAL).asText()));
+            }
+
+            List<Component> componentList = (new ComponentCodec()).decode((ArrayNode)
+                    nullIsIllegal(maNode.get(COMPONENT_LIST),
+                            "component-list is required"), context);
+            for (Component component:componentList) {
+                builder = builder.addToComponentList(component);
+            }
+
+            List<MepId> remoteMeps = (new RMepCodec()).decode(
+                    (ArrayNode) nullIsIllegal(maNode.get(RMEP_LIST), "rmep-list is required"), context);
+            for (MepId remoteMep:remoteMeps) {
+                builder = builder.addToRemoteMepIdList(remoteMep);
+            }
+
+            return builder.build();
+        } catch (CfmConfigException e) {
+            throw new IllegalArgumentException(e);
+        }
+
+    }
+
+    @Override
+    public ArrayNode encode(Iterable<MaintenanceAssociation> maEntities, CodecContext context) {
+        ArrayNode an = context.mapper().createArrayNode();
+        maEntities.forEach(ma -> an.add(encode(ma, context)));
+        return an;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/web/MaintenanceDomainCodec.java b/apps/cfm/src/main/java/org/onosproject/cfm/web/MaintenanceDomainCodec.java
new file mode 100644
index 0000000..a6bcf01
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/web/MaintenanceDomainCodec.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.web;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.nullIsIllegal;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.cfm.DefaultMaintenanceDomain;
+import org.onosproject.incubator.net.l2monitoring.cfm.MaintenanceDomain;
+import org.onosproject.incubator.net.l2monitoring.cfm.MaintenanceDomain.MdLevel;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdId;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdDomainName;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdMacUint;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdNone;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmConfigException;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Encode and decode to/from JSON to MaintenanceDomain object.
+ */
+public class MaintenanceDomainCodec extends JsonCodec<MaintenanceDomain> {
+
+    private static final String MD_LEVEL = "mdLevel";
+    private static final String MD_NUMERIC_ID = "mdNumericId";
+    private static final String MD = "md";
+    private static final String MD_NAME = "mdName";
+    private static final String MD_NAME_TYPE = "mdNameType";
+
+    public MaintenanceDomainCodec() {
+        super();
+
+    }
+
+    @Override
+    public ObjectNode encode(MaintenanceDomain md, CodecContext context) {
+        checkNotNull(md, "Maintenance Domain cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put(MD_NAME, md.mdId().toString())
+                .put(MD_NAME_TYPE, md.mdId().nameType().name())
+                .put(MD_LEVEL, md.mdLevel().name());
+        if (md.mdNumericId() > 0) {
+            result = result.put(MD_NUMERIC_ID, md.mdNumericId());
+        }
+        result.set("maList",
+                new MaintenanceAssociationCodec()
+                .encode(md.maintenanceAssociationList(), context));
+
+        return result;
+    }
+
+    @Override
+    public MaintenanceDomain decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        JsonNode mdNode = json.get(MD);
+
+        String mdName = nullIsIllegal(mdNode.get(MD_NAME), "mdName is required").asText();
+        String mdNameType = MdId.MdNameType.CHARACTERSTRING.name();
+        if (mdNode.get(MD_NAME_TYPE) != null) {
+            mdNameType = mdNode.get(MD_NAME_TYPE).asText();
+        }
+
+        try {
+            MdId mdId = null;
+            MdId.MdNameType nameType =
+                    MdId.MdNameType.valueOf(mdNameType);
+            switch (nameType) {
+                case DOMAINNAME:
+                    mdId = MdIdDomainName.asMdId(mdName);
+                    break;
+                case MACANDUINT:
+                    mdId = MdIdMacUint.asMdId(mdName);
+                    break;
+                case NONE:
+                    mdId = MdIdNone.asMdId();
+                    break;
+                case CHARACTERSTRING:
+                default:
+                    mdId = MdIdCharStr.asMdId(mdName);
+            }
+
+            MaintenanceDomain.MdBuilder builder = DefaultMaintenanceDomain.builder(mdId);
+            JsonNode mdLevelNode = mdNode.get(MD_LEVEL);
+            if (mdLevelNode != null) {
+                MdLevel mdLevel = MdLevel.valueOf(mdLevelNode.asText());
+                builder = builder.mdLevel(mdLevel);
+            }
+            JsonNode mdNumericIdNode = mdNode.get(MD_NUMERIC_ID);
+            if (mdNumericIdNode != null) {
+                short mdNumericId = (short) mdNumericIdNode.asInt();
+                builder = builder.mdNumericId(mdNumericId);
+            }
+
+            return builder.build();
+        } catch (CfmConfigException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    @Override
+    public ArrayNode encode(Iterable<MaintenanceDomain> mdEntities, CodecContext context) {
+        ArrayNode an = context.mapper().createArrayNode();
+        mdEntities.forEach(md -> an.add(encode(md, context)));
+        return an;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/web/MepCodec.java b/apps/cfm/src/main/java/org/onosproject/cfm/web/MepCodec.java
new file mode 100644
index 0000000..73a58b6
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/web/MepCodec.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.web;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.nullIsIllegal;
+
+import org.onlab.packet.VlanId;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.cfm.DefaultMep;
+import org.onosproject.incubator.net.l2monitoring.cfm.Mep;
+import org.onosproject.incubator.net.l2monitoring.cfm.Mep.MepBuilder;
+import org.onosproject.incubator.net.l2monitoring.cfm.Mep.MepDirection;
+import org.onosproject.incubator.net.l2monitoring.cfm.Mep.Priority;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdShort;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdId;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MepId;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmConfigException;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import java.time.Duration;
+
+/**
+ * Encode and decode to/from JSON to Mep object.
+ */
+public class MepCodec extends JsonCodec<Mep> {
+    private static final String ADMINISTRATIVE_STATE = "administrative-state";
+    private static final String PRIMARY_VID = "primary-vid";
+    private static final String CCM_LTM_PRIORITY = "ccm-ltm-priority";
+    private static final String CCI_ENABLED = "cci-enabled";
+    private static final String FNG_ADDRESS = "fng-address";
+    private static final String LOWEST_FAULT_PRIORITY_DEFECT = "lowest-fault-priority-defect";
+    private static final String DEFECT_PRESENT_TIME = "defect-present-time";
+    private static final String DEFECT_ABSENT_TIME = "defect-absent-time";
+
+     public Mep decode(ObjectNode json, CodecContext context, String
+                        mdName, String maName) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        JsonNode mepNode = json.get("mep");
+
+        int mepId = Integer.parseInt(
+                nullIsIllegal(mepNode.get("mepId"), "mepId is required").asText());
+        DeviceId deviceId = DeviceId.deviceId(
+                nullIsIllegal(mepNode.get("deviceId"), "deviceId is required")
+                .asText());
+        PortNumber port = PortNumber
+                .portNumber(Long.parseLong(
+                        nullIsIllegal(mepNode.get("port"), "port is required")
+                        .asText()));
+        MepDirection direction = MepDirection.valueOf(
+                nullIsIllegal(mepNode.get("direction"), "direction is required").
+                asText());
+
+        try {
+            MdId mdId = MdIdCharStr.asMdId(mdName);
+            MaIdShort maId = MaIdCharStr.asMaId(maName);
+            MepBuilder mepBuilder = DefaultMep
+                    .builder(MepId.valueOf((short) mepId),
+                            deviceId, port, direction, mdId, maId);
+
+            if (mepNode.get(PRIMARY_VID) != null) {
+                mepBuilder.primaryVid(VlanId.vlanId(
+                        (short) mepNode.get(PRIMARY_VID).asInt(0)));
+            }
+
+            if (mepNode.get(ADMINISTRATIVE_STATE) != null) {
+                mepBuilder.administrativeState(mepNode.get(ADMINISTRATIVE_STATE)
+                        .asBoolean());
+            }
+
+            if (mepNode.get(CCM_LTM_PRIORITY) != null) {
+                mepBuilder.ccmLtmPriority(
+                        Priority.values()[mepNode.get(CCM_LTM_PRIORITY).asInt(0)]);
+            }
+
+            if (mepNode.get(CCI_ENABLED) != null) {
+                mepBuilder.cciEnabled(mepNode.get(CCI_ENABLED).asBoolean());
+            }
+
+            if (mepNode.get(LOWEST_FAULT_PRIORITY_DEFECT) != null) {
+                mepBuilder.lowestFaultPriorityDefect(
+                        Mep.LowestFaultDefect.values()[mepNode.get(LOWEST_FAULT_PRIORITY_DEFECT).asInt()]);
+            }
+
+            if (mepNode.get(DEFECT_ABSENT_TIME) != null) {
+                mepBuilder.defectAbsentTime(
+                        Duration.parse(mepNode.get(DEFECT_ABSENT_TIME).asText()));
+            }
+
+            if (mepNode.get(DEFECT_PRESENT_TIME) != null) {
+                mepBuilder.defectPresentTime(
+                        Duration.parse(mepNode.get(DEFECT_PRESENT_TIME).asText()));
+            }
+
+            if (mepNode.get(FNG_ADDRESS) != null) {
+                mepBuilder.fngAddress((new FngAddressCodec())
+                        .decode((ObjectNode) mepNode, context));
+            }
+
+
+            return mepBuilder.build();
+        } catch (CfmConfigException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    @Override
+    public ObjectNode encode(Mep mep, CodecContext context) {
+        checkNotNull(mep, "Mep cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put("mepId", mep.mepId().id())
+                .put("deviceId", mep.deviceId().toString())
+                .put("port", mep.port().toLong())
+                .put("direction", mep.direction().name())
+                .put("mdName", mep.mdId().toString())
+                .put("maName", mep.maId().toString())
+                .put(ADMINISTRATIVE_STATE, mep.administrativeState())
+                .put(CCI_ENABLED, mep.cciEnabled());
+        if (mep.ccmLtmPriority() != null) {
+            result.put(CCM_LTM_PRIORITY, mep.ccmLtmPriority().ordinal());
+        }
+        if (mep.primaryVid() != null) {
+            result.put(PRIMARY_VID, mep.primaryVid().toShort());
+        }
+        if (mep.fngAddress() != null) {
+            result.put(FNG_ADDRESS, new FngAddressCodec().encode(mep.fngAddress(), context));
+        }
+        if (mep.lowestFaultPriorityDefect() != null) {
+            result.put(LOWEST_FAULT_PRIORITY_DEFECT, mep.lowestFaultPriorityDefect().ordinal());
+        }
+        if (mep.defectPresentTime() != null) {
+            result.put(DEFECT_PRESENT_TIME, mep.defectPresentTime().toString());
+        }
+        if (mep.defectAbsentTime() != null) {
+            result.put(DEFECT_ABSENT_TIME, mep.defectAbsentTime().toString());
+        }
+
+        return result;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/web/MepEntryCodec.java b/apps/cfm/src/main/java/org/onosproject/cfm/web/MepEntryCodec.java
new file mode 100644
index 0000000..d1618a7
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/web/MepEntryCodec.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.web;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Iterator;
+import java.util.Map.Entry;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.cfm.Mep;
+import org.onosproject.incubator.net.l2monitoring.cfm.MepEntry;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Encode and decode to/from JSON to MepEntry object.
+ */
+public class MepEntryCodec extends JsonCodec<MepEntry> {
+
+    @Override
+    public ObjectNode encode(MepEntry mepEntry, CodecContext context) {
+        checkNotNull(mepEntry, "Mep cannot be null");
+
+        ObjectNode result = context.mapper().createObjectNode();
+
+        //Get the common attributes
+        Mep mep = (Mep) mepEntry;
+        ObjectNode mepAttrs = new MepCodec().encode(mep, context);
+        Iterator<Entry<String, JsonNode>> elements = mepAttrs.fields();
+        while (elements.hasNext()) {
+            Entry<String, JsonNode> element = elements.next();
+            result.set(element.getKey(), element.getValue());
+        }
+
+        if (mepEntry.macAddress() != null) {
+            result.put("macAddress", mepEntry.macAddress().toString());
+        }
+
+        if (mepEntry.loopbackAttributes() != null) {
+            result.set("loopback", new MepLbEntryCodec()
+                    .encode(mepEntry.loopbackAttributes(), context));
+        }
+
+        if (mepEntry.activeRemoteMepList() != null) {
+            result.set("remoteMeps", new RemoteMepEntryCodec()
+                    .encode(mepEntry.activeRemoteMepList(), context));
+        }
+
+        if (mepEntry.activeErrorCcmDefect()) {
+            result.put("activeErrorCcmDefect", true);
+        }
+        if (mepEntry.activeMacStatusDefect()) {
+            result.put("activeMacStatusDefect", true);
+        }
+        if (mepEntry.activeRdiCcmDefect()) {
+            result.put("activeRdiCcmDefect", true);
+        }
+        if (mepEntry.activeRemoteCcmDefect()) {
+            result.put("activeRemoteCcmDefect", true);
+        }
+        if (mepEntry.activeXconCcmDefect()) {
+            result.put("activeXconCcmDefect", true);
+        }
+        return result;
+    }
+
+    @Override
+    public ArrayNode encode(Iterable<MepEntry> mepEntryEntities, CodecContext context) {
+        ArrayNode an = context.mapper().createArrayNode();
+        if (mepEntryEntities != null) {
+            mepEntryEntities.forEach(mepEntry -> an.add(encode(mepEntry, context)));
+        }
+        return an;
+    }
+
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/web/MepLbCreateCodec.java b/apps/cfm/src/main/java/org/onosproject/cfm/web/MepLbCreateCodec.java
new file mode 100644
index 0000000..e29415c
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/web/MepLbCreateCodec.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.web;
+
+import org.onlab.packet.MacAddress;
+import org.onlab.util.HexString;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.cfm.DefaultMepLbCreate;
+import org.onosproject.incubator.net.l2monitoring.cfm.Mep.Priority;
+import org.onosproject.incubator.net.l2monitoring.cfm.MepLbCreate;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MepId;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Encode and decode to/from JSON to MepLbCreate object.
+ */
+public class MepLbCreateCodec extends JsonCodec<MepLbCreate> {
+
+    public static final String NUMBER_MESSAGES = "numberMessages";
+    public static final String REMOTE_MEP_ID = "remoteMepId";
+    public static final String REMOTE_MEP_MAC = "remoteMepMac";
+    public static final String DATA_TLV_HEX = "dataTlvHex";
+    public static final String VLAN_DROP_ELIGIBLE = "vlanDropEligible";
+    public static final String VLAN_PRIORITY = "vlanPriority";
+    public static final String LOOPBACK = "loopback";
+
+    @Override
+    public ObjectNode encode(MepLbCreate mepLbCreate, CodecContext context) {
+        checkNotNull(mepLbCreate, "Mep Lb Create cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put(NUMBER_MESSAGES, mepLbCreate.numberMessages());
+
+        if (mepLbCreate.remoteMepId() != null) {
+            result.put(REMOTE_MEP_ID, mepLbCreate.remoteMepId().value());
+        } else {
+            result.put(REMOTE_MEP_MAC, mepLbCreate.remoteMepAddress().toString());
+        }
+
+        if (mepLbCreate.dataTlvHex() != null) {
+            result.put(DATA_TLV_HEX, mepLbCreate.dataTlvHex());
+        }
+        if (mepLbCreate.vlanDropEligible() != null) {
+            result.put(VLAN_DROP_ELIGIBLE, mepLbCreate.vlanDropEligible());
+        }
+        if (mepLbCreate.vlanPriority() != null) {
+            result.put(VLAN_PRIORITY, mepLbCreate.vlanPriority().ordinal());
+        }
+        return result;
+    }
+
+
+    @Override
+    public MepLbCreate decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        JsonNode loopbackNode = json.get(LOOPBACK);
+
+        JsonNode remoteMepIdNode = loopbackNode.get(REMOTE_MEP_ID);
+        JsonNode remoteMepMacNode = loopbackNode.get(REMOTE_MEP_MAC);
+
+        MepLbCreate.MepLbCreateBuilder lbCreateBuilder = null;
+        if (remoteMepIdNode != null) {
+            MepId remoteMepId = MepId.valueOf((short) remoteMepIdNode.asInt());
+            lbCreateBuilder = DefaultMepLbCreate.builder(remoteMepId);
+        } else if (remoteMepMacNode != null) {
+            MacAddress remoteMepMac = MacAddress.valueOf(
+                                            remoteMepMacNode.asText());
+            lbCreateBuilder = DefaultMepLbCreate.builder(remoteMepMac);
+        } else {
+            throw new IllegalArgumentException(
+                    "Either a remoteMepId or a remoteMepMac");
+        }
+
+        JsonNode numMessagesNode = loopbackNode.get(NUMBER_MESSAGES);
+        if (numMessagesNode != null) {
+            int numMessages = numMessagesNode.asInt();
+            lbCreateBuilder.numberMessages(numMessages);
+        }
+
+        JsonNode vlanDropEligibleNode = loopbackNode.get(VLAN_DROP_ELIGIBLE);
+        if (vlanDropEligibleNode != null) {
+            boolean vlanDropEligible = vlanDropEligibleNode.asBoolean();
+            lbCreateBuilder.vlanDropEligible(vlanDropEligible);
+        }
+
+        JsonNode vlanPriorityNode = loopbackNode.get(VLAN_PRIORITY);
+        if (vlanPriorityNode != null) {
+            short vlanPriority = (short) vlanPriorityNode.asInt();
+            lbCreateBuilder.vlanPriority(Priority.values()[vlanPriority]);
+        }
+
+        JsonNode dataTlvHexNode = loopbackNode.get(DATA_TLV_HEX);
+        if (dataTlvHexNode != null) {
+            String dataTlvHex = loopbackNode.get(DATA_TLV_HEX).asText();
+            if (!dataTlvHex.isEmpty()) {
+                lbCreateBuilder.dataTlv(HexString.fromHexString(dataTlvHex));
+            }
+        }
+
+        return lbCreateBuilder.build();
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/web/MepLbEntryCodec.java b/apps/cfm/src/main/java/org/onosproject/cfm/web/MepLbEntryCodec.java
new file mode 100644
index 0000000..62409cd
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/web/MepLbEntryCodec.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.web;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.cfm.MepLbEntry;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Encode and decode to/from JSON to MepLbEntry object.
+ */
+public class MepLbEntryCodec extends JsonCodec<MepLbEntry> {
+
+    @Override
+    public ObjectNode encode(MepLbEntry mepLbEntry, CodecContext context) {
+        checkNotNull(mepLbEntry, "Mep Lb Entry cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put("nextLbmIdentifier", mepLbEntry.nextLbmIdentifier())
+                .put("countLbrTransmitted", mepLbEntry.countLbrTransmitted())
+                .put("countLbrReceived", mepLbEntry.countLbrReceived())
+                .put("countLbrValidInOrder", mepLbEntry.countLbrValidInOrder())
+                .put("countLbrValidOutOfOrder", mepLbEntry.countLbrValidOutOfOrder())
+                .put("countLbrMacMisMatch", mepLbEntry.countLbrMacMisMatch());
+
+        return result;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/web/MepLtCreateCodec.java b/apps/cfm/src/main/java/org/onosproject/cfm/web/MepLtCreateCodec.java
new file mode 100644
index 0000000..0141a73
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/web/MepLtCreateCodec.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.web;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.packet.MacAddress;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.cfm.DefaultMepLtCreate;
+import org.onosproject.incubator.net.l2monitoring.cfm.MepLtCreate;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MepId;
+
+import java.util.BitSet;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Encode and decode to/from JSON to MepLtCreate object.
+ */
+public class MepLtCreateCodec extends JsonCodec<MepLtCreate> {
+
+    private static final String REMOTE_MEP_ID = "remoteMepId";
+    private static final String REMOTE_MEP_MAC = "remoteMepMac";
+    private static final String DEFAULT_TTL = "defaultTtl";
+    private static final String TRANSMIT_LTM_FLAGS = "transmitLtmFlags";
+    private static final String LINKTRACE = "linktrace";
+    private static final String USE_FDB_ONLY = "use-fdb-only";
+
+    @Override
+    public ObjectNode encode(MepLtCreate mepLtCreate, CodecContext context) {
+        checkNotNull(mepLtCreate, "Mep Lt Create cannot be null");
+        ObjectNode result = context.mapper().createObjectNode();
+
+        if (mepLtCreate.remoteMepId() != null) {
+            result.put(REMOTE_MEP_ID, mepLtCreate.remoteMepId().value());
+        } else {
+            result.put(REMOTE_MEP_MAC, mepLtCreate.remoteMepAddress().toString());
+        }
+
+        if (mepLtCreate.defaultTtl() != null) {
+            result.put(DEFAULT_TTL, mepLtCreate.defaultTtl());
+        }
+        if (mepLtCreate.transmitLtmFlags() != null) {
+            result.put(TRANSMIT_LTM_FLAGS,
+                    mepLtCreate.transmitLtmFlags().get(0) ? USE_FDB_ONLY : "");
+        }
+
+        return result;
+    }
+
+
+    @Override
+    public MepLtCreate decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        JsonNode linktraceNode = json.get(LINKTRACE);
+
+        JsonNode remoteMepIdNode = linktraceNode.get(REMOTE_MEP_ID);
+        JsonNode remoteMepMacNode = linktraceNode.get(REMOTE_MEP_MAC);
+
+        MepLtCreate.MepLtCreateBuilder ltCreateBuilder = null;
+        if (remoteMepIdNode != null) {
+            MepId remoteMepId = MepId.valueOf((short) remoteMepIdNode.asInt());
+            ltCreateBuilder = DefaultMepLtCreate.builder(remoteMepId);
+        } else if (remoteMepMacNode != null) {
+            MacAddress remoteMepMac = MacAddress.valueOf(
+                                            remoteMepMacNode.asText());
+            ltCreateBuilder = DefaultMepLtCreate.builder(remoteMepMac);
+        } else {
+            throw new IllegalArgumentException(
+                    "Either a remoteMepId or a remoteMepMac");
+        }
+
+        JsonNode defaultTtlNode = linktraceNode.get(DEFAULT_TTL);
+        if (defaultTtlNode != null) {
+            short defaultTtl = (short) defaultTtlNode.asInt();
+            ltCreateBuilder.defaultTtl(defaultTtl);
+        }
+
+        JsonNode transmitLtmFlagsNode = linktraceNode.get(TRANSMIT_LTM_FLAGS);
+        if (transmitLtmFlagsNode != null) {
+            if (transmitLtmFlagsNode.asText().isEmpty()) {
+                ltCreateBuilder.transmitLtmFlags(BitSet.valueOf(new long[]{0}));
+            } else if (transmitLtmFlagsNode.asText().equals(USE_FDB_ONLY)) {
+               ltCreateBuilder.transmitLtmFlags(BitSet.valueOf(new long[]{1}));
+            } else {
+                throw new IllegalArgumentException("Expecting value 'use-fdb-only' " +
+                        "or '' for " + TRANSMIT_LTM_FLAGS);
+            }
+        }
+
+        return ltCreateBuilder.build();
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/web/RMepCodec.java b/apps/cfm/src/main/java/org/onosproject/cfm/web/RMepCodec.java
new file mode 100644
index 0000000..7edb204
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/web/RMepCodec.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.web;
+
+import static org.onlab.util.Tools.nullIsIllegal;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MepId;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Encode and decode to/from JSON to RMep object.
+ */
+public class RMepCodec extends JsonCodec<MepId> {
+
+    @Override
+    public ObjectNode encode(MepId rmep, CodecContext context) {
+        return context.mapper().createObjectNode().put("rmep", rmep.id());
+    }
+
+    @Override
+    public ArrayNode encode(Iterable<MepId> rmeps, CodecContext context) {
+        ArrayNode an = context.mapper().createArrayNode();
+        rmeps.forEach(rmep -> {
+            an.add(encode(rmep, context));
+        });
+        return an;
+    }
+
+    @Override
+    public MepId decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        JsonNode vidNode = json.get("rmep");
+
+        return MepId.valueOf(
+                    nullIsIllegal((short) vidNode.asInt(), "rmep is required"));
+
+    }
+
+    @Override
+    public List<MepId> decode(ArrayNode json, CodecContext context) {
+        List<MepId> rmepList = new ArrayList<>();
+        json.forEach(node -> rmepList.add(decode((ObjectNode) node, context)));
+        return rmepList;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/web/RemoteMepEntryCodec.java b/apps/cfm/src/main/java/org/onosproject/cfm/web/RemoteMepEntryCodec.java
new file mode 100644
index 0000000..4f8344a
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/web/RemoteMepEntryCodec.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.web;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.cfm.RemoteMepEntry;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Encode and decode to/from JSON to RemoteMepEntry object.
+ */
+public class RemoteMepEntryCodec extends JsonCodec<RemoteMepEntry> {
+
+    @Override
+    public ObjectNode encode(RemoteMepEntry remoteMepEntry, CodecContext context) {
+        checkNotNull(remoteMepEntry, "Mep cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put("remoteMepId", remoteMepEntry.remoteMepId().toString())
+                .put("remoteMepState", remoteMepEntry.state().name())
+                .put("rdi", remoteMepEntry.rdi());
+
+        if (remoteMepEntry.failedOrOkTime() != null) {
+            result = result.put("failedOrOkTime",
+                    remoteMepEntry.failedOrOkTime().toString());
+        }
+
+        if (remoteMepEntry.macAddress() != null) {
+            result = result.put("macAddress", remoteMepEntry.macAddress().toString());
+        }
+
+        if (remoteMepEntry.portStatusTlvType() != null) {
+            result = result.put("portStatusTlvType",
+                    remoteMepEntry.portStatusTlvType().name());
+        }
+        if (remoteMepEntry.interfaceStatusTlvType() != null) {
+            result = result.put("interfaceStatusTlvType",
+                    remoteMepEntry.interfaceStatusTlvType().name());
+        }
+        if (remoteMepEntry.senderIdTlvType() != null) {
+            result = result.put("senderIdTlvType",
+                    remoteMepEntry.senderIdTlvType().name());
+        }
+
+        return result;
+    }
+
+    @Override
+    public ArrayNode encode(Iterable<RemoteMepEntry> remoteMepEntries,
+            CodecContext context) {
+        ArrayNode an = context.mapper().createArrayNode();
+        remoteMepEntries.forEach(remoteMepEntry ->
+                        an.add(encode(remoteMepEntry, context)));
+        return an;
+    }
+
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/web/VidCodec.java b/apps/cfm/src/main/java/org/onosproject/cfm/web/VidCodec.java
new file mode 100644
index 0000000..a673719
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/web/VidCodec.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.web;
+
+import static org.onlab.util.Tools.nullIsIllegal;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.onlab.packet.VlanId;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Encode and decode to/from JSON to Vid object.
+ */
+public class VidCodec extends JsonCodec<VlanId> {
+
+    @Override
+    public ObjectNode encode(VlanId vid, CodecContext context) {
+        return context.mapper().createObjectNode().put("vid", vid.toString());
+    }
+
+    @Override
+    public ArrayNode encode(Iterable<VlanId> vids, CodecContext context) {
+        ArrayNode an = context.mapper().createArrayNode();
+        vids.forEach(vid -> {
+            an.add(encode(vid, context));
+        });
+        return an;
+    }
+
+    @Override
+    public VlanId decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        JsonNode vidNode = json.get("vid");
+
+        int vid = (nullIsIllegal(vidNode.asInt(), "vid is required"));
+        if (vid < 0 || vid > 4095) {
+            throw new IllegalArgumentException("VID values must be between 0 and 4095");
+        }
+        return VlanId.vlanId((short) vid);
+    }
+
+    @Override
+    public List<VlanId> decode(ArrayNode json, CodecContext context) {
+        List<VlanId> vidList = new ArrayList<>();
+        json.forEach(node -> vidList.add(decode((ObjectNode) node, context)));
+        return vidList;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/cfm/web/package-info.java b/apps/cfm/src/main/java/org/onosproject/cfm/web/package-info.java
new file mode 100644
index 0000000..265a1fa
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/cfm/web/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.
+ */
+/**
+ * Codecs for converting CFM objects to and from JSON.
+ */
+package org.onosproject.cfm.web;
\ No newline at end of file
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/impl/DmWebResource.java b/apps/cfm/src/main/java/org/onosproject/soam/impl/DmWebResource.java
new file mode 100644
index 0000000..b732450
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/impl/DmWebResource.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.soam.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collection;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.cfm.Mep;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdShort;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdId;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MepId;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmConfigException;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmMepService;
+import org.onosproject.incubator.net.l2monitoring.soam.SoamConfigException;
+import org.onosproject.incubator.net.l2monitoring.soam.SoamId;
+import org.onosproject.incubator.net.l2monitoring.soam.SoamService;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementCreate;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementEntry;
+import org.onosproject.rest.AbstractWebResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Layer 2 SOAM Delay Measurement web resource.
+ */
+@Path("md/{md_name}/ma/{ma_name}/mep/{mep_id}/dm")
+public class DmWebResource extends AbstractWebResource {
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    /**
+     * Get all DMs for a Mep.
+     *
+     * @param mdName The name of a Maintenance Domain
+     * @param maName The name of a Maintenance Association belonging to the MD
+     * @param mepId The ID of a Mep belonging to the MA
+     * @return 200 OK with a list of DMs or 500 on error
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response getAllDmsForMep(@PathParam("md_name") String mdName,
+            @PathParam("ma_name") String maName,
+            @PathParam("mep_id") short mepId) {
+        log.debug("GET all DMs called for MEP {}", mdName + "/" + maName + "/" + mepId);
+        try {
+            MdId mdId = MdIdCharStr.asMdId(mdName);
+            MaIdShort maId = MaIdCharStr.asMaId(maName);
+            MepId mepIdObj = MepId.valueOf(mepId);
+            Collection<DelayMeasurementEntry> dmCollection =
+                    get(SoamService.class).getAllDms(mdId, maId, mepIdObj);
+            ArrayNode an = mapper().createArrayNode();
+            an.add(codec(DelayMeasurementEntry.class).encode(dmCollection, this));
+            return ok(mapper().createObjectNode().set("dms", an)).build();
+        } catch (CfmConfigException | SoamConfigException e) {
+            log.error("Get DM {} failed because of exception {}",
+                    mdName + "/" + maName + "/" + mepId, e.toString());
+            return Response.serverError().entity("{ \"failure\":\"" + e.toString() + "\" }").build();
+        }
+    }
+
+    /**
+     * Get DM by MD name, MA name, Mep Id and Dm id.
+     *
+     * @param mdName The name of a Maintenance Domain
+     * @param maName The name of a Maintenance Association belonging to the MD
+     * @param mepId The Id of the MEP
+     * @param dmId The Id of the DM
+     * @return 200 OK with details of the DM or 500 on error
+     */
+    @GET
+    @Path("{dm_id}")
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response getDm(@PathParam("md_name") String mdName,
+            @PathParam("ma_name") String maName,
+            @PathParam("mep_id") short mepId, @PathParam("dm_id") int dmId) {
+        log.debug("GET called for DM {}", mdName + "/" + maName + "/" + mepId + "/" + dmId);
+        try {
+            MdId mdId = MdIdCharStr.asMdId(mdName);
+            MaIdShort maId = MaIdCharStr.asMaId(maName);
+            MepId mepIdObj = MepId.valueOf(mepId);
+            SoamId dmIdObj = SoamId.valueOf(dmId);
+            DelayMeasurementEntry dm = get(SoamService.class)
+                                    .getDm(mdId, maId, mepIdObj, dmIdObj);
+            if (dm == null) {
+                return Response.serverError().entity("{ \"failure\":\"DM " +
+                        mdName + "/" + maName + "/" + mepId + "/" + dmId + " not found\" }").build();
+            }
+            ObjectNode node = mapper().createObjectNode();
+            node.set("dm", codec(DelayMeasurementEntry.class).encode(dm, this));
+            return ok(node).build();
+        } catch (CfmConfigException | SoamConfigException e) {
+            log.error("Get DM {} failed because of exception {}",
+                    mdName + "/" + maName + "/" + mepId + "/" + dmId, e.toString());
+            return Response.serverError().entity("{ \"failure\":\"" + e.toString() + "\" }").build();
+        }
+    }
+
+    /**
+     * Abort DM by MD name, MA name, Mep Id and DM Id.
+     * In the API the measurement is aborted, and not truly deleted. It still
+     * remains so that its results may be read. Depending on the device it will
+     * get overwritten on the creation of subsequent measurements.
+     * Use clear stats to delete old results
+     * measurements.
+     *
+     * @param mdName The name of a Maintenance Domain
+     * @param maName The name of a Maintenance Association belonging to the MD
+     * @param mepId The Id of the MEP
+     * @param dmId The Id of the DM
+     * @return 200 OK or 304 if not found, or 500 on error
+     */
+    @DELETE
+    @Path("{dm_id}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response abortDm(@PathParam("md_name") String mdName,
+            @PathParam("ma_name") String maName,
+            @PathParam("mep_id") short mepId,
+            @PathParam("dm_id") int dmId) {
+        log.debug("DELETE called for DM {}", mdName + "/" + maName + "/" + mepId + "/" + dmId);
+        try {
+            MdId mdId = MdIdCharStr.asMdId(mdName);
+            MaIdShort maId = MaIdCharStr.asMaId(maName);
+            MepId mepIdObj = MepId.valueOf(mepId);
+            SoamId dmIdObj = SoamId.valueOf(dmId);
+
+            get(SoamService.class).abortDm(mdId, maId, mepIdObj, dmIdObj);
+            return ok("{ \"success\":\"deleted (aborted) " + mdName + "/" + maName +
+                    "/" + mepId + "/" + dmId + "\" }").build();
+        } catch (CfmConfigException e) {
+            log.error("Delete (abort) DM {} failed because of exception {}",
+                    mdName + "/" + maName + "/" + mepId + "/" + dmId, e.toString());
+            return Response.serverError().entity("{ \"failure\":\"" + e.toString() + "\" }").build();
+        }
+    }
+
+    /**
+     * Create DM with MD name, MA name, Mep id and DM Json.
+     *
+     * @param mdName The name of a Maintenance Domain
+     * @param maName The name of a Maintenance Association belonging to the MD
+     * @param mepId The Id of the MEP belonging to the MEP
+     * @param input A JSON formatted input stream specifying the DM parameters
+     * @return 201 Created or 304 if already exists or 500 on error
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createDm(@PathParam("md_name") String mdName,
+            @PathParam("ma_name") String maName,
+            @PathParam("mep_id") short mepId, InputStream input) {
+        log.debug("POST called to Create Dm");
+        try {
+            MdId mdId = MdIdCharStr.asMdId(mdName);
+            MaIdShort maId = MaIdCharStr.asMaId(maName);
+            MepId mepIdObj = MepId.valueOf(mepId);
+
+            Mep mep = get(CfmMepService.class).getMep(mdId, maId, mepIdObj);
+            if (mep == null) {
+                return Response.serverError().entity("{ \"failure\":\"mep " +
+                        mdName + "/" + maName + "/" + mepId + " does not exist\" }").build();
+            }
+
+            ObjectMapper mapper = new ObjectMapper();
+            JsonNode cfg = mapper.readTree(input);
+            JsonCodec<DelayMeasurementCreate> dmCodec = codec(DelayMeasurementCreate.class);
+
+            DelayMeasurementCreate dm = dmCodec.decode((ObjectNode) cfg, this);
+            get(SoamService.class).createDm(mdId, maId, mepIdObj, dm);
+            return Response
+                    .created(new URI("md/" + mdName + "/ma/" + maName + "/mep/" +
+                            mepId + "/dm"))
+                    .entity("{ \"success\":\"dm " + mdName + "/" + maName + "/" +
+                            mepId + " created\" }")
+                    .build();
+        } catch (CfmConfigException | SoamConfigException | IllegalArgumentException |
+                IOException | URISyntaxException e) {
+            log.error("Create DM on " + mdName + "/" + maName + "/" + mepId +
+                    " failed because of exception {}", e.toString());
+            return Response.serverError()
+                    .entity("{ \"failure\":\"" + e.toString() + "\" }")
+                    .build();
+        }
+    }
+
+    /**
+     * Clear DM history stats by MD name, MA name, Mep Id and DM Id.
+     *
+     * @param mdName The name of a Maintenance Domain
+     * @param maName The name of a Maintenance Association belonging to the MD
+     * @param mepId The Id of the MEP
+     * @param dmId The Id of the DM
+     * @return 200 OK or 304 if not found, or 500 on error
+     */
+    @PUT
+    @Path("{dm_id}/clear-history")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response clearDmHistory(@PathParam("md_name") String mdName,
+                            @PathParam("ma_name") String maName,
+                            @PathParam("mep_id") short mepId,
+                            @PathParam("dm_id") int dmId) {
+        log.debug("clear-history called for DM {}", mdName + "/" + maName +
+                "/" + mepId + "/" + dmId);
+        try {
+            MdId mdId = MdIdCharStr.asMdId(mdName);
+            MaIdShort maId = MaIdCharStr.asMaId(maName);
+            MepId mepIdObj = MepId.valueOf(mepId);
+            SoamId dmIdObj = SoamId.valueOf(dmId);
+
+            get(SoamService.class).clearDelayHistoryStats(mdId, maId, mepIdObj, dmIdObj);
+            return ok("{ \"success\":\"cleared DM history stats for " +
+                    mdName + "/" + maName + "/" + mepId + "/" + dmId + "\" }").build();
+        } catch (CfmConfigException e) {
+            log.error("Clear history stats for DM {} failed because of exception {}",
+                    mdName + "/" + maName + "/" + mepId + "/" + dmId, e.toString());
+            return Response.serverError().entity("{ \"failure\":\"" +
+                    e.toString() + "\" }").build();
+        }
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/impl/LmWebResource.java b/apps/cfm/src/main/java/org/onosproject/soam/impl/LmWebResource.java
new file mode 100644
index 0000000..d889668
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/impl/LmWebResource.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.soam.impl;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.cfm.Mep;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdShort;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdId;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MepId;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmConfigException;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmMepService;
+import org.onosproject.incubator.net.l2monitoring.soam.SoamConfigException;
+import org.onosproject.incubator.net.l2monitoring.soam.SoamId;
+import org.onosproject.incubator.net.l2monitoring.soam.SoamService;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementCreate;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementEntry;
+import org.onosproject.rest.AbstractWebResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collection;
+
+/**
+ * Layer 2 SOAM Loss Measurement web resource.
+ */
+@Path("md/{md_name}/ma/{ma_name}/mep/{mep_id}/lm")
+public class LmWebResource extends AbstractWebResource {
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    /**
+     * Get all LMs for a Mep.
+     *
+     * @param mdName The name of a Maintenance Domain
+     * @param maName The name of a Maintenance Association belonging to the MD
+     * @param mepId The ID of a Mep belonging to the MA
+     * @return 200 OK with a list of LMs or 500 on error
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response getAllLmsForMep(@PathParam("md_name") String mdName,
+                                    @PathParam("ma_name") String maName,
+                                    @PathParam("mep_id") short mepId) {
+
+        log.debug("GET all LMs called for MEP {}", mdName + "/" + maName + "/" + mepId);
+        try {
+            MdId mdId = MdIdCharStr.asMdId(mdName);
+            MaIdShort maId = MaIdCharStr.asMaId(maName);
+            MepId mepIdObj = MepId.valueOf(mepId);
+            Collection<LossMeasurementEntry> lmCollection =
+                    get(SoamService.class).getAllLms(mdId, maId, mepIdObj);
+            ArrayNode an = mapper().createArrayNode();
+            an.add(codec(LossMeasurementEntry.class).encode(lmCollection, this));
+            return ok(mapper().createObjectNode().set("lms", an)).build();
+        } catch (CfmConfigException | SoamConfigException e) {
+            log.error("Get LM {} failed because of exception {}",
+                    mdName + "/" + maName + "/" + mepId, e.toString());
+            return Response.serverError().entity("{ \"failure\":\"" + e.toString() + "\" }").build();
+        }
+    }
+
+    /**
+     * Get LM by MD name, MA name, Mep Id and Dm id.
+     *
+     * @param mdName The name of a Maintenance Domain
+     * @param maName The name of a Maintenance Association belonging to the MD
+     * @param mepId The Id of the MEP
+     * @param lmId The Id of the LM
+     * @return 200 OK with details of the LM or 500 on error
+     */
+    @GET
+    @Path("{lm_id}")
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response getLm(@PathParam("md_name") String mdName,
+                          @PathParam("ma_name") String maName,
+                          @PathParam("mep_id") short mepId, @PathParam("lm_id") int lmId) {
+        log.debug("GET called for LM {}", mdName + "/" + maName + "/" + mepId + "/" + lmId);
+        try {
+            MdId mdId = MdIdCharStr.asMdId(mdName);
+            MaIdShort maId = MaIdCharStr.asMaId(maName);
+            MepId mepIdObj = MepId.valueOf(mepId);
+            SoamId lmIdObj = SoamId.valueOf(lmId);
+            LossMeasurementEntry lm = get(SoamService.class)
+                                    .getLm(mdId, maId, mepIdObj, lmIdObj);
+            if (lm == null) {
+                return Response.serverError().entity("{ \"failure\":\"LM " +
+                        mdName + "/" + maName + "/" + mepId + "/" + lmId + " not found\" }").build();
+            }
+            ObjectNode node = mapper().createObjectNode();
+            node.set("lm", codec(LossMeasurementEntry.class).encode(lm, this));
+            return ok(node).build();
+        } catch (CfmConfigException | SoamConfigException e) {
+            log.error("Get LM {} failed because of exception {}",
+                    mdName + "/" + maName + "/" + mepId + "/" + lmId, e.toString());
+            return Response.serverError().entity("{ \"failure\":\"" + e.toString() + "\" }").build();
+        }
+    }
+
+    /**
+     * Abort LM by MD name, MA name, Mep Id and LM Id.
+     * In the API the measurement is aborted, and not truly deleted. It still
+     * remains so that its results may be read. Depending on the device it will
+     * get overwritten on the creation of subsequent measurements.
+     * Use clear stats to delete old results measurements.
+     *
+     * @param mdName The name of a Maintenance Domain
+     * @param maName The name of a Maintenance Association belonging to the MD
+     * @param mepId The Id of the MEP
+     * @param lmId The Id of the LM
+     * @return 200 OK or 304 if not found, or 500 on error
+     */
+    @DELETE
+    @Path("{lm_id}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response abortLm(@PathParam("md_name") String mdName,
+                            @PathParam("ma_name") String maName,
+                            @PathParam("mep_id") short mepId,
+                            @PathParam("lm_id") int lmId) {
+        log.debug("DELETE called for LM {}", mdName + "/" + maName + "/" + mepId + "/" + lmId);
+        try {
+            MdId mdId = MdIdCharStr.asMdId(mdName);
+            MaIdShort maId = MaIdCharStr.asMaId(maName);
+            MepId mepIdObj = MepId.valueOf(mepId);
+            SoamId lmIdObj = SoamId.valueOf(lmId);
+
+            get(SoamService.class).abortLm(mdId, maId, mepIdObj, lmIdObj);
+            return ok("{ \"success\":\"deleted (aborted) " + mdName + "/" + maName +
+                    "/" + mepId + "/" + lmId + "\" }").build();
+        } catch (CfmConfigException e) {
+            log.error("Delete (abort) LM {} failed because of exception {}",
+                    mdName + "/" + maName + "/" + mepId + "/" + lmId, e.toString());
+            return Response.serverError().entity("{ \"failure\":\"" + e.toString() + "\" }").build();
+        }
+    }
+
+    /**
+     * Create LM with MD name, MA name, Mep id and LM Json.
+     *
+     * @param mdName The name of a Maintenance Domain
+     * @param maName The name of a Maintenance Association belonging to the MD
+     * @param mepId The Id of the MEP belonging to the MEP
+     * @param input A JSON formatted input stream specifying the LM parameters
+     * @return 201 Created or 304 if already exists or 500 on error
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createLm(@PathParam("md_name") String mdName,
+                             @PathParam("ma_name") String maName,
+                             @PathParam("mep_id") short mepId, InputStream input) {
+        log.debug("POST called to Create Lm");
+        try {
+            MdId mdId = MdIdCharStr.asMdId(mdName);
+            MaIdShort maId = MaIdCharStr.asMaId(maName);
+            MepId mepIdObj = MepId.valueOf(mepId);
+
+            Mep mep = get(CfmMepService.class).getMep(mdId, maId, mepIdObj);
+            if (mep == null) {
+                return Response.serverError().entity("{ \"failure\":\"mep " +
+                        mdName + "/" + maName + "/" + mepId + " does not exist\" }").build();
+            }
+
+            ObjectMapper mapper = new ObjectMapper();
+            JsonNode cfg = mapper.readTree(input);
+            JsonCodec<LossMeasurementCreate> lmCodec = codec(LossMeasurementCreate.class);
+
+            LossMeasurementCreate lm = lmCodec.decode((ObjectNode) cfg, this);
+            get(SoamService.class).createLm(mdId, maId, mepIdObj, lm);
+            return Response
+                    .created(new URI("md/" + mdName + "/ma/" + maName + "/mep/" +
+                            mepId + "/lm"))
+                    .entity("{ \"success\":\"lm " + mdName + "/" + maName + "/" +
+                            mepId + " created\" }")
+                    .build();
+        } catch (CfmConfigException | SoamConfigException | IllegalArgumentException |
+                IOException | URISyntaxException e) {
+            log.error("Create LM on " + mdName + "/" + maName +  "/" + mepId +
+                    " failed because of exception {}", e.toString());
+            return Response.serverError()
+                    .entity("{ \"failure\":\"" + e.toString() + "\" }")
+                    .build();
+        }
+    }
+
+    /**
+     * Clear LM history stats by MD name, MA name, Mep Id and LM Id.
+     *
+     * @param mdName The name of a Maintenance Domain
+     * @param maName The name of a Maintenance Association belonging to the MD
+     * @param mepId The Id of the MEP
+     * @param lmId The Id of the LM
+     * @return 200 OK or 304 if not found, or 500 on error
+     */
+    @PUT
+    @Path("{lm_id}/clear-history")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response clearLmHistory(@PathParam("md_name") String mdName,
+                                   @PathParam("ma_name") String maName,
+                                   @PathParam("mep_id") short mepId,
+                                   @PathParam("lm_id") int lmId) {
+        log.debug("clear-history called for LM {}", mdName + "/" + maName +
+                "/" + mepId + "/" + lmId);
+        try {
+            MdId mdId = MdIdCharStr.asMdId(mdName);
+            MaIdShort maId = MaIdCharStr.asMaId(maName);
+            MepId mepIdObj = MepId.valueOf(mepId);
+            SoamId lmIdObj = SoamId.valueOf(lmId);
+
+            get(SoamService.class).clearDelayHistoryStats(mdId, maId, mepIdObj, lmIdObj);
+            return ok("{ \"success\":\"cleared LM history stats for " +
+                    mdName + "/" + maName + "/" + mepId + "/" + lmId + "\" }").build();
+        } catch (CfmConfigException e) {
+            log.error("Clear history stats for LM {} failed because of exception {}",
+                    mdName + "/" + maName + "/" + mepId + "/" + lmId, e.toString());
+            return Response.serverError().entity("{ \"failure\":\"" +
+                    e.toString() + "\" }").build();
+        }
+    }
+
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/impl/package-info.java b/apps/cfm/src/main/java/org/onosproject/soam/impl/package-info.java
new file mode 100644
index 0000000..370030f
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/impl/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.
+ */
+/**
+ * REST Web Application for SOAM.
+ */
+package org.onosproject.soam.impl;
\ No newline at end of file
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/DelayMeasurementStatCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/DelayMeasurementStatCodec.java
new file mode 100644
index 0000000..7bd0d7a
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/DelayMeasurementStatCodec.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.soam.web;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.time.Duration;
+import java.util.Map;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementStat;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Encode and decode to/from JSON to DelayMeasurementStat object.
+ */
+public class DelayMeasurementStatCodec extends JsonCodec<DelayMeasurementStat> {
+
+    private static final String LOWER_LIMIT = "lowerLimit";
+    private static final String COUNT = "count";
+    private static final String BINS = "bins";
+    private static final String SOAM_PDUS_SENT = "soamPdusSent";
+    private static final String SOAM_PDUS_RECEIVED = "soamPdusReceived";
+
+    @Override
+    public ObjectNode encode(DelayMeasurementStat dmStat, CodecContext context) {
+        checkNotNull(dmStat, "DM stat cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put("elapsedTime", dmStat.elapsedTime().toString())
+                .put("suspectStatus", String.valueOf(dmStat.suspectStatus()));
+
+        if (dmStat.frameDelayTwoWayMin() != null) {
+            result = result.put("frameDelayTwoWayMin",
+                    dmStat.frameDelayTwoWayMin().toString());
+        }
+        if (dmStat.frameDelayTwoWayMax() != null) {
+            result = result.put("frameDelayTwoWayMax",
+                    dmStat.frameDelayTwoWayMax().toString());
+        }
+        if (dmStat.frameDelayTwoWayAvg() != null) {
+            result = result.put("frameDelayTwoWayAvg",
+                    dmStat.frameDelayTwoWayAvg().toString());
+        }
+        if (dmStat.frameDelayTwoWayBins() != null) {
+            result = (ObjectNode) result.set("frameDelayTwoWayBins",
+                    encode(dmStat.frameDelayTwoWayBins(), context));
+        }
+        if (dmStat.frameDelayForwardMin() != null) {
+            result = result.put("frameDelayForwardMin",
+                    dmStat.frameDelayForwardMin().toString());
+        }
+        if (dmStat.frameDelayForwardMax() != null) {
+            result = result.put("frameDelayForwardMax",
+                    dmStat.frameDelayForwardMax().toString());
+        }
+        if (dmStat.frameDelayForwardAvg() != null) {
+            result = result.put("frameDelayForwardAvg",
+                    dmStat.frameDelayForwardAvg().toString());
+        }
+        if (dmStat.frameDelayForwardBins() != null) {
+            result = (ObjectNode) result.set("frameDelayForwardBins",
+                    encode(dmStat.frameDelayForwardBins(), context));
+        }
+        if (dmStat.frameDelayBackwardMin() != null) {
+            result = result.put("frameDelayBackwardMin",
+                    dmStat.frameDelayBackwardMin().toString());
+        }
+        if (dmStat.frameDelayBackwardMax() != null) {
+            result = result.put("frameDelayBackwardMax",
+                    dmStat.frameDelayBackwardMax().toString());
+        }
+        if (dmStat.frameDelayBackwardAvg() != null) {
+            result = result.put("frameDelayBackwardAvg",
+                    dmStat.frameDelayBackwardAvg().toString());
+        }
+        if (dmStat.frameDelayBackwardBins() != null) {
+            result = (ObjectNode) result.set("frameDelayBackwardBins",
+                    encode(dmStat.frameDelayBackwardBins(), context));
+        }
+        if (dmStat.interFrameDelayVariationTwoWayMin() != null) {
+            result = result.put("interFrameDelayVariationTwoWayMin",
+                    dmStat.interFrameDelayVariationTwoWayMin().toString());
+        }
+        if (dmStat.interFrameDelayVariationTwoWayMax() != null) {
+            result.put("interFrameDelayVariationTwoWayMax",
+                    dmStat.interFrameDelayVariationTwoWayMax().toString());
+        }
+        if (dmStat.interFrameDelayVariationTwoWayAvg() != null) {
+            result.put("interFrameDelayVariationTwoWayAvg",
+                    dmStat.interFrameDelayVariationTwoWayAvg().toString());
+        }
+        if (dmStat.interFrameDelayVariationTwoWayBins() != null) {
+            result = (ObjectNode) result.set("interFrameDelayVariationTwoWayBins",
+                    encode(dmStat.interFrameDelayVariationTwoWayBins(), context));
+        }
+        if (dmStat.interFrameDelayVariationForwardMin() != null) {
+            result = result.put("interFrameDelayVariationForwardMin",
+                    dmStat.interFrameDelayVariationForwardMin().toString());
+        }
+        if (dmStat.interFrameDelayVariationForwardMax() != null) {
+            result = result.put("interFrameDelayVariationForwardMax",
+                    dmStat.interFrameDelayVariationForwardMax().toString());
+        }
+        if (dmStat.interFrameDelayVariationForwardAvg() != null) {
+            result = result.put("interFrameDelayVariationForwardAvg",
+                    dmStat.interFrameDelayVariationForwardAvg().toString());
+        }
+        if (dmStat.interFrameDelayVariationForwardBins() != null) {
+            result = (ObjectNode) result.set("interFrameDelayVariationForwardBins",
+                    encode(dmStat.interFrameDelayVariationForwardBins(), context));
+        }
+        if (dmStat.interFrameDelayVariationBackwardMin() != null) {
+            result = result.put("interFrameDelayVariationBackwardMin",
+                    dmStat.interFrameDelayVariationBackwardMin().toString());
+        }
+        if (dmStat.interFrameDelayVariationBackwardMax() != null) {
+            result = result.put("interFrameDelayVariationBackwardMax",
+                    dmStat.interFrameDelayVariationBackwardMax().toString());
+        }
+        if (dmStat.interFrameDelayVariationBackwardAvg() != null) {
+            result = result.put("interFrameDelayVariationBackwardAvg",
+                    dmStat.interFrameDelayVariationBackwardAvg().toString());
+        }
+        if (dmStat.interFrameDelayVariationBackwardBins() != null) {
+            result = (ObjectNode) result.set("interFrameDelayVariationBackwardBins",
+                    encode(dmStat.interFrameDelayVariationBackwardBins(), context));
+        }
+        if (dmStat.frameDelayRangeTwoWayMax() != null) {
+            result = result.put("frameDelayRangeTwoWayMax",
+                    dmStat.frameDelayRangeTwoWayMax().toString());
+        }
+        if (dmStat.frameDelayRangeTwoWayAvg() != null) {
+            result = result.put("frameDelayRangeTwoWayAvg",
+                    dmStat.frameDelayRangeTwoWayAvg().toString());
+        }
+        if (dmStat.frameDelayRangeTwoWayBins() != null) {
+            result = (ObjectNode) result.set("frameDelayRangeTwoWayBins",
+                    encode(dmStat.frameDelayRangeTwoWayBins(), context));
+        }
+        if (dmStat.frameDelayRangeForwardMax() != null) {
+            result = result.put("frameDelayRangeForwardMax",
+                    dmStat.frameDelayRangeForwardMax().toString());
+        }
+        if (dmStat.frameDelayRangeForwardAvg() != null) {
+            result = result.put("frameDelayRangeForwardAvg",
+                    dmStat.frameDelayRangeForwardAvg().toString());
+        }
+        if (dmStat.frameDelayRangeForwardBins() != null) {
+            result = (ObjectNode) result.set("frameDelayRangeForwardBins",
+                    encode(dmStat.frameDelayRangeForwardBins(), context));
+        }
+        if (dmStat.frameDelayRangeBackwardMax() != null) {
+            result = result.put("frameDelayRangeBackwardMax",
+                    dmStat.frameDelayRangeBackwardMax().toString());
+        }
+        if (dmStat.frameDelayRangeBackwardAvg() != null) {
+            result = result.put("frameDelayRangeBackwardAvg",
+                    dmStat.frameDelayRangeBackwardAvg().toString());
+        }
+        if (dmStat.frameDelayRangeBackwardBins() != null) {
+            result = (ObjectNode) result.set("frameDelayRangeBackwardBins",
+                    encode(dmStat.frameDelayRangeBackwardBins(), context));
+        }
+
+        if (dmStat.soamPdusReceived() != null) {
+            result = result.put(SOAM_PDUS_RECEIVED, dmStat.soamPdusReceived().toString());
+        }
+
+        if (dmStat.soamPdusSent() != null) {
+            result = result.put(SOAM_PDUS_SENT, dmStat.soamPdusSent().toString());
+        }
+
+        return result;
+    }
+
+    private ObjectNode encode(Map<Duration, Integer> bins, CodecContext context) {
+        checkNotNull(bins, "Bins cannot be null");
+        ArrayNode binsResult = context.mapper().createArrayNode();
+        bins.keySet().forEach(lwrLimit -> binsResult.add(encode(lwrLimit, bins.get(lwrLimit), context)));
+
+        return (ObjectNode) context.mapper().createObjectNode().set(BINS, binsResult);
+    }
+
+    private ObjectNode encode(Duration duration, Integer count, CodecContext context) {
+        return context.mapper().createObjectNode()
+            .put(LOWER_LIMIT, duration.toString())
+            .put(COUNT, count);
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/DelayMeasurementStatCurrentCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/DelayMeasurementStatCurrentCodec.java
new file mode 100644
index 0000000..a79cd90
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/DelayMeasurementStatCurrentCodec.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.soam.web;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementStatCurrent;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Encode and decode to/from JSON to DelayMeasurementStatCurrent object.
+ */
+public class DelayMeasurementStatCurrentCodec extends JsonCodec<DelayMeasurementStatCurrent> {
+
+    @Override
+    public ObjectNode encode(DelayMeasurementStatCurrent dmCurrent, CodecContext context) {
+        checkNotNull(dmCurrent, "DM current cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put("startTime", dmCurrent.startTime().toString())
+                .put("elapsedTime", dmCurrent.elapsedTime().toString());
+
+        ObjectNode resultAbstract = new DelayMeasurementStatCodec().encode(dmCurrent, context);
+        result.setAll(resultAbstract);
+
+        return result;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/DelayMeasurementStatHistoryCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/DelayMeasurementStatHistoryCodec.java
new file mode 100644
index 0000000..8901958
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/DelayMeasurementStatHistoryCodec.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.soam.web;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementStatHistory;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Encode and decode to/from JSON to DelayMeasurementStatHistory object.
+ */
+public class DelayMeasurementStatHistoryCodec extends JsonCodec<DelayMeasurementStatHistory> {
+
+    @Override
+    public ObjectNode encode(DelayMeasurementStatHistory dmHistory, CodecContext context) {
+        checkNotNull(dmHistory, "DM history cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put("historyId", String.valueOf(dmHistory.historyStatsId()))
+                .put("endTime", dmHistory.endTime().toString());
+        ObjectNode resultAbstract = new DelayMeasurementStatCodec().encode(dmHistory, context);
+        result.setAll(resultAbstract);
+        return result;
+    }
+
+    @Override
+    public ArrayNode encode(Iterable<DelayMeasurementStatHistory> historyEntities, CodecContext context) {
+        ArrayNode an = context.mapper().createArrayNode();
+        historyEntities.forEach(history -> an.add(encode(history, context)));
+        return an;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/DmCreateCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/DmCreateCodec.java
new file mode 100644
index 0000000..8743eb4
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/DmCreateCodec.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.soam.web;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.nullIsIllegal;
+
+import java.time.Duration;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.cfm.Mep.Priority;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MepId;
+import org.onosproject.incubator.net.l2monitoring.soam.SoamConfigException;
+import org.onosproject.incubator.net.l2monitoring.soam.StartTime;
+import org.onosproject.incubator.net.l2monitoring.soam.StopTime;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DefaultDelayMeasurementCreate;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementCreate;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementCreate.DmCreateBuilder;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementCreate.DmType;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementCreate.MeasurementOption;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementCreate.Version;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Encode and decode to/from JSON to DelayMeasurementCreate object.
+ */
+public class DmCreateCodec extends JsonCodec<DelayMeasurementCreate> {
+
+    private static final String VERSION = "version";
+    private static final String DM = "dm";
+    private static final String DM_CFG_TYPE = "dmCfgType";
+    private static final String DMDMM = "DMDMM";
+    private static final String REMOTE_MEP_ID = "remoteMepId";
+    private static final String PRIORITY = "priority";
+    private static final String MEASUREMENTS_ENABLED = "measurementsEnabled";
+    private static final String BINS_PER_FD_INTERVAL = "binsPerFdInterval";
+    private static final String BINS_PER_IFDV_INTERVAL = "binsPerIfdvInterval";
+    private static final String IFDV_SELECTION_OFFSET = "ifdvSelectionOffset";
+    private static final String BINS_PER_FDR_INTERVAL = "binsPerFdrInterval";
+    private static final String FRAME_SIZE = "frameSize";
+    private static final String MESSAGE_PERIOD_MS = "messagePeriodMs";
+    private static final String MEASUREMENT_INTERVAL_MINS = "measurementIntervalMins";
+    private static final String ALIGN_MEASUREMENT_INTERVALS = "alignMeasurementIntervals";
+    private static final String ALIGN_MEASUREMENT_OFFSET_MINS = "alignMeasurementOffsetMins";
+    private static final String START_TIME = "startTime";
+    private static final String STOP_TIME = "stopTime";
+
+    @Override
+    public DelayMeasurementCreate decode(ObjectNode json,
+            CodecContext context) {
+
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        JsonNode dmNode = json.get(DM);
+        Version version = Version.Y17312011;
+        if (dmNode.get(VERSION) != null) {
+            version = Version.valueOf(dmNode.get(VERSION).asText());
+        }
+        DmType dmCfgType = DmType.DMDMM;
+        if (dmNode.get(DM_CFG_TYPE) != null) {
+            dmCfgType = DmType.valueOf(dmNode.get(DM_CFG_TYPE).asText(DMDMM));
+        }
+        MepId remoteMepId = MepId.valueOf(
+                nullIsIllegal(dmNode.get(REMOTE_MEP_ID), REMOTE_MEP_ID + " is required")
+                .shortValue());
+        Priority prio = Priority.valueOf(nullIsIllegal(dmNode.get(PRIORITY),
+                PRIORITY + " is required in the format 'PRIOn'").asText());
+
+        try {
+            DmCreateBuilder builder = DefaultDelayMeasurementCreate
+                    .builder(dmCfgType, version, remoteMepId, prio);
+
+            if (dmNode.get(MEASUREMENTS_ENABLED) != null) {
+                context.codec(MeasurementOption.class)
+                    .decode((ArrayNode) (dmNode.get(MEASUREMENTS_ENABLED)), context)
+                    .forEach(builder::addToMeasurementsEnabled);
+            }
+
+            if (dmNode.get(BINS_PER_FD_INTERVAL) != null) {
+                builder = builder.binsPerFdInterval(
+                        (short) dmNode.get(BINS_PER_FD_INTERVAL).asInt());
+            }
+            if (dmNode.get(BINS_PER_IFDV_INTERVAL) != null) {
+                builder = builder.binsPerIfdvInterval(
+                        (short) dmNode.get(BINS_PER_IFDV_INTERVAL).asInt());
+            }
+            if (dmNode.get(IFDV_SELECTION_OFFSET) != null) {
+                builder = builder.ifdvSelectionOffset(
+                        (short) dmNode.get(IFDV_SELECTION_OFFSET).asInt());
+            }
+            if (dmNode.get(BINS_PER_FDR_INTERVAL) != null) {
+                builder = builder.binsPerFdrInterval(
+                        (short) dmNode.get(BINS_PER_FDR_INTERVAL).asInt());
+            }
+            if (dmNode.get(FRAME_SIZE) != null) {
+                builder = (DmCreateBuilder) builder.frameSize(
+                        (short) dmNode.get(FRAME_SIZE).asInt());
+            }
+            if (dmNode.get(MESSAGE_PERIOD_MS) != null) {
+                builder = (DmCreateBuilder) builder.messagePeriod(Duration.ofMillis(
+                        dmNode.get(MESSAGE_PERIOD_MS).asInt()));
+            }
+            if (dmNode.get(MEASUREMENT_INTERVAL_MINS) != null) {
+                builder = (DmCreateBuilder) builder.measurementInterval(
+                        Duration.ofMinutes(
+                        dmNode.get(MEASUREMENT_INTERVAL_MINS).asInt()));
+            }
+            if (dmNode.get(ALIGN_MEASUREMENT_INTERVALS) != null) {
+                builder = (DmCreateBuilder) builder.alignMeasurementIntervals(
+                        dmNode.get(ALIGN_MEASUREMENT_INTERVALS).asBoolean());
+            }
+            if (dmNode.get(ALIGN_MEASUREMENT_OFFSET_MINS) != null) {
+                builder = (DmCreateBuilder) builder.alignMeasurementOffset(Duration.ofMinutes(
+                        dmNode.get(ALIGN_MEASUREMENT_OFFSET_MINS).asInt()));
+            }
+            if (dmNode.get(START_TIME) != null) {
+                builder = (DmCreateBuilder) builder.startTime(context.codec(StartTime.class)
+                .decode((ObjectNode) dmNode.get(START_TIME), context));
+            }
+            if (dmNode.get(STOP_TIME) != null) {
+                builder = (DmCreateBuilder) builder.stopTime(context.codec(StopTime.class)
+                .decode((ObjectNode) dmNode.get(STOP_TIME), context));
+            }
+
+            return builder.build();
+        } catch (SoamConfigException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    @Override
+    public ObjectNode encode(DelayMeasurementCreate dm, CodecContext context) {
+        checkNotNull(dm, "DM cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put(DM_CFG_TYPE, dm.dmCfgType().name())
+                .put(VERSION, dm.version().name())
+                .put(REMOTE_MEP_ID, dm.remoteMepId().id())
+                .put(PRIORITY, dm.priority().name());
+
+        if (dm.measurementsEnabled() != null) {
+            result.set(MEASUREMENTS_ENABLED, new DmMeasurementOptionCodec()
+                    .encode(dm.measurementsEnabled(), context));
+        }
+
+        if (dm.messagePeriod() != null) {
+            result.put(MESSAGE_PERIOD_MS, dm.messagePeriod().toMillis());
+        }
+        if (dm.frameSize() != null) {
+            result.put(FRAME_SIZE, dm.frameSize());
+        }
+
+
+        return result;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/DmEntryCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/DmEntryCodec.java
new file mode 100644
index 0000000..ce851d6
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/DmEntryCodec.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.soam.web;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Iterator;
+import java.util.Map.Entry;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementEntry;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Encode and decode to/from JSON to DelayMeasurementEntry object.
+ */
+public class DmEntryCodec extends JsonCodec<DelayMeasurementEntry> {
+
+    private static final String DM_ID = "dmId";
+    private static final String SESSION_STATUS = "sessionStatus";
+    private static final String FRAME_DELAY_TWO_WAY = "frameDelayTwoWay";
+    private static final String FRAME_DELAY_FORWARD = "frameDelayForward";
+    private static final String FRAME_DELAY_BACKWARD = "frameDelayBackward";
+    private static final String INTER_FRAME_DELAY_VARIATION_TWO_WAY = "interFrameDelayVariationTwoWay";
+    private static final String INTER_FRAME_DELAY_VARIATION_FORWARD = "interFrameDelayVariationForward";
+    private static final String INTER_FRAME_DELAY_VARIATION_BACKWARD = "interFrameDelayVariationBackward";
+    private static final String CURRENT = "current";
+    private static final String HISTORIC = "historic";
+
+    @Override
+    public ObjectNode encode(DelayMeasurementEntry dm, CodecContext context) {
+        checkNotNull(dm, "DM cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put(DM_ID, dm.dmId().toString());
+
+        if (dm.sessionStatus() != null) {
+            result.put(SESSION_STATUS, dm.sessionStatus().name());
+        }
+        if (dm.frameDelayTwoWay() != null) {
+            result.put(FRAME_DELAY_TWO_WAY, dm.frameDelayTwoWay().toString());
+        }
+        if (dm.frameDelayForward() != null) {
+            result.put(FRAME_DELAY_FORWARD, dm.frameDelayForward().toString());
+        }
+        if (dm.frameDelayBackward() != null) {
+            result.put(FRAME_DELAY_BACKWARD, dm.frameDelayBackward().toString());
+        }
+        if (dm.interFrameDelayVariationTwoWay() != null) {
+            result.put(INTER_FRAME_DELAY_VARIATION_TWO_WAY,
+                    dm.interFrameDelayVariationTwoWay().toString());
+        }
+        if (dm.interFrameDelayVariationForward() != null) {
+            result.put(INTER_FRAME_DELAY_VARIATION_FORWARD,
+                    dm.interFrameDelayVariationForward().toString());
+        }
+        if (dm.interFrameDelayVariationBackward() != null) {
+            result.put(INTER_FRAME_DELAY_VARIATION_BACKWARD,
+                    dm.interFrameDelayVariationBackward().toString());
+        }
+
+        ObjectNode dmAttrs = new DmCreateCodec().encode(dm, context);
+        Iterator<Entry<String, JsonNode>> elements = dmAttrs.fields();
+        while (elements.hasNext()) {
+            Entry<String, JsonNode> element = elements.next();
+            result.set(element.getKey(), element.getValue());
+        }
+
+        if (dm.currentResult() != null) {
+            result.set(CURRENT, new DelayMeasurementStatCurrentCodec()
+                    .encode(dm.currentResult(), context));
+        }
+
+        if (dm.historicalResults() != null) {
+            result.set(HISTORIC, new DelayMeasurementStatHistoryCodec()
+                    .encode(dm.historicalResults(), context));
+        }
+
+        return result;
+    }
+
+    @Override
+    public ArrayNode encode(Iterable<DelayMeasurementEntry> dmEntities, CodecContext context) {
+        ArrayNode an = context.mapper().createArrayNode();
+        dmEntities.forEach(dm -> an.add(encode(dm, context)));
+        return an;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/DmMeasurementOptionCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/DmMeasurementOptionCodec.java
new file mode 100644
index 0000000..8c8a5a0
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/DmMeasurementOptionCodec.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.soam.web;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementCreate.MeasurementOption;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+
+/**
+ * Encode and decode to/from JSON to MeasurementOption object.
+ */
+public class DmMeasurementOptionCodec extends JsonCodec<MeasurementOption> {
+
+    @Override
+    public ArrayNode encode(Iterable<MeasurementOption> entities,
+            CodecContext context) {
+        ArrayNode an = context.mapper().createArrayNode();
+        entities.forEach(node -> an.add(node.name()));
+
+        return an;
+    }
+
+    @Override
+    public List<MeasurementOption> decode(ArrayNode json,
+            CodecContext context) {
+        if (json == null) {
+            return null;
+        }
+        List<MeasurementOption> moList = new ArrayList<>();
+        json.forEach(node -> moList.add(MeasurementOption.valueOf(node.asText())));
+        return moList;
+    }
+
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/LmCounterOptionCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/LmCounterOptionCodec.java
new file mode 100644
index 0000000..9e3e40e
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/LmCounterOptionCodec.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.soam.web;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementCreate.CounterOption;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Encode and decode to/from JSON to CounterOption object.
+ */
+public class LmCounterOptionCodec extends JsonCodec<CounterOption> {
+
+    @Override
+    public ArrayNode encode(Iterable<CounterOption> entities,
+            CodecContext context) {
+        ArrayNode an = context.mapper().createArrayNode();
+        entities.forEach(node -> an.add(node.name()));
+
+        return an;
+    }
+
+    @Override
+    public List<CounterOption> decode(ArrayNode json,
+            CodecContext context) {
+        if (json == null) {
+            return null;
+        }
+        List<CounterOption> moList = new ArrayList<>();
+        json.forEach(node -> moList.add(CounterOption.valueOf(node.asText())));
+        return moList;
+    }
+
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/LmCreateCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/LmCreateCodec.java
new file mode 100644
index 0000000..e88fb2f
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/LmCreateCodec.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.soam.web;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.cfm.Mep.Priority;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MepId;
+import org.onosproject.incubator.net.l2monitoring.soam.MilliPct;
+import org.onosproject.incubator.net.l2monitoring.soam.SoamConfigException;
+import org.onosproject.incubator.net.l2monitoring.soam.StartTime;
+import org.onosproject.incubator.net.l2monitoring.soam.StopTime;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementCreate.Version;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.DefaultLmCreate;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementCreate;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementThreshold;
+
+import java.time.Duration;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.nullIsIllegal;
+import static org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementCreate.CounterOption;
+import static org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementCreate.LmType;
+import static org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementCreate.LmCreateBuilder;
+
+/**
+ * Encode and decode to/from JSON to LossMeasurementCreate object.
+ */
+public class LmCreateCodec extends JsonCodec<LossMeasurementCreate> {
+
+    public static final String LM = "lm";
+    public static final String VERSION = "version";
+    public static final String LM_CFG_TYPE = "lmCfgType";
+    public static final String LMLMM = "LMLMM";
+    public static final String REMOTE_MEP_ID = "remoteMepId";
+    public static final String PRIORITY = "priority";
+    public static final String COUNTERS_ENABLED = "countersEnabled";
+    public static final String THRESHOLDS = "thresholds";
+    public static final String AVAILABILITY_MEASUREMENT_INTERVAL_MINS =
+                                "availabilityMeasurementIntervalMins";
+    public static final String AVAILABILITY_NUMBER_CONSECUTIVE_FLR_MEASUREMENTS =
+                                "availabilityNumberConsecutiveFlrMeasurements";
+    public static final String AVAILABILITY_FLR_THRESHOLD_PCT =
+                                "availabilityFlrThresholdPct";
+    public static final String AVAILABILITY_NUMBER_CONSECUTIVE_INTERVALS =
+                                "availabilityNumberConsecutiveIntervals";
+    public static final String AVAILABILITY_NUMBER_CONSECUTIVE_HIGH_FLR =
+                                "availabilityNumberConsecutiveHighFlr";
+    public static final String FRAME_SIZE = "frameSize";
+    public static final String MESSAGE_PERIOD_MS = "messagePeriodMs";
+    public static final String MEASUREMENT_INTERVAL_MINS =
+                                "measurementIntervalMins";
+    public static final String ALIGN_MEASUREMENT_INTERVALS =
+                                "alignMeasurementIntervals";
+    public static final String ALIGN_MEASUREMENT_OFFSET_MINS =
+                                "alignMeasurementOffsetMins";
+    public static final String START_TIME = "startTime";
+    public static final String STOP_TIME = "stopTime";
+
+    @Override
+    public LossMeasurementCreate decode(ObjectNode json,
+            CodecContext context) {
+
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        JsonNode lmNode = json.get(LM);
+        Version version = Version.Y17312011;
+        if (lmNode.get(VERSION) != null) {
+            version = Version.valueOf(lmNode.get(VERSION).asText());
+        }
+        LmType lmCfgType = LmType.LMLMM;
+        if (lmNode.get(LM_CFG_TYPE) != null) {
+            lmCfgType = LmType.valueOf(lmNode.get(LM_CFG_TYPE).asText(LMLMM));
+        }
+        MepId remoteMepId = MepId.valueOf(
+                nullIsIllegal(lmNode.get(REMOTE_MEP_ID), REMOTE_MEP_ID + " is required")
+                .shortValue());
+        Priority prio = Priority.valueOf(nullIsIllegal(lmNode.get(PRIORITY),
+                PRIORITY + " is required in the format 'PRIOn'").asText());
+
+        try {
+            LmCreateBuilder builder = DefaultLmCreate
+                    .builder(version, remoteMepId, prio, lmCfgType);
+
+            if (lmNode.get(COUNTERS_ENABLED) != null) {
+                context.codec(CounterOption.class)
+                    .decode((ArrayNode) (lmNode.get(COUNTERS_ENABLED)), context)
+                    .forEach(builder::addToCountersEnabled);
+            }
+
+            if (lmNode.get(THRESHOLDS) != null) {
+                context.codec(LossMeasurementThreshold.class)
+                        .decode((ArrayNode) (lmNode.get(THRESHOLDS)), context)
+                        .forEach(builder::addToLossMeasurementThreshold);
+            }
+
+            if (lmNode.get(AVAILABILITY_MEASUREMENT_INTERVAL_MINS) != null) {
+                builder = builder.availabilityMeasurementInterval(
+                        Duration.ofMinutes(lmNode.get(AVAILABILITY_MEASUREMENT_INTERVAL_MINS).asInt()));
+            }
+            if (lmNode.get(AVAILABILITY_NUMBER_CONSECUTIVE_FLR_MEASUREMENTS) != null) {
+                builder = builder.availabilityNumberConsecutiveFlrMeasurements(
+                        lmNode.get(AVAILABILITY_NUMBER_CONSECUTIVE_FLR_MEASUREMENTS).asInt());
+            }
+            if (lmNode.get(AVAILABILITY_FLR_THRESHOLD_PCT) != null) {
+                builder = builder.availabilityFlrThreshold(
+                        MilliPct.ofPercent((float) lmNode.get(AVAILABILITY_FLR_THRESHOLD_PCT).asDouble()));
+            }
+            if (lmNode.get(AVAILABILITY_NUMBER_CONSECUTIVE_INTERVALS) != null) {
+                builder = builder.availabilityNumberConsecutiveIntervals(
+                        (short) lmNode.get(AVAILABILITY_NUMBER_CONSECUTIVE_INTERVALS).asInt());
+            }
+            if (lmNode.get(AVAILABILITY_NUMBER_CONSECUTIVE_HIGH_FLR) != null) {
+                builder = builder.availabilityNumberConsecutiveHighFlr(
+                        (short) lmNode.get(AVAILABILITY_NUMBER_CONSECUTIVE_HIGH_FLR).asInt());
+            }
+            if (lmNode.get(FRAME_SIZE) != null) {
+                builder = (LmCreateBuilder) builder.frameSize(
+                        (short) lmNode.get(FRAME_SIZE).asInt());
+            }
+            if (lmNode.get(MESSAGE_PERIOD_MS) != null) {
+                builder = (LmCreateBuilder) builder.messagePeriod(Duration.ofMillis(
+                        lmNode.get(MESSAGE_PERIOD_MS).asInt()));
+            }
+            if (lmNode.get(MEASUREMENT_INTERVAL_MINS) != null) {
+                builder = (LmCreateBuilder) builder.measurementInterval(
+                        Duration.ofMinutes(
+                                lmNode.get(MEASUREMENT_INTERVAL_MINS).asInt()));
+            }
+            if (lmNode.get(ALIGN_MEASUREMENT_INTERVALS) != null) {
+                builder = (LmCreateBuilder) builder.alignMeasurementIntervals(
+                        lmNode.get(ALIGN_MEASUREMENT_INTERVALS).asBoolean());
+            }
+            if (lmNode.get(ALIGN_MEASUREMENT_OFFSET_MINS) != null) {
+                builder = (LmCreateBuilder) builder.alignMeasurementOffset(Duration.ofMinutes(
+                        lmNode.get(ALIGN_MEASUREMENT_OFFSET_MINS).asInt()));
+            }
+            if (lmNode.get(START_TIME) != null) {
+                builder = (LmCreateBuilder) builder.startTime(context.codec(StartTime.class)
+                        .decode((ObjectNode) lmNode.get(START_TIME), context));
+            }
+            if (lmNode.get(STOP_TIME) != null) {
+                builder = (LmCreateBuilder) builder.stopTime(context.codec(StopTime.class)
+                        .decode((ObjectNode) lmNode.get(STOP_TIME), context));
+            }
+
+
+            return builder.build();
+        } catch (SoamConfigException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    @Override
+    public ObjectNode encode(LossMeasurementCreate lm, CodecContext context) {
+        checkNotNull(lm, "LM cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put(LM_CFG_TYPE, lm.lmCfgType().name())
+                .put(VERSION, lm.version().name())
+                .put(REMOTE_MEP_ID, lm.remoteMepId().id())
+                .put(PRIORITY, lm.priority().name());
+
+        if (lm.countersEnabled() != null) {
+            result.set(COUNTERS_ENABLED, new LmCounterOptionCodec()
+                    .encode(lm.countersEnabled(), context));
+        }
+
+        if (lm.messagePeriod() != null) {
+            result.put(MESSAGE_PERIOD_MS, lm.messagePeriod().toMillis());
+        }
+        if (lm.frameSize() != null) {
+            result.put(FRAME_SIZE, lm.frameSize());
+        }
+
+
+        return result;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/LmEntryCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/LmEntryCodec.java
new file mode 100644
index 0000000..251bcd9
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/LmEntryCodec.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.soam.web;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementEntry;
+
+import java.util.Iterator;
+import java.util.Map.Entry;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Encode and decode to/from JSON to LossMeasurementEntry object.
+ */
+public class LmEntryCodec extends JsonCodec<LossMeasurementEntry> {
+
+    @Override
+    public ObjectNode encode(LossMeasurementEntry lm, CodecContext context) {
+        checkNotNull(lm, "LM cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put("lmId", lm.lmId().toString());
+
+        if (lm.measuredForwardFlr() != null) {
+            result.put("measuredForwardFlr", lm.measuredForwardFlr().percentValue());
+        }
+        if (lm.measuredBackwardFlr() != null) {
+            result.put("measuredBackwardFlr", lm.measuredBackwardFlr().percentValue());
+        }
+        if (lm.measuredAvailabilityForwardStatus() != null) {
+            result.put("measuredAvailabilityForwardStatus",
+                    lm.measuredAvailabilityForwardStatus().name());
+        }
+        if (lm.measuredAvailabilityBackwardStatus() != null) {
+            result.put("measuredAvailabilityBackwardStatus",
+                    lm.measuredAvailabilityBackwardStatus().name());
+        }
+        if (lm.measuredForwardLastTransitionTime() != null) {
+            result.put("measuredForwardLastTransitionTime",
+                    lm.measuredForwardLastTransitionTime().toString());
+        }
+        if (lm.measuredBackwardLastTransitionTime() != null) {
+            result.put("measuredBackwardLastTransitionTime",
+                    lm.measuredBackwardLastTransitionTime().toString());
+        }
+
+        ObjectNode lmAttrs = new LmCreateCodec().encode(lm, context);
+        Iterator<Entry<String, JsonNode>> elements = lmAttrs.fields();
+        while (elements.hasNext()) {
+            Entry<String, JsonNode> element = elements.next();
+            result.set(element.getKey(), element.getValue());
+        }
+
+        if (lm.measurementCurrent() != null) {
+            result.set("measurementCurrent", new LossMeasurementStatCurrentCodec()
+                    .encode(lm.measurementCurrent(), context));
+        }
+
+        if (lm.measurementHistories() != null) {
+            result.set("measurementHistories", new LossMeasurementStatHistoryCodec()
+                    .encode(lm.measurementHistories(), context));
+        }
+
+        if (lm.availabilityCurrent() != null) {
+            result.set("availabilityCurrent", new LossAvailabilityStatCurrentCodec()
+                    .encode(lm.availabilityCurrent(), context));
+        }
+
+        if (lm.availabilityHistories() != null) {
+            result.set("availabilityHistories", new LossAvailabilityStatHistoryCodec()
+                    .encode(lm.availabilityHistories(), context));
+        }
+
+        return result;
+    }
+
+    @Override
+    public ArrayNode encode(Iterable<LossMeasurementEntry> lmEntities, CodecContext context) {
+        ArrayNode an = context.mapper().createArrayNode();
+        lmEntities.forEach(dm -> an.add(encode(dm, context)));
+        return an;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/LmThresholdOptionCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/LmThresholdOptionCodec.java
new file mode 100644
index 0000000..98c0b62
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/LmThresholdOptionCodec.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.soam.web;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementThreshold.ThresholdOption;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Encode and decode to/from JSON to ThresholdOption object.
+ */
+public class LmThresholdOptionCodec extends JsonCodec<ThresholdOption> {
+
+    @Override
+    public ArrayNode encode(Iterable<ThresholdOption> entities,
+            CodecContext context) {
+        ArrayNode an = context.mapper().createArrayNode();
+        entities.forEach(node -> an.add(node.name()));
+
+        return an;
+    }
+
+    @Override
+    public List<ThresholdOption> decode(ArrayNode json,
+            CodecContext context) {
+        if (json == null) {
+            return null;
+        }
+        List<ThresholdOption> moList = new ArrayList<>();
+        json.forEach(node -> moList.add(ThresholdOption.valueOf(node.asText())));
+        return moList;
+    }
+
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/LossAvailabilityStatCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/LossAvailabilityStatCodec.java
new file mode 100644
index 0000000..3124976
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/LossAvailabilityStatCodec.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.soam.web;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossAvailabilityStat;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Encode and decode to/from JSON to LossAvailabilityStat object.
+ */
+public class LossAvailabilityStatCodec extends JsonCodec<LossAvailabilityStat> {
+
+    @Override
+    public ObjectNode encode(LossAvailabilityStat laStat, CodecContext context) {
+        checkNotNull(laStat, "LA stat cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put("elapsedTime", laStat.elapsedTime().toString())
+                .put("suspectStatus", String.valueOf(laStat.suspectStatus()));
+
+        if (laStat.forwardHighLoss() != null) {
+            result = result.put("forwardHighLoss",
+                    laStat.forwardHighLoss().toString());
+        }
+        if (laStat.backwardHighLoss() != null) {
+            result = result.put("backwardHighLoss",
+                    laStat.backwardHighLoss().toString());
+        }
+        if (laStat.forwardConsecutiveHighLoss() != null) {
+            result = result.put("forwardConsecutiveHighLoss",
+                    laStat.forwardConsecutiveHighLoss().toString());
+        }
+        if (laStat.backwardConsecutiveHighLoss() != null) {
+            result = result.put("backwardConsecutiveHighLoss",
+                    laStat.backwardConsecutiveHighLoss().toString());
+        }
+        if (laStat.forwardAvailable() != null) {
+            result = result.put("forwardAvailable",
+                    laStat.forwardAvailable().toString());
+        }
+        if (laStat.backwardAvailable() != null) {
+            result = result.put("backwardAvailable",
+                    laStat.backwardAvailable().toString());
+        }
+        if (laStat.forwardUnavailable() != null) {
+            result = result.put("forwardUnavailable",
+                    laStat.forwardUnavailable().toString());
+        }
+        if (laStat.backwardUnavailable() != null) {
+            result = result.put("backwardUnavailable",
+                    laStat.backwardUnavailable().toString());
+        }
+        if (laStat.backwardMinFrameLossRatio() != null) {
+            result = result.put("backwardMinFrameLossRatio",
+                    laStat.backwardMinFrameLossRatio().toString());
+        }
+        if (laStat.backwardMaxFrameLossRatio() != null) {
+            result = result.put("backwardMaxFrameLossRatio",
+                    laStat.backwardMaxFrameLossRatio().toString());
+        }
+        if (laStat.backwardAverageFrameLossRatio() != null) {
+            result = result.put("backwardAverageFrameLossRatio",
+                    laStat.backwardAverageFrameLossRatio().toString());
+        }
+        if (laStat.forwardMinFrameLossRatio() != null) {
+            result = result.put("forwardMinFrameLossRatio",
+                    laStat.forwardMinFrameLossRatio().toString());
+        }
+        if (laStat.forwardMaxFrameLossRatio() != null) {
+            result = result.put("forwardMaxFrameLossRatio",
+                    laStat.forwardMaxFrameLossRatio().toString());
+        }
+        if (laStat.forwardAverageFrameLossRatio() != null) {
+            result = result.put("forwardAverageFrameLossRatio",
+                    laStat.forwardAverageFrameLossRatio().toString());
+        }
+
+        return result;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/LossAvailabilityStatCurrentCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/LossAvailabilityStatCurrentCodec.java
new file mode 100644
index 0000000..a6dc5de
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/LossAvailabilityStatCurrentCodec.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.soam.web;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossAvailabilityStatCurrent;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Encode and decode to/from JSON to LossAvailabilityStatCurrent object.
+ */
+public class LossAvailabilityStatCurrentCodec extends JsonCodec<LossAvailabilityStatCurrent> {
+
+    @Override
+    public ObjectNode encode(LossAvailabilityStatCurrent laCurrent, CodecContext context) {
+        checkNotNull(laCurrent, "LA current cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put("startTime", laCurrent.startTime().toString())
+                .put("elapsedTime", laCurrent.elapsedTime().toString());
+
+        ObjectNode resultAbstract = new LossAvailabilityStatCodec().encode(laCurrent, context);
+        result.setAll(resultAbstract);
+
+        return result;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/LossAvailabilityStatHistoryCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/LossAvailabilityStatHistoryCodec.java
new file mode 100644
index 0000000..54e5160
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/LossAvailabilityStatHistoryCodec.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.soam.web;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossAvailabilityStatHistory;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Encode and decode to/from JSON to LossAvailabilityStatHistory object.
+ */
+public class LossAvailabilityStatHistoryCodec extends JsonCodec<LossAvailabilityStatHistory> {
+
+    @Override
+    public ObjectNode encode(LossAvailabilityStatHistory laHistory, CodecContext context) {
+        checkNotNull(laHistory, "LA history cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put("historyId", String.valueOf(laHistory.historyStatsId()))
+                .put("endTime", laHistory.endTime().toString());
+        ObjectNode resultAbstract = new LossAvailabilityStatCodec().encode(laHistory, context);
+        result.setAll(resultAbstract);
+        return result;
+    }
+
+    @Override
+    public ArrayNode encode(Iterable<LossAvailabilityStatHistory> historyEntities, CodecContext context) {
+        ArrayNode an = context.mapper().createArrayNode();
+        historyEntities.forEach(history -> an.add(encode(history, context)));
+        return an;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/LossMeasurementStatCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/LossMeasurementStatCodec.java
new file mode 100644
index 0000000..eac21e6
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/LossMeasurementStatCodec.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.soam.web;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementStat;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Encode and decode to/from JSON to LossMeasurementStat object.
+ */
+public class LossMeasurementStatCodec extends JsonCodec<LossMeasurementStat> {
+
+    @Override
+    public ObjectNode encode(LossMeasurementStat lmStat, CodecContext context) {
+        checkNotNull(lmStat, "LM stat cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put("elapsedTime", lmStat.elapsedTime().toString())
+                .put("suspectStatus", String.valueOf(lmStat.suspectStatus()));
+
+        if (lmStat.forwardTransmittedFrames() != null) {
+            result = result.put("forwardTransmittedFrames",
+                    lmStat.forwardTransmittedFrames().toString());
+        }
+        if (lmStat.forwardReceivedFrames() != null) {
+            result = result.put("forwardReceivedFrames",
+                    lmStat.forwardReceivedFrames().toString());
+        }
+        if (lmStat.forwardMinFrameLossRatio() != null) {
+            result = result.put("forwardMinFrameLossRatio",
+                    lmStat.forwardMinFrameLossRatio().toString());
+        }
+        if (lmStat.forwardMaxFrameLossRatio() != null) {
+            result = result.put("forwardMaxFrameLossRatio",
+                    lmStat.forwardMaxFrameLossRatio().toString());
+        }
+        if (lmStat.forwardAverageFrameLossRatio() != null) {
+            result = result.put("forwardAverageFrameLossRatio",
+                    lmStat.forwardAverageFrameLossRatio().toString());
+        }
+        if (lmStat.backwardTransmittedFrames() != null) {
+            result = result.put("backwardTransmittedFrames",
+                    lmStat.backwardTransmittedFrames().toString());
+        }
+        if (lmStat.backwardReceivedFrames() != null) {
+            result = result.put("backwardReceivedFrames",
+                    lmStat.backwardReceivedFrames().toString());
+        }
+        if (lmStat.backwardMinFrameLossRatio() != null) {
+            result = result.put("backwardMinFrameLossRatio",
+                    lmStat.backwardMinFrameLossRatio().toString());
+        }
+        if (lmStat.backwardMaxFrameLossRatio() != null) {
+            result = result.put("backwardMaxFrameLossRatio",
+                    lmStat.backwardMaxFrameLossRatio().toString());
+        }
+        if (lmStat.backwardAverageFrameLossRatio() != null) {
+            result = result.put("backwardAverageFrameLossRatio",
+                    lmStat.backwardAverageFrameLossRatio().toString());
+        }
+        if (lmStat.soamPdusSent() != null) {
+            result = result.put("soamPdusSent",
+                    lmStat.soamPdusSent().toString());
+        }
+        if (lmStat.soamPdusReceived() != null) {
+            result.put("soamPdusReceived",
+                    lmStat.soamPdusReceived().toString());
+        }
+
+        return result;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/LossMeasurementStatCurrentCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/LossMeasurementStatCurrentCodec.java
new file mode 100644
index 0000000..d525493
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/LossMeasurementStatCurrentCodec.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.soam.web;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementStatCurrent;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Encode and decode to/from JSON to LossMeasurementStatCurrent object.
+ */
+public class LossMeasurementStatCurrentCodec extends JsonCodec<LossMeasurementStatCurrent> {
+
+    @Override
+    public ObjectNode encode(LossMeasurementStatCurrent lmCurrent, CodecContext context) {
+        checkNotNull(lmCurrent, "LM current cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put("startTime", lmCurrent.startTime().toString())
+                .put("elapsedTime", lmCurrent.elapsedTime().toString());
+
+        ObjectNode resultAbstract = new LossMeasurementStatCodec().encode(lmCurrent, context);
+        result.setAll(resultAbstract);
+
+        return result;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/LossMeasurementStatHistoryCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/LossMeasurementStatHistoryCodec.java
new file mode 100644
index 0000000..e4ec282
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/LossMeasurementStatHistoryCodec.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.soam.web;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementStatHistory;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Encode and decode to/from JSON to LossMeasurementStatHistory object.
+ */
+public class LossMeasurementStatHistoryCodec extends JsonCodec<LossMeasurementStatHistory> {
+
+    @Override
+    public ObjectNode encode(LossMeasurementStatHistory lmHistory, CodecContext context) {
+        checkNotNull(lmHistory, "LM history cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put("historyId", String.valueOf(lmHistory.historyStatsId()))
+                .put("endTime", lmHistory.endTime().toString());
+        ObjectNode resultAbstract = new LossMeasurementStatCodec().encode(lmHistory, context);
+        result.setAll(resultAbstract);
+        return result;
+    }
+
+    @Override
+    public ArrayNode encode(Iterable<LossMeasurementStatHistory> historyEntities, CodecContext context) {
+        ArrayNode an = context.mapper().createArrayNode();
+        historyEntities.forEach(history -> an.add(encode(history, context)));
+        return an;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/LossMeasurementThresholdCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/LossMeasurementThresholdCodec.java
new file mode 100644
index 0000000..3a17f94
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/LossMeasurementThresholdCodec.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.soam.web;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.MilliPct;
+import org.onosproject.incubator.net.l2monitoring.soam.SoamId;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementThreshold;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.nullIsIllegal;
+import static org.onosproject.incubator.net.l2monitoring.soam.loss.DefaultLmThreshold.*;
+
+/**
+ * Encode and decode to/from JSON to LossMeasurementThreshold object.
+ */
+public class LossMeasurementThresholdCodec extends JsonCodec<LossMeasurementThreshold> {
+    static final String MEASUREDFLRFORWARD = "measuredFlrForward";
+    static final String MAXFLRFORWARD = "maxFlrForward";
+    static final String AVERAGEFLRFORWARD = "averageFlrForward";
+    static final String MEASUREDFLRBACKWARD = "measuredFlrBackward";
+    static final String MAXFLRBACKWARD = "maxFlrBackward";
+    static final String AVERAGEFLRBACKWARD = "averageFlrBackward";
+    static final String FORWARDHIGHLOSS = "forwardHighLoss";
+    static final String FORWARDCONSECUTIVEHIGHLOSS = "forwardConsecutiveHighLoss";
+    static final String BACKWARDHIGHLOSS = "backwardHighLoss";
+    static final String BACKWARDCONSECUTIVEHIGHLOSS = "backwardConsecutiveHighLoss";
+    static final String FORWARDUNAVAILABLECOUNT = "forwardUnavailableCount";
+    static final String FORWARDAVAILABLERATIO = "forwardAvailableRatio";
+    static final String BACKWARDUNAVAILABLECOUNT = "backwardUnavailableCount";
+    static final String BACKWARDAVAILABLERATIO = "backwardAvailableRatio";
+    static final String THRESHOLDOPTIONS = "thresholdOptions";
+
+    @Override
+    public ObjectNode encode(LossMeasurementThreshold lmt, CodecContext context) {
+        checkNotNull(lmt, "LM thresholds cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put("id", lmt.thresholdId().value());
+
+        if (lmt.thresholds() != null) {
+            result.set(THRESHOLDOPTIONS, new LmThresholdOptionCodec()
+                    .encode(lmt.thresholds(), context));
+        }
+
+        if (lmt.measuredFlrForward() != null) {
+            result.put(MEASUREDFLRFORWARD, lmt.measuredFlrForward().percentValue());
+        }
+        if (lmt.maxFlrForward() != null) {
+            result.put(MAXFLRFORWARD, lmt.maxFlrForward().percentValue());
+        }
+        if (lmt.averageFlrForward() != null) {
+            result.put(AVERAGEFLRFORWARD, lmt.averageFlrForward().percentValue());
+        }
+        if (lmt.measuredFlrBackward() != null) {
+            result.put(MEASUREDFLRBACKWARD, lmt.measuredFlrBackward().percentValue());
+        }
+        if (lmt.maxFlrBackward() != null) {
+            result.put(MAXFLRBACKWARD, lmt.maxFlrBackward().percentValue());
+        }
+        if (lmt.averageFlrBackward() != null) {
+            result.put(AVERAGEFLRBACKWARD, lmt.averageFlrBackward().percentValue());
+        }
+        if (lmt.forwardHighLoss() != null) {
+            result.put(FORWARDHIGHLOSS, lmt.forwardHighLoss().longValue());
+        }
+        if (lmt.forwardConsecutiveHighLoss() != null) {
+            result.put(FORWARDCONSECUTIVEHIGHLOSS, lmt.measuredFlrForward().longValue());
+        }
+        if (lmt.backwardHighLoss() != null) {
+            result.put(BACKWARDHIGHLOSS, lmt.backwardHighLoss().longValue());
+        }
+        if (lmt.backwardConsecutiveHighLoss() != null) {
+            result.put(BACKWARDCONSECUTIVEHIGHLOSS, lmt.backwardConsecutiveHighLoss().longValue());
+        }
+        if (lmt.forwardUnavailableCount() != null) {
+            result.put(FORWARDUNAVAILABLECOUNT, lmt.forwardUnavailableCount().longValue());
+        }
+        if (lmt.forwardAvailableRatio() != null) {
+            result.put(FORWARDAVAILABLERATIO, lmt.forwardAvailableRatio().percentValue());
+        }
+        if (lmt.backwardUnavailableCount() != null) {
+            result.put(BACKWARDUNAVAILABLECOUNT, lmt.backwardUnavailableCount().longValue());
+        }
+        if (lmt.backwardAvailableRatio() != null) {
+            result.put(BACKWARDAVAILABLERATIO, lmt.backwardAvailableRatio().percentValue());
+        }
+
+        return result;
+    }
+
+    @Override
+    public List<LossMeasurementThreshold> decode(ArrayNode json, CodecContext context) {
+        if (json == null) {
+            return null;
+        }
+        List<LossMeasurementThreshold> thrList = new ArrayList<>();
+        json.forEach(node -> thrList.add(decode((ObjectNode) node, context)));
+        return thrList;
+    }
+
+    @Override
+    public LossMeasurementThreshold decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        JsonNode thrNode = json.get("threshold");
+
+        SoamId thresholdId = SoamId.valueOf(nullIsIllegal(thrNode.get("id"),
+                "thresholdId must not be null").asInt());
+        LossMeasurementThreshold.LmThresholdBuilder builder = builder(thresholdId);
+
+        if (thrNode.get("thresholds") != null) {
+            context.codec(ThresholdOption.class)
+                    .decode((ArrayNode) (thrNode.get("thresholds")), context)
+                    .forEach(builder::addToThreshold);
+        }
+
+        if (thrNode.get(MEASUREDFLRFORWARD) != null) {
+            builder.measuredFlrForward(MilliPct.ofPercent(
+                    (float) thrNode.get(MEASUREDFLRFORWARD).asDouble()));
+        }
+        if (thrNode.get(MAXFLRFORWARD) != null) {
+            builder.maxFlrForward(MilliPct.ofPercent(
+                    (float) thrNode.get(MAXFLRFORWARD).asDouble()));
+        }
+        if (thrNode.get(AVERAGEFLRFORWARD) != null) {
+            builder.averageFlrForward(MilliPct.ofPercent(
+                    (float) thrNode.get(AVERAGEFLRFORWARD).asDouble()));
+        }
+        if (thrNode.get(MEASUREDFLRBACKWARD) != null) {
+            builder.measuredFlrBackward(MilliPct.ofPercent(
+                    (float) thrNode.get(MEASUREDFLRBACKWARD).asDouble()));
+        }
+        if (thrNode.get(MAXFLRBACKWARD) != null) {
+            builder.maxFlrBackward(MilliPct.ofPercent(
+                    (float) thrNode.get(MAXFLRBACKWARD).asDouble()));
+        }
+        if (thrNode.get(AVERAGEFLRBACKWARD) != null) {
+            builder.averageFlrBackward(MilliPct.ofPercent(
+                    (float) thrNode.get(AVERAGEFLRBACKWARD).asDouble()));
+        }
+        if (thrNode.get(FORWARDHIGHLOSS) != null) {
+            builder.forwardHighLoss(thrNode.get(FORWARDHIGHLOSS).asLong());
+        }
+        if (thrNode.get(FORWARDCONSECUTIVEHIGHLOSS) != null) {
+            builder.forwardConsecutiveHighLoss(thrNode.get(FORWARDCONSECUTIVEHIGHLOSS).asLong());
+        }
+        if (thrNode.get(BACKWARDHIGHLOSS) != null) {
+            builder.backwardHighLoss(thrNode.get(BACKWARDHIGHLOSS).asLong());
+        }
+        if (thrNode.get(BACKWARDCONSECUTIVEHIGHLOSS) != null) {
+            builder.backwardConsecutiveHighLoss(thrNode.get(BACKWARDCONSECUTIVEHIGHLOSS).asLong());
+        }
+        if (thrNode.get(FORWARDUNAVAILABLECOUNT) != null) {
+            builder.forwardUnavailableCount(thrNode.get(FORWARDUNAVAILABLECOUNT).asLong());
+        }
+        if (thrNode.get(FORWARDAVAILABLERATIO) != null) {
+            builder.forwardAvailableRatio(MilliPct.ofPercent(
+                    (float) thrNode.get(FORWARDAVAILABLERATIO).asDouble()));
+        }
+        if (thrNode.get(BACKWARDUNAVAILABLECOUNT) != null) {
+            builder.backwardUnavailableCount(thrNode.get(BACKWARDUNAVAILABLECOUNT).asLong());
+        }
+        if (thrNode.get(BACKWARDAVAILABLERATIO) != null) {
+            builder.backwardAvailableRatio(MilliPct.ofPercent(
+                    (float) thrNode.get(BACKWARDAVAILABLERATIO).asDouble()));
+        }
+
+        return builder.build();
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/StartTimeCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/StartTimeCodec.java
new file mode 100644
index 0000000..f84bffe
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/StartTimeCodec.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.soam.web;
+
+import java.time.Duration;
+import java.time.OffsetDateTime;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.StartTime;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Encode and decode to/from JSON to StartTime object.
+ */
+public class StartTimeCodec extends JsonCodec<StartTime> {
+
+    @Override
+    public StartTime decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        if (json.get("immediate") != null) {
+            return StartTime.immediate();
+        } else if (json.get("absolute") != null) {
+            if (json.get("absolute").get("start-time") != null) {
+                return StartTime.absolute(OffsetDateTime
+                        .parse(json.get("absolute").get("start-time").asText())
+                        .toInstant());
+            }
+            throw new IllegalArgumentException("StartTime absolute must contain "
+                    + "a start-time in date-time format with offset");
+        } else if (json.get("relative") != null) {
+            if (json.get("relative").get("start-time") != null) {
+                return StartTime.relative(Duration.parse(json.get("relative").get("start-time").asText()));
+            }
+            throw new IllegalArgumentException("StartTime relative must contain a start-time duration");
+        } else {
+            throw new IllegalArgumentException("StartTime must be either immediate, absolute or relative");
+        }
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/StopTimeCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/StopTimeCodec.java
new file mode 100644
index 0000000..30a1538
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/StopTimeCodec.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.soam.web;
+
+import java.time.Duration;
+import java.time.OffsetDateTime;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.StopTime;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Encode and decode to/from JSON to StopTime object.
+ */
+public class StopTimeCodec extends JsonCodec<StopTime> {
+
+    @Override
+    public StopTime decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        if (json.get("none") != null) {
+            return StopTime.none();
+        } else if (json.get("absolute") != null) {
+            if (json.get("absolute").get("stop-time") != null) {
+                return StopTime.absolute(OffsetDateTime
+                        .parse(json.get("absolute").get("stop-time").asText())
+                        .toInstant());
+            }
+            throw new IllegalArgumentException("StopTime absolute must contain "
+                    + "a stop-time in date-time format with offset");
+        } else if (json.get("relative") != null) {
+            if (json.get("relative").get("stop-time") != null) {
+                return StopTime.relative(Duration.parse(json.get("relative").get("stop-time").asText()));
+            }
+            throw new IllegalArgumentException("StopTime relative must contain a stop-time duration");
+        } else {
+            throw new IllegalArgumentException("StopTime must be either none, absolute or relative");
+        }
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/package-info.java b/apps/cfm/src/main/java/org/onosproject/soam/web/package-info.java
new file mode 100644
index 0000000..473be24
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.
+ */
+/**
+ * Codecs for converting SOAM objects to and from JSON.
+ */
+package org.onosproject.soam.web;
\ No newline at end of file
diff --git a/apps/cfm/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/cfm/src/main/resources/OSGI-INF/blueprint/shell-config.xml
new file mode 100644
index 0000000..5f53eba
--- /dev/null
+++ b/apps/cfm/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -0,0 +1,74 @@
+<!--
+  ~ Copyright 2017-present Open Networking Foundation
+  ~
+  ~ 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.cfm.cli.CfmMdAddCommand"/>
+            <completers>
+                <ref component-id="mdNameTypeCompleter"/>
+                <ref component-id="placeholderCompleter"/>
+                <ref component-id="mdLevelCompleter"/>
+                <ref component-id="placeholderCompleter"/>
+            </completers>
+        </command>
+        <command>
+            <action class="org.onosproject.cfm.cli.CfmMdDeleteCommand"/>
+            <completers>
+                <ref component-id="mdNameCompleter"/>
+            </completers>
+        </command>
+        <command>
+            <action class="org.onosproject.cfm.cli.CfmMdListMdCommand"/>
+            <completers>
+                <ref component-id="mdNameCompleter"/>
+            </completers>
+        </command>
+        <command>
+            <action class="org.onosproject.cfm.cli.CfmMaAddCommand"/>
+            <completers>
+                <ref component-id="mdNameCompleter"/>
+                <ref component-id="maNameTypeCompleter"/>
+                <ref component-id="placeholderCompleter"/>
+                <ref component-id="maCcmIntervalCompleter"/>
+                <ref component-id="placeholderCompleter"/>
+                <ref component-id="placeholderCompleter"/>
+                <ref component-id="compTagTypeCompleter"/>
+                <ref component-id="compMhfCompleter"/>
+                <ref component-id="placeholderCompleter"/>
+            </completers>
+        </command>
+        <command>
+            <action class="org.onosproject.cfm.cli.CfmMaDeleteCommand"/>
+            <completers>
+                <ref component-id="maNameCompleter"/>
+            </completers>
+        </command>
+    </command-bundle>
+
+    <bean id="placeholderCompleter" class="org.onosproject.cli.PlaceholderCompleter"/>
+    <bean id="mdNameTypeCompleter" class="org.onosproject.cfm.cli.completer.CfmMdNameTypeCompleter"/>
+    <bean id="maNameTypeCompleter" class="org.onosproject.cfm.cli.completer.CfmMaNameTypeCompleter"/>
+    <bean id="mdNameCompleter" class="org.onosproject.cfm.cli.completer.CfmMdNameCompleter"/>
+    <bean id="maNameCompleter" class="org.onosproject.cfm.cli.completer.CfmMaNameCompleter"/>
+    <bean id="mdLevelCompleter" class="org.onosproject.cfm.cli.completer.CfmMdLevelCompleter"/>
+    <bean id="maCcmIntervalCompleter" class="org.onosproject.cfm.cli.completer.CfmMaCcmIntervalCompleter"/>
+    <bean id="compTagTypeCompleter" class="org.onosproject.cfm.cli.completer.CfmCompTagTypeCompleter"/>
+    <bean id="compMhfCompleter" class="org.onosproject.cfm.cli.completer.CfmCompMhfCompleter"/>
+
+</blueprint>
+
+
diff --git a/apps/cfm/src/main/webapp/WEB-INF/web.xml b/apps/cfm/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..71c89e9
--- /dev/null
+++ b/apps/cfm/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2017-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.
+  -->
+<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://java.sun.com/xml/ns/javaee"
+         xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+         id="ONOS" version="2.5">
+    <display-name>Layer 2 Cfm REST API v1.0</display-name>
+
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Secured</web-resource-name>
+            <url-pattern>/*</url-pattern>
+        </web-resource-collection>
+        <auth-constraint>
+            <role-name>admin</role-name>
+        </auth-constraint>
+    </security-constraint>
+
+    <security-role>
+        <role-name>admin</role-name>
+    </security-role>
+
+    <login-config>
+        <auth-method>BASIC</auth-method>
+        <realm-name>karaf</realm-name>
+    </login-config>
+
+    <servlet>
+        <servlet-name>JAX-RS Service</servlet-name>
+        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
+        <init-param>
+            <param-name>javax.ws.rs.Application</param-name>
+            <param-value>org.onosproject.cfm.impl.CfmWebApplication</param-value>
+        </init-param>
+        <load-on-startup>1</load-on-startup>
+    </servlet>
+
+    <servlet-mapping>
+        <servlet-name>JAX-RS Service</servlet-name>
+        <url-pattern>/*</url-pattern>
+    </servlet-mapping>
+</web-app>
diff --git a/apps/cfm/src/test/java/org/onosproject/cfm/CfmCodecContext.java b/apps/cfm/src/test/java/org/onosproject/cfm/CfmCodecContext.java
new file mode 100644
index 0000000..0f1de5e
--- /dev/null
+++ b/apps/cfm/src/test/java/org/onosproject/cfm/CfmCodecContext.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.CodecService;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.codec.impl.CodecManager;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Mock codec context for use in codec unit tests.
+ */
+public class CfmCodecContext implements CodecContext {
+
+    private final ObjectMapper mapper = new ObjectMapper();
+    private final CodecManager codecManager = new CodecManager();
+    private final CfmWebComponent manager = new CfmWebComponent();
+
+    /**
+     * Constructs a new mock codec context.
+     */
+    public CfmCodecContext() {
+        codecManager.activate();
+        manager.codecService = codecManager;
+        manager.activate();
+    }
+
+    @Override
+    public ObjectMapper mapper() {
+        return mapper;
+    }
+
+    @Override
+    public <T> T getService(Class<T> serviceClass) {
+        return null;
+    }
+
+    @Override
+    public <T> JsonCodec<T> codec(Class<T> entityClass) {
+        return codecManager.getCodec(entityClass);
+    }
+
+    /**
+     * Get the codec manager.
+     *
+     * @return instance of codec manager
+     */
+    public CodecService codecManager() {
+        return codecManager;
+    }
+}
diff --git a/apps/cfm/src/test/java/org/onosproject/cfm/impl/CfmResourceTest.java b/apps/cfm/src/test/java/org/onosproject/cfm/impl/CfmResourceTest.java
new file mode 100644
index 0000000..bcbaa9a
--- /dev/null
+++ b/apps/cfm/src/test/java/org/onosproject/cfm/impl/CfmResourceTest.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.impl;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.onosproject.rest.resources.ResourceTest;
+
+/**
+ * Base class for CFM REST API tests.  Performs common configuration operations.
+ */
+public class CfmResourceTest extends ResourceTest {
+
+    /**
+     * Creates a new web-resource test.
+     */
+    public CfmResourceTest() {
+        super(ResourceConfig.forApplicationClass(CfmWebApplication.class));
+    }
+}
diff --git a/apps/cfm/src/test/java/org/onosproject/cfm/impl/MaWebResourceTest.java b/apps/cfm/src/test/java/org/onosproject/cfm/impl/MaWebResourceTest.java
new file mode 100644
index 0000000..6378ea9
--- /dev/null
+++ b/apps/cfm/src/test/java/org/onosproject/cfm/impl/MaWebResourceTest.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.impl;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onlab.packet.VlanId;
+import org.onlab.rest.BaseResource;
+import org.onosproject.cfm.CfmCodecContext;
+import org.onosproject.codec.CodecService;
+import org.onosproject.incubator.net.l2monitoring.cfm.Component;
+import org.onosproject.incubator.net.l2monitoring.cfm.DefaultComponent;
+import org.onosproject.incubator.net.l2monitoring.cfm.DefaultMaintenanceAssociation;
+import org.onosproject.incubator.net.l2monitoring.cfm.DefaultMaintenanceDomain;
+import org.onosproject.incubator.net.l2monitoring.cfm.MaintenanceAssociation;
+import org.onosproject.incubator.net.l2monitoring.cfm.MaintenanceDomain;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdShort;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdId;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MepId;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmConfigException;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmMdService;
+
+import javax.ws.rs.InternalServerErrorException;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Optional;
+
+import static junit.framework.TestCase.fail;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+public class MaWebResourceTest extends CfmResourceTest {
+    private final CfmMdService mdService = createMock(CfmMdService.class);
+
+    private static final MdId MDNAME1 = MdIdCharStr.asMdId("md-1");
+    private static final MaIdShort MANAME1 = MaIdCharStr.asMaId("ma-1-1");
+
+    private MaintenanceAssociation ma1;
+
+    @Before
+    public void setUpTest() throws CfmConfigException {
+        CfmCodecContext context = new CfmCodecContext();
+        ServiceDirectory testDirectory = new TestServiceDirectory()
+                .add(CfmMdService.class, mdService)
+                .add(CodecService.class, context.codecManager());
+        BaseResource.setServiceDirectory(testDirectory);
+
+        ma1 = DefaultMaintenanceAssociation
+                .builder(MANAME1, MDNAME1.getNameLength())
+                .addToRemoteMepIdList(MepId.valueOf((short) 101))
+                .addToRemoteMepIdList(MepId.valueOf((short) 102))
+                .ccmInterval(MaintenanceAssociation.CcmInterval.INTERVAL_3MS)
+                .maNumericId((short) 1)
+                .addToComponentList(
+                        DefaultComponent.builder(1)
+                                .tagType(Component.TagType.VLAN_STAG)
+                                .mhfCreationType(Component.MhfCreationType.NONE)
+                                .idPermission(Component.IdPermissionType.MANAGE)
+                                .addToVidList(VlanId.vlanId((short) 1010))
+                                .build())
+                .build();
+    }
+
+    @Test
+    public void testGetMa() {
+
+        expect(mdService.getMaintenanceAssociation(MDNAME1, MANAME1))
+                .andReturn(Optional.ofNullable(ma1)).anyTimes();
+        replay(mdService);
+
+        final WebTarget wt = target();
+        final String response = wt.path("md/" + MDNAME1.mdName()
+                + "/ma/" + MANAME1.maName()).request().get(String.class);
+
+        assertThat(response, is("{\"ma\":" +
+                "{\"maName\":\"ma-1-1\"," +
+                "\"maNameType\":\"CHARACTERSTRING\"," +
+                "\"maNumericId\":1," +
+                "\"ccm-interval\":\"INTERVAL_3MS\"," +
+                "\"component-list\":[{\"component\":" +
+                    "{\"component-id\":1," +
+                    "\"vid-list\":[{\"vid\":\"1010\"}]," +
+                    "\"mhf-creation-type\":\"NONE\"," +
+                    "\"id-permission\":\"MANAGE\"," +
+                    "\"tag-type\":\"VLAN_STAG\"}}]," +
+                "\"rmep-list\":" +
+                    "[{\"rmep\":101}," +
+                    "{\"rmep\":102}]}}"));
+    }
+
+    @Test
+    public void testGetMaEmpty() throws IOException {
+        MaIdShort maId2 = MaIdCharStr.asMaId("ma-2");
+        expect(mdService
+                .getMaintenanceAssociation(MDNAME1, maId2))
+                .andReturn(Optional.empty()).anyTimes();
+        replay(mdService);
+
+        final WebTarget wt = target();
+        try {
+            final String response = wt.path("md/" + MDNAME1.mdName()
+                    + "/ma/" + maId2.maName()).request().get(String.class);
+            fail("Expected InternalServerErrorException, as MA is unknown");
+        } catch (InternalServerErrorException e) {
+            ByteArrayInputStream is = (ByteArrayInputStream) e.getResponse().getEntity();
+            BufferedReader br = new BufferedReader(new InputStreamReader(is));
+            String line = null;
+            StringBuffer sb = new StringBuffer();
+            while ((line = br.readLine()) != null) {
+                sb.append(line);
+            }
+
+            assertThat(sb.toString(), is("{ \"failure\":" +
+                    "\"java.lang.IllegalArgumentException: MA ma-2 not Found\" }"));
+        }
+    }
+
+    @Test
+    public void testDeleteMa() throws CfmConfigException {
+
+        expect(mdService.deleteMaintenanceAssociation(MDNAME1, MANAME1))
+                .andReturn(true).anyTimes();
+        replay(mdService);
+
+        final WebTarget wt = target();
+        final Response response = wt.path("md/" + MDNAME1.mdName()
+                + "/ma/" + MANAME1.maName()).request().delete();
+
+        assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testDeleteMaEmpty() throws CfmConfigException {
+        MaIdShort maId2 = MaIdCharStr.asMaId("ma-2");
+
+        expect(mdService.deleteMaintenanceAssociation(MDNAME1, maId2))
+                .andReturn(false).anyTimes();
+        replay(mdService);
+
+        final WebTarget wt = target();
+        final Response response = wt.path("md/" + MDNAME1.mdName()
+                + "/ma/" + maId2.maName()).request().delete();
+
+        assertEquals(304, response.getStatus());
+    }
+
+    @Test
+    public void testCreateMa() throws CfmConfigException {
+        MaintenanceDomain md1 = DefaultMaintenanceDomain
+                .builder(MDNAME1).mdLevel(MaintenanceDomain.MdLevel.LEVEL2).build();
+
+        expect(mdService.getMaintenanceDomain(MDNAME1))
+                .andReturn(Optional.ofNullable(md1)).anyTimes();
+        expect(mdService.createMaintenanceAssociation(MDNAME1, ma1))
+                .andReturn(false).anyTimes();
+        replay(mdService);
+
+        ObjectMapper mapper = new ObjectMapper();
+        CfmCodecContext context = new CfmCodecContext();
+        ObjectNode node = mapper.createObjectNode();
+        node.set("ma", context.codec(MaintenanceAssociation.class)
+                .encode(ma1, context));
+
+        final WebTarget wt = target();
+        final Response response = wt.path("md/" + MDNAME1.mdName()
+                + "/ma").request().post(Entity.json(node.toString()));
+
+        assertEquals(201, response.getStatus());
+    }
+}
diff --git a/apps/cfm/src/test/java/org/onosproject/cfm/impl/MdWebResourceTest.java b/apps/cfm/src/test/java/org/onosproject/cfm/impl/MdWebResourceTest.java
new file mode 100644
index 0000000..f4c9b5a
--- /dev/null
+++ b/apps/cfm/src/test/java/org/onosproject/cfm/impl/MdWebResourceTest.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.impl;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onlab.rest.BaseResource;
+import org.onosproject.cfm.CfmCodecContext;
+import org.onosproject.codec.CodecService;
+import org.onosproject.incubator.net.l2monitoring.cfm.DefaultMaintenanceDomain;
+import org.onosproject.incubator.net.l2monitoring.cfm.MaintenanceDomain;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdId;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmConfigException;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmMdService;
+
+import javax.ws.rs.InternalServerErrorException;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import static junit.framework.TestCase.fail;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+public class MdWebResourceTest extends CfmResourceTest {
+    private final CfmMdService mdService = createMock(CfmMdService.class);
+
+    private static final MdId MDNAME1 = MdIdCharStr.asMdId("md-1");
+    private static final MdId MDNAME2 = MdIdCharStr.asMdId("md-2");
+
+    private List<MaintenanceDomain> mdList;
+
+    @Before
+    public void setUpTest() throws CfmConfigException {
+        CfmCodecContext context = new CfmCodecContext();
+        ServiceDirectory testDirectory = new TestServiceDirectory()
+                .add(CfmMdService.class, mdService)
+                .add(CodecService.class, context.codecManager());
+        BaseResource.setServiceDirectory(testDirectory);
+
+        mdList = new ArrayList<>();
+
+        mdList.add(DefaultMaintenanceDomain.builder(MDNAME1)
+                .mdLevel(MaintenanceDomain.MdLevel.LEVEL1).build());
+        mdList.add(DefaultMaintenanceDomain.builder(MDNAME2)
+                .mdLevel(MaintenanceDomain.MdLevel.LEVEL2).build());
+    }
+
+    @Test
+    public void testGetMds() {
+        expect(mdService.getAllMaintenanceDomain()).andReturn(mdList).anyTimes();
+        replay(mdService);
+
+        final WebTarget wt = target();
+        final String response = wt.path("md").request().get(String.class);
+
+        assertThat(response, is("{\"mds\":[[" +
+                "{\"mdName\":\"md-1\",\"mdNameType\":\"CHARACTERSTRING\"," +
+                "\"mdLevel\":\"LEVEL1\",\"maList\":[]}," +
+                "{\"mdName\":\"md-2\",\"mdNameType\":\"CHARACTERSTRING\"," +
+                "\"mdLevel\":\"LEVEL2\",\"maList\":[]}]]}"));
+    }
+
+    @Test
+    public void testGetMdsEmpty() {
+        expect(mdService.getAllMaintenanceDomain())
+                            .andReturn(new ArrayList<>()).anyTimes();
+        replay(mdService);
+
+        final WebTarget wt = target();
+        final String response = wt.path("md").request().get(String.class);
+
+        assertThat(response, is("{\"mds\":[[]]}"));
+    }
+
+    @Test
+    public void testGetMd() {
+        expect(mdService.getMaintenanceDomain(MDNAME1))
+                .andReturn(Optional.ofNullable(mdList.get(0))).anyTimes();
+        replay(mdService);
+
+        final WebTarget wt = target();
+        final String response = wt.path("md/" + MDNAME1).request().get(String.class);
+
+        assertThat(response, is("{\"md\":" +
+                "{\"mdName\":\"md-1\",\"mdNameType\":\"CHARACTERSTRING\"," +
+                    "\"mdLevel\":\"LEVEL1\",\"maList\":[]}}"));
+    }
+
+    @Test
+    public void testGetMdEmpty() throws IOException {
+        final MdId mdName3 = MdIdCharStr.asMdId("md-3");
+        expect(mdService.getMaintenanceDomain(mdName3))
+                .andReturn(Optional.empty()).anyTimes();
+        replay(mdService);
+
+        final WebTarget wt = target();
+        try {
+            final String response = wt.path("md/" + mdName3).request().get(String.class);
+            fail("Expected InternalServerErrorException, as MD is unknown");
+        } catch (InternalServerErrorException e) {
+            ByteArrayInputStream is = (ByteArrayInputStream) e.getResponse().getEntity();
+            BufferedReader br = new BufferedReader(new InputStreamReader(is));
+            String line = null;
+            StringBuffer sb = new StringBuffer();
+            while ((line = br.readLine()) != null) {
+                sb.append(line);
+            }
+
+            assertThat(sb.toString(), is("{ \"failure\":" +
+                    "\"java.lang.IllegalArgumentException: MD md-3 not Found\" }"));
+        }
+    }
+
+    @Test
+    public void testDeleteMd() throws CfmConfigException {
+        expect(mdService.deleteMaintenanceDomain(MDNAME1))
+                .andReturn(true).anyTimes();
+        replay(mdService);
+
+        final WebTarget wt = target();
+        final Response response = wt.path("md/" + MDNAME1).request().delete();
+
+        assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testDeleteMdNotPresent() throws CfmConfigException {
+        expect(mdService.deleteMaintenanceDomain(MDNAME1))
+                .andReturn(false).anyTimes();
+        replay(mdService);
+
+        final WebTarget wt = target();
+        final Response response = wt.path("md/" + MDNAME1).request().delete();
+
+        assertEquals(304, response.getStatus());
+    }
+
+    @Test
+    public void testCreateMd() throws CfmConfigException {
+        MaintenanceDomain md3 = DefaultMaintenanceDomain
+                .builder(MdIdCharStr.asMdId("md-3"))
+                .mdLevel(MaintenanceDomain.MdLevel.LEVEL3)
+                .mdNumericId((short) 3)
+                .build();
+
+        expect(mdService.createMaintenanceDomain(mdList.get(1)))
+                .andReturn(false).anyTimes();
+        replay(mdService);
+
+        ObjectMapper mapper = new ObjectMapper();
+        CfmCodecContext context = new CfmCodecContext();
+        ObjectNode node = mapper.createObjectNode();
+        node.set("md", context.codec(MaintenanceDomain.class)
+                    .encode(mdList.get(1), context));
+
+
+        final WebTarget wt = target();
+        final Response response = wt.path("md")
+                .request().post(Entity.json(node.toString()));
+
+        assertEquals(201, response.getStatus());
+    }
+}
diff --git a/apps/cfm/src/test/java/org/onosproject/cfm/impl/MepWebResourceTest.java b/apps/cfm/src/test/java/org/onosproject/cfm/impl/MepWebResourceTest.java
new file mode 100644
index 0000000..4dd83db
--- /dev/null
+++ b/apps/cfm/src/test/java/org/onosproject/cfm/impl/MepWebResourceTest.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.impl;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onlab.packet.VlanId;
+import org.onlab.rest.BaseResource;
+import org.onosproject.cfm.CfmCodecContext;
+import org.onosproject.codec.CodecService;
+import org.onosproject.incubator.net.l2monitoring.cfm.DefaultMaintenanceAssociation;
+import org.onosproject.incubator.net.l2monitoring.cfm.DefaultMaintenanceDomain;
+import org.onosproject.incubator.net.l2monitoring.cfm.DefaultMep;
+import org.onosproject.incubator.net.l2monitoring.cfm.DefaultMepEntry;
+import org.onosproject.incubator.net.l2monitoring.cfm.DefaultMepLbCreate;
+import org.onosproject.incubator.net.l2monitoring.cfm.DefaultMepLtCreate;
+import org.onosproject.incubator.net.l2monitoring.cfm.MaintenanceAssociation;
+import org.onosproject.incubator.net.l2monitoring.cfm.MaintenanceDomain;
+import org.onosproject.incubator.net.l2monitoring.cfm.Mep;
+import org.onosproject.incubator.net.l2monitoring.cfm.MepEntry;
+import org.onosproject.incubator.net.l2monitoring.cfm.MepLbCreate;
+import org.onosproject.incubator.net.l2monitoring.cfm.MepLtCreate;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdShort;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdId;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MepId;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmConfigException;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmMdService;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmMepService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+
+import javax.ws.rs.InternalServerErrorException;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Collection;
+import java.util.Optional;
+
+import static junit.framework.TestCase.fail;
+import static org.easymock.EasyMock.*;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+public class MepWebResourceTest extends CfmResourceTest {
+    private final CfmMepService mepService = createMock(CfmMepService.class);
+    private final CfmMdService mdService = createMock(CfmMdService.class);
+
+    private static final MdId MDNAME1 = MdIdCharStr.asMdId("md-1");
+    private static final MaIdShort MANAME1 = MaIdCharStr.asMaId("ma-1-1");
+    private static final MepId MEPID1 = MepId.valueOf((short) 1);
+
+    private MepEntry mepEntry1 = null;
+
+    @Before
+    public void setUpTest() throws CfmConfigException {
+        CfmCodecContext context = new CfmCodecContext();
+        ServiceDirectory testDirectory = new TestServiceDirectory()
+                .add(CfmMepService.class, mepService)
+                .add(CfmMdService.class, mdService)
+                .add(CodecService.class, context.codecManager());
+        BaseResource.setServiceDirectory(testDirectory);
+
+        mepEntry1 = DefaultMepEntry.builder(
+                    MEPID1,
+                    DeviceId.deviceId("netconf:1.2.3.4:830"),
+                    PortNumber.portNumber(1),
+                    Mep.MepDirection.UP_MEP, MDNAME1, MANAME1)
+                .buildEntry();
+
+    }
+
+    @Test
+    public void testGetAllMepsForMaEmpty() throws CfmConfigException {
+
+        expect(mepService.getAllMeps(MDNAME1, MANAME1)).andReturn(null).anyTimes();
+        replay(mepService);
+
+        final WebTarget wt = target();
+        final String response = wt.path("md/" + MDNAME1.mdName() + "/ma/" +
+                MANAME1.maName() + "/mep").request().get(String.class);
+        assertThat(response, is("{\"meps\":[[]]}"));
+    }
+
+    @Test
+    public void testGetAllMepsForMa1Mep() throws CfmConfigException {
+        Collection<MepEntry> meps = new ArrayList<>();
+        meps.add(mepEntry1);
+
+        expect(mepService.getAllMeps(MDNAME1, MANAME1)).andReturn(meps).anyTimes();
+        replay(mepService);
+
+        final WebTarget wt = target();
+        final String response = wt.path("md/" + MDNAME1.mdName() + "/ma/" +
+                MANAME1.maName() + "/mep").request().get(String.class);
+
+        assertThat(response, is("{\"meps\":" +
+                "[[{" +
+                "\"mepId\":" + MEPID1.value() + "," +
+                "\"deviceId\":\"netconf:1.2.3.4:830\"," +
+                "\"port\":1," +
+                "\"direction\":\"UP_MEP\"," +
+                "\"mdName\":\"" + MDNAME1.mdName() + "\"," +
+                "\"maName\":\"" + MANAME1.maName() + "\"," +
+                "\"administrative-state\":false," +
+                "\"cci-enabled\":false," +
+                "\"remoteMeps\":[]}]]}"));
+    }
+
+    @Test
+    public void testGetMepValid() throws CfmConfigException {
+
+        expect(mepService.getMep(MDNAME1, MANAME1, MepId.valueOf((short) 1)))
+                .andReturn(mepEntry1).anyTimes();
+        replay(mepService);
+
+        final WebTarget wt = target();
+        final String response = wt.path("md/" + MDNAME1.mdName() + "/ma/" +
+                MANAME1.maName() + "/mep/" + MEPID1.value()).request().get(String.class);
+
+        assertThat(response, is("{\"mep\":" +
+                "{" +
+                "\"mepId\":" + MEPID1.value() + "," +
+                "\"deviceId\":\"netconf:1.2.3.4:830\"," +
+                "\"port\":1," +
+                "\"direction\":\"UP_MEP\"," +
+                "\"mdName\":\"" + MDNAME1.mdName() + "\"," +
+                "\"maName\":\"" + MANAME1.maName() + "\"," +
+                "\"administrative-state\":false," +
+                "\"cci-enabled\":false," +
+                "\"remoteMeps\":[]}}"));
+    }
+
+    @Test
+    public void testGetMepNotFound() throws CfmConfigException, IOException {
+
+        expect(mepService.getMep(MDNAME1, MANAME1, MepId.valueOf((short) 2)))
+                .andReturn(null).anyTimes();
+        replay(mepService);
+
+        final WebTarget wt = target();
+
+        try {
+            final String response = wt.path("md/" + MDNAME1.mdName() + "/ma/" +
+                    MANAME1.maName() + "/mep/" + 2).request().get(String.class);
+            fail("Expected exception to be thrown");
+        } catch (InternalServerErrorException e) {
+            ByteArrayInputStream is = (ByteArrayInputStream) e.getResponse().getEntity();
+            BufferedReader br = new BufferedReader(new InputStreamReader(is));
+            String line = null;
+            StringBuffer sb = new StringBuffer();
+            while ((line = br.readLine()) != null) {
+                sb.append(line);
+            }
+            assertEquals("{ \"failure\":\"MEP md-1/ma-1-1/2 not found\" }", sb.toString());
+        }
+    }
+
+    @Test
+    public void testDeleteMepValid() throws CfmConfigException {
+
+        expect(mepService.deleteMep(MDNAME1, MANAME1, MepId.valueOf((short) 1)))
+                .andReturn(true).anyTimes();
+        replay(mepService);
+
+        final WebTarget wt = target();
+        final Response response = wt.path("md/" + MDNAME1.mdName() + "/ma/" +
+                MANAME1.maName() + "/mep/" + MEPID1.value()).request().delete();
+
+        assertEquals("Expecting 200", 200, response.getStatus());
+    }
+
+    @Test
+    public void testDeleteMepNotFound() throws CfmConfigException {
+
+        expect(mepService.deleteMep(MDNAME1, MANAME1, MepId.valueOf((short) 2)))
+                .andReturn(false).anyTimes();
+        replay(mepService);
+
+        final WebTarget wt = target();
+        final Response response = wt.path("md/" + MDNAME1.mdName() + "/ma/" +
+                MANAME1.maName() + "/mep/2").request().delete();
+
+        assertEquals("Expecting 304", 304, response.getStatus());
+    }
+
+    @Test
+    public void testCreateMep() throws CfmConfigException, IOException {
+        MepId mepId2 = MepId.valueOf((short) 2);
+        Mep mep2 = DefaultMep.builder(
+                mepId2,
+                DeviceId.deviceId("netconf:2.2.3.4:830"),
+                PortNumber.portNumber(2),
+                Mep.MepDirection.UP_MEP, MDNAME1, MANAME1).build();
+
+        MaintenanceAssociation ma1 = DefaultMaintenanceAssociation
+                .builder(MANAME1, MDNAME1.getNameLength()).build();
+
+        expect(mdService.getMaintenanceAssociation(MDNAME1, MANAME1))
+                                .andReturn(Optional.ofNullable(ma1)).anyTimes();
+        replay(mdService);
+        expect(mepService.createMep(MDNAME1, MANAME1, mep2))
+                                .andReturn(true).anyTimes();
+        replay(mepService);
+
+        ObjectMapper mapper = new ObjectMapper();
+        CfmCodecContext context = new CfmCodecContext();
+        ObjectNode node = mapper.createObjectNode();
+        node.set("mep", context.codec(Mep.class).encode(mep2, context));
+
+        final WebTarget wt = target();
+        final Response response = wt.path("md/" + MDNAME1.mdName() + "/ma/" +
+                MANAME1.maName() + "/mep")
+                .request()
+                .post(Entity.json(node.toString()));
+
+        assertEquals("Expecting 201", 201, response.getStatus());
+    }
+
+    @Test
+    public void testCreateMepAlreadyExists() throws CfmConfigException, IOException {
+        MepId mepId3 = MepId.valueOf((short) 3);
+        Mep mep3 = DefaultMep.builder(
+                    mepId3,
+                    DeviceId.deviceId("netconf:3.2.3.4:830"),
+                    PortNumber.portNumber(3),
+                    Mep.MepDirection.UP_MEP, MDNAME1, MANAME1)
+                .cciEnabled(true)
+                .ccmLtmPriority(Mep.Priority.PRIO3)
+                .administrativeState(false)
+                .primaryVid(VlanId.vlanId((short) 3))
+                .defectAbsentTime(Duration.ofMinutes(2))
+                .defectPresentTime(Duration.ofMinutes(3))
+                .fngAddress(Mep.FngAddress.notSpecified())
+                .lowestFaultPriorityDefect(Mep.LowestFaultDefect.ALL_DEFECTS)
+                .build();
+
+        MaintenanceAssociation ma1 = DefaultMaintenanceAssociation
+                .builder(MANAME1, MDNAME1.getNameLength()).build();
+
+        expect(mdService.getMaintenanceAssociation(MDNAME1, MANAME1))
+                .andReturn(Optional.ofNullable(ma1)).anyTimes();
+        replay(mdService);
+        expect(mepService.createMep(MDNAME1, MANAME1, mep3))
+                .andReturn(false).anyTimes();
+        replay(mepService);
+
+        ObjectMapper mapper = new ObjectMapper();
+        CfmCodecContext context = new CfmCodecContext();
+        ObjectNode node = mapper.createObjectNode();
+        node.set("mep", context.codec(Mep.class).encode(mep3, context));
+
+        final WebTarget wt = target();
+        final Response response = wt.path("md/" + MDNAME1.mdName() + "/ma/" +
+                MANAME1.maName() + "/mep")
+                .request()
+                .post(Entity.json(node.toString()));
+
+        assertEquals("Expecting 304", 304, response.getStatus());
+    }
+
+    @Test
+    public void testTransmitLoopback() throws CfmConfigException {
+        MepLbCreate mepLbCreate1 = DefaultMepLbCreate
+                .builder(MEPID1)
+                .numberMessages(20)
+                .dataTlvHex("AA:BB:CC:DD")
+                .vlanDropEligible(true)
+                .build();
+
+        MaintenanceAssociation ma1 = DefaultMaintenanceAssociation
+                .builder(MANAME1, MDNAME1.getNameLength()).build();
+        MaintenanceDomain md1 = DefaultMaintenanceDomain.builder(MDNAME1)
+                .addToMaList(ma1).build();
+        expect(mdService.getMaintenanceDomain(MDNAME1))
+                        .andReturn(Optional.ofNullable(md1)).anyTimes();
+        expect(mdService.getMaintenanceAssociation(MDNAME1, MANAME1))
+                        .andReturn(Optional.ofNullable(ma1)).anyTimes();
+        replay(mdService);
+
+        ObjectMapper mapper = new ObjectMapper();
+        CfmCodecContext context = new CfmCodecContext();
+        ObjectNode node = mapper.createObjectNode();
+        node.set("loopback", context.codec(MepLbCreate.class).encode(mepLbCreate1, context));
+
+        final WebTarget wt = target();
+        final Response response = wt.path("md/" + MDNAME1.mdName() + "/ma/" +
+                MANAME1.maName() + "/mep/" + MEPID1.value() + "/transmit-loopback")
+                .request()
+                .put(Entity.json(node.toString()));
+
+        assertEquals("Expecting 202", 202, response.getStatus());
+    }
+
+    @Test
+    public void testAbortLoopback() throws CfmConfigException {
+
+        MepLbCreate mepLbCreate1 = DefaultMepLbCreate.builder(MEPID1).build();
+
+        MaintenanceAssociation ma1 = DefaultMaintenanceAssociation
+                .builder(MANAME1, MDNAME1.getNameLength()).build();
+        MaintenanceDomain md1 = DefaultMaintenanceDomain.builder(MDNAME1)
+                .addToMaList(ma1).build();
+        expect(mdService.getMaintenanceDomain(MDNAME1))
+                .andReturn(Optional.ofNullable(md1)).anyTimes();
+        expect(mdService.getMaintenanceAssociation(MDNAME1, MANAME1))
+                .andReturn(Optional.ofNullable(ma1)).anyTimes();
+        replay(mdService);
+
+        final WebTarget wt = target();
+        final Response response = wt.path("md/" + MDNAME1.mdName() + "/ma/" +
+                MANAME1.maName() + "/mep/" + MEPID1.value() + "/abort-loopback")
+                .request()
+                .put(Entity.json(""));
+
+        assertEquals("Expecting 202", 202, response.getStatus());
+
+    }
+
+    @Test
+    public void testTransmitLinktrace() throws CfmConfigException {
+        MepLtCreate mepLtCreate1 = DefaultMepLtCreate
+                .builder(MEPID1)
+                .defaultTtl((short) 20)
+                .transmitLtmFlags(BitSet.valueOf(new byte[]{1}))
+                .build();
+
+        MaintenanceAssociation ma1 = DefaultMaintenanceAssociation
+                .builder(MANAME1, MDNAME1.getNameLength()).build();
+        MaintenanceDomain md1 = DefaultMaintenanceDomain.builder(MDNAME1)
+                .addToMaList(ma1).build();
+        expect(mdService.getMaintenanceDomain(MDNAME1))
+                .andReturn(Optional.ofNullable(md1)).anyTimes();
+        expect(mdService.getMaintenanceAssociation(MDNAME1, MANAME1))
+                .andReturn(Optional.ofNullable(ma1)).anyTimes();
+        replay(mdService);
+
+        ObjectMapper mapper = new ObjectMapper();
+        CfmCodecContext context = new CfmCodecContext();
+        ObjectNode node = mapper.createObjectNode();
+        node.set("linktrace", context.codec(MepLtCreate.class).encode(mepLtCreate1, context));
+
+        final WebTarget wt = target();
+        final Response response = wt.path("md/" + MDNAME1.mdName() + "/ma/" +
+                MANAME1.maName() + "/mep/" + MEPID1.value() + "/transmit-linktrace")
+                .request()
+                .put(Entity.json(node.toString()));
+
+        assertEquals("Expecting 202", 202, response.getStatus());
+    }
+}
diff --git a/apps/cfm/src/test/java/org/onosproject/cfm/web/MaintenanceAssociationCodecTest.java b/apps/cfm/src/test/java/org/onosproject/cfm/web/MaintenanceAssociationCodecTest.java
new file mode 100644
index 0000000..9712c7d
--- /dev/null
+++ b/apps/cfm/src/test/java/org/onosproject/cfm/web/MaintenanceAssociationCodecTest.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.web;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.cfm.CfmCodecContext;
+import org.onosproject.incubator.net.l2monitoring.cfm.DefaultMaintenanceAssociation;
+import org.onosproject.incubator.net.l2monitoring.cfm.MaintenanceAssociation;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaId2Octet;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdIccY1731;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdPrimaryVid;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdRfc2685VpnId;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdShort;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmConfigException;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test that the MaintenanceAssociationCodec can successfully parse Json in to a MaintenanceAssociation.
+ */
+public class MaintenanceAssociationCodecTest {
+    private static final MaIdShort MAID1_CHAR = MaIdCharStr.asMaId("ma-1");
+    private static final MaIdShort MAID2_VID = MaIdPrimaryVid.asMaId(1234);
+    private static final MaIdShort MAID3_OCTET = MaId2Octet.asMaId(12467);
+    private static final MaIdShort MAID4_RFC = MaIdRfc2685VpnId.asMaIdHex("aa:bb:cc:dd:ee:ff:99");
+    private static final MaIdShort MAID5_Y1731 = MaIdIccY1731.asMaId("abc", "defghij");
+
+
+    private ObjectMapper mapper;
+    private CfmCodecContext context;
+
+    @Before
+    public void setUp() throws Exception, CfmConfigException {
+        mapper = new ObjectMapper();
+        context = new CfmCodecContext();
+    }
+
+    @Test
+    public void testDecodeMa1() throws IOException {
+        String mdString = "{\"ma\": {    \"maName\": \"ma-1\"," +
+                "\"maNameType\": \"CHARACTERSTRING\"," +
+                "\"component-list\": [], " +
+                "\"rmep-list\": [], " +
+                "\"maNumericId\": 1}}";
+
+        InputStream input = new ByteArrayInputStream(
+                mdString.getBytes(StandardCharsets.UTF_8));
+        JsonNode cfg = mapper.readTree(input);
+        MaintenanceAssociation maDecode1 = ((MaintenanceAssociationCodec) context
+                .codec(MaintenanceAssociation.class))
+                .decode((ObjectNode) cfg, context, 10);
+        assertEquals(MAID1_CHAR, maDecode1.maId());
+        assertEquals(1, maDecode1.maNumericId());
+    }
+
+    @Test
+    public void testDecodeMa1NoTypeGiven() throws IOException {
+        String mdString = "{\"ma\": {    \"maName\": \"ma-1\"," +
+                "\"component-list\": [], " +
+                "\"rmep-list\": [], " +
+                "\"maNumericId\": 1}}";
+
+        InputStream input = new ByteArrayInputStream(
+                mdString.getBytes(StandardCharsets.UTF_8));
+        JsonNode cfg = mapper.readTree(input);
+        MaintenanceAssociation maDecode1 = ((MaintenanceAssociationCodec) context
+                .codec(MaintenanceAssociation.class))
+                .decode((ObjectNode) cfg, context, 10);
+        assertEquals(MAID1_CHAR, maDecode1.maId());
+        assertEquals(1, maDecode1.maNumericId());
+    }
+
+    @Test
+    public void testDecodeMa2() throws IOException {
+        String mdString = "{\"ma\": {    \"maName\": 1234," +
+                "\"maNameType\": \"PRIMARYVID\"," +
+                "\"component-list\": [], " +
+                "\"rmep-list\": [], " +
+                "\"maNumericId\": 2}}";
+
+        InputStream input = new ByteArrayInputStream(
+                mdString.getBytes(StandardCharsets.UTF_8));
+        JsonNode cfg = mapper.readTree(input);
+        MaintenanceAssociation maDecode2 = ((MaintenanceAssociationCodec) context
+                .codec(MaintenanceAssociation.class))
+                .decode((ObjectNode) cfg, context, 10);
+        assertEquals(MAID2_VID, maDecode2.maId());
+    }
+
+    @Test
+    public void testDecodeMa3() throws IOException {
+        String mdString = "{\"ma\": {    \"maName\": 12467," +
+                "\"maNameType\": \"TWOOCTET\"," +
+                "\"component-list\": [], " +
+                "\"rmep-list\": [], " +
+                "\"maNumericId\": 3}}";
+
+        InputStream input = new ByteArrayInputStream(
+                mdString.getBytes(StandardCharsets.UTF_8));
+        JsonNode cfg = mapper.readTree(input);
+        MaintenanceAssociation maDecode3 = ((MaintenanceAssociationCodec) context
+                .codec(MaintenanceAssociation.class))
+                .decode((ObjectNode) cfg, context, 10);
+        assertEquals(MAID3_OCTET, maDecode3.maId());
+    }
+
+    @Test
+    public void testDecodeMa4() throws IOException {
+        String mdString = "{\"ma\": {    \"maName\": \"aa:bb:cc:dd:ee:ff:99\"," +
+                "\"maNameType\": \"RFC2685VPNID\"," +
+                "\"component-list\": [], " +
+                "\"rmep-list\": [], " +
+                "\"maNumericId\": 4}}";
+
+        InputStream input = new ByteArrayInputStream(
+                mdString.getBytes(StandardCharsets.UTF_8));
+        JsonNode cfg = mapper.readTree(input);
+        MaintenanceAssociation maDecode4 = ((MaintenanceAssociationCodec) context
+                .codec(MaintenanceAssociation.class))
+                .decode((ObjectNode) cfg, context, 10);
+        assertEquals(MAID4_RFC, maDecode4.maId());
+    }
+
+    @Test
+    public void testDecodeMa5() throws IOException {
+        String mdString = "{\"ma\": {    \"maName\": \"abc:defghij\"," +
+                "\"maNameType\": \"ICCY1731\"," +
+                "\"component-list\": [], " +
+                "\"rmep-list\": [], " +
+                "\"maNumericId\": 5}}";
+
+        InputStream input = new ByteArrayInputStream(
+                mdString.getBytes(StandardCharsets.UTF_8));
+        JsonNode cfg = mapper.readTree(input);
+        MaintenanceAssociation maDecode5 = ((MaintenanceAssociationCodec) context
+                .codec(MaintenanceAssociation.class))
+                .decode((ObjectNode) cfg, context, 10);
+        assertEquals(MAID5_Y1731, maDecode5.maId());
+    }
+
+    @Test
+    public void testEncodeMa1() throws CfmConfigException {
+        MaintenanceAssociation ma1 = DefaultMaintenanceAssociation.builder(MAID1_CHAR, 10)
+                .maNumericId((short) 1)
+                .build();
+
+        ObjectNode node = mapper.createObjectNode();
+        node.set("ma", context.codec(MaintenanceAssociation.class).encode(ma1, context));
+
+        assertEquals("{\"ma\":{" +
+                "\"maName\":\"ma-1\"," +
+                "\"maNameType\":\"CHARACTERSTRING\"," +
+                "\"maNumericId\":1," +
+                "\"component-list\":[]," +
+                "\"rmep-list\":[]}}", node.toString());
+    }
+
+    @Test
+    public void testEncodeMa2() throws CfmConfigException {
+        MaintenanceAssociation ma1 = DefaultMaintenanceAssociation.builder(MAID2_VID, 10)
+                .maNumericId((short) 2)
+                .build();
+
+        ObjectNode node = mapper.createObjectNode();
+        node.set("ma", context.codec(MaintenanceAssociation.class).encode(ma1, context));
+
+        assertEquals("{\"ma\":{" +
+                "\"maName\":\"1234\"," +
+                "\"maNameType\":\"PRIMARYVID\"," +
+                "\"maNumericId\":2," +
+                "\"component-list\":[]," +
+                "\"rmep-list\":[]}}", node.toString());
+    }
+
+    @Test
+    public void testEncodeMa3() throws CfmConfigException {
+        MaintenanceAssociation ma1 = DefaultMaintenanceAssociation.builder(MAID3_OCTET, 10)
+                .maNumericId((short) 3)
+                .build();
+
+        ObjectNode node = mapper.createObjectNode();
+        node.set("ma", context.codec(MaintenanceAssociation.class).encode(ma1, context));
+
+        assertEquals("{\"ma\":{" +
+                "\"maName\":\"12467\"," +
+                "\"maNameType\":\"TWOOCTET\"," +
+                "\"maNumericId\":3," +
+                "\"component-list\":[]," +
+                "\"rmep-list\":[]}}", node.toString());
+    }
+
+    @Test
+    public void testEncodeMa4() throws CfmConfigException {
+        MaintenanceAssociation ma1 = DefaultMaintenanceAssociation.builder(MAID4_RFC, 10)
+                .maNumericId((short) 4)
+                .build();
+
+        ObjectNode node = mapper.createObjectNode();
+        node.set("ma", context.codec(MaintenanceAssociation.class).encode(ma1, context));
+
+        assertEquals("{\"ma\":{" +
+                "\"maName\":\"aa:bb:cc:dd:ee:ff:99\"," +
+                "\"maNameType\":\"RFC2685VPNID\"," +
+                "\"maNumericId\":4," +
+                "\"component-list\":[]," +
+                "\"rmep-list\":[]}}", node.toString());
+    }
+
+    @Test
+    public void testEncodeMa5() throws CfmConfigException {
+        MaintenanceAssociation ma1 = DefaultMaintenanceAssociation.builder(MAID5_Y1731, 10)
+                .maNumericId((short) 5)
+                .build();
+
+        ObjectNode node = mapper.createObjectNode();
+        node.set("ma", context.codec(MaintenanceAssociation.class).encode(ma1, context));
+
+        assertEquals("{\"ma\":{" +
+                "\"maName\":\"abc:defghij\"," +
+                "\"maNameType\":\"ICCY1731\"," +
+                "\"maNumericId\":5," +
+                "\"component-list\":[]," +
+                "\"rmep-list\":[]}}", node.toString());
+    }
+}
diff --git a/apps/cfm/src/test/java/org/onosproject/cfm/web/MaintenanceDomainCodecTest.java b/apps/cfm/src/test/java/org/onosproject/cfm/web/MaintenanceDomainCodecTest.java
new file mode 100644
index 0000000..b1fff52
--- /dev/null
+++ b/apps/cfm/src/test/java/org/onosproject/cfm/web/MaintenanceDomainCodecTest.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.web;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.net.InternetDomainName;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.MacAddress;
+import org.onosproject.cfm.CfmCodecContext;
+import org.onosproject.incubator.net.l2monitoring.cfm.DefaultMaintenanceDomain;
+import org.onosproject.incubator.net.l2monitoring.cfm.MaintenanceDomain;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdId;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdDomainName;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdMacUint;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdNone;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmConfigException;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test that the MaintenanceDomainCodec can successfully parse Json in to a MaintenanceDomain.
+ */
+public class MaintenanceDomainCodecTest {
+    private static final MdId MDID1_CHAR = MdIdCharStr.asMdId("test-1");
+    private static final MdId MDID2_DOMAIN = MdIdDomainName.asMdId(
+                        InternetDomainName.from("test.opennetworking.org"));
+    private static final MdId MDID3_MACUINT =
+            MdIdMacUint.asMdId(MacAddress.valueOf("aa:bb:cc:dd:ee:ff"), 181);
+    private static final MdId MDID4_NONE = MdIdNone.asMdId();
+
+    private ObjectMapper mapper;
+    private CfmCodecContext context;
+
+    @Before
+    public void setUp() throws Exception, CfmConfigException {
+        mapper = new ObjectMapper();
+        context = new CfmCodecContext();
+    }
+
+    @Test
+    public void testDecodeMd1() throws IOException {
+        String mdString = "{\"md\": {    \"mdName\": \"test-1\"," +
+                "\"mdNameType\": \"CHARACTERSTRING\"," +
+                "\"mdLevel\": \"LEVEL1\", \"mdNumericId\": 1}}";
+
+        InputStream input = new ByteArrayInputStream(
+                mdString.getBytes(StandardCharsets.UTF_8));
+        JsonNode cfg = mapper.readTree(input);
+        MaintenanceDomain mdDecode1 = context
+                .codec(MaintenanceDomain.class).decode((ObjectNode) cfg, context);
+        assertEquals(MDID1_CHAR, mdDecode1.mdId());
+        assertEquals(MaintenanceDomain.MdLevel.LEVEL1, mdDecode1.mdLevel());
+        assertEquals(1, mdDecode1.mdNumericId());
+    }
+
+    @Test
+    public void testDecodeMd1NoTypeGiven() throws IOException {
+        String mdString = "{\"md\": {    \"mdName\": \"test-1\"," +
+                "\"mdLevel\": \"LEVEL1\", \"mdNumericId\": 1}}";
+
+        InputStream input = new ByteArrayInputStream(
+                mdString.getBytes(StandardCharsets.UTF_8));
+        JsonNode cfg = mapper.readTree(input);
+        MaintenanceDomain mdDecode1 = context
+                .codec(MaintenanceDomain.class).decode((ObjectNode) cfg, context);
+        assertEquals(MDID1_CHAR, mdDecode1.mdId());
+        assertEquals(MaintenanceDomain.MdLevel.LEVEL1, mdDecode1.mdLevel());
+        assertEquals(1, mdDecode1.mdNumericId());
+    }
+
+
+    @Test
+    public void testDecodeMd2() throws IOException {
+        String mdString = "{\"md\": {    \"mdName\": \"test.opennetworking.org\"," +
+                "\"mdNameType\": \"DOMAINNAME\"}}";
+
+        InputStream input = new ByteArrayInputStream(
+                mdString.getBytes(StandardCharsets.UTF_8));
+        JsonNode cfg = mapper.readTree(input);
+        MaintenanceDomain mdDecode1 = context
+                .codec(MaintenanceDomain.class).decode((ObjectNode) cfg, context);
+        assertEquals(MDID2_DOMAIN, mdDecode1.mdId());
+        assertEquals(MaintenanceDomain.MdLevel.LEVEL0, mdDecode1.mdLevel());
+        assertEquals(0, mdDecode1.mdNumericId());
+    }
+
+    @Test
+    public void testDecodeMd3() throws IOException {
+        String mdString = "{\"md\": {    \"mdName\": \"aa:bb:cc:dd:ee:ff:181\"," +
+                "\"mdNameType\": \"MACANDUINT\"}}";
+
+        InputStream input = new ByteArrayInputStream(
+                mdString.getBytes(StandardCharsets.UTF_8));
+        JsonNode cfg = mapper.readTree(input);
+        MaintenanceDomain mdDecode1 = context
+                .codec(MaintenanceDomain.class).decode((ObjectNode) cfg, context);
+        assertEquals(MDID3_MACUINT, mdDecode1.mdId());
+    }
+
+    @Test
+    public void testDecodeMd4() throws IOException {
+        String mdString = "{\"md\": {    \"mdName\": \"\"," +
+                "\"mdNameType\": \"NONE\"}}";
+
+        InputStream input = new ByteArrayInputStream(
+                mdString.getBytes(StandardCharsets.UTF_8));
+        JsonNode cfg = mapper.readTree(input);
+        MaintenanceDomain mdDecode1 = context
+                .codec(MaintenanceDomain.class).decode((ObjectNode) cfg, context);
+        assertEquals(MDID4_NONE, mdDecode1.mdId());
+    }
+
+    @Test
+    public void testEncodeMd1() throws CfmConfigException {
+        MaintenanceDomain md1 = DefaultMaintenanceDomain.builder(MDID1_CHAR)
+                .mdLevel(MaintenanceDomain.MdLevel.LEVEL1)
+                .mdNumericId((short) 1)
+                .build();
+
+        ObjectNode node = mapper.createObjectNode();
+        node.set("md", context.codec(MaintenanceDomain.class).encode(md1, context));
+
+        assertEquals("{\"md\":{" +
+                "\"mdName\":\"test-1\"," +
+                "\"mdNameType\":\"CHARACTERSTRING\"," +
+                "\"mdLevel\":\"LEVEL1\"," +
+                "\"mdNumericId\":1," +
+                "\"maList\":[]}}", node.toString());
+    }
+
+    @Test
+    public void testEncodeMd2() throws CfmConfigException {
+        MaintenanceDomain md2 = DefaultMaintenanceDomain.builder(MDID2_DOMAIN)
+                .mdLevel(MaintenanceDomain.MdLevel.LEVEL2).build();
+
+        ObjectNode node = mapper.createObjectNode();
+        node.set("md", context.codec(MaintenanceDomain.class).encode(md2, context));
+
+        assertEquals("{\"md\":{" +
+                "\"mdName\":\"test.opennetworking.org\"," +
+                "\"mdNameType\":\"DOMAINNAME\"," +
+                "\"mdLevel\":\"LEVEL2\"," +
+                "\"maList\":[]}}", node.toString());
+    }
+
+    @Test
+    public void testEncodeMd3() throws CfmConfigException {
+        MaintenanceDomain md3 = DefaultMaintenanceDomain.builder(MDID3_MACUINT)
+                .mdLevel(MaintenanceDomain.MdLevel.LEVEL3).build();
+
+        ObjectNode node = mapper.createObjectNode();
+        node.set("md", context.codec(MaintenanceDomain.class).encode(md3, context));
+
+        assertEquals("{\"md\":{" +
+                "\"mdName\":\"AA:BB:CC:DD:EE:FF:181\"," +
+                "\"mdNameType\":\"MACANDUINT\"," +
+                "\"mdLevel\":\"LEVEL3\"," +
+                "\"maList\":[]}}", node.toString());
+    }
+
+    @Test
+    public void testEncodeMd4() throws CfmConfigException {
+        MaintenanceDomain md4 = DefaultMaintenanceDomain.builder(MDID4_NONE)
+                .mdLevel(MaintenanceDomain.MdLevel.LEVEL4).build();
+
+        ObjectNode node = mapper.createObjectNode();
+        node.set("md", context.codec(MaintenanceDomain.class).encode(md4, context));
+
+        assertEquals("{\"md\":{" +
+                "\"mdName\":\"\"," +
+                "\"mdNameType\":\"NONE\"," +
+                "\"mdLevel\":\"LEVEL4\"," +
+                "\"maList\":[]}}", node.toString());
+    }
+}
diff --git a/apps/cfm/src/test/java/org/onosproject/cfm/web/MepEntryCodecTest.java b/apps/cfm/src/test/java/org/onosproject/cfm/web/MepEntryCodecTest.java
new file mode 100644
index 0000000..ea07b8e
--- /dev/null
+++ b/apps/cfm/src/test/java/org/onosproject/cfm/web/MepEntryCodecTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.web;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.MacAddress;
+import org.onosproject.cfm.CfmCodecContext;
+import org.onosproject.incubator.net.l2monitoring.cfm.DefaultMepEntry;
+import org.onosproject.incubator.net.l2monitoring.cfm.Mep.MepDirection;
+import org.onosproject.incubator.net.l2monitoring.cfm.Mep.Priority;
+import org.onosproject.incubator.net.l2monitoring.cfm.MepEntry;
+import org.onosproject.incubator.net.l2monitoring.cfm.MepEntry.MepEntryBuilder;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MepId;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmConfigException;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Test that the MepEntryCodec can successfully parse Json in to a Mep.
+ */
+public class MepEntryCodecTest {
+    ObjectMapper mapper;
+    CfmCodecContext context;
+    MepEntry mepEntry1;
+
+    @Before
+    public void setUp() throws Exception, CfmConfigException {
+        mapper = new ObjectMapper();
+        context = new CfmCodecContext();
+        MepEntryBuilder builder = DefaultMepEntry.builder(
+                MepId.valueOf((short) 22),
+                DeviceId.deviceId("netconf:1234:830"),
+                PortNumber.portNumber(2),
+                MepDirection.UP_MEP,
+                MdIdCharStr.asMdId("md-1"),
+                MaIdCharStr.asMaId("ma-1-1"))
+            .macAddress(MacAddress.valueOf("aa:bb:cc:dd:ee:ff"));
+        builder = (MepEntryBuilder) builder
+                .administrativeState(true)
+                .cciEnabled(true)
+                .ccmLtmPriority(Priority.PRIO1);
+        mepEntry1 = builder.buildEntry();
+
+    }
+
+    @Test
+    public void testEncodeMepEntryCodecContext() {
+        ObjectNode node = mapper.createObjectNode();
+        node.set("mep", context.codec(MepEntry.class).encode(mepEntry1, context));
+
+        assertEquals(22, node.get("mep").get("mepId").asInt());
+        assertEquals("aa:bb:cc:dd:ee:ff".toUpperCase(),
+                node.get("mep").get("macAddress").asText());
+        assertTrue(node.get("mep").get("administrative-state").asBoolean());
+        assertTrue(node.get("mep").get("cci-enabled").asBoolean());
+        assertEquals(Priority.PRIO1.ordinal(),
+                node.get("mep").get("ccm-ltm-priority").asInt());
+    }
+
+    @Test
+    public void testEncodeIterableOfMepEntryCodecContext() throws CfmConfigException {
+        MepEntry mepEntry2 = DefaultMepEntry.builder(
+                MepId.valueOf((short) 33),
+                DeviceId.deviceId("netconf:4321:830"),
+                PortNumber.portNumber(1),
+                MepDirection.DOWN_MEP,
+                MdIdCharStr.asMdId("md-2"),
+                MaIdCharStr.asMaId("ma-2-2"))
+            .buildEntry();
+
+        ArrayList<MepEntry> meps = new ArrayList<>();
+        meps.add(mepEntry1);
+        meps.add(mepEntry2);
+
+        ObjectNode node = mapper.createObjectNode();
+        node.set("mep", context.codec(MepEntry.class)
+                .encode(meps, context));
+
+        Iterator<JsonNode> an = node.get("mep").elements();
+        while (an.hasNext()) {
+            JsonNode jn = an.next();
+            assertEquals("md-", jn.get("mdName").asText().substring(0, 3));
+        }
+    }
+
+}
diff --git a/apps/cfm/src/test/java/org/onosproject/cfm/web/MepLbCreateCodecTest.java b/apps/cfm/src/test/java/org/onosproject/cfm/web/MepLbCreateCodecTest.java
new file mode 100644
index 0000000..e2c853a
--- /dev/null
+++ b/apps/cfm/src/test/java/org/onosproject/cfm/web/MepLbCreateCodecTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.web;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.cfm.CfmCodecContext;
+import org.onosproject.incubator.net.l2monitoring.cfm.MepLbCreate;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+public class MepLbCreateCodecTest {
+    ObjectMapper mapper;
+    CfmCodecContext context;
+
+    @Before
+    public void setUp() throws Exception {
+        mapper = new ObjectMapper();
+        context = new CfmCodecContext();
+    }
+
+    @Test
+    public void testDecodeMepLbCreateMepId() throws JsonProcessingException, IOException {
+        String loopbackString = "{\"loopback\": {    \"remoteMepId\": 20," +
+                "\"numberMessages\": 10,    \"vlanDropEligible\": true," +
+                "\"vlanPriority\": 6,    \"dataTlvHex\": \"0A:BB:CC\" }}";
+
+        InputStream input = new ByteArrayInputStream(
+                loopbackString.getBytes(StandardCharsets.UTF_8));
+        JsonNode cfg = mapper.readTree(input);
+        MepLbCreate mepLbCreate = context
+                .codec(MepLbCreate.class).decode((ObjectNode) cfg, context);
+
+        assertNull(mepLbCreate.remoteMepAddress());
+        assertEquals(20, mepLbCreate.remoteMepId().id().shortValue());
+        assertEquals(10, mepLbCreate.numberMessages().intValue());
+        assertEquals(6, mepLbCreate.vlanPriority().ordinal());
+        assertEquals(true, mepLbCreate.vlanDropEligible());
+        assertEquals("0A:BB:CC".toLowerCase(), mepLbCreate.dataTlvHex());
+    }
+
+    @Test
+    public void testDecodeMepLbCreateMepMac() throws JsonProcessingException, IOException {
+        String loopbackString = "{\"loopback\": {    " +
+                "\"remoteMepMac\": \"AA:BB:CC:DD:EE:FF\" }}";
+        InputStream input = new ByteArrayInputStream(
+                loopbackString.getBytes(StandardCharsets.UTF_8));
+        JsonNode cfg = mapper.readTree(input);
+        MepLbCreate mepLbCreate = context
+                .codec(MepLbCreate.class).decode((ObjectNode) cfg, context);
+
+        assertNull(mepLbCreate.remoteMepId());
+        assertEquals("AA:BB:CC:DD:EE:FF", mepLbCreate.remoteMepAddress().toString());
+        assertNull(mepLbCreate.dataTlvHex());
+    }
+}
diff --git a/apps/cfm/src/test/java/org/onosproject/cfm/web/MepLbEntryCodecTest.java b/apps/cfm/src/test/java/org/onosproject/cfm/web/MepLbEntryCodecTest.java
new file mode 100644
index 0000000..844e4a2
--- /dev/null
+++ b/apps/cfm/src/test/java/org/onosproject/cfm/web/MepLbEntryCodecTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.web;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.cfm.CfmCodecContext;
+import org.onosproject.incubator.net.l2monitoring.cfm.DefaultMepLbEntry;
+import org.onosproject.incubator.net.l2monitoring.cfm.MepLbEntry;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class MepLbEntryCodecTest {
+    ObjectMapper mapper;
+    CfmCodecContext context;
+
+    @Before
+    public void setUp() throws Exception {
+        mapper = new ObjectMapper();
+        context = new CfmCodecContext();
+    }
+
+    @Test
+    public void testEncodeMepLbEntryCodecContext() {
+        MepLbEntry mepLbEntry1 = DefaultMepLbEntry.builder()
+                .countLbrMacMisMatch(987654321L)
+                .countLbrReceived(987654322L)
+                .countLbrTransmitted(987654323L)
+                .countLbrValidInOrder(987654324L)
+                .countLbrValidOutOfOrder(987654325L)
+                .nextLbmIdentifier(987654326L)
+                .build();
+
+        assertEquals(987654321L, mepLbEntry1.countLbrMacMisMatch());
+        assertEquals(987654322L, mepLbEntry1.countLbrReceived());
+        assertEquals(987654323L, mepLbEntry1.countLbrTransmitted());
+        assertEquals(987654324L, mepLbEntry1.countLbrValidInOrder());
+        assertEquals(987654325L, mepLbEntry1.countLbrValidOutOfOrder());
+        assertEquals(987654326L, mepLbEntry1.nextLbmIdentifier());
+    }
+
+}
diff --git a/apps/cfm/src/test/java/org/onosproject/cfm/web/MepLtCreateCodecTest.java b/apps/cfm/src/test/java/org/onosproject/cfm/web/MepLtCreateCodecTest.java
new file mode 100644
index 0000000..3e0ec46
--- /dev/null
+++ b/apps/cfm/src/test/java/org/onosproject/cfm/web/MepLtCreateCodecTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.web;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.cfm.CfmCodecContext;
+import org.onosproject.incubator.net.l2monitoring.cfm.DefaultMepLtCreate;
+import org.onosproject.incubator.net.l2monitoring.cfm.MepLtCreate;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MepId;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.BitSet;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class MepLtCreateCodecTest {
+    ObjectMapper mapper;
+    CfmCodecContext context;
+
+    @Before
+    public void setUp() throws Exception {
+        mapper = new ObjectMapper();
+        context = new CfmCodecContext();
+    }
+
+    @Test
+    public void testDecodeMepLtCreateMepId() throws JsonProcessingException, IOException {
+        String linktraceString = "{\"linktrace\": {    " +
+                "\"remoteMepId\": 20," +
+                "\"defaultTtl\": 21," +
+                "\"transmitLtmFlags\": \"use-fdb-only\"}}";
+
+        InputStream input = new ByteArrayInputStream(
+                linktraceString.getBytes(StandardCharsets.UTF_8));
+        JsonNode cfg = mapper.readTree(input);
+        MepLtCreate mepLtCreate = context
+                .codec(MepLtCreate.class).decode((ObjectNode) cfg, context);
+
+        assertNull(mepLtCreate.remoteMepAddress());
+        assertEquals(20, mepLtCreate.remoteMepId().id().shortValue());
+        assertEquals(21, mepLtCreate.defaultTtl().intValue());
+        assertEquals(BitSet.valueOf(new byte[]{1}), mepLtCreate.transmitLtmFlags());
+    }
+
+    @Test
+    public void testDecodeMepLtCreateInvalidTransmitLtmFlags()
+            throws JsonProcessingException, IOException {
+        String linktraceString = "{\"linktrace\": {    " +
+                "\"remoteMepId\": 20," +
+                "\"transmitLtmFlags\": \"1\"}}";
+
+        InputStream input = new ByteArrayInputStream(
+                linktraceString.getBytes(StandardCharsets.UTF_8));
+        JsonNode cfg = mapper.readTree(input);
+        try {
+            context.codec(MepLtCreate.class).decode((ObjectNode) cfg, context);
+        } catch (IllegalArgumentException e) {
+            assertEquals("Expecting value 'use-fdb-only' or '' " +
+                    "for transmitLtmFlags", e.getMessage());
+        }
+    }
+
+    @Test
+    public void testEncodeMepLtCreate() {
+        MepId mepId1 = MepId.valueOf((short) 1);
+        MepLtCreate mepLtCreate1 = DefaultMepLtCreate
+                .builder(mepId1)
+                .defaultTtl((short) 20)
+                .transmitLtmFlags(BitSet.valueOf(new byte[]{1}))
+                .build();
+
+        ObjectMapper mapper = new ObjectMapper();
+        CfmCodecContext context = new CfmCodecContext();
+        ObjectNode node = mapper.createObjectNode();
+        node.set("linktrace", context.codec(MepLtCreate.class).encode(mepLtCreate1, context));
+
+        assertEquals("{\"linktrace\":{" +
+                "\"remoteMepId\":1," +
+                "\"defaultTtl\":20," +
+                "\"transmitLtmFlags\":\"use-fdb-only\"}}", node.toString());
+    }
+}
diff --git a/apps/cfm/src/test/java/org/onosproject/cfm/web/RemoteMepEntryCodecTest.java b/apps/cfm/src/test/java/org/onosproject/cfm/web/RemoteMepEntryCodecTest.java
new file mode 100644
index 0000000..7ad77ac
--- /dev/null
+++ b/apps/cfm/src/test/java/org/onosproject/cfm/web/RemoteMepEntryCodecTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.cfm.web;
+
+import static org.junit.Assert.assertEquals;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.MacAddress;
+import org.onosproject.cfm.CfmCodecContext;
+import org.onosproject.incubator.net.l2monitoring.cfm.DefaultRemoteMepEntry;
+import org.onosproject.incubator.net.l2monitoring.cfm.RemoteMepEntry;
+import org.onosproject.incubator.net.l2monitoring.cfm.RemoteMepEntry.InterfaceStatusTlvType;
+import org.onosproject.incubator.net.l2monitoring.cfm.RemoteMepEntry.PortStatusTlvType;
+import org.onosproject.incubator.net.l2monitoring.cfm.RemoteMepEntry.RemoteMepState;
+import org.onosproject.incubator.net.l2monitoring.cfm.SenderIdTlv.SenderIdTlvType;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MepId;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmConfigException;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+public class RemoteMepEntryCodecTest {
+    ObjectMapper mapper;
+    CfmCodecContext context;
+    RemoteMepEntry remoteMep1;
+
+    @Before
+    public void setUp() throws Exception, CfmConfigException {
+        mapper = new ObjectMapper();
+        context = new CfmCodecContext();
+        remoteMep1 = DefaultRemoteMepEntry
+                .builder(MepId.valueOf((short) 10), RemoteMepState.RMEP_OK)
+                .failedOrOkTime(Duration.ofMillis(546546546L))
+                .interfaceStatusTlvType(InterfaceStatusTlvType.IS_LOWERLAYERDOWN)
+                .macAddress(MacAddress.IPV4_MULTICAST)
+                .portStatusTlvType(PortStatusTlvType.PS_NO_STATUS_TLV)
+                .rdi(true)
+                .senderIdTlvType(SenderIdTlvType.SI_NETWORK_ADDRESS)
+                .build();
+    }
+
+    @Test
+    public void testEncodeRemoteMepEntryCodecContext() {
+        ObjectNode node = mapper.createObjectNode();
+        node.set("remoteMep", context.codec(RemoteMepEntry.class)
+                .encode(remoteMep1, context));
+
+        assertEquals(10, node.get("remoteMep").get("remoteMepId").asInt());
+    }
+
+    @Test
+    public void testEncodeIterableOfRemoteMepEntryCodecContext()
+            throws CfmConfigException {
+        RemoteMepEntry remoteMep2 = DefaultRemoteMepEntry
+                .builder(MepId.valueOf((short) 20), RemoteMepState.RMEP_IDLE)
+                .build();
+
+        ArrayList<RemoteMepEntry> remoteMeps = new ArrayList<>();
+        remoteMeps.add(remoteMep1);
+        remoteMeps.add(remoteMep2);
+
+        ObjectNode node = mapper.createObjectNode();
+        node.set("remoteMep", context.codec(RemoteMepEntry.class)
+                .encode(remoteMeps, context));
+
+        Iterator<JsonNode> an = node.get("remoteMep").elements();
+        while (an.hasNext()) {
+            JsonNode jn = an.next();
+            assertEquals("RMEP_", jn.get("remoteMepState").asText().substring(0, 5));
+        }
+    }
+}
diff --git a/apps/cfm/src/test/java/org/onosproject/soam/impl/DmWebResourceTest.java b/apps/cfm/src/test/java/org/onosproject/soam/impl/DmWebResourceTest.java
new file mode 100644
index 0000000..702bc09
--- /dev/null
+++ b/apps/cfm/src/test/java/org/onosproject/soam/impl/DmWebResourceTest.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.soam.impl;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onlab.rest.BaseResource;
+import org.onosproject.cfm.CfmCodecContext;
+import org.onosproject.cfm.impl.CfmResourceTest;
+import org.onosproject.codec.CodecService;
+import org.onosproject.incubator.net.l2monitoring.cfm.DefaultMepEntry;
+import org.onosproject.incubator.net.l2monitoring.cfm.Mep;
+import org.onosproject.incubator.net.l2monitoring.cfm.MepEntry;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdShort;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdId;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MepId;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmConfigException;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmMepService;
+import org.onosproject.incubator.net.l2monitoring.soam.SoamConfigException;
+import org.onosproject.incubator.net.l2monitoring.soam.SoamId;
+import org.onosproject.incubator.net.l2monitoring.soam.SoamService;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DefaultDelayMeasurementEntry;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DefaultDelayMeasurementStatCurrent;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementCreate;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementEntry;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementStatCurrent;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+
+import javax.ws.rs.InternalServerErrorException;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+
+import static junit.framework.TestCase.fail;
+import static org.easymock.EasyMock.*;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+public class DmWebResourceTest extends CfmResourceTest {
+    private final CfmMepService mepService = createMock(CfmMepService.class);
+    private final SoamService soamService = createMock(SoamService.class);
+
+    private static final MdId MDNAME1 = MdIdCharStr.asMdId("md-1");
+    private static final MaIdShort MANAME1 = MaIdCharStr.asMaId("ma-1-1");
+    private static final MepId MEPID1 = MepId.valueOf((short) 1);
+    private static final SoamId DM1 = SoamId.valueOf(1);
+    private static final SoamId DM2 = SoamId.valueOf(2);
+
+    private DelayMeasurementEntry dm1;
+    private DelayMeasurementEntry dm2;
+
+    private final Instant now = Instant.now();
+
+    @Before
+    public void setUpTest() throws CfmConfigException, SoamConfigException {
+        CfmCodecContext context = new CfmCodecContext();
+        ServiceDirectory testDirectory = new TestServiceDirectory()
+                .add(CfmMepService.class, mepService)
+                .add(SoamService.class, soamService)
+                .add(CodecService.class, context.codecManager());
+        BaseResource.setServiceDirectory(testDirectory);
+
+        DelayMeasurementStatCurrent.DmStatCurrentBuilder dmCurrBuilder1 =
+                (DelayMeasurementStatCurrent.DmStatCurrentBuilder)
+                        DefaultDelayMeasurementStatCurrent
+                        .builder(Duration.ofMinutes(1), false)
+                        .startTime(now)
+                        .frameDelayBackwardAvg(Duration.ofMillis(10))
+                        .frameDelayForwardAvg(Duration.ofMillis(11))
+                        .frameDelayRangeBackwardAvg(Duration.ofMillis(12));
+
+        dm1 = DefaultDelayMeasurementEntry.builder(DM1,
+                    DelayMeasurementCreate.DmType.DMDMM,
+                    DelayMeasurementCreate.Version.Y17312008,
+                    MepId.valueOf((short) 2),
+                    Mep.Priority.PRIO1)
+                .sessionStatus(DelayMeasurementEntry.SessionStatus.ACTIVE)
+                .frameDelayTwoWay(Duration.ofMillis(40))
+                .frameDelayBackward(Duration.ofMillis(30))
+                .frameDelayForward(Duration.ofMillis(10))
+                .interFrameDelayVariationTwoWay(Duration.ofMillis(8))
+                .interFrameDelayVariationBackward(Duration.ofMillis(3))
+                .interFrameDelayVariationForward(Duration.ofMillis(5))
+                .currentResult((DelayMeasurementStatCurrent) dmCurrBuilder1.build())
+                .build();
+
+        dm2 = DefaultDelayMeasurementEntry.builder(DM2,
+                    DelayMeasurementCreate.DmType.DMDMM,
+                    DelayMeasurementCreate.Version.Y17312011,
+                    MepId.valueOf((short) 2),
+                    Mep.Priority.PRIO2)
+                .build();
+    }
+
+    @Test
+    public void testGetAllDmsForMep() throws CfmConfigException, SoamConfigException {
+
+        List<DelayMeasurementEntry> dmList = new ArrayList<>();
+        dmList.add(dm1);
+        dmList.add(dm2);
+
+        expect(soamService.getAllDms(MDNAME1, MANAME1, MEPID1)).andReturn(dmList).anyTimes();
+        replay(soamService);
+
+        final WebTarget wt = target();
+        final String response = wt.path("md/" + MDNAME1.mdName() + "/ma/" +
+                MANAME1.maName() + "/mep/" + MEPID1.value() + "/dm")
+                .request().get(String.class);
+
+        assertThat(response, is("{\"dms\":[[" +
+                "{" +
+                    "\"dmId\":\"1\"," +
+                    "\"sessionStatus\":\"ACTIVE\"," +
+                    "\"frameDelayTwoWay\":\"PT0.04S\"," +
+                    "\"frameDelayForward\":\"PT0.01S\"," +
+                    "\"frameDelayBackward\":\"PT0.03S\"," +
+                    "\"interFrameDelayVariationTwoWay\":\"PT0.008S\"," +
+                    "\"interFrameDelayVariationForward\":\"PT0.005S\"," +
+                    "\"interFrameDelayVariationBackward\":\"PT0.003S\"," +
+                    "\"dmCfgType\":\"DMDMM\"," +
+                    "\"version\":\"Y17312008\"," +
+                    "\"remoteMepId\":2," +
+                    "\"priority\":\"PRIO1\"," +
+                    "\"measurementsEnabled\":[]," +
+                    "\"current\":{" +
+                        "\"startTime\":\"" + now + "\"," +
+                        "\"elapsedTime\":\"PT1M\"," +
+                        "\"suspectStatus\":\"false\"," +
+                        "\"frameDelayForwardAvg\":\"PT0.011S\"," +
+                        "\"frameDelayBackwardAvg\":\"PT0.01S\"," +
+                        "\"frameDelayRangeBackwardAvg\":\"PT0.012S\"" +
+                    "}," +
+                    "\"historic\":[]" +
+                "},{" +
+                    "\"dmId\":\"2\"," +
+                    "\"dmCfgType\":\"DMDMM\"," +
+                    "\"version\":\"Y17312011\"," +
+                    "\"remoteMepId\":2," +
+                    "\"priority\":\"PRIO2\"," +
+                    "\"measurementsEnabled\":[]," +
+                    "\"historic\":[]}]]" +
+                "}"));
+    }
+
+    @Test
+    public void testGetAllDmsForMepEmpty() throws CfmConfigException, SoamConfigException {
+
+        List<DelayMeasurementEntry> dmListEmpty = new ArrayList<>();
+
+        expect(soamService.getAllDms(MDNAME1, MANAME1, MEPID1)).andReturn(dmListEmpty).anyTimes();
+        replay(soamService);
+
+        final WebTarget wt = target();
+        final String response = wt.path("md/" + MDNAME1.mdName() + "/ma/" +
+                MANAME1.maName() + "/mep/" + MEPID1.value() + "/dm")
+                .request().get(String.class);
+
+        assertThat(response, is("{\"dms\":[[]]}"));
+    }
+
+    @Test
+    public void testGetDm() throws CfmConfigException, SoamConfigException {
+
+        expect(soamService.getDm(MDNAME1, MANAME1, MEPID1, DM1)).andReturn(dm1).anyTimes();
+        replay(soamService);
+
+        final WebTarget wt = target();
+        final String response = wt.path("md/" + MDNAME1.mdName() + "/ma/" +
+                MANAME1.maName() + "/mep/" + MEPID1.value() + "/dm/" + DM1.value())
+                .request().get(String.class);
+
+        assertThat(response, is("{\"dm\":" +
+                "{" +
+                    "\"dmId\":\"1\"," +
+                    "\"sessionStatus\":\"ACTIVE\"," +
+                    "\"frameDelayTwoWay\":\"PT0.04S\"," +
+                    "\"frameDelayForward\":\"PT0.01S\"," +
+                    "\"frameDelayBackward\":\"PT0.03S\"," +
+                    "\"interFrameDelayVariationTwoWay\":\"PT0.008S\"," +
+                    "\"interFrameDelayVariationForward\":\"PT0.005S\"," +
+                    "\"interFrameDelayVariationBackward\":\"PT0.003S\"," +
+                    "\"dmCfgType\":\"DMDMM\"," +
+                    "\"version\":\"Y17312008\"," +
+                    "\"remoteMepId\":2," +
+                    "\"priority\":\"PRIO1\"," +
+                    "\"measurementsEnabled\":[]," +
+                    "\"current\":{" +
+                        "\"startTime\":\"" + now + "\"," +
+                        "\"elapsedTime\":\"PT1M\"," +
+                        "\"suspectStatus\":\"false\"," +
+                        "\"frameDelayForwardAvg\":\"PT0.011S\"," +
+                        "\"frameDelayBackwardAvg\":\"PT0.01S\"," +
+                        "\"frameDelayRangeBackwardAvg\":\"PT0.012S\"" +
+                    "}," +
+                    "\"historic\":[]" +
+                "}}"));
+    }
+
+    @Test
+    public void testGetDmInvalid() throws CfmConfigException, SoamConfigException, IOException {
+
+        SoamId dm3 = SoamId.valueOf(3);
+
+        expect(soamService.getDm(MDNAME1, MANAME1, MEPID1, dm3)).andReturn(null).anyTimes();
+        replay(soamService);
+
+        final WebTarget wt = target();
+        try {
+            final String response = wt.path("md/" + MDNAME1.mdName() + "/ma/" +
+                    MANAME1.maName() + "/mep/" + MEPID1.value() + "/dm/" + dm3.value())
+                    .request().get(String.class);
+            fail("Expecting excpetion");
+        } catch (InternalServerErrorException e) {
+            ByteArrayInputStream is = (ByteArrayInputStream) e.getResponse().getEntity();
+            BufferedReader br = new BufferedReader(new InputStreamReader(is));
+            String line = null;
+            StringBuffer sb = new StringBuffer();
+            while ((line = br.readLine()) != null) {
+                sb.append(line);
+            }
+            assertEquals("{ \"failure\":\"DM md-1/ma-1-1/1/3 not found\" }", sb.toString());
+        }
+    }
+
+    @Test
+    public void testAbortDm() {
+
+        final WebTarget wt = target();
+        final Response response = wt.path("md/" + MDNAME1.mdName() + "/ma/" +
+                MANAME1.maName() + "/mep/" + MEPID1.value() + "/dm/" + DM1.value())
+                .request().delete();
+
+        assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testCreateDm() throws CfmConfigException, SoamConfigException {
+        MepEntry mep1 = DefaultMepEntry.builder(MEPID1, DeviceId.deviceId("netconf:1.2.3.4:830"),
+                PortNumber.portNumber(1), Mep.MepDirection.UP_MEP, MDNAME1, MANAME1).buildEntry();
+
+        expect(mepService.getMep(MDNAME1, MANAME1, MEPID1)).andReturn(mep1).anyTimes();
+        replay(mepService);
+
+        ObjectMapper mapper = new ObjectMapper();
+        CfmCodecContext context = new CfmCodecContext();
+        ObjectNode node = mapper.createObjectNode();
+        node.set("dm", context.codec(DelayMeasurementCreate.class).encode(dm1, context));
+
+        final WebTarget wt = target();
+        final Response response = wt.path("md/" + MDNAME1.mdName() + "/ma/" +
+                MANAME1.maName() + "/mep/" + MEPID1.value() + "/dm")
+                .request().post(Entity.json(node.toString()));
+
+        assertEquals(201, response.getStatus());
+    }
+
+    @Test
+    public void testClearDmHistory() {
+
+        final WebTarget wt = target();
+        final Response response = wt.path("md/" + MDNAME1.mdName() + "/ma/" +
+                MANAME1.maName() + "/mep/" + MEPID1.value() + "/dm/" + DM1.value() +
+                "/clear-history")
+                .request().put(Entity.json(""));
+
+        assertEquals(200, response.getStatus());
+    }
+
+}
diff --git a/apps/cfm/src/test/java/org/onosproject/soam/impl/LmWebResourceTest.java b/apps/cfm/src/test/java/org/onosproject/soam/impl/LmWebResourceTest.java
new file mode 100644
index 0000000..9dd70e6
--- /dev/null
+++ b/apps/cfm/src/test/java/org/onosproject/soam/impl/LmWebResourceTest.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.soam.impl;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onlab.rest.BaseResource;
+import org.onosproject.cfm.CfmCodecContext;
+import org.onosproject.cfm.impl.CfmResourceTest;
+import org.onosproject.codec.CodecService;
+import org.onosproject.incubator.net.l2monitoring.cfm.DefaultMepEntry;
+import org.onosproject.incubator.net.l2monitoring.cfm.Mep;
+import org.onosproject.incubator.net.l2monitoring.cfm.MepEntry;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MaIdShort;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdId;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MdIdCharStr;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MepId;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmConfigException;
+import org.onosproject.incubator.net.l2monitoring.cfm.service.CfmMepService;
+import org.onosproject.incubator.net.l2monitoring.soam.MilliPct;
+import org.onosproject.incubator.net.l2monitoring.soam.SoamConfigException;
+import org.onosproject.incubator.net.l2monitoring.soam.SoamId;
+import org.onosproject.incubator.net.l2monitoring.soam.SoamService;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementCreate;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.DefaultLmEntry;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementCreate;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementEntry;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+
+import javax.ws.rs.InternalServerErrorException;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+public class LmWebResourceTest extends CfmResourceTest {
+    private final CfmMepService mepService = createMock(CfmMepService.class);
+    private final SoamService soamService = createMock(SoamService.class);
+
+    private static final MdId MDNAME1 = MdIdCharStr.asMdId("md-1");
+    private static final MaIdShort MANAME1 = MaIdCharStr.asMaId("ma-1-1");
+    private static final MepId MEPID1 = MepId.valueOf((short) 1);
+    private static final SoamId LMID1 = SoamId.valueOf(1);
+    private static final SoamId LMID2 = SoamId.valueOf(2);
+
+    private LossMeasurementEntry lm1;
+    private LossMeasurementEntry lm2;
+
+    private final Instant now = Instant.now();
+
+    @Before
+    public void setUpTest() throws CfmConfigException, SoamConfigException {
+        CfmCodecContext context = new CfmCodecContext();
+        ServiceDirectory testDirectory = new TestServiceDirectory()
+                .add(CfmMepService.class, mepService)
+                .add(SoamService.class, soamService)
+                .add(CodecService.class, context.codecManager());
+        BaseResource.setServiceDirectory(testDirectory);
+
+        lm1 = DefaultLmEntry.builder(
+                    DelayMeasurementCreate.Version.Y17312008,
+                    MepId.valueOf((short) 10),
+                    Mep.Priority.PRIO1,
+                    LossMeasurementCreate.LmType.LMLMM,
+                LMID1)
+                .build();
+        lm2 = DefaultLmEntry.builder(
+                    DelayMeasurementCreate.Version.Y17312011,
+                    MepId.valueOf((short) 10),
+                    Mep.Priority.PRIO2,
+                    LossMeasurementCreate.LmType.LMLMM,
+                LMID2)
+                .measuredAvailabilityBackwardStatus(LossMeasurementEntry.AvailabilityType.AVAILABLE)
+                .measuredAvailabilityForwardStatus(LossMeasurementEntry.AvailabilityType.UNKNOWN)
+                .measuredBackwardFlr(MilliPct.ofPercent(49.9f))
+                .measuredForwardFlr(MilliPct.ofRatio(0.51f))
+                .measuredBackwardLastTransitionTime(now)
+                .measuredForwardLastTransitionTime(now)
+                .build();
+
+    }
+
+    @Test
+    public void testGetAllLmsForMep() throws CfmConfigException, SoamConfigException {
+        List<LossMeasurementEntry> lmList = new ArrayList<>();
+        lmList.add(lm1);
+        lmList.add(lm2);
+
+        expect(soamService.getAllLms(MDNAME1, MANAME1, MEPID1)).andReturn(lmList).anyTimes();
+        replay(soamService);
+
+        final WebTarget wt = target();
+        final String response = wt.path("md/" + MDNAME1.mdName() + "/ma/" +
+                MANAME1.maName() + "/mep/" + MEPID1.value() + "/lm")
+                .request().get(String.class);
+
+        assertThat(response, is("{\"lms\":[[" +
+                "{" +
+                    "\"lmId\":\"1\"," +
+                    "\"lmCfgType\":\"LMLMM\"," +
+                    "\"version\":\"Y17312008\"," +
+                    "\"remoteMepId\":10," +
+                    "\"priority\":\"PRIO1\"," +
+                    "\"countersEnabled\":[]," +
+                    "\"measurementHistories\":[]," +
+                    "\"availabilityHistories\":[]" +
+                "},{" +
+                    "\"lmId\":\"2\"," +
+                    "\"measuredForwardFlr\":51.0," +
+                    "\"measuredBackwardFlr\":49.9," +
+                    "\"measuredAvailabilityForwardStatus\":\"UNKNOWN\"," +
+                    "\"measuredAvailabilityBackwardStatus\":\"AVAILABLE\"," +
+                    "\"measuredForwardLastTransitionTime\":\"" + now + "\"," +
+                    "\"measuredBackwardLastTransitionTime\":\"" + now + "\"," +
+                    "\"lmCfgType\":\"LMLMM\"," +
+                    "\"version\":\"Y17312011\"," +
+                    "\"remoteMepId\":10," +
+                    "\"priority\":\"PRIO2\"," +
+                    "\"countersEnabled\":[]," +
+                    "\"measurementHistories\":[]," +
+                    "\"availabilityHistories\":[]" +
+                "}]]}"));
+    }
+
+    @Test
+    public void testGetAllLmsForMepEmpty() throws CfmConfigException, SoamConfigException {
+        List<LossMeasurementEntry> lmList = new ArrayList<>();
+
+        expect(soamService.getAllLms(MDNAME1, MANAME1, MEPID1)).andReturn(lmList).anyTimes();
+        replay(soamService);
+
+        final WebTarget wt = target();
+        final String response = wt.path("md/" + MDNAME1.mdName() + "/ma/" +
+                MANAME1.maName() + "/mep/" + MEPID1.value() + "/lm")
+                .request().get(String.class);
+
+        assertThat(response, is("{\"lms\":[[]]}"));
+    }
+
+    @Test
+    public void testGetLm() throws CfmConfigException, SoamConfigException {
+
+        expect(soamService.getLm(MDNAME1, MANAME1, MEPID1, LMID1)).andReturn(lm1).anyTimes();
+        replay(soamService);
+
+        final WebTarget wt = target();
+        final String response = wt.path("md/" + MDNAME1.mdName() + "/ma/" +
+                MANAME1.maName() + "/mep/" + MEPID1.value() + "/lm/" + LMID1.value())
+                .request().get(String.class);
+
+        assertThat(response, is("{\"lm\":" +
+                "{" +
+                "\"lmId\":\"1\"," +
+                "\"lmCfgType\":\"LMLMM\"," +
+                "\"version\":\"Y17312008\"," +
+                "\"remoteMepId\":10," +
+                "\"priority\":\"PRIO1\"," +
+                "\"countersEnabled\":[]," +
+                "\"measurementHistories\":[]," +
+                "\"availabilityHistories\":[]" +
+                "}}"));
+    }
+
+    @Test
+    public void testGetLmEmpty() throws CfmConfigException, SoamConfigException, IOException {
+        SoamId lmId3 = SoamId.valueOf(3);
+        expect(soamService.getLm(MDNAME1, MANAME1, MEPID1, lmId3))
+                .andReturn(null).anyTimes();
+        replay(soamService);
+
+        final WebTarget wt = target();
+        try {
+            final String response = wt.path("md/" + MDNAME1.mdName() + "/ma/" +
+                    MANAME1.maName() + "/mep/" + MEPID1.value() + "/lm/" + lmId3.value())
+                    .request().get(String.class);
+        } catch (InternalServerErrorException e) {
+            ByteArrayInputStream is = (ByteArrayInputStream) e.getResponse().getEntity();
+            BufferedReader br = new BufferedReader(new InputStreamReader(is));
+            String line = null;
+            StringBuffer sb = new StringBuffer();
+            while ((line = br.readLine()) != null) {
+                sb.append(line);
+            }
+            assertEquals("{ \"failure\":\"LM md-1/ma-1-1/1/3 not found\" }",
+                    sb.toString());
+        }
+    }
+
+    @Test
+    public void testAbortLm() {
+
+        final WebTarget wt = target();
+        final Response response = wt.path("md/" + MDNAME1.mdName() + "/ma/" +
+                MANAME1.maName() + "/mep/" + MEPID1.value() + "/lm/" + LMID1.value())
+                .request().delete();
+        assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testCreateLm() throws CfmConfigException, SoamConfigException {
+        MepEntry mep1 = DefaultMepEntry.builder(MEPID1, DeviceId.deviceId("netconf:1.2.3.4:830"),
+                PortNumber.portNumber(1), Mep.MepDirection.UP_MEP, MDNAME1, MANAME1).buildEntry();
+
+        expect(mepService.getMep(MDNAME1, MANAME1, MEPID1)).andReturn(mep1).anyTimes();
+        replay(mepService);
+
+        ObjectMapper mapper = new ObjectMapper();
+        CfmCodecContext context = new CfmCodecContext();
+        ObjectNode node = mapper.createObjectNode();
+        node.set("lm", context.codec(LossMeasurementCreate.class).encode(lm1, context));
+
+        final WebTarget wt = target();
+        final Response response = wt.path("md/" + MDNAME1.mdName() + "/ma/" +
+                MANAME1.maName() + "/mep/" + MEPID1.value() + "/lm")
+                .request().post(Entity.json(node.toString()));
+        assertEquals(201, response.getStatus());
+    }
+
+    @Test
+    public void testClearLmHistory() {
+
+        final WebTarget wt = target();
+        final Response response = wt.path("md/" + MDNAME1.mdName() + "/ma/" +
+                MANAME1.maName() + "/mep/" + MEPID1.value() + "/lm/" + LMID1.value() +
+                "/clear-history")
+                .request().put(Entity.json(""));
+
+        assertEquals(200, response.getStatus());
+    }
+}
diff --git a/apps/cfm/src/test/java/org/onosproject/soam/web/DmCreateCodecTest.java b/apps/cfm/src/test/java/org/onosproject/soam/web/DmCreateCodecTest.java
new file mode 100644
index 0000000..2f31310
--- /dev/null
+++ b/apps/cfm/src/test/java/org/onosproject/soam/web/DmCreateCodecTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.soam.web;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.cfm.CfmCodecContext;
+import org.onosproject.incubator.net.l2monitoring.cfm.Mep.Priority;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MepId;
+import org.onosproject.incubator.net.l2monitoring.soam.SoamConfigException;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DefaultDelayMeasurementCreate;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementCreate;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementCreate.DmCreateBuilder;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementCreate.DmType;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementCreate.MeasurementOption;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementCreate.Version;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+public class DmCreateCodecTest {
+    ObjectMapper mapper;
+    CfmCodecContext context;
+
+    @Before
+    public void setUp() throws Exception {
+        mapper = new ObjectMapper();
+        context = new CfmCodecContext();
+    }
+
+    @Test
+    public void testDecodeObjectNodeCodecContext1()
+            throws JsonProcessingException, IOException {
+        String moStr = "{\"dm\": {}}";
+
+        InputStream input = new ByteArrayInputStream(
+                moStr.getBytes(StandardCharsets.UTF_8));
+        JsonNode cfg = mapper.readTree(input);
+
+        try {
+            context.codec(DelayMeasurementCreate.class)
+                .decode((ObjectNode) cfg, context);
+            fail("Expecting an exception");
+        } catch (IllegalArgumentException e) {
+            assertEquals("remoteMepId is required", e.getMessage());
+        }
+    }
+
+    @Test
+    public void testDecodeObjectNodeCodecContext2()
+            throws JsonProcessingException, IOException {
+        String moStr = "{\"dm\": {" +
+            "\"version\":\"Y17312008\"," +
+            "\"dmType\":\"DMDMM\"," +
+            "\"remoteMepId\":12," +
+            "\"priority\":\"PRIO6\"," +
+            "\"measurementsEnabled\" :" +
+            "[\"FRAME_DELAY_RANGE_BACKWARD_AVERAGE\", " +
+            "\"INTER_FRAME_DELAY_VARIATION_FORWARD_AVERAGE\"]" +
+            "}}";
+
+        InputStream input = new ByteArrayInputStream(
+                moStr.getBytes(StandardCharsets.UTF_8));
+        JsonNode cfg = mapper.readTree(input);
+
+        DelayMeasurementCreate dmCreate = context
+                .codec(DelayMeasurementCreate.class)
+                .decode((ObjectNode) cfg, context);
+
+        assertEquals(Version.Y17312008, dmCreate.version());
+        assertEquals(DmType.DMDMM, dmCreate.dmCfgType());
+        assertEquals(12, dmCreate.remoteMepId().id().shortValue());
+    }
+
+    @Test
+    public void testEncodeDelayMeasurementCreateCodecContext()
+            throws SoamConfigException {
+        DmCreateBuilder builder = DefaultDelayMeasurementCreate
+                .builder(DmType.DM1DMRX, Version.Y17312011,
+                        MepId.valueOf((short) 16), Priority.PRIO5);
+        builder.addToMeasurementsEnabled(
+                MeasurementOption.FRAME_DELAY_BACKWARD_MAX);
+        builder.addToMeasurementsEnabled(
+                MeasurementOption.FRAME_DELAY_TWO_WAY_MAX);
+        builder.addToMeasurementsEnabled(
+                MeasurementOption.INTER_FRAME_DELAY_VARIATION_BACKWARD_BINS);
+        builder = (DmCreateBuilder) builder.messagePeriod(Duration.ofMillis(100));
+        builder = (DmCreateBuilder) builder.frameSize((short) 1200);
+
+        ObjectNode node = mapper.createObjectNode();
+        node.set("dm", context.codec(DelayMeasurementCreate.class)
+                .encode(builder.build(), context));
+
+        assertEquals(DmType.DM1DMRX.name(), node.get("dm").get("dmCfgType").asText());
+        assertEquals(Version.Y17312011.name(), node.get("dm").get("version").asText());
+        assertEquals(16, node.get("dm").get("remoteMepId").asInt());
+        assertEquals(Priority.PRIO5.name(), node.get("dm").get("priority").asText());
+        assertEquals(100, node.get("dm").get("messagePeriodMs").asInt());
+        assertEquals(1200, node.get("dm").get("frameSize").asInt());
+
+        assertEquals(3, ((ArrayNode) node.get("dm").get("measurementsEnabled")).size());
+    }
+
+}
diff --git a/apps/cfm/src/test/java/org/onosproject/soam/web/DmEntryCodecTest.java b/apps/cfm/src/test/java/org/onosproject/soam/web/DmEntryCodecTest.java
new file mode 100644
index 0000000..028fad8
--- /dev/null
+++ b/apps/cfm/src/test/java/org/onosproject/soam/web/DmEntryCodecTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.soam.web;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.cfm.CfmCodecContext;
+import org.onosproject.incubator.net.l2monitoring.cfm.Mep.Priority;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MepId;
+import org.onosproject.incubator.net.l2monitoring.soam.SoamConfigException;
+import org.onosproject.incubator.net.l2monitoring.soam.SoamId;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DefaultDelayMeasurementEntry;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementCreate.DmType;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementCreate.MeasurementOption;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementCreate.Version;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementEntry;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementEntry.DmEntryBuilder;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementEntry.SessionStatus;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+public class DmEntryCodecTest {
+    ObjectMapper mapper;
+    CfmCodecContext context;
+    DelayMeasurementEntry dmEntry1;
+
+    @Before
+    public void setUp() throws Exception, SoamConfigException {
+        mapper = new ObjectMapper();
+        context = new CfmCodecContext();
+        DmEntryBuilder builder = DefaultDelayMeasurementEntry
+                .builder(SoamId.valueOf(12), DmType.DM1DMTX,
+                        Version.Y17312008, MepId.valueOf((short) 10), Priority.PRIO4);
+        builder = builder.sessionStatus(SessionStatus.NOT_ACTIVE);
+        builder = builder.frameDelayTwoWay(Duration.ofNanos(101 * 1000));
+        builder = builder.frameDelayForward(Duration.ofNanos(102 * 1000));
+        builder = builder.frameDelayBackward(Duration.ofNanos(103 * 1000));
+        builder = builder.interFrameDelayVariationTwoWay(Duration.ofNanos(104 * 1000));
+        builder = builder.interFrameDelayVariationForward(Duration.ofNanos(105 * 1000));
+        builder = builder.interFrameDelayVariationBackward(Duration.ofNanos(106 * 1000));
+        builder.addToMeasurementsEnabled(MeasurementOption.FRAME_DELAY_BACKWARD_MAX);
+        builder.addToMeasurementsEnabled(MeasurementOption.FRAME_DELAY_TWO_WAY_MAX);
+        builder.addToMeasurementsEnabled(MeasurementOption.INTER_FRAME_DELAY_VARIATION_BACKWARD_BINS);
+
+        dmEntry1 = builder.build();
+    }
+
+    @Test
+    public void testEncodeDelayMeasurementEntryCodecContext()
+            throws JsonProcessingException, IOException {
+        ObjectNode node = mapper.createObjectNode();
+        node.set("dm", context.codec(DelayMeasurementEntry.class)
+                .encode(dmEntry1, context));
+
+        assertEquals(12, node.get("dm").get("dmId").asInt());
+        assertEquals(DmType.DM1DMTX.name(), node.get("dm").get("dmCfgType").asText());
+        assertEquals(Version.Y17312008.name(), node.get("dm").get("version").asText());
+        assertEquals(10, node.get("dm").get("remoteMepId").asInt());
+        assertEquals(3, ((ArrayNode) node.get("dm").get("measurementsEnabled")).size());
+
+        assertEquals(SessionStatus.NOT_ACTIVE.name(),
+                node.get("dm").get("sessionStatus").asText());
+        assertEquals("PT0.000101S",
+                node.get("dm").get("frameDelayTwoWay").asText());
+        assertEquals("PT0.000102S",
+                node.get("dm").get("frameDelayForward").asText());
+        assertEquals("PT0.000103S",
+                node.get("dm").get("frameDelayBackward").asText());
+        assertEquals("PT0.000104S",
+                node.get("dm").get("interFrameDelayVariationTwoWay").asText());
+        assertEquals("PT0.000105S",
+                node.get("dm").get("interFrameDelayVariationForward").asText());
+        assertEquals("PT0.000106S",
+                node.get("dm").get("interFrameDelayVariationBackward").asText());
+
+    }
+
+    @Test
+    public void testEncodeIterableOfDelayMeasurementEntryCodecContext()
+            throws SoamConfigException {
+        DmEntryBuilder builder2 = DefaultDelayMeasurementEntry
+                .builder(SoamId.valueOf(14), DmType.DM1DMRX,
+                        Version.Y17312011, MepId.valueOf((short) 16), Priority.PRIO5);
+        builder2.addToMeasurementsEnabled(MeasurementOption.FRAME_DELAY_BACKWARD_MIN);
+        builder2.addToMeasurementsEnabled(MeasurementOption.FRAME_DELAY_TWO_WAY_MIN);
+        builder2.addToMeasurementsEnabled(MeasurementOption.INTER_FRAME_DELAY_VARIATION_BACKWARD_MIN);
+
+        Collection<DelayMeasurementEntry> dmEntries = new ArrayList<>();
+        dmEntries.add(dmEntry1);
+        dmEntries.add(builder2.build());
+        ObjectNode node = mapper.createObjectNode();
+        node.set("dm", context.codec(DelayMeasurementEntry.class)
+                .encode(dmEntries, context));
+
+        assertEquals(2, ((ArrayNode) node.get("dm")).size());
+    }
+
+}
diff --git a/apps/cfm/src/test/java/org/onosproject/soam/web/DmMeasurementOptionCodecTest.java b/apps/cfm/src/test/java/org/onosproject/soam/web/DmMeasurementOptionCodecTest.java
new file mode 100644
index 0000000..4986d5a
--- /dev/null
+++ b/apps/cfm/src/test/java/org/onosproject/soam/web/DmMeasurementOptionCodecTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.soam.web;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.cfm.CfmCodecContext;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementCreate.MeasurementOption;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+
+public class DmMeasurementOptionCodecTest {
+    ObjectMapper mapper;
+    CfmCodecContext context;
+
+    @Before
+    public void setUp() throws Exception {
+        mapper = new ObjectMapper();
+        context = new CfmCodecContext();
+    }
+
+    @Test
+    public void testEncodeIterableOfMeasurementOptionCodecContext() {
+        List<MeasurementOption> moList = new ArrayList<>();
+        moList.add(MeasurementOption.FRAME_DELAY_BACKWARD_MAX);
+        moList.add(MeasurementOption.FRAME_DELAY_FORWARD_BINS);
+
+        ArrayNode an =
+                context.codec(MeasurementOption.class).encode(moList, context);
+
+        assertEquals(MeasurementOption.FRAME_DELAY_BACKWARD_MAX.toString(),
+                an.get(0).asText());
+        assertEquals(MeasurementOption.FRAME_DELAY_FORWARD_BINS.toString(),
+                an.get(1).asText());
+    }
+
+    @Test
+    public void testDecodeArrayNodeCodecContext()
+            throws JsonProcessingException, IOException {
+        String moStr = "{\"measurementsEnabled\": " +
+                "[\"FRAME_DELAY_RANGE_BACKWARD_AVERAGE\", " +
+                "\"INTER_FRAME_DELAY_VARIATION_FORWARD_AVERAGE\"]}";
+        InputStream input = new ByteArrayInputStream(
+                moStr.getBytes(StandardCharsets.UTF_8));
+        JsonNode cfg = mapper.readTree(input);
+        Iterable<MeasurementOption> moIter = context
+                .codec(MeasurementOption.class)
+                .decode((ArrayNode) cfg.get("measurementsEnabled"), context);
+
+        Iterator<MeasurementOption> source = moIter.iterator();
+        List<MeasurementOption> moList = new ArrayList<>();
+        source.forEachRemaining(moList::add);
+
+        assertEquals(MeasurementOption.FRAME_DELAY_RANGE_BACKWARD_AVERAGE.toString(),
+                moList.get(0).name());
+        assertEquals(MeasurementOption.INTER_FRAME_DELAY_VARIATION_FORWARD_AVERAGE.toString(),
+                moList.get(1).name());
+    }
+
+}
diff --git a/apps/cfm/src/test/resources/examples/CreateDM_Mep10.json b/apps/cfm/src/test/resources/examples/CreateDM_Mep10.json
new file mode 100644
index 0000000..4b8a962
--- /dev/null
+++ b/apps/cfm/src/test/resources/examples/CreateDM_Mep10.json
@@ -0,0 +1,18 @@
+{
+  "dm": {
+    "remoteMepId":20,
+    "dmCfgType": "DMDMM",
+    "version": "Y17312008",
+    "priority": "PRIO1",
+    "messagePeriodMs": 100,
+    "startTime": {
+      "immediate":true
+    },
+    "stopTime": {
+      "none":true
+    },
+    "frameSize": 1500,
+    "measurementIntervalMins": 1,
+    "measurementsEnabled": ["FRAME_DELAY_TWO_WAY_BINS","FRAME_DELAY_TWO_WAY_AVERAGE"]
+  }
+}
\ No newline at end of file
diff --git a/apps/cfm/src/test/resources/examples/CreateMa1InDomainA.json b/apps/cfm/src/test/resources/examples/CreateMa1InDomainA.json
new file mode 100644
index 0000000..0790aee
--- /dev/null
+++ b/apps/cfm/src/test/resources/examples/CreateMa1InDomainA.json
@@ -0,0 +1,23 @@
+{
+  "ma": {
+    "maName": "ma-vlan-1",
+    "maNameType": "CHARACTERSTRING",
+    "maNumericId": 1,
+    "ccm-interval": "INTERVAL_1S",
+    "component-list": [
+      { "component": {
+        "component-id":"1",
+        "tag-type": "VLAN_STAG",
+        "vid-list": [
+          {"vid":1}
+        ]
+      }
+      }
+    ],
+    "rmep-list": [
+      { "rmep":10 },
+      { "rmep":20 },
+      { "rmep":30 }
+    ]
+  }
+}
\ No newline at end of file
diff --git a/apps/cfm/src/test/resources/examples/CreateMa2InDomainA.json b/apps/cfm/src/test/resources/examples/CreateMa2InDomainA.json
new file mode 100644
index 0000000..67afdba
--- /dev/null
+++ b/apps/cfm/src/test/resources/examples/CreateMa2InDomainA.json
@@ -0,0 +1,22 @@
+{
+  "ma": {
+    "maName": "ma-vlan-2",
+    "maNameType": "CHARACTERSTRING",
+    "maNumericId": 2,
+    "ccm-interval": "INTERVAL_1S",
+    "component-list": [
+      { "component": {
+        "component-id":"1",
+        "tag-type": "VLAN_STAG",
+        "vid-list": [
+          {"vid":2}
+        ]
+      }
+      }
+    ],
+    "rmep-list": [
+      { "rmep":10 },
+      { "rmep":20 }
+    ]
+  }
+}
\ No newline at end of file
diff --git a/apps/cfm/src/test/resources/examples/CreateMdDomainA.json b/apps/cfm/src/test/resources/examples/CreateMdDomainA.json
new file mode 100644
index 0000000..2dbb761
--- /dev/null
+++ b/apps/cfm/src/test/resources/examples/CreateMdDomainA.json
@@ -0,0 +1,7 @@
+{"md": {
+  "mdName": "DomainA",
+  "mdNameType": "CHARACTERSTRING",
+  "mdLevel": "LEVEL3",
+  "mdNumeridId": 1
+}
+}
\ No newline at end of file
diff --git a/apps/cfm/src/test/resources/examples/CreateMep10.json b/apps/cfm/src/test/resources/examples/CreateMep10.json
new file mode 100644
index 0000000..3b393c2
--- /dev/null
+++ b/apps/cfm/src/test/resources/examples/CreateMep10.json
@@ -0,0 +1,14 @@
+{
+  "mep": {
+    "mepId": 10,
+    "deviceId": "netconf:192.168.56.10:830",
+    "port": 0,
+    "direction": "DOWN_MEP",
+    "mdName": "DomainA",
+    "maName": "ma-vlan-1",
+    "primary-vid": 1,
+    "administrative-state": true,
+    "ccm-ltm-priority": 4,
+    "cci-enabled" :true
+  }
+}
\ No newline at end of file
diff --git a/apps/cfm/src/test/resources/examples/README.md b/apps/cfm/src/test/resources/examples/README.md
new file mode 100644
index 0000000..3557cff
--- /dev/null
+++ b/apps/cfm/src/test/resources/examples/README.md
@@ -0,0 +1,12 @@
+Run:
+
+* curl --user onos:rocks -d @CreateMdDomainA.json http://localhost:8181/onos/cfm/md --header "Content-Type:application/json"
+* curl --user onos:rocks -d @CreateMa1InDomainA.json http://localhost:8181/onos/cfm/md/DomainA/ma --header "Content-Type:application/json"
+* curl --user onos:rocks -d @CreateMa2InDomainA.json http://localhost:8181/onos/cfm/md/DomainA/ma --header "Content-Type:application/json"
+* curl --user onos:rocks http://localhost:8181/onos/cfm/md 
+
+Create a Device that supports CFM Programmable, say at netconf:192.168.56.10:830
+* curl --user onos:rocks -d @CreateMep10.json http://localhost:8181/onos/cfm/md/DomainA/ma/ma-vlan-1/mep --header "Content-Type:application/json"
+
+Now call the RPC to create a Delay Measurement on that device
+* curl --user onos:rocks -X PUT -d @CreateDM_Mep10.json http://localhost:8181/onos/cfm/md/DomainA/ma/ma-vlan-1/mep/10/dm --header "Content-Type:application/json"
diff --git a/apps/pom.xml b/apps/pom.xml
index 26deeee..a5dba52 100644
--- a/apps/pom.xml
+++ b/apps/pom.xml
@@ -97,6 +97,7 @@
         <module>evpn-route-service</module>
         <module>l3vpn</module>
         <module>openstacknetworkingui</module>
+        <module>cfm</module>
     </modules>
 
     <properties>