Implement L2 load balancing service
Including event/listener, CLI support
Change-Id: I26f1da578a72f5b3ead413aa5155233fbf9ab2b6
diff --git a/apps/l2lb/BUCK b/apps/l2lb/BUCK
new file mode 100644
index 0000000..d293b2d
--- /dev/null
+++ b/apps/l2lb/BUCK
@@ -0,0 +1,23 @@
+COMPILE_DEPS = [
+ '//lib:CORE_DEPS',
+ '//lib:KRYO',
+ '//cli:onos-cli',
+ '//core/store/serializers:onos-core-serializers',
+]
+
+TEST_DEPS = [
+ '//lib:TEST_ADAPTERS',
+]
+
+osgi_jar_with_tests (
+ deps = COMPILE_DEPS,
+ test_deps = TEST_DEPS,
+)
+
+onos_app (
+ app_name = 'org.onosproject.l2lb',
+ category = 'Utilities',
+ description = 'L2 Load Balance Service',
+ title = 'L2 Load Balance Service',
+ url = 'http://onosproject.org',
+)
diff --git a/apps/l2lb/BUILD b/apps/l2lb/BUILD
new file mode 100644
index 0000000..7209251
--- /dev/null
+++ b/apps/l2lb/BUILD
@@ -0,0 +1,18 @@
+COMPILE_DEPS = CORE_DEPS + KRYO + CLI + [
+ "//core/store/serializers:onos-core-serializers",
+]
+
+TEST_DEPS = TEST_ADAPTERS
+
+osgi_jar_with_tests(
+ test_deps = TEST_DEPS,
+ deps = COMPILE_DEPS,
+)
+
+onos_app(
+ app_name = "org.onosproject.l2lb",
+ category = "Utilities",
+ description = "L2 Load Balance Service",
+ title = "L2 Load Balance Service",
+ url = "http://onosproject.org",
+)
diff --git a/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2Lb.java b/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2Lb.java
new file mode 100644
index 0000000..3c377ff
--- /dev/null
+++ b/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2Lb.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.l2lb.api;
+
+import org.onosproject.net.PortNumber;
+
+import java.util.Objects;
+import java.util.Set;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Represents L2 load balancer information.
+ */
+public class L2Lb {
+ private L2LbId l2LbId;
+ private Set<PortNumber> ports;
+ private L2LbMode mode;
+
+ /**
+ * Constructs a L2 load balancer.
+ *
+ * @param l2LbId L2 load balancer ID
+ * @param ports Set of member ports
+ * @param mode L2 load balancer mode
+ */
+ public L2Lb(L2LbId l2LbId, Set<PortNumber> ports, L2LbMode mode) {
+ this.l2LbId = l2LbId;
+ this.ports = ports;
+ this.mode = mode;
+ }
+
+ /**
+ * Gets L2 load balancer ID.
+ *
+ * @return L2 load balancer ID
+ */
+ public L2LbId l2LbId() {
+ return l2LbId;
+ }
+
+ /**
+ * Gets set of member ports.
+ *
+ * @return Set of member ports
+ */
+ public Set<PortNumber> ports() {
+ return ports;
+ }
+
+ /**
+ * Gets L2 load balancer mode.
+ *
+ * @return L2 load balancer mode.
+ */
+ public L2LbMode mode() {
+ return mode;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(l2LbId, ports, mode);
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof L2Lb)) {
+ return false;
+ }
+ final L2Lb other = (L2Lb) obj;
+
+ return Objects.equals(this.l2LbId, other.l2LbId) &&
+ Objects.equals(this.ports, other.ports) &&
+ this.mode == other.mode;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(getClass())
+ .add("l2LbId", l2LbId)
+ .add("ports", ports)
+ .add("mode", mode)
+ .toString();
+ }
+}
diff --git a/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbAdminService.java b/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbAdminService.java
new file mode 100644
index 0000000..076ed36
--- /dev/null
+++ b/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbAdminService.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.l2lb.api;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+
+import java.util.Set;
+
+/**
+ * LACP admin service.
+ */
+public interface L2LbAdminService {
+ /**
+ * Creates or updates a L2 load balancer.
+ *
+ * @param deviceId Device ID
+ * @param key L2 load balancer key
+ * @param ports physical ports in the L2 load balancer
+ * @param mode L2 load balancer mode
+ * @return L2 load balancer that is created or updated
+ */
+ L2Lb createOrUpdate(DeviceId deviceId, int key, Set<PortNumber> ports, L2LbMode mode);
+
+ /**
+ * Removes a L2 load balancer.
+ *
+ * @param deviceId Device ID
+ * @param key L2 load balancer key
+ * @return L2 load balancer that is removed
+ */
+ L2Lb remove(DeviceId deviceId, int key);
+
+}
diff --git a/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbEvent.java b/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbEvent.java
new file mode 100644
index 0000000..f0ac847
--- /dev/null
+++ b/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbEvent.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.l2lb.api;
+
+import org.onlab.util.Tools;
+import org.onosproject.event.AbstractEvent;
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+public class L2LbEvent extends AbstractEvent<L2LbEvent.Type, L2Lb> {
+
+ private L2Lb prevSubject;
+
+ /**
+ * L2 load balancer event type.
+ */
+ public enum Type {
+ ADDED,
+ REMOVED,
+ UPDATED
+ }
+
+ /**
+ * Constructs a L2 load balancer event.
+ *
+ * @param type event type
+ * @param subject current L2 load balancer information
+ * @param prevSubject previous L2 load balancer information
+ */
+ public L2LbEvent(Type type, L2Lb subject, L2Lb prevSubject) {
+ super(type, subject);
+ this.prevSubject = prevSubject;
+ }
+
+ /**
+ * Gets previous L2 load balancer information.
+ *
+ * @return previous subject
+ */
+ public L2Lb prevSubject() {
+ return prevSubject;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(subject(), time(), prevSubject);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (!(other instanceof L2LbEvent)) {
+ return false;
+ }
+
+ L2LbEvent that = (L2LbEvent) other;
+ return Objects.equals(this.subject(), that.subject()) &&
+ Objects.equals(this.type(), that.type()) &&
+ Objects.equals(this.prevSubject, that.prevSubject);
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("type", type())
+ .add("subject", subject())
+ .add("prevSubject", prevSubject)
+ .add("time", Tools.defaultOffsetDataTime(time()))
+ .toString();
+ }
+}
diff --git a/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbId.java b/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbId.java
new file mode 100644
index 0000000..65aecef
--- /dev/null
+++ b/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbId.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.l2lb.api;
+
+import org.onosproject.net.DeviceId;
+
+import java.util.Objects;
+
+public class L2LbId {
+ private final DeviceId deviceId;
+ private final int key;
+
+ /**
+ * Constructs L2 load balancer ID.
+ *
+ * @param deviceId device ID
+ * @param key L2 load balancer key
+ */
+ public L2LbId(DeviceId deviceId, int key) {
+ this.deviceId = deviceId;
+ this.key = key;
+ }
+
+ /**
+ * Returns L2 load balancer device ID.
+ *
+ * @return L2 load balancer device ID
+ */
+ public DeviceId deviceId() {
+ return deviceId;
+ }
+
+ /**
+ * Returns L2 load balancer key.
+ *
+ * @return L2 load balancer key
+ */
+ public int key() {
+ return key;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(deviceId, key);
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof L2LbId)) {
+ return false;
+ }
+ final L2LbId other = (L2LbId) obj;
+
+ return Objects.equals(this.deviceId, other.deviceId) &&
+ Objects.equals(this.key, other.key);
+ }
+
+ @Override
+ public String toString() {
+ return deviceId.toString() + " (" + key + ")";
+ }
+}
diff --git a/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbListener.java b/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbListener.java
new file mode 100644
index 0000000..f04ca47
--- /dev/null
+++ b/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbListener.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.l2lb.api;
+
+import org.onosproject.event.EventListener;
+
+/**
+ * L2 load balancer event listener.
+ */
+public interface L2LbListener extends EventListener<L2LbEvent> {
+}
diff --git a/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbMode.java b/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbMode.java
new file mode 100644
index 0000000..9c38bd6
--- /dev/null
+++ b/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbMode.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.l2lb.api;
+
+/**
+ * L2 load balancer mode.
+ */
+public enum L2LbMode {
+ /**
+ * Static L2 load balancer.
+ */
+ STATIC,
+
+ /**
+ * L2 load balancer based on LACP.
+ */
+ LACP
+}
diff --git a/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbService.java b/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbService.java
new file mode 100644
index 0000000..8779192
--- /dev/null
+++ b/apps/l2lb/src/main/java/org/onosproject/l2lb/api/L2LbService.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.l2lb.api;
+
+import org.onosproject.event.ListenerService;
+import org.onosproject.net.DeviceId;
+
+import java.util.Map;
+
+/**
+ * L2 load balance service.
+ */
+public interface L2LbService extends ListenerService<L2LbEvent, L2LbListener> {
+ /**
+ * Gets all L2 load balancers from the store.
+ *
+ * @return L2 load balancer ID and L2 load balancer information mapping
+ */
+ Map<L2LbId, L2Lb> getL2Lbs();
+
+ /**
+ * Gets L2 load balancer that matches given device ID and key, or null if not found.
+ *
+ * @param deviceId Device ID
+ * @param key L2 load balancer key
+ * @return L2 load balancer information
+ */
+ L2Lb getL2Lb(DeviceId deviceId, int key);
+
+ /**
+ * Gets L2 load balancer next ids from the store.
+ *
+ * @return L2 load balancer id and next id mapping
+ */
+ Map<L2LbId, Integer> getL2LbNexts();
+
+ /**
+ * Gets L2 load balancer next id that matches given device Id and key, or null if not found.
+ *
+ * @param deviceId Device ID
+ * @param key L2 load balancer key
+ * @return next ID
+ */
+ int getL2LbNexts(DeviceId deviceId, int key);
+}
diff --git a/apps/l2lb/src/main/java/org/onosproject/l2lb/api/package-info.java b/apps/l2lb/src/main/java/org/onosproject/l2lb/api/package-info.java
new file mode 100644
index 0000000..862428b
--- /dev/null
+++ b/apps/l2lb/src/main/java/org/onosproject/l2lb/api/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-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.
+ */
+
+/**
+ * L2 load balancer API.
+ */
+package org.onosproject.l2lb.api;
\ No newline at end of file
diff --git a/apps/l2lb/src/main/java/org/onosproject/l2lb/app/L2LbManager.java b/apps/l2lb/src/main/java/org/onosproject/l2lb/app/L2LbManager.java
new file mode 100644
index 0000000..1420e03
--- /dev/null
+++ b/apps/l2lb/src/main/java/org/onosproject/l2lb/app/L2LbManager.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.l2lb.app;
+
+
+import com.google.common.collect.Sets;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.l2lb.api.L2Lb;
+import org.onosproject.l2lb.api.L2LbEvent;
+import org.onosproject.l2lb.api.L2LbAdminService;
+import org.onosproject.l2lb.api.L2LbId;
+import org.onosproject.l2lb.api.L2LbListener;
+import org.onosproject.l2lb.api.L2LbMode;
+import org.onosproject.l2lb.api.L2LbService;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flowobjective.DefaultNextObjective;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.NextObjective;
+import org.onosproject.net.flowobjective.Objective;
+import org.onosproject.net.flowobjective.ObjectiveContext;
+import org.onosproject.net.flowobjective.ObjectiveError;
+import org.onosproject.net.intf.InterfaceService;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.MapEvent;
+import org.onosproject.store.service.MapEventListener;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.Versioned;
+import org.slf4j.Logger;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.onlab.util.Tools.groupedThreads;
+import static org.slf4j.LoggerFactory.getLogger;
+
+@Component(immediate = true)
+@Service
+public class L2LbManager implements L2LbService, L2LbAdminService {
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private PacketService packetService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private InterfaceService interfaceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private StorageService storageService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private FlowObjectiveService flowObjService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private MastershipService mastershipService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private DeviceService deviceService;
+
+ private static final Logger log = getLogger(L2LbManager.class);
+ private static final String APP_NAME = "org.onosproject.l2lb";
+
+ private ApplicationId appId;
+ private ConsistentMap<L2LbId, L2Lb> l2LbStore;
+ private ConsistentMap<L2LbId, Integer> l2LbNextStore;
+ private Set<L2LbListener> listeners = Sets.newConcurrentHashSet();
+
+ private ExecutorService l2LbEventExecutor;
+ private ExecutorService l2LbProvExecutor;
+ private MapEventListener<L2LbId, L2Lb> l2LbStoreListener;
+ // TODO build CLI to view and clear the next store
+ private MapEventListener<L2LbId, Integer> l2LbNextStoreListener;
+
+ @Activate
+ public void activate() {
+ appId = coreService.registerApplication(APP_NAME);
+
+ l2LbEventExecutor = Executors.newSingleThreadExecutor(groupedThreads("l2lb-event", "%d", log));
+ l2LbProvExecutor = Executors.newSingleThreadExecutor(groupedThreads("l2lb-prov", "%d", log));
+ l2LbStoreListener = new L2LbStoreListener();
+ l2LbNextStoreListener = new L2LbNextStoreListener();
+
+ KryoNamespace serializer = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(L2Lb.class)
+ .register(L2LbId.class)
+ .register(L2LbMode.class)
+ .build();
+ l2LbStore = storageService.<L2LbId, L2Lb>consistentMapBuilder()
+ .withName("onos-l2lb-store")
+ .withRelaxedReadConsistency()
+ .withSerializer(Serializer.using(serializer))
+ .build();
+ l2LbStore.addListener(l2LbStoreListener);
+ l2LbNextStore = storageService.<L2LbId, Integer>consistentMapBuilder()
+ .withName("onos-l2lb-next-store")
+ .withRelaxedReadConsistency()
+ .withSerializer(Serializer.using(serializer))
+ .build();
+ l2LbNextStore.addListener(l2LbNextStoreListener);
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ l2LbStore.removeListener(l2LbStoreListener);
+ l2LbNextStore.removeListener(l2LbNextStoreListener);
+
+ l2LbEventExecutor.shutdown();
+
+ log.info("Stopped");
+ }
+
+ @Override
+ public void addListener(L2LbListener listener) {
+ listeners.add(listener);
+ }
+
+ @Override
+ public void removeListener(L2LbListener listener) {
+ listeners.remove(listener);
+ }
+
+ @Override
+ public L2Lb createOrUpdate(DeviceId deviceId, int key, Set<PortNumber> ports, L2LbMode mode) {
+ L2LbId l2LbId = new L2LbId(deviceId, key);
+ log.debug("Putting {} -> {} {} into L2 load balancer store", l2LbId, mode, ports);
+ return Versioned.valueOrNull(l2LbStore.put(l2LbId, new L2Lb(l2LbId, ports, mode)));
+ }
+
+ @Override
+ public L2Lb remove(DeviceId deviceId, int key) {
+ L2LbId l2LbId = new L2LbId(deviceId, key);
+ log.debug("Removing {} from L2 load balancer store", l2LbId);
+ return Versioned.valueOrNull(l2LbStore.remove(l2LbId));
+ }
+
+ @Override
+ public Map<L2LbId, L2Lb> getL2Lbs() {
+ return l2LbStore.asJavaMap();
+ }
+
+ @Override
+ public L2Lb getL2Lb(DeviceId deviceId, int key) {
+ return Versioned.valueOrNull(l2LbStore.get(new L2LbId(deviceId, key)));
+ }
+
+ @Override
+ public Map<L2LbId, Integer> getL2LbNexts() {
+ return l2LbNextStore.asJavaMap();
+ }
+
+ @Override
+ public int getL2LbNexts(DeviceId deviceId, int key) {
+ return Versioned.valueOrNull(l2LbNextStore.get(new L2LbId(deviceId, key)));
+ }
+
+ private class L2LbStoreListener implements MapEventListener<L2LbId, L2Lb> {
+ public void event(MapEvent<L2LbId, L2Lb> event) {
+ switch (event.type()) {
+ case INSERT:
+ log.debug("L2Lb {} insert new={}, old={}", event.key(), event.newValue(), event.oldValue());
+ post(new L2LbEvent(L2LbEvent.Type.ADDED, event.newValue().value(), null));
+ populateL2Lb(event.newValue().value());
+ break;
+ case REMOVE:
+ log.debug("L2Lb {} remove new={}, old={}", event.key(), event.newValue(), event.oldValue());
+ post(new L2LbEvent(L2LbEvent.Type.REMOVED, null, event.oldValue().value()));
+ revokeL2Lb(event.oldValue().value());
+ break;
+ case UPDATE:
+ log.debug("L2Lb {} update new={}, old={}", event.key(), event.newValue(), event.oldValue());
+ post(new L2LbEvent(L2LbEvent.Type.UPDATED, event.newValue().value(),
+ event.oldValue().value()));
+ updateL2Lb(event.newValue().value(), event.oldValue().value());
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ private class L2LbNextStoreListener implements MapEventListener<L2LbId, Integer> {
+ public void event(MapEvent<L2LbId, Integer> event) {
+ switch (event.type()) {
+ case INSERT:
+ log.debug("L2Lb next {} insert new={}, old={}", event.key(), event.newValue(), event.oldValue());
+ break;
+ case REMOVE:
+ log.debug("L2Lb next {} remove new={}, old={}", event.key(), event.newValue(), event.oldValue());
+ break;
+ case UPDATE:
+ log.debug("L2Lb next {} update new={}, old={}", event.key(), event.newValue(), event.oldValue());
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ private void post(L2LbEvent l2LbEvent) {
+ l2LbEventExecutor.execute(() -> {
+ for (L2LbListener l : listeners) {
+ l.event(l2LbEvent);
+ }
+ });
+ }
+
+ // TODO repopulate when device reconnect
+ private void populateL2Lb(L2Lb l2Lb) {
+ DeviceId deviceId = l2Lb.l2LbId().deviceId();
+ if (!mastershipService.isLocalMaster(deviceId)) {
+ log.debug("Not the master of {}. Skip populateL2Lb {}", deviceId, l2Lb.l2LbId());
+ return;
+ }
+
+ l2LbProvExecutor.execute(() -> {
+ L2LbObjectiveContext context = new L2LbObjectiveContext(l2Lb.l2LbId());
+ NextObjective nextObj = nextObjBuilder(l2Lb.l2LbId(), l2Lb.ports()).add(context);
+
+ flowObjService.next(deviceId, nextObj);
+ l2LbNextStore.put(l2Lb.l2LbId(), nextObj.id());
+ });
+ }
+
+ private void revokeL2Lb(L2Lb l2Lb) {
+ DeviceId deviceId = l2Lb.l2LbId().deviceId();
+ if (!mastershipService.isLocalMaster(deviceId)) {
+ log.debug("Not the master of {}. Skip revokeL2Lb {}", deviceId, l2Lb.l2LbId());
+ return;
+ }
+
+ l2LbProvExecutor.execute(() -> {
+ l2LbNextStore.remove(l2Lb.l2LbId());
+ // NOTE group is not removed and we rely on the garbage collection mechanism
+ });
+ }
+
+ private void updateL2Lb(L2Lb newL2Lb, L2Lb oldL2Lb) {
+ DeviceId deviceId = newL2Lb.l2LbId().deviceId();
+ if (!mastershipService.isLocalMaster(deviceId)) {
+ log.debug("Not the master of {}. Skip updateL2Lb {}", deviceId, newL2Lb.l2LbId());
+ return;
+ }
+
+ l2LbProvExecutor.execute(() -> {
+ L2LbObjectiveContext context = new L2LbObjectiveContext(newL2Lb.l2LbId());
+ Set<PortNumber> portsToBeAdded = Sets.difference(newL2Lb.ports(), oldL2Lb.ports());
+ Set<PortNumber> portsToBeRemoved = Sets.difference(oldL2Lb.ports(), newL2Lb.ports());
+
+ flowObjService.next(deviceId, nextObjBuilder(newL2Lb.l2LbId(), portsToBeAdded).addToExisting(context));
+ flowObjService.next(deviceId, nextObjBuilder(newL2Lb.l2LbId(), portsToBeRemoved)
+ .removeFromExisting(context));
+ });
+ }
+
+ private NextObjective.Builder nextObjBuilder(L2LbId l2LbId, Set<PortNumber> ports) {
+ return nextObjBuilder(l2LbId, ports, flowObjService.allocateNextId());
+ }
+
+ private NextObjective.Builder nextObjBuilder(L2LbId l2LbId, Set<PortNumber> ports, int nextId) {
+ // TODO replace logical l2lb port
+ TrafficSelector meta = DefaultTrafficSelector.builder()
+ .matchInPort(PortNumber.portNumber(l2LbId.key())).build();
+ NextObjective.Builder nextObjBuilder = DefaultNextObjective.builder()
+ .withId(nextId)
+ .withMeta(meta)
+ .withType(NextObjective.Type.HASHED)
+ .fromApp(appId);
+ ports.forEach(port -> {
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder().setOutput(port).build();
+ nextObjBuilder.addTreatment(treatment);
+ });
+ return nextObjBuilder;
+ }
+
+ private final class L2LbObjectiveContext implements ObjectiveContext {
+ private final L2LbId l2LbId;
+
+ private L2LbObjectiveContext(L2LbId l2LbId) {
+ this.l2LbId = l2LbId;
+ }
+
+ @Override
+ public void onSuccess(Objective objective) {
+ NextObjective nextObj = (NextObjective) objective;
+ log.debug("Added nextobj {} for L2 load balancer {}", nextObj, l2LbId);
+ }
+
+ @Override
+ public void onError(Objective objective, ObjectiveError error) {
+ NextObjective nextObj = (NextObjective) objective;
+ log.debug("Failed to add nextobj {} for L2 load balancer {}", nextObj, l2LbId);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/apps/l2lb/src/main/java/org/onosproject/l2lb/app/package-info.java b/apps/l2lb/src/main/java/org/onosproject/l2lb/app/package-info.java
new file mode 100644
index 0000000..6e155f6
--- /dev/null
+++ b/apps/l2lb/src/main/java/org/onosproject/l2lb/app/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-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.
+ */
+
+/**
+ * L2 load balancer app.
+ */
+package org.onosproject.l2lb.app;
\ No newline at end of file
diff --git a/apps/l2lb/src/main/java/org/onosproject/l2lb/cli/L2LbAddCommand.java b/apps/l2lb/src/main/java/org/onosproject/l2lb/cli/L2LbAddCommand.java
new file mode 100644
index 0000000..831f017
--- /dev/null
+++ b/apps/l2lb/src/main/java/org/onosproject/l2lb/cli/L2LbAddCommand.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.l2lb.cli;
+
+import com.google.common.collect.Sets;
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.l2lb.api.L2LbAdminService;
+import org.onosproject.l2lb.api.L2LbMode;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Command to add a L2 load balancer.
+ */
+@Command(scope = "onos", name = "l2lb-add", description = "Create or update L2 load balancer ")
+public class L2LbAddCommand extends AbstractShellCommand {
+ @Argument(index = 0, name = "deviceId",
+ description = "Device ID",
+ required = true, multiValued = false)
+ private String deviceIdStr;
+
+ @Argument(index = 1, name = "key",
+ description = "L2 load balancer key",
+ required = true, multiValued = false)
+ private String keyStr;
+
+ @Argument(index = 2, name = "mode",
+ description = "L2 load balancer mode. STATIC or LACP",
+ required = true, multiValued = false)
+ private String modeStr;
+
+ @Argument(index = 3, name = "ports",
+ description = "L2 load balancer physical ports",
+ required = true, multiValued = true)
+ private String[] portsStr;
+
+ @Override
+ protected void execute() {
+ DeviceId deviceId = DeviceId.deviceId(deviceIdStr);
+ int l2LbPort = Integer.parseInt(keyStr);
+
+ L2LbMode mode = L2LbMode.valueOf(modeStr.toUpperCase());
+ Set<PortNumber> ports = Sets.newHashSet(portsStr).stream()
+ .map(PortNumber::fromString).collect(Collectors.toSet());
+
+ L2LbAdminService l2LbAdminService = get(L2LbAdminService.class);
+ l2LbAdminService.createOrUpdate(deviceId, l2LbPort, ports, mode);
+
+ }
+}
diff --git a/apps/l2lb/src/main/java/org/onosproject/l2lb/cli/L2LbListCommand.java b/apps/l2lb/src/main/java/org/onosproject/l2lb/cli/L2LbListCommand.java
new file mode 100644
index 0000000..36db012
--- /dev/null
+++ b/apps/l2lb/src/main/java/org/onosproject/l2lb/cli/L2LbListCommand.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.l2lb.cli;
+
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.l2lb.api.L2Lb;
+import org.onosproject.l2lb.api.L2LbId;
+import org.onosproject.l2lb.api.L2LbService;
+
+import java.util.Map;
+
+/**
+ * Command to show all L2 load balancers.
+ */
+@Command(scope = "onos", name = "l2lbs", description = "Lists L2 load balancers")
+public class L2LbListCommand extends AbstractShellCommand {
+ @Override
+ public void execute() {
+ L2LbService service = get(L2LbService.class);
+ Map<L2LbId, L2Lb> l2LbStore = service.getL2Lbs();
+
+ l2LbStore.forEach((l2LbId, l2Lb) -> print("%s -> %s, %s", l2LbId, l2Lb.ports(), l2Lb.mode()));
+ }
+}
\ No newline at end of file
diff --git a/apps/l2lb/src/main/java/org/onosproject/l2lb/cli/L2LbRemoveCommand.java b/apps/l2lb/src/main/java/org/onosproject/l2lb/cli/L2LbRemoveCommand.java
new file mode 100644
index 0000000..b8d79b1
--- /dev/null
+++ b/apps/l2lb/src/main/java/org/onosproject/l2lb/cli/L2LbRemoveCommand.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.l2lb.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.l2lb.api.L2LbAdminService;
+import org.onosproject.net.DeviceId;
+
+/**
+ * Command to remove a L2 load balancer.
+ */
+@Command(scope = "onos", name = "l2lb-remove", description = "Remove L2 load balancers ")
+public class L2LbRemoveCommand extends AbstractShellCommand {
+ @Argument(index = 0, name = "deviceId",
+ description = "Device ID",
+ required = true, multiValued = false)
+ private String deviceIdStr;
+
+ @Argument(index = 1, name = "key",
+ description = "L2 load balancer key",
+ required = true, multiValued = false)
+ private String keyStr;
+
+ @Override
+ protected void execute() {
+ DeviceId deviceId = DeviceId.deviceId(deviceIdStr);
+ int l2LbPort = Integer.parseInt(keyStr);
+
+ L2LbAdminService l2LbAdminService = get(L2LbAdminService.class);
+ l2LbAdminService.remove(deviceId, l2LbPort);
+ }
+}
diff --git a/apps/l2lb/src/main/java/org/onosproject/l2lb/cli/package-info.java b/apps/l2lb/src/main/java/org/onosproject/l2lb/cli/package-info.java
new file mode 100644
index 0000000..4e157c1
--- /dev/null
+++ b/apps/l2lb/src/main/java/org/onosproject/l2lb/cli/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-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.
+ */
+
+/**
+ * L2 load balancer CLI.
+ */
+package org.onosproject.l2lb.cli;
\ No newline at end of file
diff --git a/apps/l2lb/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/l2lb/src/main/resources/OSGI-INF/blueprint/shell-config.xml
new file mode 100644
index 0000000..1a6faf1e
--- /dev/null
+++ b/apps/l2lb/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -0,0 +1,32 @@
+<!--
+ ~ Copyright 2018-present Open Networking Foundation
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
+
+ <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
+
+ <command>
+ <action class="org.onosproject.l2lb.cli.L2LbListCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.l2lb.cli.L2LbAddCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.l2lb.cli.L2LbRemoveCommand"/>
+ </command>
+
+ </command-bundle>
+
+</blueprint>
diff --git a/modules.bzl b/modules.bzl
index 837ecd0..fa2e82d 100644
--- a/modules.bzl
+++ b/modules.bzl
@@ -164,6 +164,7 @@
"//apps/flowanalyzer:onos-apps-flowanalyzer-oar",
"//apps/intentsync:onos-apps-intentsync-oar",
"//apps/influxdbmetrics:onos-apps-influxdbmetrics-oar",
+ "//apps/l2lb:onos-apps-l2lb-oar",
"//apps/metrics:onos-apps-metrics-oar",
"//apps/mfwd:onos-apps-mfwd-oar",
"//apps/mlb:onos-apps-mlb-oar",
diff --git a/modules.defs b/modules.defs
index 5c16ff3..04aa942 100644
--- a/modules.defs
+++ b/modules.defs
@@ -128,6 +128,7 @@
'//providers/bgpcep:onos-providers-bgpcep-oar',
'//providers/host:onos-providers-host-oar',
'//providers/hostprobing:onos-providers-hostprobing-oar',
+ '//providers/lacp:onos-providers-lacp-oar',
'//providers/lldp:onos-providers-lldp-oar',
'//providers/netcfghost:onos-providers-netcfghost-oar',
'//providers/netcfglinks:onos-providers-netcfglinks-oar',