initial code for the mac-learning app
Change-Id: I04f3b17c453e502f20477e83cb7cdc796b4160ae
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7ca0587
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,19 @@
+# Created by .ignore support plugin (hsz.mobi)
+
+# IntelliJ project files
+.idea
+*.iml
+out
+gen
+
+# core
+/*/target/
+/target/
+*.log
+*.gz
+
+# Visual Studio Code
+.settings
+.project
+.classpath
+.factorypath
\ No newline at end of file
diff --git a/.gitreview b/.gitreview
new file mode 100644
index 0000000..0d979d3
--- /dev/null
+++ b/.gitreview
@@ -0,0 +1,5 @@
+[gerrit]
+host=gerrit.opencord.org
+port=29418
+project=mac-learning.git
+defaultremote=origin
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..4b4b15c
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,3 @@
+We expect all ONF employees, member companies, and participants to abide by our Code of Conduct.
+
+If you are being harassed, notice that someone else is being harassed, or have any other concerns involving someone’s welfare, please notify a member of the ONF team or email conduct@opennetworking.org.
\ No newline at end of file
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..d9d9d30
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2016 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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e011b80
--- /dev/null
+++ b/README.md
@@ -0,0 +1,21 @@
+ONOS MAC Learner Application
+=====================================
+MAC Learner is an ONOS application that examines the DISCOVER or REQUEST type DHCP packets and
+keeps the MAC address temporarily according to the device, port and vlanId information obtained from
+these packets. If the package has a single tag, VlanVID is used;
+if it is a double tag, QinqVID is used as vlanId.
+
+Functionalities
+---------------
+- MAC Learner has REST API and CLI integration. You can access and modify MAC Address map via these.
+
+- By defining ports to the Igonered Port Map,
+it can be ensured that DHCP packets from these ports are not taken into account.
+
+###Parameters
+* __cacheDurationSec__ - MAC Mappings are held with a timestamp and scheduled executor running in the
+background that clears expired mappings(exist more than cacheDuration). The operating frequency of
+this executor can be set with this parameter. By default, it is 86400(1 day).
+
+* __enableDeviceListener__ - By enabling this parameter, the relevant mappings can be cleared
+for DEVICE_REMOVED and PORT_REMOVED device events. By default it is disabled.
\ No newline at end of file
diff --git a/api/pom.xml b/api/pom.xml
new file mode 100644
index 0000000..042b5e4
--- /dev/null
+++ b/api/pom.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>maclearner</artifactId>
+ <groupId>org.opencord</groupId>
+ <version>1.0.0-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>maclearner-api</artifactId>
+ <packaging>bundle</packaging>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/api/src/main/java/org/opencord/maclearner/api/DefaultMacLearner.java b/api/src/main/java/org/opencord/maclearner/api/DefaultMacLearner.java
new file mode 100644
index 0000000..17109f3
--- /dev/null
+++ b/api/src/main/java/org/opencord/maclearner/api/DefaultMacLearner.java
@@ -0,0 +1,72 @@
+/*
+ * 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.opencord.maclearner.api;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+
+/**
+ * Default Mac Learner object model implementation.
+ */
+public class DefaultMacLearner implements MacLearner {
+
+ private MacLearnerKey macLearnerKey;
+ private MacAddress macAddress;
+
+ public static final String DEVICE_ID_FN = "deviceId";
+ public static final String PORT_NUMBER_FN = "portNumber";
+ public static final String VLAN_ID_FN = "vlanId";
+ public static final String MAC_ADDRESS_FN = "macAddress";
+
+ @Override
+ public DeviceId deviceId() {
+ return macLearnerKey.getDeviceId();
+ }
+
+ @Override
+ public PortNumber portNumber() {
+ return macLearnerKey.getPortNumber();
+ }
+
+ @Override
+ public VlanId vlanId() {
+ return macLearnerKey.getVlanId();
+ }
+
+ @Override
+ public MacAddress macAddress() {
+ return macAddress;
+ }
+
+ public DefaultMacLearner(DeviceId deviceId, PortNumber portNumber, VlanId vlanId, MacAddress macAddress) {
+ this.macLearnerKey = new MacLearnerKey(deviceId, portNumber, vlanId);
+ this.macAddress = macAddress;
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .append(DEVICE_ID_FN, deviceId())
+ .append(PORT_NUMBER_FN, portNumber())
+ .append(VLAN_ID_FN, vlanId())
+ .append(MAC_ADDRESS_FN, macAddress())
+ .toString();
+ }
+
+}
diff --git a/api/src/main/java/org/opencord/maclearner/api/MacDeleteResult.java b/api/src/main/java/org/opencord/maclearner/api/MacDeleteResult.java
new file mode 100644
index 0000000..3db8a62
--- /dev/null
+++ b/api/src/main/java/org/opencord/maclearner/api/MacDeleteResult.java
@@ -0,0 +1,25 @@
+/*
+ * 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.opencord.maclearner.api;
+
+/**
+ * Result of Delete Mac Mapping operation.
+ */
+public enum MacDeleteResult {
+ SUCCESSFUL,
+ UNSUCCESSFUL,
+ NOT_EXIST
+}
diff --git a/api/src/main/java/org/opencord/maclearner/api/MacLearner.java b/api/src/main/java/org/opencord/maclearner/api/MacLearner.java
new file mode 100644
index 0000000..1ec8d36
--- /dev/null
+++ b/api/src/main/java/org/opencord/maclearner/api/MacLearner.java
@@ -0,0 +1,54 @@
+/*
+ * 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.opencord.maclearner.api;
+
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+
+/**
+ * Representation of a mac learner object.
+ */
+public interface MacLearner {
+
+ /**
+ * Returns the device identifier.
+ *
+ * @return device id
+ */
+ DeviceId deviceId();
+
+ /**
+ * Returns from which port the mac address is learned.
+ * @return port number
+ */
+ PortNumber portNumber();
+
+ /**
+ * Returns which vlan id of the package the mac address is learned from.
+ * If packet is double tagged, vlan id is equal to QinQVID.
+ * @return vlan id
+ */
+ VlanId vlanId();
+
+ /**
+ * Returns Mac Address information.
+ * @return mac address
+ */
+ MacAddress macAddress();
+
+}
diff --git a/api/src/main/java/org/opencord/maclearner/api/MacLearnerEvent.java b/api/src/main/java/org/opencord/maclearner/api/MacLearnerEvent.java
new file mode 100644
index 0000000..d28e31d
--- /dev/null
+++ b/api/src/main/java/org/opencord/maclearner/api/MacLearnerEvent.java
@@ -0,0 +1,54 @@
+/*
+ * 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.opencord.maclearner.api;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.onosproject.event.AbstractEvent;
+
+/**
+ * Entity that represents Mac Learner events.
+ */
+public class MacLearnerEvent extends AbstractEvent<MacLearnerEvent.Type, MacLearner> {
+
+ public enum Type {
+
+ /**
+ * New entry added to MacLearnerMap.
+ */
+ ADDED,
+ /**
+ * An entry removed from MacLearnerMap.
+ */
+ REMOVED,
+ }
+
+ /**
+ * Creates an event due to mac learner map update.
+ * @param type type of event
+ * @param macLearner mac learner object
+ */
+ public MacLearnerEvent(MacLearnerEvent.Type type, MacLearner macLearner) {
+ super(type, macLearner);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .append(super.toString())
+ .toString();
+ }
+
+}
diff --git a/api/src/main/java/org/opencord/maclearner/api/MacLearnerKey.java b/api/src/main/java/org/opencord/maclearner/api/MacLearnerKey.java
new file mode 100644
index 0000000..d892f18
--- /dev/null
+++ b/api/src/main/java/org/opencord/maclearner/api/MacLearnerKey.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.opencord.maclearner.api;
+
+import org.onlab.packet.VlanId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+
+import java.util.Objects;
+
+/**
+ * Key of Mac Address Map.
+ */
+public class MacLearnerKey {
+
+ private DeviceId deviceId;
+ private PortNumber portNumber;
+ private VlanId vlanId;
+
+ public DeviceId getDeviceId() {
+ return deviceId;
+ }
+
+ public void setDeviceId(DeviceId deviceId) {
+ this.deviceId = deviceId;
+ }
+
+ public PortNumber getPortNumber() {
+ return portNumber;
+ }
+
+ public void setPortNumber(PortNumber portNumber) {
+ this.portNumber = portNumber;
+ }
+
+ public VlanId getVlanId() {
+ return vlanId;
+ }
+
+ public void setVlanId(VlanId vlanId) {
+ this.vlanId = vlanId;
+ }
+
+ public MacLearnerKey(DeviceId deviceId, PortNumber portNumber, VlanId vlanId) {
+ this.deviceId = deviceId;
+ this.portNumber = portNumber;
+ this.vlanId = vlanId;
+ }
+
+ @Override
+ public String toString() {
+ return "MacLearnerKey{" +
+ "deviceId=" + deviceId +
+ ", portNumber=" + portNumber +
+ ", vlanId=" + vlanId +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ MacLearnerKey that = (MacLearnerKey) o;
+ return Objects.equals(deviceId, that.deviceId) &&
+ Objects.equals(portNumber, that.portNumber) &&
+ Objects.equals(vlanId, that.vlanId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(deviceId, portNumber, vlanId);
+ }
+
+}
diff --git a/api/src/main/java/org/opencord/maclearner/api/MacLearnerListener.java b/api/src/main/java/org/opencord/maclearner/api/MacLearnerListener.java
new file mode 100644
index 0000000..e8ea9cb
--- /dev/null
+++ b/api/src/main/java/org/opencord/maclearner/api/MacLearnerListener.java
@@ -0,0 +1,24 @@
+/*
+ * 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.opencord.maclearner.api;
+
+import org.onosproject.event.EventListener;
+
+/**
+ * Entity capable of receiving Mac Learner related events.
+ */
+public interface MacLearnerListener extends EventListener<MacLearnerEvent> {
+}
diff --git a/api/src/main/java/org/opencord/maclearner/api/MacLearnerProvider.java b/api/src/main/java/org/opencord/maclearner/api/MacLearnerProvider.java
new file mode 100644
index 0000000..fb4ace9
--- /dev/null
+++ b/api/src/main/java/org/opencord/maclearner/api/MacLearnerProvider.java
@@ -0,0 +1,24 @@
+/*
+ * 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.opencord.maclearner.api;
+
+import org.onosproject.net.provider.Provider;
+
+/**
+ * Abstraction of mac learner provider.
+ */
+public interface MacLearnerProvider extends Provider {
+}
diff --git a/api/src/main/java/org/opencord/maclearner/api/MacLearnerProviderService.java b/api/src/main/java/org/opencord/maclearner/api/MacLearnerProviderService.java
new file mode 100644
index 0000000..226aa98
--- /dev/null
+++ b/api/src/main/java/org/opencord/maclearner/api/MacLearnerProviderService.java
@@ -0,0 +1,24 @@
+/*
+ * 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.opencord.maclearner.api;
+
+import org.onosproject.net.provider.ProviderService;
+
+/**
+ * Abstraction of a flow rule provider service.
+ */
+public interface MacLearnerProviderService extends ProviderService<MacLearnerProvider> {
+}
diff --git a/api/src/main/java/org/opencord/maclearner/api/MacLearnerService.java b/api/src/main/java/org/opencord/maclearner/api/MacLearnerService.java
new file mode 100644
index 0000000..1fc3d97
--- /dev/null
+++ b/api/src/main/java/org/opencord/maclearner/api/MacLearnerService.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.opencord.maclearner.api;
+
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.event.ListenerService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Mac Learner Service.
+ */
+public interface MacLearnerService extends ListenerService<MacLearnerEvent, MacLearnerListener> {
+
+ /**
+ * Adding a port to ignore map to prevent mac mapping.
+ * @param deviceId openflow device id
+ * @param portNumber port number
+ */
+ void addPortToIgnore(DeviceId deviceId, PortNumber portNumber);
+
+ /**
+ * Removing a port from ignore port map.
+ * @param deviceId openflow device id
+ * @param portNumber port number
+ */
+ void removeFromIgnoredPorts(DeviceId deviceId, PortNumber portNumber);
+
+ /**
+ * Getting All MAC Mappings.
+ * @return Map of MAC Addresses by device id, port number and vlan id
+ */
+ Map<MacLearnerKey, MacAddress> getAllMappings();
+
+ /**
+ * Getting Requested MAC Address.
+ * @param deviceId openflow device id
+ * @param portNumber port number
+ * @param vlanId vlan id
+ * @return MAC Address of requested device id, port number and vlan id
+ */
+ Optional<MacAddress> getMacMapping(DeviceId deviceId, PortNumber portNumber, VlanId vlanId);
+
+ /**
+ * Deleting a MAC Mapping.
+ * @param deviceId openflow device id
+ * @param portNumber port number
+ * @param vlanId vlan id
+ * @return MacDeleteResult situation after method call
+ */
+ MacDeleteResult deleteMacMapping(DeviceId deviceId, PortNumber portNumber, VlanId vlanId);
+
+ /**
+ * Deleting MAC Mappings of port.
+ * @param deviceId openflow device id
+ * @param portNumber port number
+ * @return true if the mappings deleted successfully; false otherwise
+ */
+ boolean deleteMacMappings(DeviceId deviceId, PortNumber portNumber);
+
+ /**
+ * Deleting MAC Mappings of device.
+ * @param deviceId openflow device id
+ * @return true if the mappings deleted successfully; false otherwise
+ */
+ boolean deleteMacMappings(DeviceId deviceId);
+
+ /**
+ * Getting Device List in MAC Mapping List.
+ * @return mapped ONOS devices
+ */
+ Set<DeviceId> getMappedDevices();
+
+ /**
+ * Getting Port Number List in MAC Mapping List.
+ * @return mapped port numbers
+ */
+ Set<PortNumber> getMappedPorts();
+
+ /**
+ * Getting Ignored Ports for MAC Mapping.
+ * @return device and its ignored ports map
+ */
+ Map<DeviceId, Set<PortNumber>> getIgnoredPorts();
+
+}
diff --git a/api/src/main/java/org/opencord/maclearner/api/MacLearnerValue.java b/api/src/main/java/org/opencord/maclearner/api/MacLearnerValue.java
new file mode 100644
index 0000000..bc1f244
--- /dev/null
+++ b/api/src/main/java/org/opencord/maclearner/api/MacLearnerValue.java
@@ -0,0 +1,77 @@
+/*
+ * 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.opencord.maclearner.api;
+
+import org.onlab.packet.MacAddress;
+
+import java.util.Objects;
+
+/**
+ * Value of Mac Address Map which keeps a timestamp via Mac Address.
+ */
+public class MacLearnerValue {
+
+ private MacAddress macAddress;
+ private long timestamp;
+
+ public MacAddress getMacAddress() {
+ return macAddress;
+ }
+
+ public void setMacAddress(MacAddress macAddress) {
+ this.macAddress = macAddress;
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(long timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ public MacLearnerValue(MacAddress macAddress, long timestamp) {
+ this.macAddress = macAddress;
+ this.timestamp = timestamp;
+ }
+
+ @Override
+ public String toString() {
+ return "MacLearnerValue{" +
+ "macAddress=" + macAddress +
+ ", timestamp=" + timestamp +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ MacLearnerValue that = (MacLearnerValue) o;
+ return timestamp == that.timestamp &&
+ Objects.equals(macAddress, that.macAddress);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(macAddress, timestamp);
+ }
+
+}
diff --git a/api/src/main/java/org/opencord/maclearner/api/package-info.java b/api/src/main/java/org/opencord/maclearner/api/package-info.java
new file mode 100755
index 0000000..11e53de
--- /dev/null
+++ b/api/src/main/java/org/opencord/maclearner/api/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Mac Learner API.
+ */
+package org.opencord.maclearner.api;
diff --git a/app/app.xml b/app/app.xml
new file mode 100644
index 0000000..1cb39b2
--- /dev/null
+++ b/app/app.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2015-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.
+ -->
+<app name="org.opencord.maclearner" origin="${onos.app.origin}"
+ version="${project.version}"
+ title="Mac Learner" category="Utility" url="${onos.app.url}"
+ featuresRepo="mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features"
+ features="${project.artifactId}">
+ <description>Mapping clients' MAC Address and Port Number information temporarily.</description>
+ <artifact>mvn:${project.groupId}/${project.artifactId}/${project.version}</artifact>
+ <artifact>mvn:${project.groupId}/maclearner-api/${project.version}</artifact>
+</app>
diff --git a/app/features.xml b/app/features.xml
new file mode 100644
index 0000000..ca3587a
--- /dev/null
+++ b/app/features.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--
+ ~ 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.
+ -->
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${project.version}">
+ <feature name="${project.artifactId}" version="${project.version}"
+ description="${project.description}">
+ <bundle>mvn:${project.groupId}/maclearner-api/${project.version}</bundle>
+ <bundle>mvn:${project.groupId}/${project.artifactId}/${project.version}</bundle>
+ </feature>
+</features>
\ No newline at end of file
diff --git a/app/pom.xml b/app/pom.xml
new file mode 100644
index 0000000..fff910f
--- /dev/null
+++ b/app/pom.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>maclearner</artifactId>
+ <groupId>org.opencord</groupId>
+ <version>1.0.0-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>maclearner-app</artifactId>
+ <packaging>bundle</packaging>
+
+ <properties>
+ <!--For REST-->
+ <web.context>/onos/v2</web.context>
+ <api.version>2.0.0</api.version>
+ <api.title>MAC Learner REST API</api.title>
+ <api.description>MAC Learner REST API</api.description>
+ <api.package>org.opencord.maclearner.app.rest</api.package>
+ <!--For REST-->
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.opencord</groupId>
+ <artifactId>maclearner-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-cli</artifactId>
+ <version>${onos.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-core-serializers</artifactId>
+ <version>${onos.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.karaf.shell</groupId>
+ <artifactId>org.apache.karaf.shell.console</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-api</artifactId>
+ <version>${onos.version}</version>
+ <scope>test</scope>
+ <classifier>tests</classifier>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onlab-junit</artifactId>
+ <version>2.2.2</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-maven-plugin</artifactId>
+ </plugin>
+ <!--For REST-->
+ <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>
+ <!--For Command-->
+ <Karaf-Commands>org.opencord.maclearner.app.cli</Karaf-Commands>
+ <!--For Command-->
+ </instructions>
+ </configuration>
+ </plugin>
+ <!--For REST-->
+ </plugins>
+ </build>
+
+</project>
\ No newline at end of file
diff --git a/app/src/main/java/org/opencord/maclearner/app/cli/MacLearnerAddIgnorePort.java b/app/src/main/java/org/opencord/maclearner/app/cli/MacLearnerAddIgnorePort.java
new file mode 100644
index 0000000..614bcbe
--- /dev/null
+++ b/app/src/main/java/org/opencord/maclearner/app/cli/MacLearnerAddIgnorePort.java
@@ -0,0 +1,59 @@
+/*
+ * 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.opencord.maclearner.app.cli;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.opencord.maclearner.api.MacLearnerService;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+
+/**
+ * Adds a port of device will not be MAC mapping.
+ */
+@Service
+@Command(scope = "onos", name = "mac-learner-add-ignore-port",
+ description = "Adds a port of device will not be MAC mapping")
+public class MacLearnerAddIgnorePort extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "deviceId",
+ description = "OpenFlow Device Id",
+ required = true)
+ String devId = null;
+
+ @Argument(index = 1, name = "portNo",
+ description = "Integer value of Port Number",
+ required = true)
+ Integer portNo;
+
+ @Override
+ protected void doExecute() {
+ try {
+ MacLearnerService macLearnerService = AbstractShellCommand.get(MacLearnerService.class);
+ macLearnerService.addPortToIgnore(DeviceId.deviceId(devId),
+ PortNumber.portNumber(portNo));
+
+ } catch (IllegalArgumentException e) {
+ String msg = String.format("Exception occurred while executing %s command",
+ this.getClass().getSimpleName());
+ print(msg);
+ log.error(msg, e);
+ }
+ }
+
+}
diff --git a/app/src/main/java/org/opencord/maclearner/app/cli/MacLearnerDeleteMapping.java b/app/src/main/java/org/opencord/maclearner/app/cli/MacLearnerDeleteMapping.java
new file mode 100644
index 0000000..6e8149a
--- /dev/null
+++ b/app/src/main/java/org/opencord/maclearner/app/cli/MacLearnerDeleteMapping.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.opencord.maclearner.app.cli;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onlab.packet.VlanId;
+import org.opencord.maclearner.api.MacDeleteResult;
+import org.opencord.maclearner.api.MacLearnerService;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+
+/**
+ * Deletes MAC Address information of client connected to requested device and port.
+ */
+@Service
+@Command(scope = "onos", name = "mac-learner-delete-mapping",
+ description = "Deletes MAC Address information of client connected to requested device and port")
+public class MacLearnerDeleteMapping extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "deviceId",
+ description = "OpenFlow Device Id",
+ required = true)
+ @Completion(MappedDeviceIdCompleter.class)
+ String devId = null;
+
+ @Argument(index = 1, name = "portNo",
+ description = "Integer value of Port Number",
+ required = true)
+ @Completion(MappedPortNumberCompleter.class)
+ Integer portNo;
+
+ @Argument(index = 2, name = "vlanId",
+ description = "Short value of Vlan Id",
+ required = true)
+ Short vlanId;
+
+ private static final String DELETE_MAPPING_SUCCESS = "Mac Mapping Successfully Deleted.";
+ private static final String DELETE_MAPPING_FAILURE = "Mac Mapping Deletion Failed.";
+ private static final String MAPPING_NOT_FOUND = "Mac Mapping requested to delete is not found.";
+
+ @Override
+ protected void doExecute() {
+ MacLearnerService macLearnerService = AbstractShellCommand.get(MacLearnerService.class);
+ try {
+ if (portNo == null || devId == null || vlanId == null) {
+ throw new IllegalArgumentException();
+ }
+
+ MacDeleteResult result = macLearnerService.deleteMacMapping(DeviceId.deviceId(devId),
+ PortNumber.portNumber(portNo),
+ VlanId.vlanId(vlanId));
+ switch (result) {
+ case SUCCESSFUL:
+ print(DELETE_MAPPING_SUCCESS);
+ break;
+ case NOT_EXIST:
+ print(MAPPING_NOT_FOUND);
+ break;
+ case UNSUCCESSFUL:
+ print(DELETE_MAPPING_FAILURE);
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+
+ } catch (IllegalArgumentException e) {
+ String msg = String.format("Exception occurred while executing %s command",
+ this.getClass().getSimpleName());
+ print(msg);
+ log.error(msg, e);
+ }
+ }
+
+}
diff --git a/app/src/main/java/org/opencord/maclearner/app/cli/MacLearnerGetMapping.java b/app/src/main/java/org/opencord/maclearner/app/cli/MacLearnerGetMapping.java
new file mode 100644
index 0000000..8de3cbd
--- /dev/null
+++ b/app/src/main/java/org/opencord/maclearner/app/cli/MacLearnerGetMapping.java
@@ -0,0 +1,89 @@
+/*
+ * 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.opencord.maclearner.app.cli;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onlab.packet.VlanId;
+import org.opencord.maclearner.api.MacLearnerKey;
+import org.opencord.maclearner.api.MacLearnerService;
+import org.onlab.packet.MacAddress;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * Gets MAC Address information of client connected to requested device and port.
+ */
+@Service
+@Command(scope = "onos", name = "mac-learner-get-mapping",
+ description = "Gets MAC Address information of client connected to requested device and port")
+public class MacLearnerGetMapping extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "deviceId",
+ description = "OpenFlow Device Id")
+ @Completion(MappedDeviceIdCompleter.class)
+ String devId = null;
+
+ @Argument(index = 1, name = "portNo",
+ description = "Integer value of Port Number")
+ @Completion(MappedPortNumberCompleter.class)
+ Integer portNo;
+
+ @Argument(index = 2, name = "vlanId",
+ description = "Short value of Vlan Id")
+ Short vlanId;
+
+ @Override
+ protected void doExecute() {
+ try {
+ MacLearnerService macLearnerService = AbstractShellCommand.get(MacLearnerService.class);
+ if (portNo == null && devId == null && vlanId == null) {
+ Map<MacLearnerKey, MacAddress> mapMacAddressMap = macLearnerService.getAllMappings();
+ for (Map.Entry<MacLearnerKey, MacAddress> entry : mapMacAddressMap.entrySet()) {
+ print("Client with MAC: %s and VlanID: %s, uses port number: %s of device with id: %s",
+ entry.getValue(),
+ entry.getKey().getVlanId(),
+ entry.getKey().getPortNumber(),
+ entry.getKey().getDeviceId());
+ }
+ } else if (portNo != null && devId != null && vlanId != null) {
+ Optional<MacAddress> macAddress = macLearnerService.getMacMapping(DeviceId.deviceId(devId),
+ PortNumber.portNumber(portNo),
+ VlanId.vlanId(vlanId));
+ if (!macAddress.isPresent()) {
+ print("MAC Address not found with given parameters.\nUse -1 for VlanId=None");
+ } else {
+ print(String.format("MAC: %s", macAddress.get()));
+ }
+ } else {
+ print("Either device id, port number and vlan id must be entered or not at all!");
+ }
+
+ } catch (IllegalArgumentException e) {
+ String msg = String.format("Exception occurred while executing %s command",
+ this.getClass().getSimpleName());
+ print(msg);
+ log.error(msg, e);
+ }
+ }
+
+}
diff --git a/app/src/main/java/org/opencord/maclearner/app/cli/MacLearnerListIgnoredPorts.java b/app/src/main/java/org/opencord/maclearner/app/cli/MacLearnerListIgnoredPorts.java
new file mode 100644
index 0000000..7f66dc2
--- /dev/null
+++ b/app/src/main/java/org/opencord/maclearner/app/cli/MacLearnerListIgnoredPorts.java
@@ -0,0 +1,73 @@
+/*
+ * 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.opencord.maclearner.app.cli;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.opencord.maclearner.api.MacLearnerService;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Gets ignored port information for MAC Mapping.
+ */
+@Service
+@Command(scope = "onos", name = "mac-learner-list-ignored-ports",
+ description = "Gets ignored port information for MAC Mapping")
+public class MacLearnerListIgnoredPorts extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "deviceId",
+ description = "Device Id")
+ @Completion(MappedDeviceIdCompleter.class)
+ String devId = null;
+
+ @Override
+ protected void doExecute() {
+ try {
+ MacLearnerService macLearnerService = AbstractShellCommand.get(MacLearnerService.class);
+ Map<DeviceId, Set<PortNumber>> ignoredPorts = macLearnerService.getIgnoredPorts();
+ if (devId == null) {
+ if (ignoredPorts != null && !ignoredPorts.isEmpty()) {
+ for (Map.Entry<DeviceId, Set<PortNumber>> entry : ignoredPorts.entrySet()) {
+ print("Port(s): %s ignored of device with ID: %s", entry.getValue(), entry.getKey());
+ }
+ } else {
+ print("There is no ignored port.");
+ }
+ } else {
+ Set<PortNumber> portNumbers = ignoredPorts.get(DeviceId.deviceId(devId));
+ if (!ignoredPorts.isEmpty()) {
+ print("Port(s): %s ignored of device with ID: %s", portNumbers, devId);
+ } else {
+ print("There is no ignored port of device with ID: %s", devId);
+ }
+ }
+
+ } catch (IllegalArgumentException e) {
+ String msg = String.format("Exception occurred while executing %s command",
+ this.getClass().getSimpleName());
+ print(msg);
+ log.error(msg, e);
+ }
+ }
+
+}
diff --git a/app/src/main/java/org/opencord/maclearner/app/cli/MacLearnerRemoveIgnorePort.java b/app/src/main/java/org/opencord/maclearner/app/cli/MacLearnerRemoveIgnorePort.java
new file mode 100644
index 0000000..62aed8d
--- /dev/null
+++ b/app/src/main/java/org/opencord/maclearner/app/cli/MacLearnerRemoveIgnorePort.java
@@ -0,0 +1,59 @@
+/*
+ * 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.opencord.maclearner.app.cli;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.opencord.maclearner.api.MacLearnerService;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+
+/**
+ * Removes a port of device from Ignore Map.
+ */
+@Service
+@Command(scope = "onos", name = "mac-learner-remove-ignore-port",
+ description = "Removes a port of device from Ignore Map")
+public class MacLearnerRemoveIgnorePort extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "deviceId",
+ description = "OpenFlow Device Id",
+ required = true)
+ String devId = null;
+
+ @Argument(index = 1, name = "portNo",
+ description = "Integer value of Port Number",
+ required = true)
+ Integer portNo;
+
+ @Override
+ protected void doExecute() {
+ try {
+ MacLearnerService macLearnerService = AbstractShellCommand.get(MacLearnerService.class);
+ macLearnerService.removeFromIgnoredPorts(DeviceId.deviceId(devId),
+ PortNumber.portNumber(portNo));
+
+ } catch (IllegalArgumentException e) {
+ String msg = String.format("Exception occurred while executing %s command",
+ this.getClass().getSimpleName());
+ print(msg);
+ log.error(msg, e);
+ }
+ }
+
+}
diff --git a/app/src/main/java/org/opencord/maclearner/app/cli/MappedDeviceIdCompleter.java b/app/src/main/java/org/opencord/maclearner/app/cli/MappedDeviceIdCompleter.java
new file mode 100644
index 0000000..8f64bff
--- /dev/null
+++ b/app/src/main/java/org/opencord/maclearner/app/cli/MappedDeviceIdCompleter.java
@@ -0,0 +1,53 @@
+/*
+ * 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.opencord.maclearner.app.cli;
+
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
+import org.onosproject.cli.AbstractShellCommand;
+import org.opencord.maclearner.api.MacLearnerService;
+import org.onosproject.net.DeviceId;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.SortedSet;
+
+/**
+ * CLI completer for mapped device ids.
+ */
+@Service
+public class MappedDeviceIdCompleter implements Completer {
+
+ @Override
+ public int complete(Session session, CommandLine commandLine, List<String> candidates) {
+ // Delegate string completer
+ StringsCompleter delegate = new StringsCompleter();
+ MacLearnerService macLearnerService = AbstractShellCommand.get(MacLearnerService.class);
+ Iterator<DeviceId> it = macLearnerService.getMappedDevices().iterator();
+ SortedSet<String> strings = delegate.getStrings();
+
+ while (it.hasNext()) {
+ strings.add(it.next().toString());
+ }
+
+ // Now let the completer do the work for figuring out what to offer.
+ return delegate.complete(session, commandLine, candidates);
+ }
+
+}
diff --git a/app/src/main/java/org/opencord/maclearner/app/cli/MappedPortNumberCompleter.java b/app/src/main/java/org/opencord/maclearner/app/cli/MappedPortNumberCompleter.java
new file mode 100644
index 0000000..829f1e7
--- /dev/null
+++ b/app/src/main/java/org/opencord/maclearner/app/cli/MappedPortNumberCompleter.java
@@ -0,0 +1,53 @@
+/*
+ * 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.opencord.maclearner.app.cli;
+
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
+import org.opencord.maclearner.api.MacLearnerService;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.PortNumber;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.SortedSet;
+
+/**
+ * CLI completer for mapped port numbers.
+ */
+@Service
+public class MappedPortNumberCompleter implements Completer {
+
+ @Override
+ public int complete(Session session, CommandLine commandLine, List<String> candidates) {
+ // Delegate string completer
+ StringsCompleter delegate = new StringsCompleter();
+ MacLearnerService macLearnerService = AbstractShellCommand.get(MacLearnerService.class);
+ Iterator<PortNumber> it = macLearnerService.getMappedPorts().iterator();
+ SortedSet<String> strings = delegate.getStrings();
+
+ while (it.hasNext()) {
+ strings.add(it.next().toString());
+ }
+
+ // Now let the completer do the work for figuring out what to offer.
+ return delegate.complete(session, commandLine, candidates);
+ }
+
+}
diff --git a/app/src/main/java/org/opencord/maclearner/app/cli/package-info.java b/app/src/main/java/org/opencord/maclearner/app/cli/package-info.java
new file mode 100755
index 0000000..5270265
--- /dev/null
+++ b/app/src/main/java/org/opencord/maclearner/app/cli/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Mac Learner CLI.
+ */
+package org.opencord.maclearner.app.cli;
diff --git a/app/src/main/java/org/opencord/maclearner/app/impl/MacLearnerManager.java b/app/src/main/java/org/opencord/maclearner/app/impl/MacLearnerManager.java
new file mode 100644
index 0000000..0a73e3e
--- /dev/null
+++ b/app/src/main/java/org/opencord/maclearner/app/impl/MacLearnerManager.java
@@ -0,0 +1,554 @@
+/*
+ * 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.opencord.maclearner.app.impl;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.onlab.packet.VlanId;
+import org.onlab.util.Tools;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.Versioned;
+import org.opencord.maclearner.api.DefaultMacLearner;
+import org.opencord.maclearner.api.MacDeleteResult;
+import org.opencord.maclearner.api.MacLearnerEvent;
+import org.opencord.maclearner.api.MacLearnerKey;
+import org.opencord.maclearner.api.MacLearnerListener;
+import org.opencord.maclearner.api.MacLearnerProvider;
+import org.opencord.maclearner.api.MacLearnerProviderService;
+import org.opencord.maclearner.api.MacLearnerService;
+import org.opencord.maclearner.api.MacLearnerValue;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Modified;
+import org.osgi.service.component.annotations.Reference;
+import org.onlab.packet.DHCP;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.UDP;
+import org.onlab.packet.dhcp.DhcpOption;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.net.provider.AbstractListenerProviderRegistry;
+import org.onosproject.net.provider.AbstractProviderService;
+import org.onosproject.store.LogicalTimestamp;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.WallClockTimestamp;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.URI;
+import java.util.Date;
+import java.util.Dictionary;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.opencord.maclearner.app.impl.OsgiPropertyConstants.CACHE_DURATION_DEFAULT;
+import static org.opencord.maclearner.app.impl.OsgiPropertyConstants.CACHE_DURATION;
+import static org.opencord.maclearner.app.impl.OsgiPropertyConstants.ENABLE_DEVICE_LISTENER;
+import static org.opencord.maclearner.app.impl.OsgiPropertyConstants.ENABLE_DEVICE_LISTENER_DEFAULT;
+import static org.osgi.service.component.annotations.ReferenceCardinality.MANDATORY;
+
+/**
+ * Mac Learner Service implementation.
+ */
+@Component(immediate = true,
+ property = {
+ CACHE_DURATION + ":Integer=" + CACHE_DURATION_DEFAULT,
+ ENABLE_DEVICE_LISTENER + ":Boolean=" + ENABLE_DEVICE_LISTENER_DEFAULT
+ },
+ service = MacLearnerService.class
+)
+public class MacLearnerManager
+ extends AbstractListenerProviderRegistry<MacLearnerEvent, MacLearnerListener,
+ MacLearnerProvider, MacLearnerProviderService>
+ implements MacLearnerService {
+
+ private static final String MAC_LEARNER_APP = "org.opencord.maclearner";
+ private static final String MAC_LEARNER = "maclearner";
+ private ApplicationId appId;
+
+ private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
+ private ScheduledFuture scheduledFuture;
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ @Reference(cardinality = MANDATORY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = MANDATORY)
+ protected MastershipService mastershipService;
+
+ @Reference(cardinality = MANDATORY)
+ protected DeviceService deviceService;
+
+ @Reference(cardinality = MANDATORY)
+ protected PacketService packetService;
+
+ @Reference(cardinality = MANDATORY)
+ protected StorageService storageService;
+
+
+ @Reference(cardinality = MANDATORY)
+ protected ComponentConfigService componentConfigService;
+
+ private MacLearnerPacketProcessor macLearnerPacketProcessor =
+ new MacLearnerPacketProcessor();
+
+ private DeviceListener deviceListener = new InternalDeviceListener();
+
+ /**
+ * Minimum duration of mapping, mapping can be exist until 2*cacheDuration because of cleanerTimer fixed rate.
+ */
+ protected int cacheDurationSec = CACHE_DURATION_DEFAULT;
+
+ /**
+ * Register a device event listener for removing mappings from MAC Address Map for removed events.
+ */
+ protected boolean enableDeviceListener = ENABLE_DEVICE_LISTENER_DEFAULT;
+
+ private ConsistentMap<DeviceId, Set<PortNumber>> ignoredPortsMap;
+ private ConsistentMap<MacLearnerKey, MacLearnerValue> macAddressMap;
+
+ protected ExecutorService eventExecutor;
+
+ @Activate
+ public void activate() {
+ eventExecutor = Executors.newFixedThreadPool(5, groupedThreads("onos/maclearner",
+ "events-%d", log));
+ appId = coreService.registerApplication(MAC_LEARNER_APP);
+ componentConfigService.registerProperties(getClass());
+ eventDispatcher.addSink(MacLearnerEvent.class, listenerRegistry);
+ macAddressMap = storageService.<MacLearnerKey, MacLearnerValue>consistentMapBuilder()
+ .withName(MAC_LEARNER)
+ .withSerializer(createSerializer())
+ .withApplicationId(appId)
+ .build();
+ ignoredPortsMap = storageService
+ .<DeviceId, Set<PortNumber>>consistentMapBuilder()
+ .withName("maclearner-ignored")
+ .withSerializer(createSerializer())
+ .withApplicationId(appId)
+ .build();
+ //mac learner must process the packet before director processors
+ packetService.addProcessor(macLearnerPacketProcessor,
+ PacketProcessor.advisor(2));
+ if (enableDeviceListener) {
+ deviceService.addListener(deviceListener);
+ }
+ createSchedulerForClearMacMappings();
+ log.info("{} is started.", getClass().getSimpleName());
+ }
+
+ private Serializer createSerializer() {
+ return Serializer.using(KryoNamespace.newBuilder()
+ .register(KryoNamespace.newBuilder().build(MAC_LEARNER))
+ // not so robust way to avoid collision with other
+ // user supplied registrations
+ .nextId(KryoNamespaces.BEGIN_USER_CUSTOM_ID + 100)
+ .register(KryoNamespaces.BASIC)
+ .register(LogicalTimestamp.class)
+ .register(WallClockTimestamp.class)
+ .register(MacLearnerKey.class)
+ .register(MacLearnerValue.class)
+ .register(DeviceId.class)
+ .register(URI.class)
+ .register(PortNumber.class)
+ .register(VlanId.class)
+ .register(MacAddress.class)
+ .build(MAC_LEARNER + "-ecmap"));
+ }
+
+ private void createSchedulerForClearMacMappings() {
+ scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(this::clearExpiredMacMappings,
+ 0,
+ cacheDurationSec,
+ TimeUnit.SECONDS);
+ }
+
+ private void clearExpiredMacMappings() {
+ Date curDate = new Date();
+ for (Map.Entry<MacLearnerKey, Versioned<MacLearnerValue>> entry : macAddressMap.entrySet()) {
+ if (!mastershipService.isLocalMaster(entry.getKey().getDeviceId())) {
+ return;
+ }
+ if (curDate.getTime() - entry.getValue().value().getTimestamp() > cacheDurationSec * 1000) {
+ removeFromMacAddressMap(entry.getKey());
+ }
+ }
+ }
+
+ @Deactivate
+ public void deactivate() {
+ if (scheduledFuture != null) {
+ scheduledFuture.cancel(true);
+ }
+ packetService.removeProcessor(macLearnerPacketProcessor);
+ if (enableDeviceListener) {
+ deviceService.removeListener(deviceListener);
+ }
+ eventDispatcher.removeSink(MacLearnerEvent.class);
+ componentConfigService.unregisterProperties(getClass(), false);
+ log.info("{} is stopped.", getClass().getSimpleName());
+ }
+
+ @Modified
+ public void modified(ComponentContext context) {
+ Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
+
+ String cacheDuration = Tools.get(properties, CACHE_DURATION);
+ if (!isNullOrEmpty(cacheDuration)) {
+ int cacheDur = Integer.parseInt(cacheDuration.trim());
+ if (cacheDurationSec != cacheDur) {
+ setMacMappingCacheDuration(cacheDur);
+ }
+ }
+
+ Boolean enableDevListener = Tools.isPropertyEnabled(properties, ENABLE_DEVICE_LISTENER);
+ if (enableDevListener != null && enableDeviceListener != enableDevListener) {
+ enableDeviceListener = enableDevListener;
+ log.info("enableDeviceListener parameter changed to: {}", enableDeviceListener);
+ if (this.enableDeviceListener) {
+ deviceService.addListener(deviceListener);
+ } else {
+ deviceService.removeListener(deviceListener);
+ }
+ }
+ }
+
+ private Integer setMacMappingCacheDuration(Integer second) {
+ if (cacheDurationSec == second) {
+ log.info("Cache duration already: {}", second);
+ return second;
+ }
+ log.info("Changing cache duration to: {} second from {} second...", second, cacheDurationSec);
+ this.cacheDurationSec = second;
+ if (scheduledFuture != null) {
+ scheduledFuture.cancel(false);
+ }
+ createSchedulerForClearMacMappings();
+ return cacheDurationSec;
+ }
+
+ @Override
+ public void addPortToIgnore(DeviceId deviceId, PortNumber portNumber) {
+ log.info("Adding ignore port: {} {}", deviceId, portNumber);
+ Set<PortNumber> updatedPorts = Sets.newHashSet();
+ Versioned<Set<PortNumber>> storedPorts = ignoredPortsMap.get(deviceId);
+ if (storedPorts == null || !storedPorts.value().contains(portNumber)) {
+ if (storedPorts != null) {
+ updatedPorts.addAll(storedPorts.value());
+ }
+ updatedPorts.add(portNumber);
+ ignoredPortsMap.put(deviceId, updatedPorts);
+ log.info("Port:{} of device: {} is added to ignoredPortsMap.", portNumber, deviceId);
+ deleteMacMappings(deviceId, portNumber);
+ } else {
+ log.warn("Port:{} of device: {} is already ignored.", portNumber, deviceId);
+ }
+ }
+
+ @Override
+ public void removeFromIgnoredPorts(DeviceId deviceId, PortNumber portNumber) {
+ log.info("Removing ignore port: {} {}", deviceId, portNumber);
+ Versioned<Set<PortNumber>> storedPorts = ignoredPortsMap.get(deviceId);
+ if (storedPorts != null && storedPorts.value().contains(portNumber)) {
+ if (storedPorts.value().size() == 1) {
+ ignoredPortsMap.remove(deviceId);
+ } else {
+ Set<PortNumber> updatedPorts = Sets.newHashSet();
+ updatedPorts.addAll(storedPorts.value());
+ updatedPorts.remove(portNumber);
+ ignoredPortsMap.put(deviceId, updatedPorts);
+ }
+ log.info("Port:{} of device: {} is removed ignoredPortsMap.", portNumber, deviceId);
+ } else {
+ log.warn("Port:{} of device: {} is not found in ignoredPortsMap.", portNumber, deviceId);
+ }
+ }
+
+ @Override
+ public ImmutableMap<MacLearnerKey, MacAddress> getAllMappings() {
+ log.info("Getting all MAC Mappings");
+ Map<MacLearnerKey, MacAddress> immutableMap = Maps.newHashMap();
+ macAddressMap.entrySet().forEach(entry ->
+ immutableMap.put(entry.getKey(),
+ entry.getValue() != null ? entry.getValue().value().getMacAddress() : null));
+ return ImmutableMap.copyOf(immutableMap);
+ }
+
+ @Override
+ public Optional<MacAddress> getMacMapping(DeviceId deviceId, PortNumber portNumber, VlanId vlanId) {
+ log.info("Getting MAC mapping for: {} {} {}", deviceId, portNumber, vlanId);
+ Versioned<MacLearnerValue> value = macAddressMap.get(new MacLearnerKey(deviceId, portNumber, vlanId));
+ return value != null ? Optional.ofNullable(value.value().getMacAddress()) : Optional.empty();
+ }
+
+ @Override
+ public MacDeleteResult deleteMacMapping(DeviceId deviceId, PortNumber portNumber, VlanId vlanId) {
+ log.info("Deleting MAC mapping for: {} {} {}", deviceId, portNumber, vlanId);
+ MacLearnerKey key = new MacLearnerKey(deviceId, portNumber, vlanId);
+ return removeFromMacAddressMap(key);
+ }
+
+ @Override
+ public boolean deleteMacMappings(DeviceId deviceId, PortNumber portNumber) {
+ log.info("Deleting MAC mappings for: {} {}", deviceId, portNumber);
+ Set<Map.Entry<MacLearnerKey, Versioned<MacLearnerValue>>> entriesToDelete = macAddressMap.entrySet().stream()
+ .filter(entry -> entry.getKey().getDeviceId().equals(deviceId) &&
+ entry.getKey().getPortNumber().equals(portNumber))
+ .collect(Collectors.toSet());
+ if (entriesToDelete.isEmpty()) {
+ log.warn("MAC mapping not found for deviceId: {} and portNumber: {}", deviceId, portNumber);
+ return false;
+ }
+ entriesToDelete.forEach(e -> removeFromMacAddressMap(e.getKey()));
+ return true;
+ }
+
+ @Override
+ public boolean deleteMacMappings(DeviceId deviceId) {
+ log.info("Deleting MAC mappings for: {}", deviceId);
+ Set<Map.Entry<MacLearnerKey, Versioned<MacLearnerValue>>> entriesToDelete = macAddressMap.entrySet().stream()
+ .filter(entry -> entry.getKey().getDeviceId().equals(deviceId))
+ .collect(Collectors.toSet());
+ if (entriesToDelete.isEmpty()) {
+ log.warn("MAC mapping not found for deviceId: {}", deviceId);
+ return false;
+ }
+ entriesToDelete.forEach(e -> removeFromMacAddressMap(e.getKey()));
+ return true;
+ }
+
+ @Override
+ public ImmutableSet<DeviceId> getMappedDevices() {
+ Set<DeviceId> deviceIds = Sets.newHashSet();
+ for (Map.Entry<MacLearnerKey, MacAddress> entry : getAllMappings().entrySet()) {
+ deviceIds.add(entry.getKey().getDeviceId());
+ }
+ return ImmutableSet.copyOf(deviceIds);
+ }
+
+ @Override
+ public ImmutableSet<PortNumber> getMappedPorts() {
+ Set<PortNumber> portNumbers = Sets.newHashSet();
+ for (Map.Entry<MacLearnerKey, MacAddress> entry : getAllMappings().entrySet()) {
+ portNumbers.add(entry.getKey().getPortNumber());
+ }
+ return ImmutableSet.copyOf(portNumbers);
+ }
+
+ @Override
+ public ImmutableMap<DeviceId, Set<PortNumber>> getIgnoredPorts() {
+ log.info("Getting ignored ports");
+ Map<DeviceId, Set<PortNumber>> immutableMap = Maps.newHashMap();
+ ignoredPortsMap.forEach(entry -> immutableMap.put(entry.getKey(),
+ entry.getValue() != null ? entry.getValue().value() : Sets.newHashSet()));
+ return ImmutableMap.copyOf(immutableMap);
+ }
+
+ @Override
+ protected MacLearnerProviderService createProviderService(MacLearnerProvider provider) {
+ return new InternalMacLearnerProviderService(provider);
+ }
+
+ private static class InternalMacLearnerProviderService extends AbstractProviderService<MacLearnerProvider>
+ implements MacLearnerProviderService {
+
+ InternalMacLearnerProviderService(MacLearnerProvider provider) {
+ super(provider);
+ }
+ }
+
+ private void sendMacLearnerEvent(MacLearnerEvent.Type type, DeviceId deviceId,
+ PortNumber portNumber, VlanId vlanId, MacAddress macAddress) {
+ log.info("Sending MAC Learner Event: type: {} deviceId: {} portNumber: {} vlanId: {} macAddress: {}",
+ type, deviceId, portNumber, vlanId.toShort(), macAddress);
+ DefaultMacLearner macLearner = new DefaultMacLearner(deviceId, portNumber, vlanId, macAddress);
+ MacLearnerEvent macLearnerEvent = new MacLearnerEvent(type, macLearner);
+ post(macLearnerEvent);
+ }
+
+ private class MacLearnerPacketProcessor implements PacketProcessor {
+
+ @Override
+ public void process(PacketContext context) {
+ // process the packet and get the payload
+ Ethernet packet = context.inPacket().parsed();
+
+ if (packet == null) {
+ log.warn("Packet is null");
+ return;
+ }
+
+ PortNumber sourcePort = context.inPacket().receivedFrom().port();
+ DeviceId deviceId = context.inPacket().receivedFrom().deviceId();
+
+ Versioned<Set<PortNumber>> ignoredPortsOfDevice = ignoredPortsMap.get(deviceId);
+ if (ignoredPortsOfDevice != null && ignoredPortsOfDevice.value().contains(sourcePort)) {
+ log.warn("Port Number: {} is in ignoredPortsMap. Returning", sourcePort);
+ return;
+ }
+
+ if (packet.getEtherType() == Ethernet.TYPE_IPV4) {
+ IPv4 ipv4Packet = (IPv4) packet.getPayload();
+
+ if (ipv4Packet.getProtocol() == IPv4.PROTOCOL_UDP) {
+ UDP udpPacket = (UDP) ipv4Packet.getPayload();
+ int udpSourcePort = udpPacket.getSourcePort();
+ if ((udpSourcePort == UDP.DHCP_CLIENT_PORT) || (udpSourcePort == UDP.DHCP_SERVER_PORT)) {
+ DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
+ //This packet is dhcp.
+ VlanId vlanId = packet.getQinQVID() != -1 ?
+ VlanId.vlanId(packet.getQinQVID()) : VlanId.vlanId(packet.getVlanID());
+ processDhcpPacket(context, packet, dhcpPayload, sourcePort, deviceId, vlanId);
+ }
+ }
+ }
+ }
+
+ //process the dhcp packet before forwarding
+ private void processDhcpPacket(PacketContext context, Ethernet packet,
+ DHCP dhcpPayload, PortNumber sourcePort, DeviceId deviceId, VlanId vlanId) {
+ if (dhcpPayload == null) {
+ log.warn("DHCP payload is null");
+ return;
+ }
+
+ DHCP.MsgType incomingPacketType = getDhcpPacketType(dhcpPayload);
+
+ if (incomingPacketType == null) {
+ log.warn("Incoming packet type is null!");
+ return;
+ }
+
+ log.info("Received DHCP Packet of type {} from {}",
+ incomingPacketType, context.inPacket().receivedFrom());
+
+ if (incomingPacketType.equals(DHCP.MsgType.DHCPDISCOVER) ||
+ incomingPacketType.equals(DHCP.MsgType.DHCPREQUEST)) {
+ addToMacAddressMap(deviceId, sourcePort, vlanId, packet.getSourceMAC());
+ }
+ }
+
+ // get type of the DHCP packet
+ private DHCP.MsgType getDhcpPacketType(DHCP dhcpPayload) {
+
+ for (DhcpOption option : dhcpPayload.getOptions()) {
+ if (option.getCode() == OptionCode_MessageType.getValue()) {
+ byte[] data = option.getData();
+ return DHCP.MsgType.getType(data[0]);
+ }
+ }
+ return null;
+ }
+
+ private void addToMacAddressMap(DeviceId deviceId, PortNumber portNumber,
+ VlanId vlanId, MacAddress macAddress) {
+ Versioned<MacLearnerValue> prevMacAddress =
+ macAddressMap.put(new MacLearnerKey(deviceId, portNumber, vlanId),
+ new MacLearnerValue(macAddress, new Date().getTime()));
+ if (prevMacAddress != null && !prevMacAddress.value().getMacAddress().equals(macAddress)) {
+ sendMacLearnerEvent(MacLearnerEvent.Type.REMOVED,
+ deviceId,
+ portNumber,
+ vlanId,
+ prevMacAddress.value().getMacAddress());
+ } else if (prevMacAddress == null || !prevMacAddress.value().getMacAddress().equals(macAddress)) {
+ // Not sending event for already mapped
+ log.info("Mapped MAC: {} for port: {} of deviceId: {} and vlanId: {}",
+ macAddress, portNumber, deviceId, vlanId);
+ sendMacLearnerEvent(MacLearnerEvent.Type.ADDED, deviceId, portNumber, vlanId, macAddress);
+ }
+ }
+
+ }
+
+ private MacDeleteResult removeFromMacAddressMap(MacLearnerKey macLearnerKey) {
+ Versioned<MacLearnerValue> verMacAddress = macAddressMap.remove(macLearnerKey);
+ if (verMacAddress != null) {
+ log.info("Mapping removed. deviceId: {} portNumber: {} vlanId: {} macAddress: {}",
+ macLearnerKey.getDeviceId(), macLearnerKey.getPortNumber(),
+ verMacAddress.value(), verMacAddress.value().getMacAddress());
+ sendMacLearnerEvent(MacLearnerEvent.Type.REMOVED,
+ macLearnerKey.getDeviceId(),
+ macLearnerKey.getPortNumber(),
+ macLearnerKey.getVlanId(),
+ verMacAddress.value().getMacAddress());
+ return MacDeleteResult.SUCCESSFUL;
+ } else {
+ log.warn("MAC not removed, because mapping not found for deviceId: {} and portNumber: {} and vlanId: {}",
+ macLearnerKey.getDeviceId(),
+ macLearnerKey.getPortNumber(),
+ macLearnerKey.getVlanId());
+ return MacDeleteResult.NOT_EXIST;
+ }
+ }
+
+ private class InternalDeviceListener implements DeviceListener {
+
+ @Override
+ public void event(DeviceEvent event) {
+ eventExecutor.execute(() -> {
+ switch (event.type()) {
+ case DEVICE_REMOVED:
+ deleteMacMappings(event.subject().id());
+ break;
+ case PORT_REMOVED:
+ deleteMacMappings(event.subject().id(), event.port().number());
+ break;
+ default:
+ log.debug("Unhandled device event for Mac Learner: {}", event.type());
+ }
+ });
+ }
+
+ @Override
+ public boolean isRelevant(DeviceEvent event) {
+ return mastershipService.isLocalMaster(event.subject().id());
+ }
+
+ }
+
+}
diff --git a/app/src/main/java/org/opencord/maclearner/app/impl/OsgiPropertyConstants.java b/app/src/main/java/org/opencord/maclearner/app/impl/OsgiPropertyConstants.java
new file mode 100644
index 0000000..d57d322
--- /dev/null
+++ b/app/src/main/java/org/opencord/maclearner/app/impl/OsgiPropertyConstants.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2019-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.opencord.maclearner.app.impl;
+
+/**
+ * Constants for default values of configurable properties.
+ */
+public final class OsgiPropertyConstants {
+
+ private OsgiPropertyConstants() {
+ }
+
+ public static final String CACHE_DURATION = "cacheDurationSec";
+ public static final int CACHE_DURATION_DEFAULT = 86400; // 1 day
+
+ public static final String ENABLE_DEVICE_LISTENER = "enableDeviceListener";
+ public static final boolean ENABLE_DEVICE_LISTENER_DEFAULT = false;
+
+}
diff --git a/app/src/main/java/org/opencord/maclearner/app/impl/package-info.java b/app/src/main/java/org/opencord/maclearner/app/impl/package-info.java
new file mode 100755
index 0000000..df7a157
--- /dev/null
+++ b/app/src/main/java/org/opencord/maclearner/app/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Mac Learner Service Implementation.
+ */
+package org.opencord.maclearner.app.impl;
diff --git a/app/src/main/java/org/opencord/maclearner/app/rest/MacLearnerCodec.java b/app/src/main/java/org/opencord/maclearner/app/rest/MacLearnerCodec.java
new file mode 100644
index 0000000..cb7771a
--- /dev/null
+++ b/app/src/main/java/org/opencord/maclearner/app/rest/MacLearnerCodec.java
@@ -0,0 +1,94 @@
+/*
+ * 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.opencord.maclearner.app.rest;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.opencord.maclearner.api.DefaultMacLearner;
+import org.opencord.maclearner.api.MacLearner;
+import org.opencord.maclearner.api.MacLearnerKey;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * MAC Learner JSON codec.
+ */
+public final class MacLearnerCodec extends JsonCodec<MacLearner> {
+
+ private static final Logger log = LoggerFactory.getLogger(MacLearnerCodec.class);
+
+ @Override
+ public ObjectNode encode(MacLearner macLearner, CodecContext context) {
+ checkNotNull(macLearner, "macLearner cannot be null");
+
+ ObjectMapper mapper = context.mapper();
+ ObjectNode ofAgentNode = mapper.createObjectNode();
+ ofAgentNode
+ .put(DefaultMacLearner.DEVICE_ID_FN, macLearner.deviceId().toString())
+ .put(DefaultMacLearner.PORT_NUMBER_FN, macLearner.portNumber().toString())
+ .put(DefaultMacLearner.VLAN_ID_FN, macLearner.vlanId().toString())
+ .put(DefaultMacLearner.MAC_ADDRESS_FN, macLearner.macAddress().toString());
+ return ofAgentNode;
+ }
+
+ public ObjectNode encodePort(MacLearnerKey ignoredPort, CodecContext context) {
+ checkNotNull(ignoredPort, "ignoredPort cannot be null");
+
+ ObjectMapper mapper = context.mapper();
+ ObjectNode ofAgentNode = mapper.createObjectNode();
+ ofAgentNode
+ .put(DefaultMacLearner.DEVICE_ID_FN, ignoredPort.getDeviceId().toString())
+ .put(DefaultMacLearner.PORT_NUMBER_FN, ignoredPort.getPortNumber().toString());
+ return ofAgentNode;
+ }
+
+ public ObjectNode encodeMac(MacAddress macAddress, CodecContext context) {
+ checkNotNull(macAddress, "macAddress cannot be null");
+
+ ObjectMapper mapper = context.mapper();
+ ObjectNode ofAgentNode = mapper.createObjectNode();
+ ofAgentNode
+ .put(DefaultMacLearner.MAC_ADDRESS_FN, macAddress.toString());
+ return ofAgentNode;
+ }
+
+ @Override
+ public MacLearner decode(ObjectNode json, CodecContext context) {
+ JsonNode deviceId = json.get(DefaultMacLearner.DEVICE_ID_FN);
+ checkNotNull(deviceId);
+ JsonNode portNumber = json.get(DefaultMacLearner.PORT_NUMBER_FN);
+ checkNotNull(portNumber);
+ JsonNode vlanId = json.get(DefaultMacLearner.VLAN_ID_FN);
+ checkNotNull(vlanId);
+ JsonNode macAddress = json.get(DefaultMacLearner.MAC_ADDRESS_FN);
+ checkNotNull(macAddress);
+
+ return new DefaultMacLearner(DeviceId.deviceId(deviceId.asText()),
+ PortNumber.portNumber(portNumber.asLong()),
+ VlanId.vlanId(vlanId.shortValue()),
+ MacAddress.valueOf(macAddress.asText()));
+ }
+
+}
diff --git a/app/src/main/java/org/opencord/maclearner/app/rest/MacLearnerWebApplication.java b/app/src/main/java/org/opencord/maclearner/app/rest/MacLearnerWebApplication.java
new file mode 100644
index 0000000..968925b
--- /dev/null
+++ b/app/src/main/java/org/opencord/maclearner/app/rest/MacLearnerWebApplication.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.opencord.maclearner.app.rest;
+
+import org.onlab.rest.AbstractWebApplication;
+
+import java.util.Set;
+
+/**
+ * Mac Learner REST API web application.
+ */
+public class MacLearnerWebApplication extends AbstractWebApplication {
+
+ @Override
+ public Set<Class<?>> getClasses() {
+ return getClasses(MacLearnerWebResource.class);
+ }
+
+}
diff --git a/app/src/main/java/org/opencord/maclearner/app/rest/MacLearnerWebResource.java b/app/src/main/java/org/opencord/maclearner/app/rest/MacLearnerWebResource.java
new file mode 100644
index 0000000..334540e
--- /dev/null
+++ b/app/src/main/java/org/opencord/maclearner/app/rest/MacLearnerWebResource.java
@@ -0,0 +1,274 @@
+/*
+ * 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.opencord.maclearner.app.rest;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.rest.AbstractWebResource;
+import org.opencord.maclearner.api.DefaultMacLearner;
+import org.opencord.maclearner.api.MacDeleteResult;
+import org.opencord.maclearner.api.MacLearnerKey;
+import org.opencord.maclearner.api.MacLearnerService;
+import org.slf4j.Logger;
+
+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 java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static org.slf4j.LoggerFactory.getLogger;
+import static javax.ws.rs.core.Response.Status.*;
+
+/**
+ * Mac Learner web resource.
+ */
+@Path("maclearner")
+public class MacLearnerWebResource extends AbstractWebResource {
+
+ MacLearnerCodec codec = new MacLearnerCodec();
+
+ private final MacLearnerService macLearnerService = get(MacLearnerService.class);
+
+ private final Logger log = getLogger(getClass());
+
+ private static final String INVALID_PATH_PARAMETERS = "Invalid path parameters";
+ private static final String PATH_DELIMITER = "/";
+
+ /**
+ * Get all MAC Mappings.
+ *
+ * @return list of MAC Mapping json object with deviceId, portNumber, vlanId, macAddress fields
+ */
+ @GET
+ @Path("/mapping/all")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response getAllMappings() {
+ Map<MacLearnerKey, MacAddress> macMappings = macLearnerService.getAllMappings();
+ ObjectMapper mapper = new ObjectMapper();
+ ObjectNode root = mapper.createObjectNode();
+ ArrayNode macArray = mapper.createArrayNode();
+ if (macMappings == null) {
+ root.set("data", macArray);
+ return Response.ok(root, MediaType.APPLICATION_JSON_TYPE).build();
+ } else {
+ macMappings.forEach((k, v) -> macArray.add(
+ codec.encode(new DefaultMacLearner(k.getDeviceId(),
+ k.getPortNumber(),
+ k.getVlanId(),
+ v), this)
+ ));
+ }
+ root.set("data", macArray);
+ return Response.ok(root, MediaType.APPLICATION_JSON_TYPE).build();
+ }
+
+ /**
+ * Get MAC Mapping for request paramaters.
+ *
+ * @param ofDeviceId device id
+ * @param portNumber port number
+ * @param vlanId vlan id
+ * @return MAC Address json object with macAddress field
+ * 204 NO_CONTENT if it does not exist
+ */
+ @GET
+ @Path("/mapping/{ofDeviceId}/{portNumber}/{vlanId}")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response getMacMapping(@PathParam("ofDeviceId") String ofDeviceId,
+ @PathParam("portNumber") Integer portNumber,
+ @PathParam("vlanId") Short vlanId) {
+ Optional<MacAddress> mac = macLearnerService.getMacMapping(DeviceId.deviceId(ofDeviceId),
+ PortNumber.portNumber(portNumber),
+ VlanId.vlanId(vlanId));
+ if (mac.isEmpty()) {
+ log.warn("MAC Address not found for: ofDeviceId:{} portNumber:{} vlanId:{}",
+ ofDeviceId, portNumber, vlanId);
+ return Response.status(NO_CONTENT).build();
+ }
+ ObjectMapper mapper = new ObjectMapper();
+ ObjectNode root = mapper.createObjectNode();
+ root.set("data", codec.encodeMac(mac.get(), this));
+ return Response.ok(root, MediaType.APPLICATION_JSON_TYPE).status(OK).build();
+ }
+
+ /**
+ * Delete MAC Mapping for request paramaters.
+ *
+ * @param ofDeviceId device id
+ * @param portNumber port number
+ * @param vlanId vlan id
+ * @return URI of request
+ */
+ @DELETE
+ @Path("/mapping/{ofDeviceId}/{portNumber}/{vlanId}")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response deleteMacMapping(@PathParam("ofDeviceId") String ofDeviceId,
+ @PathParam("portNumber") Integer portNumber,
+ @PathParam("vlanId") Short vlanId) {
+ try {
+ if (ofDeviceId == null || portNumber == null || vlanId == null) {
+ throw new IllegalArgumentException(INVALID_PATH_PARAMETERS);
+ }
+ MacDeleteResult deleteResult = macLearnerService.deleteMacMapping(DeviceId.deviceId(ofDeviceId),
+ PortNumber.portNumber(portNumber),
+ VlanId.vlanId(vlanId));
+ if (deleteResult.equals(MacDeleteResult.UNSUCCESSFUL)) {
+ return Response.status(NO_CONTENT).build();
+ }
+ return Response
+ .created(new URI("/delete/mapping/" +
+ ofDeviceId + PATH_DELIMITER +
+ portNumber + PATH_DELIMITER +
+ vlanId))
+ .status(OK)
+ .build();
+ } catch (URISyntaxException e) {
+ log.error("URI Syntax Exception occurred while deleting MAC Mapping " +
+ "for deviceId: {} portNumber: {} vlanId: {}",
+ ofDeviceId, portNumber, vlanId, e);
+ return Response.serverError().build();
+ }
+ }
+
+ /**
+ * Delete MAC Mappings for specific port of a device.
+ *
+ * @param ofDeviceId device id
+ * @param portNumber port number
+ * @return URI of request
+ */
+ @DELETE
+ @Path("/mappings/{ofDeviceId}/{portNumber}")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response deleteMacMappings(@PathParam("ofDeviceId") String ofDeviceId,
+ @PathParam("portNumber") Integer portNumber) {
+ try {
+ if (ofDeviceId == null || portNumber == null) {
+ throw new IllegalArgumentException(INVALID_PATH_PARAMETERS);
+ }
+ boolean deleteSuccess = macLearnerService.deleteMacMappings(DeviceId.deviceId(ofDeviceId),
+ PortNumber.portNumber(portNumber));
+ if (!deleteSuccess) {
+ return Response.status(NO_CONTENT).build();
+ }
+ return Response.created(new URI("/delete/mappings/" +
+ ofDeviceId + PATH_DELIMITER +
+ portNumber)).status(OK).build();
+ } catch (URISyntaxException e) {
+ log.error("URI Syntax Exception occurred while deleting MAC Mappings for deviceId: {} portNumber: {}",
+ ofDeviceId, portNumber, e);
+ return Response.serverError().build();
+ }
+ }
+
+ /**
+ * Get ignored ports for MAC Mapping.
+ *
+ * @return list of ignored port json object with deviceId and portNumber fields
+ */
+ @GET
+ @Path("/ports/ignored")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response getIgnoredPorts() {
+ Map<DeviceId, Set<PortNumber>> ignoredPorts = macLearnerService.getIgnoredPorts();
+ ObjectMapper mapper = new ObjectMapper();
+ ObjectNode root = mapper.createObjectNode();
+ ArrayNode ignoredPortsArray = mapper.createArrayNode();
+ if (ignoredPorts == null) {
+ root.set("data", ignoredPortsArray);
+ return Response.ok(root, MediaType.APPLICATION_JSON_TYPE).build();
+ } else {
+ for (Map.Entry<DeviceId, Set<PortNumber>> entry : ignoredPorts.entrySet()) {
+ entry.getValue().forEach(portNumber -> ignoredPortsArray.add(
+ codec.encodePort(new MacLearnerKey(entry.getKey(), portNumber, VlanId.NONE), this)
+ ));
+ }
+ }
+ root.set("data", ignoredPortsArray);
+ return Response.ok(root, MediaType.APPLICATION_JSON_TYPE).build();
+ }
+
+ /**
+ * Add to ignore ports map.
+ *
+ * @param ofDeviceId deviceId
+ * @param portNumber portNumber
+ * @return URI of request
+ */
+ @POST
+ @Path("/ignore-port/{ofDeviceId}/{portNumber}")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response addPortToIgnore(@PathParam("ofDeviceId") String ofDeviceId,
+ @PathParam("portNumber") Integer portNumber) {
+ try {
+ if (ofDeviceId == null || portNumber == null) {
+ throw new IllegalArgumentException(INVALID_PATH_PARAMETERS);
+ }
+ macLearnerService.addPortToIgnore(DeviceId.deviceId(ofDeviceId),
+ PortNumber.portNumber(portNumber));
+ return Response.created(new URI("/add/ignore-port/" +
+ ofDeviceId + PATH_DELIMITER +
+ portNumber)).status(OK).build();
+ } catch (URISyntaxException e) {
+ log.error("URI Syntax Exception occurred while adding ignore port deviceId: {} portNumber {}",
+ ofDeviceId, portNumber, e);
+ return Response.serverError().build();
+ }
+ }
+
+ /**
+ * Remove from ignored ports map.
+ *
+ * @param ofDeviceId device id
+ * @param portNumber port number
+ * @return URI of request
+ */
+ @DELETE
+ @Path("/ignore-port/{ofDeviceId}/{portNumber}")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response removeFromIgnoredPorts(@PathParam("ofDeviceId") String ofDeviceId,
+ @PathParam("portNumber") Integer portNumber) {
+ try {
+ if (ofDeviceId == null || portNumber == null) {
+ throw new IllegalArgumentException(INVALID_PATH_PARAMETERS);
+ }
+ macLearnerService.removeFromIgnoredPorts(DeviceId.deviceId(ofDeviceId),
+ PortNumber.portNumber(portNumber));
+ return Response.created(new URI("/remove/ignore-port/" +
+ ofDeviceId + PATH_DELIMITER +
+ portNumber)).status(OK).build();
+ } catch (URISyntaxException e) {
+ log.error("URISyntaxException occurred while removing ignore port deviceId: {} portNumber {}",
+ ofDeviceId, portNumber, e);
+ return Response.serverError().build();
+ }
+ }
+
+}
diff --git a/app/src/main/java/org/opencord/maclearner/app/rest/package-info.java b/app/src/main/java/org/opencord/maclearner/app/rest/package-info.java
new file mode 100755
index 0000000..564a108
--- /dev/null
+++ b/app/src/main/java/org/opencord/maclearner/app/rest/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Mac Learner REST API.
+ */
+package org.opencord.maclearner.app.rest;
diff --git a/app/src/main/webapp/WEB-INF/web.xml b/app/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..c842ded
--- /dev/null
+++ b/app/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+<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>Mac Learner 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.opencord.maclearner.app.rest.MacLearnerWebApplication</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/app/src/test/java/org/opencord/maclearner/app/impl/MacLearnerManagerTest.java b/app/src/test/java/org/opencord/maclearner/app/impl/MacLearnerManagerTest.java
new file mode 100644
index 0000000..1e05637
--- /dev/null
+++ b/app/src/test/java/org/opencord/maclearner/app/impl/MacLearnerManagerTest.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.opencord.maclearner.app.impl;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ConnectPoint;
+
+import java.util.Optional;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Set of tests of the Mac Learner ONOS application component.
+ */
+public class MacLearnerManagerTest extends TestBaseMacLearner {
+
+ @Before
+ public void setUp() {
+ setUpApp();
+ }
+
+ @After
+ public void tearDown() {
+ this.macLearnerManager.deactivate();
+ }
+
+ private static final MacAddress CLIENT_MAC = MacAddress.valueOf("00:00:00:00:00:01");
+ private static final VlanId CLIENT_VLAN = VlanId.vlanId("100");
+ private static final VlanId CLIENT_QINQ_VLAN = VlanId.vlanId("200");
+ private static final ConnectPoint CLIENT_CP = ConnectPoint.deviceConnectPoint("of:0000000000000001/1");
+
+ @Test
+ public void testSingleTagDhcpPacket() {
+ packetService.processPacket(new TestDhcpRequestPacketContext(CLIENT_MAC,
+ CLIENT_VLAN,
+ VlanId.NONE,
+ CLIENT_CP));
+ Optional<MacAddress> macAddress = macLearnerManager.getMacMapping(CLIENT_CP.deviceId(),
+ CLIENT_CP.port(), CLIENT_VLAN);
+ assertTrue(macAddress.isPresent());
+ assertEquals(CLIENT_MAC, macAddress.get());
+ }
+
+ @Test
+ public void testDoubleTagDhcpPacket() {
+ packetService.processPacket(new TestDhcpRequestPacketContext(CLIENT_MAC,
+ CLIENT_VLAN,
+ CLIENT_QINQ_VLAN,
+ CLIENT_CP));
+ Optional<MacAddress> macAddress = macLearnerManager.getMacMapping(CLIENT_CP.deviceId(),
+ CLIENT_CP.port(), CLIENT_QINQ_VLAN);
+ assertTrue(macAddress.isPresent());
+ assertEquals(CLIENT_MAC, macAddress.get());
+ }
+
+}
diff --git a/app/src/test/java/org/opencord/maclearner/app/impl/TestBaseMacLearner.java b/app/src/test/java/org/opencord/maclearner/app/impl/TestBaseMacLearner.java
new file mode 100644
index 0000000..c9d4af5
--- /dev/null
+++ b/app/src/test/java/org/opencord/maclearner/app/impl/TestBaseMacLearner.java
@@ -0,0 +1,487 @@
+/*
+ * 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.opencord.maclearner.app.impl;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.onlab.junit.TestUtils;
+import org.onlab.packet.DHCP;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.UDP;
+import org.onlab.packet.VlanId;
+import org.onlab.packet.dhcp.DhcpOption;
+import org.onosproject.cfg.ComponentConfigAdapter;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.event.DefaultEventSinkRegistry;
+import org.onosproject.event.Event;
+import org.onosproject.event.EventDeliveryService;
+import org.onosproject.event.EventSink;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.packet.DefaultInboundPacket;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketContextAdapter;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketServiceAdapter;
+import org.onosproject.store.service.AsyncConsistentMap;
+import org.onosproject.store.service.AsyncDistributedSet;
+import org.onosproject.store.service.AtomicCounter;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.ConsistentMapAdapter;
+import org.onosproject.store.service.ConsistentMapBuilder;
+import org.onosproject.store.service.DistributedSet;
+import org.onosproject.store.service.DistributedSetAdapter;
+import org.onosproject.store.service.DistributedSetBuilder;
+import org.onosproject.store.service.MapEvent;
+import org.onosproject.store.service.MapEventListener;
+import org.onosproject.store.service.SetEventListener;
+import org.onosproject.store.service.StorageServiceAdapter;
+import org.onosproject.store.service.Versioned;
+
+import java.lang.reflect.Field;
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.BiFunction;
+
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * Mac Learner mock services class.
+ */
+public abstract class TestBaseMacLearner {
+
+ private static final Ip4Address SERVER_IP = Ip4Address.valueOf("10.0.3.253");
+ private static final Ip4Address INTERFACE_IP = Ip4Address.valueOf("10.0.3.254");
+
+ protected MacLearnerManager macLearnerManager;
+ protected ObjectMapper mapper;
+ protected ApplicationId subject;
+
+ protected ComponentConfigService componentConfigService = new MockComponentConfigService();
+ protected MockCoreService coreService = new MockCoreService();
+ protected MockStorageService storageService = new MockStorageService();
+ protected MockPacketService packetService = new MockPacketService();
+
+ public void setUpApp() {
+ macLearnerManager = new MacLearnerManager();
+ macLearnerManager.componentConfigService = this.componentConfigService;
+ macLearnerManager.coreService = this.coreService;
+ macLearnerManager.storageService = this.storageService;
+ macLearnerManager.packetService = this.packetService;
+ injectEventDispatcher(macLearnerManager, new MockEventDispatcher());
+ mapper = new ObjectMapper();
+ subject = macLearnerManager.coreService.registerApplication("org.opencord.maclearner");
+
+ macLearnerManager.activate();
+ }
+
+ /**
+ * Mocks an instance of {@link ApplicationId} so that the application
+ * component under test can query and use its application ID.
+ */
+ private static final class MockApplicationId implements ApplicationId {
+
+ private final short id;
+ private final String name;
+
+ public MockApplicationId(short id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ @Override
+ public short id() {
+ return id;
+ }
+
+ @Override
+ public String name() {
+ return name;
+ }
+ }
+
+ private static final class MockComponentConfigService extends ComponentConfigAdapter {
+
+ }
+
+ /**
+ * Mocks the core services of ONOS so that the application under test can
+ * register and query application IDs.
+ */
+ private static final class MockCoreService extends CoreServiceAdapter {
+
+ private List<ApplicationId> idList = Lists.newArrayList();
+ private Map<String, ApplicationId> idMap = Maps.newHashMap();
+
+ @Override
+ public ApplicationId getAppId(Short id) {
+ if (id >= idList.size()) {
+ return null;
+ }
+ return idList.get(id);
+ }
+
+ @Override
+ public ApplicationId getAppId(String name) {
+ return idMap.get(name);
+ }
+
+ @Override
+ public ApplicationId registerApplication(String name) {
+ ApplicationId appId = idMap.get(name);
+ if (appId == null) {
+ appId = new MockApplicationId((short) idList.size(), name);
+ idList.add(appId);
+ idMap.put(name, appId);
+ }
+ return appId;
+ }
+
+ }
+
+ /**
+ * Mocks the storage service of ONOS so that the application under test can
+ * use consistent maps.
+ */
+ private static class MockStorageService extends StorageServiceAdapter {
+
+ @Override
+ public <K, V> ConsistentMapBuilder<K, V> consistentMapBuilder() {
+ ConsistentMapBuilder<K, V> builder = new ConsistentMapBuilder<K, V>() {
+ @Override
+ public AsyncConsistentMap<K, V> buildAsyncMap() {
+ return null;
+ }
+
+ @Override
+ public ConsistentMap<K, V> build() {
+ return new TestConsistentMap<>();
+ }
+ };
+
+ return builder;
+ }
+
+ @Override
+ public <E> DistributedSetBuilder<E> setBuilder() {
+ DistributedSetBuilder<E> builder = new DistributedSetBuilder<E>() {
+ @Override
+ public AsyncDistributedSet<E> build() {
+ return new DistributedSetAdapter<E>() {
+ @Override
+ public DistributedSet<E> asDistributedSet() {
+ return new TestDistributedSet<>();
+ }
+ };
+ }
+ };
+
+ return builder;
+ }
+
+ @Override
+ public AtomicCounter getAtomicCounter(String name) {
+ return new MockAtomicCounter();
+ }
+
+ // Mock ConsistentMap that behaves as a HashMap
+ class TestConsistentMap<K, V> extends ConsistentMapAdapter<K, V> {
+ private Map<K, Versioned<V>> map = new HashMap<>();
+ private Map<MapEventListener<K, V>, Executor> listeners = new HashMap<>();
+
+ public void notifyListeners(MapEvent<K, V> event) {
+ listeners.forEach((c, e) -> e.execute(() -> c.event(event)));
+ }
+
+ @Override
+ public int size() {
+ return map.size();
+ }
+
+ @Override
+ public Versioned<V> put(K key, V value) {
+ Versioned<V> oldValue = map.get(key);
+ Versioned<V> newValue = new Versioned<>(value, oldValue == null ? 0 : oldValue.version() + 1);
+ map.put(key, newValue);
+ notifyListeners(new MapEvent<>(name(), key, newValue, oldValue));
+ return newValue;
+ }
+
+ @Override
+ public Versioned<V> get(K key) {
+ return map.get(key);
+ }
+
+ @Override
+ public Versioned<V> remove(K key) {
+ Versioned<V> oldValue = map.remove(key);
+ notifyListeners(new MapEvent<>(name(), key, oldValue, null));
+ return oldValue;
+ }
+
+ @Override
+ public Versioned<V> computeIfPresent(K key,
+ BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ Versioned<V> oldValue = map.get(key);
+ Versioned<V> newValue = new Versioned<>(remappingFunction.apply(key, oldValue.value()),
+ oldValue == null ? 0 : oldValue.version() + 1);
+ map.put(key, newValue);
+ notifyListeners(new MapEvent<>(name(), key, newValue, oldValue));
+ return newValue;
+ }
+
+ @Override
+ public Set<Map.Entry<K, Versioned<V>>> entrySet() {
+ return map.entrySet();
+ }
+
+ @Override
+ public Set<K> keySet() {
+ return map.keySet();
+ }
+
+ @Override
+ public Collection<Versioned<V>> values() {
+ return map.values();
+ }
+
+ @Override
+ public void clear() {
+ map.clear();
+ }
+
+ @Override
+ public void addListener(MapEventListener<K, V> listener, Executor executor) {
+ listeners.put(listener, executor);
+ }
+
+ @Override
+ public void removeListener(MapEventListener<K, V> listener) {
+ listeners.remove(listener);
+ }
+ }
+
+ // Mock DistributedSet that behaves as a HashSet
+ class TestDistributedSet<E> extends HashSet<E> implements DistributedSet<E> {
+
+ @Override
+ public void addListener(SetEventListener<E> listener) {
+ }
+
+ @Override
+ public void removeListener(SetEventListener<E> listener) {
+ }
+
+ @Override
+ public String name() {
+ return null;
+ }
+
+ @Override
+ public Type primitiveType() {
+ return null;
+ }
+ }
+ }
+
+ private static class MockAtomicCounter implements AtomicCounter {
+ long id = 0;
+
+ @Override
+ public long incrementAndGet() {
+ return ++id;
+ }
+
+ @Override
+ public long getAndIncrement() {
+ return id++;
+ }
+
+ @Override
+ public long getAndAdd(long delta) {
+ long oldId = id;
+ id += delta;
+ return oldId;
+ }
+
+ @Override
+ public long addAndGet(long delta) {
+ id += delta;
+ return id;
+ }
+
+ @Override
+ public void set(long value) {
+ id = value;
+ }
+
+ @Override
+ public boolean compareAndSet(long expectedValue, long updateValue) {
+ if (id == expectedValue) {
+ id = updateValue;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public long get() {
+ return id;
+ }
+
+ @Override
+ public String name() {
+ return "MockAtomicCounter";
+ }
+ }
+
+ /**
+ * Mocks the packet service of ONOS so that the application under test can
+ * observe network packets.
+ */
+ public static class MockPacketService extends PacketServiceAdapter {
+ Set<PacketProcessor> packetProcessors = Sets.newHashSet();
+ OutboundPacket emittedPacket;
+
+ @Override
+ public void addProcessor(PacketProcessor processor, int priority) {
+ packetProcessors.add(processor);
+ }
+
+ public void processPacket(PacketContext packetContext) {
+ packetProcessors.forEach(p -> p.process(packetContext));
+ }
+
+ @Override
+ public void emit(OutboundPacket packet) {
+ this.emittedPacket = packet;
+ }
+ }
+
+ /**
+ * Implements event delivery system that delivers events synchronously, or
+ * in-line with the post method invocation.
+ */
+ public static class MockEventDispatcher extends DefaultEventSinkRegistry
+ implements EventDeliveryService {
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public synchronized void post(Event event) {
+ EventSink sink = getSink(event.getClass());
+ checkState(sink != null, "No sink for event %s", event);
+ sink.process(event);
+ }
+
+ @Override
+ public void setDispatchTimeLimit(long millis) {
+ }
+
+ @Override
+ public long getDispatchTimeLimit() {
+ return 0;
+ }
+ }
+
+ public static void injectEventDispatcher(Object manager, EventDeliveryService svc) {
+ Class mc = manager.getClass();
+ Field[] var3 = mc.getSuperclass().getDeclaredFields();
+
+ for (Field f : var3) {
+ if (f.getType().equals(EventDeliveryService.class)) {
+ try {
+ TestUtils.setField(manager, f.getName(), svc);
+ break;
+ } catch (TestUtils.TestUtilsException var8) {
+ throw new IllegalArgumentException("Unable to inject reference", var8);
+ }
+ }
+ }
+ }
+
+ /**
+ * Generates DHCP REQUEST packet.
+ */
+ protected static class TestDhcpRequestPacketContext extends PacketContextAdapter {
+
+ private InboundPacket inPacket;
+
+ public TestDhcpRequestPacketContext(MacAddress clientMac, VlanId vlanId,
+ VlanId qinqQVid,
+ ConnectPoint clientCp) {
+ super(0, null, null, false);
+ byte[] dhcpMsgType = new byte[1];
+ dhcpMsgType[0] = (byte) DHCP.MsgType.DHCPREQUEST.getValue();
+
+ DhcpOption dhcpOption = new DhcpOption();
+ dhcpOption.setCode(DHCP.DHCPOptionCode.OptionCode_MessageType.getValue());
+ dhcpOption.setData(dhcpMsgType);
+ dhcpOption.setLength((byte) 1);
+ DhcpOption endOption = new DhcpOption();
+ endOption.setCode(DHCP.DHCPOptionCode.OptionCode_END.getValue());
+
+ DHCP dhcp = new DHCP();
+ dhcp.setHardwareType(DHCP.HWTYPE_ETHERNET);
+ dhcp.setHardwareAddressLength((byte) 6);
+ dhcp.setClientHardwareAddress(clientMac.toBytes());
+ dhcp.setOptions(ImmutableList.of(dhcpOption, endOption));
+
+
+ UDP udp = new UDP();
+ udp.setPayload(dhcp);
+ udp.setSourcePort(UDP.DHCP_CLIENT_PORT);
+ udp.setDestinationPort(UDP.DHCP_SERVER_PORT);
+
+ IPv4 ipv4 = new IPv4();
+ ipv4.setPayload(udp);
+ ipv4.setDestinationAddress(SERVER_IP.toInt());
+ ipv4.setSourceAddress(INTERFACE_IP.toInt());
+
+ Ethernet eth = new Ethernet();
+ eth.setEtherType(Ethernet.TYPE_IPV4)
+ .setVlanID(vlanId.toShort())
+ .setQinQVID(qinqQVid.toShort())
+ .setSourceMACAddress(clientMac)
+ .setDestinationMACAddress(MacAddress.BROADCAST)
+ .setPayload(ipv4);
+
+ this.inPacket = new DefaultInboundPacket(clientCp, eth,
+ ByteBuffer.wrap(eth.serialize()));
+ }
+
+ @Override
+ public InboundPacket inPacket() {
+ return this.inPacket;
+ }
+ }
+
+}
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..87462a3
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+<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-dependencies</artifactId>
+ <version>2.2.2</version>
+ <relativePath></relativePath>
+ </parent>
+
+ <groupId>org.opencord</groupId>
+ <artifactId>maclearner</artifactId>
+ <packaging>pom</packaging>
+ <version>1.0.0-SNAPSHOT</version>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <onos.version>2.2.2</onos.version>
+ <onos.app.name>org.opencord.maclearner</onos.app.name>
+ <onos.app.category>Utility</onos.app.category>
+ <onos.app.title>Mac Address by Connected Port Information Service</onos.app.title>
+ <onos.app.origin>ON.Lab</onos.app.origin>
+ <onos.app.url>http://onosproject.org</onos.app.url>
+ </properties>
+
+ <modules>
+ <module>api</module>
+ <module>app</module>
+ </modules>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-api</artifactId>
+ <version>${onos.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.karaf.shell</groupId>
+ <artifactId>org.apache.karaf.shell.console</artifactId>
+ </dependency>
+ </dependencies>
+
+ <repositories>
+ <repository>
+ <id>central</id>
+ <name>Central Repository</name>
+ <url>https://repo.maven.apache.org/maven2</url>
+ <layout>default</layout>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ <releases>
+ <enabled>true</enabled>
+ <updatePolicy>always</updatePolicy>
+ <checksumPolicy>fail</checksumPolicy>
+ </releases>
+ </repository>
+ <repository>
+ <id>snapshots</id>
+ <url>https://oss.sonatype.org/content/repositories/snapshots</url>
+ <snapshots>
+ <enabled>true</enabled>
+ <updatePolicy>always</updatePolicy>
+ <checksumPolicy>fail</checksumPolicy>
+ </snapshots>
+ </repository>
+ </repositories>
+
+</project>