Detangling incubator: virtual nets, tunnels, resource labels, oh my
- virtual networking moved to /apps/virtual; with CLI & REST API
- tunnels and labels moved to /apps/tunnel; with CLI & REST API; UI disabled for now
- protobuf/models moved to /core/protobuf/models
- defunct grpc/rpc registry stuff left under /graveyard
- compile dependencies on /incubator moved to respective modules for compilation
- run-time dependencies will need to be re-tested for dependent apps
- /graveyard will be removed in not-too-distant future
Change-Id: I0a0b995c635487edcf95a352f50dd162186b0b39
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/TenantAddCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/TenantAddCommand.java
new file mode 100644
index 0000000..a449afb
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/TenantAddCommand.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.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.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+
+/**
+ * Creates a new virtual network tenant.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-add-tenant",
+ description = "Creates a new virtual network tenant.")
+
+public class TenantAddCommand extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "id", description = "Tenant ID",
+ required = true, multiValued = false)
+ String id = null;
+
+ @Override
+ protected void doExecute() {
+ VirtualNetworkAdminService service = get(VirtualNetworkAdminService.class);
+ service.registerTenantId(TenantId.tenantId(id));
+ print("Tenant successfully added.");
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/TenantCompleter.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/TenantCompleter.java
new file mode 100644
index 0000000..29a625c
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/TenantCompleter.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.cli;
+
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+
+import java.util.List;
+import java.util.SortedSet;
+
+/**
+ * Tenant Id completer.
+ */
+@Service
+public class TenantCompleter implements Completer {
+ @Override
+ public int complete(Session session, CommandLine commandLine, List<String> candidates) {
+ // Delegate string completer
+ StringsCompleter delegate = new StringsCompleter();
+
+ // Fetch our service and feed it's offerings to the string completer
+ VirtualNetworkAdminService service = AbstractShellCommand.get(VirtualNetworkAdminService.class);
+
+ SortedSet<String> strings = delegate.getStrings();
+
+ for (TenantId tenantId : service.getTenantIds()) {
+ strings.add(tenantId.id());
+ }
+
+ // Now let the completer do the work for figuring out what to offer.
+ return delegate.complete(session, commandLine, candidates);
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/TenantListCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/TenantListCommand.java
new file mode 100644
index 0000000..6590090
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/TenantListCommand.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.cli;
+
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+import org.onosproject.utils.Comparators;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Lists all tenants.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-tenants",
+ description = "Lists all virtual network tenants.")
+public class TenantListCommand extends AbstractShellCommand {
+
+ private static final String FMT_TENANT = "tenantId=%s";
+
+ @Override
+ protected void doExecute() {
+ VirtualNetworkAdminService service = get(VirtualNetworkAdminService.class);
+ List<TenantId> tenants = new ArrayList<>();
+ tenants.addAll(service.getTenantIds());
+ Collections.sort(tenants, Comparators.TENANT_ID_COMPARATOR);
+
+ tenants.forEach(this::printTenant);
+ }
+
+ private void printTenant(TenantId tenantId) {
+ print(FMT_TENANT, tenantId.id());
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/TenantRemoveCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/TenantRemoveCommand.java
new file mode 100644
index 0000000..2877f59
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/TenantRemoveCommand.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.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.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+
+/**
+ * Creates a new virtual network tenant.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-remove-tenant",
+ description = "Removes a virtual network tenant.")
+
+public class TenantRemoveCommand extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "id", description = "Tenant ID",
+ required = true, multiValued = false)
+ @Completion(TenantCompleter.class)
+ String id = null;
+
+ @Override
+ protected void doExecute() {
+ VirtualNetworkAdminService service = get(VirtualNetworkAdminService.class);
+ service.unregisterTenantId(TenantId.tenantId(id));
+ print("Tenant successfully removed.");
+ }
+}
\ No newline at end of file
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualDeviceCompleter.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualDeviceCompleter.java
new file mode 100644
index 0000000..410b483
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualDeviceCompleter.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.cli;
+
+import static org.onlab.osgi.DefaultServiceDirectory.getService;
+
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractChoicesCompleter;
+import org.onosproject.incubator.net.virtual.Comparators;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualDevice;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Virtual device completer.
+ *
+ * Assumes the first argument which can be parsed to a number is network id.
+ */
+@Service
+public class VirtualDeviceCompleter extends AbstractChoicesCompleter {
+ @Override
+ protected List<String> choices() {
+ //parse argument list for network id
+ String[] argsArray = commandLine.getArguments();
+ for (String str : argsArray) {
+ if (str.matches("[0-9]+")) {
+ long networkId = Long.valueOf(str);
+ return getSortedVirtualDevices(networkId).stream()
+ .map(virtualDevice -> virtualDevice.id().toString())
+ .collect(Collectors.toList());
+ }
+ }
+ return Collections.singletonList("Missing network id");
+ }
+
+ /**
+ * Returns the list of virtual devices sorted using the network identifier.
+ *
+ * @param networkId network id
+ * @return sorted virtual device list
+ */
+ private List<VirtualDevice> getSortedVirtualDevices(long networkId) {
+ VirtualNetworkService service = getService(VirtualNetworkService.class);
+
+ List<VirtualDevice> virtualDevices = new ArrayList<>();
+ virtualDevices.addAll(service.getVirtualDevices(NetworkId.networkId(networkId)));
+ Collections.sort(virtualDevices, Comparators.VIRTUAL_DEVICE_COMPARATOR);
+ return virtualDevices;
+ }
+
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualDeviceCreateCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualDeviceCreateCommand.java
new file mode 100644
index 0000000..187c0e1
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualDeviceCreateCommand.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.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.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+import org.onosproject.net.DeviceId;
+
+/**
+ * Creates a new virtual device.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-create-device",
+ description = "Creates a new virtual device in a network.")
+public class VirtualDeviceCreateCommand extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "networkId", description = "Network ID",
+ required = true, multiValued = false)
+ @Completion(VirtualNetworkCompleter.class)
+ Long networkId = null;
+
+ @Argument(index = 1, name = "deviceId", description = "Device ID",
+ required = true, multiValued = false)
+ String deviceId = null;
+
+ @Override
+ protected void doExecute() {
+ VirtualNetworkAdminService service = get(VirtualNetworkAdminService.class);
+ service.createVirtualDevice(NetworkId.networkId(networkId), DeviceId.deviceId(deviceId));
+ print("Virtual device successfully created.");
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualDeviceListCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualDeviceListCommand.java
new file mode 100644
index 0000000..4a4acfc
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualDeviceListCommand.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.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.incubator.net.virtual.Comparators;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualDevice;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Lists all virtual devices for the network ID.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-devices",
+ description = "Lists all virtual devices in a virtual network.")
+public class VirtualDeviceListCommand extends AbstractShellCommand {
+
+ private static final String FMT_VIRTUAL_DEVICE =
+ "deviceId=%s";
+
+ @Argument(index = 0, name = "networkId", description = "Network ID",
+ required = true, multiValued = false)
+ @Completion(VirtualNetworkCompleter.class)
+ Long networkId = null;
+
+ @Override
+ protected void doExecute() {
+
+ getSortedVirtualDevices().forEach(this::printVirtualDevice);
+ }
+
+ /**
+ * Returns the list of virtual devices sorted using the device identifier.
+ *
+ * @return sorted virtual device list
+ */
+ private List<VirtualDevice> getSortedVirtualDevices() {
+ VirtualNetworkService service = get(VirtualNetworkService.class);
+
+ List<VirtualDevice> virtualDevices = new ArrayList<>();
+ virtualDevices.addAll(service.getVirtualDevices(NetworkId.networkId(networkId)));
+ Collections.sort(virtualDevices, Comparators.VIRTUAL_DEVICE_COMPARATOR);
+ return virtualDevices;
+ }
+
+ /**
+ * Prints out each virtual device.
+ *
+ * @param virtualDevice virtual device
+ */
+ private void printVirtualDevice(VirtualDevice virtualDevice) {
+ print(FMT_VIRTUAL_DEVICE, virtualDevice.id());
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualDeviceRemoveCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualDeviceRemoveCommand.java
new file mode 100644
index 0000000..f6213c7
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualDeviceRemoveCommand.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.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.cli.net.DeviceIdCompleter;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+import org.onosproject.net.DeviceId;
+
+/**
+ * Removes a virtual device.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-remove-device",
+ description = "Removes a virtual device.")
+public class VirtualDeviceRemoveCommand extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "networkId", description = "Network ID",
+ required = true, multiValued = false)
+ @Completion(VirtualNetworkCompleter.class)
+ Long networkId = null;
+
+ @Argument(index = 1, name = "deviceId", description = "Device ID",
+ required = true, multiValued = false)
+ @Completion(DeviceIdCompleter.class)
+ String deviceId = null;
+
+ @Override
+ protected void doExecute() {
+ VirtualNetworkAdminService service = get(VirtualNetworkAdminService.class);
+ service.removeVirtualDevice(NetworkId.networkId(networkId), DeviceId.deviceId(deviceId));
+ print("Virtual device successfully removed.");
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualFlowsListCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualFlowsListCommand.java
new file mode 100644
index 0000000..05da804
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualFlowsListCommand.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual.cli;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.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.apache.karaf.shell.api.action.Option;
+import org.onlab.util.StringFilter;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.cli.PlaceholderCompleter;
+import org.onosproject.cli.net.FlowRuleStatusCompleter;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowEntry.FlowEntryState;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.utils.Comparators;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+
+/**
+ * Lists all currently-known flows.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-flows",
+ description = "Lists all currently-known flows for a virtual network.")
+public class VirtualFlowsListCommand extends AbstractShellCommand {
+
+ private static final Predicate<FlowEntry> TRUE_PREDICATE = f -> true;
+
+ public static final String ANY = "any";
+
+ private static final String LONG_FORMAT = " id=%s, state=%s, bytes=%s, "
+ + "packets=%s, duration=%s, liveType=%s, priority=%s, tableId=%s, appId=%s, "
+ + "payLoad=%s, selector=%s, treatment=%s";
+
+ private static final String SHORT_FORMAT = " %s, bytes=%s, packets=%s, "
+ + "table=%s, priority=%s, selector=%s, treatment=%s";
+
+ @Argument(index = 0, name = "networkId", description = "Network ID",
+ required = true, multiValued = false)
+ @Completion(VirtualNetworkCompleter.class)
+ Long networkId = null;
+
+ @Argument(index = 1, name = "state", description = "Flow Rule state",
+ required = false, multiValued = false)
+ @Completion(FlowRuleStatusCompleter.class)
+ String state = null;
+
+ @Argument(index = 2, name = "uri", description = "Device ID",
+ required = false, multiValued = false)
+ @Completion(VirtualDeviceCompleter.class)
+ String uri = null;
+
+ @Argument(index = 3, name = "table", description = "Table ID",
+ required = false, multiValued = false)
+ @Completion(PlaceholderCompleter.class)
+ String table = null;
+
+ @Option(name = "-s", aliases = "--short",
+ description = "Print more succinct output for each flow",
+ required = false, multiValued = false)
+ private boolean shortOutput = false;
+
+ @Option(name = "-c", aliases = "--count",
+ description = "Print flow count only",
+ required = false, multiValued = false)
+ private boolean countOnly = false;
+
+ @Option(name = "-f", aliases = "--filter",
+ description = "Filter flows by specific key",
+ required = false, multiValued = true)
+ private List<String> filter = new ArrayList<>();
+
+ private Predicate<FlowEntry> predicate = TRUE_PREDICATE;
+
+ private StringFilter contentFilter;
+
+ @Override
+ protected void doExecute() {
+ CoreService coreService = get(CoreService.class);
+
+ VirtualNetworkService vnetservice = get(VirtualNetworkService.class);
+ DeviceService deviceService = vnetservice.get(NetworkId.networkId(networkId),
+ DeviceService.class);
+ FlowRuleService service = vnetservice.get(NetworkId.networkId(networkId),
+ FlowRuleService.class);
+ contentFilter = new StringFilter(filter, StringFilter.Strategy.AND);
+
+ compilePredicate();
+
+ SortedMap<Device, List<FlowEntry>> flows = getSortedFlows(deviceService, service);
+
+ if (outputJson()) {
+ print("%s", json(flows.keySet(), flows));
+ } else {
+ flows.forEach((device, flow) -> printFlows(device, flow, coreService));
+ }
+ }
+
+ /**
+ * Produces a JSON array of flows grouped by the each device.
+ *
+ * @param devices collection of devices to group flow by
+ * @param flows collection of flows per each device
+ * @return JSON array
+ */
+ private JsonNode json(Iterable<Device> devices,
+ Map<Device, List<FlowEntry>> flows) {
+ ObjectMapper mapper = new ObjectMapper();
+ ArrayNode result = mapper.createArrayNode();
+ for (Device device : devices) {
+ result.add(json(mapper, device, flows.get(device)));
+ }
+ return result;
+ }
+
+ /**
+ * Compiles a predicate to find matching flows based on the command
+ * arguments.
+ */
+ private void compilePredicate() {
+ if (state != null && !state.equals(ANY)) {
+ final FlowEntryState feState = FlowEntryState.valueOf(state.toUpperCase());
+ predicate = predicate.and(f -> f.state().equals(feState));
+ }
+
+ if (table != null) {
+ final int tableId = Integer.parseInt(table);
+ predicate = predicate.and(f -> f.tableId() == tableId);
+ }
+ }
+
+ // Produces JSON object with the flows of the given device.
+ private ObjectNode json(ObjectMapper mapper,
+ Device device, List<FlowEntry> flows) {
+ ObjectNode result = mapper.createObjectNode();
+ ArrayNode array = mapper.createArrayNode();
+
+ flows.forEach(flow -> array.add(jsonForEntity(flow, FlowEntry.class)));
+
+ result.put("device", device.id().toString())
+ .put("flowCount", flows.size())
+ .set("flows", array);
+ return result;
+ }
+
+ /**
+ * Returns the list of devices sorted using the device ID URIs.
+ *
+ * @param deviceService device service
+ * @param service flow rule service
+ * @return sorted device list
+ */
+ protected SortedMap<Device, List<FlowEntry>> getSortedFlows(DeviceService deviceService,
+ FlowRuleService service) {
+ SortedMap<Device, List<FlowEntry>> flows = new TreeMap<>(Comparators.ELEMENT_COMPARATOR);
+ List<FlowEntry> rules;
+
+ Iterable<Device> devices = null;
+ if (uri == null) {
+ devices = deviceService.getDevices();
+ } else {
+ Device dev = deviceService.getDevice(DeviceId.deviceId(uri));
+ devices = (dev == null) ? deviceService.getDevices()
+ : Collections.singletonList(dev);
+ }
+
+ for (Device d : devices) {
+ if (predicate.equals(TRUE_PREDICATE)) {
+ rules = newArrayList(service.getFlowEntries(d.id()));
+ } else {
+ rules = newArrayList();
+ for (FlowEntry f : service.getFlowEntries(d.id())) {
+ if (predicate.test(f)) {
+ rules.add(f);
+ }
+ }
+ }
+ rules.sort(Comparators.FLOW_RULE_COMPARATOR);
+
+ flows.put(d, rules);
+ }
+ return flows;
+ }
+
+ /**
+ * Prints flows.
+ *
+ * @param d the device
+ * @param flows the set of flows for that device
+ * @param coreService core system service
+ */
+ protected void printFlows(Device d, List<FlowEntry> flows,
+ CoreService coreService) {
+ List<FlowEntry> filteredFlows = flows.stream().
+ filter(f -> contentFilter.filter(f)).collect(Collectors.toList());
+ boolean empty = filteredFlows == null || filteredFlows.isEmpty();
+ print("deviceId=%s, flowRuleCount=%d", d.id(), empty ? 0 : filteredFlows.size());
+ if (empty || countOnly) {
+ return;
+ }
+
+ for (FlowEntry f : filteredFlows) {
+ if (shortOutput) {
+ print(SHORT_FORMAT, f.state(), f.bytes(), f.packets(),
+ f.tableId(), f.priority(), f.selector().criteria(),
+ printTreatment(f.treatment()));
+ } else {
+ ApplicationId appId = coreService.getAppId(f.appId());
+ print(LONG_FORMAT, Long.toHexString(f.id().value()), f.state(),
+ f.bytes(), f.packets(), f.life(), f.liveType(), f.priority(), f.tableId(),
+ appId != null ? appId.name() : "<none>",
+ f.payLoad() == null ? null : Arrays.toString(f.payLoad().payLoad()),
+ f.selector().criteria(), f.treatment());
+ }
+ }
+ }
+
+ private String printTreatment(TrafficTreatment treatment) {
+ final String delimiter = ", ";
+ StringBuilder builder = new StringBuilder("[");
+ if (!treatment.immediate().isEmpty()) {
+ builder.append("immediate=" + treatment.immediate() + delimiter);
+ }
+ if (!treatment.deferred().isEmpty()) {
+ builder.append("deferred=" + treatment.deferred() + delimiter);
+ }
+ if (treatment.clearedDeferred()) {
+ builder.append("clearDeferred" + delimiter);
+ }
+ if (treatment.tableTransition() != null) {
+ builder.append("transition=" + treatment.tableTransition() + delimiter);
+ }
+ if (treatment.metered() != null) {
+ builder.append("meter=" + treatment.metered() + delimiter);
+ }
+ if (treatment.writeMetadata() != null) {
+ builder.append("metadata=" + treatment.writeMetadata() + delimiter);
+ }
+ // Chop off last delimiter
+ builder.replace(builder.length() - delimiter.length(), builder.length(), "");
+ builder.append("]");
+ return builder.toString();
+ }
+
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualHostCompleter.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualHostCompleter.java
new file mode 100644
index 0000000..c8f6c44
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualHostCompleter.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.cli;
+
+import org.onosproject.cli.AbstractChoicesCompleter;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualHost;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.onlab.osgi.DefaultServiceDirectory.getService;
+
+/**
+ * Virtual host completer.
+ *
+ * Assumes the first argument which can be parsed to a number is network id.
+ */
+public class VirtualHostCompleter extends AbstractChoicesCompleter {
+ @Override
+ protected List<String> choices() {
+ //parse argument list for network id
+ String[] argsArray = commandLine.getArguments();
+ for (String str : argsArray) {
+ if (str.matches("[0-9]+")) {
+ long networkId = Long.valueOf(str);
+ return getSortedVirtualHosts(networkId).stream()
+ .map(virtualHost -> virtualHost.id().toString())
+ .collect(Collectors.toList());
+ }
+ }
+ return Collections.singletonList("Missing network id");
+ }
+
+ /**
+ * Returns the list of virtual hosts sorted using the host identifier.
+ *
+ * @param networkId network id
+ * @return virtual host list
+ */
+ private List<VirtualHost> getSortedVirtualHosts(long networkId) {
+ VirtualNetworkService service = getService(VirtualNetworkService.class);
+
+ List<VirtualHost> virtualHosts = new ArrayList<>();
+ virtualHosts.addAll(service.getVirtualHosts(NetworkId.networkId(networkId)));
+ return virtualHosts;
+ }
+
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualHostCreateCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualHostCreateCommand.java
new file mode 100644
index 0000000..da198b1
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualHostCreateCommand.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.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.apache.karaf.shell.api.action.Option;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.PortNumber;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Creates a new virtual host.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-create-host",
+ description = "Creates a new virtual host in a network.")
+public class VirtualHostCreateCommand extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "networkId", description = "Network ID",
+ required = true, multiValued = false)
+ @Completion(VirtualNetworkCompleter.class)
+ Long networkId = null;
+
+ @Argument(index = 1, name = "mac", description = "Mac address",
+ required = true, multiValued = false)
+ String mac = null;
+
+ @Argument(index = 2, name = "vlan", description = "Vlan",
+ required = true, multiValued = false)
+ short vlan;
+
+ @Argument(index = 3, name = "hostLocationDeviceId", description = "Host location device ID",
+ required = true, multiValued = false)
+ String hostLocationDeviceId;
+
+ @Argument(index = 4, name = "hostLocationPortNumber", description = "Host location port number",
+ required = true, multiValued = false)
+ long hostLocationPortNumber;
+
+ // ip addresses
+ @Option(name = "--hostIp", description = "Host IP addresses. Can be specified multiple times.",
+ required = false, multiValued = true)
+ protected String[] hostIpStrings;
+
+ @Override
+ protected void doExecute() {
+ VirtualNetworkAdminService service = get(VirtualNetworkAdminService.class);
+
+ Set<IpAddress> hostIps = new HashSet<>();
+ if (hostIpStrings != null) {
+ Arrays.stream(hostIpStrings).forEach(s -> hostIps.add(IpAddress.valueOf(s)));
+ }
+ HostLocation hostLocation = new HostLocation(DeviceId.deviceId(hostLocationDeviceId),
+ PortNumber.portNumber(hostLocationPortNumber),
+ System.currentTimeMillis());
+ MacAddress macAddress = MacAddress.valueOf(mac);
+ VlanId vlanId = VlanId.vlanId(vlan);
+ service.createVirtualHost(NetworkId.networkId(networkId),
+ HostId.hostId(macAddress, vlanId), macAddress, vlanId,
+ hostLocation, hostIps);
+ print("Virtual host successfully created.");
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualHostListCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualHostListCommand.java
new file mode 100644
index 0000000..38533ee
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualHostListCommand.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.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.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualHost;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Lists all virtual hosts for the network ID.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-hosts",
+ description = "Lists all virtual hosts in a virtual network.")
+public class VirtualHostListCommand extends AbstractShellCommand {
+
+ private static final String FMT_VIRTUAL_HOST =
+ "id=%s, mac=%s, vlan=%s, location=%s, ips=%s";
+
+ @Argument(index = 0, name = "networkId", description = "Network ID",
+ required = true, multiValued = false)
+ @Completion(VirtualNetworkCompleter.class)
+ Long networkId = null;
+
+ @Override
+ protected void doExecute() {
+ getSortedVirtualHosts().forEach(this::printVirtualHost);
+ }
+
+ /**
+ * Returns the list of virtual hosts sorted using the device identifier.
+ *
+ * @return virtual host list
+ */
+ private List<VirtualHost> getSortedVirtualHosts() {
+ VirtualNetworkService service = get(VirtualNetworkService.class);
+
+ List<VirtualHost> virtualHosts = new ArrayList<>();
+ virtualHosts.addAll(service.getVirtualHosts(NetworkId.networkId(networkId)));
+ return virtualHosts;
+ }
+
+ /**
+ * Prints out each virtual host.
+ *
+ * @param virtualHost virtual host
+ */
+ private void printVirtualHost(VirtualHost virtualHost) {
+ print(FMT_VIRTUAL_HOST, virtualHost.id().toString(), virtualHost.mac().toString(),
+ virtualHost.vlan().toString(), virtualHost.location().toString(),
+ virtualHost.ipAddresses().toString());
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualHostRemoveCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualHostRemoveCommand.java
new file mode 100644
index 0000000..329e4a3
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualHostRemoveCommand.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.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.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+import org.onosproject.net.HostId;
+//import org.onosproject.net.HostId;
+
+/**
+ * Removes a virtual host.
+ */
+
+@Service
+@Command(scope = "onos", name = "vnet-remove-host",
+ description = "Removes a virtual host.")
+public class VirtualHostRemoveCommand extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "networkId", description = "Network ID",
+ required = true, multiValued = false)
+ @Completion(VirtualNetworkCompleter.class)
+ Long networkId = null;
+
+ @Argument(index = 1, name = "id", description = "Host ID",
+ required = true, multiValued = false)
+ @Completion(VirtualHostCompleter.class)
+ String id = null;
+
+ @Override
+ protected void doExecute() {
+ VirtualNetworkAdminService service = get(VirtualNetworkAdminService.class);
+ service.removeVirtualHost(NetworkId.networkId(networkId), HostId.hostId(id));
+ print("Virtual host successfully removed.");
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualLinkCreateCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualLinkCreateCommand.java
new file mode 100644
index 0000000..4bc6ea5
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualLinkCreateCommand.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.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.apache.karaf.shell.api.action.Option;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+
+/**
+ * Creates a new virtual link.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-create-link",
+ description = "Creates a new virtual link in a network.")
+public class VirtualLinkCreateCommand extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "networkId", description = "Network ID",
+ required = true, multiValued = false)
+ @Completion(VirtualNetworkCompleter.class)
+ Long networkId = null;
+
+ @Argument(index = 1, name = "srcDeviceId", description = "Source device ID",
+ required = true, multiValued = false)
+ @Completion(VirtualDeviceCompleter.class)
+ String srcDeviceId = null;
+
+ @Argument(index = 2, name = "srcPortNum", description = "Source port number",
+ required = true, multiValued = false)
+ @Completion(VirtualPortCompleter.class)
+ Integer srcPortNum = null;
+
+ @Argument(index = 3, name = "dstDeviceId", description = "Destination device ID",
+ required = true, multiValued = false)
+ @Completion(VirtualDeviceCompleter.class)
+ String dstDeviceId = null;
+
+ @Argument(index = 4, name = "dstPortNum", description = "Destination port number",
+ required = true, multiValued = false)
+ @Completion(VirtualPortCompleter.class)
+ Integer dstPortNum = null;
+
+ @Option(name = "-b", aliases = "--bidirectional",
+ description = "If this argument is passed in then the virtual link created will be bidirectional, " +
+ "otherwise the link will be unidirectional.",
+ required = false, multiValued = false)
+ boolean bidirectional = false;
+
+ @Override
+ protected void doExecute() {
+ VirtualNetworkAdminService service = get(VirtualNetworkAdminService.class);
+ ConnectPoint src = new ConnectPoint(DeviceId.deviceId(srcDeviceId), PortNumber.portNumber(srcPortNum));
+ ConnectPoint dst = new ConnectPoint(DeviceId.deviceId(dstDeviceId), PortNumber.portNumber(dstPortNum));
+
+ service.createVirtualLink(NetworkId.networkId(networkId), src, dst);
+ if (bidirectional) {
+ service.createVirtualLink(NetworkId.networkId(networkId), dst, src);
+ }
+ print("Virtual link successfully created.");
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualLinkListCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualLinkListCommand.java
new file mode 100644
index 0000000..a69e0b7
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualLinkListCommand.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.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.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualLink;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Lists all virtual links for the network ID.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-links",
+ description = "Lists all virtual links in a virtual network.")
+public class VirtualLinkListCommand extends AbstractShellCommand {
+
+ private static final String FMT_VIRTUAL_LINK =
+ "src=%s, dst=%s, state=%s, tunnelId=%s";
+
+ @Argument(index = 0, name = "networkId", description = "Network ID",
+ required = true, multiValued = false)
+ @Completion(VirtualNetworkCompleter.class)
+ Long networkId = null;
+
+ @Override
+ protected void doExecute() {
+
+ getSortedVirtualLinks().forEach(this::printVirtualLink);
+ }
+
+ /**
+ * Returns the list of virtual links sorted using the device identifier.
+ *
+ * @return virtual link list
+ */
+ private List<VirtualLink> getSortedVirtualLinks() {
+ VirtualNetworkService service = get(VirtualNetworkService.class);
+
+ List<VirtualLink> virtualLinks = new ArrayList<>();
+ virtualLinks.addAll(service.getVirtualLinks(NetworkId.networkId(networkId)));
+ return virtualLinks;
+ }
+
+ /**
+ * Prints out each virtual link.
+ *
+ * @param virtualLink virtual link
+ */
+ private void printVirtualLink(VirtualLink virtualLink) {
+ print(FMT_VIRTUAL_LINK, virtualLink.src().toString(), virtualLink.dst().toString(),
+ virtualLink.state(),
+ virtualLink.tunnelId() == null ? null : virtualLink.tunnelId().toString());
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualLinkRemoveCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualLinkRemoveCommand.java
new file mode 100644
index 0000000..5f613f4
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualLinkRemoveCommand.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.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.apache.karaf.shell.api.action.Option;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+
+/**
+ * Removes a virtual link.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-remove-link",
+ description = "Removes a virtual link.")
+public class VirtualLinkRemoveCommand extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "networkId", description = "Network ID",
+ required = true, multiValued = false)
+ @Completion(VirtualNetworkCompleter.class)
+ Long networkId = null;
+
+ @Argument(index = 1, name = "srcDeviceId", description = "Source device ID",
+ required = true, multiValued = false)
+ @Completion(VirtualDeviceCompleter.class)
+ String srcDeviceId = null;
+
+ @Argument(index = 2, name = "srcPortNum", description = "Source port number",
+ required = true, multiValued = false)
+ @Completion(VirtualPortCompleter.class)
+ Integer srcPortNum = null;
+
+ @Argument(index = 3, name = "dstDeviceId", description = "Destination device ID",
+ required = true, multiValued = false)
+ @Completion(VirtualDeviceCompleter.class)
+ String dstDeviceId = null;
+
+ @Argument(index = 4, name = "dstPortNum", description = "Destination port number",
+ required = true, multiValued = false)
+ @Completion(VirtualPortCompleter.class)
+ Integer dstPortNum = null;
+
+ @Option(name = "-b", aliases = "--bidirectional",
+ description = "If this argument is passed in then then bidirectional virtual link will be removed, " +
+ "otherwise the unidirectional link will be removed.",
+ required = false, multiValued = false)
+ boolean bidirectional = false;
+
+ @Override
+ protected void doExecute() {
+ VirtualNetworkAdminService service = get(VirtualNetworkAdminService.class);
+ ConnectPoint src = new ConnectPoint(DeviceId.deviceId(srcDeviceId), PortNumber.portNumber(srcPortNum));
+ ConnectPoint dst = new ConnectPoint(DeviceId.deviceId(dstDeviceId), PortNumber.portNumber(dstPortNum));
+
+ service.removeVirtualLink(NetworkId.networkId(networkId), src, dst);
+ if (bidirectional) {
+ service.removeVirtualLink(NetworkId.networkId(networkId), dst, src);
+ }
+ print("Virtual link successfully removed.");
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkBalanceMastersCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkBalanceMastersCommand.java
new file mode 100644
index 0000000..51a0c96
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkBalanceMastersCommand.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2014-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.incubator.net.virtual.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.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.mastership.MastershipAdminService;
+
+/**
+ * Forces virtual network device mastership rebalancing.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-balance-masters",
+ description = "Forces virtual network device mastership rebalancing")
+public class VirtualNetworkBalanceMastersCommand extends AbstractShellCommand {
+ @Argument(index = 0, name = "networkId", description = "Network ID",
+ required = true, multiValued = false)
+ @Completion(VirtualNetworkCompleter.class)
+ Long networkId = null;
+ @Override
+ protected void doExecute() {
+ VirtualNetworkService vnetService = get(VirtualNetworkService.class);
+ MastershipAdminService mastershipAdminService = vnetService
+ .get(NetworkId.networkId(networkId), MastershipAdminService.class);
+ mastershipAdminService.balanceRoles();
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkCompleter.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkCompleter.java
new file mode 100644
index 0000000..1a3e7a2
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkCompleter.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.cli;
+
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.virtual.Comparators;
+import org.onosproject.net.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualNetwork;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+
+import java.util.List;
+import java.util.Set;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.SortedSet;
+
+/**
+ * Virtual network completer.
+ */
+@Service
+public class VirtualNetworkCompleter implements Completer {
+ @Override
+ public int complete(Session session, CommandLine commandLine, List<String> candidates) {
+ // Delegate string completer
+ StringsCompleter delegate = new StringsCompleter();
+
+ // Fetch our service and feed it's offerings to the string completer
+ VirtualNetworkAdminService service = AbstractShellCommand.get(VirtualNetworkAdminService.class);
+
+ List<VirtualNetwork> virtualNetworks = new ArrayList<>();
+
+ Set<TenantId> tenantSet = service.getTenantIds();
+ tenantSet.forEach(tenantId -> virtualNetworks.addAll(service.getVirtualNetworks(tenantId)));
+
+ Collections.sort(virtualNetworks, Comparators.VIRTUAL_NETWORK_COMPARATOR);
+
+ SortedSet<String> strings = delegate.getStrings();
+ virtualNetworks.forEach(virtualNetwork -> strings.add(virtualNetwork.id().toString()));
+
+ // Now let the completer do the work for figuring out what to offer.
+ return delegate.complete(session, commandLine, candidates);
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkCreateCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkCreateCommand.java
new file mode 100644
index 0000000..d1ebe47
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkCreateCommand.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.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.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+
+/**
+ * Creates a new virtual network.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-create",
+ description = "Creates a new virtual network.")
+public class VirtualNetworkCreateCommand extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "id", description = "Tenant ID",
+ required = true, multiValued = false)
+ @Completion(TenantCompleter.class)
+ String id = null;
+
+ @Override
+ protected void doExecute() {
+ VirtualNetworkAdminService service = get(VirtualNetworkAdminService.class);
+ service.createVirtualNetwork(TenantId.tenantId(id));
+ print("Virtual network successfully created.");
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkIntentCreateCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkIntentCreateCommand.java
new file mode 100644
index 0000000..4a8b400
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkIntentCreateCommand.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.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.net.ConnectivityIntentCommand;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkIntent;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentService;
+
+import java.util.List;
+
+/**
+ * Installs virtual network intents.
+ */
+@Service
+@Command(scope = "onos", name = "add-vnet-intent",
+ description = "Installs virtual network connectivity intent")
+public class VirtualNetworkIntentCreateCommand extends ConnectivityIntentCommand {
+
+ @Argument(index = 0, name = "networkId", description = "Network ID",
+ required = true, multiValued = false)
+ @Completion(VirtualNetworkCompleter.class)
+ Long networkId = null;
+
+ @Argument(index = 1, name = "ingressDevice",
+ description = "Ingress Device/Port Description",
+ required = true, multiValued = false)
+ String ingressDeviceString = null;
+
+ @Argument(index = 2, name = "egressDevice",
+ description = "Egress Device/Port Description",
+ required = true, multiValued = false)
+ String egressDeviceString = null;
+
+ @Override
+ protected void doExecute() {
+ VirtualNetworkService service = get(VirtualNetworkService.class);
+ IntentService virtualNetworkIntentService = service.get(NetworkId.networkId(networkId), IntentService.class);
+
+ ConnectPoint ingress = ConnectPoint.deviceConnectPoint(ingressDeviceString);
+ ConnectPoint egress = ConnectPoint.deviceConnectPoint(egressDeviceString);
+
+ TrafficSelector selector = buildTrafficSelector();
+ TrafficTreatment treatment = buildTrafficTreatment();
+
+ List<Constraint> constraints = buildConstraints();
+
+ Intent intent = VirtualNetworkIntent.builder()
+ .networkId(NetworkId.networkId(networkId))
+ .appId(appId())
+ .key(key())
+ .selector(selector)
+ .treatment(treatment)
+ .ingressPoint(ingress)
+ .egressPoint(egress)
+ .constraints(constraints)
+ .priority(priority())
+ .build();
+ virtualNetworkIntentService.submit(intent);
+ print("Virtual intent submitted:\n%s", intent.toString());
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkIntentRemoveCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkIntentRemoveCommand.java
new file mode 100644
index 0000000..eea3d45
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkIntentRemoveCommand.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.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.apache.karaf.shell.api.action.Option;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentEvent;
+import org.onosproject.net.intent.IntentListener;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.intent.IntentState;
+import org.onosproject.net.intent.Key;
+
+import java.math.BigInteger;
+import java.util.EnumSet;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static org.onosproject.net.intent.IntentState.FAILED;
+import static org.onosproject.net.intent.IntentState.WITHDRAWN;
+
+/**
+ * Removes a virtual network intent.
+ */
+@Service
+@Command(scope = "onos", name = "remove-vnet-intent",
+ description = "Removes the virtual network intent")
+public class VirtualNetworkIntentRemoveCommand extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "networkId", description = "Network ID",
+ required = true, multiValued = false)
+ @Completion(VirtualNetworkCompleter.class)
+ Long networkId = null;
+
+ @Argument(index = 1, name = "app",
+ description = "Application ID",
+ required = false, multiValued = false)
+ String applicationIdString = null;
+
+ @Argument(index = 2, name = "key",
+ description = "Intent Key",
+ required = false, multiValued = false)
+ String keyString = null;
+
+ @Option(name = "-p", aliases = "--purge",
+ description = "Purge the intent from the store after removal",
+ required = false, multiValued = false)
+ private boolean purgeAfterRemove = false;
+
+ @Option(name = "-s", aliases = "--sync",
+ description = "Waits for the removal before returning",
+ required = false, multiValued = false)
+ private boolean sync = false;
+
+ private static final EnumSet<IntentState> CAN_PURGE = EnumSet.of(WITHDRAWN, FAILED);
+
+ @Override
+ protected void doExecute() {
+ VirtualNetworkService service = get(VirtualNetworkService.class);
+ IntentService intentService = service.get(NetworkId.networkId(networkId), IntentService.class);
+ CoreService coreService = get(CoreService.class);
+
+ if (purgeAfterRemove || sync) {
+ print("Using \"sync\" to remove/purge intents - this may take a while...");
+ print("Check \"summary\" to see remove/purge progress.");
+ }
+
+ ApplicationId appId = appId();
+ if (!isNullOrEmpty(applicationIdString)) {
+ appId = coreService.getAppId(applicationIdString);
+ if (appId == null) {
+ print("Cannot find application Id %s", applicationIdString);
+ return;
+ }
+ }
+
+ if (isNullOrEmpty(keyString)) {
+ for (Intent intent : intentService.getIntents()) {
+ if (intent.appId().equals(appId)) {
+ removeIntent(intentService, intent);
+ }
+ }
+
+ } else {
+ final Key key;
+ if (keyString.startsWith("0x")) {
+ // The intent uses a LongKey
+ keyString = keyString.replaceFirst("0x", "");
+ key = Key.of(new BigInteger(keyString, 16).longValue(), appId);
+ } else {
+ // The intent uses a StringKey
+ key = Key.of(keyString, appId);
+ }
+
+ Intent intent = intentService.getIntent(key);
+ if (intent != null) {
+ removeIntent(intentService, intent);
+ } else {
+ print("Intent not found!");
+ }
+ }
+ }
+
+ /**
+ * Removes the intent using the specified intentService.
+ *
+ * @param intentService intent service
+ * @param intent intent
+ */
+ private void removeIntent(IntentService intentService, Intent intent) {
+ IntentListener listener = null;
+ Key key = intent.key();
+ final CountDownLatch withdrawLatch, purgeLatch;
+ if (purgeAfterRemove || sync) {
+ // set up latch and listener to track uninstall progress
+ withdrawLatch = new CountDownLatch(1);
+ purgeLatch = purgeAfterRemove ? new CountDownLatch(1) : null;
+ listener = (IntentEvent event) -> {
+ if (Objects.equals(event.subject().key(), key)) {
+ if (event.type() == IntentEvent.Type.WITHDRAWN ||
+ event.type() == IntentEvent.Type.FAILED) {
+ withdrawLatch.countDown();
+ } else if (purgeLatch != null && purgeAfterRemove &&
+ event.type() == IntentEvent.Type.PURGED) {
+ purgeLatch.countDown();
+ }
+ }
+ };
+ intentService.addListener(listener);
+ } else {
+ purgeLatch = null;
+ withdrawLatch = null;
+ }
+
+ // request the withdraw
+ intentService.withdraw(intent);
+
+ if ((purgeAfterRemove || sync) && purgeLatch != null) {
+ try { // wait for withdraw event
+ withdrawLatch.await(5, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ print("Timed out waiting for intent {} withdraw", key);
+ }
+ if (purgeAfterRemove && CAN_PURGE.contains(intentService.getIntentState(key))) {
+ intentService.purge(intent);
+ if (sync) { // wait for purge event
+ /* TODO
+ Technically, the event comes before map.remove() is called.
+ If we depend on sync and purge working together, we will
+ need to address this.
+ */
+ try {
+ purgeLatch.await(5, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ print("Timed out waiting for intent {} purge", key);
+ }
+ }
+ }
+ }
+
+ if (listener != null) {
+ // clean up the listener
+ intentService.removeListener(listener);
+ }
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkListCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkListCommand.java
new file mode 100644
index 0000000..df1172c
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkListCommand.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.cli;
+
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.virtual.Comparators;
+import org.onosproject.net.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualNetwork;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Lists all virtual networks for the tenant ID.
+ */
+@Service
+@Command(scope = "onos", name = "vnets",
+ description = "Lists all virtual networks.")
+public class VirtualNetworkListCommand extends AbstractShellCommand {
+
+ private static final String FMT_VIRTUAL_NETWORK =
+ "tenantId=%s, networkId=%s";
+
+ @Override
+ protected void doExecute() {
+
+ getSortedVirtualNetworks().forEach(this::printVirtualNetwork);
+ }
+
+ /**
+ * Returns the list of virtual networks sorted using the tenant identifier.
+ *
+ * @return sorted virtual network list
+ */
+ private List<VirtualNetwork> getSortedVirtualNetworks() {
+ VirtualNetworkService service = get(VirtualNetworkService.class);
+ VirtualNetworkAdminService adminService = get(VirtualNetworkAdminService.class);
+
+ List<VirtualNetwork> virtualNetworks = new ArrayList<>();
+
+ Set<TenantId> tenantSet = adminService.getTenantIds();
+ tenantSet.forEach(tenantId -> virtualNetworks.addAll(service.getVirtualNetworks(tenantId)));
+
+ Collections.sort(virtualNetworks, Comparators.VIRTUAL_NETWORK_COMPARATOR);
+ return virtualNetworks;
+ }
+
+ /**
+ * Prints out each virtual network.
+ *
+ * @param virtualNetwork virtual network
+ */
+ private void printVirtualNetwork(VirtualNetwork virtualNetwork) {
+ print(FMT_VIRTUAL_NETWORK, virtualNetwork.tenantId(), virtualNetwork.id());
+ }
+}
+
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkPacketRequestCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkPacketRequestCommand.java
new file mode 100644
index 0000000..059829d
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkPacketRequestCommand.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.cli;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+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.apache.karaf.shell.api.action.Option;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.VlanId;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.cli.net.EthType;
+import org.onosproject.cli.net.EthTypeCompleter;
+import org.onosproject.cli.net.ExtHeader;
+import org.onosproject.cli.net.ExtHeaderCompleter;
+import org.onosproject.cli.net.Icmp6Code;
+import org.onosproject.cli.net.Icmp6CodeCompleter;
+import org.onosproject.cli.net.Icmp6Type;
+import org.onosproject.cli.net.Icmp6TypeCompleter;
+import org.onosproject.cli.net.IpProtocol;
+import org.onosproject.cli.net.IpProtocolCompleter;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.packet.PacketPriority;
+import org.onosproject.net.packet.PacketRequest;
+import org.onosproject.net.packet.PacketService;
+
+import java.util.List;
+import java.util.Optional;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+
+/**
+ * Tests virtual network packet requests.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-packet",
+ description = "Tests virtual network packet requests")
+public class VirtualNetworkPacketRequestCommand extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "command",
+ description = "Command name (requestPackets|getRequests|cancelPackets)",
+ required = true, multiValued = false)
+ private String command = null;
+
+ @Argument(index = 1, name = "networkId", description = "Network ID",
+ required = true, multiValued = false)
+ private Long networkId = null;
+
+ @Option(name = "--deviceId", description = "Device ID",
+ required = false, multiValued = false)
+ private String deviceIdString = null;
+
+ // Traffic selector
+ @Option(name = "-s", aliases = "--ethSrc", description = "Source MAC Address",
+ required = false, multiValued = false)
+ private String srcMacString = null;
+
+ @Option(name = "-d", aliases = "--ethDst", description = "Destination MAC Address",
+ required = false, multiValued = false)
+ private String dstMacString = null;
+
+ @Option(name = "-t", aliases = "--ethType", description = "Ethernet Type",
+ required = false, multiValued = false)
+ @Completion(EthTypeCompleter.class)
+ private String ethTypeString = null;
+
+ @Option(name = "-v", aliases = "--vlan", description = "VLAN ID",
+ required = false, multiValued = false)
+ private String vlanString = null;
+
+ @Option(name = "--ipProto", description = "IP Protocol",
+ required = false, multiValued = false)
+ @Completion(IpProtocolCompleter.class)
+ private String ipProtoString = null;
+
+ @Option(name = "--ipSrc", description = "Source IP Prefix",
+ required = false, multiValued = false)
+ private String srcIpString = null;
+
+ @Option(name = "--ipDst", description = "Destination IP Prefix",
+ required = false, multiValued = false)
+ private String dstIpString = null;
+
+ @Option(name = "--fLabel", description = "IPv6 Flow Label",
+ required = false, multiValued = false)
+ private String fLabelString = null;
+
+ @Option(name = "--icmp6Type", description = "ICMPv6 Type",
+ required = false, multiValued = false)
+ @Completion(Icmp6TypeCompleter.class)
+ private String icmp6TypeString = null;
+
+ @Option(name = "--icmp6Code", description = "ICMPv6 Code",
+ required = false, multiValued = false)
+ @Completion(Icmp6CodeCompleter.class)
+ private String icmp6CodeString = null;
+
+ @Option(name = "--ndTarget", description = "IPv6 Neighbor Discovery Target Address",
+ required = false, multiValued = false)
+ private String ndTargetString = null;
+
+ @Option(name = "--ndSLL", description = "IPv6 Neighbor Discovery Source Link-Layer",
+ required = false, multiValued = false)
+ private String ndSllString = null;
+
+ @Option(name = "--ndTLL", description = "IPv6 Neighbor Discovery Target Link-Layer",
+ required = false, multiValued = false)
+ private String ndTllString = null;
+
+ @Option(name = "--tcpSrc", description = "Source TCP Port",
+ required = false, multiValued = false)
+ private String srcTcpString = null;
+
+ @Option(name = "--tcpDst", description = "Destination TCP Port",
+ required = false, multiValued = false)
+ private String dstTcpString = null;
+
+ @Option(name = "--extHdr", description = "IPv6 Extension Header Pseudo-field",
+ required = false, multiValued = true)
+ @Completion(ExtHeaderCompleter.class)
+ private List<String> extHdrStringList = null;
+
+ @Override
+ protected void doExecute() {
+ VirtualNetworkService service = get(VirtualNetworkService.class);
+ PacketService virtualPacketService = service.get(NetworkId.networkId(networkId), PacketService.class);
+
+ if (command == null) {
+ print("Command is not defined");
+ return;
+ }
+
+ if (command.equals("getRequests")) {
+ getRequests(virtualPacketService);
+ return;
+ }
+
+ TrafficSelector selector = buildTrafficSelector();
+ PacketPriority packetPriority = PacketPriority.CONTROL; //TODO allow user to specify
+ Optional<DeviceId> optionalDeviceId = null;
+ if (!isNullOrEmpty(deviceIdString)) {
+ optionalDeviceId = Optional.of(DeviceId.deviceId(deviceIdString));
+ }
+
+ if (command.equals("requestPackets")) {
+ if (optionalDeviceId != null) {
+ virtualPacketService.requestPackets(selector, packetPriority, appId(), optionalDeviceId);
+ } else {
+ virtualPacketService.requestPackets(selector, packetPriority, appId());
+ }
+ print("Virtual packet requested:\n%s", selector);
+ return;
+ }
+
+ if (command.equals("cancelPackets")) {
+ if (optionalDeviceId != null) {
+ virtualPacketService.cancelPackets(selector, packetPriority, appId(), optionalDeviceId);
+ } else {
+ virtualPacketService.cancelPackets(selector, packetPriority, appId());
+ }
+ print("Virtual packet cancelled:\n%s", selector);
+ return;
+ }
+
+ print("Unsupported command %s", command);
+ }
+
+ private void getRequests(PacketService packetService) {
+ List<PacketRequest> packetRequests = packetService.getRequests();
+ if (outputJson()) {
+ print("%s", json(packetRequests));
+ } else {
+ packetRequests.forEach(packetRequest -> print(packetRequest.toString()));
+ }
+ }
+
+ private JsonNode json(List<PacketRequest> packetRequests) {
+ ObjectMapper mapper = new ObjectMapper();
+ ArrayNode result = mapper.createArrayNode();
+ packetRequests.forEach(packetRequest ->
+ result.add(jsonForEntity(packetRequest, PacketRequest.class)));
+ return result;
+ }
+
+ /**
+ * Constructs a traffic selector based on the command line arguments
+ * presented to the command.
+ * @return traffic selector
+ */
+ private TrafficSelector buildTrafficSelector() {
+ IpPrefix srcIpPrefix = null;
+ IpPrefix dstIpPrefix = null;
+
+ TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder();
+
+ if (!isNullOrEmpty(srcIpString)) {
+ srcIpPrefix = IpPrefix.valueOf(srcIpString);
+ if (srcIpPrefix.isIp4()) {
+ selectorBuilder.matchIPSrc(srcIpPrefix);
+ } else {
+ selectorBuilder.matchIPv6Src(srcIpPrefix);
+ }
+ }
+
+ if (!isNullOrEmpty(dstIpString)) {
+ dstIpPrefix = IpPrefix.valueOf(dstIpString);
+ if (dstIpPrefix.isIp4()) {
+ selectorBuilder.matchIPDst(dstIpPrefix);
+ } else {
+ selectorBuilder.matchIPv6Dst(dstIpPrefix);
+ }
+ }
+
+ if ((srcIpPrefix != null) && (dstIpPrefix != null) &&
+ (srcIpPrefix.version() != dstIpPrefix.version())) {
+ // ERROR: IP src/dst version mismatch
+ throw new IllegalArgumentException(
+ "IP source and destination version mismatch");
+ }
+
+ //
+ // Set the default EthType based on the IP version if the matching
+ // source or destination IP prefixes.
+ //
+ Short ethType = null;
+ if ((srcIpPrefix != null) && srcIpPrefix.isIp6()) {
+ ethType = EthType.IPV6.value();
+ }
+ if ((dstIpPrefix != null) && dstIpPrefix.isIp6()) {
+ ethType = EthType.IPV6.value();
+ }
+ if (!isNullOrEmpty(ethTypeString)) {
+ ethType = EthType.parseFromString(ethTypeString);
+ }
+ if (ethType != null) {
+ selectorBuilder.matchEthType(ethType);
+ }
+ if (!isNullOrEmpty(vlanString)) {
+ selectorBuilder.matchVlanId(VlanId.vlanId(Short.parseShort(vlanString)));
+ }
+ if (!isNullOrEmpty(srcMacString)) {
+ selectorBuilder.matchEthSrc(MacAddress.valueOf(srcMacString));
+ }
+
+ if (!isNullOrEmpty(dstMacString)) {
+ selectorBuilder.matchEthDst(MacAddress.valueOf(dstMacString));
+ }
+
+ if (!isNullOrEmpty(ipProtoString)) {
+ short ipProtoShort = IpProtocol.parseFromString(ipProtoString);
+ selectorBuilder.matchIPProtocol((byte) ipProtoShort);
+ }
+
+ if (!isNullOrEmpty(fLabelString)) {
+ selectorBuilder.matchIPv6FlowLabel(Integer.parseInt(fLabelString));
+ }
+
+ if (!isNullOrEmpty(icmp6TypeString)) {
+ byte icmp6Type = Icmp6Type.parseFromString(icmp6TypeString);
+ selectorBuilder.matchIcmpv6Type(icmp6Type);
+ }
+
+ if (!isNullOrEmpty(icmp6CodeString)) {
+ byte icmp6Code = Icmp6Code.parseFromString(icmp6CodeString);
+ selectorBuilder.matchIcmpv6Code(icmp6Code);
+ }
+
+ if (!isNullOrEmpty(ndTargetString)) {
+ selectorBuilder.matchIPv6NDTargetAddress(Ip6Address.valueOf(ndTargetString));
+ }
+
+ if (!isNullOrEmpty(ndSllString)) {
+ selectorBuilder.matchIPv6NDSourceLinkLayerAddress(MacAddress.valueOf(ndSllString));
+ }
+
+ if (!isNullOrEmpty(ndTllString)) {
+ selectorBuilder.matchIPv6NDTargetLinkLayerAddress(MacAddress.valueOf(ndTllString));
+ }
+
+ if (!isNullOrEmpty(srcTcpString)) {
+ selectorBuilder.matchTcpSrc(TpPort.tpPort(Integer.parseInt(srcTcpString)));
+ }
+
+ if (!isNullOrEmpty(dstTcpString)) {
+ selectorBuilder.matchTcpDst(TpPort.tpPort(Integer.parseInt(dstTcpString)));
+ }
+
+ if (extHdrStringList != null) {
+ short extHdr = 0;
+ for (String extHdrString : extHdrStringList) {
+ extHdr = (short) (extHdr | ExtHeader.parseFromString(extHdrString));
+ }
+ selectorBuilder.matchIPv6ExthdrFlags(extHdr);
+ }
+
+ return selectorBuilder.build();
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkRemoveCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkRemoveCommand.java
new file mode 100644
index 0000000..2b28044
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkRemoveCommand.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.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.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+
+/**
+ * Removes a virtual network.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-remove",
+ description = "Removes a virtual network.")
+public class VirtualNetworkRemoveCommand extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "id", description = "Network ID",
+ required = true, multiValued = false)
+ @Completion(VirtualNetworkCompleter.class)
+ Long id;
+
+ @Override
+ protected void doExecute() {
+ VirtualNetworkAdminService service = get(VirtualNetworkAdminService.class);
+ service.removeVirtualNetwork(NetworkId.networkId(id));
+ print("Virtual network successfully removed.");
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortBindCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortBindCommand.java
new file mode 100644
index 0000000..7013a01
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortBindCommand.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.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.cli.net.DeviceIdCompleter;
+import org.onosproject.cli.net.PortNumberCompleter;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.VirtualPort;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
+
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Binds an existing virtual port with a physical port.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-bind-port",
+ description = "Binds an existing virtual port with a physical port.")
+public class VirtualPortBindCommand extends AbstractShellCommand {
+ @Argument(index = 0, name = "networkId", description = "Network ID",
+ required = true, multiValued = false)
+ @Completion(VirtualNetworkCompleter.class)
+ Long networkId = null;
+
+ @Argument(index = 1, name = "deviceId", description = "Virtual Device ID",
+ required = true, multiValued = false)
+ @Completion(VirtualDeviceCompleter.class)
+ String deviceId = null;
+
+ @Argument(index = 2, name = "portNum", description = "Virtual device port number",
+ required = true, multiValued = false)
+ @Completion(VirtualPortCompleter.class)
+ Integer portNum = null;
+
+ @Argument(index = 3, name = "physDeviceId", description = "Physical Device ID",
+ required = true, multiValued = false)
+ @Completion(DeviceIdCompleter.class)
+ String physDeviceId = null;
+
+ @Argument(index = 4, name = "physPortNum", description = "Physical device port number",
+ required = true, multiValued = false)
+ @Completion(PortNumberCompleter.class)
+ Integer physPortNum = null;
+
+ @Override
+ protected void doExecute() {
+ VirtualNetworkAdminService service = get(VirtualNetworkAdminService.class);
+ DeviceService deviceService = get(DeviceService.class);
+
+ VirtualPort vPort = getVirtualPort(PortNumber.portNumber(portNum));
+ checkNotNull(vPort, "The virtual Port does not exist");
+
+ ConnectPoint realizedBy = new ConnectPoint(DeviceId.deviceId(physDeviceId),
+ PortNumber.portNumber(physPortNum));
+ service.bindVirtualPort(NetworkId.networkId(networkId), DeviceId.deviceId(deviceId),
+ PortNumber.portNumber(portNum), realizedBy);
+ print("Virtual port is successfully bound.");
+ }
+
+ /**
+ * Returns the virtual port matching the device and port identifier.
+ *
+ * @param aPortNumber port identifier
+ * @return matching virtual port, or null.
+ */
+ private VirtualPort getVirtualPort(PortNumber aPortNumber) {
+ VirtualNetworkService service = get(VirtualNetworkService.class);
+ Set<VirtualPort> ports = service.getVirtualPorts(NetworkId.networkId(networkId),
+ DeviceId.deviceId(deviceId));
+ return ports.stream().filter(p -> p.number().equals(aPortNumber))
+ .findFirst().get();
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortCompleter.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortCompleter.java
new file mode 100644
index 0000000..141c964
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortCompleter.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.cli;
+
+import static org.onlab.osgi.DefaultServiceDirectory.getService;
+
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractChoicesCompleter;
+import org.onosproject.incubator.net.virtual.Comparators;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.VirtualPort;
+import org.onosproject.net.DeviceId;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+/**
+ * Virtual port completer.
+ *
+ * Assumes the first argument which can be parsed to a number is network id
+ * and the argument right before the one being completed is device id
+ */
+@Service
+public class VirtualPortCompleter extends AbstractChoicesCompleter {
+ @Override
+ protected List<String> choices() {
+ //parse argument list for network id
+ String[] argsArray = commandLine.getArguments();
+ for (String str : argsArray) {
+ if (str.matches("[0-9]+")) {
+ long networkId = Long.valueOf(str);
+ String deviceId = argsArray[argsArray.length - 1];
+ return getSortedVirtualPorts(networkId, deviceId).stream()
+ .map(virtualPort -> virtualPort.number().toString())
+ .collect(Collectors.toList());
+ }
+ }
+
+ return Collections.singletonList("Missing network id");
+ }
+
+ /**
+ * Returns the list of virtual ports sorted using the port number.
+ *
+ * @param networkId network id.
+ * @param deviceId device id
+ * @return sorted virtual port list
+ */
+ private List<VirtualPort> getSortedVirtualPorts(long networkId, String deviceId) {
+ VirtualNetworkService service = getService(VirtualNetworkService.class);
+
+ List<VirtualPort> virtualPorts = new ArrayList<>();
+ virtualPorts.addAll(service.getVirtualPorts(NetworkId.networkId(networkId),
+ DeviceId.deviceId(deviceId)));
+ Collections.sort(virtualPorts, Comparators.VIRTUAL_PORT_COMPARATOR);
+ return virtualPorts;
+ }
+
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortCreateCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortCreateCommand.java
new file mode 100644
index 0000000..a466a64
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortCreateCommand.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.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.cli.net.DeviceIdCompleter;
+import org.onosproject.cli.net.PortNumberCompleter;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualDevice;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
+
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Creates a new virtual port.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-create-port",
+ description = "Creates a new virtual port in a network.")
+public class VirtualPortCreateCommand extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "networkId", description = "Network ID",
+ required = true, multiValued = false)
+ @Completion(VirtualNetworkCompleter.class)
+ Long networkId = null;
+
+ @Argument(index = 1, name = "deviceId", description = "Virtual Device ID",
+ required = true, multiValued = false)
+ @Completion(VirtualDeviceCompleter.class)
+ String deviceId = null;
+
+ @Argument(index = 2, name = "portNum", description = "Virtual device port number",
+ required = true, multiValued = false)
+ Integer portNum = null;
+
+ @Argument(index = 3, name = "physDeviceId", description = "Physical Device ID",
+ required = false, multiValued = false)
+ @Completion(DeviceIdCompleter.class)
+ String physDeviceId = null;
+
+ @Argument(index = 4, name = "physPortNum", description = "Physical device port number",
+ required = false, multiValued = false)
+ @Completion(PortNumberCompleter.class)
+ Integer physPortNum = null;
+
+ @Override
+ protected void doExecute() {
+ VirtualNetworkAdminService service = get(VirtualNetworkAdminService.class);
+ DeviceService deviceService = get(DeviceService.class);
+
+ VirtualDevice virtualDevice = getVirtualDevice(DeviceId.deviceId(deviceId));
+ checkNotNull(virtualDevice, "The virtual device does not exist.");
+
+ ConnectPoint realizedBy = null;
+ if (physDeviceId != null && physPortNum != null) {
+ checkNotNull(physPortNum, "The physical port does not specified.");
+ realizedBy = new ConnectPoint(DeviceId.deviceId(physDeviceId),
+ PortNumber.portNumber(physPortNum));
+ checkNotNull(realizedBy, "The physical port does not exist.");
+ }
+
+ service.createVirtualPort(NetworkId.networkId(networkId), DeviceId.deviceId(deviceId),
+ PortNumber.portNumber(portNum), realizedBy);
+ print("Virtual port successfully created.");
+ }
+
+ /**
+ * Returns the virtual device matching the device identifier.
+ *
+ * @param aDeviceId device identifier
+ * @return matching virtual device, or null.
+ */
+ private VirtualDevice getVirtualDevice(DeviceId aDeviceId) {
+ VirtualNetworkService service = get(VirtualNetworkService.class);
+
+ Set<VirtualDevice> virtualDevices = service.getVirtualDevices(NetworkId.networkId(networkId));
+
+ for (VirtualDevice virtualDevice : virtualDevices) {
+ if (virtualDevice.id().equals(aDeviceId)) {
+ return virtualDevice;
+ }
+ }
+ return null;
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortListCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortListCommand.java
new file mode 100644
index 0000000..f7a6c08
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortListCommand.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.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.incubator.net.virtual.Comparators;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.VirtualPort;
+import org.onosproject.net.DeviceId;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Lists all virtual ports for the network ID.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-ports",
+ description = "Lists all virtual ports in a virtual network.")
+public class VirtualPortListCommand extends AbstractShellCommand {
+
+ private static final String FMT_VIRTUAL_PORT =
+ "virtual portNumber=%s, physical deviceId=%s, portNumber=%s, isEnabled=%s";
+
+ @Argument(index = 0, name = "networkId", description = "Network ID",
+ required = true, multiValued = false)
+ @Completion(VirtualNetworkCompleter.class)
+ Long networkId = null;
+
+ @Argument(index = 1, name = "deviceId", description = "Virtual Device ID",
+ required = true, multiValued = false)
+ @Completion(VirtualDeviceCompleter.class)
+ String deviceId = null;
+
+ @Override
+ protected void doExecute() {
+
+ getSortedVirtualPorts().forEach(this::printVirtualPort);
+ }
+
+ /**
+ * Returns the list of virtual ports sorted using the network identifier.
+ *
+ * @return sorted virtual port list
+ */
+ private List<VirtualPort> getSortedVirtualPorts() {
+ VirtualNetworkService service = get(VirtualNetworkService.class);
+
+ List<VirtualPort> virtualPorts = new ArrayList<>();
+ virtualPorts.addAll(service.getVirtualPorts(NetworkId.networkId(networkId),
+ DeviceId.deviceId(deviceId)));
+ Collections.sort(virtualPorts, Comparators.VIRTUAL_PORT_COMPARATOR);
+ return virtualPorts;
+ }
+
+ /**
+ * Prints out each virtual port.
+ *
+ * @param virtualPort virtual port
+ */
+ private void printVirtualPort(VirtualPort virtualPort) {
+ if (virtualPort.realizedBy() == null) {
+ print(FMT_VIRTUAL_PORT, virtualPort.number(), "None", "None", virtualPort.isEnabled());
+ } else {
+ print(FMT_VIRTUAL_PORT, virtualPort.number(),
+ virtualPort.realizedBy().deviceId(),
+ virtualPort.realizedBy().port(),
+ virtualPort.isEnabled());
+ }
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortRemoveCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortRemoveCommand.java
new file mode 100644
index 0000000..6c79276
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortRemoveCommand.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.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.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+
+/**
+ * Removes a virtual port.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-remove-port",
+ description = "Removes a virtual port.")
+public class VirtualPortRemoveCommand extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "networkId", description = "Network ID",
+ required = true, multiValued = false)
+ @Completion(VirtualNetworkCompleter.class)
+ Long networkId = null;
+
+ @Argument(index = 1, name = "deviceId", description = "Device ID",
+ required = true, multiValued = false)
+ @Completion(VirtualDeviceCompleter.class)
+ String deviceId = null;
+
+ @Argument(index = 2, name = "portNum", description = "Device port number",
+ required = true, multiValued = false)
+ @Completion(VirtualPortCompleter.class)
+ Integer portNum = null;
+
+ @Override
+ protected void doExecute() {
+ VirtualNetworkAdminService service = get(VirtualNetworkAdminService.class);
+ service.removeVirtualPort(NetworkId.networkId(networkId), DeviceId.deviceId(deviceId),
+ PortNumber.portNumber(portNum));
+ print("Virtual port successfully removed.");
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortStateCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortStateCommand.java
new file mode 100644
index 0000000..56af2ba
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortStateCommand.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.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.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.VirtualPort;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Administratively enables or disables state of an existing virtual port.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-port-state",
+ description = "Administratively enables or disables state of an existing virtual port.")
+public class VirtualPortStateCommand extends AbstractShellCommand {
+ @Argument(index = 0, name = "networkId", description = "Network ID",
+ required = true, multiValued = false)
+ @Completion(VirtualNetworkCompleter.class)
+ Long networkId = null;
+
+ @Argument(index = 1, name = "deviceId", description = "Virtual Device ID",
+ required = true, multiValued = false)
+ @Completion(VirtualDeviceCompleter.class)
+ String deviceId = null;
+
+ @Argument(index = 2, name = "portNum", description = "Virtual device port number",
+ required = true, multiValued = false)
+ @Completion(VirtualPortCompleter.class)
+ Integer portNum = null;
+
+ @Argument(index = 3, name = "portState",
+ description = "Desired State. Either \"enable\" or \"disable\".",
+ required = true, multiValued = false)
+ String portState = null;
+
+ @Override
+ protected void doExecute() {
+ VirtualNetworkAdminService service = get(VirtualNetworkAdminService.class);
+
+ VirtualPort vPort = getVirtualPort(PortNumber.portNumber(portNum));
+ checkNotNull(vPort, "The virtual Port does not exist");
+
+ boolean isEnabled;
+ if ("enable".equals(portState)) {
+ isEnabled = true;
+ } else if ("disable".equals(portState)) {
+ isEnabled = false;
+ } else {
+ print("State must be enable or disable");
+ return;
+ }
+
+ service.updatePortState(NetworkId.networkId(networkId),
+ DeviceId.deviceId(deviceId), vPort.number(), isEnabled);
+ print("Virtual port state updated.");
+ }
+
+ /**
+ * Returns the virtual port matching the device and port identifier.
+ *
+ * @param aPortNumber port identifier
+ * @return matching virtual port, or null.
+ */
+ private VirtualPort getVirtualPort(PortNumber aPortNumber) {
+ VirtualNetworkService service = get(VirtualNetworkService.class);
+ Set<VirtualPort> ports = service.getVirtualPorts(NetworkId.networkId(networkId),
+ DeviceId.deviceId(deviceId));
+ return ports.stream().filter(p -> p.number().equals(aPortNumber))
+ .findFirst().get();
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/package-info.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/package-info.java
new file mode 100644
index 0000000..049ee4f
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-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.
+ */
+
+/**
+ * CLI commands for querying and administering virtual networks.
+ */
+package org.onosproject.incubator.net.virtual.cli;
\ No newline at end of file
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkDeviceManager.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkDeviceManager.java
new file mode 100644
index 0000000..21ba3b9
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkDeviceManager.java
@@ -0,0 +1,230 @@
+/*
+ * 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.incubator.net.virtual.impl;
+
+import com.google.common.collect.ImmutableList;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualDevice;
+import org.onosproject.incubator.net.virtual.VirtualNetworkEvent;
+import org.onosproject.incubator.net.virtual.VirtualNetworkListener;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.VirtualPort;
+import org.onosproject.incubator.net.virtual.event.AbstractVirtualListenerManager;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceEvent.Type;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.device.PortStatistics;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Device service implementation built on the virtual network service.
+ */
+public class VirtualNetworkDeviceManager
+ extends AbstractVirtualListenerManager<DeviceEvent, DeviceListener>
+ implements DeviceService {
+
+ private static final String TYPE_NULL = "Type cannot be null";
+ private static final String DEVICE_NULL = "Device cannot be null";
+ private static final String PORT_NUMBER_NULL = "PortNumber cannot be null";
+ private VirtualNetworkListener virtualNetworkListener = new InternalVirtualNetworkListener();
+
+ /**
+ * Creates a new VirtualNetworkDeviceService object.
+ *
+ * @param virtualNetworkManager virtual network manager service
+ * @param networkId a virtual network identifier
+ */
+ public VirtualNetworkDeviceManager(VirtualNetworkService virtualNetworkManager,
+ NetworkId networkId) {
+ super(virtualNetworkManager, networkId, DeviceEvent.class);
+ manager.addListener(virtualNetworkListener);
+ }
+
+ @Override
+ public int getDeviceCount() {
+ return manager.getVirtualDevices(this.networkId).size();
+ }
+
+ @Override
+ public Iterable<Device> getDevices() {
+ return manager.getVirtualDevices(
+ this.networkId).stream().collect(Collectors.toSet());
+ }
+
+ @Override
+ public Iterable<Device> getDevices(Device.Type type) {
+ checkNotNull(type, TYPE_NULL);
+ return manager.getVirtualDevices(this.networkId)
+ .stream()
+ .filter(device -> type.equals(device.type()))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public Iterable<Device> getAvailableDevices() {
+ return getDevices();
+ }
+
+ @Override
+ public Iterable<Device> getAvailableDevices(Device.Type type) {
+ return getDevices(type);
+ }
+
+ @Override
+ public Device getDevice(DeviceId deviceId) {
+ checkNotNull(deviceId, DEVICE_NULL);
+ Optional<VirtualDevice> foundDevice =
+ manager.getVirtualDevices(this.networkId)
+ .stream()
+ .filter(device -> deviceId.equals(device.id()))
+ .findFirst();
+ if (foundDevice.isPresent()) {
+ return foundDevice.get();
+ }
+ return null;
+ }
+
+ @Override
+ public MastershipRole getRole(DeviceId deviceId) {
+ checkNotNull(deviceId, DEVICE_NULL);
+ // TODO hard coded to master for now.
+ return MastershipRole.MASTER;
+ }
+
+ @Override
+ public List<Port> getPorts(DeviceId deviceId) {
+ checkNotNull(deviceId, DEVICE_NULL);
+ return manager.getVirtualPorts(this.networkId, deviceId)
+ .stream()
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public List<PortStatistics> getPortStatistics(DeviceId deviceId) {
+ checkNotNull(deviceId, DEVICE_NULL);
+ // TODO not supported at the moment.
+ return ImmutableList.of();
+ }
+
+ @Override
+ public List<PortStatistics> getPortDeltaStatistics(DeviceId deviceId) {
+ checkNotNull(deviceId, DEVICE_NULL);
+ // TODO not supported at the moment.
+ return ImmutableList.of();
+ }
+
+ @Override
+ public PortStatistics getStatisticsForPort(DeviceId deviceId,
+ PortNumber portNumber) {
+ checkNotNull(deviceId, DEVICE_NULL);
+ checkNotNull(deviceId, PORT_NUMBER_NULL);
+ // TODO not supported at the moment.
+ return null;
+ }
+
+ @Override
+ public PortStatistics getDeltaStatisticsForPort(DeviceId deviceId,
+ PortNumber portNumber) {
+ checkNotNull(deviceId, DEVICE_NULL);
+ checkNotNull(deviceId, PORT_NUMBER_NULL);
+ // TODO not supported at the moment.
+ return null;
+ }
+
+ @Override
+ public Port getPort(DeviceId deviceId, PortNumber portNumber) {
+ checkNotNull(deviceId, DEVICE_NULL);
+
+ Optional<VirtualPort> foundPort =
+ manager.getVirtualPorts(this.networkId, deviceId)
+ .stream()
+ .filter(port -> port.number().equals(portNumber))
+ .findFirst();
+ if (foundPort.isPresent()) {
+ return foundPort.get();
+ }
+ return null;
+ }
+
+ @Override
+ public boolean isAvailable(DeviceId deviceId) {
+ return getDevice(deviceId) != null;
+ }
+
+ @Override
+ public String localStatus(DeviceId deviceId) {
+ // TODO not supported at this time
+ return null;
+ }
+
+ @Override
+ public long getLastUpdatedInstant(DeviceId deviceId) {
+ // TODO not supported at this time
+ return 0;
+ }
+
+ /**
+ * Translates VirtualNetworkEvent to DeviceEvent.
+ */
+ private class InternalVirtualNetworkListener implements VirtualNetworkListener {
+ @Override
+ public boolean isRelevant(VirtualNetworkEvent event) {
+ return networkId().equals(event.subject());
+ }
+
+ @Override
+ public void event(VirtualNetworkEvent event) {
+ switch (event.type()) {
+ case VIRTUAL_DEVICE_ADDED:
+ post(new DeviceEvent(Type.DEVICE_ADDED, event.virtualDevice()));
+ break;
+ case VIRTUAL_DEVICE_UPDATED:
+ post(new DeviceEvent(Type.DEVICE_UPDATED, event.virtualDevice()));
+ break;
+ case VIRTUAL_DEVICE_REMOVED:
+ post(new DeviceEvent(Type.DEVICE_REMOVED, event.virtualDevice()));
+ break;
+ case VIRTUAL_PORT_ADDED:
+ post(new DeviceEvent(Type.PORT_ADDED, event.virtualDevice(), event.virtualPort()));
+ break;
+ case VIRTUAL_PORT_UPDATED:
+ post(new DeviceEvent(Type.PORT_UPDATED, event.virtualDevice(), event.virtualPort()));
+ break;
+ case VIRTUAL_PORT_REMOVED:
+ post(new DeviceEvent(Type.PORT_REMOVED, event.virtualDevice(), event.virtualPort()));
+ break;
+ case NETWORK_UPDATED:
+ case NETWORK_REMOVED:
+ case NETWORK_ADDED:
+ default:
+ // do nothing
+ break;
+ }
+ }
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowObjectiveManager.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowObjectiveManager.java
new file mode 100644
index 0000000..c4e2031
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowObjectiveManager.java
@@ -0,0 +1,716 @@
+/*
+ * 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.incubator.net.virtual.impl;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.RemovalCause;
+import com.google.common.cache.RemovalNotification;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.incubator.net.virtual.AbstractVnetService;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkFlowObjectiveStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.behaviour.NextGroup;
+import org.onosproject.net.behaviour.Pipeliner;
+import org.onosproject.net.behaviour.PipelinerContext;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.driver.AbstractHandlerBehaviour;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleOperations;
+import org.onosproject.net.flow.FlowRuleOperationsContext;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flowobjective.FilteringObjective;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.FlowObjectiveStore;
+import org.onosproject.net.flowobjective.FlowObjectiveStoreDelegate;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.flowobjective.NextObjective;
+import org.onosproject.net.flowobjective.Objective;
+import org.onosproject.net.flowobjective.ObjectiveError;
+import org.onosproject.net.flowobjective.ObjectiveEvent;
+import org.onosproject.net.group.DefaultGroupKey;
+import org.onosproject.net.group.GroupKey;
+import org.slf4j.Logger;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.BoundedThreadPool.newFixedThreadPool;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Provides implementation of the flow objective programming service for virtual networks.
+ */
+// NOTE: This manager is designed to provide flow objective programming service
+// for virtual networks. Actually, virtual networks don't need to consider
+// the different implementation of data-path pipeline. But, the interfaces
+// and usages of flow objective service are still valuable for virtual network.
+// This manager is working as an interpreter from FlowObjective to FlowRules
+// to provide symmetric interfaces with ONOS core services.
+// The behaviours are based on DefaultSingleTablePipeline.
+
+public class VirtualNetworkFlowObjectiveManager extends AbstractVnetService
+ implements FlowObjectiveService {
+
+ public static final int INSTALL_RETRY_ATTEMPTS = 5;
+ public static final long INSTALL_RETRY_INTERVAL = 1000; // ms
+
+ private final Logger log = getLogger(getClass());
+
+ protected DeviceService deviceService;
+
+ // Note: The following dependencies are added on behalf of the pipeline
+ // driver behaviours to assure these services are available for their
+ // initialization.
+ protected FlowRuleService flowRuleService;
+
+ protected VirtualNetworkFlowObjectiveStore virtualFlowObjectiveStore;
+ protected FlowObjectiveStore flowObjectiveStore;
+ private final FlowObjectiveStoreDelegate delegate;
+
+ private final PipelinerContext context = new InnerPipelineContext();
+
+ private final Map<DeviceId, Pipeliner> pipeliners = Maps.newConcurrentMap();
+
+ // local stores for queuing fwd and next objectives that are waiting for an
+ // associated next objective execution to complete. The signal for completed
+ // execution comes from a pipeline driver, in this or another controller
+ // instance, via the DistributedFlowObjectiveStore.
+ private final Map<Integer, Set<PendingFlowObjective>> pendingForwards =
+ Maps.newConcurrentMap();
+ private final Map<Integer, Set<PendingFlowObjective>> pendingNexts =
+ Maps.newConcurrentMap();
+
+ // local store to track which nextObjectives were sent to which device
+ // for debugging purposes
+ private Map<Integer, DeviceId> nextToDevice = Maps.newConcurrentMap();
+
+ private ExecutorService executorService;
+
+ public VirtualNetworkFlowObjectiveManager(VirtualNetworkService manager,
+ NetworkId networkId) {
+ super(manager, networkId);
+
+ deviceService = manager.get(networkId(), DeviceService.class);
+ flowRuleService = manager.get(networkId(), FlowRuleService.class);
+
+ executorService = newFixedThreadPool(4, groupedThreads("onos/virtual/objective-installer", "%d", log));
+
+ virtualFlowObjectiveStore =
+ serviceDirectory.get(VirtualNetworkFlowObjectiveStore.class);
+ delegate = new InternalStoreDelegate();
+ virtualFlowObjectiveStore.setDelegate(networkId(), delegate);
+ flowObjectiveStore = new StoreConvertor();
+ }
+
+ @Override
+ public void filter(DeviceId deviceId, FilteringObjective filteringObjective) {
+ executorService.execute(new ObjectiveInstaller(deviceId, filteringObjective));
+ }
+
+ @Override
+ public void forward(DeviceId deviceId, ForwardingObjective forwardingObjective) {
+ if (forwardingObjective.nextId() == null ||
+ forwardingObjective.op() == Objective.Operation.REMOVE ||
+ flowObjectiveStore.getNextGroup(forwardingObjective.nextId()) != null ||
+ !queueFwdObjective(deviceId, forwardingObjective)) {
+ // fast path
+ executorService.execute(new ObjectiveInstaller(deviceId, forwardingObjective));
+ }
+ }
+
+ @Override
+ public void next(DeviceId deviceId, NextObjective nextObjective) {
+ nextToDevice.put(nextObjective.id(), deviceId);
+ if (nextObjective.op() == Objective.Operation.ADD ||
+ flowObjectiveStore.getNextGroup(nextObjective.id()) != null ||
+ !queueNextObjective(deviceId, nextObjective)) {
+ // either group exists or we are trying to create it - let it through
+ executorService.execute(new ObjectiveInstaller(deviceId, nextObjective));
+ }
+ }
+
+ @Override
+ public int allocateNextId() {
+ return flowObjectiveStore.allocateNextId();
+ }
+
+ @Override
+ public void initPolicy(String policy) {
+
+ }
+
+ @Override
+ public List<String> getNextMappings() {
+ List<String> mappings = new ArrayList<>();
+ Map<Integer, NextGroup> allnexts = flowObjectiveStore.getAllGroups();
+ // XXX if the NextGroup after de-serialization actually stored info of the deviceId
+ // then info on any nextObj could be retrieved from one controller instance.
+ // Right now the drivers on one instance can only fetch for next-ids that came
+ // to them.
+ // Also, we still need to send the right next-id to the right driver as potentially
+ // there can be different drivers for different devices. But on that account,
+ // no instance should be decoding for another instance's nextIds.
+
+ for (Map.Entry<Integer, NextGroup> e : allnexts.entrySet()) {
+ // get the device this next Objective was sent to
+ DeviceId deviceId = nextToDevice.get(e.getKey());
+ mappings.add("NextId " + e.getKey() + ": " +
+ ((deviceId != null) ? deviceId : "nextId not in this onos instance"));
+ if (deviceId != null) {
+ // this instance of the controller sent the nextObj to a driver
+ Pipeliner pipeliner = getDevicePipeliner(deviceId);
+ List<String> nextMappings = pipeliner.getNextMappings(e.getValue());
+ if (nextMappings != null) {
+ mappings.addAll(nextMappings);
+ }
+ }
+ }
+ return mappings;
+ }
+
+ @Override
+ public List<String> getPendingFlowObjectives() {
+ List<String> pendingFlowObjectives = new ArrayList<>();
+
+ for (Integer nextId : pendingForwards.keySet()) {
+ Set<PendingFlowObjective> pfwd = pendingForwards.get(nextId);
+ StringBuilder pend = new StringBuilder();
+ pend.append("NextId: ")
+ .append(nextId);
+ for (PendingFlowObjective pf : pfwd) {
+ pend.append("\n FwdId: ")
+ .append(String.format("%11s", pf.flowObjective().id()))
+ .append(", DeviceId: ")
+ .append(pf.deviceId())
+ .append(", Selector: ")
+ .append(((ForwardingObjective) pf.flowObjective())
+ .selector().criteria());
+ }
+ pendingFlowObjectives.add(pend.toString());
+ }
+
+ for (Integer nextId : pendingNexts.keySet()) {
+ Set<PendingFlowObjective> pnext = pendingNexts.get(nextId);
+ StringBuilder pend = new StringBuilder();
+ pend.append("NextId: ")
+ .append(nextId);
+ for (PendingFlowObjective pn : pnext) {
+ pend.append("\n NextOp: ")
+ .append(pn.flowObjective().op())
+ .append(", DeviceId: ")
+ .append(pn.deviceId())
+ .append(", Treatments: ")
+ .append(((NextObjective) pn.flowObjective())
+ .next());
+ }
+ pendingFlowObjectives.add(pend.toString());
+ }
+
+ return pendingFlowObjectives;
+ }
+
+ private boolean queueFwdObjective(DeviceId deviceId, ForwardingObjective fwd) {
+ boolean queued = false;
+ synchronized (pendingForwards) {
+ // double check the flow objective store, because this block could run
+ // after a notification arrives
+ if (flowObjectiveStore.getNextGroup(fwd.nextId()) == null) {
+ pendingForwards.compute(fwd.nextId(), (id, pending) -> {
+ PendingFlowObjective pendfo = new PendingFlowObjective(deviceId, fwd);
+ if (pending == null) {
+ return Sets.newHashSet(pendfo);
+ } else {
+ pending.add(pendfo);
+ return pending;
+ }
+ });
+ queued = true;
+ }
+ }
+ if (queued) {
+ log.debug("Queued forwarding objective {} for nextId {} meant for device {}",
+ fwd.id(), fwd.nextId(), deviceId);
+ }
+ return queued;
+ }
+
+ private boolean queueNextObjective(DeviceId deviceId, NextObjective next) {
+
+ // we need to hold off on other operations till we get notified that the
+ // initial group creation has succeeded
+ boolean queued = false;
+ synchronized (pendingNexts) {
+ // double check the flow objective store, because this block could run
+ // after a notification arrives
+ if (flowObjectiveStore.getNextGroup(next.id()) == null) {
+ pendingNexts.compute(next.id(), (id, pending) -> {
+ PendingFlowObjective pendfo = new PendingFlowObjective(deviceId, next);
+ if (pending == null) {
+ return Sets.newHashSet(pendfo);
+ } else {
+ pending.add(pendfo);
+ return pending;
+ }
+ });
+ queued = true;
+ }
+ }
+ if (queued) {
+ log.debug("Queued next objective {} with operation {} meant for device {}",
+ next.id(), next.op(), deviceId);
+ }
+ return queued;
+ }
+
+ /**
+ * Task that passes the flow objective down to the driver. The task will
+ * make a few attempts to find the appropriate driver, then eventually give
+ * up and report an error if no suitable driver could be found.
+ */
+ private class ObjectiveInstaller implements Runnable {
+ private final DeviceId deviceId;
+ private final Objective objective;
+
+ private final int numAttempts;
+
+ public ObjectiveInstaller(DeviceId deviceId, Objective objective) {
+ this(deviceId, objective, 1);
+ }
+
+ public ObjectiveInstaller(DeviceId deviceId, Objective objective, int attemps) {
+ this.deviceId = checkNotNull(deviceId);
+ this.objective = checkNotNull(objective);
+ this.numAttempts = attemps;
+ }
+
+ @Override
+ public void run() {
+ try {
+ Pipeliner pipeliner = getDevicePipeliner(deviceId);
+
+ if (pipeliner != null) {
+ if (objective instanceof NextObjective) {
+ nextToDevice.put(objective.id(), deviceId);
+ pipeliner.next((NextObjective) objective);
+ } else if (objective instanceof ForwardingObjective) {
+ pipeliner.forward((ForwardingObjective) objective);
+ } else {
+ pipeliner.filter((FilteringObjective) objective);
+ }
+ //Attempts to check if pipeliner is null for retry attempts
+ } else if (numAttempts < INSTALL_RETRY_ATTEMPTS) {
+ Thread.sleep(INSTALL_RETRY_INTERVAL);
+ executorService.execute(new ObjectiveInstaller(deviceId, objective, numAttempts + 1));
+ } else {
+ // Otherwise we've tried a few times and failed, report an
+ // error back to the user.
+ objective.context().ifPresent(
+ c -> c.onError(objective, ObjectiveError.NOPIPELINER));
+ }
+ //Exception thrown
+ } catch (Exception e) {
+ log.warn("Exception while installing flow objective", e);
+ }
+ }
+ }
+
+ private class InternalStoreDelegate implements FlowObjectiveStoreDelegate {
+ @Override
+ public void notify(ObjectiveEvent event) {
+ if (event.type() == ObjectiveEvent.Type.ADD) {
+ log.debug("Received notification of obj event {}", event);
+ Set<PendingFlowObjective> pending;
+
+ // first send all pending flows
+ synchronized (pendingForwards) {
+ // needs to be synchronized for queueObjective lookup
+ pending = pendingForwards.remove(event.subject());
+ }
+ if (pending == null) {
+ log.debug("No forwarding objectives pending for this "
+ + "obj event {}", event);
+ } else {
+ log.debug("Processing {} pending forwarding objectives for nextId {}",
+ pending.size(), event.subject());
+ pending.forEach(p -> getDevicePipeliner(p.deviceId())
+ .forward((ForwardingObjective) p.flowObjective()));
+ }
+
+ // now check for pending next-objectives
+ synchronized (pendingNexts) {
+ // needs to be synchronized for queueObjective lookup
+ pending = pendingNexts.remove(event.subject());
+ }
+ if (pending == null) {
+ log.debug("No next objectives pending for this "
+ + "obj event {}", event);
+ } else {
+ log.debug("Processing {} pending next objectives for nextId {}",
+ pending.size(), event.subject());
+ pending.forEach(p -> getDevicePipeliner(p.deviceId())
+ .next((NextObjective) p.flowObjective()));
+ }
+ }
+ }
+ }
+
+ /**
+ * Retrieves (if it exists) the device pipeline behaviour from the cache.
+ * Otherwise it warms the caches and triggers the init method of the Pipeline.
+ * For virtual network, it returns OVS pipeliner.
+ *
+ * @param deviceId the id of the device associated to the pipeline
+ * @return the implementation of the Pipeliner behaviour
+ */
+ private Pipeliner getDevicePipeliner(DeviceId deviceId) {
+ return pipeliners.computeIfAbsent(deviceId, this::initPipelineHandler);
+ }
+
+ /**
+ * Creates and initialize {@link Pipeliner}.
+ * <p>
+ * Note: Expected to be called under per-Device lock.
+ * e.g., {@code pipeliners}' Map#compute family methods
+ *
+ * @param deviceId Device to initialize pipeliner
+ * @return {@link Pipeliner} instance or null
+ */
+ private Pipeliner initPipelineHandler(DeviceId deviceId) {
+ //FIXME: do we need a standard pipeline for virtual device?
+ Pipeliner pipeliner = new DefaultVirtualDevicePipeline();
+ pipeliner.init(deviceId, context);
+ return pipeliner;
+ }
+
+ // Processing context for initializing pipeline driver behaviours.
+ private class InnerPipelineContext implements PipelinerContext {
+ @Override
+ public ServiceDirectory directory() {
+ return serviceDirectory;
+ }
+
+ @Override
+ public FlowObjectiveStore store() {
+ return flowObjectiveStore;
+ }
+ }
+
+ /**
+ * Data class used to hold a pending flow objective that could not
+ * be processed because the associated next object was not present.
+ * Note that this pending flow objective could be a forwarding objective
+ * waiting for a next objective to complete execution. Or it could a
+ * next objective (with a different operation - remove, addToExisting, or
+ * removeFromExisting) waiting for a next objective with the same id to
+ * complete execution.
+ */
+ private class PendingFlowObjective {
+ private final DeviceId deviceId;
+ private final Objective flowObj;
+
+ public PendingFlowObjective(DeviceId deviceId, Objective flowObj) {
+ this.deviceId = deviceId;
+ this.flowObj = flowObj;
+ }
+
+ public DeviceId deviceId() {
+ return deviceId;
+ }
+
+ public Objective flowObjective() {
+ return flowObj;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(deviceId, flowObj);
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof PendingFlowObjective)) {
+ return false;
+ }
+ final PendingFlowObjective other = (PendingFlowObjective) obj;
+ if (this.deviceId.equals(other.deviceId) &&
+ this.flowObj.equals(other.flowObj)) {
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * This class is a wrapping class from VirtualNetworkFlowObjectiveStore
+ * to FlowObjectiveStore for PipelinerContext.
+ */
+ private class StoreConvertor implements FlowObjectiveStore {
+
+ @Override
+ public void setDelegate(FlowObjectiveStoreDelegate delegate) {
+ virtualFlowObjectiveStore.setDelegate(networkId(), delegate);
+ }
+
+ @Override
+ public void unsetDelegate(FlowObjectiveStoreDelegate delegate) {
+ virtualFlowObjectiveStore.unsetDelegate(networkId(), delegate);
+ }
+
+ @Override
+ public boolean hasDelegate() {
+ return virtualFlowObjectiveStore.hasDelegate(networkId());
+ }
+
+ @Override
+ public void putNextGroup(Integer nextId, NextGroup group) {
+ virtualFlowObjectiveStore.putNextGroup(networkId(), nextId, group);
+ }
+
+ @Override
+ public NextGroup getNextGroup(Integer nextId) {
+ return virtualFlowObjectiveStore.getNextGroup(networkId(), nextId);
+ }
+
+ @Override
+ public NextGroup removeNextGroup(Integer nextId) {
+ return virtualFlowObjectiveStore.removeNextGroup(networkId(), nextId);
+ }
+
+ @Override
+ public Map<Integer, NextGroup> getAllGroups() {
+ return virtualFlowObjectiveStore.getAllGroups(networkId());
+ }
+
+ @Override
+ public int allocateNextId() {
+ return virtualFlowObjectiveStore.allocateNextId(networkId());
+ }
+ }
+
+ /**
+ * Simple single table pipeline abstraction for virtual networks.
+ */
+ private class DefaultVirtualDevicePipeline
+ extends AbstractHandlerBehaviour implements Pipeliner {
+
+ private final Logger log = getLogger(getClass());
+
+ private DeviceId deviceId;
+
+ private Cache<Integer, NextObjective> pendingNext;
+
+ private KryoNamespace appKryo = new KryoNamespace.Builder()
+ .register(GroupKey.class)
+ .register(DefaultGroupKey.class)
+ .register(SingleGroup.class)
+ .register(byte[].class)
+ .build("DefaultVirtualDevicePipeline");
+
+ @Override
+ public void init(DeviceId deviceId, PipelinerContext context) {
+ this.deviceId = deviceId;
+
+ pendingNext = CacheBuilder.newBuilder()
+ .expireAfterWrite(20, TimeUnit.SECONDS)
+ .removalListener((RemovalNotification<Integer, NextObjective> notification) -> {
+ if (notification.getCause() == RemovalCause.EXPIRED) {
+ notification.getValue().context()
+ .ifPresent(c -> c.onError(notification.getValue(),
+ ObjectiveError.FLOWINSTALLATIONFAILED));
+ }
+ }).build();
+ }
+
+ @Override
+ public void filter(FilteringObjective filter) {
+
+ TrafficTreatment.Builder actions;
+ switch (filter.type()) {
+ case PERMIT:
+ actions = (filter.meta() == null) ?
+ DefaultTrafficTreatment.builder().punt() :
+ DefaultTrafficTreatment.builder(filter.meta());
+ break;
+ case DENY:
+ actions = (filter.meta() == null) ?
+ DefaultTrafficTreatment.builder() :
+ DefaultTrafficTreatment.builder(filter.meta());
+ actions.drop();
+ break;
+ default:
+ log.warn("Unknown filter type: {}", filter.type());
+ actions = DefaultTrafficTreatment.builder().drop();
+ }
+
+ TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+
+ filter.conditions().forEach(selector::add);
+
+ if (filter.key() != null) {
+ selector.add(filter.key());
+ }
+
+ FlowRule.Builder ruleBuilder = DefaultFlowRule.builder()
+ .forDevice(deviceId)
+ .withSelector(selector.build())
+ .withTreatment(actions.build())
+ .fromApp(filter.appId())
+ .withPriority(filter.priority());
+
+ if (filter.permanent()) {
+ ruleBuilder.makePermanent();
+ } else {
+ ruleBuilder.makeTemporary(filter.timeout());
+ }
+
+ installObjective(ruleBuilder, filter);
+ }
+
+ @Override
+ public void forward(ForwardingObjective fwd) {
+ TrafficSelector selector = fwd.selector();
+
+ if (fwd.treatment() != null) {
+ // Deal with SPECIFIC and VERSATILE in the same manner.
+ FlowRule.Builder ruleBuilder = DefaultFlowRule.builder()
+ .forDevice(deviceId)
+ .withSelector(selector)
+ .fromApp(fwd.appId())
+ .withPriority(fwd.priority())
+ .withTreatment(fwd.treatment());
+
+ if (fwd.permanent()) {
+ ruleBuilder.makePermanent();
+ } else {
+ ruleBuilder.makeTemporary(fwd.timeout());
+ }
+ installObjective(ruleBuilder, fwd);
+
+ } else {
+ NextObjective nextObjective = pendingNext.getIfPresent(fwd.nextId());
+ if (nextObjective != null) {
+ pendingNext.invalidate(fwd.nextId());
+ nextObjective.next().forEach(treat -> {
+ FlowRule.Builder ruleBuilder = DefaultFlowRule.builder()
+ .forDevice(deviceId)
+ .withSelector(selector)
+ .fromApp(fwd.appId())
+ .withPriority(fwd.priority())
+ .withTreatment(treat);
+
+ if (fwd.permanent()) {
+ ruleBuilder.makePermanent();
+ } else {
+ ruleBuilder.makeTemporary(fwd.timeout());
+ }
+ installObjective(ruleBuilder, fwd);
+ });
+ } else {
+ fwd.context().ifPresent(c -> c.onError(fwd,
+ ObjectiveError.GROUPMISSING));
+ }
+ }
+ }
+
+ private void installObjective(FlowRule.Builder ruleBuilder, Objective objective) {
+ FlowRuleOperations.Builder flowBuilder = FlowRuleOperations.builder();
+ switch (objective.op()) {
+
+ case ADD:
+ flowBuilder.add(ruleBuilder.build());
+ break;
+ case REMOVE:
+ flowBuilder.remove(ruleBuilder.build());
+ break;
+ default:
+ log.warn("Unknown operation {}", objective.op());
+ }
+
+ flowRuleService.apply(flowBuilder.build(new FlowRuleOperationsContext() {
+ @Override
+ public void onSuccess(FlowRuleOperations ops) {
+ objective.context().ifPresent(context -> context.onSuccess(objective));
+ }
+
+ @Override
+ public void onError(FlowRuleOperations ops) {
+ objective.context()
+ .ifPresent(context ->
+ context.onError(objective,
+ ObjectiveError.FLOWINSTALLATIONFAILED));
+ }
+ }));
+ }
+
+ @Override
+ public void next(NextObjective nextObjective) {
+
+ pendingNext.put(nextObjective.id(), nextObjective);
+ flowObjectiveStore.putNextGroup(nextObjective.id(),
+ new SingleGroup(
+ new DefaultGroupKey(
+ appKryo.serialize(nextObjective.id()))));
+ nextObjective.context().ifPresent(context -> context.onSuccess(nextObjective));
+ }
+
+ @Override
+ public List<String> getNextMappings(NextGroup nextGroup) {
+ // Default single table pipeline does not use nextObjectives or groups
+ return null;
+ }
+
+ private class SingleGroup implements NextGroup {
+
+ private final GroupKey key;
+
+ public SingleGroup(GroupKey key) {
+ this.key = key;
+ }
+
+ public GroupKey key() {
+ return key;
+ }
+
+ @Override
+ public byte[] data() {
+ return appKryo.serialize(key);
+ }
+ }
+ }
+
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowRuleManager.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowRuleManager.java
new file mode 100644
index 0000000..f501696
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowRuleManager.java
@@ -0,0 +1,574 @@
+/*
+ * 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.incubator.net.virtual.impl;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkFlowRuleStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.event.AbstractVirtualListenerManager;
+import org.onosproject.incubator.net.virtual.provider.AbstractVirtualProviderService;
+import org.onosproject.incubator.net.virtual.provider.VirtualFlowRuleProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualFlowRuleProviderService;
+import org.onosproject.incubator.net.virtual.provider.VirtualProviderRegistryService;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.CompletedBatchOperation;
+import org.onosproject.net.flow.DefaultFlowEntry;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEvent;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchRequest;
+import org.onosproject.net.flow.FlowRuleEvent;
+import org.onosproject.net.flow.FlowRuleListener;
+import org.onosproject.net.flow.FlowRuleOperation;
+import org.onosproject.net.flow.FlowRuleOperations;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.FlowRuleStoreDelegate;
+import org.onosproject.net.flow.TableStatisticsEntry;
+import org.onosproject.net.provider.ProviderId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_ADD_REQUESTED;
+import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_REMOVE_REQUESTED;
+
+/**
+ * Flow rule service implementation built on the virtual network service.
+ */
+public class VirtualNetworkFlowRuleManager
+ extends AbstractVirtualListenerManager<FlowRuleEvent, FlowRuleListener>
+ implements FlowRuleService {
+
+ private static final String VIRTUAL_FLOW_OP_TOPIC = "virtual-flow-ops-ids";
+ private static final String THREAD_GROUP_NAME = "onos/virtual-flowservice";
+ private static final String DEVICE_INSTALLER_PATTERN = "device-installer-%d";
+ private static final String OPERATION_PATTERN = "operations-%d";
+ public static final String FLOW_RULE_NULL = "FlowRule cannot be null";
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private final VirtualNetworkFlowRuleStore store;
+ private final DeviceService deviceService;
+
+ protected ExecutorService deviceInstallers =
+ Executors.newFixedThreadPool(32,
+ groupedThreads(THREAD_GROUP_NAME,
+ DEVICE_INSTALLER_PATTERN, log));
+ protected ExecutorService operationsService =
+ Executors.newFixedThreadPool(32,
+ groupedThreads(THREAD_GROUP_NAME,
+ OPERATION_PATTERN, log));
+ private IdGenerator idGenerator;
+
+ private final Map<Long, FlowOperationsProcessor> pendingFlowOperations = new ConcurrentHashMap<>();
+
+ private VirtualProviderRegistryService providerRegistryService = null;
+ private InternalFlowRuleProviderService innerProviderService = null;
+
+ private final FlowRuleStoreDelegate storeDelegate;
+
+ /**
+ * Creates a new VirtualNetworkFlowRuleService object.
+ *
+ * @param virtualNetworkManager virtual network manager service
+ * @param networkId a virtual network identifier
+ */
+ public VirtualNetworkFlowRuleManager(VirtualNetworkService virtualNetworkManager,
+ NetworkId networkId) {
+ super(virtualNetworkManager, networkId, FlowRuleEvent.class);
+
+ store = serviceDirectory.get(VirtualNetworkFlowRuleStore.class);
+
+ idGenerator = serviceDirectory.get(CoreService.class)
+ .getIdGenerator(VIRTUAL_FLOW_OP_TOPIC + networkId().toString());
+ providerRegistryService =
+ serviceDirectory.get(VirtualProviderRegistryService.class);
+ innerProviderService = new InternalFlowRuleProviderService();
+ providerRegistryService.registerProviderService(networkId(), innerProviderService);
+
+ this.deviceService = manager.get(networkId, DeviceService.class);
+ this.storeDelegate = new InternalStoreDelegate();
+ store.setDelegate(networkId, this.storeDelegate);
+ }
+
+ @Override
+ public int getFlowRuleCount() {
+ return store.getFlowRuleCount(networkId());
+ }
+
+ @Override
+ public Iterable<FlowEntry> getFlowEntries(DeviceId deviceId) {
+ return store.getFlowEntries(networkId(), deviceId);
+ }
+
+ @Override
+ public void applyFlowRules(FlowRule... flowRules) {
+ FlowRuleOperations.Builder builder = FlowRuleOperations.builder();
+ for (FlowRule flowRule : flowRules) {
+ builder.add(flowRule);
+ }
+ apply(builder.build());
+ }
+
+ @Override
+ public void purgeFlowRules(DeviceId deviceId) {
+ store.purgeFlowRule(networkId(), deviceId);
+ }
+
+ @Override
+ public void removeFlowRules(FlowRule... flowRules) {
+ FlowRuleOperations.Builder builder = FlowRuleOperations.builder();
+ for (FlowRule flowRule : flowRules) {
+ builder.remove(flowRule);
+ }
+ apply(builder.build());
+ }
+
+ @Override
+ public void removeFlowRulesById(ApplicationId id) {
+ removeFlowRules(Iterables.toArray(getFlowRulesById(id), FlowRule.class));
+ }
+
+ @Override
+ public Iterable<FlowRule> getFlowRulesById(ApplicationId id) {
+ DeviceService deviceService = manager.get(networkId(), DeviceService.class);
+
+ Set<FlowRule> flowEntries = Sets.newHashSet();
+ for (Device d : deviceService.getDevices()) {
+ for (FlowEntry flowEntry : store.getFlowEntries(networkId(), d.id())) {
+ if (flowEntry.appId() == id.id()) {
+ flowEntries.add(flowEntry);
+ }
+ }
+ }
+ return flowEntries;
+ }
+
+ @Override
+ public Iterable<FlowEntry> getFlowEntriesById(ApplicationId id) {
+ DeviceService deviceService = manager.get(networkId(), DeviceService.class);
+
+ Set<FlowEntry> flowEntries = Sets.newHashSet();
+ for (Device d : deviceService.getDevices()) {
+ for (FlowEntry flowEntry : store.getFlowEntries(networkId(), d.id())) {
+ if (flowEntry.appId() == id.id()) {
+ flowEntries.add(flowEntry);
+ }
+ }
+ }
+ return flowEntries;
+ }
+
+ @Override
+ public Iterable<FlowRule> getFlowRulesByGroupId(ApplicationId appId, short groupId) {
+ DeviceService deviceService = manager.get(networkId(), DeviceService.class);
+
+ Set<FlowRule> matches = Sets.newHashSet();
+ long toLookUp = ((long) appId.id() << 16) | groupId;
+ for (Device d : deviceService.getDevices()) {
+ for (FlowEntry flowEntry : store.getFlowEntries(networkId(), d.id())) {
+ if ((flowEntry.id().value() >>> 32) == toLookUp) {
+ matches.add(flowEntry);
+ }
+ }
+ }
+ return matches;
+ }
+
+ @Override
+ public void apply(FlowRuleOperations ops) {
+ operationsService.execute(new FlowOperationsProcessor(ops));
+ }
+
+ @Override
+ public Iterable<TableStatisticsEntry> getFlowTableStatistics(DeviceId deviceId) {
+ return store.getTableStatistics(networkId(), deviceId);
+ }
+
+ private static FlowRuleBatchEntry.FlowRuleOperation mapOperationType(FlowRuleOperation.Type input) {
+ switch (input) {
+ case ADD:
+ return FlowRuleBatchEntry.FlowRuleOperation.ADD;
+ case MODIFY:
+ return FlowRuleBatchEntry.FlowRuleOperation.MODIFY;
+ case REMOVE:
+ return FlowRuleBatchEntry.FlowRuleOperation.REMOVE;
+ default:
+ throw new UnsupportedOperationException("Unknown flow rule type " + input);
+ }
+ }
+
+ private class FlowOperationsProcessor implements Runnable {
+ // Immutable
+ private final FlowRuleOperations fops;
+
+ // Mutable
+ private final List<Set<FlowRuleOperation>> stages;
+ private final Set<DeviceId> pendingDevices = new HashSet<>();
+ private boolean hasFailed = false;
+
+ FlowOperationsProcessor(FlowRuleOperations ops) {
+ this.stages = Lists.newArrayList(ops.stages());
+ this.fops = ops;
+ }
+
+ @Override
+ public synchronized void run() {
+ if (!stages.isEmpty()) {
+ process(stages.remove(0));
+ } else if (!hasFailed) {
+ fops.callback().onSuccess(fops);
+ }
+ }
+
+ private void process(Set<FlowRuleOperation> ops) {
+ Multimap<DeviceId, FlowRuleBatchEntry> perDeviceBatches = ArrayListMultimap.create();
+
+ for (FlowRuleOperation op : ops) {
+ perDeviceBatches.put(op.rule().deviceId(),
+ new FlowRuleBatchEntry(mapOperationType(op.type()), op.rule()));
+ }
+ pendingDevices.addAll(perDeviceBatches.keySet());
+
+ for (DeviceId deviceId : perDeviceBatches.keySet()) {
+ long id = idGenerator.getNewId();
+ final FlowRuleBatchOperation b = new FlowRuleBatchOperation(perDeviceBatches.get(deviceId),
+ deviceId, id);
+ pendingFlowOperations.put(id, this);
+ deviceInstallers.execute(() -> store.storeBatch(networkId(), b));
+ }
+ }
+
+ synchronized void satisfy(DeviceId devId) {
+ pendingDevices.remove(devId);
+ if (pendingDevices.isEmpty()) {
+ operationsService.execute(this);
+ }
+ }
+
+ synchronized void fail(DeviceId devId, Set<? extends FlowRule> failures) {
+ hasFailed = true;
+ pendingDevices.remove(devId);
+ if (pendingDevices.isEmpty()) {
+ operationsService.execute(this);
+ }
+
+ FlowRuleOperations.Builder failedOpsBuilder = FlowRuleOperations.builder();
+ failures.forEach(failedOpsBuilder::add);
+
+ fops.callback().onError(failedOpsBuilder.build());
+ }
+ }
+
+ private final class InternalFlowRuleProviderService
+ extends AbstractVirtualProviderService<VirtualFlowRuleProvider>
+ implements VirtualFlowRuleProviderService {
+
+ final Map<FlowEntry, Long> firstSeen = Maps.newConcurrentMap();
+ final Map<FlowEntry, Long> lastSeen = Maps.newConcurrentMap();
+
+ private InternalFlowRuleProviderService() {
+ //TODO: find a proper virtual provider.
+ Set<ProviderId> providerIds =
+ providerRegistryService.getProvidersByService(this);
+ ProviderId providerId = providerIds.stream().findFirst().get();
+ VirtualFlowRuleProvider provider = (VirtualFlowRuleProvider)
+ providerRegistryService.getProvider(providerId);
+ setProvider(provider);
+ }
+
+ @Override
+ public void flowRemoved(FlowEntry flowEntry) {
+ checkNotNull(flowEntry, FLOW_RULE_NULL);
+ checkValidity();
+
+ lastSeen.remove(flowEntry);
+ firstSeen.remove(flowEntry);
+ FlowEntry stored = store.getFlowEntry(networkId(), flowEntry);
+ if (stored == null) {
+ log.debug("Rule already evicted from store: {}", flowEntry);
+ return;
+ }
+ if (flowEntry.reason() == FlowEntry.FlowRemoveReason.HARD_TIMEOUT) {
+ ((DefaultFlowEntry) stored).setState(FlowEntry.FlowEntryState.REMOVED);
+ }
+
+ //FIXME: obtains provider from devices providerId()
+ FlowRuleEvent event = null;
+ switch (stored.state()) {
+ case ADDED:
+ case PENDING_ADD:
+ provider().applyFlowRule(networkId(), stored);
+ break;
+ case PENDING_REMOVE:
+ case REMOVED:
+ event = store.removeFlowRule(networkId(), stored);
+ break;
+ default:
+ break;
+
+ }
+ if (event != null) {
+ log.debug("Flow {} removed", flowEntry);
+ post(event);
+ }
+ }
+
+ private void flowMissing(FlowEntry flowRule) {
+ checkNotNull(flowRule, FLOW_RULE_NULL);
+ checkValidity();
+
+ FlowRuleEvent event = null;
+ switch (flowRule.state()) {
+ case PENDING_REMOVE:
+ case REMOVED:
+ event = store.removeFlowRule(networkId(), flowRule);
+ break;
+ case ADDED:
+ case PENDING_ADD:
+ event = store.pendingFlowRule(networkId(), flowRule);
+
+ try {
+ provider().applyFlowRule(networkId(), flowRule);
+ } catch (UnsupportedOperationException e) {
+ log.warn(e.getMessage());
+ if (flowRule instanceof DefaultFlowEntry) {
+ //FIXME modification of "stored" flow entry outside of store
+ ((DefaultFlowEntry) flowRule).setState(FlowEntry.FlowEntryState.FAILED);
+ }
+ }
+ break;
+ default:
+ log.debug("Flow {} has not been installed.", flowRule);
+ }
+
+ if (event != null) {
+ log.debug("Flow {} removed", flowRule);
+ post(event);
+ }
+ }
+
+ private void extraneousFlow(FlowRule flowRule) {
+ checkNotNull(flowRule, FLOW_RULE_NULL);
+ checkValidity();
+
+ provider().removeFlowRule(networkId(), flowRule);
+ log.debug("Flow {} is on switch but not in store.", flowRule);
+ }
+
+ private void flowAdded(FlowEntry flowEntry) {
+ checkNotNull(flowEntry, FLOW_RULE_NULL);
+
+ if (checkRuleLiveness(flowEntry, store.getFlowEntry(networkId(), flowEntry))) {
+ FlowRuleEvent event = store.addOrUpdateFlowRule(networkId(), flowEntry);
+ if (event == null) {
+ log.debug("No flow store event generated.");
+ } else {
+ log.trace("Flow {} {}", flowEntry, event.type());
+ post(event);
+ }
+ } else {
+ log.debug("Removing flow rules....");
+ removeFlowRules(flowEntry);
+ }
+ }
+
+ private boolean checkRuleLiveness(FlowEntry swRule, FlowEntry storedRule) {
+ if (storedRule == null) {
+ return false;
+ }
+ if (storedRule.isPermanent()) {
+ return true;
+ }
+
+ final long timeout = storedRule.timeout() * 1000L;
+ final long currentTime = System.currentTimeMillis();
+
+ // Checking flow with hardTimeout
+ if (storedRule.hardTimeout() != 0) {
+ if (!firstSeen.containsKey(storedRule)) {
+ // First time rule adding
+ firstSeen.put(storedRule, currentTime);
+ } else {
+ Long first = firstSeen.get(storedRule);
+ final long hardTimeout = storedRule.hardTimeout() * 1000L;
+ if ((currentTime - first) > hardTimeout) {
+ return false;
+ }
+ }
+ }
+
+ if (storedRule.packets() != swRule.packets()) {
+ lastSeen.put(storedRule, currentTime);
+ return true;
+ }
+ if (!lastSeen.containsKey(storedRule)) {
+ // checking for the first time
+ lastSeen.put(storedRule, storedRule.lastSeen());
+ // Use following if lastSeen attr. was removed.
+ //lastSeen.put(storedRule, currentTime);
+ }
+ Long last = lastSeen.get(storedRule);
+
+ // concurrently removed? let the liveness check fail
+ return last != null && (currentTime - last) <= timeout;
+ }
+
+ @Override
+ public void pushFlowMetrics(DeviceId deviceId, Iterable<FlowEntry> flowEntries) {
+ pushFlowMetricsInternal(deviceId, flowEntries, true);
+ }
+
+ @Override
+ public void pushFlowMetricsWithoutFlowMissing(DeviceId deviceId, Iterable<FlowEntry> flowEntries) {
+ pushFlowMetricsInternal(deviceId, flowEntries, false);
+ }
+
+ private void pushFlowMetricsInternal(DeviceId deviceId, Iterable<FlowEntry> flowEntries,
+ boolean useMissingFlow) {
+ Map<FlowEntry, FlowEntry> storedRules = Maps.newHashMap();
+ store.getFlowEntries(networkId(), deviceId).forEach(f -> storedRules.put(f, f));
+
+ for (FlowEntry rule : flowEntries) {
+ try {
+ FlowEntry storedRule = storedRules.remove(rule);
+ if (storedRule != null) {
+ if (storedRule.id().equals(rule.id())) {
+ // we both have the rule, let's update some info then.
+ flowAdded(rule);
+ } else {
+ // the two rules are not an exact match - remove the
+ // switch's rule and install our rule
+ extraneousFlow(rule);
+ flowMissing(storedRule);
+ }
+ }
+ } catch (Exception e) {
+ log.debug("Can't process added or extra rule {}", e.getMessage());
+ }
+ }
+
+ // DO NOT reinstall
+ if (useMissingFlow) {
+ for (FlowEntry rule : storedRules.keySet()) {
+ try {
+ // there are rules in the store that aren't on the switch
+ log.debug("Adding rule in store, but not on switch {}", rule);
+ flowMissing(rule);
+ } catch (Exception e) {
+ log.debug("Can't add missing flow rule:", e);
+ }
+ }
+ }
+ }
+
+ public void batchOperationCompleted(long batchId, CompletedBatchOperation operation) {
+ store.batchOperationComplete(networkId(), FlowRuleBatchEvent.completed(
+ new FlowRuleBatchRequest(batchId, Collections.emptySet()),
+ operation
+ ));
+ }
+
+ @Override
+ public void pushTableStatistics(DeviceId deviceId,
+ List<TableStatisticsEntry> tableStats) {
+ store.updateTableStatistics(networkId(), deviceId, tableStats);
+ }
+ }
+
+ // Store delegate to re-post events emitted from the store.
+ private class InternalStoreDelegate implements FlowRuleStoreDelegate {
+
+ // TODO: Right now we only dispatch events at individual flowEntry level.
+ // It may be more efficient for also dispatch events as a batch.
+ @Override
+ public void notify(FlowRuleBatchEvent event) {
+ final FlowRuleBatchRequest request = event.subject();
+ switch (event.type()) {
+ case BATCH_OPERATION_REQUESTED:
+ // Request has been forwarded to MASTER Node, and was
+ request.ops().forEach(
+ op -> {
+ switch (op.operator()) {
+ case ADD:
+ post(new FlowRuleEvent(RULE_ADD_REQUESTED, op.target()));
+ break;
+ case REMOVE:
+ post(new FlowRuleEvent(RULE_REMOVE_REQUESTED, op.target()));
+ break;
+ case MODIFY:
+ //TODO: do something here when the time comes.
+ break;
+ default:
+ log.warn("Unknown flow operation operator: {}", op.operator());
+ }
+ }
+ );
+
+ DeviceId deviceId = event.deviceId();
+ FlowRuleBatchOperation batchOperation = request.asBatchOperation(deviceId);
+
+ VirtualFlowRuleProvider provider = innerProviderService.provider();
+ if (provider != null) {
+ provider.executeBatch(networkId, batchOperation);
+ }
+
+ break;
+
+ case BATCH_OPERATION_COMPLETED:
+ FlowOperationsProcessor fops = pendingFlowOperations.remove(
+ event.subject().batchId());
+ if (fops == null) {
+ return;
+ }
+
+ if (event.result().isSuccess()) {
+ fops.satisfy(event.deviceId());
+ } else {
+ fops.fail(event.deviceId(), event.result().failedItems());
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkGroupManager.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkGroupManager.java
new file mode 100644
index 0000000..5fed4a8
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkGroupManager.java
@@ -0,0 +1,273 @@
+/*
+ * 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.incubator.net.virtual.impl;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkGroupStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.event.AbstractVirtualListenerManager;
+import org.onosproject.incubator.net.virtual.provider.AbstractVirtualProviderService;
+import org.onosproject.incubator.net.virtual.provider.VirtualGroupProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualGroupProviderService;
+import org.onosproject.incubator.net.virtual.provider.VirtualProviderRegistryService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.group.GroupEvent;
+import org.onosproject.net.group.GroupKey;
+import org.onosproject.net.group.GroupListener;
+import org.onosproject.net.group.GroupOperation;
+import org.onosproject.net.group.GroupOperations;
+import org.onosproject.net.group.GroupService;
+import org.onosproject.net.group.GroupStoreDelegate;
+import org.onosproject.net.provider.ProviderId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Group service implementation built on the virtual network service.
+ */
+public class VirtualNetworkGroupManager
+ extends AbstractVirtualListenerManager<GroupEvent, GroupListener>
+ implements GroupService {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private final VirtualNetworkGroupStore store;
+
+ private VirtualProviderRegistryService providerRegistryService = null;
+ private VirtualGroupProviderService innerProviderService;
+ private InternalStoreDelegate storeDelegate;
+ private DeviceService deviceService;
+
+ //TODO: make this configurable
+ private boolean purgeOnDisconnection = false;
+
+ public VirtualNetworkGroupManager(VirtualNetworkService manager, NetworkId networkId) {
+ super(manager, networkId, GroupEvent.class);
+
+ store = serviceDirectory.get(VirtualNetworkGroupStore.class);
+ deviceService = manager.get(networkId, DeviceService.class);
+
+ providerRegistryService =
+ serviceDirectory.get(VirtualProviderRegistryService.class);
+ innerProviderService = new InternalGroupProviderService();
+ providerRegistryService.registerProviderService(networkId(), innerProviderService);
+
+ this.storeDelegate = new InternalStoreDelegate();
+ store.setDelegate(networkId, this.storeDelegate);
+
+ log.info("Started");
+ }
+
+ @Override
+ public void addGroup(GroupDescription groupDesc) {
+ store.storeGroupDescription(networkId(), groupDesc);
+ }
+
+ @Override
+ public Group getGroup(DeviceId deviceId, GroupKey appCookie) {
+ return store.getGroup(networkId(), deviceId, appCookie);
+ }
+
+ @Override
+ public void addBucketsToGroup(DeviceId deviceId, GroupKey oldCookie, GroupBuckets buckets,
+ GroupKey newCookie, ApplicationId appId) {
+ store.updateGroupDescription(networkId(),
+ deviceId,
+ oldCookie,
+ VirtualNetworkGroupStore.UpdateType.ADD,
+ buckets,
+ newCookie);
+ }
+
+ @Override
+ public void removeBucketsFromGroup(DeviceId deviceId, GroupKey oldCookie,
+ GroupBuckets buckets, GroupKey newCookie,
+ ApplicationId appId) {
+ store.updateGroupDescription(networkId(),
+ deviceId,
+ oldCookie,
+ VirtualNetworkGroupStore.UpdateType.REMOVE,
+ buckets,
+ newCookie);
+
+ }
+
+ @Override
+ public void setBucketsForGroup(DeviceId deviceId,
+ GroupKey oldCookie,
+ GroupBuckets buckets,
+ GroupKey newCookie,
+ ApplicationId appId) {
+ store.updateGroupDescription(networkId(),
+ deviceId,
+ oldCookie,
+ VirtualNetworkGroupStore.UpdateType.SET,
+ buckets,
+ newCookie);
+ }
+
+ @Override
+ public void purgeGroupEntries(DeviceId deviceId) {
+ store.purgeGroupEntry(networkId(), deviceId);
+ }
+
+ @Override
+ public void purgeGroupEntries() {
+ store.purgeGroupEntries(networkId());
+ }
+
+ @Override
+ public void removeGroup(DeviceId deviceId, GroupKey appCookie, ApplicationId appId) {
+ store.deleteGroupDescription(networkId(), deviceId, appCookie);
+ }
+
+ @Override
+ public Iterable<Group> getGroups(DeviceId deviceId, ApplicationId appId) {
+ return store.getGroups(networkId(), deviceId);
+ }
+
+ @Override
+ public Iterable<Group> getGroups(DeviceId deviceId) {
+ return store.getGroups(networkId(), deviceId);
+ }
+
+ private class InternalGroupProviderService
+ extends AbstractVirtualProviderService<VirtualGroupProvider>
+ implements VirtualGroupProviderService {
+
+ protected InternalGroupProviderService() {
+ Set<ProviderId> providerIds =
+ providerRegistryService.getProvidersByService(this);
+ ProviderId providerId = providerIds.stream().findFirst().get();
+ VirtualGroupProvider provider = (VirtualGroupProvider)
+ providerRegistryService.getProvider(providerId);
+ setProvider(provider);
+ }
+
+ @Override
+ public void groupOperationFailed(DeviceId deviceId,
+ GroupOperation operation) {
+ store.groupOperationFailed(networkId(), deviceId, operation);
+ }
+
+ @Override
+ public void pushGroupMetrics(DeviceId deviceId, Collection<Group> groupEntries) {
+ log.trace("Received group metrics from device {}", deviceId);
+ checkValidity();
+ store.pushGroupMetrics(networkId(), deviceId, groupEntries);
+ }
+
+ @Override
+ public void notifyOfFailovers(Collection<Group> failoverGroups) {
+ store.notifyOfFailovers(networkId(), failoverGroups);
+ }
+ }
+
+ private class InternalStoreDelegate implements GroupStoreDelegate {
+ @Override
+ public void notify(GroupEvent event) {
+ final Group group = event.subject();
+ VirtualGroupProvider groupProvider = innerProviderService.provider();
+ GroupOperations groupOps = null;
+ switch (event.type()) {
+ case GROUP_ADD_REQUESTED:
+ log.debug("GROUP_ADD_REQUESTED for Group {} on device {}",
+ group.id(), group.deviceId());
+ GroupOperation groupAddOp = GroupOperation.
+ createAddGroupOperation(group.id(),
+ group.type(),
+ group.buckets());
+ groupOps = new GroupOperations(
+ Collections.singletonList(groupAddOp));
+ groupProvider.performGroupOperation(networkId(), group.deviceId(),
+ groupOps);
+ break;
+
+ case GROUP_UPDATE_REQUESTED:
+ log.debug("GROUP_UPDATE_REQUESTED for Group {} on device {}",
+ group.id(), group.deviceId());
+ GroupOperation groupModifyOp = GroupOperation.
+ createModifyGroupOperation(group.id(),
+ group.type(),
+ group.buckets());
+ groupOps = new GroupOperations(
+ Collections.singletonList(groupModifyOp));
+ groupProvider.performGroupOperation(networkId(), group.deviceId(),
+ groupOps);
+ break;
+
+ case GROUP_REMOVE_REQUESTED:
+ log.debug("GROUP_REMOVE_REQUESTED for Group {} on device {}",
+ group.id(), group.deviceId());
+ GroupOperation groupDeleteOp = GroupOperation.
+ createDeleteGroupOperation(group.id(),
+ group.type());
+ groupOps = new GroupOperations(
+ Collections.singletonList(groupDeleteOp));
+ groupProvider.performGroupOperation(networkId(), group.deviceId(),
+ groupOps);
+ break;
+
+ case GROUP_ADDED:
+ case GROUP_UPDATED:
+ case GROUP_REMOVED:
+ case GROUP_ADD_FAILED:
+ case GROUP_UPDATE_FAILED:
+ case GROUP_REMOVE_FAILED:
+ case GROUP_BUCKET_FAILOVER:
+ post(event);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ private class InternalDeviceListener implements DeviceListener {
+ @Override
+ public void event(DeviceEvent event) {
+ switch (event.type()) {
+ case DEVICE_REMOVED:
+ case DEVICE_AVAILABILITY_CHANGED:
+ DeviceId deviceId = event.subject().id();
+ if (!deviceService.isAvailable(deviceId)) {
+ log.debug("Device {} became un available; clearing initial audit status",
+ event.type(), event.subject().id());
+ store.deviceInitialAuditCompleted(networkId(), event.subject().id(), false);
+
+ if (purgeOnDisconnection) {
+ store.purgeGroupEntry(networkId(), deviceId);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkHostManager.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkHostManager.java
new file mode 100644
index 0000000..5d14d4a
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkHostManager.java
@@ -0,0 +1,153 @@
+/*
+ * 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.incubator.net.virtual.impl;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualHost;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.event.AbstractVirtualListenerManager;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostService;
+
+import java.util.Collection;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Host service implementation built on the virtual network service.
+ */
+public class VirtualNetworkHostManager
+ extends AbstractVirtualListenerManager<HostEvent, HostListener>
+ implements HostService {
+
+ private static final String HOST_NULL = "Host ID cannot be null";
+
+ /**
+ * Creates a new virtual network host service object.
+ *
+ * @param virtualNetworkManager virtual network manager service
+ * @param networkId a virtual network identifier
+ */
+ public VirtualNetworkHostManager(VirtualNetworkService virtualNetworkManager,
+ NetworkId networkId) {
+ super(virtualNetworkManager, networkId, HostEvent.class);
+ }
+
+
+ @Override
+ public int getHostCount() {
+ return manager.getVirtualHosts(this.networkId()).size();
+ }
+
+ @Override
+ public Iterable<Host> getHosts() {
+ return getHostsColl();
+ }
+
+ @Override
+ public Host getHost(HostId hostId) {
+ checkNotNull(hostId, HOST_NULL);
+ Optional<VirtualHost> foundHost =
+ manager.getVirtualHosts(this.networkId())
+ .stream()
+ .filter(host -> hostId.equals(host.id()))
+ .findFirst();
+ if (foundHost.isPresent()) {
+ return foundHost.get();
+ }
+ return null;
+ }
+
+ /**
+ * Gets a collection of virtual hosts.
+ *
+ * @return collection of virtual hosts.
+ */
+ private Collection<Host> getHostsColl() {
+ return manager.getVirtualHosts(this.networkId())
+ .stream().collect(Collectors.toSet());
+ }
+
+ /**
+ * Filters specified collection.
+ *
+ * @param collection collection of hosts to filter
+ * @param predicate condition to filter on
+ * @return collection of virtual hosts that satisfy the filter condition
+ */
+ private Set<Host> filter(Collection<Host> collection, Predicate<Host> predicate) {
+ return collection.stream().filter(predicate).collect(Collectors.toSet());
+ }
+
+ @Override
+ public Set<Host> getHostsByVlan(VlanId vlanId) {
+ checkNotNull(vlanId, "VLAN identifier cannot be null");
+ return filter(getHostsColl(), host -> Objects.equals(host.vlan(), vlanId));
+ }
+
+ @Override
+ public Set<Host> getHostsByMac(MacAddress mac) {
+ checkNotNull(mac, "MAC address cannot be null");
+ return filter(getHostsColl(), host -> Objects.equals(host.mac(), mac));
+ }
+
+ @Override
+ public Set<Host> getHostsByIp(IpAddress ip) {
+ checkNotNull(ip, "IP address cannot be null");
+ return filter(getHostsColl(), host -> host.ipAddresses().contains(ip));
+ }
+
+ @Override
+ public Set<Host> getConnectedHosts(ConnectPoint connectPoint) {
+ checkNotNull(connectPoint, "Connect point cannot be null");
+ return filter(getHostsColl(), host -> host.location().equals(connectPoint));
+ }
+
+ @Override
+ public Set<Host> getConnectedHosts(DeviceId deviceId) {
+ checkNotNull(deviceId, "Device identifier cannot be null");
+ return filter(getHostsColl(), host -> host.location().deviceId().equals(deviceId));
+ }
+
+ @Override
+ public void startMonitoringIp(IpAddress ip) {
+ //TODO check what needs to be done here
+ }
+
+ @Override
+ public void stopMonitoringIp(IpAddress ip) {
+ //TODO check what needs to be done here
+ }
+
+ @Override
+ public void requestMac(IpAddress ip) {
+ //TODO check what needs to be done here
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkIntentManager.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkIntentManager.java
new file mode 100644
index 0000000..f4c1137
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkIntentManager.java
@@ -0,0 +1,412 @@
+/*
+ * 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.incubator.net.virtual.impl;
+
+import org.onlab.util.Tools;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkIntent;
+import org.onosproject.incubator.net.virtual.VirtualNetworkIntentStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.VirtualNetworkStore;
+import org.onosproject.incubator.net.virtual.VirtualPort;
+import org.onosproject.incubator.net.virtual.VnetService;
+import org.onosproject.incubator.net.virtual.event.AbstractVirtualListenerManager;
+import org.onosproject.incubator.net.virtual.impl.intent.phase.VirtualFinalIntentProcessPhase;
+import org.onosproject.incubator.net.virtual.impl.intent.VirtualIntentInstallCoordinator;
+import org.onosproject.incubator.net.virtual.impl.intent.VirtualIntentAccumulator;
+import org.onosproject.incubator.net.virtual.impl.intent.VirtualIntentCompilerRegistry;
+import org.onosproject.incubator.net.virtual.impl.intent.VirtualIntentInstallerRegistry;
+import org.onosproject.incubator.net.virtual.impl.intent.phase.VirtualIntentProcessPhase;
+import org.onosproject.incubator.net.virtual.impl.intent.VirtualIntentProcessor;
+import org.onosproject.incubator.net.virtual.impl.intent.VirtualIntentSkipped;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.group.GroupService;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentBatchDelegate;
+import org.onosproject.net.intent.IntentData;
+import org.onosproject.net.intent.IntentEvent;
+import org.onosproject.net.intent.IntentListener;
+import org.onosproject.net.intent.IntentStoreDelegate;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.intent.IntentState;
+import org.onosproject.net.intent.Key;
+import org.onosproject.net.resource.ResourceConsumer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.*;
+import static org.onlab.util.BoundedThreadPool.newFixedThreadPool;
+import static org.onlab.util.BoundedThreadPool.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.incubator.net.virtual.impl.intent.phase.VirtualIntentProcessPhase.newInitialPhase;
+import static org.onosproject.net.intent.IntentState.FAILED;
+
+/**
+ * Intent service implementation built on the virtual network service.
+ */
+public class VirtualNetworkIntentManager
+ extends AbstractVirtualListenerManager<IntentEvent, IntentListener>
+ implements IntentService, VnetService {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private static final int DEFAULT_NUM_THREADS = 12;
+ private int numThreads = DEFAULT_NUM_THREADS;
+
+ private static final String NETWORK_ID_NULL = "Network ID cannot be null";
+ private static final String DEVICE_NULL = "Device cannot be null";
+ private static final String INTENT_NULL = "Intent cannot be null";
+ private static final String KEY_NULL = "Key cannot be null";
+ private static final String APP_ID_NULL = "Intent app identifier cannot be null";
+ private static final String INTENT_KEY_NULL = "Intent key cannot be null";
+ private static final String CP_NULL = "Connect Point cannot be null";
+
+ //FIXME: Tracker service for vnet.
+
+ //ONOS core services
+ protected VirtualNetworkStore virtualNetworkStore;
+ protected VirtualNetworkIntentStore intentStore;
+
+ //Virtual network services
+ protected GroupService groupService;
+
+ private final IntentBatchDelegate batchDelegate = new InternalBatchDelegate();
+ private final InternalIntentProcessor processor = new InternalIntentProcessor();
+ private final IntentStoreDelegate delegate = new InternalStoreDelegate();
+ private final VirtualIntentCompilerRegistry compilerRegistry =
+ VirtualIntentCompilerRegistry.getInstance();
+ private final VirtualIntentInstallerRegistry installerRegistry =
+ VirtualIntentInstallerRegistry.getInstance();
+ private final VirtualIntentAccumulator accumulator =
+ new VirtualIntentAccumulator(batchDelegate);
+
+ private VirtualIntentInstallCoordinator installCoordinator;
+ private ExecutorService batchExecutor;
+ private ExecutorService workerExecutor;
+
+ /**
+ * Creates a new VirtualNetworkIntentService object.
+ *
+ * @param virtualNetworkManager virtual network manager service
+ * @param networkId a virtual network identifier
+ */
+ public VirtualNetworkIntentManager(VirtualNetworkService virtualNetworkManager,
+ NetworkId networkId) {
+
+ super(virtualNetworkManager, networkId, IntentEvent.class);
+
+ this.virtualNetworkStore = serviceDirectory.get(VirtualNetworkStore.class);
+ this.intentStore = serviceDirectory.get(VirtualNetworkIntentStore.class);
+
+ this.groupService = manager.get(networkId, GroupService.class);
+
+ intentStore.setDelegate(networkId, delegate);
+ batchExecutor = newSingleThreadExecutor(groupedThreads("onos/intent", "batch", log));
+ workerExecutor = newFixedThreadPool(numThreads, groupedThreads("onos/intent", "worker-%d", log));
+
+ installCoordinator = new VirtualIntentInstallCoordinator(networkId, installerRegistry, intentStore);
+ log.info("Started");
+
+ }
+
+ @Override
+ public void submit(Intent intent) {
+ checkNotNull(intent, INTENT_NULL);
+ checkState(intent instanceof VirtualNetworkIntent, "Only VirtualNetworkIntent is supported.");
+ checkArgument(validateIntent((VirtualNetworkIntent) intent), "Invalid Intent");
+
+ IntentData data = IntentData.submit(intent);
+ intentStore.addPending(networkId, data);
+ }
+
+ /**
+ * Returns true if the virtual network intent is valid.
+ *
+ * @param intent virtual network intent
+ * @return true if intent is valid
+ */
+ private boolean validateIntent(VirtualNetworkIntent intent) {
+ checkNotNull(intent, INTENT_NULL);
+ checkNotNull(intent.networkId(), NETWORK_ID_NULL);
+ checkNotNull(intent.appId(), APP_ID_NULL);
+ checkNotNull(intent.key(), INTENT_KEY_NULL);
+ ConnectPoint ingressPoint = intent.ingressPoint();
+ ConnectPoint egressPoint = intent.egressPoint();
+
+ return (validateConnectPoint(ingressPoint) && validateConnectPoint(egressPoint));
+ }
+
+ /**
+ * Returns true if the connect point is valid.
+ *
+ * @param connectPoint connect point
+ * @return true if connect point is valid
+ */
+ private boolean validateConnectPoint(ConnectPoint connectPoint) {
+ checkNotNull(connectPoint, CP_NULL);
+ Port port = getPort(connectPoint.deviceId(), connectPoint.port());
+ return port != null;
+ }
+
+ /**
+ * Returns the virtual port for the given device identifier and port number.
+ *
+ * @param deviceId virtual device identifier
+ * @param portNumber virtual port number
+ * @return virtual port
+ */
+ private Port getPort(DeviceId deviceId, PortNumber portNumber) {
+ checkNotNull(deviceId, DEVICE_NULL);
+
+ Optional<VirtualPort> foundPort = manager.getVirtualPorts(this.networkId(), deviceId)
+ .stream()
+ .filter(port -> port.number().equals(portNumber))
+ .findFirst();
+ if (foundPort.isPresent()) {
+ return foundPort.get();
+ }
+ return null;
+ }
+
+ @Override
+ public void withdraw(Intent intent) {
+ checkNotNull(intent, INTENT_NULL);
+ IntentData data = IntentData.withdraw(intent);
+ intentStore.addPending(networkId, data);
+ }
+
+ @Override
+ public void purge(Intent intent) {
+ checkNotNull(intent, INTENT_NULL);
+
+ IntentData data = IntentData.purge(intent);
+ intentStore.addPending(networkId, data);
+
+ // remove associated group if there is one
+ // FIXME: Remove P2P intent for vnets
+ }
+
+ @Override
+ public Intent getIntent(Key key) {
+ checkNotNull(key, KEY_NULL);
+ return intentStore.getIntent(networkId, key);
+ }
+
+ @Override
+ public Iterable<Intent> getIntents() {
+ return intentStore.getIntents(networkId);
+ }
+
+ @Override
+ public void addPending(IntentData intentData) {
+ checkNotNull(intentData, INTENT_NULL);
+ //TODO we might consider further checking / assertions
+ intentStore.addPending(networkId, intentData);
+ }
+
+ @Override
+ public Iterable<IntentData> getIntentData() {
+ return intentStore.getIntentData(networkId, false, 0);
+ }
+
+ @Override
+ public long getIntentCount() {
+ return intentStore.getIntentCount(networkId);
+ }
+
+ @Override
+ public IntentState getIntentState(Key intentKey) {
+ checkNotNull(intentKey, KEY_NULL);
+ return intentStore.getIntentState(networkId, intentKey);
+ }
+
+ @Override
+ public List<Intent> getInstallableIntents(Key intentKey) {
+ return intentStore.getInstallableIntents(networkId, intentKey);
+ }
+
+ @Override
+ public boolean isLocal(Key intentKey) {
+ return intentStore.isMaster(networkId, intentKey);
+ }
+
+ @Override
+ public Iterable<Intent> getPending() {
+ return intentStore.getPending(networkId);
+ }
+
+ // Store delegate to re-post events emitted from the store.
+ private class InternalStoreDelegate implements IntentStoreDelegate {
+ @Override
+ public void notify(IntentEvent event) {
+ post(event);
+ switch (event.type()) {
+ case WITHDRAWN:
+ //FIXME: release resources
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public void process(IntentData data) {
+ accumulator.add(data);
+ }
+
+ @Override
+ public void onUpdate(IntentData intentData) {
+ //FIXME: track intent
+ }
+
+ private void releaseResources(Intent intent) {
+ // If a resource group is set on the intent, the resource consumer is
+ // set equal to it. Otherwise it's set to the intent key
+ ResourceConsumer resourceConsumer =
+ intent.resourceGroup() != null ? intent.resourceGroup() : intent.key();
+
+ // By default the resource doesn't get released
+ boolean removeResource = false;
+
+ if (intent.resourceGroup() == null) {
+ // If the intent doesn't have a resource group, it means the
+ // resource was registered using the intent key, so it can be
+ // released
+ removeResource = true;
+ } else {
+ // When a resource group is set, we make sure there are no other
+ // intents using the same resource group, before deleting the
+ // related resources.
+ Long remainingIntents =
+ Tools.stream(intentStore.getIntents(networkId))
+ .filter(i -> {
+ return i.resourceGroup() != null
+ && i.resourceGroup().equals(intent.resourceGroup());
+ })
+ .count();
+ if (remainingIntents == 0) {
+ removeResource = true;
+ }
+ }
+
+ if (removeResource) {
+ // Release resources allocated to withdrawn intent
+ // FIXME: confirm resources are released
+ }
+ }
+ }
+
+ private class InternalBatchDelegate implements IntentBatchDelegate {
+ @Override
+ public void execute(Collection<IntentData> operations) {
+ log.debug("Execute {} operation(s).", operations.size());
+ log.trace("Execute operations: {}", operations);
+
+ // batchExecutor is single-threaded, so only one batch is in flight at a time
+ CompletableFuture.runAsync(() -> {
+ // process intent until the phase reaches one of the final phases
+ List<CompletableFuture<IntentData>> futures = operations.stream()
+ .map(data -> {
+ log.debug("Start processing of {} {}@{}", data.request(), data.key(), data.version());
+ return data;
+ })
+ .map(x -> CompletableFuture.completedFuture(x)
+ .thenApply(VirtualNetworkIntentManager.this::createInitialPhase)
+ .thenApplyAsync(VirtualIntentProcessPhase::process, workerExecutor)
+ .thenApply(VirtualFinalIntentProcessPhase::data)
+ .exceptionally(e -> {
+ // When the future fails, we update the Intent to simulate the failure of
+ // the installation/withdrawal phase and we save in the current map. In
+ // the next round the CleanUp Thread will pick this Intent again.
+ log.warn("Future failed", e);
+ log.warn("Intent {} - state {} - request {}",
+ x.key(), x.state(), x.request());
+ switch (x.state()) {
+ case INSTALL_REQ:
+ case INSTALLING:
+ case WITHDRAW_REQ:
+ case WITHDRAWING:
+ // TODO should we swtich based on current
+ IntentData current = intentStore.getIntentData(networkId, x.key());
+ return IntentData.nextState(current, FAILED);
+ default:
+ return null;
+ }
+ }))
+ .collect(Collectors.toList());
+
+ // write multiple data to store in order
+ intentStore.batchWrite(networkId, Tools.allOf(futures).join().stream()
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList()));
+ }, batchExecutor).exceptionally(e -> {
+ log.error("Error submitting batches:", e);
+ // FIXME incomplete Intents should be cleaned up
+ // (transition to FAILED, etc.)
+
+ // the batch has failed
+ // TODO: maybe we should do more?
+ log.error("Walk the plank, matey...");
+ return null;
+ }).thenRun(accumulator::ready);
+
+ }
+ }
+
+ private VirtualIntentProcessPhase createInitialPhase(IntentData data) {
+ IntentData pending = intentStore.getPendingData(networkId, data.key());
+ if (pending == null || pending.version().isNewerThan(data.version())) {
+ /*
+ If the pending map is null, then this intent was compiled by a
+ previous batch iteration, so we can skip it.
+ If the pending map has a newer request, it will get compiled as
+ part of the next batch, so we can skip it.
+ */
+ return VirtualIntentSkipped.getPhase();
+ }
+ IntentData current = intentStore.getIntentData(networkId, data.key());
+ return newInitialPhase(networkId, processor, data, current);
+ }
+
+ private class InternalIntentProcessor implements VirtualIntentProcessor {
+ @Override
+ public List<Intent> compile(NetworkId networkId,
+ Intent intent,
+ List<Intent> previousInstallables) {
+ return compilerRegistry.compile(networkId, intent, previousInstallables);
+ }
+
+ @Override
+ public void apply(NetworkId networkId,
+ Optional<IntentData> toUninstall,
+ Optional<IntentData> toInstall) {
+
+ installCoordinator.installIntents(toUninstall, toInstall);
+ }
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkLinkManager.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkLinkManager.java
new file mode 100644
index 0000000..1a1c2bf
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkLinkManager.java
@@ -0,0 +1,148 @@
+/*
+ * 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.incubator.net.virtual.impl;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualLink;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.event.AbstractVirtualListenerManager;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.link.LinkEvent;
+import org.onosproject.net.link.LinkListener;
+import org.onosproject.net.link.LinkService;
+
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Link service implementation built on the virtual network service.
+ */
+public class VirtualNetworkLinkManager
+ extends AbstractVirtualListenerManager<LinkEvent, LinkListener>
+ implements LinkService {
+
+ private static final String DEVICE_NULL = "Device cannot be null";
+ private static final String CONNECT_POINT_NULL = "Connect point cannot be null";
+
+ /**
+ * Creates a new VirtualNetworkLinkService object.
+ *
+ * @param virtualNetworkManager virtual network manager service
+ * @param networkId a virtual networkIdentifier
+ */
+ public VirtualNetworkLinkManager(VirtualNetworkService virtualNetworkManager,
+ NetworkId networkId) {
+ super(virtualNetworkManager, networkId, LinkEvent.class);
+ }
+
+ @Override
+ public int getLinkCount() {
+ return manager.getVirtualLinks(this.networkId()).size();
+ }
+
+ @Override
+ public Iterable<Link> getLinks() {
+ return manager.getVirtualLinks(this.networkId())
+ .stream().collect(Collectors.toSet());
+ }
+
+ @Override
+ public Iterable<Link> getActiveLinks() {
+
+ return manager.getVirtualLinks(this.networkId())
+ .stream()
+ .filter(link -> (link.state().equals(Link.State.ACTIVE)))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public Set<Link> getDeviceLinks(DeviceId deviceId) {
+ checkNotNull(deviceId, DEVICE_NULL);
+ return manager.getVirtualLinks(this.networkId())
+ .stream()
+ .filter(link -> (deviceId.equals(link.src().elementId()) ||
+ deviceId.equals(link.dst().elementId())))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public Set<Link> getDeviceEgressLinks(DeviceId deviceId) {
+ checkNotNull(deviceId, DEVICE_NULL);
+ return manager.getVirtualLinks(this.networkId())
+ .stream()
+ .filter(link -> (deviceId.equals(link.dst().elementId())))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public Set<Link> getDeviceIngressLinks(DeviceId deviceId) {
+ checkNotNull(deviceId, DEVICE_NULL);
+ return manager.getVirtualLinks(this.networkId())
+ .stream()
+ .filter(link -> (deviceId.equals(link.src().elementId())))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public Set<Link> getLinks(ConnectPoint connectPoint) {
+ checkNotNull(connectPoint, CONNECT_POINT_NULL);
+ return manager.getVirtualLinks(this.networkId())
+ .stream()
+ .filter(link -> (connectPoint.equals(link.src()) ||
+ connectPoint.equals(link.dst())))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public Set<Link> getEgressLinks(ConnectPoint connectPoint) {
+ checkNotNull(connectPoint, CONNECT_POINT_NULL);
+ return manager.getVirtualLinks(this.networkId())
+ .stream()
+ .filter(link -> (connectPoint.equals(link.dst())))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public Set<Link> getIngressLinks(ConnectPoint connectPoint) {
+ checkNotNull(connectPoint, CONNECT_POINT_NULL);
+ return manager.getVirtualLinks(this.networkId())
+ .stream()
+ .filter(link -> (connectPoint.equals(link.src())))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public Link getLink(ConnectPoint src, ConnectPoint dst) {
+ checkNotNull(src, CONNECT_POINT_NULL);
+ checkNotNull(dst, CONNECT_POINT_NULL);
+ Optional<VirtualLink> foundLink = manager.getVirtualLinks(this.networkId())
+ .stream()
+ .filter(link -> (src.equals(link.src()) &&
+ dst.equals(link.dst())))
+ .findFirst();
+
+ if (foundLink.isPresent()) {
+ return foundLink.get();
+ }
+ return null;
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkManager.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkManager.java
new file mode 100644
index 0000000..3b4ed8c
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkManager.java
@@ -0,0 +1,628 @@
+/*
+ * 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.incubator.net.virtual.impl;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import org.onlab.osgi.DefaultServiceDirectory;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.incubator.net.tunnel.TunnelId;
+import org.onosproject.incubator.net.virtual.DefaultVirtualLink;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualDevice;
+import org.onosproject.incubator.net.virtual.VirtualHost;
+import org.onosproject.incubator.net.virtual.VirtualLink;
+import org.onosproject.incubator.net.virtual.VirtualNetwork;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+import org.onosproject.incubator.net.virtual.VirtualNetworkEvent;
+import org.onosproject.incubator.net.virtual.VirtualNetworkListener;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.VirtualNetworkStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkStoreDelegate;
+import org.onosproject.incubator.net.virtual.VirtualPort;
+import org.onosproject.incubator.net.virtual.VnetService;
+import org.onosproject.incubator.net.virtual.event.VirtualEvent;
+import org.onosproject.incubator.net.virtual.event.VirtualListenerRegistryManager;
+import org.onosproject.incubator.net.virtual.provider.VirtualNetworkProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualNetworkProviderRegistry;
+import org.onosproject.incubator.net.virtual.provider.VirtualNetworkProviderService;
+import org.onosproject.mastership.MastershipAdminService;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.mastership.MastershipTermService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.Link;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.group.GroupService;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.link.LinkService;
+import org.onosproject.net.meter.MeterService;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.net.provider.AbstractListenerProviderRegistry;
+import org.onosproject.net.provider.AbstractProviderService;
+import org.onosproject.net.topology.PathService;
+import org.onosproject.net.topology.TopologyService;
+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.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Implementation of the virtual network service.
+ */
+@Component(service = {
+ VirtualNetworkService.class,
+ VirtualNetworkAdminService.class,
+ VirtualNetworkService.class,
+ VirtualNetworkProviderRegistry.class
+ })
+public class VirtualNetworkManager
+ extends AbstractListenerProviderRegistry<VirtualNetworkEvent,
+ VirtualNetworkListener, VirtualNetworkProvider, VirtualNetworkProviderService>
+ implements VirtualNetworkService, VirtualNetworkAdminService, VirtualNetworkProviderRegistry {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private static final String TENANT_NULL = "Tenant ID cannot be null";
+ private static final String NETWORK_NULL = "Network ID cannot be null";
+ private static final String DEVICE_NULL = "Device ID cannot be null";
+ private static final String LINK_POINT_NULL = "Link end-point cannot be null";
+
+ private static final String VIRTUAL_NETWORK_APP_ID_STRING =
+ "org.onosproject.virtual-network";
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected VirtualNetworkStore store;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected CoreService coreService;
+
+ private VirtualNetworkStoreDelegate delegate = this::post;
+
+ private ServiceDirectory serviceDirectory = new DefaultServiceDirectory();
+ private ApplicationId appId;
+
+ // TODO: figure out how to coordinate "implementation" of a virtual network in a cluster
+
+ /**
+ * Only used for Junit test methods outside of this package.
+ *
+ * @param store virtual network store
+ */
+ public void setStore(VirtualNetworkStore store) {
+ this.store = store;
+ }
+
+ @Activate
+ public void activate() {
+ eventDispatcher.addSink(VirtualNetworkEvent.class, listenerRegistry);
+ eventDispatcher.addSink(VirtualEvent.class,
+ VirtualListenerRegistryManager.getInstance());
+ store.setDelegate(delegate);
+ appId = coreService.registerApplication(VIRTUAL_NETWORK_APP_ID_STRING);
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ store.unsetDelegate(delegate);
+ eventDispatcher.removeSink(VirtualNetworkEvent.class);
+ eventDispatcher.removeSink(VirtualEvent.class);
+ log.info("Stopped");
+ }
+
+ @Override
+ public void registerTenantId(TenantId tenantId) {
+ checkNotNull(tenantId, TENANT_NULL);
+ store.addTenantId(tenantId);
+ }
+
+ @Override
+ public void unregisterTenantId(TenantId tenantId) {
+ checkNotNull(tenantId, TENANT_NULL);
+ store.removeTenantId(tenantId);
+ }
+
+ @Override
+ public Set<TenantId> getTenantIds() {
+ return store.getTenantIds();
+ }
+
+ @Override
+ public VirtualNetwork createVirtualNetwork(TenantId tenantId) {
+ checkNotNull(tenantId, TENANT_NULL);
+ return store.addNetwork(tenantId);
+ }
+
+ @Override
+ public void removeVirtualNetwork(NetworkId networkId) {
+ checkNotNull(networkId, NETWORK_NULL);
+ store.removeNetwork(networkId);
+ }
+
+ @Override
+ public VirtualDevice createVirtualDevice(NetworkId networkId, DeviceId deviceId) {
+ checkNotNull(networkId, NETWORK_NULL);
+ checkNotNull(deviceId, DEVICE_NULL);
+ return store.addDevice(networkId, deviceId);
+ }
+
+ @Override
+ public void removeVirtualDevice(NetworkId networkId, DeviceId deviceId) {
+ checkNotNull(networkId, NETWORK_NULL);
+ checkNotNull(deviceId, DEVICE_NULL);
+ store.removeDevice(networkId, deviceId);
+ }
+
+ @Override
+ public VirtualHost createVirtualHost(NetworkId networkId, HostId hostId,
+ MacAddress mac, VlanId vlan,
+ HostLocation location, Set<IpAddress> ips) {
+ checkNotNull(networkId, NETWORK_NULL);
+ checkNotNull(hostId, DEVICE_NULL);
+ return store.addHost(networkId, hostId, mac, vlan, location, ips);
+ }
+
+ @Override
+ public void removeVirtualHost(NetworkId networkId, HostId hostId) {
+ checkNotNull(networkId, NETWORK_NULL);
+ checkNotNull(hostId, DEVICE_NULL);
+ store.removeHost(networkId, hostId);
+ }
+
+ @Override
+ public VirtualLink createVirtualLink(NetworkId networkId,
+ ConnectPoint src, ConnectPoint dst) {
+ checkNotNull(networkId, NETWORK_NULL);
+ checkNotNull(src, LINK_POINT_NULL);
+ checkNotNull(dst, LINK_POINT_NULL);
+ ConnectPoint physicalSrc = mapVirtualToPhysicalPort(networkId, src);
+ checkNotNull(physicalSrc, LINK_POINT_NULL);
+ ConnectPoint physicalDst = mapVirtualToPhysicalPort(networkId, dst);
+ checkNotNull(physicalDst, LINK_POINT_NULL);
+
+ VirtualNetworkProvider provider = getProvider(DefaultVirtualLink.PID);
+ Link.State state = Link.State.INACTIVE;
+ if (provider != null) {
+ boolean traversable = provider.isTraversable(physicalSrc, physicalDst);
+ state = traversable ? Link.State.ACTIVE : Link.State.INACTIVE;
+ }
+ return store.addLink(networkId, src, dst, state, null);
+ }
+
+ /**
+ * Maps the virtual connect point to a physical connect point.
+ *
+ * @param networkId network identifier
+ * @param virtualCp virtual connect point
+ * @return physical connect point
+ */
+ private ConnectPoint mapVirtualToPhysicalPort(NetworkId networkId,
+ ConnectPoint virtualCp) {
+ Set<VirtualPort> ports = store.getPorts(networkId, virtualCp.deviceId());
+ for (VirtualPort port : ports) {
+ if (port.number().equals(virtualCp.port())) {
+ return new ConnectPoint(port.realizedBy().deviceId(),
+ port.realizedBy().port());
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Maps the physical connect point to a virtual connect point.
+ *
+ * @param networkId network identifier
+ * @param physicalCp physical connect point
+ * @return virtual connect point
+ */
+ private ConnectPoint mapPhysicalToVirtualToPort(NetworkId networkId,
+ ConnectPoint physicalCp) {
+ Set<VirtualPort> ports = store.getPorts(networkId, null);
+ for (VirtualPort port : ports) {
+ if (port.realizedBy().deviceId().equals(physicalCp.elementId()) &&
+ port.realizedBy().port().equals(physicalCp.port())) {
+ return new ConnectPoint(port.element().id(), port.number());
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void removeVirtualLink(NetworkId networkId, ConnectPoint src,
+ ConnectPoint dst) {
+ checkNotNull(networkId, NETWORK_NULL);
+ checkNotNull(src, LINK_POINT_NULL);
+ checkNotNull(dst, LINK_POINT_NULL);
+ store.removeLink(networkId, src, dst);
+ }
+
+ @Override
+ public VirtualPort createVirtualPort(NetworkId networkId, DeviceId deviceId,
+ PortNumber portNumber, ConnectPoint realizedBy) {
+ checkNotNull(networkId, NETWORK_NULL);
+ checkNotNull(deviceId, DEVICE_NULL);
+ checkNotNull(portNumber, "Port description cannot be null");
+ return store.addPort(networkId, deviceId, portNumber, realizedBy);
+ }
+
+ @Override
+ public void bindVirtualPort(NetworkId networkId, DeviceId deviceId,
+ PortNumber portNumber, ConnectPoint realizedBy) {
+ checkNotNull(networkId, NETWORK_NULL);
+ checkNotNull(deviceId, DEVICE_NULL);
+ checkNotNull(portNumber, "Port description cannot be null");
+ checkNotNull(realizedBy, "Physical port description cannot be null");
+
+ store.bindPort(networkId, deviceId, portNumber, realizedBy);
+ }
+
+ @Override
+ public void updatePortState(NetworkId networkId, DeviceId deviceId,
+ PortNumber portNumber, boolean isEnabled) {
+ checkNotNull(networkId, NETWORK_NULL);
+ checkNotNull(deviceId, DEVICE_NULL);
+ checkNotNull(portNumber, "Port description cannot be null");
+
+ store.updatePortState(networkId, deviceId, portNumber, isEnabled);
+ }
+
+ @Override
+ public void removeVirtualPort(NetworkId networkId, DeviceId deviceId,
+ PortNumber portNumber) {
+ checkNotNull(networkId, NETWORK_NULL);
+ checkNotNull(deviceId, DEVICE_NULL);
+ checkNotNull(portNumber, "Port number cannot be null");
+ store.removePort(networkId, deviceId, portNumber);
+ }
+
+ @Override
+ public ServiceDirectory getServiceDirectory() {
+ return serviceDirectory;
+ }
+
+ @Override
+ public Set<VirtualNetwork> getVirtualNetworks(TenantId tenantId) {
+ checkNotNull(tenantId, TENANT_NULL);
+ return store.getNetworks(tenantId);
+ }
+
+ @Override
+ public VirtualNetwork getVirtualNetwork(NetworkId networkId) {
+ checkNotNull(networkId, NETWORK_NULL);
+ return store.getNetwork(networkId);
+ }
+
+ @Override
+ public TenantId getTenantId(NetworkId networkId) {
+ VirtualNetwork virtualNetwork = getVirtualNetwork(networkId);
+ checkNotNull(virtualNetwork, "The network does not exist.");
+ return virtualNetwork.tenantId();
+ }
+
+ @Override
+ public Set<VirtualDevice> getVirtualDevices(NetworkId networkId) {
+ checkNotNull(networkId, NETWORK_NULL);
+ return store.getDevices(networkId);
+ }
+
+ @Override
+ public Set<VirtualHost> getVirtualHosts(NetworkId networkId) {
+ checkNotNull(networkId, NETWORK_NULL);
+ return store.getHosts(networkId);
+ }
+
+ @Override
+ public Set<VirtualLink> getVirtualLinks(NetworkId networkId) {
+ checkNotNull(networkId, NETWORK_NULL);
+ return store.getLinks(networkId);
+ }
+
+ @Override
+ public Set<VirtualPort> getVirtualPorts(NetworkId networkId, DeviceId deviceId) {
+ checkNotNull(networkId, NETWORK_NULL);
+ return store.getPorts(networkId, deviceId);
+ }
+
+ @Override
+ public Set<DeviceId> getPhysicalDevices(NetworkId networkId, DeviceId deviceId) {
+ checkNotNull(networkId, "Network ID cannot be null");
+ checkNotNull(deviceId, "Virtual device ID cannot be null");
+ Set<VirtualPort> virtualPortSet = getVirtualPorts(networkId, deviceId);
+ Set<DeviceId> physicalDeviceSet = new HashSet<>();
+
+ virtualPortSet.forEach(virtualPort -> {
+ if (virtualPort.realizedBy() != null) {
+ physicalDeviceSet.add(virtualPort.realizedBy().deviceId());
+ }
+ });
+
+ return ImmutableSet.copyOf(physicalDeviceSet);
+ }
+
+ private final Map<ServiceKey, VnetService> networkServices = Maps.newConcurrentMap();
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T> T get(NetworkId networkId, Class<T> serviceClass) {
+ checkNotNull(networkId, NETWORK_NULL);
+ ServiceKey serviceKey = networkServiceKey(networkId, serviceClass);
+ VnetService service = lookup(serviceKey);
+ if (service == null) {
+ service = create(serviceKey);
+ }
+ return (T) service;
+ }
+
+ @Override
+ public ApplicationId getVirtualNetworkApplicationId(NetworkId networkId) {
+ return appId;
+ }
+
+ /**
+ * Returns the Vnet service matching the service key.
+ *
+ * @param serviceKey service key
+ * @return vnet service
+ */
+ private VnetService lookup(ServiceKey serviceKey) {
+ return networkServices.get(serviceKey);
+ }
+
+ /**
+ * Creates a new service key using the specified network identifier and service class.
+ *
+ * @param networkId network identifier
+ * @param serviceClass service class
+ * @param <T> type of service
+ * @return service key
+ */
+ private <T> ServiceKey networkServiceKey(NetworkId networkId, Class<T> serviceClass) {
+ return new ServiceKey(networkId, serviceClass);
+ }
+
+
+ /**
+ * Create a new vnet service instance.
+ *
+ * @param serviceKey service key
+ * @return vnet service
+ */
+ private VnetService create(ServiceKey serviceKey) {
+ VirtualNetwork network = getVirtualNetwork(serviceKey.networkId());
+ checkNotNull(network, NETWORK_NULL);
+
+ VnetService service;
+ if (serviceKey.serviceClass.equals(DeviceService.class)) {
+ service = new VirtualNetworkDeviceManager(this, network.id());
+ } else if (serviceKey.serviceClass.equals(LinkService.class)) {
+ service = new VirtualNetworkLinkManager(this, network.id());
+ } else if (serviceKey.serviceClass.equals(TopologyService.class)) {
+ service = new VirtualNetworkTopologyManager(this, network.id());
+ } else if (serviceKey.serviceClass.equals(IntentService.class)) {
+ service = new VirtualNetworkIntentManager(this, network.id());
+ } else if (serviceKey.serviceClass.equals(HostService.class)) {
+ service = new VirtualNetworkHostManager(this, network.id());
+ } else if (serviceKey.serviceClass.equals(PathService.class)) {
+ service = new VirtualNetworkPathManager(this, network.id());
+ } else if (serviceKey.serviceClass.equals(FlowRuleService.class)) {
+ service = new VirtualNetworkFlowRuleManager(this, network.id());
+ } else if (serviceKey.serviceClass.equals(PacketService.class)) {
+ service = new VirtualNetworkPacketManager(this, network.id());
+ } else if (serviceKey.serviceClass.equals(GroupService.class)) {
+ service = new VirtualNetworkGroupManager(this, network.id());
+ } else if (serviceKey.serviceClass.equals(MeterService.class)) {
+ service = new VirtualNetworkMeterManager(this, network.id());
+ } else if (serviceKey.serviceClass.equals(FlowObjectiveService.class)) {
+ service = new VirtualNetworkFlowObjectiveManager(this, network.id());
+ } else if (serviceKey.serviceClass.equals(MastershipService.class) ||
+ serviceKey.serviceClass.equals(MastershipAdminService.class) ||
+ serviceKey.serviceClass.equals(MastershipTermService.class)) {
+ service = new VirtualNetworkMastershipManager(this, network.id());
+ } else {
+ return null;
+ }
+ networkServices.put(serviceKey, service);
+ return service;
+ }
+
+ /**
+ * Service key class.
+ */
+ private static class ServiceKey {
+ final NetworkId networkId;
+ final Class serviceClass;
+
+ /**
+ * Constructor for service key.
+ *
+ * @param networkId network identifier
+ * @param serviceClass service class
+ */
+ ServiceKey(NetworkId networkId, Class serviceClass) {
+
+ checkNotNull(networkId, NETWORK_NULL);
+ this.networkId = networkId;
+ this.serviceClass = serviceClass;
+ }
+
+ /**
+ * Returns the network identifier.
+ *
+ * @return network identifier
+ */
+ public NetworkId networkId() {
+ return networkId;
+ }
+
+ /**
+ * Returns the service class.
+ *
+ * @return service class
+ */
+ public Class serviceClass() {
+ return serviceClass;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(networkId, serviceClass);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof ServiceKey) {
+ ServiceKey that = (ServiceKey) obj;
+ return Objects.equals(this.networkId, that.networkId) &&
+ Objects.equals(this.serviceClass, that.serviceClass);
+ }
+ return false;
+ }
+ }
+
+ @Override
+ protected VirtualNetworkProviderService
+ createProviderService(VirtualNetworkProvider provider) {
+ return new InternalVirtualNetworkProviderService(provider);
+ }
+
+ /**
+ * Service issued to registered virtual network providers so that they
+ * can interact with the core.
+ */
+ private class InternalVirtualNetworkProviderService
+ extends AbstractProviderService<VirtualNetworkProvider>
+ implements VirtualNetworkProviderService {
+ /**
+ * Constructor.
+ * @param provider virtual network provider
+ */
+ InternalVirtualNetworkProviderService(VirtualNetworkProvider provider) {
+ super(provider);
+ }
+
+ @Override
+ public void topologyChanged(Set<Set<ConnectPoint>> clusters) {
+ Set<TenantId> tenantIds = getTenantIds();
+ tenantIds.forEach(tenantId -> {
+ Set<VirtualNetwork> virtualNetworks = getVirtualNetworks(tenantId);
+
+ virtualNetworks.forEach(virtualNetwork -> {
+ Set<VirtualLink> virtualLinks = getVirtualLinks(virtualNetwork.id());
+
+ virtualLinks.forEach(virtualLink -> {
+ if (isVirtualLinkInCluster(virtualNetwork.id(),
+ virtualLink, clusters)) {
+ store.updateLink(virtualLink, virtualLink.tunnelId(),
+ Link.State.ACTIVE);
+ } else {
+ store.updateLink(virtualLink, virtualLink.tunnelId(),
+ Link.State.INACTIVE);
+ }
+ });
+ });
+ });
+ }
+
+ /**
+ * Determines if the virtual link (both source and destination connect point)
+ * is in a cluster.
+ *
+ * @param networkId virtual network identifier
+ * @param virtualLink virtual link
+ * @param clusters topology clusters
+ * @return true if the virtual link is in a cluster.
+ */
+ private boolean isVirtualLinkInCluster(NetworkId networkId, VirtualLink virtualLink,
+ Set<Set<ConnectPoint>> clusters) {
+ ConnectPoint srcPhysicalCp =
+ mapVirtualToPhysicalPort(networkId, virtualLink.src());
+ ConnectPoint dstPhysicalCp =
+ mapVirtualToPhysicalPort(networkId, virtualLink.dst());
+
+ final boolean[] foundSrc = {false};
+ final boolean[] foundDst = {false};
+ clusters.forEach(connectPoints -> {
+ connectPoints.forEach(connectPoint -> {
+ if (connectPoint.equals(srcPhysicalCp)) {
+ foundSrc[0] = true;
+ } else if (connectPoint.equals(dstPhysicalCp)) {
+ foundDst[0] = true;
+ }
+ });
+ if (foundSrc[0] && foundDst[0]) {
+ return;
+ }
+ });
+ return foundSrc[0] && foundDst[0];
+ }
+
+ @Override
+ public void tunnelUp(NetworkId networkId, ConnectPoint src,
+ ConnectPoint dst, TunnelId tunnelId) {
+ ConnectPoint srcVirtualCp = mapPhysicalToVirtualToPort(networkId, src);
+ ConnectPoint dstVirtualCp = mapPhysicalToVirtualToPort(networkId, dst);
+ if ((srcVirtualCp == null) || (dstVirtualCp == null)) {
+ log.error("Src or dst virtual connection point was not found.");
+ }
+
+ VirtualLink virtualLink = store.getLink(networkId, srcVirtualCp, dstVirtualCp);
+ if (virtualLink != null) {
+ store.updateLink(virtualLink, tunnelId, Link.State.ACTIVE);
+ }
+ }
+
+ @Override
+ public void tunnelDown(NetworkId networkId, ConnectPoint src,
+ ConnectPoint dst, TunnelId tunnelId) {
+ ConnectPoint srcVirtualCp = mapPhysicalToVirtualToPort(networkId, src);
+ ConnectPoint dstVirtualCp = mapPhysicalToVirtualToPort(networkId, dst);
+ if ((srcVirtualCp == null) || (dstVirtualCp == null)) {
+ log.error("Src or dst virtual connection point was not found.");
+ }
+
+ VirtualLink virtualLink = store.getLink(networkId, srcVirtualCp, dstVirtualCp);
+ if (virtualLink != null) {
+ store.updateLink(virtualLink, tunnelId, Link.State.INACTIVE);
+ }
+ }
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkMastershipManager.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkMastershipManager.java
new file mode 100644
index 0000000..57c0774
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkMastershipManager.java
@@ -0,0 +1,213 @@
+/*
+ * 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.incubator.net.virtual.impl;
+
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.Futures;
+import org.onlab.metrics.MetricsService;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.cluster.RoleInfo;
+import org.onosproject.core.MetricsHelper;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualDevice;
+import org.onosproject.incubator.net.virtual.VirtualNetworkMastershipStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.event.AbstractVirtualListenerManager;
+import org.onosproject.mastership.MastershipAdminService;
+import org.onosproject.mastership.MastershipEvent;
+import org.onosproject.mastership.MastershipInfo;
+import org.onosproject.mastership.MastershipListener;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.mastership.MastershipStoreDelegate;
+import org.onosproject.mastership.MastershipTerm;
+import org.onosproject.mastership.MastershipTermService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+import org.slf4j.Logger;
+import com.codahale.metrics.Timer;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.metrics.MetricsUtil.startTimer;
+import static org.onlab.metrics.MetricsUtil.stopTimer;
+import static org.slf4j.LoggerFactory.getLogger;
+
+public class VirtualNetworkMastershipManager
+ extends AbstractVirtualListenerManager<MastershipEvent, MastershipListener>
+ implements MastershipService, MastershipAdminService, MastershipTermService,
+ MetricsHelper {
+
+ private static final String NODE_ID_NULL = "Node ID cannot be null";
+ private static final String DEVICE_ID_NULL = "Device ID cannot be null";
+ private static final String ROLE_NULL = "Mastership role cannot be null";
+
+ private final Logger log = getLogger(getClass());
+
+ protected ClusterService clusterService;
+
+ VirtualNetworkMastershipStore store;
+ MastershipStoreDelegate storeDelegate;
+
+ private NodeId localNodeId;
+ private Timer requestRoleTimer;
+
+ /**
+ * Creates a new VirtualNetworkMastershipManager object.
+ *
+ * @param manager virtual network manager service
+ * @param networkId virtual network identifier
+ */
+ public VirtualNetworkMastershipManager(VirtualNetworkService manager, NetworkId networkId) {
+ super(manager, networkId, MastershipEvent.class);
+
+ clusterService = serviceDirectory.get(ClusterService.class);
+
+ store = serviceDirectory.get(VirtualNetworkMastershipStore.class);
+ this.storeDelegate = new InternalDelegate();
+ store.setDelegate(networkId, this.storeDelegate);
+
+ requestRoleTimer = createTimer("Virtual-mastership", "requestRole", "responseTime");
+ localNodeId = clusterService.getLocalNode().id();
+ }
+
+ @Override
+ public CompletableFuture<Void> setRole(NodeId nodeId, DeviceId deviceId,
+ MastershipRole role) {
+ checkNotNull(nodeId, NODE_ID_NULL);
+ checkNotNull(deviceId, DEVICE_ID_NULL);
+ checkNotNull(role, ROLE_NULL);
+
+ CompletableFuture<MastershipEvent> eventFuture = null;
+
+ switch (role) {
+ case MASTER:
+ eventFuture = store.setMaster(networkId, nodeId, deviceId);
+ break;
+ case STANDBY:
+ eventFuture = store.setStandby(networkId, nodeId, deviceId);
+ break;
+ case NONE:
+ eventFuture = store.relinquishRole(networkId, nodeId, deviceId);
+ break;
+ default:
+ log.info("Unknown role; ignoring");
+ return CompletableFuture.completedFuture(null);
+ }
+
+ return eventFuture.thenAccept(this::post).thenApply(v -> null);
+ }
+
+ @Override
+ public MastershipRole getLocalRole(DeviceId deviceId) {
+ checkNotNull(deviceId, DEVICE_ID_NULL);
+
+ return store.getRole(networkId, localNodeId, deviceId);
+ }
+
+ @Override
+ public CompletableFuture<MastershipRole> requestRoleFor(DeviceId deviceId) {
+ checkNotNull(deviceId, DEVICE_ID_NULL);
+
+ final Timer.Context timer = startTimer(requestRoleTimer);
+ return store.requestRole(networkId, deviceId)
+ .whenComplete((result, error) -> stopTimer(timer));
+ }
+
+ @Override
+ public CompletableFuture<Void> relinquishMastership(DeviceId deviceId) {
+ return store.relinquishRole(networkId, localNodeId, deviceId)
+ .thenAccept(this::post)
+ .thenApply(v -> null);
+ }
+
+ @Override
+ public NodeId getMasterFor(DeviceId deviceId) {
+ checkNotNull(deviceId, DEVICE_ID_NULL);
+
+ return store.getMaster(networkId, deviceId);
+ }
+
+ @Override
+ public RoleInfo getNodesFor(DeviceId deviceId) {
+ checkNotNull(deviceId, DEVICE_ID_NULL);
+
+ return store.getNodes(networkId, deviceId);
+ }
+
+ @Override
+ public MastershipInfo getMastershipFor(DeviceId deviceId) {
+ checkNotNull(deviceId, DEVICE_ID_NULL);
+ return store.getMastership(networkId, deviceId);
+ }
+
+ @Override
+ public Set<DeviceId> getDevicesOf(NodeId nodeId) {
+ checkNotNull(nodeId, NODE_ID_NULL);
+
+ return store.getDevices(networkId, nodeId);
+ }
+
+ @Override
+ public MastershipTerm getMastershipTerm(DeviceId deviceId) {
+ return store.getTermFor(networkId, deviceId);
+ }
+
+ @Override
+ public MetricsService metricsService() {
+ //TODO: support metric service for virtual network
+ log.warn("Currently, virtual network does not support metric service.");
+ return null;
+ }
+
+ @Override
+ public void balanceRoles() {
+ //FIXME: More advanced logic for balancing virtual network roles.
+ List<ControllerNode> nodes = clusterService.getNodes().stream()
+ .filter(n -> clusterService.getState(n.id())
+ .equals(ControllerNode.State.ACTIVE))
+ .collect(Collectors.toList());
+
+ nodes.sort(Comparator.comparing(ControllerNode::id));
+
+ //Pick a node using network Id,
+ NodeId masterNode = nodes.get((int) ((networkId.id() - 1) % nodes.size())).id();
+
+ List<CompletableFuture<Void>> setRoleFutures = Lists.newLinkedList();
+ for (VirtualDevice device : manager.getVirtualDevices(networkId)) {
+ setRoleFutures.add(setRole(masterNode, device.id(), MastershipRole.MASTER));
+ }
+
+ CompletableFuture<Void> balanceRolesFuture = CompletableFuture.allOf(
+ setRoleFutures.toArray(new CompletableFuture[setRoleFutures.size()]));
+
+ Futures.getUnchecked(balanceRolesFuture);
+ }
+
+ public class InternalDelegate implements MastershipStoreDelegate {
+ @Override
+ public void notify(MastershipEvent event) {
+ post(event);
+ }
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkMeterManager.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkMeterManager.java
new file mode 100644
index 0000000..2cce14c
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkMeterManager.java
@@ -0,0 +1,298 @@
+/*
+ * 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.incubator.net.virtual.impl;
+
+import com.google.common.collect.Maps;
+import org.apache.commons.lang3.tuple.Pair;
+import org.onlab.util.TriConsumer;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkMeterStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.event.AbstractVirtualListenerManager;
+import org.onosproject.incubator.net.virtual.provider.AbstractVirtualProviderService;
+import org.onosproject.incubator.net.virtual.provider.VirtualMeterProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualMeterProviderService;
+import org.onosproject.incubator.net.virtual.provider.VirtualProviderRegistryService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.meter.DefaultMeter;
+import org.onosproject.net.meter.Meter;
+import org.onosproject.net.meter.MeterEvent;
+import org.onosproject.net.meter.MeterFailReason;
+import org.onosproject.net.meter.MeterFeatures;
+import org.onosproject.net.meter.MeterFeaturesKey;
+import org.onosproject.net.meter.MeterId;
+import org.onosproject.net.meter.MeterKey;
+import org.onosproject.net.meter.MeterListener;
+import org.onosproject.net.meter.MeterOperation;
+import org.onosproject.net.meter.MeterRequest;
+import org.onosproject.net.meter.MeterService;
+import org.onosproject.net.meter.MeterState;
+import org.onosproject.net.meter.MeterStoreDelegate;
+import org.onosproject.net.meter.MeterStoreResult;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.service.AtomicCounter;
+import org.onosproject.store.service.StorageService;
+import org.slf4j.Logger;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+public class VirtualNetworkMeterManager
+ extends AbstractVirtualListenerManager<MeterEvent, MeterListener>
+ implements MeterService {
+
+ private static final String METERCOUNTERIDENTIFIER = "meter-id-counter-%s";
+ private final Logger log = getLogger(getClass());
+
+ protected StorageService coreStorageService;
+
+ protected VirtualNetworkMeterStore store;
+ private final MeterStoreDelegate storeDelegate = new InternalMeterStoreDelegate();
+
+ private VirtualProviderRegistryService providerRegistryService;
+ private InternalMeterProviderService innerProviderService;
+
+ private Map<DeviceId, AtomicCounter> meterIdCounters
+ = Maps.newConcurrentMap();
+
+ private TriConsumer<MeterRequest, MeterStoreResult, Throwable> onComplete;
+
+ /**
+ * Creates a new VirtualNetworkMeterManager object.
+ *
+ * @param manager virtual network manager service
+ * @param networkId a virtual network identifier
+ */
+ public VirtualNetworkMeterManager(VirtualNetworkService manager,
+ NetworkId networkId) {
+ super(manager, networkId, MeterEvent.class);
+
+ coreStorageService = serviceDirectory.get(StorageService.class);
+ providerRegistryService =
+ serviceDirectory.get(VirtualProviderRegistryService.class);
+
+ store = serviceDirectory.get(VirtualNetworkMeterStore.class);
+ store.setDelegate(networkId, this.storeDelegate);
+
+ innerProviderService = new InternalMeterProviderService();
+ providerRegistryService.registerProviderService(networkId(), innerProviderService);
+
+
+ onComplete = (request, result, error) -> {
+ request.context().ifPresent(c -> {
+ if (error != null) {
+ c.onError(request, MeterFailReason.UNKNOWN);
+ } else {
+ if (result.reason().isPresent()) {
+ c.onError(request, result.reason().get());
+ } else {
+ c.onSuccess(request);
+ }
+ }
+ });
+
+ };
+
+ log.info("Started");
+ }
+
+ @Override
+ public Meter submit(MeterRequest request) {
+
+ MeterId id = allocateMeterId(request.deviceId());
+
+ Meter.Builder mBuilder = DefaultMeter.builder()
+ .forDevice(request.deviceId())
+ .fromApp(request.appId())
+ .withBands(request.bands())
+ .withId(id)
+ .withUnit(request.unit());
+
+ if (request.isBurst()) {
+ mBuilder.burst();
+ }
+ DefaultMeter m = (DefaultMeter) mBuilder.build();
+ m.setState(MeterState.PENDING_ADD);
+ store.storeMeter(networkId(), m).whenComplete((result, error) ->
+ onComplete.accept(request, result, error));
+ return m;
+ }
+
+ @Override
+ public void withdraw(MeterRequest request, MeterId meterId) {
+ Meter.Builder mBuilder = DefaultMeter.builder()
+ .forDevice(request.deviceId())
+ .fromApp(request.appId())
+ .withBands(request.bands())
+ .withId(meterId)
+ .withUnit(request.unit());
+
+ if (request.isBurst()) {
+ mBuilder.burst();
+ }
+
+ DefaultMeter m = (DefaultMeter) mBuilder.build();
+ m.setState(MeterState.PENDING_REMOVE);
+ store.deleteMeter(networkId(), m).whenComplete((result, error) ->
+ onComplete.accept(request, result, error));
+ }
+
+ @Override
+ public Meter getMeter(DeviceId deviceId, MeterId id) {
+ MeterKey key = MeterKey.key(deviceId, id);
+ return store.getMeter(networkId(), key);
+ }
+
+ @Override
+ public Collection<Meter> getMeters(DeviceId deviceId) {
+ return store.getAllMeters(networkId()).stream()
+ .filter(m -> m.deviceId().equals(deviceId)).collect(Collectors.toList());
+ }
+
+ @Override
+ public Collection<Meter> getAllMeters() {
+ return store.getAllMeters(networkId());
+ }
+
+ private long queryMeters(DeviceId device) {
+ //FIXME: how to decide maximum number of meters per virtual device?
+ return 1;
+ }
+
+ private AtomicCounter allocateCounter(DeviceId deviceId) {
+ return coreStorageService
+ .getAtomicCounter(String.format(METERCOUNTERIDENTIFIER, deviceId));
+ }
+
+ public MeterId allocateMeterId(DeviceId deviceId) {
+ long maxMeters = store.getMaxMeters(networkId(), MeterFeaturesKey.key(deviceId));
+ if (maxMeters == 0L) {
+ // MeterFeatures couldn't be retrieved, trying with queryMeters
+ maxMeters = queryMeters(deviceId);
+ }
+
+ if (maxMeters == 0L) {
+ throw new IllegalStateException("Meters not supported by device " + deviceId);
+ }
+
+ final long mmeters = maxMeters;
+ long id = meterIdCounters.compute(deviceId, (k, v) -> {
+ if (v == null) {
+ return allocateCounter(k);
+ }
+ if (v.get() >= mmeters) {
+ throw new IllegalStateException("Maximum number of meters " +
+ meterIdCounters.get(deviceId).get() +
+ " reached for device " + deviceId +
+ " virtual network " + networkId());
+ }
+ return v;
+ }).incrementAndGet();
+
+ return MeterId.meterId(id);
+ }
+
+ @Override
+ public void freeMeterId(DeviceId deviceId, MeterId meterId) {
+ // Do nothing
+ }
+
+ private class InternalMeterProviderService
+ extends AbstractVirtualProviderService<VirtualMeterProvider>
+ implements VirtualMeterProviderService {
+
+ /**
+ * Creates a provider service on behalf of the specified provider.
+ */
+ protected InternalMeterProviderService() {
+ Set<ProviderId> providerIds =
+ providerRegistryService.getProvidersByService(this);
+ ProviderId providerId = providerIds.stream().findFirst().get();
+ VirtualMeterProvider provider = (VirtualMeterProvider)
+ providerRegistryService.getProvider(providerId);
+ setProvider(provider);
+ }
+
+ @Override
+ public void meterOperationFailed(MeterOperation operation,
+ MeterFailReason reason) {
+ store.failedMeter(networkId(), operation, reason);
+ }
+
+ @Override
+ public void pushMeterMetrics(DeviceId deviceId, Collection<Meter> meterEntries) {
+ //FIXME: FOLLOWING CODE CANNOT BE TESTED UNTIL SOMETHING THAT
+ //FIXME: IMPLEMENTS METERS EXISTS
+ Map<Pair<DeviceId, MeterId>, Meter> storedMeterMap =
+ store.getAllMeters(networkId()).stream()
+ .collect(Collectors.toMap(m -> Pair.of(m.deviceId(), m.id()), Function.identity()));
+
+ meterEntries.stream()
+ .filter(m -> storedMeterMap.remove(Pair.of(m.deviceId(), m.id())) != null)
+ .forEach(m -> store.updateMeterState(networkId(), m));
+
+ storedMeterMap.values().forEach(m -> {
+ if (m.state() == MeterState.PENDING_ADD) {
+ provider().performMeterOperation(networkId(), m.deviceId(),
+ new MeterOperation(m,
+ MeterOperation.Type.MODIFY));
+ } else if (m.state() == MeterState.PENDING_REMOVE) {
+ store.deleteMeterNow(networkId(), m);
+ }
+ });
+ }
+
+ @Override
+ public void pushMeterFeatures(DeviceId deviceId, MeterFeatures meterfeatures) {
+ store.storeMeterFeatures(networkId(), meterfeatures);
+ }
+
+ @Override
+ public void deleteMeterFeatures(DeviceId deviceId) {
+ store.deleteMeterFeatures(networkId(), deviceId);
+ }
+ }
+
+ private class InternalMeterStoreDelegate implements MeterStoreDelegate {
+
+ @Override
+ public void notify(MeterEvent event) {
+ DeviceId deviceId = event.subject().deviceId();
+ VirtualMeterProvider p = innerProviderService.provider();
+
+ switch (event.type()) {
+ case METER_ADD_REQ:
+ p.performMeterOperation(networkId(), deviceId,
+ new MeterOperation(event.subject(),
+ MeterOperation.Type.ADD));
+ break;
+ case METER_REM_REQ:
+ p.performMeterOperation(networkId(), deviceId,
+ new MeterOperation(event.subject(),
+ MeterOperation.Type.REMOVE));
+ break;
+ default:
+ log.warn("Unknown meter event {}", event.type());
+ }
+ }
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkPacketManager.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkPacketManager.java
new file mode 100644
index 0000000..d1869ce
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkPacketManager.java
@@ -0,0 +1,371 @@
+/*
+ * 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.incubator.net.virtual.impl;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.incubator.net.virtual.AbstractVnetService;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkPacketStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.provider.AbstractVirtualProviderService;
+import org.onosproject.incubator.net.virtual.provider.VirtualPacketProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualPacketProviderService;
+import org.onosproject.incubator.net.virtual.provider.VirtualProviderRegistryService;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flowobjective.DefaultForwardingObjective;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.flowobjective.Objective;
+import org.onosproject.net.flowobjective.ObjectiveContext;
+import org.onosproject.net.flowobjective.ObjectiveError;
+import org.onosproject.net.packet.DefaultPacketRequest;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketEvent;
+import org.onosproject.net.packet.PacketPriority;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketProcessorEntry;
+import org.onosproject.net.packet.PacketRequest;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.net.packet.PacketStoreDelegate;
+import org.onosproject.net.provider.ProviderId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+public class VirtualNetworkPacketManager extends AbstractVnetService
+ implements PacketService {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private final VirtualNetworkService manager;
+
+ protected VirtualNetworkPacketStore store;
+ private final List<ProcessorEntry> processors = Lists.newCopyOnWriteArrayList();
+
+ private NodeId localNodeId;
+
+ private DeviceService deviceService;
+ private FlowObjectiveService objectiveService;
+
+ private VirtualProviderRegistryService providerRegistryService = null;
+
+ private InternalPacketProviderService providerService = null;
+
+ public VirtualNetworkPacketManager(VirtualNetworkService virtualNetworkManager,
+ NetworkId networkId) {
+ super(virtualNetworkManager, networkId);
+ this.manager = virtualNetworkManager;
+
+ //Set node id as same as the node hosting virtual manager
+ ClusterService clusterService = serviceDirectory.get(ClusterService.class);
+ this.localNodeId = clusterService.getLocalNode().id();
+
+ this.store = serviceDirectory.get(VirtualNetworkPacketStore.class);
+ this.store.setDelegate(networkId(), new InternalStoreDelegate());
+
+ this.deviceService = manager.get(networkId(), DeviceService.class);
+ this.objectiveService = manager.get(networkId(), FlowObjectiveService.class);
+
+ providerRegistryService =
+ serviceDirectory.get(VirtualProviderRegistryService.class);
+ providerService = new InternalPacketProviderService();
+ providerRegistryService.registerProviderService(networkId(), providerService);
+ }
+
+ @Override
+ public void addProcessor(PacketProcessor processor, int priority) {
+ ProcessorEntry entry = new ProcessorEntry(processor, priority);
+
+ // Insert the new processor according to its priority.
+ int i = 0;
+ for (; i < processors.size(); i++) {
+ if (priority < processors.get(i).priority()) {
+ break;
+ }
+ }
+ processors.add(i, entry);
+ }
+
+ @Override
+ public void removeProcessor(PacketProcessor processor) {
+ // Remove the processor entry.
+ for (int i = 0; i < processors.size(); i++) {
+ if (processors.get(i).processor() == processor) {
+ processors.remove(i);
+ break;
+ }
+ }
+ }
+
+ @Override
+ public List<PacketProcessorEntry> getProcessors() {
+ return ImmutableList.copyOf(processors);
+ }
+
+ @Override
+ public void requestPackets(TrafficSelector selector, PacketPriority priority, ApplicationId appId) {
+ PacketRequest request = new DefaultPacketRequest(selector, priority, appId,
+ localNodeId, Optional.empty());
+ store.requestPackets(networkId(), request);
+ }
+
+ @Override
+ public void requestPackets(TrafficSelector selector, PacketPriority priority,
+ ApplicationId appId, Optional<DeviceId> deviceId) {
+ PacketRequest request =
+ new DefaultPacketRequest(selector, priority, appId,
+ localNodeId, deviceId);
+
+ store.requestPackets(networkId(), request);
+ }
+
+ @Override
+ public void cancelPackets(TrafficSelector selector, PacketPriority priority, ApplicationId appId) {
+ PacketRequest request = new DefaultPacketRequest(selector, priority, appId,
+ localNodeId, Optional.empty());
+ store.cancelPackets(networkId(), request);
+ }
+
+ @Override
+ public void cancelPackets(TrafficSelector selector, PacketPriority priority,
+ ApplicationId appId, Optional<DeviceId> deviceId) {
+ PacketRequest request = new DefaultPacketRequest(selector, priority,
+ appId, localNodeId,
+ deviceId);
+ store.cancelPackets(networkId(), request);
+ }
+
+ @Override
+ public List<PacketRequest> getRequests() {
+ return store.existingRequests(networkId());
+ }
+
+ @Override
+ public void emit(OutboundPacket packet) {
+ store.emit(networkId(), packet);
+ }
+
+ /**
+ * Personalized packet provider service issued to the supplied provider.
+ */
+ private class InternalPacketProviderService
+ extends AbstractVirtualProviderService<VirtualPacketProvider>
+ implements VirtualPacketProviderService {
+
+ protected InternalPacketProviderService() {
+ super();
+
+ Set<ProviderId> providerIds =
+ providerRegistryService.getProvidersByService(this);
+ ProviderId providerId = providerIds.stream().findFirst().get();
+ VirtualPacketProvider provider = (VirtualPacketProvider)
+ providerRegistryService.getProvider(providerId);
+ setProvider(provider);
+ }
+
+ @Override
+ public void processPacket(PacketContext context) {
+ // TODO filter packets sent to processors based on registrations
+ for (ProcessorEntry entry : processors) {
+ try {
+ long start = System.nanoTime();
+ entry.processor().process(context);
+ entry.addNanos(System.nanoTime() - start);
+ } catch (Exception e) {
+ log.warn("Packet processor {} threw an exception", entry.processor(), e);
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Entity for tracking stats for a packet processor.
+ */
+ private class ProcessorEntry implements PacketProcessorEntry {
+ private final PacketProcessor processor;
+ private final int priority;
+ private long invocations = 0;
+ private long nanos = 0;
+
+ public ProcessorEntry(PacketProcessor processor, int priority) {
+ this.processor = processor;
+ this.priority = priority;
+ }
+
+ @Override
+ public PacketProcessor processor() {
+ return processor;
+ }
+
+ @Override
+ public int priority() {
+ return priority;
+ }
+
+ @Override
+ public long invocations() {
+ return invocations;
+ }
+
+ @Override
+ public long totalNanos() {
+ return nanos;
+ }
+
+ @Override
+ public long averageNanos() {
+ return invocations > 0 ? nanos / invocations : 0;
+ }
+
+ void addNanos(long nanos) {
+ this.nanos += nanos;
+ this.invocations++;
+ }
+ }
+
+ private void localEmit(NetworkId networkId, OutboundPacket packet) {
+ Device device = deviceService.getDevice(packet.sendThrough());
+ if (device == null) {
+ return;
+ }
+ VirtualPacketProvider packetProvider = providerService.provider();
+
+ if (packetProvider != null) {
+ packetProvider.emit(networkId, packet);
+ }
+ }
+
+ /**
+ * Internal callback from the packet store.
+ */
+ protected class InternalStoreDelegate implements PacketStoreDelegate {
+ @Override
+ public void notify(PacketEvent event) {
+ localEmit(networkId(), event.subject());
+ }
+
+ @Override
+ public void requestPackets(PacketRequest request) {
+ DeviceId deviceid = request.deviceId().orElse(null);
+
+ if (deviceid != null) {
+ pushRule(deviceService.getDevice(deviceid), request);
+ } else {
+ pushToAllDevices(request);
+ }
+ }
+
+ @Override
+ public void cancelPackets(PacketRequest request) {
+ DeviceId deviceid = request.deviceId().orElse(null);
+
+ if (deviceid != null) {
+ removeRule(deviceService.getDevice(deviceid), request);
+ } else {
+ removeFromAllDevices(request);
+ }
+ }
+ }
+
+ /**
+ * Pushes packet intercept flow rules to the device.
+ *
+ * @param device the device to push the rules to
+ * @param request the packet request
+ */
+ private void pushRule(Device device, PacketRequest request) {
+ if (!device.type().equals(Device.Type.VIRTUAL)) {
+ return;
+ }
+
+ ForwardingObjective forwarding = createBuilder(request)
+ .add(new ObjectiveContext() {
+ @Override
+ public void onError(Objective objective, ObjectiveError error) {
+ log.warn("Failed to install packet request {} to {}: {}",
+ request, device.id(), error);
+ }
+ });
+
+ objectiveService.forward(device.id(), forwarding);
+ }
+
+ /**
+ * Removes packet intercept flow rules from the device.
+ *
+ * @param device the device to remove the rules deom
+ * @param request the packet request
+ */
+ private void removeRule(Device device, PacketRequest request) {
+ if (!device.type().equals(Device.Type.VIRTUAL)) {
+ return;
+ }
+ ForwardingObjective forwarding = createBuilder(request)
+ .remove(new ObjectiveContext() {
+ @Override
+ public void onError(Objective objective, ObjectiveError error) {
+ log.warn("Failed to withdraw packet request {} from {}: {}",
+ request, device.id(), error);
+ }
+ });
+ objectiveService.forward(device.id(), forwarding);
+ }
+
+ /**
+ * Pushes a packet request flow rule to all devices.
+ *
+ * @param request the packet request
+ */
+ private void pushToAllDevices(PacketRequest request) {
+ log.debug("Pushing packet request {} to all devices", request);
+ for (Device device : deviceService.getDevices()) {
+ pushRule(device, request);
+ }
+ }
+
+ /**
+ * Removes packet request flow rule from all devices.
+ *
+ * @param request the packet request
+ */
+ private void removeFromAllDevices(PacketRequest request) {
+ deviceService.getAvailableDevices().forEach(d -> removeRule(d, request));
+ }
+
+ private DefaultForwardingObjective.Builder createBuilder(PacketRequest request) {
+ return DefaultForwardingObjective.builder()
+ .withPriority(request.priority().priorityValue())
+ .withSelector(request.selector())
+ .fromApp(request.appId())
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withTreatment(DefaultTrafficTreatment.builder().punt().build())
+ .makePermanent();
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkPathManager.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkPathManager.java
new file mode 100644
index 0000000..de0dffa
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkPathManager.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.incubator.net.virtual.impl;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.VnetService;
+import org.onosproject.net.DisjointPath;
+import org.onosproject.net.ElementId;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.topology.LinkWeigher;
+import org.onosproject.net.topology.PathService;
+import org.onosproject.net.topology.AbstractPathService;
+import org.onosproject.net.topology.TopologyService;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Path service implementation built on the virtual network service.
+ */
+public class VirtualNetworkPathManager
+ extends AbstractPathService
+ implements PathService, VnetService {
+
+ private final NetworkId networkId;
+
+ /**
+ * Creates a new virtual network path service object.
+ *
+ * @param virtualNetworkManager virtual network manager service
+ * @param networkId a virtual network identifier
+ */
+
+ public VirtualNetworkPathManager(VirtualNetworkService virtualNetworkManager,
+ NetworkId networkId) {
+ this.networkId = networkId;
+
+ topologyService = virtualNetworkManager.get(networkId(), TopologyService.class);
+ hostService = virtualNetworkManager.get(networkId(), HostService.class);
+ }
+
+ @Override
+ public Set<Path> getPaths(ElementId src, ElementId dst) {
+ return super.getPaths(src, dst, (LinkWeigher) null);
+ }
+
+ @Override
+ public Set<DisjointPath> getDisjointPaths(ElementId src, ElementId dst) {
+ return getDisjointPaths(src, dst, (LinkWeigher) null);
+ }
+
+ @Override
+ public Set<DisjointPath> getDisjointPaths(ElementId src, ElementId dst,
+ Map<Link, Object> riskProfile) {
+ return getDisjointPaths(src, dst, (LinkWeigher) null, riskProfile);
+ }
+
+ @Override
+ public NetworkId networkId() {
+ return this.networkId;
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkTopologyManager.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkTopologyManager.java
new file mode 100644
index 0000000..aad3ac4
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkTopologyManager.java
@@ -0,0 +1,197 @@
+/*
+ * 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.incubator.net.virtual.impl;
+
+import org.onosproject.common.DefaultTopology;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.event.AbstractVirtualListenerManager;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.DisjointPath;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.topology.ClusterId;
+import org.onosproject.net.topology.DefaultGraphDescription;
+import org.onosproject.net.topology.LinkWeigher;
+import org.onosproject.net.topology.Topology;
+import org.onosproject.net.topology.TopologyCluster;
+import org.onosproject.net.topology.TopologyEvent;
+import org.onosproject.net.topology.TopologyGraph;
+import org.onosproject.net.topology.TopologyListener;
+import org.onosproject.net.topology.TopologyService;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.incubator.net.virtual.DefaultVirtualLink.PID;
+
+/**
+ * Topology service implementation built on the virtual network service.
+ */
+public class VirtualNetworkTopologyManager
+ extends AbstractVirtualListenerManager<TopologyEvent, TopologyListener>
+ implements TopologyService {
+
+ private static final String TOPOLOGY_NULL = "Topology cannot be null";
+ private static final String DEVICE_ID_NULL = "Device ID cannot be null";
+ private static final String CLUSTER_ID_NULL = "Cluster ID cannot be null";
+ private static final String CLUSTER_NULL = "Topology cluster cannot be null";
+ private static final String CONNECTION_POINT_NULL = "Connection point cannot be null";
+ private static final String LINK_WEIGHT_NULL = "Link weight cannot be null";
+
+ /**
+ * Creates a new VirtualNetworkTopologyService object.
+ *
+ * @param virtualNetworkManager virtual network manager service
+ * @param networkId a virtual network identifier
+ */
+ public VirtualNetworkTopologyManager(VirtualNetworkService virtualNetworkManager,
+ NetworkId networkId) {
+ super(virtualNetworkManager, networkId, TopologyEvent.class);
+ }
+
+ @Override
+ public Topology currentTopology() {
+ Iterable<Device> devices = manager.getVirtualDevices(networkId())
+ .stream()
+ .collect(Collectors.toSet());
+ Iterable<Link> links = manager.getVirtualLinks(networkId())
+ .stream()
+ .collect(Collectors.toSet());
+
+ DefaultGraphDescription graph =
+ new DefaultGraphDescription(System.nanoTime(),
+ System.currentTimeMillis(),
+ devices, links);
+ return new DefaultTopology(PID, graph);
+ }
+
+ @Override
+ public boolean isLatest(Topology topology) {
+ Topology currentTopology = currentTopology();
+ return defaultTopology(topology).getGraph()
+ .equals(defaultTopology(currentTopology).getGraph());
+ }
+
+ @Override
+ public TopologyGraph getGraph(Topology topology) {
+ return defaultTopology(topology).getGraph();
+ }
+
+ // Validates the specified topology and returns it as a default
+ private DefaultTopology defaultTopology(Topology topology) {
+ checkNotNull(topology, TOPOLOGY_NULL);
+ checkArgument(topology instanceof DefaultTopology,
+ "Topology class %s not supported", topology.getClass());
+ return (DefaultTopology) topology;
+ }
+
+ @Override
+ public Set<TopologyCluster> getClusters(Topology topology) {
+ return defaultTopology(topology).getClusters();
+ }
+
+ @Override
+ public TopologyCluster getCluster(Topology topology, ClusterId clusterId) {
+ checkNotNull(clusterId, CLUSTER_ID_NULL);
+ return defaultTopology(topology).getCluster(clusterId);
+ }
+
+ @Override
+ public Set<DeviceId> getClusterDevices(Topology topology, TopologyCluster cluster) {
+ checkNotNull(cluster, CLUSTER_NULL);
+ return defaultTopology(topology).getClusterDevices(cluster);
+ }
+
+ @Override
+ public Set<Link> getClusterLinks(Topology topology, TopologyCluster cluster) {
+ checkNotNull(cluster, CLUSTER_NULL);
+ return defaultTopology(topology).getClusterLinks(cluster);
+ }
+
+ @Override
+ public Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst) {
+ checkNotNull(src, DEVICE_ID_NULL);
+ checkNotNull(dst, DEVICE_ID_NULL);
+ return defaultTopology(topology).getPaths(src, dst);
+ }
+
+ @Override
+ public Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst,
+ LinkWeigher weigher) {
+ checkNotNull(src, DEVICE_ID_NULL);
+ checkNotNull(dst, DEVICE_ID_NULL);
+ checkNotNull(weigher, LINK_WEIGHT_NULL);
+ return defaultTopology(topology).getPaths(src, dst, weigher);
+ }
+
+ @Override
+ public Set<DisjointPath> getDisjointPaths(Topology topology, DeviceId src,
+ DeviceId dst) {
+ checkNotNull(src, DEVICE_ID_NULL);
+ checkNotNull(dst, DEVICE_ID_NULL);
+ return defaultTopology(topology).getDisjointPaths(src, dst);
+ }
+
+ @Override
+ public Set<DisjointPath> getDisjointPaths(Topology topology, DeviceId src,
+ DeviceId dst,
+ LinkWeigher weigher) {
+ checkNotNull(src, DEVICE_ID_NULL);
+ checkNotNull(dst, DEVICE_ID_NULL);
+ checkNotNull(weigher, LINK_WEIGHT_NULL);
+ return defaultTopology(topology).getDisjointPaths(src, dst, weigher);
+ }
+
+ @Override
+ public Set<DisjointPath> getDisjointPaths(Topology topology, DeviceId src,
+ DeviceId dst,
+ Map<Link, Object> riskProfile) {
+ checkNotNull(src, DEVICE_ID_NULL);
+ checkNotNull(dst, DEVICE_ID_NULL);
+ return defaultTopology(topology).getDisjointPaths(src, dst, riskProfile);
+ }
+
+ @Override
+ public Set<DisjointPath> getDisjointPaths(Topology topology, DeviceId src,
+ DeviceId dst,
+ LinkWeigher weigher,
+ Map<Link, Object> riskProfile) {
+ checkNotNull(src, DEVICE_ID_NULL);
+ checkNotNull(dst, DEVICE_ID_NULL);
+ checkNotNull(weigher, LINK_WEIGHT_NULL);
+ return defaultTopology(topology).getDisjointPaths(src, dst, weigher,
+ riskProfile);
+ }
+
+ @Override
+ public boolean isInfrastructure(Topology topology, ConnectPoint connectPoint) {
+ checkNotNull(connectPoint, CONNECTION_POINT_NULL);
+ return defaultTopology(topology).isInfrastructure(connectPoint);
+ }
+
+ @Override
+ public boolean isBroadcastPoint(Topology topology, ConnectPoint connectPoint) {
+ checkNotNull(connectPoint, CONNECTION_POINT_NULL);
+ return defaultTopology(topology).isBroadcastPoint(connectPoint);
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentAccumulator.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentAccumulator.java
new file mode 100644
index 0000000..0328383
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentAccumulator.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent;
+
+import com.google.common.collect.Maps;
+import org.onlab.util.AbstractAccumulator;
+import org.onosproject.net.intent.IntentBatchDelegate;
+import org.onosproject.net.intent.IntentData;
+import org.onosproject.net.intent.Key;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Timer;
+
+/**
+ * An accumulator for building batches of intent operations for virtual network.
+ * Only one batch should be in process per instance at a time.
+ */
+public class VirtualIntentAccumulator extends AbstractAccumulator<IntentData> {
+ private static final int DEFAULT_MAX_EVENTS = 1000;
+ private static final int DEFAULT_MAX_IDLE_MS = 10;
+ private static final int DEFAULT_MAX_BATCH_MS = 50;
+
+ // FIXME: Replace with a system-wide timer instance;
+ // TODO: Convert to use HashedWheelTimer or produce a variant of that; then decide which we want to adopt
+ private static final Timer TIMER = new Timer("virtual-intent-op-batching");
+
+ private final IntentBatchDelegate delegate;
+
+ private volatile boolean ready;
+
+ /**
+ * Creates an intent operation accumulator.
+ *
+ * @param delegate the intent batch delegate
+ */
+ public VirtualIntentAccumulator(IntentBatchDelegate delegate) {
+ super(TIMER, DEFAULT_MAX_EVENTS, DEFAULT_MAX_BATCH_MS, DEFAULT_MAX_IDLE_MS);
+ this.delegate = delegate;
+ // Assume that the delegate is ready for work at the start
+ ready = true; //TODO validate the assumption that delegate is ready
+ }
+
+ @Override
+ public void processItems(List<IntentData> items) {
+ ready = false;
+ delegate.execute(reduce(items));
+ }
+
+ private Collection<IntentData> reduce(List<IntentData> ops) {
+ Map<Key, IntentData> map = Maps.newHashMap();
+ for (IntentData op : ops) {
+ map.put(op.key(), op);
+ }
+ //TODO check the version... or maybe store will handle this.
+ return map.values();
+ }
+
+ @Override
+ public boolean isReady() {
+ return ready;
+ }
+
+ public void ready() {
+ ready = true;
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentCompilerRegistry.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentCompilerRegistry.java
new file mode 100644
index 0000000..f922c22
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentCompilerRegistry.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.intent.VirtualIntentCompiler;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentException;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+public final class VirtualIntentCompilerRegistry {
+ private final ConcurrentMap<Class<? extends Intent>,
+ VirtualIntentCompiler<? extends Intent>> compilers = new ConcurrentHashMap<>();
+
+ // non-instantiable (except for our Singleton)
+ private VirtualIntentCompilerRegistry() {
+
+ }
+
+ public static VirtualIntentCompilerRegistry getInstance() {
+ return SingletonHelper.INSTANCE;
+ }
+
+ /**
+ * Registers the specified compiler for the given intent class.
+ *
+ * @param cls intent class
+ * @param compiler intent compiler
+ * @param <T> the type of intent
+ */
+ public <T extends Intent> void registerCompiler(Class<T> cls,
+ VirtualIntentCompiler<T> compiler) {
+ compilers.put(cls, compiler);
+ }
+
+ /**
+ * Unregisters the compiler for the specified intent class.
+ *
+ * @param cls intent class
+ * @param <T> the type of intent
+ */
+ public <T extends Intent> void unregisterCompiler(Class<T> cls) {
+ compilers.remove(cls);
+ }
+
+ /**
+ * Returns immutable set of bindings of currently registered intent compilers.
+ *
+ * @return the set of compiler bindings
+ */
+ public Map<Class<? extends Intent>, VirtualIntentCompiler<? extends Intent>> getCompilers() {
+ return ImmutableMap.copyOf(compilers);
+ }
+
+ /**
+ * Compiles an intent recursively.
+ *
+ * @param networkId network identifier
+ * @param intent intent
+ * @param previousInstallables previous intent installables
+ * @return result of compilation
+ */
+ public List<Intent> compile(NetworkId networkId,
+ Intent intent, List<Intent> previousInstallables) {
+ if (intent.isInstallable()) {
+ return ImmutableList.of(intent);
+ }
+
+ // FIXME: get previous resources
+ List<Intent> installables = new ArrayList<>();
+ Queue<Intent> compileQueue = new LinkedList<>();
+ compileQueue.add(intent);
+
+ Intent compiling;
+ while ((compiling = compileQueue.poll()) != null) {
+ registerSubclassCompilerIfNeeded(compiling);
+
+ List<Intent> compiled = getCompiler(compiling)
+ .compile(networkId, compiling, previousInstallables);
+
+ compiled.forEach(i -> {
+ if (i.isInstallable()) {
+ installables.add(i);
+ } else {
+ compileQueue.add(i);
+ }
+ });
+ }
+ return installables;
+ }
+
+ /**
+ * Returns the corresponding intent compiler to the specified intent.
+ *
+ * @param intent intent
+ * @param <T> the type of intent
+ * @return intent compiler corresponding to the specified intent
+ */
+ private <T extends Intent> VirtualIntentCompiler<T> getCompiler(T intent) {
+ @SuppressWarnings("unchecked")
+ VirtualIntentCompiler<T> compiler =
+ (VirtualIntentCompiler<T>) compilers.get(intent.getClass());
+ if (compiler == null) {
+ throw new IntentException("no compiler for class " + intent.getClass());
+ }
+ return compiler;
+ }
+
+ /**
+ * Registers an intent compiler of the specified intent if an intent compiler
+ * for the intent is not registered. This method traverses the class hierarchy of
+ * the intent. Once an intent compiler for a parent type is found, this method
+ * registers the found intent compiler.
+ *
+ * @param intent intent
+ */
+ private void registerSubclassCompilerIfNeeded(Intent intent) {
+ if (!compilers.containsKey(intent.getClass())) {
+ Class<?> cls = intent.getClass();
+ while (cls != Object.class) {
+ // As long as we're within the Intent class descendants
+ if (Intent.class.isAssignableFrom(cls)) {
+ VirtualIntentCompiler<?> compiler = compilers.get(cls);
+ if (compiler != null) {
+ compilers.put(intent.getClass(), compiler);
+ return;
+ }
+ }
+ cls = cls.getSuperclass();
+ }
+ }
+ }
+
+ /**
+ * Prevents object instantiation from external.
+ */
+ private static final class SingletonHelper {
+ private static final String ILLEGAL_ACCESS_MSG =
+ "Should not instantiate this class.";
+ private static final VirtualIntentCompilerRegistry INSTANCE =
+ new VirtualIntentCompilerRegistry();
+
+ private SingletonHelper() {
+ throw new IllegalAccessError(ILLEGAL_ACCESS_MSG);
+ }
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentInstallCoordinator.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentInstallCoordinator.java
new file mode 100644
index 0000000..4a09250
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentInstallCoordinator.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkIntentStore;
+import org.onosproject.incubator.net.virtual.impl.VirtualNetworkIntentManager;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentData;
+import org.onosproject.net.intent.IntentInstallationContext;
+import org.onosproject.net.intent.IntentInstaller;
+import org.onosproject.net.intent.IntentOperationContext;
+import org.slf4j.Logger;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static org.onosproject.net.intent.IntentState.*;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of IntentInstallCoordinator for virtual network.
+ */
+public class VirtualIntentInstallCoordinator {
+ private static final String INSTALLER_NOT_FOUND = "Intent installer not found, Intent: {}";
+ private final Logger log = getLogger(VirtualNetworkIntentManager.class);
+
+ NetworkId networkId;
+ private VirtualIntentInstallerRegistry installerRegistry;
+ private VirtualNetworkIntentStore intentStore;
+
+ /**
+ * Creates an InstallCoordinator.
+ *
+ * @param networkId virtual network identifier
+ * @param installerRegistry the installer registry
+ * @param intentStore the Intent store
+ */
+ public VirtualIntentInstallCoordinator(NetworkId networkId,
+ VirtualIntentInstallerRegistry installerRegistry,
+ VirtualNetworkIntentStore intentStore) {
+ this.networkId = networkId;
+ this.installerRegistry = installerRegistry;
+ this.intentStore = intentStore;
+ }
+
+ /**
+ * Applies Intent data to be uninstalled and to be installed.
+ *
+ * @param toUninstall Intent data to be uninstalled
+ * @param toInstall Intent data to be installed
+ */
+ public void installIntents(Optional<IntentData> toUninstall,
+ Optional<IntentData> toInstall) {
+ // If no any Intents to be uninstalled or installed, ignore it.
+ if (!toUninstall.isPresent() && !toInstall.isPresent()) {
+ return;
+ }
+
+ // Classify installable Intents to different installers.
+ ArrayListMultimap<IntentInstaller, Intent> uninstallInstallers;
+ ArrayListMultimap<IntentInstaller, Intent> installInstallers;
+ Set<IntentInstaller> allInstallers = Sets.newHashSet();
+
+ if (toUninstall.isPresent()) {
+ uninstallInstallers = getInstallers(toUninstall.get());
+ allInstallers.addAll(uninstallInstallers.keySet());
+ } else {
+ uninstallInstallers = ArrayListMultimap.create();
+ }
+
+ if (toInstall.isPresent()) {
+ installInstallers = getInstallers(toInstall.get());
+ allInstallers.addAll(installInstallers.keySet());
+ } else {
+ installInstallers = ArrayListMultimap.create();
+ }
+
+ // Generates an installation context for the high level Intent.
+ IntentInstallationContext installationContext =
+ new IntentInstallationContext(toUninstall.orElse(null), toInstall.orElse(null));
+
+ //Generates different operation context for different installable Intents.
+ Map<IntentInstaller, IntentOperationContext> contexts = Maps.newHashMap();
+ allInstallers.forEach(installer -> {
+ List<Intent> intentsToUninstall = uninstallInstallers.get(installer);
+ List<Intent> intentsToInstall = installInstallers.get(installer);
+
+ // Connect context to high level installation context
+ IntentOperationContext context =
+ new IntentOperationContext(intentsToUninstall, intentsToInstall,
+ installationContext);
+ installationContext.addPendingContext(context);
+ contexts.put(installer, context);
+ });
+
+ // Apply contexts to installers
+ contexts.forEach((installer, context) -> {
+ installer.apply(context);
+ });
+ }
+
+ /**
+ * Generates a mapping for installable Intents to installers.
+ *
+ * @param intentData the Intent data which contains installable Intents
+ * @return the mapping for installable Intents to installers
+ */
+ private ArrayListMultimap<IntentInstaller, Intent> getInstallers(IntentData intentData) {
+ ArrayListMultimap<IntentInstaller, Intent> intentInstallers = ArrayListMultimap.create();
+ intentData.installables().forEach(intent -> {
+ IntentInstaller installer = installerRegistry.getInstaller(intent.getClass());
+ if (installer != null) {
+ intentInstallers.put(installer, intent);
+ } else {
+ log.warn(INSTALLER_NOT_FOUND, intent);
+ }
+ });
+ return intentInstallers;
+ }
+
+ /**
+ * Handles success operation context.
+ *
+ * @param context the operation context
+ */
+ public void success(IntentOperationContext context) {
+ IntentInstallationContext intentInstallationContext =
+ context.intentInstallationContext();
+ intentInstallationContext.removePendingContext(context);
+
+ if (intentInstallationContext.isPendingContextsEmpty()) {
+ finish(intentInstallationContext);
+ }
+ }
+
+ /**
+ * Handles failed operation context.
+ *
+ * @param context the operation context
+ */
+ public void failed(IntentOperationContext context) {
+ IntentInstallationContext intentInstallationContext =
+ context.intentInstallationContext();
+ intentInstallationContext.addErrorContext(context);
+ intentInstallationContext.removePendingContext(context);
+
+ if (intentInstallationContext.isPendingContextsEmpty()) {
+ finish(intentInstallationContext);
+ }
+ }
+
+ /**
+ * Completed the installation context and update the Intent store.
+ *
+ * @param intentInstallationContext the installation context
+ */
+ private void finish(IntentInstallationContext intentInstallationContext) {
+ Set<IntentOperationContext> errCtxs = intentInstallationContext.errorContexts();
+ Optional<IntentData> toUninstall = intentInstallationContext.toUninstall();
+ Optional<IntentData> toInstall = intentInstallationContext.toInstall();
+
+ // Intent install success
+ if (errCtxs == null || errCtxs.isEmpty()) {
+ if (toInstall.isPresent()) {
+ IntentData installData = toInstall.get();
+ log.debug("Completed installing: {}", installData.key());
+ installData = new IntentData(installData, installData.installables());
+ installData.setState(INSTALLED);
+ intentStore.write(networkId, installData);
+ } else if (toUninstall.isPresent()) {
+ IntentData uninstallData = toUninstall.get();
+ uninstallData = new IntentData(uninstallData, Collections.emptyList());
+ log.debug("Completed withdrawing: {}", uninstallData.key());
+ switch (uninstallData.request()) {
+ case INSTALL_REQ:
+ log.warn("{} was requested to withdraw during installation?",
+ uninstallData.intent());
+ uninstallData.setState(FAILED);
+ break;
+ case WITHDRAW_REQ:
+ default: //TODO "default" case should not happen
+ uninstallData.setState(WITHDRAWN);
+ break;
+ }
+ // Intent has been withdrawn; we can clear the installables
+ intentStore.write(networkId, uninstallData);
+ }
+ } else {
+ // if toInstall was cause of error, then recompile (manage/increment counter, when exceeded -> CORRUPT)
+ if (toInstall.isPresent()) {
+ IntentData installData = toInstall.get();
+ installData.setState(CORRUPT);
+ installData.incrementErrorCount();
+ intentStore.write(networkId, installData);
+ }
+ // if toUninstall was cause of error, then CORRUPT (another job will clean this up)
+ if (toUninstall.isPresent()) {
+ IntentData uninstallData = toUninstall.get();
+ uninstallData.setState(CORRUPT);
+ uninstallData.incrementErrorCount();
+ intentStore.write(networkId, uninstallData);
+ }
+ }
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentInstallerRegistry.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentInstallerRegistry.java
new file mode 100644
index 0000000..a0e6448
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentInstallerRegistry.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentInstaller;
+
+import java.util.Map;
+
+/**
+ * The local registry for Intent installer for virtual networks.
+ */
+public final class VirtualIntentInstallerRegistry {
+ private final Map<Class<? extends Intent>,
+ IntentInstaller<? extends Intent>> installers;
+
+ // non-instantiable (except for our Singleton)
+ private VirtualIntentInstallerRegistry() {
+ installers = Maps.newConcurrentMap();
+ }
+
+ public static VirtualIntentInstallerRegistry getInstance() {
+ return SingletonHelper.INSTANCE;
+ }
+
+ /**
+ * Registers the specific installer for the given intent class.
+ *
+ * @param cls intent class
+ * @param installer intent installer
+ * @param <T> the type of intent
+ */
+ public <T extends Intent> void registerInstaller(Class<T> cls, IntentInstaller<T> installer) {
+ installers.put(cls, installer);
+ }
+
+ /**
+ * Unregisters the installer for the specific intent class.
+ *
+ * @param cls intent class
+ * @param <T> the type of intent
+ */
+ public <T extends Intent> void unregisterInstaller(Class<T> cls) {
+ installers.remove(cls);
+ }
+
+ /**
+ * Returns immutable set of binding of currently registered intent installers.
+ *
+ * @return the set of installer bindings
+ */
+ public Map<Class<? extends Intent>, IntentInstaller<? extends Intent>> getInstallers() {
+ return ImmutableMap.copyOf(installers);
+ }
+
+ /**
+ * Get an Intent installer by given Intent type.
+ *
+ * @param cls the Intent type
+ * @param <T> the Intent type
+ * @return the Intent installer of the Intent type if exists; null otherwise
+ */
+ public <T extends Intent> IntentInstaller<T> getInstaller(Class<T> cls) {
+ return (IntentInstaller<T>) installers.get(cls);
+ }
+
+ /**
+ * Prevents object instantiation from external.
+ */
+ private static final class SingletonHelper {
+ private static final String ILLEGAL_ACCESS_MSG =
+ "Should not instantiate this class.";
+ private static final VirtualIntentInstallerRegistry INSTANCE =
+ new VirtualIntentInstallerRegistry();
+
+ private SingletonHelper() {
+ throw new IllegalAccessError(ILLEGAL_ACCESS_MSG);
+ }
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentProcessor.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentProcessor.java
new file mode 100644
index 0000000..a7719f9
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentProcessor.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentData;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * A collection of methods to process an intent for virtual networks.
+ *
+ * This interface is public, but intended to be used only by IntentManager and
+ * IntentProcessPhase subclasses stored under phase package.
+ */
+public interface VirtualIntentProcessor {
+ /**
+ * Compiles an intent recursively.
+ *
+ * @param networkId virtual network identifier
+ * @param intent intent
+ * @param previousInstallables previous intent installables
+ * @return result of compilation
+ */
+ List<Intent> compile(NetworkId networkId, Intent intent, List<Intent> previousInstallables);
+
+ /**
+ * Applies intents.
+ *
+ * @param networkId virtual network identifier
+ * @param toUninstall Intent data describing flows to uninstall.
+ * @param toInstall Intent data describing flows to install.
+ */
+ void apply(NetworkId networkId, Optional<IntentData> toUninstall,
+ Optional<IntentData> toInstall);
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentSkipped.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentSkipped.java
new file mode 100644
index 0000000..df7f451
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentSkipped.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent;
+
+import org.onosproject.incubator.net.virtual.impl.intent.phase.VirtualFinalIntentProcessPhase;
+import org.onosproject.net.intent.IntentData;
+
+/**
+ * Represents a phase where an intent is not compiled for a virtual network.
+ * This should be used if a new version of the intent will immediately override
+ * this one.
+ */
+public final class VirtualIntentSkipped extends VirtualFinalIntentProcessPhase {
+
+ private static final VirtualIntentSkipped SINGLETON = new VirtualIntentSkipped();
+
+ /**
+ * Returns a shared skipped phase.
+ *
+ * @return skipped phase
+ */
+ public static VirtualIntentSkipped getPhase() {
+ return SINGLETON;
+ }
+
+ // Prevent object construction; use getPhase()
+ private VirtualIntentSkipped() {
+ }
+
+ @Override
+ public IntentData data() {
+ return null;
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/package-info.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/package-info.java
new file mode 100644
index 0000000..c6758ae
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * Core subsystem for tracking high-level intents for treatment of selected
+ * network traffic for virtual networks.
+ */
+package org.onosproject.incubator.net.virtual.impl.intent;
\ No newline at end of file
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualFinalIntentProcessPhase.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualFinalIntentProcessPhase.java
new file mode 100644
index 0000000..978c95c
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualFinalIntentProcessPhase.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent.phase;
+
+import org.onosproject.net.intent.IntentData;
+
+import java.util.Optional;
+
+/**
+ * Represents a final phase of processing an intent for virtual networks.
+ */
+public abstract class VirtualFinalIntentProcessPhase
+ implements VirtualIntentProcessPhase {
+
+ @Override
+ public final Optional<VirtualIntentProcessPhase> execute() {
+ preExecute();
+ return Optional.empty();
+ }
+
+ /**
+ * Executes operations that must take place before the phase starts.
+ */
+ protected void preExecute() {}
+
+ /**
+ * Returns the IntentData object being acted on by this phase.
+ *
+ * @return intent data object for the phase
+ */
+ public abstract IntentData data();
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentCompiling.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentCompiling.java
new file mode 100644
index 0000000..639c04c
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentCompiling.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent.phase;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.impl.intent.VirtualIntentProcessor;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentData;
+import org.onosproject.net.intent.IntentException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.Optional;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Represents a phase where an intent is being compiled or recompiled
+ * for virtual networks.
+ */
+public class VirtualIntentCompiling implements VirtualIntentProcessPhase {
+ private static final Logger log = LoggerFactory.getLogger(VirtualIntentCompiling.class);
+
+ private final NetworkId networkId;
+ private final VirtualIntentProcessor processor;
+ private final IntentData data;
+ private final Optional<IntentData> stored;
+
+ /**
+ * Creates a intent recompiling phase.
+ *
+ * @param networkId virtual network identifier
+ * @param processor intent processor that does work for recompiling
+ * @param data intent data containing an intent to be recompiled
+ * @param stored intent data stored in the store
+ */
+ VirtualIntentCompiling(NetworkId networkId, VirtualIntentProcessor processor,
+ IntentData data, Optional<IntentData> stored) {
+ this.networkId = checkNotNull(networkId);
+ this.processor = checkNotNull(processor);
+ this.data = checkNotNull(data);
+ this.stored = checkNotNull(stored);
+ }
+
+ @Override
+ public Optional<VirtualIntentProcessPhase> execute() {
+ try {
+ List<Intent> compiled = processor
+ .compile(networkId, data.intent(),
+ //TODO consider passing an optional here in the future
+ stored.map(IntentData::installables).orElse(null));
+ return Optional.of(new VirtualIntentInstalling(networkId, processor,
+ IntentData.compiled(data, compiled), stored));
+ } catch (IntentException e) {
+ log.warn("Unable to compile intent {} due to:", data.intent(), e);
+ if (stored.filter(x -> !x.installables().isEmpty()).isPresent()) {
+ // removing orphaned flows and deallocating resources
+ return Optional.of(
+ new VirtualIntentWithdrawing(networkId, processor,
+ new IntentData(data, stored.get().installables())));
+ } else {
+ return Optional.of(new VirtualIntentFailed(data));
+ }
+ }
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentFailed.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentFailed.java
new file mode 100644
index 0000000..dff1d13
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentFailed.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent.phase;
+
+import org.onosproject.net.intent.IntentData;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.intent.IntentState.FAILED;
+
+/**
+ * Represents a phase where the compile has failed.
+ */
+public class VirtualIntentFailed extends VirtualFinalIntentProcessPhase {
+
+ private final IntentData data;
+
+ /**
+ * Create an instance with the specified data.
+ *
+ * @param data intentData
+ */
+ VirtualIntentFailed(IntentData data) {
+ this.data = IntentData.nextState(checkNotNull(data), FAILED);
+ }
+
+ @Override
+ public IntentData data() {
+ return data;
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentInstallRequest.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentInstallRequest.java
new file mode 100644
index 0000000..08bfec7
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentInstallRequest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent.phase;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.impl.intent.VirtualIntentProcessor;
+import org.onosproject.net.intent.IntentData;
+
+import java.util.Optional;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.incubator.net.virtual.impl.intent.phase.VirtualIntentProcessPhase.transferErrorCount;
+
+/**
+ * Represents a phase where intent installation has been requested
+ * for a virtual network.
+ */
+final class VirtualIntentInstallRequest implements VirtualIntentProcessPhase {
+
+ private final NetworkId networkId;
+ private final VirtualIntentProcessor processor;
+ private final IntentData data;
+ private final Optional<IntentData> stored;
+
+ /**
+ * Creates an install request phase.
+ *
+ * @param networkId virtual network identifier
+ * @param processor intent processor to be passed to intent process phases
+ * generated after this phase
+ * @param intentData intent data to be processed
+ * @param stored intent data stored in the store
+ */
+ VirtualIntentInstallRequest(NetworkId networkId, VirtualIntentProcessor processor,
+ IntentData intentData, Optional<IntentData> stored) {
+ this.networkId = checkNotNull(networkId);
+ this.processor = checkNotNull(processor);
+ this.data = checkNotNull(intentData);
+ this.stored = checkNotNull(stored);
+ }
+
+ @Override
+ public Optional<VirtualIntentProcessPhase> execute() {
+ transferErrorCount(data, stored);
+
+ return Optional.of(new VirtualIntentCompiling(networkId, processor, data, stored));
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentInstalling.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentInstalling.java
new file mode 100644
index 0000000..cd8a185
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentInstalling.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent.phase;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.impl.intent.VirtualIntentProcessor;
+import org.onosproject.net.intent.IntentData;
+
+import java.util.Optional;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.intent.IntentState.INSTALLING;
+
+/**
+ * Represents a phase where an intent is being installed for a virtual network.
+ */
+//FIXME: better way to implement intent phase and processing for virtual networks?
+public class VirtualIntentInstalling extends VirtualFinalIntentProcessPhase {
+
+ private final NetworkId networkId;
+ private final VirtualIntentProcessor processor;
+ private final IntentData data;
+ private final Optional<IntentData> stored;
+
+ /**
+ * Create an installing phase.
+ *
+ * @param networkId virtual network identifier
+ * @param processor intent processor that does work for installing
+ * @param data intent data containing an intent to be installed
+ * @param stored intent data already stored
+ */
+ VirtualIntentInstalling(NetworkId networkId, VirtualIntentProcessor processor,
+ IntentData data,
+ Optional<IntentData> stored) {
+ this.networkId = checkNotNull(networkId);
+ this.processor = checkNotNull(processor);
+ this.data = checkNotNull(data);
+ this.stored = checkNotNull(stored);
+ this.data.setState(INSTALLING);
+ }
+
+ @Override
+ public void preExecute() {
+ processor.apply(networkId, stored, Optional.of(data));
+ }
+
+ @Override
+ public IntentData data() {
+ return data;
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentProcessPhase.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentProcessPhase.java
new file mode 100644
index 0000000..99fab54
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentProcessPhase.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent.phase;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.impl.intent.VirtualIntentProcessor;
+import org.onosproject.net.intent.IntentData;
+
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * Represents a phase of processing an intent.
+ */
+public interface VirtualIntentProcessPhase {
+ /**
+ * Execute the procedure represented by the instance
+ * and generates the next update instance.
+ *
+ * @return next update
+ */
+ Optional<VirtualIntentProcessPhase> execute();
+
+ /**
+ * Create a starting intent process phase according to intent data this class holds.
+ *
+ * @param networkId virtual network identifier
+ * @param processor intent processor to be passed to intent process phases
+ * generated while this instance is working
+ * @param data intent data to be processed
+ * @param current intent date that is stored in the store
+ * @return starting intent process phase
+ */
+ static VirtualIntentProcessPhase newInitialPhase(NetworkId networkId,
+ VirtualIntentProcessor processor,
+ IntentData data, IntentData current) {
+ switch (data.request()) {
+ case INSTALL_REQ:
+ return new VirtualIntentInstallRequest(networkId, processor, data,
+ Optional.ofNullable(current));
+ case WITHDRAW_REQ:
+ return new VirtualIntentWithdrawRequest(networkId, processor, data,
+ Optional.ofNullable(current));
+ case PURGE_REQ:
+ return new VirtualIntentPurgeRequest(data, Optional.ofNullable(current));
+ default:
+ // illegal state
+ return new VirtualIntentFailed(data);
+ }
+ }
+
+ static VirtualFinalIntentProcessPhase process(VirtualIntentProcessPhase initial) {
+ Optional<VirtualIntentProcessPhase> currentPhase = Optional.of(initial);
+ VirtualIntentProcessPhase previousPhase = initial;
+
+ while (currentPhase.isPresent()) {
+ previousPhase = currentPhase.get();
+ currentPhase = previousPhase.execute();
+ }
+ return (VirtualFinalIntentProcessPhase) previousPhase;
+ }
+
+ static void transferErrorCount(IntentData data, Optional<IntentData> stored) {
+ stored.ifPresent(storedData -> {
+ if (Objects.equals(data.intent(), storedData.intent()) &&
+ Objects.equals(data.request(), storedData.request())) {
+ data.setErrorCount(storedData.errorCount());
+ } else {
+ data.setErrorCount(0);
+ }
+ });
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentPurgeRequest.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentPurgeRequest.java
new file mode 100644
index 0000000..a6f29ea
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentPurgeRequest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent.phase;
+
+import org.onosproject.net.intent.IntentData;
+import org.onosproject.net.intent.IntentState;
+import org.slf4j.Logger;
+
+import java.util.Optional;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Represents a phase of requesting a purge of an intent for a virtual network.
+ * Note: The purge will only succeed if the intent is FAILED or WITHDRAWN.
+ */
+final class VirtualIntentPurgeRequest extends VirtualFinalIntentProcessPhase {
+ private static final Logger log = getLogger(VirtualIntentPurgeRequest.class);
+
+ private final IntentData data;
+ protected final Optional<IntentData> stored;
+
+ VirtualIntentPurgeRequest(IntentData intentData, Optional<IntentData> stored) {
+ this.data = checkNotNull(intentData);
+ this.stored = checkNotNull(stored);
+ }
+
+ private boolean shouldAcceptPurge() {
+ if (!stored.isPresent()) {
+ log.info("Purge for intent {}, but intent is not present",
+ data.key());
+ return true;
+ }
+
+ IntentData storedData = stored.get();
+ if (storedData.state() == IntentState.WITHDRAWN
+ || storedData.state() == IntentState.FAILED) {
+ return true;
+ }
+ log.info("Purge for intent {} is rejected because intent state is {}",
+ data.key(), storedData.state());
+ return false;
+ }
+
+ @Override
+ public IntentData data() {
+ if (shouldAcceptPurge()) {
+ return data;
+ } else {
+ return stored.get();
+ }
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentWithdrawRequest.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentWithdrawRequest.java
new file mode 100644
index 0000000..ce8dfc9
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentWithdrawRequest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent.phase;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.impl.intent.VirtualIntentProcessor;
+import org.onosproject.net.intent.IntentData;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Optional;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.incubator.net.virtual.impl.intent.phase.VirtualIntentProcessPhase.transferErrorCount;
+
+/**
+ * Represents a phase of requesting a withdraw of an intent for a virtual network.
+ */
+final class VirtualIntentWithdrawRequest implements VirtualIntentProcessPhase {
+ private static final Logger log = LoggerFactory.getLogger(VirtualIntentWithdrawRequest.class);
+
+ private final NetworkId networkId;
+ private final VirtualIntentProcessor processor;
+ private final IntentData data;
+ private final Optional<IntentData> stored;
+
+ /**
+ * Creates a withdraw request phase.
+ *
+ * @param networkId virtual network identifier
+ * @param processor intent processor to be passed to intent process phases
+ * generated after this phase
+ * @param intentData intent data to be processed
+ * @param stored intent data stored in the store
+ */
+ VirtualIntentWithdrawRequest(NetworkId networkId, VirtualIntentProcessor processor,
+ IntentData intentData, Optional<IntentData> stored) {
+ this.networkId = checkNotNull(networkId);
+ this.processor = checkNotNull(processor);
+ this.data = checkNotNull(intentData);
+ this.stored = checkNotNull(stored);
+ }
+
+ @Override
+ public Optional<VirtualIntentProcessPhase> execute() {
+ //TODO perhaps we want to validate that the pending and current are the
+ // same version i.e. they are the same
+ // Note: this call is not just the symmetric version of submit
+
+ transferErrorCount(data, stored);
+
+ if (!stored.isPresent() || stored.get().installables().isEmpty()) {
+ switch (data.request()) {
+ case INSTALL_REQ:
+ // illegal state?
+ log.warn("{} was requested to withdraw during installation?", data.intent());
+ return Optional.of(new VirtualIntentFailed(data));
+ case WITHDRAW_REQ:
+ default: //TODO "default" case should not happen
+ return Optional.of(new VirtualIntentWithdrawn(data));
+ }
+ }
+
+ return Optional.of(new VirtualIntentWithdrawing(networkId, processor,
+ new IntentData(data, stored.get().installables())));
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentWithdrawing.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentWithdrawing.java
new file mode 100644
index 0000000..f7a2867
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentWithdrawing.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent.phase;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.impl.intent.VirtualIntentProcessor;
+import org.onosproject.net.intent.IntentData;
+
+import java.util.Optional;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.intent.IntentState.WITHDRAWING;
+
+/**
+ * Represents a phase where an intent is withdrawing.
+ */
+final class VirtualIntentWithdrawing extends VirtualFinalIntentProcessPhase {
+
+ private final NetworkId networkId;
+ private final VirtualIntentProcessor processor;
+ private final IntentData data;
+
+ /**
+ * Creates a withdrawing phase.
+ *
+ * @param networkId virtual network identifier
+ * @param processor intent processor that does work for withdrawing
+ * @param data intent data containing an intent to be withdrawn
+ */
+ VirtualIntentWithdrawing(NetworkId networkId, VirtualIntentProcessor processor,
+ IntentData data) {
+ this.networkId = checkNotNull(networkId);
+ this.processor = checkNotNull(processor);
+ this.data = checkNotNull(data);
+ this.data.setState(WITHDRAWING);
+ }
+
+ @Override
+ protected void preExecute() {
+ processor.apply(networkId, Optional.of(data), Optional.empty());
+ }
+
+ @Override
+ public IntentData data() {
+ return data;
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentWithdrawn.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentWithdrawn.java
new file mode 100644
index 0000000..7e592c2
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentWithdrawn.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent.phase;
+
+import org.onosproject.net.intent.IntentData;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.intent.IntentState.WITHDRAWN;
+
+/**
+ * Represents a phase where an intent has been withdrawn for a virtual network.
+ */
+final class VirtualIntentWithdrawn extends VirtualFinalIntentProcessPhase {
+
+ private final IntentData data;
+
+ /**
+ * Create a withdrawn phase.
+ *
+ * @param data intent data containing an intent to be withdrawn
+ */
+ VirtualIntentWithdrawn(IntentData data) {
+ this.data = IntentData.nextState(checkNotNull(data), WITHDRAWN);
+ }
+
+ @Override
+ public IntentData data() {
+ return data;
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/package-info.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/package-info.java
new file mode 100644
index 0000000..5fac168
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/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.
+ */
+
+/**
+ * Implementations of various intent processing phases for virtual networks.
+ */
+package org.onosproject.incubator.net.virtual.impl.intent.phase;
\ No newline at end of file
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/package-info.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/package-info.java
new file mode 100644
index 0000000..b0750eb
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015-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.
+ */
+
+/**
+ * Implementation of the virtual network subsystem.
+ */
+package org.onosproject.incubator.net.virtual.impl;
\ No newline at end of file
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualFlowRuleProvider.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualFlowRuleProvider.java
new file mode 100644
index 0000000..436e1d4
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualFlowRuleProvider.java
@@ -0,0 +1,792 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.impl.provider;
+
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Table;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.VirtualPort;
+import org.onosproject.incubator.net.virtual.provider.AbstractVirtualProvider;
+import org.onosproject.incubator.net.virtual.provider.InternalRoutingAlgorithm;
+import org.onosproject.incubator.net.virtual.provider.VirtualFlowRuleProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualFlowRuleProviderService;
+import org.onosproject.incubator.net.virtual.provider.VirtualProviderRegistryService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.BatchOperationEntry;
+import org.onosproject.net.flow.CompletedBatchOperation;
+import org.onosproject.net.flow.DefaultFlowEntry;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleEvent;
+import org.onosproject.net.flow.FlowRuleListener;
+import org.onosproject.net.flow.FlowRuleOperations;
+import org.onosproject.net.flow.FlowRuleOperationsContext;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.PortCriterion;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.net.topology.TopologyService;
+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.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.ImmutableSet.copyOf;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Provider that translate virtual flow rules into physical rules.
+ * Current implementation is based on FlowRules.
+ * This virtualize and de-virtualize virtual flow rules into physical flow rules.
+ * {@link org.onosproject.net.flow.FlowRule}
+ */
+@Component(service = VirtualFlowRuleProvider.class)
+public class DefaultVirtualFlowRuleProvider extends AbstractVirtualProvider
+ implements VirtualFlowRuleProvider {
+
+ private static final String APP_ID_STR = "org.onosproject.virtual.vnet-flow_";
+
+ private final Logger log = getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected TopologyService topologyService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected VirtualNetworkService vnService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected FlowRuleService flowRuleService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected DeviceService deviceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected VirtualProviderRegistryService providerRegistryService;
+
+ private InternalRoutingAlgorithm internalRoutingAlgorithm;
+ private InternalVirtualFlowRuleManager frm;
+ private ApplicationId appId;
+ private FlowRuleListener flowRuleListener;
+
+ /**
+ * Creates a provider with the identifier.
+ */
+ public DefaultVirtualFlowRuleProvider() {
+ super(new ProviderId("vnet-flow", "org.onosproject.virtual.vnet-flow"));
+ }
+
+ @Activate
+ public void activate() {
+ appId = coreService.registerApplication(APP_ID_STR);
+
+ providerRegistryService.registerProvider(this);
+
+ flowRuleListener = new InternalFlowRuleListener();
+ flowRuleService.addListener(flowRuleListener);
+
+ internalRoutingAlgorithm = new DefaultInternalRoutingAlgorithm();
+ frm = new InternalVirtualFlowRuleManager();
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ flowRuleService.removeListener(flowRuleListener);
+ flowRuleService.removeFlowRulesById(appId);
+ providerRegistryService.unregisterProvider(this);
+ log.info("Stopped");
+ }
+
+ @Modified
+ protected void modified(ComponentContext context) {
+ Dictionary<?, ?> properties = context.getProperties();
+ }
+
+ @Override
+ public void applyFlowRule(NetworkId networkId, FlowRule... flowRules) {
+ for (FlowRule flowRule : flowRules) {
+ devirtualize(networkId, flowRule).forEach(
+ r -> flowRuleService.applyFlowRules(r));
+ }
+ }
+
+ @Override
+ public void removeFlowRule(NetworkId networkId, FlowRule... flowRules) {
+ for (FlowRule flowRule : flowRules) {
+ devirtualize(networkId, flowRule).forEach(
+ r -> flowRuleService.removeFlowRules(r));
+ }
+ }
+
+ @Override
+ public void executeBatch(NetworkId networkId, FlowRuleBatchOperation batch) {
+ checkNotNull(batch);
+
+ for (FlowRuleBatchEntry fop : batch.getOperations()) {
+ FlowRuleOperations.Builder builder = FlowRuleOperations.builder();
+
+ switch (fop.operator()) {
+ case ADD:
+ devirtualize(networkId, fop.target()).forEach(builder::add);
+ break;
+ case REMOVE:
+ devirtualize(networkId, fop.target()).forEach(builder::remove);
+ break;
+ case MODIFY:
+ devirtualize(networkId, fop.target()).forEach(builder::modify);
+ break;
+ default:
+ break;
+ }
+
+ flowRuleService.apply(builder.build(new FlowRuleOperationsContext() {
+ @Override
+ public void onSuccess(FlowRuleOperations ops) {
+ CompletedBatchOperation status =
+ new CompletedBatchOperation(true,
+ Sets.newConcurrentHashSet(),
+ batch.deviceId());
+
+ VirtualFlowRuleProviderService providerService =
+ (VirtualFlowRuleProviderService) providerRegistryService
+ .getProviderService(networkId,
+ VirtualFlowRuleProvider.class);
+ providerService.batchOperationCompleted(batch.id(), status);
+ }
+
+ @Override
+ public void onError(FlowRuleOperations ops) {
+ Set<FlowRule> failures = ImmutableSet.copyOf(
+ Lists.transform(batch.getOperations(),
+ BatchOperationEntry::target));
+
+ CompletedBatchOperation status =
+ new CompletedBatchOperation(false,
+ failures,
+ batch.deviceId());
+
+ VirtualFlowRuleProviderService providerService =
+ (VirtualFlowRuleProviderService) providerRegistryService
+ .getProviderService(networkId,
+ VirtualFlowRuleProvider.class);
+ providerService.batchOperationCompleted(batch.id(), status);
+ }
+ }));
+ }
+ }
+
+ public void setEmbeddingAlgorithm(InternalRoutingAlgorithm
+ internalRoutingAlgorithm) {
+ this.internalRoutingAlgorithm = internalRoutingAlgorithm;
+ }
+
+ /**
+ * Translate the requested physical flow rules into virtual flow rules.
+ *
+ * @param flowRule A virtual flow rule to be translated
+ * @return A flow rule for a specific virtual network
+ */
+ private FlowRule virtualizeFlowRule(FlowRule flowRule) {
+
+ FlowRule storedrule = frm.getVirtualRule(flowRule);
+
+ if (flowRule.reason() == FlowRule.FlowRemoveReason.NO_REASON) {
+ return storedrule;
+ } else {
+ return DefaultFlowRule.builder()
+ .withReason(flowRule.reason())
+ .withPriority(storedrule.priority())
+ .forDevice(storedrule.deviceId())
+ .forTable(storedrule.tableId())
+ .fromApp(new DefaultApplicationId(storedrule.appId(), null))
+ .withIdleTimeout(storedrule.timeout())
+ .withHardTimeout(storedrule.hardTimeout())
+ .withSelector(storedrule.selector())
+ .withTreatment(storedrule.treatment())
+ .build();
+ }
+ }
+
+ private FlowEntry virtualize(FlowEntry flowEntry) {
+ FlowRule vRule = virtualizeFlowRule(flowEntry);
+
+ if (vRule == null) {
+ return null;
+ }
+
+ FlowEntry vEntry = new DefaultFlowEntry(vRule, flowEntry.state(),
+ flowEntry.life(),
+ flowEntry.packets(),
+ flowEntry.bytes());
+ return vEntry;
+ }
+
+ /**
+ * Translate the requested virtual flow rules into physical flow rules.
+ * The translation could be one to many.
+ *
+ * @param flowRule A flow rule from underlying data plane to be translated
+ * @return A set of flow rules for physical network
+ */
+ private Set<FlowRule> devirtualize(NetworkId networkId, FlowRule flowRule) {
+
+ Set<FlowRule> outRules = new HashSet<>();
+
+ Set<ConnectPoint> ingressPoints = extractIngressPoints(networkId,
+ flowRule.deviceId(),
+ flowRule.selector());
+
+ ConnectPoint egressPoint = extractEgressPoints(networkId,
+ flowRule.deviceId(),
+ flowRule.treatment());
+
+ if (egressPoint == null) {
+ return outRules;
+ }
+
+ TrafficSelector.Builder commonSelectorBuilder
+ = DefaultTrafficSelector.builder();
+ flowRule.selector().criteria().stream()
+ .filter(c -> c.type() != Criterion.Type.IN_PORT)
+ .forEach(c -> commonSelectorBuilder.add(c));
+ TrafficSelector commonSelector = commonSelectorBuilder.build();
+
+ TrafficTreatment.Builder commonTreatmentBuilder
+ = DefaultTrafficTreatment.builder();
+ flowRule.treatment().allInstructions().stream()
+ .filter(i -> i.type() != Instruction.Type.OUTPUT)
+ .forEach(i -> commonTreatmentBuilder.add(i));
+ TrafficTreatment commonTreatment = commonTreatmentBuilder.build();
+
+ for (ConnectPoint ingressPoint : ingressPoints) {
+ if (egressPoint.port() == PortNumber.FLOOD) {
+ Set<ConnectPoint> outPoints = vnService
+ .getVirtualPorts(networkId, flowRule.deviceId())
+ .stream()
+ .map(VirtualPort::realizedBy)
+ .filter(p -> !p.equals(ingressPoint))
+ .collect(Collectors.toSet());
+
+ for (ConnectPoint outPoint : outPoints) {
+ outRules.addAll(generateRules(networkId, ingressPoint, outPoint,
+ commonSelector, commonTreatment, flowRule));
+ }
+ } else {
+ outRules.addAll(generateRules(networkId, ingressPoint, egressPoint,
+ commonSelector, commonTreatment, flowRule));
+ }
+ }
+
+ return outRules;
+ }
+
+ /**
+ * Extract ingress connect points of the physical network
+ * from the requested traffic selector.
+ *
+ * @param networkId the virtual network identifier
+ * @param deviceId the virtual device identifier
+ * @param selector the traffic selector to extract ingress point
+ * @return the set of ingress connect points of the physical network
+ */
+ private Set<ConnectPoint> extractIngressPoints(NetworkId networkId,
+ DeviceId deviceId,
+ TrafficSelector selector) {
+
+ Set<ConnectPoint> ingressPoints = new HashSet<>();
+
+ Set<VirtualPort> vPorts = vnService
+ .getVirtualPorts(networkId, deviceId);
+
+ PortCriterion portCriterion = ((PortCriterion) selector
+ .getCriterion(Criterion.Type.IN_PORT));
+
+ if (portCriterion != null) {
+ PortNumber vInPortNum = portCriterion.port();
+
+ Optional<ConnectPoint> optionalCp = vPorts.stream()
+ .filter(v -> v.number().equals(vInPortNum))
+ .map(VirtualPort::realizedBy).findFirst();
+ if (!optionalCp.isPresent()) {
+ log.warn("Port {} is not realized yet, in Network {}, Device {}",
+ vInPortNum, networkId, deviceId);
+ return ingressPoints;
+ }
+
+ ingressPoints.add(optionalCp.get());
+ } else {
+ for (VirtualPort vPort : vPorts) {
+ if (vPort.realizedBy() != null) {
+ ingressPoints.add(vPort.realizedBy());
+ } else {
+ log.warn("Port {} is not realized yet, in Network {}, " +
+ "Device {}",
+ vPort, networkId, deviceId);
+ }
+ }
+ }
+
+ return ingressPoints;
+ }
+
+ /**
+ * Extract egress connect point of the physical network
+ * from the requested traffic treatment.
+ *
+ * @param networkId the virtual network identifier
+ * @param deviceId the virtual device identifier
+ * @param treatment the traffic treatment to extract ingress point
+ * @return the egress connect point of the physical network
+ */
+ private ConnectPoint extractEgressPoints(NetworkId networkId,
+ DeviceId deviceId,
+ TrafficTreatment treatment) {
+
+ Set<VirtualPort> vPorts = vnService
+ .getVirtualPorts(networkId, deviceId);
+
+ PortNumber vOutPortNum = treatment.allInstructions().stream()
+ .filter(i -> i.type() == Instruction.Type.OUTPUT)
+ .map(i -> ((Instructions.OutputInstruction) i).port())
+ .findFirst().get();
+
+ Optional<ConnectPoint> optionalCpOut = vPorts.stream()
+ .filter(v -> v.number().equals(vOutPortNum))
+ .map(VirtualPort::realizedBy)
+ .findFirst();
+
+ if (!optionalCpOut.isPresent()) {
+ if (vOutPortNum.isLogical()) {
+ return new ConnectPoint(DeviceId.deviceId("vNet"), vOutPortNum);
+ }
+
+ log.warn("Port {} is not realized yet, in Network {}, Device {}",
+ vOutPortNum, networkId, deviceId);
+ return null;
+ }
+
+ return optionalCpOut.get();
+ }
+
+
+ /**
+ * Generates the corresponding flow rules for the physical network.
+ *
+ * @param networkId The virtual network identifier
+ * @param ingressPoint The ingress point of the physical network
+ * @param egressPoint The egress point of the physical network
+ * @param commonSelector A common traffic selector between the virtual
+ * and physical flow rules
+ * @param commonTreatment A common traffic treatment between the virtual
+ * and physical flow rules
+ * @param flowRule The virtual flow rule to be translated
+ * @return A set of flow rules for the physical network
+ */
+ private Set<FlowRule> generateRules(NetworkId networkId,
+ ConnectPoint ingressPoint,
+ ConnectPoint egressPoint,
+ TrafficSelector commonSelector,
+ TrafficTreatment commonTreatment,
+ FlowRule flowRule) {
+
+ if (ingressPoint.deviceId().equals(egressPoint.deviceId()) ||
+ egressPoint.port().isLogical()) {
+ return generateRuleForSingle(networkId, ingressPoint, egressPoint,
+ commonSelector, commonTreatment, flowRule);
+ } else {
+ return generateRuleForMulti(networkId, ingressPoint, egressPoint,
+ commonSelector, commonTreatment, flowRule);
+ }
+ }
+
+ /**
+ * Generate physical rules when a virtual flow rule can be handled inside
+ * a single physical switch.
+ *
+ * @param networkId The virtual network identifier
+ * @param ingressPoint The ingress point of the physical network
+ * @param egressPoint The egress point of the physical network
+ * @param commonSelector A common traffic selector between the virtual
+ * and physical flow rules
+ * @param commonTreatment A common traffic treatment between the virtual
+ * and physical flow rules
+ * @param flowRule The virtual flow rule to be translated
+ * @return A set of flow rules for the physical network
+ */
+ private Set<FlowRule> generateRuleForSingle(NetworkId networkId,
+ ConnectPoint ingressPoint,
+ ConnectPoint egressPoint,
+ TrafficSelector commonSelector,
+ TrafficTreatment commonTreatment,
+ FlowRule flowRule) {
+
+ Set<FlowRule> outRules = new HashSet<>();
+
+ TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector
+ .builder(commonSelector)
+ .matchInPort(ingressPoint.port());
+
+ TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment
+ .builder(commonTreatment)
+ .setOutput(egressPoint.port());
+
+ FlowRule.Builder ruleBuilder = DefaultFlowRule.builder()
+ .fromApp(vnService.getVirtualNetworkApplicationId(networkId))
+ .forDevice(ingressPoint.deviceId())
+ .withSelector(selectorBuilder.build())
+ .withTreatment(treatmentBuilder.build())
+ .withIdleTimeout(flowRule.timeout())
+ .withPriority(flowRule.priority());
+
+ FlowRule rule = ruleBuilder.build();
+ frm.addIngressRule(flowRule, rule, networkId);
+ outRules.add(rule);
+
+ return outRules;
+ }
+
+ /**
+ * Generate physical rules when a virtual flow rule can be handled with
+ * multiple physical switches.
+ *
+ * @param networkId The virtual network identifier
+ * @param ingressPoint The ingress point of the physical network
+ * @param egressPoint The egress point of the physical network
+ * @param commonSelector A common traffic selector between the virtual
+ * and physical flow rules
+ * @param commonTreatment A common traffic treatment between the virtual
+ * and physical flow rules
+ * @param flowRule The virtual flow rule to be translated
+ * @return A set of flow rules for the physical network
+ */
+ private Set<FlowRule> generateRuleForMulti(NetworkId networkId,
+ ConnectPoint ingressPoint,
+ ConnectPoint egressPoint,
+ TrafficSelector commonSelector,
+ TrafficTreatment commonTreatment,
+ FlowRule flowRule) {
+ Set<FlowRule> outRules = new HashSet<>();
+
+ Path internalPath = internalRoutingAlgorithm
+ .findPath(ingressPoint, egressPoint);
+ checkNotNull(internalPath, "No path between " +
+ ingressPoint.toString() + " " + egressPoint.toString());
+
+ ConnectPoint outCp = internalPath.links().get(0).src();
+
+ //ingress point of tunnel
+ TrafficSelector.Builder selectorBuilder =
+ DefaultTrafficSelector.builder(commonSelector);
+ selectorBuilder.matchInPort(ingressPoint.port());
+
+ TrafficTreatment.Builder treatmentBuilder =
+ DefaultTrafficTreatment.builder(commonTreatment);
+ //TODO: add the logic to check host location
+ treatmentBuilder.pushVlan()
+ .setVlanId(VlanId.vlanId(networkId.id().shortValue()));
+ treatmentBuilder.setOutput(outCp.port());
+
+ FlowRule.Builder ruleBuilder = DefaultFlowRule.builder()
+ .fromApp(vnService.getVirtualNetworkApplicationId(networkId))
+ .forDevice(ingressPoint.deviceId())
+ .withSelector(selectorBuilder.build())
+ .withIdleTimeout(flowRule.timeout())
+ .withTreatment(treatmentBuilder.build())
+ .withPriority(flowRule.priority());
+
+ FlowRule rule = ruleBuilder.build();
+ frm.addIngressRule(flowRule, rule, networkId);
+ outRules.add(rule);
+
+ //routing inside tunnel
+ ConnectPoint inCp = internalPath.links().get(0).dst();
+
+ if (internalPath.links().size() > 1) {
+ for (Link l : internalPath.links()
+ .subList(1, internalPath.links().size())) {
+
+ outCp = l.src();
+
+ selectorBuilder = DefaultTrafficSelector
+ .builder(commonSelector)
+ .matchVlanId(VlanId.vlanId(networkId.id().shortValue()))
+ .matchInPort(inCp.port());
+
+ treatmentBuilder = DefaultTrafficTreatment
+ .builder(commonTreatment)
+ .setOutput(outCp.port());
+
+ ruleBuilder = DefaultFlowRule.builder()
+ .fromApp(vnService.getVirtualNetworkApplicationId(networkId))
+ .forDevice(inCp.deviceId())
+ .withSelector(selectorBuilder.build())
+ .withTreatment(treatmentBuilder.build())
+ .withIdleTimeout(flowRule.timeout())
+ .withPriority(flowRule.priority());
+
+ outRules.add(ruleBuilder.build());
+ inCp = l.dst();
+ }
+ }
+
+ //egress point of tunnel
+ selectorBuilder = DefaultTrafficSelector.builder(commonSelector)
+ .matchVlanId(VlanId.vlanId(networkId.id().shortValue()))
+ .matchInPort(inCp.port());
+
+ treatmentBuilder = DefaultTrafficTreatment.builder(commonTreatment)
+ .popVlan()
+ .setOutput(egressPoint.port());
+
+ ruleBuilder = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .forDevice(egressPoint.deviceId())
+ .withSelector(selectorBuilder.build())
+ .withTreatment(treatmentBuilder.build())
+ .withIdleTimeout(flowRule.timeout())
+ .withPriority(flowRule.priority());
+
+ outRules.add(ruleBuilder.build());
+
+ return outRules;
+ }
+
+ private class InternalFlowRuleListener implements FlowRuleListener {
+ @Override
+ public void event(FlowRuleEvent event) {
+ if ((event.type() == FlowRuleEvent.Type.RULE_ADDED) ||
+ (event.type() == FlowRuleEvent.Type.RULE_UPDATED)) {
+ if (frm.isVirtualIngressRule(event.subject())) {
+ NetworkId networkId = frm.getVirtualNetworkId(event.subject());
+ FlowEntry vEntry = getVirtualFlowEntry(event.subject());
+
+ if (vEntry == null) {
+ return;
+ }
+
+ frm.addOrUpdateFlowEntry(networkId, vEntry.deviceId(), vEntry);
+
+ VirtualFlowRuleProviderService providerService =
+ (VirtualFlowRuleProviderService) providerRegistryService
+ .getProviderService(networkId,
+ VirtualFlowRuleProvider.class);
+
+ ImmutableList.Builder<FlowEntry> builder = ImmutableList.builder();
+ builder.addAll(frm.getFlowEntries(networkId, vEntry.deviceId()));
+
+ providerService.pushFlowMetrics(vEntry.deviceId(), builder.build());
+ }
+ } else if (event.type() == FlowRuleEvent.Type.RULE_REMOVED) {
+ if (frm.isVirtualIngressRule(event.subject())) {
+ //FIXME confirm all physical rules are removed
+ NetworkId networkId = frm.getVirtualNetworkId(event.subject());
+ FlowEntry vEntry = getVirtualFlowEntry(event.subject());
+
+ if (vEntry == null) {
+ return;
+ }
+
+ frm.removeFlowEntry(networkId, vEntry.deviceId(), vEntry);
+ frm.removeFlowRule(networkId, vEntry.deviceId(), vEntry);
+
+ VirtualFlowRuleProviderService providerService =
+ (VirtualFlowRuleProviderService) providerRegistryService
+ .getProviderService(networkId,
+ VirtualFlowRuleProvider.class);
+ providerService.flowRemoved(vEntry);
+ }
+ }
+ }
+
+ private FlowEntry getVirtualFlowEntry(FlowRule rule) {
+ FlowEntry entry = null;
+ for (FlowEntry fe :
+ flowRuleService.getFlowEntries(rule.deviceId())) {
+ if (rule.exactMatch(fe)) {
+ entry = fe;
+ }
+ }
+
+ if (entry != null) {
+ return virtualize(entry);
+ } else {
+ return virtualize(new DefaultFlowEntry(rule,
+ FlowEntry.FlowEntryState.PENDING_REMOVE));
+ }
+ }
+ }
+
+ private class InternalVirtualFlowRuleManager {
+ /** <Virtual Network ID, Virtual Device ID, Virtual Flow Rules>.*/
+ final Table<NetworkId, DeviceId, Set<FlowRule>> flowRuleTable
+ = HashBasedTable.create();
+
+ /** <Virtual Network ID, Virtual Device ID, Virtual Flow Entries>.*/
+ final Table<NetworkId, DeviceId, Set<FlowEntry>> flowEntryTable
+ = HashBasedTable.create();
+
+ /** <Physical Flow Rule, Virtual Network ID>.*/
+ final Map<FlowRule, NetworkId> ingressRuleMap = Maps.newHashMap();
+
+ /** <Physical Flow Rule, Virtual Virtual Flow Rule>.*/
+ final Map<FlowRule, FlowRule> virtualizationMap = Maps.newHashMap();
+
+ private Iterable<FlowRule> getFlowRules(NetworkId networkId,
+ DeviceId deviceId) {
+ return flowRuleTable.get(networkId, deviceId);
+ }
+
+ private Iterable<FlowEntry> getFlowEntries(NetworkId networkId,
+ DeviceId deviceId) {
+ return flowEntryTable.get(networkId, deviceId);
+ }
+
+ private void addFlowRule(NetworkId networkId, DeviceId deviceId,
+ FlowRule flowRule) {
+ Set<FlowRule> set = flowRuleTable.get(networkId, deviceId);
+ if (set == null) {
+ set = Sets.newHashSet();
+ flowRuleTable.put(networkId, deviceId, set);
+ }
+ set.add(flowRule);
+ }
+
+ private void removeFlowRule(NetworkId networkId, DeviceId deviceId,
+ FlowRule flowRule) {
+ Set<FlowRule> set = flowRuleTable.get(networkId, deviceId);
+ if (set == null) {
+ return;
+ }
+ set.remove(flowRule);
+ }
+
+ private void addOrUpdateFlowEntry(NetworkId networkId, DeviceId deviceId,
+ FlowEntry flowEntry) {
+ Set<FlowEntry> set = flowEntryTable.get(networkId, deviceId);
+ if (set == null) {
+ set = Sets.newConcurrentHashSet();
+ flowEntryTable.put(networkId, deviceId, set);
+ }
+
+ //Replace old entry with new one
+ set.stream().filter(fe -> fe.exactMatch(flowEntry))
+ .forEach(set::remove);
+ set.add(flowEntry);
+ }
+
+ private void removeFlowEntry(NetworkId networkId, DeviceId deviceId,
+ FlowEntry flowEntry) {
+ Set<FlowEntry> set = flowEntryTable.get(networkId, deviceId);
+ if (set == null) {
+ return;
+ }
+ set.remove(flowEntry);
+ }
+
+ private void addIngressRule(FlowRule virtualRule, FlowRule physicalRule,
+ NetworkId networkId) {
+ ingressRuleMap.put(physicalRule, networkId);
+ virtualizationMap.put(physicalRule, virtualRule);
+ }
+
+ private FlowRule getVirtualRule(FlowRule physicalRule) {
+ return virtualizationMap.get(physicalRule);
+ }
+
+ private void removeIngressRule(FlowRule physicalRule) {
+ ingressRuleMap.remove(physicalRule);
+ virtualizationMap.remove(physicalRule);
+ }
+
+ private Set<FlowRule> getAllPhysicalRule() {
+ return copyOf(virtualizationMap.keySet());
+ }
+
+ private NetworkId getVirtualNetworkId(FlowRule physicalRule) {
+ return ingressRuleMap.get(physicalRule);
+ }
+
+ /**
+ * Test the rule is the ingress rule for virtual rules.
+ *
+ * @param flowRule A flow rule from underlying data plane to be translated
+ * @return True when the rule is for ingress point for a virtual switch
+ */
+ private boolean isVirtualIngressRule(FlowRule flowRule) {
+ return ingressRuleMap.containsKey(flowRule);
+ }
+ }
+
+ private class DefaultInternalRoutingAlgorithm
+ implements InternalRoutingAlgorithm {
+
+ @Override
+ public Path findPath(ConnectPoint src, ConnectPoint dst) {
+ Set<Path> paths =
+ topologyService.getPaths(topologyService.currentTopology(),
+ src.deviceId(),
+ dst.deviceId());
+
+ if (paths.isEmpty()) {
+ return null;
+ }
+
+ //TODO the logic find the best path
+ return (Path) paths.toArray()[0];
+ }
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualGroupProvider.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualGroupProvider.java
new file mode 100644
index 0000000..7cc86cc
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualGroupProvider.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.provider;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.provider.AbstractVirtualProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualGroupProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualProviderRegistryService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.group.GroupEvent;
+import org.onosproject.net.group.GroupListener;
+import org.onosproject.net.group.GroupOperation;
+import org.onosproject.net.group.GroupOperations;
+import org.onosproject.net.group.GroupService;
+import org.onosproject.net.provider.ProviderId;
+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.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Provider to handle Group for virtual network.
+ */
+@Component(service = VirtualGroupProvider.class)
+public class DefaultVirtualGroupProvider extends AbstractVirtualProvider
+ implements VirtualGroupProvider {
+
+ private final Logger log = getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected VirtualProviderRegistryService providerRegistryService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected GroupService groupService;
+
+ private InternalGroupEventListener internalGroupEventListener;
+
+ /**
+ * Creates a virtual provider with the supplied identifier.
+ */
+ public DefaultVirtualGroupProvider() {
+ super(new ProviderId("vnet-group", "org.onosproject.virtual.of-group"));
+ }
+
+ @Activate
+ public void activate() {
+ providerRegistryService.registerProvider(this);
+
+ internalGroupEventListener = new InternalGroupEventListener();
+ groupService.addListener(internalGroupEventListener);
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ groupService.removeListener(internalGroupEventListener);
+ providerRegistryService.unregisterProvider(this);
+ }
+
+ @Modified
+ protected void modified(ComponentContext context) {
+ }
+
+ @Override
+ public void performGroupOperation(NetworkId networkId, DeviceId deviceId, GroupOperations groupOps) {
+ for (GroupOperation groupOperation: groupOps.operations()) {
+ switch (groupOperation.opType()) {
+ case ADD:
+ //TODO: devirtualize + groupAdd
+ log.info("Group Add is not supported, yet");
+ break;
+ case MODIFY:
+ //TODO: devirtualize + groupMod
+ log.info("Group Modify is not supported, yet");
+ break;
+ case DELETE:
+ //TODO: devirtualize + groupDel
+ log.info("Group Delete is not supported, yet");
+ break;
+ default:
+ log.error("Unsupported Group operation");
+ return;
+ }
+ }
+ }
+
+ private class InternalGroupEventListener implements GroupListener {
+ @Override
+ public void event(GroupEvent event) {
+ switch (event.type()) {
+ //TODO: virtualize + notify to virtual provider service
+ case GROUP_ADD_REQUESTED:
+ case GROUP_UPDATE_REQUESTED:
+ case GROUP_REMOVE_REQUESTED:
+ case GROUP_ADDED:
+ case GROUP_UPDATED:
+ case GROUP_REMOVED:
+ case GROUP_ADD_FAILED:
+ case GROUP_UPDATE_FAILED:
+ case GROUP_REMOVE_FAILED:
+ case GROUP_BUCKET_FAILOVER:
+ default:
+ break;
+ }
+ }
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualMeterProvider.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualMeterProvider.java
new file mode 100644
index 0000000..26462ad
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualMeterProvider.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.provider;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.RemovalCause;
+import com.google.common.cache.RemovalNotification;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.provider.AbstractVirtualProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualMeterProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualMeterProviderService;
+import org.onosproject.incubator.net.virtual.provider.VirtualProviderRegistryService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.meter.Meter;
+import org.onosproject.net.meter.MeterEvent;
+import org.onosproject.net.meter.MeterFailReason;
+import org.onosproject.net.meter.MeterListener;
+import org.onosproject.net.meter.MeterOperation;
+import org.onosproject.net.meter.MeterOperations;
+import org.onosproject.net.meter.MeterService;
+import org.onosproject.net.provider.ProviderId;
+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.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Provider to handle meters for virtual networks.
+ */
+@Component(service = VirtualMeterProvider.class)
+public class DefaultVirtualMeterProvider extends AbstractVirtualProvider
+ implements VirtualMeterProvider {
+
+ private final Logger log = getLogger(getClass());
+
+ static final long TIMEOUT = 30;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected VirtualProviderRegistryService providerRegistryService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected MeterService meterService;
+
+ private MeterListener internalMeterListener;
+ private Cache<Long, VirtualMeterOperation> pendingOperations;
+ private IdGenerator idGenerator;
+
+ @Activate
+ public void activate() {
+ providerRegistryService.registerProvider(this);
+ internalMeterListener = new InternalMeterListener();
+
+ idGenerator = getIdGenerator();
+
+ pendingOperations = CacheBuilder.newBuilder()
+ .expireAfterWrite(TIMEOUT, TimeUnit.SECONDS)
+ .removalListener(
+ (RemovalNotification<Long, VirtualMeterOperation>
+ notification) -> {
+ if (notification.getCause() == RemovalCause.EXPIRED) {
+ NetworkId networkId = notification.getValue().networkId();
+ MeterOperation op = notification.getValue().operation();
+
+ VirtualMeterProviderService providerService =
+ (VirtualMeterProviderService) providerRegistryService
+ .getProviderService(networkId,
+ VirtualMeterProvider.class);
+
+ providerService.meterOperationFailed(op,
+ MeterFailReason.TIMEOUT);
+ }
+ }).build();
+
+ meterService.addListener(internalMeterListener);
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ meterService.removeListener(internalMeterListener);
+ providerRegistryService.unregisterProvider(this);
+ }
+
+ /**
+ * Creates a provider with the identifier.
+ */
+ public DefaultVirtualMeterProvider() {
+ super(new ProviderId("vnet-meter",
+ "org.onosproject.virtual.vnet-meter"));
+ }
+
+ @Override
+ public void performMeterOperation(NetworkId networkId, DeviceId deviceId,
+ MeterOperations meterOps) {
+ meterOps.operations().forEach(op -> performOperation(networkId, deviceId, op));
+ }
+
+ @Override
+ public void performMeterOperation(NetworkId networkId, DeviceId deviceId,
+ MeterOperation meterOp) {
+ performOperation(networkId, deviceId, meterOp);
+ }
+
+ private void performOperation(NetworkId networkId, DeviceId deviceId,
+ MeterOperation op) {
+
+ VirtualMeterOperation vOp = new VirtualMeterOperation(networkId, op);
+ pendingOperations.put(idGenerator.getNewId(), vOp);
+
+ switch (op.type()) {
+ case ADD:
+ //TODO: devirtualize + submit
+ break;
+ case REMOVE:
+ //TODO: devirtualize + withdraw
+ break;
+ case MODIFY:
+ //TODO: devitualize + withdraw and submit
+ break;
+ default:
+ log.warn("Unknown Meter command {}; not sending anything",
+ op.type());
+ VirtualMeterProviderService providerService =
+ (VirtualMeterProviderService) providerRegistryService
+ .getProviderService(networkId,
+ VirtualMeterProvider.class);
+ providerService.meterOperationFailed(op,
+ MeterFailReason.UNKNOWN_COMMAND);
+ }
+
+ }
+
+ /**
+ * De-virtualizes a meter operation.
+ * It takes a virtual meter operation, and translate it to a physical meter operation.
+ *
+ * @param networkId a virtual network identifier
+ * @param deviceId a virtual network device identifier
+ * @param meterOps a meter operation to be de-virtualized
+ * @return de-virtualized meter operation
+ */
+ private VirtualMeterOperation devirtualize(NetworkId networkId,
+ DeviceId deviceId,
+ MeterOperation meterOps) {
+ return null;
+ }
+
+ /**
+ * Virtualizes meter.
+ * This translates meter events for virtual networks before delivering them.
+ *
+ * @param meter
+ * @return
+ */
+ private Meter virtualize(Meter meter) {
+ return null;
+ }
+
+
+ private class InternalMeterListener implements MeterListener {
+ @Override
+ public void event(MeterEvent event) {
+ //TODO: virtualize + notify event to meter provider service
+ //Is it enough to enable virtual network provider?
+ switch (event.type()) {
+ case METER_ADD_REQ:
+ break;
+ case METER_REM_REQ:
+ break;
+ case METER_ADDED:
+ break;
+ case METER_REMOVED:
+ break;
+ default:
+ log.warn("Unknown meter event {}", event.type());
+ }
+ }
+ }
+
+ /**
+ * A class to hold a network identifier and a meter operation.
+ * This class is designed to be used only in virtual network meter provider.
+ */
+ private final class VirtualMeterOperation {
+ private NetworkId networkId;
+ private MeterOperation op;
+
+ private VirtualMeterOperation(NetworkId networkId, MeterOperation op) {
+ this.networkId = networkId;
+ this.op = op;
+ }
+
+ private NetworkId networkId() {
+ return networkId;
+ }
+
+ private MeterOperation operation() {
+ return this.op;
+ }
+ }
+
+ /**
+ * A class to hold a network identifier and a meter.
+ * This class is designed to be used in only virtual network meter provider.
+ */
+ private final class VirtualMeter {
+ private NetworkId networkId;
+ private Meter meter;
+
+ private VirtualMeter(NetworkId networkId, Meter meter) {
+ this.networkId = networkId;
+ this.meter = meter;
+ }
+
+ private NetworkId networkId() {
+ return this.networkId;
+ }
+
+ private Meter meter() {
+ return this.meter;
+ }
+ }
+
+ /**
+ * Id generator for virtual meters to guarantee the uniqueness of its identifier
+ * among multiple virtual network meters.
+ *
+ * @return an ID generator
+ */
+ private IdGenerator getIdGenerator() {
+ return new IdGenerator() {
+ private AtomicLong counter = new AtomicLong(0);
+
+ @Override
+ public long getNewId() {
+ return counter.getAndIncrement();
+ }
+ };
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualNetworkProvider.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualNetworkProvider.java
new file mode 100644
index 0000000..3860dee
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualNetworkProvider.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.impl.provider;
+
+import org.onosproject.incubator.net.virtual.DefaultVirtualLink;
+import org.onosproject.incubator.net.virtual.provider.VirtualNetworkProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualNetworkProviderRegistry;
+import org.onosproject.incubator.net.virtual.provider.VirtualNetworkProviderService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.link.LinkEvent;
+import org.onosproject.net.provider.AbstractProvider;
+import org.onosproject.net.topology.Topology;
+import org.onosproject.net.topology.TopologyCluster;
+import org.onosproject.net.topology.TopologyEvent;
+import org.onosproject.net.topology.TopologyListener;
+import org.onosproject.net.topology.TopologyService;
+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.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Virtual network topology provider.
+ */
+@Component(service = VirtualNetworkProvider.class)
+public class DefaultVirtualNetworkProvider
+ extends AbstractProvider implements VirtualNetworkProvider {
+
+ private final Logger log = getLogger(DefaultVirtualNetworkProvider.class);
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected VirtualNetworkProviderRegistry providerRegistry;
+
+ private VirtualNetworkProviderService providerService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected TopologyService topologyService;
+
+ protected TopologyListener topologyListener = new InternalTopologyListener();
+
+ private ExecutorService executor;
+
+ /**
+ * Default constructor.
+ */
+ public DefaultVirtualNetworkProvider() {
+ super(DefaultVirtualLink.PID);
+ }
+
+ @Activate
+ public void activate() {
+ executor = newSingleThreadExecutor(groupedThreads("onos/vnet", "provider", log));
+ providerService = providerRegistry.register(this);
+ topologyService.addListener(topologyListener);
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ topologyService.removeListener(topologyListener);
+ executor.shutdownNow();
+ executor = null;
+ providerRegistry.unregister(this);
+ providerService = null;
+ log.info("Stopped");
+ }
+
+ @Override
+ public boolean isTraversable(ConnectPoint src, ConnectPoint dst) {
+ final boolean[] foundSrc = new boolean[1];
+ final boolean[] foundDst = new boolean[1];
+ Topology topology = topologyService.currentTopology();
+ Set<Path> paths = topologyService.getPaths(topology, src.deviceId(), dst.deviceId());
+ paths.forEach(path -> {
+ foundDst[0] = false;
+ foundSrc[0] = false;
+ // Traverse the links in each path to determine if both the src and dst connection
+ // point are in the path, if so then this src/dst pair are traversable.
+ path.links().forEach(link -> {
+ if (link.src().equals(src)) {
+ foundSrc[0] = true;
+ }
+ if (link.dst().equals(dst)) {
+ foundDst[0] = true;
+ }
+ });
+ if (foundSrc[0] && foundDst[0]) {
+ return;
+ }
+ });
+ return foundSrc[0] && foundDst[0];
+ }
+
+ /**
+ * Returns a set of set of interconnected connect points in the default topology.
+ * The inner set represents the interconnected connect points, and the outerset
+ * represents separate clusters.
+ *
+ * @param topology the default topology
+ * @return set of set of interconnected connect points.
+ */
+ public Set<Set<ConnectPoint>> getConnectPoints(Topology topology) {
+ Set<Set<ConnectPoint>> clusters = new HashSet<>();
+ Set<TopologyCluster> topologyClusters = topologyService.getClusters(topology);
+ topologyClusters.forEach(topologyCluster -> {
+ Set<ConnectPoint> connectPointSet = new HashSet<>();
+ Set<Link> clusterLinks =
+ topologyService.getClusterLinks(topology, topologyCluster);
+ clusterLinks.forEach(link -> {
+ connectPointSet.add(link.src());
+ connectPointSet.add(link.dst());
+ });
+ if (!connectPointSet.isEmpty()) {
+ clusters.add(connectPointSet);
+ }
+ });
+ return clusters;
+ }
+
+ /**
+ * Topology event listener.
+ */
+ private class InternalTopologyListener implements TopologyListener {
+ @Override
+ public void event(TopologyEvent event) {
+ // Perform processing off the listener thread.
+ executor.submit(() -> providerService
+ .topologyChanged(getConnectPoints(event.subject())));
+ }
+
+ @Override
+ public boolean isRelevant(TopologyEvent event) {
+ return event.type() == TopologyEvent.Type.TOPOLOGY_CHANGED &&
+ event.reasons().stream().anyMatch(reason -> reason instanceof LinkEvent);
+ }
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualPacketContext.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualPacketContext.java
new file mode 100644
index 0000000..014f436
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualPacketContext.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.impl.provider;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualPacketContext;
+import org.onosproject.net.packet.DefaultPacketContext;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+
+/**
+ *Default implementation of a virtual packet context.
+ */
+public class DefaultVirtualPacketContext extends DefaultPacketContext
+ implements VirtualPacketContext {
+
+ private NetworkId networkId;
+ private DefaultVirtualPacketProvider dvpp;
+
+ /**
+ * Creates a new packet context.
+ *
+ * @param time creation time
+ * @param inPkt inbound packet
+ * @param outPkt outbound packet
+ * @param block whether the context is blocked or not
+ * @param networkId virtual network ID where this context is handled
+ * @param dvpp pointer to default virtual packet provider
+ */
+
+ protected DefaultVirtualPacketContext(long time, InboundPacket inPkt,
+ OutboundPacket outPkt, boolean block,
+ NetworkId networkId,
+ DefaultVirtualPacketProvider dvpp) {
+ super(time, inPkt, outPkt, block);
+
+ this.networkId = networkId;
+ this.dvpp = dvpp;
+ }
+
+ @Override
+ public void send() {
+ if (!this.block()) {
+ dvpp.send(this);
+ }
+ }
+
+ @Override
+ public NetworkId networkId() {
+ return networkId;
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualPacketProvider.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualPacketProvider.java
new file mode 100644
index 0000000..9048954
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualPacketProvider.java
@@ -0,0 +1,421 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.impl.provider;
+
+import com.google.common.collect.Sets;
+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.osgi.service.component.annotations.ReferenceCardinality;
+import org.onlab.packet.Ethernet;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualDevice;
+import org.onosproject.incubator.net.virtual.VirtualNetwork;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+import org.onosproject.incubator.net.virtual.VirtualNetworkEvent;
+import org.onosproject.incubator.net.virtual.VirtualNetworkListener;
+import org.onosproject.incubator.net.virtual.VirtualPacketContext;
+import org.onosproject.incubator.net.virtual.VirtualPort;
+import org.onosproject.incubator.net.virtual.provider.AbstractVirtualProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualPacketProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualPacketProviderService;
+import org.onosproject.incubator.net.virtual.provider.VirtualProviderRegistryService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.packet.DefaultInboundPacket;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketPriority;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.net.provider.ProviderId;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+
+import java.nio.ByteBuffer;
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+@Component(service = VirtualPacketProvider.class)
+public class DefaultVirtualPacketProvider extends AbstractVirtualProvider
+ implements VirtualPacketProvider {
+
+ private static final int PACKET_PROCESSOR_PRIORITY = 1;
+ private static final PacketPriority VIRTUAL_PACKET_PRIORITY = PacketPriority.REACTIVE;
+
+ private final Logger log = getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected PacketService packetService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected VirtualNetworkAdminService vnaService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected VirtualProviderRegistryService providerRegistryService;
+
+ private final VirtualNetworkListener virtualNetListener = new InternalVirtualNetworkListener();
+
+ private InternalPacketProcessor processor = null;
+
+ private Set<NetworkId> networkIdSet = Sets.newConcurrentHashSet();
+
+ private ApplicationId appId;
+
+ /**
+ * Creates a provider with the supplied identifier.
+ */
+ public DefaultVirtualPacketProvider() {
+ super(new ProviderId("virtual-packet", "org.onosproject.virtual.virtual-packet"));
+ }
+
+ @Activate
+ public void activate() {
+ appId = coreService.registerApplication("org.onosproject.virtual.virtual-packet");
+ providerRegistryService.registerProvider(this);
+ vnaService.addListener(virtualNetListener);
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+
+ providerRegistryService.unregisterProvider(this);
+ vnaService.removeListener(virtualNetListener);
+
+ log.info("Stopped");
+ }
+
+ @Modified
+ protected void modified(ComponentContext context) {
+ Dictionary<?, ?> properties = context.getProperties();
+ }
+
+
+ @Override
+ public void emit(NetworkId networkId, OutboundPacket packet) {
+ devirtualize(networkId, packet)
+ .forEach(outboundPacket -> packetService.emit(outboundPacket));
+ }
+
+ /**
+ * Just for test.
+ */
+ protected void startPacketHandling() {
+ processor = new InternalPacketProcessor();
+ packetService.addProcessor(processor, PACKET_PROCESSOR_PRIORITY);
+ }
+
+ /**
+ * Send the outbound packet of a virtual context.
+ * This method is designed to support Context's send() method that invoked
+ * by applications.
+ * See {@link org.onosproject.net.packet.PacketContext}
+ *
+ * @param virtualPacketContext virtual packet context
+ */
+ protected void send(VirtualPacketContext virtualPacketContext) {
+ devirtualizeContext(virtualPacketContext)
+ .forEach(outboundPacket -> packetService.emit(outboundPacket));
+ }
+
+ /**
+ * Translate the requested physical PacketContext into a virtual PacketContext.
+ * See {@link org.onosproject.net.packet.PacketContext}
+ *
+ * @param context A physical PacketContext be translated
+ * @return A translated virtual PacketContext
+ */
+ private VirtualPacketContext virtualize(PacketContext context) {
+
+ VirtualPort vPort = getMappedVirtualPort(context.inPacket().receivedFrom());
+
+ if (vPort != null) {
+ ConnectPoint cp = new ConnectPoint(vPort.element().id(),
+ vPort.number());
+
+ Ethernet eth = context.inPacket().parsed();
+ eth.setVlanID(Ethernet.VLAN_UNTAGGED);
+
+ InboundPacket inPacket =
+ new DefaultInboundPacket(cp, eth,
+ ByteBuffer.wrap(eth.serialize()));
+
+ DefaultOutboundPacket outPkt =
+ new DefaultOutboundPacket(cp.deviceId(),
+ DefaultTrafficTreatment.builder().build(),
+ ByteBuffer.wrap(eth.serialize()));
+
+ VirtualPacketContext vContext =
+ new DefaultVirtualPacketContext(context.time(), inPacket, outPkt,
+ false, vPort.networkId(),
+ this);
+
+ return vContext;
+ } else {
+ return null;
+ }
+
+ }
+
+ /**
+ * Find the corresponding virtual port with the physical port.
+ *
+ * @param cp the connect point for the physical network
+ * @return a virtual port
+ */
+ private VirtualPort getMappedVirtualPort(ConnectPoint cp) {
+ Set<TenantId> tIds = vnaService.getTenantIds();
+
+ Set<VirtualNetwork> vNetworks = new HashSet<>();
+ tIds.forEach(tid -> vNetworks.addAll(vnaService.getVirtualNetworks(tid)));
+
+ for (VirtualNetwork vNet : vNetworks) {
+ Set<VirtualDevice> vDevices = vnaService.getVirtualDevices(vNet.id());
+
+ Set<VirtualPort> vPorts = new HashSet<>();
+ vDevices.forEach(dev -> vPorts
+ .addAll(vnaService.getVirtualPorts(dev.networkId(), dev.id())));
+
+ VirtualPort vPort = vPorts.stream()
+ .filter(vp -> vp.realizedBy().equals(cp))
+ .findFirst().orElse(null);
+
+ if (vPort != null) {
+ return vPort;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Translate the requested virtual outbound packet into
+ * a set of physical OutboundPacket.
+ * See {@link org.onosproject.net.packet.OutboundPacket}
+ *
+ * @param packet an OutboundPacket to be translated
+ * @return a set of de-virtualized (physical) OutboundPacket
+ */
+ private Set<OutboundPacket> devirtualize(NetworkId networkId, OutboundPacket packet) {
+ Set<OutboundPacket> outboundPackets = new HashSet<>();
+ Set<VirtualPort> vPorts = vnaService
+ .getVirtualPorts(networkId, packet.sendThrough());
+
+ TrafficTreatment.Builder commonTreatmentBuilder
+ = DefaultTrafficTreatment.builder();
+ packet.treatment().allInstructions().stream()
+ .filter(i -> i.type() != Instruction.Type.OUTPUT)
+ .forEach(i -> commonTreatmentBuilder.add(i));
+ TrafficTreatment commonTreatment = commonTreatmentBuilder.build();
+
+ PortNumber vOutPortNum = packet.treatment().allInstructions().stream()
+ .filter(i -> i.type() == Instruction.Type.OUTPUT)
+ .map(i -> ((Instructions.OutputInstruction) i).port())
+ .findFirst().get();
+
+ if (!vOutPortNum.isLogical()) {
+ Optional<ConnectPoint> optionalCpOut = vPorts.stream()
+ .filter(v -> v.number().equals(vOutPortNum))
+ .map(v -> v.realizedBy())
+ .findFirst();
+ if (!optionalCpOut.isPresent()) {
+ log.warn("Port {} is not realized yet, in Network {}, Device {}",
+ vOutPortNum, networkId, packet.sendThrough());
+ return outboundPackets;
+ }
+ ConnectPoint egressPoint = optionalCpOut.get();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment
+ .builder(commonTreatment)
+ .setOutput(egressPoint.port()).build();
+
+ OutboundPacket outboundPacket = new DefaultOutboundPacket(
+ egressPoint.deviceId(), treatment, packet.data());
+ outboundPackets.add(outboundPacket);
+ } else {
+ if (vOutPortNum == PortNumber.FLOOD) {
+ for (VirtualPort outPort : vPorts) {
+ ConnectPoint cpOut = outPort.realizedBy();
+ if (cpOut != null) {
+ TrafficTreatment treatment = DefaultTrafficTreatment
+ .builder(commonTreatment)
+ .setOutput(cpOut.port()).build();
+ OutboundPacket outboundPacket = new DefaultOutboundPacket(
+ cpOut.deviceId(), treatment, packet.data());
+ outboundPackets.add(outboundPacket);
+ } else {
+ log.warn("Port {} is not realized yet, in Network {}, Device {}",
+ outPort.number(), networkId, packet.sendThrough());
+ }
+ }
+ }
+ }
+
+ return outboundPackets;
+ }
+
+ /**
+ * Translate the requested virtual packet context into
+ * a set of physical outbound packets.
+ *
+ * @param context A handled virtual packet context
+ */
+ private Set<OutboundPacket> devirtualizeContext(VirtualPacketContext context) {
+
+ Set<OutboundPacket> outboundPackets = new HashSet<>();
+
+ NetworkId networkId = context.networkId();
+ TrafficTreatment vTreatment = context.treatmentBuilder().build();
+ DeviceId sendThrough = context.outPacket().sendThrough();
+
+ Set<VirtualPort> vPorts = vnaService
+ .getVirtualPorts(networkId, sendThrough);
+
+ PortNumber vOutPortNum = vTreatment.allInstructions().stream()
+ .filter(i -> i.type() == Instruction.Type.OUTPUT)
+ .map(i -> ((Instructions.OutputInstruction) i).port())
+ .findFirst().get();
+
+ TrafficTreatment.Builder commonTreatmentBuilder
+ = DefaultTrafficTreatment.builder();
+ vTreatment.allInstructions().stream()
+ .filter(i -> i.type() != Instruction.Type.OUTPUT)
+ .forEach(i -> commonTreatmentBuilder.add(i));
+ TrafficTreatment commonTreatment = commonTreatmentBuilder.build();
+
+ if (!vOutPortNum.isLogical()) {
+ Optional<ConnectPoint> optionalCpOut = vPorts.stream()
+ .filter(v -> v.number().equals(vOutPortNum))
+ .map(v -> v.realizedBy())
+ .findFirst();
+ if (!optionalCpOut.isPresent()) {
+ log.warn("Port {} is not realized yet, in Network {}, Device {}",
+ vOutPortNum, networkId, sendThrough);
+ return outboundPackets;
+ }
+ ConnectPoint egressPoint = optionalCpOut.get();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment
+ .builder(commonTreatment)
+ .setOutput(egressPoint.port()).build();
+
+ OutboundPacket outboundPacket = new DefaultOutboundPacket(
+ egressPoint.deviceId(), treatment, context.outPacket().data());
+ outboundPackets.add(outboundPacket);
+ } else {
+ if (vOutPortNum == PortNumber.FLOOD) {
+ Set<VirtualPort> outPorts = vPorts.stream()
+ .filter(vp -> !vp.number().isLogical())
+ .filter(vp -> vp.number() !=
+ context.inPacket().receivedFrom().port())
+ .collect(Collectors.toSet());
+
+ for (VirtualPort outPort : outPorts) {
+ ConnectPoint cpOut = outPort.realizedBy();
+ if (cpOut != null) {
+ TrafficTreatment treatment = DefaultTrafficTreatment
+ .builder(commonTreatment)
+ .setOutput(cpOut.port()).build();
+ OutboundPacket outboundPacket = new DefaultOutboundPacket(
+ cpOut.deviceId(), treatment, context.outPacket().data());
+ outboundPackets.add(outboundPacket);
+ } else {
+ log.warn("Port {} is not realized yet, in Network {}, Device {}",
+ outPort.number(), networkId, sendThrough);
+ }
+ }
+ }
+ }
+ return outboundPackets;
+ }
+
+ private final class InternalPacketProcessor implements PacketProcessor {
+
+ @Override
+ public void process(PacketContext context) {
+ if (context.isHandled()) {
+ return;
+ }
+ VirtualPacketContext vContexts = virtualize(context);
+
+ if (vContexts == null) {
+ return;
+ }
+
+ VirtualPacketProviderService service =
+ (VirtualPacketProviderService) providerRegistryService
+ .getProviderService(vContexts.networkId(),
+ VirtualPacketProvider.class);
+ if (service != null) {
+ service.processPacket(vContexts);
+ }
+ }
+ }
+
+ private class InternalVirtualNetworkListener implements VirtualNetworkListener {
+
+ @Override
+ public void event(VirtualNetworkEvent event) {
+ switch (event.type()) {
+ case NETWORK_ADDED:
+ if (networkIdSet.isEmpty()) {
+ processor = new InternalPacketProcessor();
+ packetService.addProcessor(processor, PACKET_PROCESSOR_PRIORITY);
+ log.info("Packet processor {} for virtual network is added.", processor.getClass().getName());
+ }
+ networkIdSet.add(event.subject());
+ break;
+
+ case NETWORK_REMOVED:
+ networkIdSet.remove(event.subject());
+ if (networkIdSet.isEmpty()) {
+ packetService.removeProcessor(processor);
+ log.info("Packet processor {} for virtual network is removed.", processor.getClass().getName());
+ processor = null;
+ }
+ break;
+
+ default:
+ // do nothing
+ break;
+ }
+ }
+ }
+
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/VirtualProviderManager.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/VirtualProviderManager.java
new file mode 100644
index 0000000..ebd87ea
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/VirtualProviderManager.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.provider;
+
+import com.google.common.collect.ImmutableSet;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.provider.VirtualProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualProviderRegistryService;
+import org.onosproject.incubator.net.virtual.provider.VirtualProviderService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.provider.ProviderId;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * Implementation of the virtual provider registry and providerService registry service.
+ */
+@Component(service = VirtualProviderRegistryService.class)
+public class VirtualProviderManager implements VirtualProviderRegistryService {
+
+ private final Map<ProviderId, VirtualProvider> providers = new HashMap<>();
+ private final Map<ProviderId, VirtualProviderService> servicesWithProvider = new HashMap<>();
+ private final Map<String, VirtualProvider> providersByScheme = new HashMap<>();
+ private final Map<NetworkId, Set<VirtualProviderService>> servicesByNetwork = new HashMap<>();
+ private static Logger log = LoggerFactory.getLogger(VirtualProviderManager.class);
+
+ @Override
+ public synchronized void registerProvider(VirtualProvider virtualProvider) {
+ checkNotNull(virtualProvider, "Provider cannot be null");
+ checkState(!providers.containsKey(virtualProvider.id()),
+ "Provider %s already registered", virtualProvider.id());
+
+ // If the provider is a primary one, check for a conflict.
+ ProviderId pid = virtualProvider.id();
+ checkState(pid.isAncillary() || !providersByScheme.containsKey(pid.scheme()),
+ "A primary provider with id %s is already registered",
+ providersByScheme.get(pid.scheme()));
+
+ providers.put(virtualProvider.id(), virtualProvider);
+
+ // Register the provider by URI scheme only if it is not ancillary.
+ if (!pid.isAncillary()) {
+ providersByScheme.put(pid.scheme(), virtualProvider);
+ }
+ }
+
+ @Override
+ public synchronized void unregisterProvider(VirtualProvider virtualProvider) {
+ checkNotNull(virtualProvider, "Provider cannot be null");
+
+ //TODO: invalidate provider services which subscribe the provider
+ providers.remove(virtualProvider.id());
+
+ if (!virtualProvider.id().isAncillary()) {
+ providersByScheme.remove(virtualProvider.id().scheme());
+ }
+ }
+
+ @Override
+ public synchronized void
+ registerProviderService(NetworkId networkId,
+ VirtualProviderService virtualProviderService) {
+ Set<VirtualProviderService> services =
+ servicesByNetwork.computeIfAbsent(networkId, k -> new HashSet<>());
+
+ services.add(virtualProviderService);
+ }
+
+ @Override
+ public synchronized void
+ unregisterProviderService(NetworkId networkId,
+ VirtualProviderService virtualProviderService) {
+ Set<VirtualProviderService> services = servicesByNetwork.get(networkId);
+
+ if (services != null) {
+ services.remove(virtualProviderService);
+ }
+ }
+
+ @Override
+ public synchronized Set<ProviderId> getProviders() {
+ return ImmutableSet.copyOf(providers.keySet());
+ }
+
+ @Override
+ public Set<ProviderId> getProvidersByService(VirtualProviderService
+ virtualProviderService) {
+ Class clazz = getProviderClass(virtualProviderService);
+
+ return ImmutableSet.copyOf(providers.values().stream()
+ .filter(clazz::isInstance)
+ .map(VirtualProvider::id)
+ .collect(Collectors.toSet()));
+ }
+
+ @Override
+ public synchronized VirtualProvider getProvider(ProviderId providerId) {
+ return providers.get(providerId);
+ }
+
+ @Override
+ public synchronized VirtualProvider getProvider(DeviceId deviceId) {
+ return providersByScheme.get(deviceId.uri().getScheme());
+ }
+
+ @Override
+ public synchronized VirtualProvider getProvider(String scheme) {
+ return providersByScheme.get(scheme);
+ }
+
+ @Override
+ public synchronized VirtualProviderService
+ getProviderService(NetworkId networkId, Class<? extends VirtualProvider> providerClass) {
+ Set<VirtualProviderService> services = servicesByNetwork.get(networkId);
+
+ if (services == null) {
+ return null;
+ }
+
+ return services.stream()
+ .filter(s -> getProviderClass(s).equals(providerClass))
+ .findFirst().orElse(null);
+ }
+
+ /**
+ * Returns the class type of parameter type.
+ * More specifically, it returns the class type of provider service's provider type.
+ *
+ * @param service a virtual provider service
+ * @return the class type of provider service of the service
+ */
+ private Class getProviderClass(VirtualProviderService service) {
+ String className = service.getClass().getGenericSuperclass().getTypeName();
+ String pramType = className.split("<")[1].split(">")[0];
+
+ try {
+ return Class.forName(pramType);
+ } catch (ClassNotFoundException e) {
+ log.warn("getProviderClass()", e);
+ }
+
+ return null;
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/package-info.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/package-info.java
new file mode 100644
index 0000000..1d9646f
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-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.
+ */
+
+/**
+ * Network virtualization provider implementations.
+ */
+package org.onosproject.incubator.net.virtual.impl.provider;
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/rest/TenantWebResource.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/rest/TenantWebResource.java
new file mode 100644
index 0000000..768c3b9
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/rest/TenantWebResource.java
@@ -0,0 +1,146 @@
+/*
+ * 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.incubator.net.virtual.rest;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.util.ItemNotFoundException;
+import org.onosproject.net.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+import org.onosproject.rest.AbstractWebResource;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.io.IOException;
+import java.io.InputStream;
+
+import static org.onlab.util.Tools.readTreeFromStream;
+
+/**
+ * Query and manage tenants of virtual networks.
+ */
+@Path("tenants")
+public class TenantWebResource extends AbstractWebResource {
+
+ private static final String MISSING_TENANTID = "Missing tenant identifier";
+ private static final String TENANTID_NOT_FOUND = "Tenant identifier not found";
+ private static final String INVALID_TENANTID = "Invalid tenant identifier ";
+
+ @Context
+ private UriInfo uriInfo;
+
+ private final VirtualNetworkAdminService vnetAdminService = get(VirtualNetworkAdminService.class);
+
+ /**
+ * Returns all tenant identifiers.
+ *
+ * @return 200 OK with set of tenant identifiers
+ * @onos.rsModel TenantIds
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response getVirtualNetworkTenantIds() {
+ Iterable<TenantId> tenantIds = vnetAdminService.getTenantIds();
+ return ok(encodeArray(TenantId.class, "tenants", tenantIds)).build();
+ }
+
+ /**
+ * Creates a tenant with the given tenant identifier.
+ *
+ * @param stream TenantId JSON stream
+ * @return status of the request - CREATED if the JSON is correct,
+ * BAD_REQUEST if the JSON is invalid
+ * @onos.rsModel TenantId
+ */
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response addTenantId(InputStream stream) {
+ try {
+ final TenantId tid = getTenantIdFromJsonStream(stream);
+ vnetAdminService.registerTenantId(tid);
+ final TenantId resultTid = getExistingTenantId(vnetAdminService, tid);
+ UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+ .path("tenants")
+ .path(resultTid.id());
+ return Response
+ .created(locationBuilder.build())
+ .build();
+ } catch (IOException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /**
+ * Removes the specified tenant with the specified tenant identifier.
+ *
+ * @param tenantId tenant identifier
+ * @return 204 NO CONTENT
+ */
+ @DELETE
+ @Path("{tenantId}")
+ public Response removeTenantId(@PathParam("tenantId") String tenantId) {
+ final TenantId tid = TenantId.tenantId(tenantId);
+ final TenantId existingTid = getExistingTenantId(vnetAdminService, tid);
+ vnetAdminService.unregisterTenantId(existingTid);
+ return Response.noContent().build();
+ }
+
+ /**
+ * Gets the tenant identifier from the JSON stream.
+ *
+ * @param stream TenantId JSON stream
+ * @return TenantId
+ * @throws IOException if unable to parse the request
+ */
+ private TenantId getTenantIdFromJsonStream(InputStream stream) throws IOException {
+ ObjectNode jsonTree = readTreeFromStream(mapper(), stream);
+ JsonNode specifiedTenantId = jsonTree.get("id");
+
+ if (specifiedTenantId == null) {
+ throw new IllegalArgumentException(MISSING_TENANTID);
+ }
+ return TenantId.tenantId(specifiedTenantId.asText());
+ }
+
+ /**
+ * Get the matching tenant identifier from existing tenant identifiers in system.
+ *
+ * @param vnetAdminSvc virtual network administration service
+ * @param tidIn tenant identifier
+ * @return TenantId
+ */
+ protected static TenantId getExistingTenantId(VirtualNetworkAdminService vnetAdminSvc,
+ TenantId tidIn) {
+ return vnetAdminSvc
+ .getTenantIds()
+ .stream()
+ .filter(tenantId -> tenantId.equals(tidIn))
+ .findFirst()
+ .orElseThrow(() -> new ItemNotFoundException(TENANTID_NOT_FOUND));
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/rest/VirtualNetworkWebApplication.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/rest/VirtualNetworkWebApplication.java
new file mode 100644
index 0000000..4a80dbe
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/rest/VirtualNetworkWebApplication.java
@@ -0,0 +1,34 @@
+/*
+ * 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.incubator.net.virtual.rest;
+
+import org.onlab.rest.AbstractWebApplication;
+
+import java.util.Set;
+
+/**
+ * Virtual network REST APIs web application.
+ */
+public class VirtualNetworkWebApplication extends AbstractWebApplication {
+ @Override
+ public Set<Class<?>> getClasses() {
+ return getClasses(
+ TenantWebResource.class,
+ VirtualNetworkWebResource.class
+ );
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/rest/VirtualNetworkWebResource.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/rest/VirtualNetworkWebResource.java
new file mode 100644
index 0000000..e8696c3
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/rest/VirtualNetworkWebResource.java
@@ -0,0 +1,487 @@
+/*
+ * 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.incubator.net.virtual.rest;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualDevice;
+import org.onosproject.incubator.net.virtual.VirtualHost;
+import org.onosproject.incubator.net.virtual.VirtualLink;
+import org.onosproject.incubator.net.virtual.VirtualNetwork;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.VirtualPort;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.rest.AbstractWebResource;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.onlab.util.Tools.readTreeFromStream;
+
+/**
+ * Query and Manage Virtual Network elements.
+ */
+@Path("vnets")
+public class VirtualNetworkWebResource extends AbstractWebResource {
+
+ private static final String MISSING_FIELD = "Missing ";
+ private static final String INVALID_FIELD = "Invalid ";
+
+ private final VirtualNetworkAdminService vnetAdminService = get(VirtualNetworkAdminService.class);
+ private final VirtualNetworkService vnetService = get(VirtualNetworkService.class);
+
+ @Context
+ private UriInfo uriInfo;
+
+ // VirtualNetwork
+
+ /**
+ * Returns all virtual networks.
+ *
+ * @return 200 OK with set of virtual networks
+ * @onos.rsModel VirtualNetworks
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response getVirtualNetworks() {
+ Set<TenantId> tenantIds = vnetAdminService.getTenantIds();
+ List<VirtualNetwork> allVnets = tenantIds.stream()
+ .map(tenantId -> vnetService.getVirtualNetworks(tenantId))
+ .flatMap(Collection::stream)
+ .collect(Collectors.toList());
+ return ok(encodeArray(VirtualNetwork.class, "vnets", allVnets)).build();
+ }
+
+ /**
+ * Returns the virtual networks with the specified tenant identifier.
+ *
+ * @param tenantId tenant identifier
+ * @return 200 OK with a virtual network, 404 not found
+ * @onos.rsModel VirtualNetworks
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{tenantId}")
+ public Response getVirtualNetworkById(@PathParam("tenantId") String tenantId) {
+ final TenantId existingTid = TenantWebResource.getExistingTenantId(vnetAdminService,
+ TenantId.tenantId(tenantId));
+ Set<VirtualNetwork> vnets = vnetService.getVirtualNetworks(existingTid);
+ return ok(encodeArray(VirtualNetwork.class, "vnets", vnets)).build();
+ }
+
+ /**
+ * Creates a virtual network from the JSON input stream.
+ *
+ * @param stream tenant identifier JSON stream
+ * @return status of the request - CREATED if the JSON is correct,
+ * BAD_REQUEST if the JSON is invalid
+ * @onos.rsModel TenantId
+ */
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response createVirtualNetwork(InputStream stream) {
+ try {
+ final TenantId tid = TenantId.tenantId(getFromJsonStream(stream, "id").asText());
+ VirtualNetwork newVnet = vnetAdminService.createVirtualNetwork(tid);
+ UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+ .path("vnets")
+ .path(newVnet.id().toString());
+ return Response
+ .created(locationBuilder.build())
+ .build();
+ } catch (IOException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /**
+ * Removes the virtual network with the specified network identifier.
+ *
+ * @param networkId network identifier
+ * @return 204 NO CONTENT
+ */
+ @DELETE
+ @Path("{networkId}")
+ public Response removeVirtualNetwork(@PathParam("networkId") long networkId) {
+ NetworkId nid = NetworkId.networkId(networkId);
+ vnetAdminService.removeVirtualNetwork(nid);
+ return Response.noContent().build();
+ }
+
+ // VirtualDevice
+
+ /**
+ * Returns all virtual network devices in a virtual network.
+ *
+ * @param networkId network identifier
+ * @return 200 OK with set of virtual devices, 404 not found
+ * @onos.rsModel VirtualDevices
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{networkId}/devices")
+ public Response getVirtualDevices(@PathParam("networkId") long networkId) {
+ NetworkId nid = NetworkId.networkId(networkId);
+ Set<VirtualDevice> vdevs = vnetService.getVirtualDevices(nid);
+ return ok(encodeArray(VirtualDevice.class, "devices", vdevs)).build();
+ }
+
+ /**
+ * Creates a virtual device from the JSON input stream.
+ *
+ * @param networkId network identifier
+ * @param stream virtual device JSON stream
+ * @return status of the request - CREATED if the JSON is correct,
+ * BAD_REQUEST if the JSON is invalid
+ * @onos.rsModel VirtualDevice
+ */
+ @POST
+ @Path("{networkId}/devices")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response createVirtualDevice(@PathParam("networkId") long networkId,
+ InputStream stream) {
+ try {
+ ObjectNode jsonTree = readTreeFromStream(mapper(), stream);
+ final VirtualDevice vdevReq = codec(VirtualDevice.class).decode(jsonTree, this);
+ JsonNode specifiedNetworkId = jsonTree.get("networkId");
+ if (specifiedNetworkId == null || specifiedNetworkId.asLong() != (networkId)) {
+ throw new IllegalArgumentException(INVALID_FIELD + "networkId");
+ }
+ final VirtualDevice vdevRes = vnetAdminService.createVirtualDevice(vdevReq.networkId(),
+ vdevReq.id());
+ UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+ .path("vnets").path(specifiedNetworkId.asText())
+ .path("devices").path(vdevRes.id().toString());
+ return Response
+ .created(locationBuilder.build())
+ .build();
+ } catch (IOException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /**
+ * Removes the virtual network device from the virtual network.
+ *
+ * @param networkId network identifier
+ * @param deviceId device identifier
+ * @return 204 NO CONTENT
+ */
+ @DELETE
+ @Path("{networkId}/devices/{deviceId}")
+ public Response removeVirtualDevice(@PathParam("networkId") long networkId,
+ @PathParam("deviceId") String deviceId) {
+ NetworkId nid = NetworkId.networkId(networkId);
+ DeviceId did = DeviceId.deviceId(deviceId);
+ vnetAdminService.removeVirtualDevice(nid, did);
+ return Response.noContent().build();
+ }
+
+ // VirtualPort
+
+ /**
+ * Returns all virtual network ports in a virtual device in a virtual network.
+ *
+ * @param networkId network identifier
+ * @param deviceId virtual device identifier
+ * @return 200 OK with set of virtual ports, 404 not found
+ * @onos.rsModel VirtualPorts
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{networkId}/devices/{deviceId}/ports")
+ public Response getVirtualPorts(@PathParam("networkId") long networkId,
+ @PathParam("deviceId") String deviceId) {
+ NetworkId nid = NetworkId.networkId(networkId);
+ Iterable<VirtualPort> vports = vnetService.getVirtualPorts(nid, DeviceId.deviceId(deviceId));
+ return ok(encodeArray(VirtualPort.class, "ports", vports)).build();
+ }
+
+ /**
+ * Creates a virtual network port in a virtual device in a virtual network.
+ *
+ * @param networkId network identifier
+ * @param virtDeviceId virtual device identifier
+ * @param stream virtual port JSON stream
+ * @return status of the request - CREATED if the JSON is correct,
+ * BAD_REQUEST if the JSON is invalid
+ * @onos.rsModel VirtualPort
+ */
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{networkId}/devices/{deviceId}/ports")
+ public Response createVirtualPort(@PathParam("networkId") long networkId,
+ @PathParam("deviceId") String virtDeviceId,
+ InputStream stream) {
+ try {
+ ObjectNode jsonTree = readTreeFromStream(mapper(), stream);
+// final VirtualPort vportReq = codec(VirtualPort.class).decode(jsonTree, this);
+ JsonNode specifiedNetworkId = jsonTree.get("networkId");
+ JsonNode specifiedDeviceId = jsonTree.get("deviceId");
+ if (specifiedNetworkId == null || specifiedNetworkId.asLong() != (networkId)) {
+ throw new IllegalArgumentException(INVALID_FIELD + "networkId");
+ }
+ if (specifiedDeviceId == null || !specifiedDeviceId.asText().equals(virtDeviceId)) {
+ throw new IllegalArgumentException(INVALID_FIELD + "deviceId");
+ }
+ JsonNode specifiedPortNum = jsonTree.get("portNum");
+ JsonNode specifiedPhysDeviceId = jsonTree.get("physDeviceId");
+ JsonNode specifiedPhysPortNum = jsonTree.get("physPortNum");
+ final NetworkId nid = NetworkId.networkId(networkId);
+ DeviceId vdevId = DeviceId.deviceId(virtDeviceId);
+
+ ConnectPoint realizedBy = new ConnectPoint(DeviceId.deviceId(specifiedPhysDeviceId.asText()),
+ PortNumber.portNumber(specifiedPhysPortNum.asText()));
+ VirtualPort vport = vnetAdminService.createVirtualPort(nid, vdevId,
+ PortNumber.portNumber(specifiedPortNum.asText()), realizedBy);
+ UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+ .path("vnets").path(specifiedNetworkId.asText())
+ .path("devices").path(specifiedDeviceId.asText())
+ .path("ports").path(vport.number().toString());
+ return Response
+ .created(locationBuilder.build())
+ .build();
+ } catch (IOException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /**
+ * Removes the virtual network port from the virtual device in a virtual network.
+ *
+ * @param networkId network identifier
+ * @param deviceId virtual device identifier
+ * @param portNum virtual port number
+ * @return 204 NO CONTENT
+ */
+ @DELETE
+ @Path("{networkId}/devices/{deviceId}/ports/{portNum}")
+ public Response removeVirtualPort(@PathParam("networkId") long networkId,
+ @PathParam("deviceId") String deviceId,
+ @PathParam("portNum") long portNum) {
+ NetworkId nid = NetworkId.networkId(networkId);
+ vnetAdminService.removeVirtualPort(nid, DeviceId.deviceId(deviceId),
+ PortNumber.portNumber(portNum));
+ return Response.noContent().build();
+ }
+
+ // VirtualLink
+
+ /**
+ * Returns all virtual network links in a virtual network.
+ *
+ * @param networkId network identifier
+ * @return 200 OK with set of virtual network links
+ * @onos.rsModel VirtualLinks
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{networkId}/links")
+ public Response getVirtualLinks(@PathParam("networkId") long networkId) {
+ NetworkId nid = NetworkId.networkId(networkId);
+ Set<VirtualLink> vlinks = vnetService.getVirtualLinks(nid);
+ return ok(encodeArray(VirtualLink.class, "links", vlinks)).build();
+ }
+
+ /**
+ * Creates a virtual network link from the JSON input stream.
+ *
+ * @param networkId network identifier
+ * @param stream virtual link JSON stream
+ * @return status of the request - CREATED if the JSON is correct,
+ * BAD_REQUEST if the JSON is invalid
+ * @onos.rsModel VirtualLink
+ */
+ @POST
+ @Path("{networkId}/links")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response createVirtualLink(@PathParam("networkId") long networkId,
+ InputStream stream) {
+ try {
+ ObjectNode jsonTree = readTreeFromStream(mapper(), stream);
+ JsonNode specifiedNetworkId = jsonTree.get("networkId");
+ if (specifiedNetworkId == null || specifiedNetworkId.asLong() != (networkId)) {
+ throw new IllegalArgumentException(INVALID_FIELD + "networkId");
+ }
+ final VirtualLink vlinkReq = codec(VirtualLink.class).decode(jsonTree, this);
+ vnetAdminService.createVirtualLink(vlinkReq.networkId(),
+ vlinkReq.src(), vlinkReq.dst());
+ UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+ .path("vnets").path(specifiedNetworkId.asText())
+ .path("links");
+ return Response
+ .created(locationBuilder.build())
+ .build();
+ } catch (IOException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /**
+ * Removes the virtual network link from the JSON input stream.
+ *
+ * @param networkId network identifier
+ * @param stream virtual link JSON stream
+ * @return 204 NO CONTENT
+ * @onos.rsModel VirtualLink
+ */
+ @DELETE
+ @Path("{networkId}/links")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response removeVirtualLink(@PathParam("networkId") long networkId,
+ InputStream stream) {
+ try {
+ ObjectNode jsonTree = readTreeFromStream(mapper(), stream);
+ JsonNode specifiedNetworkId = jsonTree.get("networkId");
+ if (specifiedNetworkId != null &&
+ specifiedNetworkId.asLong() != (networkId)) {
+ throw new IllegalArgumentException(INVALID_FIELD + "networkId");
+ }
+ final VirtualLink vlinkReq = codec(VirtualLink.class).decode(jsonTree, this);
+ vnetAdminService.removeVirtualLink(vlinkReq.networkId(),
+ vlinkReq.src(), vlinkReq.dst());
+ } catch (IOException e) {
+ throw new IllegalArgumentException(e);
+ }
+
+ return Response.noContent().build();
+ }
+
+ /**
+ * Returns all virtual network hosts in a virtual network.
+ *
+ * @param networkId network identifier
+ * @return 200 OK with set of virtual network hosts
+ * @onos.rsModel VirtualHosts
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{networkId}/hosts")
+ public Response getVirtualHosts(@PathParam("networkId") long networkId) {
+ NetworkId nid = NetworkId.networkId(networkId);
+ Set<VirtualHost> vhosts = vnetService.getVirtualHosts(nid);
+ return ok(encodeArray(VirtualHost.class, "hosts", vhosts)).build();
+ }
+
+ /**
+ * Creates a virtual network host from the JSON input stream.
+ *
+ * @param networkId network identifier
+ * @param stream virtual host JSON stream
+ * @return status of the request - CREATED if the JSON is correct,
+ * BAD_REQUEST if the JSON is invalid
+ * @onos.rsModel VirtualHostPut
+ */
+ @POST
+ @Path("{networkId}/hosts")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response createVirtualHost(@PathParam("networkId") long networkId,
+ InputStream stream) {
+ try {
+ ObjectNode jsonTree = readTreeFromStream(mapper(), stream);
+ JsonNode specifiedNetworkId = jsonTree.get("networkId");
+ if (specifiedNetworkId == null || specifiedNetworkId.asLong() != (networkId)) {
+ throw new IllegalArgumentException(INVALID_FIELD + "networkId");
+ }
+ final VirtualHost vhostReq = codec(VirtualHost.class).decode(jsonTree, this);
+ vnetAdminService.createVirtualHost(vhostReq.networkId(), vhostReq.id(),
+ vhostReq.mac(), vhostReq.vlan(),
+ vhostReq.location(), vhostReq.ipAddresses());
+ UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+ .path("vnets").path(specifiedNetworkId.asText())
+ .path("hosts");
+ return Response
+ .created(locationBuilder.build())
+ .build();
+ } catch (IOException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /**
+ * Removes the virtual network host from the JSON input stream.
+ *
+ * @param networkId network identifier
+ * @param stream virtual host JSON stream
+ * @return 204 NO CONTENT
+ * @onos.rsModel VirtualHost
+ */
+ @DELETE
+ @Path("{networkId}/hosts")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response removeVirtualHost(@PathParam("networkId") long networkId,
+ InputStream stream) {
+ try {
+ ObjectNode jsonTree = readTreeFromStream(mapper(), stream);
+ JsonNode specifiedNetworkId = jsonTree.get("networkId");
+ if (specifiedNetworkId != null &&
+ specifiedNetworkId.asLong() != (networkId)) {
+ throw new IllegalArgumentException(INVALID_FIELD + "networkId");
+ }
+ final VirtualHost vhostReq = codec(VirtualHost.class).decode(jsonTree, this);
+ vnetAdminService.removeVirtualHost(vhostReq.networkId(), vhostReq.id());
+ } catch (IOException e) {
+ throw new IllegalArgumentException(e);
+ }
+
+ return Response.noContent().build();
+ }
+
+ /**
+ * Get the tenant identifier from the JSON stream.
+ *
+ * @param stream TenantId JSON stream
+ * @param jsonFieldName field name
+ * @return JsonNode
+ * @throws IOException if unable to parse the request
+ */
+ private JsonNode getFromJsonStream(InputStream stream, String jsonFieldName) throws IOException {
+ ObjectNode jsonTree = readTreeFromStream(mapper(), stream);
+ JsonNode jsonNode = jsonTree.get(jsonFieldName);
+
+ if (jsonNode == null) {
+ throw new IllegalArgumentException(MISSING_FIELD + jsonFieldName);
+ }
+ return jsonNode;
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/rest/package-info.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/rest/package-info.java
new file mode 100644
index 0000000..0b8a711
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/rest/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.
+ */
+
+/**
+ * REST API of the virtual network subsystem.
+ */
+package org.onosproject.incubator.net.virtual.rest;
\ No newline at end of file
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/AbstractVirtualStore.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/AbstractVirtualStore.java
new file mode 100644
index 0000000..73f5bdb
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/AbstractVirtualStore.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.store.impl;
+
+import com.google.common.collect.Maps;
+import org.onosproject.event.Event;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualStore;
+import org.onosproject.store.StoreDelegate;
+
+import java.util.List;
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * Base implementation of a virtual store.
+ */
+public class AbstractVirtualStore<E extends Event, D extends StoreDelegate<E>>
+ implements VirtualStore<E, D> {
+
+ protected Map<NetworkId, D> delegateMap = Maps.newConcurrentMap();
+
+ @Override
+ public void setDelegate(NetworkId networkId, D delegate) {
+ checkState(delegateMap.get(networkId) == null
+ || delegateMap.get(networkId) == delegate,
+ "Store delegate already set");
+
+ delegateMap.putIfAbsent(networkId, delegate);
+ }
+
+ @Override
+ public void unsetDelegate(NetworkId networkId, D delegate) {
+ if (delegateMap.get(networkId) == delegate) {
+ delegateMap.remove(networkId, delegate);
+ }
+ }
+
+ @Override
+ public boolean hasDelegate(NetworkId networkId) {
+ return delegateMap.get(networkId) != null;
+ }
+
+ /**
+ * Notifies the delegate with the specified event.
+ *
+ * @param networkId a virtual network identifier
+ * @param event event to delegate
+ */
+ protected void notifyDelegate(NetworkId networkId, E event) {
+ if (delegateMap.get(networkId) != null) {
+ delegateMap.get(networkId).notify(event);
+ }
+ }
+
+ /**
+ * Notifies the delegate with the specified list of events.
+ *
+ * @param networkId a virtual network identifier
+ * @param events list of events to delegate
+ */
+ protected void notifyDelegate(NetworkId networkId, List<E> events) {
+ for (E event: events) {
+ notifyDelegate(networkId, event);
+ }
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/ConsistentVirtualDeviceMastershipStore.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/ConsistentVirtualDeviceMastershipStore.java
new file mode 100644
index 0000000..408ce9c
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/ConsistentVirtualDeviceMastershipStore.java
@@ -0,0 +1,467 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.Leadership;
+import org.onosproject.cluster.LeadershipAdminService;
+import org.onosproject.cluster.LeadershipEvent;
+import org.onosproject.cluster.LeadershipEventListener;
+import org.onosproject.cluster.LeadershipService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.cluster.RoleInfo;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkMastershipStore;
+import org.onosproject.mastership.MastershipEvent;
+import org.onosproject.mastership.MastershipInfo;
+import org.onosproject.mastership.MastershipStoreDelegate;
+import org.onosproject.mastership.MastershipTerm;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
+import org.onosproject.store.cluster.messaging.MessageSubject;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.Serializer;
+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.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.mastership.MastershipEvent.Type.BACKUPS_CHANGED;
+import static org.onosproject.mastership.MastershipEvent.Type.MASTER_CHANGED;
+import static org.onosproject.mastership.MastershipEvent.Type.SUSPENDED;
+import static org.slf4j.LoggerFactory.getLogger;
+
+@Component(immediate = true, enabled = false, service = VirtualNetworkMastershipStore.class)
+public class ConsistentVirtualDeviceMastershipStore
+ extends AbstractVirtualStore<MastershipEvent, MastershipStoreDelegate>
+ implements VirtualNetworkMastershipStore {
+
+ private final Logger log = getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected LeadershipService leadershipService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected LeadershipAdminService leadershipAdminService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected ClusterService clusterService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected ClusterCommunicationService clusterCommunicator;
+
+ private NodeId localNodeId;
+
+ private static final MessageSubject ROLE_RELINQUISH_SUBJECT =
+ new MessageSubject("virtual-mastership-store-device-role-relinquish");
+
+ private static final Pattern DEVICE_MASTERSHIP_TOPIC_PATTERN =
+ Pattern.compile("vnet:(.*),device:(.*)");
+
+ private ExecutorService eventHandler;
+ private ExecutorService messageHandlingExecutor;
+ private ScheduledExecutorService transferExecutor;
+ private final LeadershipEventListener leadershipEventListener =
+ new InternalDeviceMastershipEventListener();
+
+ private static final String NODE_ID_NULL = "Node ID cannot be null";
+ private static final String NETWORK_ID_NULL = "Network ID cannot be null";
+ private static final String DEVICE_ID_NULL = "Device ID cannot be null";
+ private static final int WAIT_BEFORE_MASTERSHIP_HANDOFF_MILLIS = 3000;
+
+ public static final Serializer SERIALIZER = Serializer.using(
+ KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(MastershipRole.class)
+ .register(MastershipEvent.class)
+ .register(MastershipEvent.Type.class)
+ .register(VirtualDeviceId.class)
+ .build("VirtualMastershipStore"));
+
+ @Activate
+ public void activate() {
+ eventHandler = Executors.newSingleThreadExecutor(
+ groupedThreads("onos/store/virtual/mastership", "event-handler", log));
+
+ messageHandlingExecutor =
+ Executors.newSingleThreadExecutor(
+ groupedThreads("onos/store/virtual/mastership", "message-handler", log));
+ transferExecutor =
+ Executors.newSingleThreadScheduledExecutor(
+ groupedThreads("onos/store/virtual/mastership", "mastership-transfer-executor", log));
+ clusterCommunicator.addSubscriber(ROLE_RELINQUISH_SUBJECT,
+ SERIALIZER::decode,
+ this::relinquishLocalRole,
+ SERIALIZER::encode,
+ messageHandlingExecutor);
+ localNodeId = clusterService.getLocalNode().id();
+ leadershipService.addListener(leadershipEventListener);
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ clusterCommunicator.removeSubscriber(ROLE_RELINQUISH_SUBJECT);
+ leadershipService.removeListener(leadershipEventListener);
+ messageHandlingExecutor.shutdown();
+ transferExecutor.shutdown();
+ eventHandler.shutdown();
+ log.info("Stopped");
+ }
+
+ @Override
+ public CompletableFuture<MastershipRole> requestRole(NetworkId networkId,
+ DeviceId deviceId) {
+ checkArgument(networkId != null, NETWORK_ID_NULL);
+ checkArgument(deviceId != null, DEVICE_ID_NULL);
+
+ String leadershipTopic = createDeviceMastershipTopic(networkId, deviceId);
+ Leadership leadership = leadershipService.runForLeadership(leadershipTopic);
+ return CompletableFuture
+ .completedFuture(localNodeId.equals(leadership.leaderNodeId()) ?
+ MastershipRole.MASTER : MastershipRole.STANDBY);
+ }
+
+ @Override
+ public MastershipRole getRole(NetworkId networkId, NodeId nodeId, DeviceId deviceId) {
+ checkArgument(networkId != null, NETWORK_ID_NULL);
+ checkArgument(nodeId != null, NODE_ID_NULL);
+ checkArgument(deviceId != null, DEVICE_ID_NULL);
+
+ String leadershipTopic = createDeviceMastershipTopic(networkId, deviceId);
+ Leadership leadership = leadershipService.getLeadership(leadershipTopic);
+ NodeId leader = leadership == null ? null : leadership.leaderNodeId();
+ List<NodeId> candidates = leadership == null ?
+ ImmutableList.of() : ImmutableList.copyOf(leadership.candidates());
+ return Objects.equal(nodeId, leader) ?
+ MastershipRole.MASTER : candidates.contains(nodeId) ?
+ MastershipRole.STANDBY : MastershipRole.NONE;
+ }
+
+ @Override
+ public NodeId getMaster(NetworkId networkId, DeviceId deviceId) {
+ checkArgument(deviceId != null, DEVICE_ID_NULL);
+
+ return leadershipService.getLeader(createDeviceMastershipTopic(networkId, deviceId));
+ }
+
+ @Override
+ public RoleInfo getNodes(NetworkId networkId, DeviceId deviceId) {
+ checkArgument(networkId != null, NETWORK_ID_NULL);
+ checkArgument(deviceId != null, DEVICE_ID_NULL);
+ Leadership leadership = leadershipService.getLeadership(createDeviceMastershipTopic(networkId, deviceId));
+ return new RoleInfo(leadership.leaderNodeId(), leadership.candidates());
+ }
+
+ @Override
+ public MastershipInfo getMastership(NetworkId networkId, DeviceId deviceId) {
+ checkArgument(networkId != null, NETWORK_ID_NULL);
+ checkArgument(deviceId != null, DEVICE_ID_NULL);
+ Leadership leadership = leadershipService.getLeadership(createDeviceMastershipTopic(networkId, deviceId));
+ return buildMastershipFromLeadership(leadership);
+ }
+
+ @Override
+ public Set<DeviceId> getDevices(NetworkId networkId, NodeId nodeId) {
+ checkArgument(networkId != null, NETWORK_ID_NULL);
+ checkArgument(nodeId != null, NODE_ID_NULL);
+
+ // FIXME This result contains REMOVED device.
+ // MastershipService cannot listen to DeviceEvent to GC removed topic,
+ // since DeviceManager depend on it.
+ // Reference count, etc. at LeadershipService layer?
+ return leadershipService
+ .ownedTopics(nodeId)
+ .stream()
+ .filter(this::isVirtualMastershipTopic)
+ .map(this::extractDeviceIdFromTopic)
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public CompletableFuture<MastershipEvent> setMaster(NetworkId networkId,
+ NodeId nodeId, DeviceId deviceId) {
+ checkArgument(networkId != null, NETWORK_ID_NULL);
+ checkArgument(nodeId != null, NODE_ID_NULL);
+ checkArgument(deviceId != null, DEVICE_ID_NULL);
+
+ String leadershipTopic = createDeviceMastershipTopic(networkId, deviceId);
+ if (leadershipAdminService.promoteToTopOfCandidateList(leadershipTopic, nodeId)) {
+ transferExecutor.schedule(() -> leadershipAdminService.transferLeadership(leadershipTopic, nodeId),
+ WAIT_BEFORE_MASTERSHIP_HANDOFF_MILLIS, TimeUnit.MILLISECONDS);
+ }
+ return CompletableFuture.completedFuture(null);
+ }
+
+ @Override
+ public MastershipTerm getTermFor(NetworkId networkId, DeviceId deviceId) {
+ checkArgument(networkId != null, NETWORK_ID_NULL);
+ checkArgument(deviceId != null, DEVICE_ID_NULL);
+
+ String leadershipTopic = createDeviceMastershipTopic(networkId, deviceId);
+ Leadership leadership = leadershipService.getLeadership(leadershipTopic);
+ return leadership != null && leadership.leaderNodeId() != null ?
+ MastershipTerm.of(leadership.leaderNodeId(),
+ leadership.leader().term()) : null;
+ }
+
+ @Override
+ public CompletableFuture<MastershipEvent> setStandby(NetworkId networkId,
+ NodeId nodeId,
+ DeviceId deviceId) {
+ checkArgument(networkId != null, NETWORK_ID_NULL);
+ checkArgument(nodeId != null, NODE_ID_NULL);
+ checkArgument(deviceId != null, DEVICE_ID_NULL);
+
+ NodeId currentMaster = getMaster(networkId, deviceId);
+ if (!nodeId.equals(currentMaster)) {
+ return CompletableFuture.completedFuture(null);
+ }
+
+ String leadershipTopic = createDeviceMastershipTopic(networkId, deviceId);
+ List<NodeId> candidates = leadershipService.getCandidates(leadershipTopic);
+
+ NodeId newMaster = candidates.stream()
+ .filter(candidate -> !Objects.equal(nodeId, candidate))
+ .findFirst()
+ .orElse(null);
+ log.info("Transitioning to role {} for {}. Next master: {}",
+ newMaster != null ? MastershipRole.STANDBY : MastershipRole.NONE,
+ deviceId, newMaster);
+
+ if (newMaster != null) {
+ return setMaster(networkId, newMaster, deviceId);
+ }
+ return relinquishRole(networkId, nodeId, deviceId);
+ }
+
+ @Override
+ public CompletableFuture<MastershipEvent> relinquishRole(NetworkId networkId,
+ NodeId nodeId,
+ DeviceId deviceId) {
+ checkArgument(networkId != null, NETWORK_ID_NULL);
+ checkArgument(nodeId != null, NODE_ID_NULL);
+ checkArgument(deviceId != null, DEVICE_ID_NULL);
+
+ if (nodeId.equals(localNodeId)) {
+ return relinquishLocalRoleByNetwork(networkId, deviceId);
+ }
+
+ log.debug("Forwarding request to relinquish "
+ + "role for vnet {} device {} to {}", deviceId, nodeId);
+ return clusterCommunicator.sendAndReceive(
+ new VirtualDeviceId(networkId, deviceId),
+ ROLE_RELINQUISH_SUBJECT,
+ SERIALIZER::encode,
+ SERIALIZER::decode,
+ nodeId);
+ }
+
+ private CompletableFuture<MastershipEvent> relinquishLocalRoleByNetwork(NetworkId networkId,
+ DeviceId deviceId) {
+ checkArgument(networkId != null, NETWORK_ID_NULL);
+ checkArgument(deviceId != null, DEVICE_ID_NULL);
+
+ String leadershipTopic = createDeviceMastershipTopic(networkId, deviceId);
+ if (!leadershipService.getCandidates(leadershipTopic).contains(localNodeId)) {
+ return CompletableFuture.completedFuture(null);
+ }
+ MastershipEvent.Type eventType = localNodeId.equals(leadershipService.getLeader(leadershipTopic)) ?
+ MastershipEvent.Type.MASTER_CHANGED : MastershipEvent.Type.BACKUPS_CHANGED;
+ leadershipService.withdraw(leadershipTopic);
+ return CompletableFuture.completedFuture(
+ new MastershipEvent(eventType, deviceId, getMastership(networkId, deviceId)));
+ }
+
+ private CompletableFuture<MastershipEvent>
+ relinquishLocalRole(VirtualDeviceId virtualDeviceId) {
+ return relinquishLocalRoleByNetwork(virtualDeviceId.networkId,
+ virtualDeviceId.deviceId);
+ }
+
+ @Override
+ public void relinquishAllRole(NetworkId networkId, NodeId nodeId) {
+ // Noop. LeadershipService already takes care of detecting and purging stale locks.
+ }
+
+ private MastershipInfo buildMastershipFromLeadership(Leadership leadership) {
+ ImmutableMap.Builder<NodeId, MastershipRole> builder = ImmutableMap.builder();
+ if (leadership.leaderNodeId() != null) {
+ builder.put(leadership.leaderNodeId(), MastershipRole.MASTER);
+ }
+ leadership.candidates().forEach(nodeId -> builder.put(nodeId, MastershipRole.STANDBY));
+ clusterService.getNodes().stream()
+ .filter(node -> !leadership.candidates().contains(node.id()))
+ .forEach(node -> builder.put(node.id(), MastershipRole.NONE));
+
+ return new MastershipInfo(
+ leadership.leader() != null ? leadership.leader().term() : 0,
+ leadership.leader() != null
+ ? Optional.of(leadership.leader().nodeId())
+ : Optional.empty(),
+ builder.build());
+ }
+
+ private class InternalDeviceMastershipEventListener
+ implements LeadershipEventListener {
+
+ @Override
+ public boolean isRelevant(LeadershipEvent event) {
+ Leadership leadership = event.subject();
+ return isVirtualMastershipTopic(leadership.topic());
+ }
+
+ @Override
+ public void event(LeadershipEvent event) {
+ eventHandler.execute(() -> handleEvent(event));
+ }
+
+ private void handleEvent(LeadershipEvent event) {
+ Leadership leadership = event.subject();
+
+ NetworkId networkId = extractNetworkIdFromTopic(leadership.topic());
+ DeviceId deviceId = extractDeviceIdFromTopic(leadership.topic());
+ MastershipInfo mastershipInfo = event.type() != LeadershipEvent.Type.SERVICE_DISRUPTED
+ ? buildMastershipFromLeadership(event.subject())
+ : new MastershipInfo();
+
+ switch (event.type()) {
+ case LEADER_AND_CANDIDATES_CHANGED:
+ notifyDelegate(networkId, new MastershipEvent(BACKUPS_CHANGED, deviceId, mastershipInfo));
+ notifyDelegate(networkId, new MastershipEvent(MASTER_CHANGED, deviceId, mastershipInfo));
+ break;
+ case LEADER_CHANGED:
+ notifyDelegate(networkId, new MastershipEvent(MASTER_CHANGED, deviceId, mastershipInfo));
+ break;
+ case CANDIDATES_CHANGED:
+ notifyDelegate(networkId, new MastershipEvent(BACKUPS_CHANGED, deviceId, mastershipInfo));
+ break;
+ case SERVICE_DISRUPTED:
+ notifyDelegate(networkId, new MastershipEvent(SUSPENDED, deviceId, mastershipInfo));
+ break;
+ case SERVICE_RESTORED:
+ // Do nothing, wait for updates from peers
+ break;
+ default:
+ }
+ }
+ }
+
+ private String createDeviceMastershipTopic(NetworkId networkId, DeviceId deviceId) {
+ return String.format("vnet:%s,device:%s", networkId.toString(), deviceId.toString());
+ }
+
+ /**
+ * Returns the virtual network identifier extracted from the topic.
+ *
+ * @param topic topic to extract virtual network identifier
+ * @return an extracted virtual network identifier
+ * @throws IllegalArgumentException the topic not match with the pattern
+ * used for virtual network mastership store
+ */
+ private NetworkId extractNetworkIdFromTopic(String topic) {
+ Matcher m = DEVICE_MASTERSHIP_TOPIC_PATTERN.matcher(topic);
+ if (m.matches()) {
+ return NetworkId.networkId(Long.getLong(m.group(1)));
+ } else {
+ throw new IllegalArgumentException("Invalid virtual mastership topic: "
+ + topic);
+ }
+ }
+
+ /**
+ * Returns the device identifier extracted from the topic.
+ *
+ * @param topic topic to extract device identifier
+ * @return an extracted virtual device identifier
+ * @throws IllegalArgumentException the topic not match with the pattern
+ * used for virtual network mastership store
+ */
+ private DeviceId extractDeviceIdFromTopic(String topic) {
+ Matcher m = DEVICE_MASTERSHIP_TOPIC_PATTERN.matcher(topic);
+ if (m.matches()) {
+ return DeviceId.deviceId(m.group(2));
+ } else {
+ throw new IllegalArgumentException("Invalid virtual mastership topic: "
+ + topic);
+ }
+ }
+
+ /**
+ * Returns whether the topic is matched with virtual mastership store topic.
+ *
+ * @param topic topic to match
+ * @return True when the topic matched with virtual network mastership store
+ */
+ private boolean isVirtualMastershipTopic(String topic) {
+ Matcher m = DEVICE_MASTERSHIP_TOPIC_PATTERN.matcher(topic);
+ return m.matches();
+ }
+
+ /**
+ * A wrapper class used for the communication service.
+ */
+ private class VirtualDeviceId {
+ NetworkId networkId;
+ DeviceId deviceId;
+
+ public VirtualDeviceId(NetworkId networkId, DeviceId deviceId) {
+ this.networkId = networkId;
+ this.deviceId = deviceId;
+ }
+
+ public int hashCode() {
+ return Objects.hashCode(networkId, deviceId);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof VirtualDeviceId) {
+ final VirtualDeviceId that = (VirtualDeviceId) obj;
+ return this.getClass() == that.getClass() &&
+ Objects.equal(this.networkId, that.networkId) &&
+ Objects.equal(this.deviceId, that.deviceId);
+ }
+ return false;
+ }
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/DistributedVirtualFlowObjectiveStore.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/DistributedVirtualFlowObjectiveStore.java
new file mode 100644
index 0000000..5bcfabc
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/DistributedVirtualFlowObjectiveStore.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl;
+
+import com.google.common.collect.Maps;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkFlowObjectiveStore;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.Serializer;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+
+import java.util.concurrent.ConcurrentMap;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Distributed flow objective store for virtual network.
+ */
+@Component(immediate = true, enabled = false, service = VirtualNetworkFlowObjectiveStore.class)
+public class DistributedVirtualFlowObjectiveStore
+ extends SimpleVirtualFlowObjectiveStore
+ implements VirtualNetworkFlowObjectiveStore {
+
+ private final Logger log = getLogger(getClass());
+
+ private ConsistentMap<NetworkId, ConcurrentMap<Integer, byte[]>> nextGroupsMap;
+ private static final String VNET_FLOW_OBJ_GROUP_MAP_NAME =
+ "onos-networkId-flowobjective-groups";
+ private static final String VNET_FLOW_OBJ_GROUP_MAP_FRIENDLYNAME =
+ "DistributedVirtualFlowObjectiveStore";
+
+ @Override
+ protected void initNextGroupsMap() {
+ nextGroupsMap = storageService.<NetworkId, ConcurrentMap<Integer, byte[]>>consistentMapBuilder()
+ .withName(VNET_FLOW_OBJ_GROUP_MAP_NAME)
+ .withSerializer(Serializer.using(
+ new KryoNamespace.Builder()
+ .register(KryoNamespaces.API)
+ .register(NetworkId.class)
+ .build(VNET_FLOW_OBJ_GROUP_MAP_FRIENDLYNAME)))
+ .build();
+
+ }
+
+ @Override
+ protected ConcurrentMap<Integer, byte[]> getNextGroups(NetworkId networkId) {
+ nextGroupsMap.computeIfAbsent(networkId, n -> {
+ log.debug("getNextGroups - creating new ConcurrentMap");
+ return Maps.newConcurrentMap();
+ });
+
+ return nextGroupsMap.get(networkId).value();
+ }
+
+ @Override
+ protected void updateNextGroupsMap(NetworkId networkId, ConcurrentMap<Integer,
+ byte[]> nextGroups) {
+ nextGroupsMap.put(networkId, nextGroups);
+ }
+
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/DistributedVirtualFlowRuleStore.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/DistributedVirtualFlowRuleStore.java
new file mode 100644
index 0000000..8722c92
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/DistributedVirtualFlowRuleStore.java
@@ -0,0 +1,922 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+import com.google.common.util.concurrent.Futures;
+import org.onlab.util.KryoNamespace;
+import org.onlab.util.Tools;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.core.CoreService;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkFlowRuleStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.store.impl.primitives.VirtualDeviceId;
+import org.onosproject.incubator.net.virtual.store.impl.primitives.VirtualFlowEntry;
+import org.onosproject.incubator.net.virtual.store.impl.primitives.VirtualFlowRule;
+import org.onosproject.incubator.net.virtual.store.impl.primitives.VirtualFlowRuleBatchEvent;
+import org.onosproject.incubator.net.virtual.store.impl.primitives.VirtualFlowRuleBatchOperation;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.BatchOperationEntry;
+import org.onosproject.net.flow.CompletedBatchOperation;
+import org.onosproject.net.flow.DefaultFlowEntry;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowId;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleEvent;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.FlowRuleStoreDelegate;
+import org.onosproject.net.flow.StoredFlowEntry;
+import org.onosproject.net.flow.TableStatisticsEntry;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEvent;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchRequest;
+import org.onosproject.store.Timestamp;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
+import org.onosproject.store.cluster.messaging.ClusterMessage;
+import org.onosproject.store.cluster.messaging.ClusterMessageHandler;
+import org.onosproject.store.cluster.messaging.MessageSubject;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.EventuallyConsistentMap;
+import org.onosproject.store.service.EventuallyConsistentMapEvent;
+import org.onosproject.store.service.EventuallyConsistentMapListener;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.WallClockTimestamp;
+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.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static org.onlab.util.Tools.get;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.incubator.net.virtual.store.impl.OsgiPropertyConstants.*;
+import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_REMOVED;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages inventory of flow rules using a distributed state management protocol
+ * for virtual networks.
+ */
+//TODO: support backup and persistent mechanism
+@Component(immediate = true, enabled = false, service = VirtualNetworkFlowRuleStore.class,
+ property = {
+ MESSAGE_HANDLER_THREAD_POOL_SIZE + ":Integer=" + MESSAGE_HANDLER_THREAD_POOL_SIZE_DEFAULT,
+ BACKUP_PERIOD_MILLIS + ":Integer=" + BACKUP_PERIOD_MILLIS_DEFAULT,
+ PERSISTENCE_ENABLED + ":Boolean=" + PERSISTENCE_ENABLED_DEFAULT,
+ })
+
+public class DistributedVirtualFlowRuleStore
+ extends AbstractVirtualStore<FlowRuleBatchEvent, FlowRuleStoreDelegate>
+ implements VirtualNetworkFlowRuleStore {
+
+ private final Logger log = getLogger(getClass());
+
+ //TODO: confirm this working fine with multiple thread more than 1
+ private static final long FLOW_RULE_STORE_TIMEOUT_MILLIS = 5000;
+
+ private static final String FLOW_OP_TOPIC = "virtual-flow-ops-ids";
+
+ // MessageSubjects used by DistributedVirtualFlowRuleStore peer-peer communication.
+ private static final MessageSubject APPLY_BATCH_FLOWS
+ = new MessageSubject("virtual-peer-forward-apply-batch");
+ private static final MessageSubject GET_FLOW_ENTRY
+ = new MessageSubject("virtual-peer-forward-get-flow-entry");
+ private static final MessageSubject GET_DEVICE_FLOW_ENTRIES
+ = new MessageSubject("virtual-peer-forward-get-device-flow-entries");
+ private static final MessageSubject REMOVE_FLOW_ENTRY
+ = new MessageSubject("virtual-peer-forward-remove-flow-entry");
+ private static final MessageSubject REMOTE_APPLY_COMPLETED
+ = new MessageSubject("virtual-peer-apply-completed");
+
+ /** Number of threads in the message handler pool. */
+ private int msgHandlerThreadPoolSize = MESSAGE_HANDLER_THREAD_POOL_SIZE_DEFAULT;
+
+ /** Delay in ms between successive backup runs. */
+ private int backupPeriod = BACKUP_PERIOD_MILLIS_DEFAULT;
+
+ /** Indicates whether or not changes in the flow table should be persisted to disk.. */
+ private boolean persistenceEnabled = PERSISTENCE_ENABLED_DEFAULT;
+
+ private InternalFlowTable flowTable = new InternalFlowTable();
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected ClusterService clusterService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected ClusterCommunicationService clusterCommunicator;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected ComponentConfigService configService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected StorageService storageService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected VirtualNetworkService vnaService;
+
+ private Map<Long, NodeId> pendingResponses = Maps.newConcurrentMap();
+ private ExecutorService messageHandlingExecutor;
+ private ExecutorService eventHandler;
+
+ private EventuallyConsistentMap<NetworkId, Map<DeviceId, List<TableStatisticsEntry>>> deviceTableStats;
+ private final EventuallyConsistentMapListener<NetworkId, Map<DeviceId, List<TableStatisticsEntry>>>
+ tableStatsListener = new InternalTableStatsListener();
+
+
+ protected final Serializer serializer = Serializer.using(KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(NetworkId.class)
+ .register(VirtualFlowRuleBatchOperation.class)
+ .register(VirtualFlowRuleBatchEvent.class)
+ .build());
+
+ protected final KryoNamespace.Builder serializerBuilder = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(MastershipBasedTimestamp.class);
+
+ private IdGenerator idGenerator;
+ private NodeId local;
+
+
+ @Activate
+ public void activate(ComponentContext context) {
+ configService.registerProperties(getClass());
+
+ idGenerator = coreService.getIdGenerator(FLOW_OP_TOPIC);
+
+ local = clusterService.getLocalNode().id();
+
+ eventHandler = Executors.newSingleThreadExecutor(
+ groupedThreads("onos/virtual-flow", "event-handler", log));
+ messageHandlingExecutor = Executors.newFixedThreadPool(
+ msgHandlerThreadPoolSize, groupedThreads("onos/store/virtual-flow", "message-handlers", log));
+
+ registerMessageHandlers(messageHandlingExecutor);
+
+ deviceTableStats = storageService
+ .<NetworkId, Map<DeviceId, List<TableStatisticsEntry>>>eventuallyConsistentMapBuilder()
+ .withName("onos-virtual-flow-table-stats")
+ .withSerializer(serializerBuilder)
+ .withAntiEntropyPeriod(5, TimeUnit.SECONDS)
+ .withTimestampProvider((k, v) -> new WallClockTimestamp())
+ .withTombstonesDisabled()
+ .build();
+ deviceTableStats.addListener(tableStatsListener);
+
+ logConfig("Started");
+ }
+
+ @Deactivate
+ public void deactivate(ComponentContext context) {
+ configService.unregisterProperties(getClass(), false);
+ unregisterMessageHandlers();
+ deviceTableStats.removeListener(tableStatsListener);
+ deviceTableStats.destroy();
+ eventHandler.shutdownNow();
+ messageHandlingExecutor.shutdownNow();
+ log.info("Stopped");
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Modified
+ public void modified(ComponentContext context) {
+ if (context == null) {
+ logConfig("Default config");
+ return;
+ }
+
+ Dictionary properties = context.getProperties();
+ int newPoolSize;
+ int newBackupPeriod;
+ try {
+ String s = get(properties, MESSAGE_HANDLER_THREAD_POOL_SIZE);
+ newPoolSize = isNullOrEmpty(s) ? msgHandlerThreadPoolSize : Integer.parseInt(s.trim());
+
+ s = get(properties, BACKUP_PERIOD_MILLIS);
+ newBackupPeriod = isNullOrEmpty(s) ? backupPeriod : Integer.parseInt(s.trim());
+ } catch (NumberFormatException | ClassCastException e) {
+ newPoolSize = MESSAGE_HANDLER_THREAD_POOL_SIZE_DEFAULT;
+ newBackupPeriod = BACKUP_PERIOD_MILLIS_DEFAULT;
+ }
+
+ boolean restartBackupTask = false;
+
+ if (newBackupPeriod != backupPeriod) {
+ backupPeriod = newBackupPeriod;
+ restartBackupTask = true;
+ }
+ if (restartBackupTask) {
+ log.warn("Currently, backup tasks are not supported.");
+ }
+ if (newPoolSize != msgHandlerThreadPoolSize) {
+ msgHandlerThreadPoolSize = newPoolSize;
+ ExecutorService oldMsgHandler = messageHandlingExecutor;
+ messageHandlingExecutor = Executors.newFixedThreadPool(
+ msgHandlerThreadPoolSize, groupedThreads("onos/store/virtual-flow", "message-handlers", log));
+
+ // replace previously registered handlers.
+ registerMessageHandlers(messageHandlingExecutor);
+ oldMsgHandler.shutdown();
+ }
+
+ logConfig("Reconfigured");
+ }
+
+ @Override
+ public int getFlowRuleCount(NetworkId networkId) {
+ AtomicInteger sum = new AtomicInteger(0);
+ DeviceService deviceService = vnaService.get(networkId, DeviceService.class);
+ deviceService.getDevices()
+ .forEach(device -> sum.addAndGet(
+ Iterables.size(getFlowEntries(networkId, device.id()))));
+ return sum.get();
+ }
+
+ @Override
+ public FlowEntry getFlowEntry(NetworkId networkId, FlowRule rule) {
+ MastershipService mastershipService =
+ vnaService.get(networkId, MastershipService.class);
+ NodeId master = mastershipService.getMasterFor(rule.deviceId());
+
+ if (master == null) {
+ log.debug("Failed to getFlowEntry: No master for {}, vnet {}",
+ rule.deviceId(), networkId);
+ return null;
+ }
+
+ if (Objects.equals(local, master)) {
+ return flowTable.getFlowEntry(networkId, rule);
+ }
+
+ log.trace("Forwarding getFlowEntry to {}, which is the primary (master) " +
+ "for device {}, vnet {}",
+ master, rule.deviceId(), networkId);
+
+ VirtualFlowRule vRule = new VirtualFlowRule(networkId, rule);
+
+ return Tools.futureGetOrElse(clusterCommunicator.sendAndReceive(vRule,
+ GET_FLOW_ENTRY,
+ serializer::encode,
+ serializer::decode,
+ master),
+ FLOW_RULE_STORE_TIMEOUT_MILLIS,
+ TimeUnit.MILLISECONDS,
+ null);
+ }
+
+ @Override
+ public Iterable<FlowEntry> getFlowEntries(NetworkId networkId, DeviceId deviceId) {
+ MastershipService mastershipService =
+ vnaService.get(networkId, MastershipService.class);
+ NodeId master = mastershipService.getMasterFor(deviceId);
+
+ if (master == null) {
+ log.debug("Failed to getFlowEntries: No master for {}, vnet {}", deviceId, networkId);
+ return Collections.emptyList();
+ }
+
+ if (Objects.equals(local, master)) {
+ return flowTable.getFlowEntries(networkId, deviceId);
+ }
+
+ log.trace("Forwarding getFlowEntries to {}, which is the primary (master) for device {}",
+ master, deviceId);
+
+ return Tools.futureGetOrElse(
+ clusterCommunicator.sendAndReceive(deviceId,
+ GET_DEVICE_FLOW_ENTRIES,
+ serializer::encode,
+ serializer::decode,
+ master),
+ FLOW_RULE_STORE_TIMEOUT_MILLIS,
+ TimeUnit.MILLISECONDS,
+ Collections.emptyList());
+ }
+
+ @Override
+ public void storeBatch(NetworkId networkId, FlowRuleBatchOperation operation) {
+ if (operation.getOperations().isEmpty()) {
+ notifyDelegate(networkId, FlowRuleBatchEvent.completed(
+ new FlowRuleBatchRequest(operation.id(), Collections.emptySet()),
+ new CompletedBatchOperation(true, Collections.emptySet(), operation.deviceId())));
+ return;
+ }
+
+ DeviceId deviceId = operation.deviceId();
+ MastershipService mastershipService =
+ vnaService.get(networkId, MastershipService.class);
+ NodeId master = mastershipService.getMasterFor(deviceId);
+
+ if (master == null) {
+ log.warn("No master for {}, vnet {} : flows will be marked for removal", deviceId, networkId);
+
+ updateStoreInternal(networkId, operation);
+
+ notifyDelegate(networkId, FlowRuleBatchEvent.completed(
+ new FlowRuleBatchRequest(operation.id(), Collections.emptySet()),
+ new CompletedBatchOperation(true, Collections.emptySet(), operation.deviceId())));
+ return;
+ }
+
+ if (Objects.equals(local, master)) {
+ storeBatchInternal(networkId, operation);
+ return;
+ }
+
+ log.trace("Forwarding storeBatch to {}, which is the primary (master) for device {}, vent {}",
+ master, deviceId, networkId);
+
+ clusterCommunicator.unicast(new VirtualFlowRuleBatchOperation(networkId, operation),
+ APPLY_BATCH_FLOWS,
+ serializer::encode,
+ master)
+ .whenComplete((result, error) -> {
+ if (error != null) {
+ log.warn("Failed to storeBatch: {} to {}", operation, master, error);
+
+ Set<FlowRule> allFailures = operation.getOperations()
+ .stream()
+ .map(BatchOperationEntry::target)
+ .collect(Collectors.toSet());
+
+ notifyDelegate(networkId, FlowRuleBatchEvent.completed(
+ new FlowRuleBatchRequest(operation.id(), Collections.emptySet()),
+ new CompletedBatchOperation(false, allFailures, deviceId)));
+ }
+ });
+ }
+
+ @Override
+ public void batchOperationComplete(NetworkId networkId, FlowRuleBatchEvent event) {
+ //FIXME: need a per device pending response
+ NodeId nodeId = pendingResponses.remove(event.subject().batchId());
+ if (nodeId == null) {
+ notifyDelegate(networkId, event);
+ } else {
+ // TODO check unicast return value
+ clusterCommunicator.unicast(new VirtualFlowRuleBatchEvent(networkId, event),
+ REMOTE_APPLY_COMPLETED, serializer::encode, nodeId);
+ //error log: log.warn("Failed to respond to peer for batch operation result");
+ }
+ }
+
+ @Override
+ public void deleteFlowRule(NetworkId networkId, FlowRule rule) {
+ storeBatch(networkId,
+ new FlowRuleBatchOperation(
+ Collections.singletonList(
+ new FlowRuleBatchEntry(
+ FlowRuleBatchEntry.FlowRuleOperation.REMOVE,
+ rule)), rule.deviceId(), idGenerator.getNewId()));
+ }
+
+ @Override
+ public FlowRuleEvent addOrUpdateFlowRule(NetworkId networkId, FlowEntry rule) {
+ MastershipService mastershipService =
+ vnaService.get(networkId, MastershipService.class);
+ NodeId master = mastershipService.getMasterFor(rule.deviceId());
+ if (Objects.equals(local, master)) {
+ return addOrUpdateFlowRuleInternal(networkId, rule);
+ }
+
+ log.warn("Tried to update FlowRule {} state,"
+ + " while the Node was not the master.", rule);
+ return null;
+ }
+
+ private FlowRuleEvent addOrUpdateFlowRuleInternal(NetworkId networkId, FlowEntry rule) {
+ // check if this new rule is an update to an existing entry
+ StoredFlowEntry stored = flowTable.getFlowEntry(networkId, rule);
+ if (stored != null) {
+ //FIXME modification of "stored" flow entry outside of flow table
+ stored.setBytes(rule.bytes());
+ stored.setLife(rule.life(TimeUnit.NANOSECONDS), TimeUnit.NANOSECONDS);
+ stored.setLiveType(rule.liveType());
+ stored.setPackets(rule.packets());
+ stored.setLastSeen();
+ if (stored.state() == FlowEntry.FlowEntryState.PENDING_ADD) {
+ stored.setState(FlowEntry.FlowEntryState.ADDED);
+ return new FlowRuleEvent(FlowRuleEvent.Type.RULE_ADDED, rule);
+ }
+ return new FlowRuleEvent(FlowRuleEvent.Type.RULE_UPDATED, rule);
+ }
+
+ // TODO: Confirm if this behavior is correct. See SimpleFlowRuleStore
+ // TODO: also update backup if the behavior is correct.
+ flowTable.add(networkId, rule);
+ return null;
+ }
+
+ @Override
+ public FlowRuleEvent removeFlowRule(NetworkId networkId, FlowEntry rule) {
+ final DeviceId deviceId = rule.deviceId();
+
+ MastershipService mastershipService =
+ vnaService.get(networkId, MastershipService.class);
+ NodeId master = mastershipService.getMasterFor(deviceId);
+
+ if (Objects.equals(local, master)) {
+ // bypass and handle it locally
+ return removeFlowRuleInternal(new VirtualFlowEntry(networkId, rule));
+ }
+
+ if (master == null) {
+ log.warn("Failed to removeFlowRule: No master for {}", deviceId);
+ // TODO: revisit if this should be null (="no-op") or Exception
+ return null;
+ }
+
+ log.trace("Forwarding removeFlowRule to {}, which is the master for device {}",
+ master, deviceId);
+
+ return Futures.getUnchecked(clusterCommunicator.sendAndReceive(
+ new VirtualFlowEntry(networkId, rule),
+ REMOVE_FLOW_ENTRY,
+ serializer::encode,
+ serializer::decode,
+ master));
+ }
+
+ @Override
+ public FlowRuleEvent pendingFlowRule(NetworkId networkId, FlowEntry rule) {
+ MastershipService mastershipService =
+ vnaService.get(networkId, MastershipService.class);
+ if (mastershipService.isLocalMaster(rule.deviceId())) {
+ StoredFlowEntry stored = flowTable.getFlowEntry(networkId, rule);
+ if (stored != null &&
+ stored.state() != FlowEntry.FlowEntryState.PENDING_ADD) {
+ stored.setState(FlowEntry.FlowEntryState.PENDING_ADD);
+ return new FlowRuleEvent(FlowRuleEvent.Type.RULE_UPDATED, rule);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void purgeFlowRules(NetworkId networkId) {
+ flowTable.purgeFlowRules(networkId);
+ }
+
+ @Override
+ public FlowRuleEvent updateTableStatistics(NetworkId networkId,
+ DeviceId deviceId,
+ List<TableStatisticsEntry> tableStats) {
+ if (deviceTableStats.get(networkId) == null) {
+ deviceTableStats.put(networkId, Maps.newConcurrentMap());
+ }
+ deviceTableStats.get(networkId).put(deviceId, tableStats);
+ return null;
+ }
+
+ @Override
+ public Iterable<TableStatisticsEntry> getTableStatistics(NetworkId networkId, DeviceId deviceId) {
+ MastershipService mastershipService =
+ vnaService.get(networkId, MastershipService.class);
+ NodeId master = mastershipService.getMasterFor(deviceId);
+
+ if (master == null) {
+ log.debug("Failed to getTableStats: No master for {}", deviceId);
+ return Collections.emptyList();
+ }
+
+ if (deviceTableStats.get(networkId) == null) {
+ deviceTableStats.put(networkId, Maps.newConcurrentMap());
+ }
+
+ List<TableStatisticsEntry> tableStats = deviceTableStats.get(networkId).get(deviceId);
+ if (tableStats == null) {
+ return Collections.emptyList();
+ }
+ return ImmutableList.copyOf(tableStats);
+ }
+
+ private void registerMessageHandlers(ExecutorService executor) {
+ clusterCommunicator.addSubscriber(APPLY_BATCH_FLOWS, new OnStoreBatch(), executor);
+ clusterCommunicator.<VirtualFlowRuleBatchEvent>addSubscriber(
+ REMOTE_APPLY_COMPLETED, serializer::decode,
+ this::notifyDelicateByNetwork, executor);
+ clusterCommunicator.addSubscriber(
+ GET_FLOW_ENTRY, serializer::decode, this::getFlowEntryByNetwork,
+ serializer::encode, executor);
+ clusterCommunicator.addSubscriber(
+ GET_DEVICE_FLOW_ENTRIES, serializer::decode,
+ this::getFlowEntriesByNetwork,
+ serializer::encode, executor);
+ clusterCommunicator.addSubscriber(
+ REMOVE_FLOW_ENTRY, serializer::decode, this::removeFlowRuleInternal,
+ serializer::encode, executor);
+ }
+
+ private void unregisterMessageHandlers() {
+ clusterCommunicator.removeSubscriber(REMOVE_FLOW_ENTRY);
+ clusterCommunicator.removeSubscriber(GET_DEVICE_FLOW_ENTRIES);
+ clusterCommunicator.removeSubscriber(GET_FLOW_ENTRY);
+ clusterCommunicator.removeSubscriber(APPLY_BATCH_FLOWS);
+ clusterCommunicator.removeSubscriber(REMOTE_APPLY_COMPLETED);
+ }
+
+
+ private void logConfig(String prefix) {
+ log.info("{} with msgHandlerPoolSize = {}; backupPeriod = {}",
+ prefix, msgHandlerThreadPoolSize, backupPeriod);
+ }
+
+ private void storeBatchInternal(NetworkId networkId, FlowRuleBatchOperation operation) {
+
+ final DeviceId did = operation.deviceId();
+ //final Collection<FlowEntry> ft = flowTable.getFlowEntries(did);
+ Set<FlowRuleBatchEntry> currentOps = updateStoreInternal(networkId, operation);
+ if (currentOps.isEmpty()) {
+ batchOperationComplete(networkId, FlowRuleBatchEvent.completed(
+ new FlowRuleBatchRequest(operation.id(), Collections.emptySet()),
+ new CompletedBatchOperation(true, Collections.emptySet(), did)));
+ return;
+ }
+
+ //Confirm that flowrule service is created
+ vnaService.get(networkId, FlowRuleService.class);
+
+ notifyDelegate(networkId, FlowRuleBatchEvent.requested(new
+ FlowRuleBatchRequest(operation.id(),
+ currentOps), operation.deviceId()));
+ }
+
+ private Set<FlowRuleBatchEntry> updateStoreInternal(NetworkId networkId,
+ FlowRuleBatchOperation operation) {
+ return operation.getOperations().stream().map(
+ op -> {
+ StoredFlowEntry entry;
+ switch (op.operator()) {
+ case ADD:
+ entry = new DefaultFlowEntry(op.target());
+ // always add requested FlowRule
+ // Note: 2 equal FlowEntry may have different treatment
+ flowTable.remove(networkId, entry.deviceId(), entry);
+ flowTable.add(networkId, entry);
+
+ return op;
+ case REMOVE:
+ entry = flowTable.getFlowEntry(networkId, op.target());
+ if (entry != null) {
+ //FIXME modification of "stored" flow entry outside of flow table
+ entry.setState(FlowEntry.FlowEntryState.PENDING_REMOVE);
+ log.debug("Setting state of rule to pending remove: {}", entry);
+ return op;
+ }
+ break;
+ case MODIFY:
+ //TODO: figure this out at some point
+ break;
+ default:
+ log.warn("Unknown flow operation operator: {}", op.operator());
+ }
+ return null;
+ }
+ ).filter(Objects::nonNull).collect(Collectors.toSet());
+ }
+
+ private FlowRuleEvent removeFlowRuleInternal(VirtualFlowEntry rule) {
+ final DeviceId deviceId = rule.flowEntry().deviceId();
+ // This is where one could mark a rule as removed and still keep it in the store.
+ final FlowEntry removed = flowTable.remove(rule.networkId(), deviceId, rule.flowEntry());
+ // rule may be partial rule that is missing treatment, we should use rule from store instead
+ return removed != null ? new FlowRuleEvent(RULE_REMOVED, removed) : null;
+ }
+
+ private final class OnStoreBatch implements ClusterMessageHandler {
+
+ @Override
+ public void handle(final ClusterMessage message) {
+ VirtualFlowRuleBatchOperation vOperation = serializer.decode(message.payload());
+ log.debug("received batch request {}", vOperation);
+
+ FlowRuleBatchOperation operation = vOperation.operation();
+
+ final DeviceId deviceId = operation.deviceId();
+ MastershipService mastershipService =
+ vnaService.get(vOperation.networkId(), MastershipService.class);
+ NodeId master = mastershipService.getMasterFor(deviceId);
+ if (!Objects.equals(local, master)) {
+ Set<FlowRule> failures = new HashSet<>(operation.size());
+ for (FlowRuleBatchEntry op : operation.getOperations()) {
+ failures.add(op.target());
+ }
+ CompletedBatchOperation allFailed = new CompletedBatchOperation(false, failures, deviceId);
+ // This node is no longer the master, respond as all failed.
+ // TODO: we might want to wrap response in envelope
+ // to distinguish sw programming failure and hand over
+ // it make sense in the latter case to retry immediately.
+ message.respond(serializer.encode(allFailed));
+ return;
+ }
+
+ pendingResponses.put(operation.id(), message.sender());
+ storeBatchInternal(vOperation.networkId(), operation);
+ }
+ }
+
+ /**
+ * Returns flow rule entry using virtual flow rule.
+ *
+ * @param rule an encapsulated flow rule to be queried
+ */
+ private FlowEntry getFlowEntryByNetwork(VirtualFlowRule rule) {
+ return flowTable.getFlowEntry(rule.networkId(), rule.rule());
+ }
+
+ /**
+ * returns flow entries using virtual device id.
+ *
+ * @param deviceId an encapsulated virtual device id
+ * @return a set of flow entries
+ */
+ private Set<FlowEntry> getFlowEntriesByNetwork(VirtualDeviceId deviceId) {
+ return flowTable.getFlowEntries(deviceId.networkId(), deviceId.deviceId());
+ }
+
+ /**
+ * span out Flow Rule Batch event according to virtual network id.
+ *
+ * @param event a event to be span out
+ */
+ private void notifyDelicateByNetwork(VirtualFlowRuleBatchEvent event) {
+ batchOperationComplete(event.networkId(), event.event());
+ }
+
+ private class InternalFlowTable {
+ //TODO replace the Map<V,V> with ExtendedSet
+ //TODO: support backup mechanism
+ private final Map<NetworkId, Map<DeviceId, Map<FlowId, Map<StoredFlowEntry, StoredFlowEntry>>>>
+ flowEntriesMap = Maps.newConcurrentMap();
+ private final Map<NetworkId, Map<DeviceId, Long>> lastUpdateTimesMap = Maps.newConcurrentMap();
+
+ private Map<DeviceId, Map<FlowId, Map<StoredFlowEntry, StoredFlowEntry>>>
+ getFlowEntriesByNetwork(NetworkId networkId) {
+ return flowEntriesMap.computeIfAbsent(networkId, k -> Maps.newConcurrentMap());
+ }
+
+ private Map<DeviceId, Long> getLastUpdateTimesByNetwork(NetworkId networkId) {
+ return lastUpdateTimesMap.computeIfAbsent(networkId, k -> Maps.newConcurrentMap());
+ }
+
+ /**
+ * Returns the flow table for specified device.
+ *
+ * @param networkId virtual network identifier
+ * @param deviceId identifier of the device
+ * @return Map representing Flow Table of given device.
+ */
+ private Map<FlowId, Map<StoredFlowEntry, StoredFlowEntry>>
+ getFlowTable(NetworkId networkId, DeviceId deviceId) {
+ Map<DeviceId, Map<FlowId, Map<StoredFlowEntry, StoredFlowEntry>>>
+ flowEntries = getFlowEntriesByNetwork(networkId);
+ if (persistenceEnabled) {
+ //TODO: support persistent
+ log.warn("Persistent is not supported");
+ return null;
+ } else {
+ return flowEntries.computeIfAbsent(deviceId, id -> Maps.newConcurrentMap());
+ }
+ }
+
+ private Map<FlowId, Map<StoredFlowEntry, StoredFlowEntry>>
+ getFlowTableCopy(NetworkId networkId, DeviceId deviceId) {
+
+ Map<DeviceId, Map<FlowId, Map<StoredFlowEntry, StoredFlowEntry>>>
+ flowEntries = getFlowEntriesByNetwork(networkId);
+ Map<FlowId, Map<StoredFlowEntry, StoredFlowEntry>> copy = Maps.newHashMap();
+
+ if (persistenceEnabled) {
+ //TODO: support persistent
+ log.warn("Persistent is not supported");
+ return null;
+ } else {
+ flowEntries.computeIfAbsent(deviceId, id -> Maps.newConcurrentMap()).forEach((k, v) -> {
+ copy.put(k, Maps.newHashMap(v));
+ });
+ return copy;
+ }
+ }
+
+ private Map<StoredFlowEntry, StoredFlowEntry>
+ getFlowEntriesInternal(NetworkId networkId, DeviceId deviceId, FlowId flowId) {
+
+ return getFlowTable(networkId, deviceId)
+ .computeIfAbsent(flowId, id -> Maps.newConcurrentMap());
+ }
+
+ private StoredFlowEntry getFlowEntryInternal(NetworkId networkId, FlowRule rule) {
+
+ return getFlowEntriesInternal(networkId, rule.deviceId(), rule.id()).get(rule);
+ }
+
+ private Set<FlowEntry> getFlowEntriesInternal(NetworkId networkId, DeviceId deviceId) {
+
+ return getFlowTable(networkId, deviceId).values().stream()
+ .flatMap(m -> m.values().stream())
+ .collect(Collectors.toSet());
+ }
+
+ public StoredFlowEntry getFlowEntry(NetworkId networkId, FlowRule rule) {
+ return getFlowEntryInternal(networkId, rule);
+ }
+
+ public Set<FlowEntry> getFlowEntries(NetworkId networkId, DeviceId deviceId) {
+
+ return getFlowEntriesInternal(networkId, deviceId);
+ }
+
+ public void add(NetworkId networkId, FlowEntry rule) {
+ Map<DeviceId, Long> lastUpdateTimes = getLastUpdateTimesByNetwork(networkId);
+
+ getFlowEntriesInternal(networkId, rule.deviceId(), rule.id())
+ .compute((StoredFlowEntry) rule, (k, stored) -> {
+ //TODO compare stored and rule timestamps
+ //TODO the key is not updated
+ return (StoredFlowEntry) rule;
+ });
+ lastUpdateTimes.put(rule.deviceId(), System.currentTimeMillis());
+ }
+
+ public FlowEntry remove(NetworkId networkId, DeviceId deviceId, FlowEntry rule) {
+ final AtomicReference<FlowEntry> removedRule = new AtomicReference<>();
+ Map<DeviceId, Long> lastUpdateTimes = getLastUpdateTimesByNetwork(networkId);
+
+ getFlowEntriesInternal(networkId, rule.deviceId(), rule.id())
+ .computeIfPresent((StoredFlowEntry) rule, (k, stored) -> {
+ if (rule instanceof DefaultFlowEntry) {
+ DefaultFlowEntry toRemove = (DefaultFlowEntry) rule;
+ if (stored instanceof DefaultFlowEntry) {
+ DefaultFlowEntry storedEntry = (DefaultFlowEntry) stored;
+ if (toRemove.created() < storedEntry.created()) {
+ log.debug("Trying to remove more recent flow entry {} (stored: {})",
+ toRemove, stored);
+ // the key is not updated, removedRule remains null
+ return stored;
+ }
+ }
+ }
+ removedRule.set(stored);
+ return null;
+ });
+
+ if (removedRule.get() != null) {
+ lastUpdateTimes.put(deviceId, System.currentTimeMillis());
+ return removedRule.get();
+ } else {
+ return null;
+ }
+ }
+
+ public void purgeFlowRule(NetworkId networkId, DeviceId deviceId) {
+ Map<DeviceId, Map<FlowId, Map<StoredFlowEntry, StoredFlowEntry>>>
+ flowEntries = getFlowEntriesByNetwork(networkId);
+ flowEntries.remove(deviceId);
+ }
+
+ public void purgeFlowRules(NetworkId networkId) {
+ Map<DeviceId, Map<FlowId, Map<StoredFlowEntry, StoredFlowEntry>>>
+ flowEntries = getFlowEntriesByNetwork(networkId);
+ flowEntries.clear();
+ }
+ }
+
+ private class InternalTableStatsListener
+ implements EventuallyConsistentMapListener<NetworkId, Map<DeviceId, List<TableStatisticsEntry>>> {
+
+ @Override
+ public void event(EventuallyConsistentMapEvent<NetworkId, Map<DeviceId,
+ List<TableStatisticsEntry>>> event) {
+ //TODO: Generate an event to listeners (do we need?)
+ }
+ }
+
+ public final class MastershipBasedTimestamp implements Timestamp {
+
+ private final long termNumber;
+ private final long sequenceNumber;
+
+ /**
+ * Default constructor for serialization.
+ */
+ protected MastershipBasedTimestamp() {
+ this.termNumber = -1;
+ this.sequenceNumber = -1;
+ }
+
+ /**
+ * Default version tuple.
+ *
+ * @param termNumber the mastership termNumber
+ * @param sequenceNumber the sequenceNumber number within the termNumber
+ */
+ public MastershipBasedTimestamp(long termNumber, long sequenceNumber) {
+ this.termNumber = termNumber;
+ this.sequenceNumber = sequenceNumber;
+ }
+
+ @Override
+ public int compareTo(Timestamp o) {
+ checkArgument(o instanceof MastershipBasedTimestamp,
+ "Must be MastershipBasedTimestamp", o);
+ MastershipBasedTimestamp that = (MastershipBasedTimestamp) o;
+
+ return ComparisonChain.start()
+ .compare(this.termNumber, that.termNumber)
+ .compare(this.sequenceNumber, that.sequenceNumber)
+ .result();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(termNumber, sequenceNumber);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof MastershipBasedTimestamp)) {
+ return false;
+ }
+ MastershipBasedTimestamp that = (MastershipBasedTimestamp) obj;
+ return Objects.equals(this.termNumber, that.termNumber) &&
+ Objects.equals(this.sequenceNumber, that.sequenceNumber);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("termNumber", termNumber)
+ .add("sequenceNumber", sequenceNumber)
+ .toString();
+ }
+
+ /**
+ * Returns the termNumber.
+ *
+ * @return termNumber
+ */
+ public long termNumber() {
+ return termNumber;
+ }
+
+ /**
+ * Returns the sequenceNumber number.
+ *
+ * @return sequenceNumber
+ */
+ public long sequenceNumber() {
+ return sequenceNumber;
+ }
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/DistributedVirtualNetworkStore.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/DistributedVirtualNetworkStore.java
new file mode 100644
index 0000000..b756c62
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/DistributedVirtualNetworkStore.java
@@ -0,0 +1,954 @@
+/*
+ * Copyright 2015-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.incubator.net.virtual.store.impl;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.core.CoreService;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.incubator.net.tunnel.TunnelId;
+import org.onosproject.incubator.net.virtual.DefaultVirtualDevice;
+import org.onosproject.incubator.net.virtual.DefaultVirtualHost;
+import org.onosproject.incubator.net.virtual.DefaultVirtualLink;
+import org.onosproject.incubator.net.virtual.DefaultVirtualNetwork;
+import org.onosproject.incubator.net.virtual.DefaultVirtualPort;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualDevice;
+import org.onosproject.incubator.net.virtual.VirtualHost;
+import org.onosproject.incubator.net.virtual.VirtualLink;
+import org.onosproject.incubator.net.virtual.VirtualNetwork;
+import org.onosproject.incubator.net.virtual.VirtualNetworkEvent;
+import org.onosproject.incubator.net.virtual.VirtualNetworkIntent;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.VirtualNetworkStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkStoreDelegate;
+import org.onosproject.incubator.net.virtual.VirtualPort;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.Link;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.Key;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.DistributedSet;
+import org.onosproject.store.service.MapEvent;
+import org.onosproject.store.service.MapEventListener;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.SetEvent;
+import org.onosproject.store.service.SetEventListener;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.WallClockTimestamp;
+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.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BiFunction;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of the virtual network store.
+ */
+@Component(immediate = true, service = VirtualNetworkStore.class)
+public class DistributedVirtualNetworkStore
+ extends AbstractStore<VirtualNetworkEvent, VirtualNetworkStoreDelegate>
+ implements VirtualNetworkStore {
+
+ private final Logger log = getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected StorageService storageService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected CoreService coreService;
+
+ private IdGenerator idGenerator;
+
+ // Track tenants by ID
+ private DistributedSet<TenantId> tenantIdSet;
+
+ // Listener for tenant events
+ private final SetEventListener<TenantId> setListener = new InternalSetListener();
+
+ // Track virtual networks by network Id
+ private ConsistentMap<NetworkId, VirtualNetwork> networkIdVirtualNetworkConsistentMap;
+ private Map<NetworkId, VirtualNetwork> networkIdVirtualNetworkMap;
+
+ // Listener for virtual network events
+ private final MapEventListener<NetworkId, VirtualNetwork> virtualNetworkMapListener =
+ new InternalMapListener<>((mapEventType, virtualNetwork) -> {
+ VirtualNetworkEvent.Type eventType =
+ mapEventType.equals(MapEvent.Type.INSERT)
+ ? VirtualNetworkEvent.Type.NETWORK_ADDED :
+ mapEventType.equals(MapEvent.Type.UPDATE)
+ ? VirtualNetworkEvent.Type.NETWORK_UPDATED :
+ mapEventType.equals(MapEvent.Type.REMOVE)
+ ? VirtualNetworkEvent.Type.NETWORK_REMOVED : null;
+ return eventType == null ? null : new VirtualNetworkEvent(eventType, virtualNetwork.id());
+ });
+
+ // Listener for virtual device events
+ private final MapEventListener<VirtualDeviceId, VirtualDevice> virtualDeviceMapListener =
+ new InternalMapListener<>((mapEventType, virtualDevice) -> {
+ VirtualNetworkEvent.Type eventType =
+ mapEventType.equals(MapEvent.Type.INSERT)
+ ? VirtualNetworkEvent.Type.VIRTUAL_DEVICE_ADDED :
+ mapEventType.equals(MapEvent.Type.UPDATE)
+ ? VirtualNetworkEvent.Type.VIRTUAL_DEVICE_UPDATED :
+ mapEventType.equals(MapEvent.Type.REMOVE)
+ ? VirtualNetworkEvent.Type.VIRTUAL_DEVICE_REMOVED : null;
+ return eventType == null ? null :
+ new VirtualNetworkEvent(eventType, virtualDevice.networkId(), virtualDevice);
+ });
+
+ // Track virtual network IDs by tenant Id
+ private ConsistentMap<TenantId, Set<NetworkId>> tenantIdNetworkIdSetConsistentMap;
+ private Map<TenantId, Set<NetworkId>> tenantIdNetworkIdSetMap;
+
+ // Track virtual devices by device Id
+ private ConsistentMap<VirtualDeviceId, VirtualDevice> deviceIdVirtualDeviceConsistentMap;
+ private Map<VirtualDeviceId, VirtualDevice> deviceIdVirtualDeviceMap;
+
+ // Track device IDs by network Id
+ private ConsistentMap<NetworkId, Set<DeviceId>> networkIdDeviceIdSetConsistentMap;
+ private Map<NetworkId, Set<DeviceId>> networkIdDeviceIdSetMap;
+
+ // Track virtual hosts by host Id
+ private ConsistentMap<HostId, VirtualHost> hostIdVirtualHostConsistentMap;
+ private Map<HostId, VirtualHost> hostIdVirtualHostMap;
+
+ // Track host IDs by network Id
+ private ConsistentMap<NetworkId, Set<HostId>> networkIdHostIdSetConsistentMap;
+ private Map<NetworkId, Set<HostId>> networkIdHostIdSetMap;
+
+ // Track virtual links by network Id
+ private ConsistentMap<NetworkId, Set<VirtualLink>> networkIdVirtualLinkSetConsistentMap;
+ private Map<NetworkId, Set<VirtualLink>> networkIdVirtualLinkSetMap;
+
+ // Track virtual ports by network Id
+ private ConsistentMap<NetworkId, Set<VirtualPort>> networkIdVirtualPortSetConsistentMap;
+ private Map<NetworkId, Set<VirtualPort>> networkIdVirtualPortSetMap;
+
+ // Track intent ID to TunnelIds
+ private ConsistentMap<Key, Set<TunnelId>> intentKeyTunnelIdSetConsistentMap;
+ private Map<Key, Set<TunnelId>> intentKeyTunnelIdSetMap;
+
+ private static final Serializer SERIALIZER = Serializer
+ .using(new KryoNamespace.Builder().register(KryoNamespaces.API)
+ .register(TenantId.class)
+ .register(NetworkId.class)
+ .register(VirtualNetwork.class)
+ .register(DefaultVirtualNetwork.class)
+ .register(VirtualDevice.class)
+ .register(VirtualDeviceId.class)
+ .register(DefaultVirtualDevice.class)
+ .register(VirtualHost.class)
+ .register(DefaultVirtualHost.class)
+ .register(VirtualLink.class)
+ .register(DefaultVirtualLink.class)
+ .register(VirtualPort.class)
+ .register(DefaultVirtualPort.class)
+ .register(Device.class)
+ .register(TunnelId.class)
+ .register(VirtualNetworkIntent.class)
+ .register(WallClockTimestamp.class)
+ .nextId(KryoNamespaces.BEGIN_USER_CUSTOM_ID)
+ .build());
+
+ /**
+ * Distributed network store service activate method.
+ */
+ @Activate
+ public void activate() {
+ idGenerator = coreService.getIdGenerator(VirtualNetworkService.VIRTUAL_NETWORK_TOPIC);
+
+ tenantIdSet = storageService.<TenantId>setBuilder()
+ .withSerializer(SERIALIZER)
+ .withName("onos-tenantId")
+ .withRelaxedReadConsistency()
+ .build()
+ .asDistributedSet();
+ tenantIdSet.addListener(setListener);
+
+ networkIdVirtualNetworkConsistentMap = storageService.<NetworkId, VirtualNetwork>consistentMapBuilder()
+ .withSerializer(SERIALIZER)
+ .withName("onos-networkId-virtualnetwork")
+ .withRelaxedReadConsistency()
+ .build();
+ networkIdVirtualNetworkConsistentMap.addListener(virtualNetworkMapListener);
+ networkIdVirtualNetworkMap = networkIdVirtualNetworkConsistentMap.asJavaMap();
+
+ tenantIdNetworkIdSetConsistentMap = storageService.<TenantId, Set<NetworkId>>consistentMapBuilder()
+ .withSerializer(SERIALIZER)
+ .withName("onos-tenantId-networkIds")
+ .withRelaxedReadConsistency()
+ .build();
+ tenantIdNetworkIdSetMap = tenantIdNetworkIdSetConsistentMap.asJavaMap();
+
+ deviceIdVirtualDeviceConsistentMap = storageService.<VirtualDeviceId, VirtualDevice>consistentMapBuilder()
+ .withSerializer(SERIALIZER)
+ .withName("onos-deviceId-virtualdevice")
+ .withRelaxedReadConsistency()
+ .build();
+ deviceIdVirtualDeviceConsistentMap.addListener(virtualDeviceMapListener);
+ deviceIdVirtualDeviceMap = deviceIdVirtualDeviceConsistentMap.asJavaMap();
+
+ networkIdDeviceIdSetConsistentMap = storageService.<NetworkId, Set<DeviceId>>consistentMapBuilder()
+ .withSerializer(SERIALIZER)
+ .withName("onos-networkId-deviceIds")
+ .withRelaxedReadConsistency()
+ .build();
+ networkIdDeviceIdSetMap = networkIdDeviceIdSetConsistentMap.asJavaMap();
+
+ hostIdVirtualHostConsistentMap = storageService.<HostId, VirtualHost>consistentMapBuilder()
+ .withSerializer(SERIALIZER)
+ .withName("onos-hostId-virtualhost")
+ .withRelaxedReadConsistency()
+ .build();
+ hostIdVirtualHostMap = hostIdVirtualHostConsistentMap.asJavaMap();
+
+ networkIdHostIdSetConsistentMap = storageService.<NetworkId, Set<HostId>>consistentMapBuilder()
+ .withSerializer(SERIALIZER)
+ .withName("onos-networkId-hostIds")
+ .withRelaxedReadConsistency()
+ .build();
+ networkIdHostIdSetMap = networkIdHostIdSetConsistentMap.asJavaMap();
+
+ networkIdVirtualLinkSetConsistentMap = storageService.<NetworkId, Set<VirtualLink>>consistentMapBuilder()
+ .withSerializer(SERIALIZER)
+ .withName("onos-networkId-virtuallinks")
+ .withRelaxedReadConsistency()
+ .build();
+ networkIdVirtualLinkSetMap = networkIdVirtualLinkSetConsistentMap.asJavaMap();
+
+ networkIdVirtualPortSetConsistentMap = storageService.<NetworkId, Set<VirtualPort>>consistentMapBuilder()
+ .withSerializer(SERIALIZER)
+ .withName("onos-networkId-virtualports")
+ .withRelaxedReadConsistency()
+ .build();
+ networkIdVirtualPortSetMap = networkIdVirtualPortSetConsistentMap.asJavaMap();
+
+ intentKeyTunnelIdSetConsistentMap = storageService.<Key, Set<TunnelId>>consistentMapBuilder()
+ .withSerializer(SERIALIZER)
+ .withName("onos-intentKey-tunnelIds")
+ .withRelaxedReadConsistency()
+ .build();
+ intentKeyTunnelIdSetMap = intentKeyTunnelIdSetConsistentMap.asJavaMap();
+
+ log.info("Started");
+ }
+
+ /**
+ * Distributed network store service deactivate method.
+ */
+ @Deactivate
+ public void deactivate() {
+ tenantIdSet.removeListener(setListener);
+ networkIdVirtualNetworkConsistentMap.removeListener(virtualNetworkMapListener);
+ deviceIdVirtualDeviceConsistentMap.removeListener(virtualDeviceMapListener);
+ log.info("Stopped");
+ }
+
+ @Override
+ public void addTenantId(TenantId tenantId) {
+ tenantIdSet.add(tenantId);
+ }
+
+ @Override
+ public void removeTenantId(TenantId tenantId) {
+ //Remove all the virtual networks of this tenant
+ Set<VirtualNetwork> networkIdSet = getNetworks(tenantId);
+ if (networkIdSet != null) {
+ networkIdSet.forEach(virtualNetwork -> removeNetwork(virtualNetwork.id()));
+ }
+
+ tenantIdSet.remove(tenantId);
+ }
+
+ @Override
+ public Set<TenantId> getTenantIds() {
+ return ImmutableSet.copyOf(tenantIdSet);
+ }
+
+ @Override
+ public VirtualNetwork addNetwork(TenantId tenantId) {
+
+ checkState(tenantIdSet.contains(tenantId), "The tenant has not been registered. " + tenantId.id());
+ VirtualNetwork virtualNetwork = new DefaultVirtualNetwork(genNetworkId(), tenantId);
+ //TODO update both maps in one transaction.
+ networkIdVirtualNetworkMap.put(virtualNetwork.id(), virtualNetwork);
+
+ Set<NetworkId> networkIdSet = tenantIdNetworkIdSetMap.get(tenantId);
+ if (networkIdSet == null) {
+ networkIdSet = new HashSet<>();
+ }
+ networkIdSet.add(virtualNetwork.id());
+ tenantIdNetworkIdSetMap.put(tenantId, networkIdSet);
+
+ return virtualNetwork;
+ }
+
+ /**
+ * Returns a new network identifier from a virtual network block of identifiers.
+ *
+ * @return NetworkId network identifier
+ */
+ private NetworkId genNetworkId() {
+ NetworkId networkId;
+ do {
+ networkId = NetworkId.networkId(idGenerator.getNewId());
+ } while (!networkId.isVirtualNetworkId());
+
+ return networkId;
+ }
+
+ @Override
+ public void removeNetwork(NetworkId networkId) {
+ // Make sure that the virtual network exists before attempting to remove it.
+ checkState(networkExists(networkId), "The network does not exist.");
+
+ //Remove all the devices of this network
+ Set<VirtualDevice> deviceSet = getDevices(networkId);
+ if (deviceSet != null) {
+ deviceSet.forEach(virtualDevice -> removeDevice(networkId, virtualDevice.id()));
+ }
+ //TODO update both maps in one transaction.
+
+ VirtualNetwork virtualNetwork = networkIdVirtualNetworkMap.remove(networkId);
+ if (virtualNetwork == null) {
+ return;
+ }
+ TenantId tenantId = virtualNetwork.tenantId();
+
+ Set<NetworkId> networkIdSet = new HashSet<>();
+ tenantIdNetworkIdSetMap.get(tenantId).forEach(networkId1 -> {
+ if (networkId1.id().equals(networkId.id())) {
+ networkIdSet.add(networkId1);
+ }
+ });
+
+ tenantIdNetworkIdSetMap.compute(virtualNetwork.tenantId(), (id, existingNetworkIds) -> {
+ if (existingNetworkIds == null || existingNetworkIds.isEmpty()) {
+ return new HashSet<>();
+ } else {
+ return new HashSet<>(Sets.difference(existingNetworkIds, networkIdSet));
+ }
+ });
+ }
+
+ /**
+ * Returns if the network identifier exists.
+ *
+ * @param networkId network identifier
+ * @return true if the network identifier exists, false otherwise.
+ */
+ private boolean networkExists(NetworkId networkId) {
+ checkNotNull(networkId, "The network identifier cannot be null.");
+ return (networkIdVirtualNetworkMap.containsKey(networkId));
+ }
+
+ @Override
+ public VirtualDevice addDevice(NetworkId networkId, DeviceId deviceId) {
+ checkState(networkExists(networkId), "The network has not been added.");
+
+ Set<DeviceId> deviceIdSet = networkIdDeviceIdSetMap.get(networkId);
+ if (deviceIdSet == null) {
+ deviceIdSet = new HashSet<>();
+ }
+
+ checkState(!deviceIdSet.contains(deviceId), "The device already exists.");
+
+ VirtualDevice virtualDevice = new DefaultVirtualDevice(networkId, deviceId);
+ //TODO update both maps in one transaction.
+ deviceIdVirtualDeviceMap.put(new VirtualDeviceId(networkId, deviceId), virtualDevice);
+ deviceIdSet.add(deviceId);
+ networkIdDeviceIdSetMap.put(networkId, deviceIdSet);
+ return virtualDevice;
+ }
+
+ @Override
+ public void removeDevice(NetworkId networkId, DeviceId deviceId) {
+ checkState(networkExists(networkId), "The network has not been added.");
+ //Remove all the virtual ports of the this device
+ Set<VirtualPort> virtualPorts = getPorts(networkId, deviceId);
+ if (virtualPorts != null) {
+ virtualPorts.forEach(virtualPort -> removePort(networkId, deviceId, virtualPort.number()));
+ }
+ //TODO update both maps in one transaction.
+
+ Set<DeviceId> deviceIdSet = new HashSet<>();
+ networkIdDeviceIdSetMap.get(networkId).forEach(deviceId1 -> {
+ if (deviceId1.equals(deviceId)) {
+ deviceIdSet.add(deviceId1);
+ }
+ });
+
+ if (!deviceIdSet.isEmpty()) {
+ networkIdDeviceIdSetMap.compute(networkId, (id, existingDeviceIds) -> {
+ if (existingDeviceIds == null || existingDeviceIds.isEmpty()) {
+ return new HashSet<>();
+ } else {
+ return new HashSet<>(Sets.difference(existingDeviceIds, deviceIdSet));
+ }
+ });
+
+ deviceIdVirtualDeviceMap.remove(new VirtualDeviceId(networkId, deviceId));
+ }
+ }
+
+ @Override
+ public VirtualHost addHost(NetworkId networkId, HostId hostId, MacAddress mac,
+ VlanId vlan, HostLocation location, Set<IpAddress> ips) {
+ checkState(networkExists(networkId), "The network has not been added.");
+ checkState(virtualPortExists(networkId, location.deviceId(), location.port()),
+ "The virtual port has not been created.");
+ Set<HostId> hostIdSet = networkIdHostIdSetMap.get(networkId);
+ if (hostIdSet == null) {
+ hostIdSet = new HashSet<>();
+ }
+ VirtualHost virtualhost = new DefaultVirtualHost(networkId, hostId, mac, vlan, location, ips);
+ //TODO update both maps in one transaction.
+ hostIdVirtualHostMap.put(hostId, virtualhost);
+ hostIdSet.add(hostId);
+ networkIdHostIdSetMap.put(networkId, hostIdSet);
+ return virtualhost;
+ }
+
+ @Override
+ public void removeHost(NetworkId networkId, HostId hostId) {
+ checkState(networkExists(networkId), "The network has not been added.");
+ //TODO update both maps in one transaction.
+
+ Set<HostId> hostIdSet = new HashSet<>();
+ networkIdHostIdSetMap.get(networkId).forEach(hostId1 -> {
+ if (hostId1.equals(hostId)) {
+ hostIdSet.add(hostId1);
+ }
+ });
+
+ networkIdHostIdSetMap.compute(networkId, (id, existingHostIds) -> {
+ if (existingHostIds == null || existingHostIds.isEmpty()) {
+ return new HashSet<>();
+ } else {
+ return new HashSet<>(Sets.difference(existingHostIds, hostIdSet));
+ }
+ });
+
+ hostIdVirtualHostMap.remove(hostId);
+ }
+
+ /**
+ * Returns if the given virtual port exists.
+ *
+ * @param networkId network identifier
+ * @param deviceId virtual device Id
+ * @param portNumber virtual port number
+ * @return true if the virtual port exists, false otherwise.
+ */
+ private boolean virtualPortExists(NetworkId networkId, DeviceId deviceId, PortNumber portNumber) {
+ Set<VirtualPort> virtualPortSet = networkIdVirtualPortSetMap.get(networkId);
+ if (virtualPortSet != null) {
+ return virtualPortSet.stream().anyMatch(
+ p -> p.element().id().equals(deviceId) &&
+ p.number().equals(portNumber));
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public VirtualLink addLink(NetworkId networkId, ConnectPoint src, ConnectPoint dst,
+ Link.State state, TunnelId realizedBy) {
+ checkState(networkExists(networkId), "The network has not been added.");
+ checkState(virtualPortExists(networkId, src.deviceId(), src.port()),
+ "The source virtual port has not been added.");
+ checkState(virtualPortExists(networkId, dst.deviceId(), dst.port()),
+ "The destination virtual port has not been added.");
+ Set<VirtualLink> virtualLinkSet = networkIdVirtualLinkSetMap.get(networkId);
+ if (virtualLinkSet == null) {
+ virtualLinkSet = new HashSet<>();
+ }
+
+ // validate that the link does not already exist in this network
+ checkState(getLink(networkId, src, dst) == null,
+ "The virtual link already exists");
+ checkState(getLink(networkId, src, null) == null,
+ "The source connection point has been used by another link");
+ checkState(getLink(networkId, null, dst) == null,
+ "The destination connection point has been used by another link");
+
+ VirtualLink virtualLink = DefaultVirtualLink.builder()
+ .networkId(networkId)
+ .src(src)
+ .dst(dst)
+ .state(state)
+ .tunnelId(realizedBy)
+ .build();
+
+ virtualLinkSet.add(virtualLink);
+ networkIdVirtualLinkSetMap.put(networkId, virtualLinkSet);
+ return virtualLink;
+ }
+
+ @Override
+ public void updateLink(VirtualLink virtualLink, TunnelId tunnelId, Link.State state) {
+ checkState(networkExists(virtualLink.networkId()), "The network has not been added.");
+ Set<VirtualLink> virtualLinkSet = networkIdVirtualLinkSetMap.get(virtualLink.networkId());
+ if (virtualLinkSet == null) {
+ virtualLinkSet = new HashSet<>();
+ networkIdVirtualLinkSetMap.put(virtualLink.networkId(), virtualLinkSet);
+ log.warn("The updated virtual link {} has not been added", virtualLink);
+ return;
+ }
+ if (!virtualLinkSet.remove(virtualLink)) {
+ log.warn("The updated virtual link {} does not exist", virtualLink);
+ return;
+ }
+
+ VirtualLink newVirtualLink = DefaultVirtualLink.builder()
+ .networkId(virtualLink.networkId())
+ .src(virtualLink.src())
+ .dst(virtualLink.dst())
+ .tunnelId(tunnelId)
+ .state(state)
+ .build();
+
+ virtualLinkSet.add(newVirtualLink);
+ networkIdVirtualLinkSetMap.put(newVirtualLink.networkId(), virtualLinkSet);
+ }
+
+ @Override
+ public VirtualLink removeLink(NetworkId networkId, ConnectPoint src, ConnectPoint dst) {
+ checkState(networkExists(networkId), "The network has not been added.");
+
+ final VirtualLink virtualLink = getLink(networkId, src, dst);
+ if (virtualLink == null) {
+ log.warn("The removed virtual link between {} and {} does not exist", src, dst);
+ return null;
+ }
+ Set<VirtualLink> virtualLinkSet = new HashSet<>();
+ virtualLinkSet.add(virtualLink);
+
+ networkIdVirtualLinkSetMap.compute(networkId, (id, existingVirtualLinks) -> {
+ if (existingVirtualLinks == null || existingVirtualLinks.isEmpty()) {
+ return new HashSet<>();
+ } else {
+ return new HashSet<>(Sets.difference(existingVirtualLinks, virtualLinkSet));
+ }
+ });
+ return virtualLink;
+ }
+
+ @Override
+ public VirtualPort addPort(NetworkId networkId, DeviceId deviceId,
+ PortNumber portNumber, ConnectPoint realizedBy) {
+ checkState(networkExists(networkId), "The network has not been added.");
+ Set<VirtualPort> virtualPortSet = networkIdVirtualPortSetMap.get(networkId);
+
+ if (virtualPortSet == null) {
+ virtualPortSet = new HashSet<>();
+ }
+
+ VirtualDevice device = deviceIdVirtualDeviceMap.get(new VirtualDeviceId(networkId, deviceId));
+ checkNotNull(device, "The device has not been created for deviceId: " + deviceId);
+
+ checkState(!virtualPortExists(networkId, deviceId, portNumber),
+ "The requested Port Number has been added.");
+
+ VirtualPort virtualPort = new DefaultVirtualPort(networkId, device,
+ portNumber, realizedBy);
+ virtualPortSet.add(virtualPort);
+ networkIdVirtualPortSetMap.put(networkId, virtualPortSet);
+ notifyDelegate(new VirtualNetworkEvent(VirtualNetworkEvent.Type.VIRTUAL_PORT_ADDED,
+ networkId, device, virtualPort));
+ return virtualPort;
+ }
+
+ @Override
+ public void bindPort(NetworkId networkId, DeviceId deviceId,
+ PortNumber portNumber, ConnectPoint realizedBy) {
+
+ Set<VirtualPort> virtualPortSet = networkIdVirtualPortSetMap
+ .get(networkId);
+
+ Optional<VirtualPort> virtualPortOptional = virtualPortSet.stream().filter(
+ p -> p.element().id().equals(deviceId) &&
+ p.number().equals(portNumber)).findFirst();
+ checkState(virtualPortOptional.isPresent(), "The virtual port has not been added.");
+
+ VirtualDevice device = deviceIdVirtualDeviceMap.get(new VirtualDeviceId(networkId, deviceId));
+ checkNotNull(device, "The device has not been created for deviceId: "
+ + deviceId);
+
+ VirtualPort vPort = virtualPortOptional.get();
+ virtualPortSet.remove(vPort);
+ vPort = new DefaultVirtualPort(networkId, device, portNumber, realizedBy);
+ virtualPortSet.add(vPort);
+ networkIdVirtualPortSetMap.put(networkId, virtualPortSet);
+ notifyDelegate(new VirtualNetworkEvent(VirtualNetworkEvent.Type.VIRTUAL_PORT_UPDATED,
+ networkId, device, vPort));
+ }
+
+ @Override
+ public void updatePortState(NetworkId networkId, DeviceId deviceId,
+ PortNumber portNumber, boolean isEnabled) {
+ checkState(networkExists(networkId), "No network with NetworkId %s exists.", networkId);
+
+ VirtualDevice device = deviceIdVirtualDeviceMap.get(new VirtualDeviceId(networkId, deviceId));
+ checkNotNull(device, "No device %s exists in NetworkId: %s", deviceId, networkId);
+
+ Set<VirtualPort> virtualPortSet = networkIdVirtualPortSetMap.get(networkId);
+ checkNotNull(virtualPortSet, "No port has been created for NetworkId: %s", networkId);
+
+ Optional<VirtualPort> virtualPortOptional = virtualPortSet.stream().filter(
+ p -> p.element().id().equals(deviceId) &&
+ p.number().equals(portNumber)).findFirst();
+ checkState(virtualPortOptional.isPresent(), "The virtual port has not been added.");
+
+ VirtualPort oldPort = virtualPortOptional.get();
+ if (oldPort.isEnabled() == isEnabled) {
+ log.debug("No change in port state - port not updated");
+ return;
+ }
+ VirtualPort newPort = new DefaultVirtualPort(networkId, device, portNumber, isEnabled,
+ oldPort.realizedBy());
+ virtualPortSet.remove(oldPort);
+ virtualPortSet.add(newPort);
+ networkIdVirtualPortSetMap.put(networkId, virtualPortSet);
+ notifyDelegate(new VirtualNetworkEvent(VirtualNetworkEvent.Type.VIRTUAL_PORT_UPDATED,
+ networkId, device, newPort));
+ log.debug("port state changed from {} to {}", oldPort.isEnabled(), isEnabled);
+ }
+
+ @Override
+ public void removePort(NetworkId networkId, DeviceId deviceId, PortNumber portNumber) {
+ checkState(networkExists(networkId), "The network has not been added.");
+ VirtualDevice device = deviceIdVirtualDeviceMap.get(new VirtualDeviceId(networkId, deviceId));
+ checkNotNull(device, "The device has not been created for deviceId: "
+ + deviceId);
+
+ if (networkIdVirtualPortSetMap.get(networkId) == null) {
+ log.warn("No port has been created for NetworkId: {}", networkId);
+ return;
+ }
+
+ Set<VirtualPort> virtualPortSet = new HashSet<>();
+ networkIdVirtualPortSetMap.get(networkId).forEach(port -> {
+ if (port.element().id().equals(deviceId) && port.number().equals(portNumber)) {
+ virtualPortSet.add(port);
+ }
+ });
+
+ if (!virtualPortSet.isEmpty()) {
+ AtomicBoolean portRemoved = new AtomicBoolean(false);
+ networkIdVirtualPortSetMap.compute(networkId, (id, existingVirtualPorts) -> {
+ if (existingVirtualPorts == null || existingVirtualPorts.isEmpty()) {
+ return new HashSet<>();
+ } else {
+ portRemoved.set(true);
+ return new HashSet<>(Sets.difference(existingVirtualPorts, virtualPortSet));
+ }
+ });
+ if (portRemoved.get()) {
+ virtualPortSet.forEach(virtualPort -> notifyDelegate(
+ new VirtualNetworkEvent(VirtualNetworkEvent.Type.VIRTUAL_PORT_REMOVED,
+ networkId, device, virtualPort)
+ ));
+
+ //Remove all the virtual links connected to this virtual port
+ Set<VirtualLink> existingVirtualLinks = networkIdVirtualLinkSetMap.get(networkId);
+ if (existingVirtualLinks != null && !existingVirtualLinks.isEmpty()) {
+ Set<VirtualLink> virtualLinkSet = new HashSet<>();
+ ConnectPoint cp = new ConnectPoint(deviceId, portNumber);
+ existingVirtualLinks.forEach(virtualLink -> {
+ if (virtualLink.src().equals(cp) || virtualLink.dst().equals(cp)) {
+ virtualLinkSet.add(virtualLink);
+ }
+ });
+ virtualLinkSet.forEach(virtualLink ->
+ removeLink(networkId, virtualLink.src(), virtualLink.dst()));
+ }
+
+ //Remove all the hosts connected to this virtual port
+ Set<HostId> hostIdSet = new HashSet<>();
+ hostIdVirtualHostMap.forEach((hostId, virtualHost) -> {
+ if (virtualHost.location().deviceId().equals(deviceId) &&
+ virtualHost.location().port().equals(portNumber)) {
+ hostIdSet.add(hostId);
+ }
+ });
+ hostIdSet.forEach(hostId -> removeHost(networkId, hostId));
+ }
+ }
+ }
+
+ @Override
+ public Set<VirtualNetwork> getNetworks(TenantId tenantId) {
+ Set<NetworkId> networkIdSet = tenantIdNetworkIdSetMap.get(tenantId);
+ Set<VirtualNetwork> virtualNetworkSet = new HashSet<>();
+ if (networkIdSet != null) {
+ networkIdSet.forEach(networkId -> {
+ if (networkIdVirtualNetworkMap.get(networkId) != null) {
+ virtualNetworkSet.add(networkIdVirtualNetworkMap.get(networkId));
+ }
+ });
+ }
+ return ImmutableSet.copyOf(virtualNetworkSet);
+ }
+
+ @Override
+ public VirtualNetwork getNetwork(NetworkId networkId) {
+ return networkIdVirtualNetworkMap.get(networkId);
+ }
+
+ @Override
+ public Set<VirtualDevice> getDevices(NetworkId networkId) {
+ checkState(networkExists(networkId), "The network has not been added.");
+ Set<DeviceId> deviceIdSet = networkIdDeviceIdSetMap.get(networkId);
+ Set<VirtualDevice> virtualDeviceSet = new HashSet<>();
+ if (deviceIdSet != null) {
+ deviceIdSet.forEach(deviceId -> virtualDeviceSet.add(
+ deviceIdVirtualDeviceMap.get(new VirtualDeviceId(networkId, deviceId))));
+ }
+ return ImmutableSet.copyOf(virtualDeviceSet);
+ }
+
+ @Override
+ public Set<VirtualHost> getHosts(NetworkId networkId) {
+ checkState(networkExists(networkId), "The network has not been added.");
+ Set<HostId> hostIdSet = networkIdHostIdSetMap.get(networkId);
+ Set<VirtualHost> virtualHostSet = new HashSet<>();
+ if (hostIdSet != null) {
+ hostIdSet.forEach(hostId -> virtualHostSet.add(hostIdVirtualHostMap.get(hostId)));
+ }
+ return ImmutableSet.copyOf(virtualHostSet);
+ }
+
+ @Override
+ public Set<VirtualLink> getLinks(NetworkId networkId) {
+ checkState(networkExists(networkId), "The network has not been added.");
+ Set<VirtualLink> virtualLinkSet = networkIdVirtualLinkSetMap.get(networkId);
+ if (virtualLinkSet == null) {
+ virtualLinkSet = new HashSet<>();
+ }
+ return ImmutableSet.copyOf(virtualLinkSet);
+ }
+
+ @Override
+ public VirtualLink getLink(NetworkId networkId, ConnectPoint src, ConnectPoint dst) {
+ Set<VirtualLink> virtualLinkSet = networkIdVirtualLinkSetMap.get(networkId);
+ if (virtualLinkSet == null) {
+ return null;
+ }
+
+ VirtualLink virtualLink = null;
+ for (VirtualLink link : virtualLinkSet) {
+ if (src == null && link.dst().equals(dst)) {
+ virtualLink = link;
+ break;
+ } else if (dst == null && link.src().equals(src)) {
+ virtualLink = link;
+ break;
+ } else if (link.src().equals(src) && link.dst().equals(dst)) {
+ virtualLink = link;
+ break;
+ }
+ }
+ return virtualLink;
+ }
+
+ @Override
+ public Set<VirtualPort> getPorts(NetworkId networkId, DeviceId deviceId) {
+ checkState(networkExists(networkId), "The network has not been added.");
+ Set<VirtualPort> virtualPortSet = networkIdVirtualPortSetMap.get(networkId);
+ if (virtualPortSet == null) {
+ virtualPortSet = new HashSet<>();
+ }
+
+ if (deviceId == null) {
+ return ImmutableSet.copyOf(virtualPortSet);
+ }
+
+ Set<VirtualPort> portSet = new HashSet<>();
+ virtualPortSet.forEach(virtualPort -> {
+ if (virtualPort.element().id().equals(deviceId)) {
+ portSet.add(virtualPort);
+ }
+ });
+ return ImmutableSet.copyOf(portSet);
+ }
+
+ @Override
+ public void addTunnelId(Intent intent, TunnelId tunnelId) {
+ // Add the tunnelId to the intent key set map
+ Set<TunnelId> tunnelIdSet = intentKeyTunnelIdSetMap.remove(intent.key());
+ if (tunnelIdSet == null) {
+ tunnelIdSet = new HashSet<>();
+ }
+ tunnelIdSet.add(tunnelId);
+ intentKeyTunnelIdSetMap.put(intent.key(), tunnelIdSet);
+ }
+
+ @Override
+ public Set<TunnelId> getTunnelIds(Intent intent) {
+ Set<TunnelId> tunnelIdSet = intentKeyTunnelIdSetMap.get(intent.key());
+ return tunnelIdSet == null ? new HashSet<TunnelId>() : ImmutableSet.copyOf(tunnelIdSet);
+ }
+
+ @Override
+ public void removeTunnelId(Intent intent, TunnelId tunnelId) {
+ Set<TunnelId> tunnelIdSet = new HashSet<>();
+ intentKeyTunnelIdSetMap.get(intent.key()).forEach(tunnelId1 -> {
+ if (tunnelId1.equals(tunnelId)) {
+ tunnelIdSet.add(tunnelId);
+ }
+ });
+
+ if (!tunnelIdSet.isEmpty()) {
+ intentKeyTunnelIdSetMap.compute(intent.key(), (key, existingTunnelIds) -> {
+ if (existingTunnelIds == null || existingTunnelIds.isEmpty()) {
+ return new HashSet<>();
+ } else {
+ return new HashSet<>(Sets.difference(existingTunnelIds, tunnelIdSet));
+ }
+ });
+ }
+ }
+
+ /**
+ * Listener class to map listener set events to the virtual network events.
+ */
+ private class InternalSetListener implements SetEventListener<TenantId> {
+ @Override
+ public void event(SetEvent<TenantId> event) {
+ VirtualNetworkEvent.Type type = null;
+ switch (event.type()) {
+ case ADD:
+ type = VirtualNetworkEvent.Type.TENANT_REGISTERED;
+ break;
+ case REMOVE:
+ type = VirtualNetworkEvent.Type.TENANT_UNREGISTERED;
+ break;
+ default:
+ log.error("Unsupported event type: " + event.type());
+ }
+ notifyDelegate(new VirtualNetworkEvent(type, null));
+ }
+ }
+
+ /**
+ * Listener class to map listener map events to the virtual network events.
+ */
+ private class InternalMapListener<K, V> implements MapEventListener<K, V> {
+
+ private final BiFunction<MapEvent.Type, V, VirtualNetworkEvent> createEvent;
+
+ InternalMapListener(BiFunction<MapEvent.Type, V, VirtualNetworkEvent> createEvent) {
+ this.createEvent = createEvent;
+ }
+
+ @Override
+ public void event(MapEvent<K, V> event) {
+ checkNotNull(event.key());
+ VirtualNetworkEvent vnetEvent = null;
+ switch (event.type()) {
+ case INSERT:
+ vnetEvent = createEvent.apply(event.type(), event.newValue().value());
+ break;
+ case UPDATE:
+ if ((event.oldValue().value() != null) && (event.newValue().value() == null)) {
+ vnetEvent = createEvent.apply(MapEvent.Type.REMOVE, event.oldValue().value());
+ } else {
+ vnetEvent = createEvent.apply(event.type(), event.newValue().value());
+ }
+ break;
+ case REMOVE:
+ if (event.oldValue() != null) {
+ vnetEvent = createEvent.apply(event.type(), event.oldValue().value());
+ }
+ break;
+ default:
+ log.error("Unsupported event type: " + event.type());
+ }
+ if (vnetEvent != null) {
+ notifyDelegate(vnetEvent);
+ }
+ }
+ }
+
+ /**
+ * A wrapper class to isolate device id from other virtual networks.
+ */
+
+ private static class VirtualDeviceId {
+
+ NetworkId networkId;
+ DeviceId deviceId;
+
+ public VirtualDeviceId(NetworkId networkId, DeviceId deviceId) {
+ this.networkId = networkId;
+ this.deviceId = deviceId;
+ }
+
+ public NetworkId getNetworkId() {
+ return networkId;
+ }
+
+ public DeviceId getDeviceId() {
+ return deviceId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(networkId, deviceId);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj instanceof VirtualDeviceId) {
+ VirtualDeviceId that = (VirtualDeviceId) obj;
+ return this.deviceId.equals(that.deviceId) &&
+ this.networkId.equals(that.networkId);
+ }
+ return false;
+ }
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/DistributedVirtualPacketStore.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/DistributedVirtualPacketStore.java
new file mode 100644
index 0000000..16636a5
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/DistributedVirtualPacketStore.java
@@ -0,0 +1,413 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+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.osgi.service.component.annotations.ReferenceCardinality;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkPacketStore;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketEvent;
+import org.onosproject.net.packet.PacketPriority;
+import org.onosproject.net.packet.PacketRequest;
+import org.onosproject.net.packet.PacketStoreDelegate;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
+import org.onosproject.store.cluster.messaging.MessageSubject;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+
+import java.util.Dictionary;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static java.util.concurrent.Executors.newFixedThreadPool;
+import static org.onlab.util.Tools.get;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.incubator.net.virtual.store.impl.OsgiPropertyConstants.MESSAGE_HANDLER_THREAD_POOL_SIZE;
+import static org.onosproject.incubator.net.virtual.store.impl.OsgiPropertyConstants.MESSAGE_HANDLER_THREAD_POOL_SIZE_DEFAULT;
+import static org.onosproject.net.packet.PacketEvent.Type.EMIT;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Distributed virtual packet store implementation allowing packets to be sent to
+ * remote instances. Implementation is based on DistributedPacketStore class.
+ */
+@Component(immediate = true, enabled = false, service = VirtualNetworkPacketStore.class,
+ property = {
+ MESSAGE_HANDLER_THREAD_POOL_SIZE + ":Integer=" + MESSAGE_HANDLER_THREAD_POOL_SIZE_DEFAULT,
+ })
+public class DistributedVirtualPacketStore
+ extends AbstractVirtualStore<PacketEvent, PacketStoreDelegate>
+ implements VirtualNetworkPacketStore {
+
+ private final Logger log = getLogger(getClass());
+
+ private static final String FORMAT = "Setting: messageHandlerThreadPoolSize={}";
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected MastershipService mastershipService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected ClusterService clusterService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected ClusterCommunicationService communicationService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected StorageService storageService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected ComponentConfigService cfgService;
+
+ private PacketRequestTracker tracker;
+
+ private static final MessageSubject PACKET_OUT_SUBJECT =
+ new MessageSubject("virtual-packet-out");
+
+ private static final Serializer SERIALIZER = Serializer.using(KryoNamespaces.API);
+
+ private ExecutorService messageHandlingExecutor;
+
+ /** Size of thread pool to assign message handler. */
+ private static int messageHandlerThreadPoolSize = MESSAGE_HANDLER_THREAD_POOL_SIZE_DEFAULT;
+
+ @Activate
+ public void activate(ComponentContext context) {
+ cfgService.registerProperties(getClass());
+
+ modified(context);
+
+ messageHandlingExecutor = Executors.newFixedThreadPool(
+ messageHandlerThreadPoolSize,
+ groupedThreads("onos/store/packet", "message-handlers", log));
+
+ communicationService.<OutboundPacketWrapper>addSubscriber(PACKET_OUT_SUBJECT,
+ SERIALIZER::decode,
+ packetWrapper -> notifyDelegate(packetWrapper.networkId,
+ new PacketEvent(EMIT,
+ packetWrapper.outboundPacket)),
+ messageHandlingExecutor);
+
+ tracker = new PacketRequestTracker();
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ cfgService.unregisterProperties(getClass(), false);
+ communicationService.removeSubscriber(PACKET_OUT_SUBJECT);
+ messageHandlingExecutor.shutdown();
+ tracker = null;
+ log.info("Stopped");
+ }
+
+ @Modified
+ public void modified(ComponentContext context) {
+ Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
+
+ int newMessageHandlerThreadPoolSize;
+
+ try {
+ String s = get(properties, MESSAGE_HANDLER_THREAD_POOL_SIZE);
+
+ newMessageHandlerThreadPoolSize =
+ isNullOrEmpty(s) ? messageHandlerThreadPoolSize : Integer.parseInt(s.trim());
+
+ } catch (NumberFormatException e) {
+ log.warn(e.getMessage());
+ newMessageHandlerThreadPoolSize = messageHandlerThreadPoolSize;
+ }
+
+ // Any change in the following parameters implies thread pool restart
+ if (newMessageHandlerThreadPoolSize != messageHandlerThreadPoolSize) {
+ setMessageHandlerThreadPoolSize(newMessageHandlerThreadPoolSize);
+ restartMessageHandlerThreadPool();
+ }
+
+ log.info(FORMAT, messageHandlerThreadPoolSize);
+ }
+
+ @Override
+ public void emit(NetworkId networkId, OutboundPacket packet) {
+ NodeId myId = clusterService.getLocalNode().id();
+ // TODO revive this when there is MastershipService support for virtual devices
+// NodeId master = mastershipService.getMasterFor(packet.sendThrough());
+//
+// if (master == null) {
+// log.warn("No master found for {}", packet.sendThrough());
+// return;
+// }
+//
+// log.debug("master {} found for {}", myId, packet.sendThrough());
+// if (myId.equals(master)) {
+// notifyDelegate(networkId, new PacketEvent(EMIT, packet));
+// return;
+// }
+//
+// communicationService.unicast(packet, PACKET_OUT_SUBJECT, SERIALIZER::encode, master)
+// .whenComplete((r, error) -> {
+// if (error != null) {
+// log.warn("Failed to send packet-out to {}", master, error);
+// }
+// });
+ }
+
+ @Override
+ public void requestPackets(NetworkId networkId, PacketRequest request) {
+ tracker.add(networkId, request);
+
+ }
+
+ @Override
+ public void cancelPackets(NetworkId networkId, PacketRequest request) {
+ tracker.remove(networkId, request);
+ }
+
+ @Override
+ public List<PacketRequest> existingRequests(NetworkId networkId) {
+ return tracker.requests(networkId);
+ }
+
+ private final class PacketRequestTracker {
+
+ private ConsistentMap<NetworkId, Map<RequestKey, Set<PacketRequest>>> distRequests;
+ private Map<NetworkId, Map<RequestKey, Set<PacketRequest>>> requests;
+
+ private PacketRequestTracker() {
+ distRequests = storageService.<NetworkId, Map<RequestKey, Set<PacketRequest>>>consistentMapBuilder()
+ .withName("onos-virtual-packet-requests")
+ .withSerializer(Serializer.using(KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(RequestKey.class)
+ .register(NetworkId.class)
+ .build()))
+ .build();
+ requests = distRequests.asJavaMap();
+ }
+
+ private void add(NetworkId networkId, PacketRequest request) {
+ AtomicBoolean firstRequest = addInternal(networkId, request);
+ PacketStoreDelegate delegate = delegateMap.get(networkId);
+ if (firstRequest.get() && delegate != null) {
+ // The instance that makes the first request will push to all devices
+ delegate.requestPackets(request);
+ }
+ }
+
+ private AtomicBoolean addInternal(NetworkId networkId, PacketRequest request) {
+ AtomicBoolean firstRequest = new AtomicBoolean(false);
+ AtomicBoolean changed = new AtomicBoolean(true);
+ Map<RequestKey, Set<PacketRequest>> requestsForNetwork = getMap(networkId);
+ requestsForNetwork.compute(key(request), (s, existingRequests) -> {
+ // Reset to false just in case this is a retry due to
+ // ConcurrentModificationException
+ firstRequest.set(false);
+ if (existingRequests == null) {
+ firstRequest.set(true);
+ return ImmutableSet.of(request);
+ } else if (!existingRequests.contains(request)) {
+ firstRequest.set(true);
+ return ImmutableSet.<PacketRequest>builder()
+ .addAll(existingRequests)
+ .add(request)
+ .build();
+ } else {
+ changed.set(false);
+ return existingRequests;
+ }
+ });
+ if (changed.get()) {
+ requests.put(networkId, requestsForNetwork);
+ }
+ return firstRequest;
+ }
+
+ private void remove(NetworkId networkId, PacketRequest request) {
+ AtomicBoolean removedLast = removeInternal(networkId, request);
+ PacketStoreDelegate delegate = delegateMap.get(networkId);
+ if (removedLast.get() && delegate != null) {
+ // The instance that removes the last request will remove from all devices
+ delegate.cancelPackets(request);
+ }
+ }
+
+ private AtomicBoolean removeInternal(NetworkId networkId, PacketRequest request) {
+ AtomicBoolean removedLast = new AtomicBoolean(false);
+ AtomicBoolean changed = new AtomicBoolean(true);
+ Map<RequestKey, Set<PacketRequest>> requestsForNetwork = getMap(networkId);
+ requestsForNetwork.computeIfPresent(key(request), (s, existingRequests) -> {
+ // Reset to false just in case this is a retry due to
+ // ConcurrentModificationException
+ removedLast.set(false);
+ if (existingRequests.contains(request)) {
+ Set<PacketRequest> newRequests = Sets.newHashSet(existingRequests);
+ newRequests.remove(request);
+ if (newRequests.size() > 0) {
+ return ImmutableSet.copyOf(newRequests);
+ } else {
+ removedLast.set(true);
+ return null;
+ }
+ } else {
+ changed.set(false);
+ return existingRequests;
+ }
+ });
+ if (changed.get()) {
+ requests.put(networkId, requestsForNetwork);
+ }
+ return removedLast;
+ }
+
+ private List<PacketRequest> requests(NetworkId networkId) {
+ Map<RequestKey, Set<PacketRequest>> requestsForNetwork = getMap(networkId);
+ List<PacketRequest> list = Lists.newArrayList();
+ requestsForNetwork.values().forEach(v -> list.addAll(v));
+ list.sort((o1, o2) -> o1.priority().priorityValue() - o2.priority().priorityValue());
+ return list;
+ }
+
+ /*
+ * Gets PacketRequests for specified networkId.
+ */
+ private Map<RequestKey, Set<PacketRequest>> getMap(NetworkId networkId) {
+ return requests.computeIfAbsent(networkId, networkId1 -> {
+ log.debug("Creating new map for {}", networkId1);
+ Map newMap = Maps.newHashMap();
+ return newMap;
+ });
+ }
+ }
+
+ /**
+ * Creates a new request key from a packet request.
+ *
+ * @param request packet request
+ * @return request key
+ */
+ private static RequestKey key(PacketRequest request) {
+ return new RequestKey(request.selector(), request.priority());
+ }
+
+ /**
+ * Key of a packet request.
+ */
+ private static final class RequestKey {
+ private final TrafficSelector selector;
+ private final PacketPriority priority;
+
+ private RequestKey(TrafficSelector selector, PacketPriority priority) {
+ this.selector = selector;
+ this.priority = priority;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(selector, priority);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof RequestKey)) {
+ return false;
+ }
+
+ RequestKey that = (RequestKey) other;
+
+ return Objects.equals(selector, that.selector) &&
+ Objects.equals(priority, that.priority);
+ }
+ }
+
+ private static OutboundPacketWrapper wrapper(NetworkId networkId, OutboundPacket outboundPacket) {
+ return new OutboundPacketWrapper(networkId, outboundPacket);
+ }
+
+ /*
+ * OutboundPacket in
+ */
+ private static final class OutboundPacketWrapper {
+ private NetworkId networkId;
+ private OutboundPacket outboundPacket;
+
+ private OutboundPacketWrapper(NetworkId networkId, OutboundPacket outboundPacket) {
+ this.networkId = networkId;
+ this.outboundPacket = outboundPacket;
+ }
+
+ }
+
+ /**
+ * Sets thread pool size of message handler.
+ *
+ * @param poolSize
+ */
+ private void setMessageHandlerThreadPoolSize(int poolSize) {
+ checkArgument(poolSize >= 0, "Message handler pool size must be 0 or more");
+ messageHandlerThreadPoolSize = poolSize;
+ }
+
+ /**
+ * Restarts thread pool of message handler.
+ */
+ private void restartMessageHandlerThreadPool() {
+ ExecutorService prevExecutor = messageHandlingExecutor;
+ messageHandlingExecutor = newFixedThreadPool(getMessageHandlerThreadPoolSize(),
+ groupedThreads("DistPktStore", "messageHandling-%d", log));
+ prevExecutor.shutdown();
+ }
+
+ /**
+ * Gets current thread pool size of message handler.
+ *
+ * @return messageHandlerThreadPoolSize
+ */
+ private int getMessageHandlerThreadPoolSize() {
+ return messageHandlerThreadPoolSize;
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/MeterData.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/MeterData.java
new file mode 100644
index 0000000..c014505
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/MeterData.java
@@ -0,0 +1,52 @@
+/*
+ * 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.incubator.net.virtual.store.impl;
+
+import org.onosproject.cluster.NodeId;
+import org.onosproject.net.meter.Meter;
+import org.onosproject.net.meter.MeterFailReason;
+
+import java.util.Optional;
+
+/**
+ * A class representing the meter information stored in the meter store.
+ */
+public class MeterData {
+
+ private final Meter meter;
+ private final Optional<MeterFailReason> reason;
+ private final NodeId origin;
+
+ public MeterData(Meter meter, MeterFailReason reason, NodeId origin) {
+ this.meter = meter;
+ this.reason = Optional.ofNullable(reason);
+ this.origin = origin;
+ }
+
+ public Meter meter() {
+ return meter;
+ }
+
+ public Optional<MeterFailReason> reason() {
+ return this.reason;
+ }
+
+ public NodeId origin() {
+ return this.origin;
+ }
+
+
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/OsgiPropertyConstants.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/OsgiPropertyConstants.java
new file mode 100644
index 0000000..39bc5b0
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/OsgiPropertyConstants.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.incubator.net.virtual.store.impl;
+
+/**
+ * Constants for default values of configurable properties.
+ */
+public final class OsgiPropertyConstants {
+
+ private OsgiPropertyConstants() {}
+
+ public static final String MESSAGE_HANDLER_THREAD_POOL_SIZE = "messageHandlerThreadPoolSize";
+ public static final int MESSAGE_HANDLER_THREAD_POOL_SIZE_DEFAULT = 4;
+
+ public static final String BACKUP_PERIOD_MILLIS = "backupPeriod";
+ public static final int BACKUP_PERIOD_MILLIS_DEFAULT = 2000;
+
+ public static final String PERSISTENCE_ENABLED = "persistenceEnabled";
+ public static final boolean PERSISTENCE_ENABLED_DEFAULT = false;
+
+ public static final String PENDING_FUTURE_TIMEOUT_MINUTES = "pendingFutureTimeoutMinutes";
+ public static final int PENDING_FUTURE_TIMEOUT_MINUTES_DEFAULT = 5;
+
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualFlowObjectiveStore.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualFlowObjectiveStore.java
new file mode 100644
index 0000000..4ef268e
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualFlowObjectiveStore.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl;
+
+import com.google.common.collect.Maps;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkFlowObjectiveStore;
+import org.onosproject.net.behaviour.DefaultNextGroup;
+import org.onosproject.net.behaviour.NextGroup;
+import org.onosproject.net.flowobjective.FlowObjectiveStoreDelegate;
+import org.onosproject.net.flowobjective.ObjectiveEvent;
+import org.onosproject.store.service.AtomicCounter;
+import org.onosproject.store.service.StorageService;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import static org.onlab.util.Tools.groupedThreads;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Single instance implementation of store to manage
+ * the inventory of created next groups for virtual network.
+ */
+@Component(immediate = true, service = VirtualNetworkFlowObjectiveStore.class)
+public class SimpleVirtualFlowObjectiveStore
+ extends AbstractVirtualStore<ObjectiveEvent, FlowObjectiveStoreDelegate>
+ implements VirtualNetworkFlowObjectiveStore {
+
+ private final Logger log = getLogger(getClass());
+
+ private ConcurrentMap<NetworkId, ConcurrentMap<Integer, byte[]>> nextGroupsMap;
+
+ private AtomicCounter nextIds;
+
+ // event queue to separate map-listener threads from event-handler threads (tpool)
+ private BlockingQueue<VirtualObjectiveEvent> eventQ;
+ private ExecutorService tpool;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected StorageService storageService;
+
+ @Activate
+ public void activate() {
+ tpool = Executors.newFixedThreadPool(4, groupedThreads("onos/virtual/flobj-notifier", "%d", log));
+ eventQ = new LinkedBlockingQueue<>();
+ tpool.execute(new FlowObjectiveNotifier());
+
+ initNextGroupsMap();
+
+ nextIds = storageService.getAtomicCounter("next-objective-counter");
+ log.info("Started");
+ }
+
+ public void deactivate() {
+ log.info("Stopped");
+ }
+
+ protected void initNextGroupsMap() {
+ nextGroupsMap = Maps.newConcurrentMap();
+ }
+
+ protected void updateNextGroupsMap(NetworkId networkId,
+ ConcurrentMap<Integer, byte[]> nextGroups) {
+ }
+
+ protected ConcurrentMap<Integer, byte[]> getNextGroups(NetworkId networkId) {
+ nextGroupsMap.computeIfAbsent(networkId, n -> Maps.newConcurrentMap());
+ return nextGroupsMap.get(networkId);
+ }
+
+ @Override
+ public void putNextGroup(NetworkId networkId, Integer nextId, NextGroup group) {
+ ConcurrentMap<Integer, byte[]> nextGroups = getNextGroups(networkId);
+ nextGroups.put(nextId, group.data());
+ updateNextGroupsMap(networkId, nextGroups);
+
+ eventQ.add(new VirtualObjectiveEvent(networkId, ObjectiveEvent.Type.ADD, nextId));
+ }
+
+ @Override
+ public NextGroup getNextGroup(NetworkId networkId, Integer nextId) {
+ ConcurrentMap<Integer, byte[]> nextGroups = getNextGroups(networkId);
+ byte[] groupData = nextGroups.get(nextId);
+ if (groupData != null) {
+ return new DefaultNextGroup(groupData);
+ }
+ return null;
+ }
+
+ @Override
+ public NextGroup removeNextGroup(NetworkId networkId, Integer nextId) {
+ ConcurrentMap<Integer, byte[]> nextGroups = getNextGroups(networkId);
+ byte[] nextGroup = nextGroups.remove(nextId);
+ updateNextGroupsMap(networkId, nextGroups);
+
+ eventQ.add(new VirtualObjectiveEvent(networkId, ObjectiveEvent.Type.REMOVE, nextId));
+
+ return new DefaultNextGroup(nextGroup);
+ }
+
+ @Override
+ public Map<Integer, NextGroup> getAllGroups(NetworkId networkId) {
+ ConcurrentMap<Integer, byte[]> nextGroups = getNextGroups(networkId);
+
+ Map<Integer, NextGroup> nextGroupMappings = new HashMap<>();
+ for (int key : nextGroups.keySet()) {
+ NextGroup nextGroup = getNextGroup(networkId, key);
+ if (nextGroup != null) {
+ nextGroupMappings.put(key, nextGroup);
+ }
+ }
+ return nextGroupMappings;
+ }
+
+ @Override
+ public int allocateNextId(NetworkId networkId) {
+ return (int) nextIds.incrementAndGet();
+ }
+
+ private class FlowObjectiveNotifier implements Runnable {
+ @Override
+ public void run() {
+ try {
+ while (!Thread.currentThread().isInterrupted()) {
+ VirtualObjectiveEvent vEvent = eventQ.take();
+ notifyDelegate(vEvent.networkId(), vEvent);
+ }
+ } catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ private class VirtualObjectiveEvent extends ObjectiveEvent {
+ NetworkId networkId;
+
+ public VirtualObjectiveEvent(NetworkId networkId, Type type,
+ Integer objective) {
+ super(type, objective);
+ this.networkId = networkId;
+ }
+
+ NetworkId networkId() {
+ return networkId;
+ }
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualFlowRuleStore.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualFlowRuleStore.java
new file mode 100644
index 0000000..1fc5cde
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualFlowRuleStore.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.store.impl;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.RemovalListener;
+import com.google.common.cache.RemovalNotification;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.SettableFuture;
+import org.onlab.util.Tools;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkFlowRuleStore;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.CompletedBatchOperation;
+import org.onosproject.net.flow.DefaultFlowEntry;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowId;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleEvent;
+import org.onosproject.net.flow.FlowRuleStoreDelegate;
+import org.onosproject.net.flow.StoredFlowEntry;
+import org.onosproject.net.flow.TableStatisticsEntry;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEvent;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchRequest;
+import org.onosproject.store.service.StorageService;
+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.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.onosproject.incubator.net.virtual.store.impl.OsgiPropertyConstants.PENDING_FUTURE_TIMEOUT_MINUTES;
+import static org.onosproject.incubator.net.virtual.store.impl.OsgiPropertyConstants.PENDING_FUTURE_TIMEOUT_MINUTES_DEFAULT;
+import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_REMOVED;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of the virtual network flow rule store to manage inventory of
+ * virtual flow rules using trivial in-memory implementation.
+ */
+//TODO: support distributed flowrule store for virtual networks
+
+@Component(immediate = true, service = VirtualNetworkFlowRuleStore.class,
+ property = {
+ PENDING_FUTURE_TIMEOUT_MINUTES + ":Integer=" + PENDING_FUTURE_TIMEOUT_MINUTES_DEFAULT,
+ })
+public class SimpleVirtualFlowRuleStore
+ extends AbstractVirtualStore<FlowRuleBatchEvent, FlowRuleStoreDelegate>
+ implements VirtualNetworkFlowRuleStore {
+
+ private final Logger log = getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected StorageService storageService;
+
+ private final ConcurrentMap<NetworkId,
+ ConcurrentMap<DeviceId, ConcurrentMap<FlowId, List<StoredFlowEntry>>>>
+ flowEntries = new ConcurrentHashMap<>();
+
+
+ private final AtomicInteger localBatchIdGen = new AtomicInteger();
+
+ /** Expiration time after an entry is created that it should be automatically removed. */
+ private int pendingFutureTimeoutMinutes = PENDING_FUTURE_TIMEOUT_MINUTES_DEFAULT;
+
+ private Cache<Integer, SettableFuture<CompletedBatchOperation>> pendingFutures =
+ CacheBuilder.newBuilder()
+ .expireAfterWrite(pendingFutureTimeoutMinutes, TimeUnit.MINUTES)
+ .removalListener(new TimeoutFuture())
+ .build();
+
+ @Activate
+ public void activate() {
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ flowEntries.clear();
+ log.info("Stopped");
+ }
+
+ @Modified
+ public void modified(ComponentContext context) {
+
+ readComponentConfiguration(context);
+
+ // Reset Cache and copy all.
+ Cache<Integer, SettableFuture<CompletedBatchOperation>> prevFutures = pendingFutures;
+ pendingFutures = CacheBuilder.newBuilder()
+ .expireAfterWrite(pendingFutureTimeoutMinutes, TimeUnit.MINUTES)
+ .removalListener(new TimeoutFuture())
+ .build();
+
+ pendingFutures.putAll(prevFutures.asMap());
+ }
+
+ /**
+ * Extracts properties from the component configuration context.
+ *
+ * @param context the component context
+ */
+ private void readComponentConfiguration(ComponentContext context) {
+ Dictionary<?, ?> properties = context.getProperties();
+
+ Integer newPendingFutureTimeoutMinutes =
+ Tools.getIntegerProperty(properties, "pendingFutureTimeoutMinutes");
+ if (newPendingFutureTimeoutMinutes == null) {
+ pendingFutureTimeoutMinutes = PENDING_FUTURE_TIMEOUT_MINUTES_DEFAULT;
+ log.info("Pending future timeout is not configured, " +
+ "using current value of {}", pendingFutureTimeoutMinutes);
+ } else {
+ pendingFutureTimeoutMinutes = newPendingFutureTimeoutMinutes;
+ log.info("Configured. Pending future timeout is configured to {}",
+ pendingFutureTimeoutMinutes);
+ }
+ }
+
+ @Override
+ public int getFlowRuleCount(NetworkId networkId) {
+ int sum = 0;
+
+ if (flowEntries.get(networkId) == null) {
+ return 0;
+ }
+
+ for (ConcurrentMap<FlowId, List<StoredFlowEntry>> ft :
+ flowEntries.get(networkId).values()) {
+ for (List<StoredFlowEntry> fes : ft.values()) {
+ sum += fes.size();
+ }
+ }
+ return sum;
+ }
+
+ @Override
+ public FlowEntry getFlowEntry(NetworkId networkId, FlowRule rule) {
+ return getFlowEntryInternal(networkId, rule.deviceId(), rule);
+ }
+
+ @Override
+ public Iterable<FlowEntry> getFlowEntries(NetworkId networkId, DeviceId deviceId) {
+ return FluentIterable.from(getFlowTable(networkId, deviceId).values())
+ .transformAndConcat(Collections::unmodifiableList);
+ }
+
+ private void storeFlowRule(NetworkId networkId, FlowRule rule) {
+ storeFlowRuleInternal(networkId, rule);
+ }
+
+ @Override
+ public void storeBatch(NetworkId networkId, FlowRuleBatchOperation batchOperation) {
+ List<FlowRuleBatchEntry> toAdd = new ArrayList<>();
+ List<FlowRuleBatchEntry> toRemove = new ArrayList<>();
+
+ for (FlowRuleBatchEntry entry : batchOperation.getOperations()) {
+ final FlowRule flowRule = entry.target();
+ if (entry.operator().equals(FlowRuleBatchEntry.FlowRuleOperation.ADD)) {
+ if (!getFlowEntries(networkId, flowRule.deviceId(),
+ flowRule.id()).contains(flowRule)) {
+ storeFlowRule(networkId, flowRule);
+ toAdd.add(entry);
+ }
+ } else if (entry.operator().equals(FlowRuleBatchEntry.FlowRuleOperation.REMOVE)) {
+ if (getFlowEntries(networkId, flowRule.deviceId(), flowRule.id()).contains(flowRule)) {
+ deleteFlowRule(networkId, flowRule);
+ toRemove.add(entry);
+ }
+ } else {
+ throw new UnsupportedOperationException("Unsupported operation type");
+ }
+ }
+
+ if (toAdd.isEmpty() && toRemove.isEmpty()) {
+ notifyDelegate(networkId, FlowRuleBatchEvent.completed(
+ new FlowRuleBatchRequest(batchOperation.id(), Collections.emptySet()),
+ new CompletedBatchOperation(true, Collections.emptySet(),
+ batchOperation.deviceId())));
+ return;
+ }
+
+ SettableFuture<CompletedBatchOperation> r = SettableFuture.create();
+ final int futureId = localBatchIdGen.incrementAndGet();
+
+ pendingFutures.put(futureId, r);
+
+ toAdd.addAll(toRemove);
+ notifyDelegate(networkId, FlowRuleBatchEvent.requested(
+ new FlowRuleBatchRequest(batchOperation.id(),
+ Sets.newHashSet(toAdd)), batchOperation.deviceId()));
+
+ }
+
+ @Override
+ public void batchOperationComplete(NetworkId networkId, FlowRuleBatchEvent event) {
+ final Long batchId = event.subject().batchId();
+ SettableFuture<CompletedBatchOperation> future
+ = pendingFutures.getIfPresent(batchId);
+ if (future != null) {
+ future.set(event.result());
+ pendingFutures.invalidate(batchId);
+ }
+ notifyDelegate(networkId, event);
+ }
+
+ @Override
+ public void deleteFlowRule(NetworkId networkId, FlowRule rule) {
+ List<StoredFlowEntry> entries = getFlowEntries(networkId, rule.deviceId(), rule.id());
+
+ synchronized (entries) {
+ for (StoredFlowEntry entry : entries) {
+ if (entry.equals(rule)) {
+ synchronized (entry) {
+ entry.setState(FlowEntry.FlowEntryState.PENDING_REMOVE);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public FlowRuleEvent addOrUpdateFlowRule(NetworkId networkId, FlowEntry rule) {
+ // check if this new rule is an update to an existing entry
+ List<StoredFlowEntry> entries = getFlowEntries(networkId, rule.deviceId(), rule.id());
+ synchronized (entries) {
+ for (StoredFlowEntry stored : entries) {
+ if (stored.equals(rule)) {
+ synchronized (stored) {
+ //FIXME modification of "stored" flow entry outside of flow table
+ stored.setBytes(rule.bytes());
+ stored.setLife(rule.life());
+ stored.setPackets(rule.packets());
+ if (stored.state() == FlowEntry.FlowEntryState.PENDING_ADD) {
+ stored.setState(FlowEntry.FlowEntryState.ADDED);
+ // TODO: Do we need to change `rule` state?
+ return new FlowRuleEvent(FlowRuleEvent.Type.RULE_ADDED, rule);
+ }
+ return new FlowRuleEvent(FlowRuleEvent.Type.RULE_UPDATED, rule);
+ }
+ }
+ }
+ }
+
+ // should not reach here
+ // storeFlowRule was expected to be called
+ log.error("FlowRule was not found in store {} to update", rule);
+
+ return null;
+ }
+
+ @Override
+ public FlowRuleEvent removeFlowRule(NetworkId networkId, FlowEntry rule) {
+ // This is where one could mark a rule as removed and still keep it in the store.
+ final DeviceId did = rule.deviceId();
+
+ List<StoredFlowEntry> entries = getFlowEntries(networkId, did, rule.id());
+ synchronized (entries) {
+ if (entries.remove(rule)) {
+ return new FlowRuleEvent(RULE_REMOVED, rule);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public FlowRuleEvent pendingFlowRule(NetworkId networkId, FlowEntry rule) {
+ List<StoredFlowEntry> entries = getFlowEntries(networkId, rule.deviceId(), rule.id());
+ synchronized (entries) {
+ for (StoredFlowEntry entry : entries) {
+ if (entry.equals(rule) &&
+ entry.state() != FlowEntry.FlowEntryState.PENDING_ADD) {
+ synchronized (entry) {
+ entry.setState(FlowEntry.FlowEntryState.PENDING_ADD);
+ return new FlowRuleEvent(FlowRuleEvent.Type.RULE_UPDATED, rule);
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void purgeFlowRule(NetworkId networkId, DeviceId deviceId) {
+ flowEntries.get(networkId).remove(deviceId);
+ }
+
+ @Override
+ public void purgeFlowRules(NetworkId networkId) {
+ flowEntries.get(networkId).clear();
+ }
+
+ @Override
+ public FlowRuleEvent
+ updateTableStatistics(NetworkId networkId, DeviceId deviceId, List<TableStatisticsEntry> tableStats) {
+ //TODO: Table operations are not supported yet
+ return null;
+ }
+
+ @Override
+ public Iterable<TableStatisticsEntry>
+ getTableStatistics(NetworkId networkId, DeviceId deviceId) {
+ //TODO: Table operations are not supported yet
+ return null;
+ }
+
+ /**
+ * Returns the flow table for specified device.
+ *
+ * @param networkId identifier of the virtual network
+ * @param deviceId identifier of the virtual device
+ * @return Map representing Flow Table of given device.
+ */
+ private ConcurrentMap<FlowId, List<StoredFlowEntry>>
+ getFlowTable(NetworkId networkId, DeviceId deviceId) {
+ return flowEntries
+ .computeIfAbsent(networkId, n -> new ConcurrentHashMap<>())
+ .computeIfAbsent(deviceId, k -> new ConcurrentHashMap<>());
+ }
+
+ private List<StoredFlowEntry>
+ getFlowEntries(NetworkId networkId, DeviceId deviceId, FlowId flowId) {
+ final ConcurrentMap<FlowId, List<StoredFlowEntry>> flowTable
+ = getFlowTable(networkId, deviceId);
+
+ List<StoredFlowEntry> r = flowTable.get(flowId);
+ if (r == null) {
+ final List<StoredFlowEntry> concurrentlyAdded;
+ r = new CopyOnWriteArrayList<>();
+ concurrentlyAdded = flowTable.putIfAbsent(flowId, r);
+ if (concurrentlyAdded != null) {
+ return concurrentlyAdded;
+ }
+ }
+ return r;
+ }
+
+ private FlowEntry
+ getFlowEntryInternal(NetworkId networkId, DeviceId deviceId, FlowRule rule) {
+ List<StoredFlowEntry> fes = getFlowEntries(networkId, deviceId, rule.id());
+ for (StoredFlowEntry fe : fes) {
+ if (fe.equals(rule)) {
+ return fe;
+ }
+ }
+ return null;
+ }
+
+ private void storeFlowRuleInternal(NetworkId networkId, FlowRule rule) {
+ StoredFlowEntry f = new DefaultFlowEntry(rule);
+ final DeviceId did = f.deviceId();
+ final FlowId fid = f.id();
+ List<StoredFlowEntry> existing = getFlowEntries(networkId, did, fid);
+ synchronized (existing) {
+ for (StoredFlowEntry fe : existing) {
+ if (fe.equals(rule)) {
+ // was already there? ignore
+ return;
+ }
+ }
+ // new flow rule added
+ existing.add(f);
+ }
+ }
+
+ private static final class TimeoutFuture
+ implements RemovalListener<Integer, SettableFuture<CompletedBatchOperation>> {
+ @Override
+ public void onRemoval(RemovalNotification<Integer,
+ SettableFuture<CompletedBatchOperation>> notification) {
+ // wrapping in ExecutionException to support Future.get
+ if (notification.wasEvicted()) {
+ notification.getValue()
+ .setException(new ExecutionException("Timed out",
+ new TimeoutException()));
+ }
+ }
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualGroupStore.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualGroupStore.java
new file mode 100644
index 0000000..3c696ce
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualGroupStore.java
@@ -0,0 +1,764 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl;
+
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Sets;
+import org.onosproject.core.GroupId;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkGroupStore;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.group.DefaultGroup;
+import org.onosproject.net.group.DefaultGroupDescription;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.group.GroupEvent;
+import org.onosproject.net.group.GroupKey;
+import org.onosproject.net.group.GroupOperation;
+import org.onosproject.net.group.GroupStoreDelegate;
+import org.onosproject.net.group.StoredGroupBucketEntry;
+import org.onosproject.net.group.StoredGroupEntry;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.slf4j.Logger;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages inventory of virtual group entries using trivial in-memory implementation.
+ */
+@Component(immediate = true, service = VirtualNetworkGroupStore.class)
+public class SimpleVirtualGroupStore
+ extends AbstractVirtualStore<GroupEvent, GroupStoreDelegate>
+ implements VirtualNetworkGroupStore {
+
+ private final Logger log = getLogger(getClass());
+
+ private final int dummyId = 0xffffffff;
+ private final GroupId dummyGroupId = new GroupId(dummyId);
+
+ // inner Map is per device group table
+ private final ConcurrentMap<NetworkId,
+ ConcurrentMap<DeviceId, ConcurrentMap<GroupKey, StoredGroupEntry>>>
+ groupEntriesByKey = new ConcurrentHashMap<>();
+
+ private final ConcurrentMap<NetworkId,
+ ConcurrentMap<DeviceId, ConcurrentMap<GroupId, StoredGroupEntry>>>
+ groupEntriesById = new ConcurrentHashMap<>();
+
+ private final ConcurrentMap<NetworkId,
+ ConcurrentMap<DeviceId, ConcurrentMap<GroupKey, StoredGroupEntry>>>
+ pendingGroupEntriesByKey = new ConcurrentHashMap<>();
+
+ private final ConcurrentMap<NetworkId,
+ ConcurrentMap<DeviceId, ConcurrentMap<GroupId, Group>>>
+ extraneousGroupEntriesById = new ConcurrentHashMap<>();
+
+ private final ConcurrentMap<NetworkId, HashMap<DeviceId, Boolean>>
+ deviceAuditStatus = new ConcurrentHashMap<>();
+
+ private final AtomicInteger groupIdGen = new AtomicInteger();
+
+ @Activate
+ public void activate() {
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ groupEntriesByKey.clear();
+ groupEntriesById.clear();
+ log.info("Stopped");
+ }
+
+ /**
+ * Returns the group key table for specified device.
+ *
+ * @param networkId identifier of the virtual network
+ * @param deviceId identifier of the device
+ * @return Map representing group key table of given device.
+ */
+ private ConcurrentMap<GroupKey, StoredGroupEntry>
+ getGroupKeyTable(NetworkId networkId, DeviceId deviceId) {
+ groupEntriesByKey.computeIfAbsent(networkId, n -> new ConcurrentHashMap<>());
+ return groupEntriesByKey.get(networkId)
+ .computeIfAbsent(deviceId, k -> new ConcurrentHashMap<>());
+ }
+
+ /**
+ * Returns the group id table for specified device.
+ *
+ * @param networkId identifier of the virtual network
+ * @param deviceId identifier of the device
+ * @return Map representing group key table of given device.
+ */
+ private ConcurrentMap<GroupId, StoredGroupEntry>
+ getGroupIdTable(NetworkId networkId, DeviceId deviceId) {
+ groupEntriesById.computeIfAbsent(networkId, n -> new ConcurrentHashMap<>());
+ return groupEntriesById.get(networkId)
+ .computeIfAbsent(deviceId, k -> new ConcurrentHashMap<>());
+ }
+
+ /**
+ * Returns the pending group key table for specified device.
+ *
+ * @param networkId identifier of the virtual network
+ * @param deviceId identifier of the device
+ * @return Map representing group key table of given device.
+ */
+ private ConcurrentMap<GroupKey, StoredGroupEntry>
+ getPendingGroupKeyTable(NetworkId networkId, DeviceId deviceId) {
+ pendingGroupEntriesByKey.computeIfAbsent(networkId, n -> new ConcurrentHashMap<>());
+ return pendingGroupEntriesByKey.get(networkId)
+ .computeIfAbsent(deviceId, k -> new ConcurrentHashMap<>());
+ }
+
+ /**
+ * Returns the extraneous group id table for specified device.
+ *
+ * @param networkId identifier of the virtual network
+ * @param deviceId identifier of the device
+ * @return Map representing group key table of given device.
+ */
+ private ConcurrentMap<GroupId, Group>
+ getExtraneousGroupIdTable(NetworkId networkId, DeviceId deviceId) {
+ extraneousGroupEntriesById.computeIfAbsent(networkId, n -> new ConcurrentHashMap<>());
+ return extraneousGroupEntriesById.get(networkId)
+ .computeIfAbsent(deviceId, k -> new ConcurrentHashMap<>());
+ }
+
+ @Override
+ public int getGroupCount(NetworkId networkId, DeviceId deviceId) {
+ return (groupEntriesByKey.get(networkId).get(deviceId) != null) ?
+ groupEntriesByKey.get(networkId).get(deviceId).size() : 0;
+ }
+
+ @Override
+ public Iterable<Group> getGroups(NetworkId networkId, DeviceId deviceId) {
+ // flatten and make iterator unmodifiable
+ return FluentIterable.from(getGroupKeyTable(networkId, deviceId).values())
+ .transform(input -> input);
+ }
+
+ @Override
+ public Group getGroup(NetworkId networkId, DeviceId deviceId, GroupKey appCookie) {
+ if (groupEntriesByKey.get(networkId) != null &&
+ groupEntriesByKey.get(networkId).get(deviceId) != null) {
+ return groupEntriesByKey.get(networkId).get(deviceId).get(appCookie);
+ }
+ return null;
+ }
+
+ @Override
+ public Group getGroup(NetworkId networkId, DeviceId deviceId, GroupId groupId) {
+ if (groupEntriesById.get(networkId) != null &&
+ groupEntriesById.get(networkId).get(deviceId) != null) {
+ return groupEntriesById.get(networkId).get(deviceId).get(groupId);
+ }
+ return null;
+ }
+
+ private int getFreeGroupIdValue(NetworkId networkId, DeviceId deviceId) {
+ int freeId = groupIdGen.incrementAndGet();
+
+ while (true) {
+ Group existing = null;
+ if (groupEntriesById.get(networkId) != null &&
+ groupEntriesById.get(networkId).get(deviceId) != null) {
+ existing = groupEntriesById.get(networkId).get(deviceId)
+ .get(new GroupId(freeId));
+ }
+
+ if (existing == null) {
+ if (extraneousGroupEntriesById.get(networkId) != null &&
+ extraneousGroupEntriesById.get(networkId).get(deviceId) != null) {
+ existing = extraneousGroupEntriesById.get(networkId).get(deviceId)
+ .get(new GroupId(freeId));
+ }
+ }
+
+ if (existing != null) {
+ freeId = groupIdGen.incrementAndGet();
+ } else {
+ break;
+ }
+ }
+ return freeId;
+ }
+
+ @Override
+ public void storeGroupDescription(NetworkId networkId, GroupDescription groupDesc) {
+ // Check if a group is existing with the same key
+ if (getGroup(networkId, groupDesc.deviceId(), groupDesc.appCookie()) != null) {
+ return;
+ }
+
+ if (deviceAuditStatus.get(networkId) == null ||
+ deviceAuditStatus.get(networkId).get(groupDesc.deviceId()) == null) {
+ // Device group audit has not completed yet
+ // Add this group description to pending group key table
+ // Create a group entry object with Dummy Group ID
+ StoredGroupEntry group = new DefaultGroup(dummyGroupId, groupDesc);
+ group.setState(Group.GroupState.WAITING_AUDIT_COMPLETE);
+ ConcurrentMap<GroupKey, StoredGroupEntry> pendingKeyTable =
+ getPendingGroupKeyTable(networkId, groupDesc.deviceId());
+ pendingKeyTable.put(groupDesc.appCookie(), group);
+ return;
+ }
+
+ storeGroupDescriptionInternal(networkId, groupDesc);
+ }
+
+ private void storeGroupDescriptionInternal(NetworkId networkId,
+ GroupDescription groupDesc) {
+ // Check if a group is existing with the same key
+ if (getGroup(networkId, groupDesc.deviceId(), groupDesc.appCookie()) != null) {
+ return;
+ }
+
+ GroupId id = null;
+ if (groupDesc.givenGroupId() == null) {
+ // Get a new group identifier
+ id = new GroupId(getFreeGroupIdValue(networkId, groupDesc.deviceId()));
+ } else {
+ id = new GroupId(groupDesc.givenGroupId());
+ }
+ // Create a group entry object
+ StoredGroupEntry group = new DefaultGroup(id, groupDesc);
+ // Insert the newly created group entry into concurrent key and id maps
+ ConcurrentMap<GroupKey, StoredGroupEntry> keyTable =
+ getGroupKeyTable(networkId, groupDesc.deviceId());
+ keyTable.put(groupDesc.appCookie(), group);
+ ConcurrentMap<GroupId, StoredGroupEntry> idTable =
+ getGroupIdTable(networkId, groupDesc.deviceId());
+ idTable.put(id, group);
+ notifyDelegate(networkId, new GroupEvent(GroupEvent.Type.GROUP_ADD_REQUESTED,
+ group));
+ }
+
+ @Override
+ public void updateGroupDescription(NetworkId networkId, DeviceId deviceId,
+ GroupKey oldAppCookie, UpdateType type,
+ GroupBuckets newBuckets, GroupKey newAppCookie) {
+ // Check if a group is existing with the provided key
+ Group oldGroup = getGroup(networkId, deviceId, oldAppCookie);
+ if (oldGroup == null) {
+ return;
+ }
+
+ List<GroupBucket> newBucketList = getUpdatedBucketList(oldGroup,
+ type,
+ newBuckets);
+ if (newBucketList != null) {
+ // Create a new group object from the old group
+ GroupBuckets updatedBuckets = new GroupBuckets(newBucketList);
+ GroupKey newCookie = (newAppCookie != null) ? newAppCookie : oldAppCookie;
+ GroupDescription updatedGroupDesc = new DefaultGroupDescription(
+ oldGroup.deviceId(),
+ oldGroup.type(),
+ updatedBuckets,
+ newCookie,
+ oldGroup.givenGroupId(),
+ oldGroup.appId());
+ StoredGroupEntry newGroup = new DefaultGroup(oldGroup.id(),
+ updatedGroupDesc);
+ newGroup.setState(Group.GroupState.PENDING_UPDATE);
+ newGroup.setLife(oldGroup.life());
+ newGroup.setPackets(oldGroup.packets());
+ newGroup.setBytes(oldGroup.bytes());
+
+ // Remove the old entry from maps and add new entry using new key
+ ConcurrentMap<GroupKey, StoredGroupEntry> keyTable =
+ getGroupKeyTable(networkId, oldGroup.deviceId());
+ ConcurrentMap<GroupId, StoredGroupEntry> idTable =
+ getGroupIdTable(networkId, oldGroup.deviceId());
+ keyTable.remove(oldGroup.appCookie());
+ idTable.remove(oldGroup.id());
+ keyTable.put(newGroup.appCookie(), newGroup);
+ idTable.put(newGroup.id(), newGroup);
+ notifyDelegate(networkId,
+ new GroupEvent(GroupEvent.Type.GROUP_UPDATE_REQUESTED,
+ newGroup));
+ }
+
+ }
+
+ private List<GroupBucket> getUpdatedBucketList(Group oldGroup,
+ UpdateType type,
+ GroupBuckets buckets) {
+ if (type == UpdateType.SET) {
+ return buckets.buckets();
+ }
+
+ List<GroupBucket> oldBuckets = oldGroup.buckets().buckets();
+ List<GroupBucket> updatedBucketList = new ArrayList<>();
+ boolean groupDescUpdated = false;
+
+ if (type == UpdateType.ADD) {
+ List<GroupBucket> newBuckets = buckets.buckets();
+
+ // Add old buckets that will not be updated and check if any will be updated.
+ for (GroupBucket oldBucket : oldBuckets) {
+ int newBucketIndex = newBuckets.indexOf(oldBucket);
+
+ if (newBucketIndex != -1) {
+ GroupBucket newBucket = newBuckets.get(newBucketIndex);
+ if (!newBucket.hasSameParameters(oldBucket)) {
+ // Bucket will be updated
+ groupDescUpdated = true;
+ }
+ } else {
+ // Old bucket will remain the same - add it.
+ updatedBucketList.add(oldBucket);
+ }
+ }
+
+ // Add all new buckets
+ updatedBucketList.addAll(newBuckets);
+ if (!oldBuckets.containsAll(newBuckets)) {
+ groupDescUpdated = true;
+ }
+
+ } else if (type == UpdateType.REMOVE) {
+ List<GroupBucket> bucketsToRemove = buckets.buckets();
+
+ // Check which old buckets should remain
+ for (GroupBucket oldBucket : oldBuckets) {
+ if (!bucketsToRemove.contains(oldBucket)) {
+ updatedBucketList.add(oldBucket);
+ } else {
+ groupDescUpdated = true;
+ }
+ }
+ }
+
+ if (groupDescUpdated) {
+ return updatedBucketList;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void deleteGroupDescription(NetworkId networkId, DeviceId deviceId,
+ GroupKey appCookie) {
+ // Check if a group is existing with the provided key
+ StoredGroupEntry existing = null;
+ if (groupEntriesByKey.get(networkId) != null &&
+ groupEntriesByKey.get(networkId).get(deviceId) != null) {
+ existing = groupEntriesByKey.get(networkId).get(deviceId).get(appCookie);
+ }
+
+ if (existing == null) {
+ return;
+ }
+
+ synchronized (existing) {
+ existing.setState(Group.GroupState.PENDING_DELETE);
+ }
+ notifyDelegate(networkId,
+ new GroupEvent(GroupEvent.Type.GROUP_REMOVE_REQUESTED, existing));
+ }
+
+ @Override
+ public void addOrUpdateGroupEntry(NetworkId networkId, Group group) {
+ // check if this new entry is an update to an existing entry
+ StoredGroupEntry existing = null;
+
+ if (groupEntriesById.get(networkId) != null &&
+ groupEntriesById.get(networkId).get(group.deviceId()) != null) {
+ existing = groupEntriesById
+ .get(networkId)
+ .get(group.deviceId())
+ .get(group.id());
+ }
+
+ GroupEvent event = null;
+
+ if (existing != null) {
+ synchronized (existing) {
+ for (GroupBucket bucket:group.buckets().buckets()) {
+ Optional<GroupBucket> matchingBucket =
+ existing.buckets().buckets()
+ .stream()
+ .filter((existingBucket) -> (existingBucket.equals(bucket)))
+ .findFirst();
+ if (matchingBucket.isPresent()) {
+ ((StoredGroupBucketEntry) matchingBucket.
+ get()).setPackets(bucket.packets());
+ ((StoredGroupBucketEntry) matchingBucket.
+ get()).setBytes(bucket.bytes());
+ } else {
+ log.warn("addOrUpdateGroupEntry: No matching "
+ + "buckets to update stats");
+ }
+ }
+ existing.setLife(group.life());
+ existing.setPackets(group.packets());
+ existing.setBytes(group.bytes());
+ if (existing.state() == Group.GroupState.PENDING_ADD) {
+ existing.setState(Group.GroupState.ADDED);
+ event = new GroupEvent(GroupEvent.Type.GROUP_ADDED, existing);
+ } else {
+ if (existing.state() == Group.GroupState.PENDING_UPDATE) {
+ existing.setState(Group.GroupState.ADDED);
+ }
+ event = new GroupEvent(GroupEvent.Type.GROUP_UPDATED, existing);
+ }
+ }
+ }
+
+ if (event != null) {
+ notifyDelegate(networkId, event);
+ }
+ }
+
+ @Override
+ public void removeGroupEntry(NetworkId networkId, Group group) {
+ StoredGroupEntry existing = null;
+ if (groupEntriesById.get(networkId) != null
+ && groupEntriesById.get(networkId).get(group.deviceId()) != null) {
+ existing = groupEntriesById
+ .get(networkId).get(group.deviceId()).get(group.id());
+ }
+
+ if (existing != null) {
+ ConcurrentMap<GroupKey, StoredGroupEntry> keyTable =
+ getGroupKeyTable(networkId, existing.deviceId());
+ ConcurrentMap<GroupId, StoredGroupEntry> idTable =
+ getGroupIdTable(networkId, existing.deviceId());
+ idTable.remove(existing.id());
+ keyTable.remove(existing.appCookie());
+ notifyDelegate(networkId,
+ new GroupEvent(GroupEvent.Type.GROUP_REMOVED, existing));
+ }
+ }
+
+ @Override
+ public void purgeGroupEntry(NetworkId networkId, DeviceId deviceId) {
+ if (groupEntriesById.get(networkId) != null) {
+ Set<Map.Entry<GroupId, StoredGroupEntry>> entryPendingRemove =
+ groupEntriesById.get(networkId).get(deviceId).entrySet();
+ groupEntriesById.get(networkId).remove(deviceId);
+ groupEntriesByKey.get(networkId).remove(deviceId);
+
+ entryPendingRemove.forEach(entry -> {
+ notifyDelegate(networkId,
+ new GroupEvent(GroupEvent.Type.GROUP_REMOVED,
+ entry.getValue()));
+ });
+ }
+ }
+
+ @Override
+ public void purgeGroupEntries(NetworkId networkId) {
+ if (groupEntriesById.get(networkId) != null) {
+ groupEntriesById.get((networkId)).values().forEach(groupEntries -> {
+ groupEntries.entrySet().forEach(entry -> {
+ notifyDelegate(networkId,
+ new GroupEvent(GroupEvent.Type.GROUP_REMOVED,
+ entry.getValue()));
+ });
+ });
+
+ groupEntriesById.get(networkId).clear();
+ groupEntriesByKey.get(networkId).clear();
+ }
+ }
+
+ @Override
+ public void addOrUpdateExtraneousGroupEntry(NetworkId networkId, Group group) {
+ ConcurrentMap<GroupId, Group> extraneousIdTable =
+ getExtraneousGroupIdTable(networkId, group.deviceId());
+ extraneousIdTable.put(group.id(), group);
+ // Check the reference counter
+ if (group.referenceCount() == 0) {
+ notifyDelegate(networkId,
+ new GroupEvent(GroupEvent.Type.GROUP_REMOVE_REQUESTED, group));
+ }
+ }
+
+ @Override
+ public void removeExtraneousGroupEntry(NetworkId networkId, Group group) {
+ ConcurrentMap<GroupId, Group> extraneousIdTable =
+ getExtraneousGroupIdTable(networkId, group.deviceId());
+ extraneousIdTable.remove(group.id());
+ }
+
+ @Override
+ public Iterable<Group> getExtraneousGroups(NetworkId networkId, DeviceId deviceId) {
+ // flatten and make iterator unmodifiable
+ return FluentIterable.from(
+ getExtraneousGroupIdTable(networkId, deviceId).values());
+ }
+
+ @Override
+ public void deviceInitialAuditCompleted(NetworkId networkId, DeviceId deviceId,
+ boolean completed) {
+ deviceAuditStatus.computeIfAbsent(networkId, k -> new HashMap<>());
+
+ HashMap<DeviceId, Boolean> deviceAuditStatusByNetwork =
+ deviceAuditStatus.get(networkId);
+
+ synchronized (deviceAuditStatusByNetwork) {
+ if (completed) {
+ log.debug("deviceInitialAuditCompleted: AUDIT "
+ + "completed for device {}", deviceId);
+ deviceAuditStatusByNetwork.put(deviceId, true);
+ // Execute all pending group requests
+ ConcurrentMap<GroupKey, StoredGroupEntry> pendingGroupRequests =
+ getPendingGroupKeyTable(networkId, deviceId);
+ for (Group group:pendingGroupRequests.values()) {
+ GroupDescription tmp = new DefaultGroupDescription(
+ group.deviceId(),
+ group.type(),
+ group.buckets(),
+ group.appCookie(),
+ group.givenGroupId(),
+ group.appId());
+ storeGroupDescriptionInternal(networkId, tmp);
+ }
+ getPendingGroupKeyTable(networkId, deviceId).clear();
+ } else {
+ if (deviceAuditStatusByNetwork.get(deviceId)) {
+ log.debug("deviceInitialAuditCompleted: Clearing AUDIT "
+ + "status for device {}", deviceId);
+ deviceAuditStatusByNetwork.put(deviceId, false);
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean deviceInitialAuditStatus(NetworkId networkId, DeviceId deviceId) {
+ deviceAuditStatus.computeIfAbsent(networkId, k -> new HashMap<>());
+
+ HashMap<DeviceId, Boolean> deviceAuditStatusByNetwork =
+ deviceAuditStatus.get(networkId);
+
+ synchronized (deviceAuditStatusByNetwork) {
+ return (deviceAuditStatusByNetwork.get(deviceId) != null)
+ ? deviceAuditStatusByNetwork.get(deviceId) : false;
+ }
+ }
+
+ @Override
+ public void groupOperationFailed(NetworkId networkId, DeviceId deviceId,
+ GroupOperation operation) {
+
+ StoredGroupEntry existing = null;
+ if (groupEntriesById.get(networkId) != null &&
+ groupEntriesById.get(networkId).get(deviceId) != null) {
+ existing = groupEntriesById.get(networkId).get(deviceId)
+ .get(operation.groupId());
+ }
+
+ if (existing == null) {
+ log.warn("No group entry with ID {} found ", operation.groupId());
+ return;
+ }
+
+ switch (operation.opType()) {
+ case ADD:
+ notifyDelegate(networkId,
+ new GroupEvent(GroupEvent.Type.GROUP_ADD_FAILED,
+ existing));
+ break;
+ case MODIFY:
+ notifyDelegate(networkId,
+ new GroupEvent(GroupEvent.Type.GROUP_UPDATE_FAILED,
+ existing));
+ break;
+ case DELETE:
+ notifyDelegate(networkId,
+ new GroupEvent(GroupEvent.Type.GROUP_REMOVE_FAILED,
+ existing));
+ break;
+ default:
+ log.warn("Unknown group operation type {}", operation.opType());
+ }
+
+ ConcurrentMap<GroupKey, StoredGroupEntry> keyTable =
+ getGroupKeyTable(networkId, existing.deviceId());
+ ConcurrentMap<GroupId, StoredGroupEntry> idTable =
+ getGroupIdTable(networkId, existing.deviceId());
+ idTable.remove(existing.id());
+ keyTable.remove(existing.appCookie());
+ }
+
+ @Override
+ public void pushGroupMetrics(NetworkId networkId, DeviceId deviceId,
+ Collection<Group> groupEntries) {
+ boolean deviceInitialAuditStatus =
+ deviceInitialAuditStatus(networkId, deviceId);
+ Set<Group> southboundGroupEntries =
+ Sets.newHashSet(groupEntries);
+ Set<Group> storedGroupEntries =
+ Sets.newHashSet(getGroups(networkId, deviceId));
+ Set<Group> extraneousStoredEntries =
+ Sets.newHashSet(getExtraneousGroups(networkId, deviceId));
+
+ if (log.isTraceEnabled()) {
+ log.trace("pushGroupMetrics: Displaying all ({}) "
+ + "southboundGroupEntries for device {}",
+ southboundGroupEntries.size(),
+ deviceId);
+ for (Group group : southboundGroupEntries) {
+ log.trace("Group {} in device {}", group, deviceId);
+ }
+
+ log.trace("Displaying all ({}) stored group entries for device {}",
+ storedGroupEntries.size(),
+ deviceId);
+ for (Group group : storedGroupEntries) {
+ log.trace("Stored Group {} for device {}", group, deviceId);
+ }
+ }
+
+ for (Iterator<Group> it2 = southboundGroupEntries.iterator(); it2.hasNext();) {
+ Group group = it2.next();
+ if (storedGroupEntries.remove(group)) {
+ // we both have the group, let's update some info then.
+ log.trace("Group AUDIT: group {} exists "
+ + "in both planes for device {}",
+ group.id(), deviceId);
+ groupAdded(networkId, group);
+ it2.remove();
+ }
+ }
+ for (Group group : southboundGroupEntries) {
+ if (getGroup(networkId, group.deviceId(), group.id()) != null) {
+ // There is a group existing with the same id
+ // It is possible that group update is
+ // in progress while we got a stale info from switch
+ if (!storedGroupEntries.remove(getGroup(
+ networkId, group.deviceId(), group.id()))) {
+ log.warn("Group AUDIT: Inconsistent state:"
+ + "Group exists in ID based table while "
+ + "not present in key based table");
+ }
+ } else {
+ // there are groups in the switch that aren't in the store
+ log.trace("Group AUDIT: extraneous group {} exists "
+ + "in data plane for device {}",
+ group.id(), deviceId);
+ extraneousStoredEntries.remove(group);
+ extraneousGroup(networkId, group);
+ }
+ }
+ for (Group group : storedGroupEntries) {
+ // there are groups in the store that aren't in the switch
+ log.trace("Group AUDIT: group {} missing "
+ + "in data plane for device {}",
+ group.id(), deviceId);
+ groupMissing(networkId, group);
+ }
+ for (Group group : extraneousStoredEntries) {
+ // there are groups in the extraneous store that
+ // aren't in the switch
+ log.trace("Group AUDIT: clearing extransoeus group {} "
+ + "from store for device {}",
+ group.id(), deviceId);
+ removeExtraneousGroupEntry(networkId, group);
+ }
+
+ if (!deviceInitialAuditStatus) {
+ log.debug("Group AUDIT: Setting device {} initial "
+ + "AUDIT completed", deviceId);
+ deviceInitialAuditCompleted(networkId, deviceId, true);
+ }
+ }
+
+ @Override
+ public void notifyOfFailovers(NetworkId networkId, Collection<Group> failoverGroups) {
+ List<GroupEvent> failoverEvents = new ArrayList<>();
+ failoverGroups.forEach(group -> {
+ if (group.type() == Group.Type.FAILOVER) {
+ failoverEvents.add(new GroupEvent(GroupEvent.Type.GROUP_BUCKET_FAILOVER, group));
+ }
+ });
+ notifyDelegate(networkId, failoverEvents);
+ }
+
+ private void groupMissing(NetworkId networkId, Group group) {
+ switch (group.state()) {
+ case PENDING_DELETE:
+ log.debug("Group {} delete confirmation from device {} " +
+ "of virtaual network {}",
+ group, group.deviceId(), networkId);
+ removeGroupEntry(networkId, group);
+ break;
+ case ADDED:
+ case PENDING_ADD:
+ case PENDING_UPDATE:
+ log.debug("Group {} is in store but not on device {}",
+ group, group.deviceId());
+ StoredGroupEntry existing = null;
+ if (groupEntriesById.get(networkId) != null &&
+ groupEntriesById.get(networkId).get(group.deviceId()) != null) {
+
+ existing = groupEntriesById.get(networkId)
+ .get(group.deviceId()).get(group.id());
+ }
+ if (existing == null) {
+ break;
+ }
+
+ log.trace("groupMissing: group "
+ + "entry {} in device {} moving "
+ + "from {} to PENDING_ADD",
+ existing.id(),
+ existing.deviceId(),
+ existing.state());
+ existing.setState(Group.GroupState.PENDING_ADD);
+ notifyDelegate(networkId, new GroupEvent(GroupEvent.Type.GROUP_ADD_REQUESTED,
+ group));
+ break;
+ default:
+ log.debug("Virtual network {} : Group {} has not been installed.",
+ networkId, group);
+ break;
+ }
+ }
+
+ private void extraneousGroup(NetworkId networkId, Group group) {
+ log.debug("Group {} is on device {} of virtual network{}, but not in store.",
+ group, group.deviceId(), networkId);
+ addOrUpdateExtraneousGroupEntry(networkId, group);
+ }
+
+ private void groupAdded(NetworkId networkId, Group group) {
+ log.trace("Group {} Added or Updated in device {} of virtual network {}",
+ group, group.deviceId(), networkId);
+ addOrUpdateGroupEntry(networkId, group);
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualIntentStore.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualIntentStore.java
new file mode 100644
index 0000000..15b6bcd
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualIntentStore.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl;
+
+import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkIntentStore;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentData;
+import org.onosproject.net.intent.IntentEvent;
+import org.onosproject.net.intent.IntentState;
+import org.onosproject.net.intent.IntentStoreDelegate;
+import org.onosproject.net.intent.Key;
+import org.onosproject.store.Timestamp;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.slf4j.Logger;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.intent.IntentState.PURGE_REQ;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Simple single-instance implementation of the intent store for virtual networks.
+ */
+
+@Component(immediate = true, service = VirtualNetworkIntentStore.class)
+public class SimpleVirtualIntentStore
+ extends AbstractVirtualStore<IntentEvent, IntentStoreDelegate>
+ implements VirtualNetworkIntentStore {
+
+ private final Logger log = getLogger(getClass());
+
+ private final Map<NetworkId, Map<Key, IntentData>> currentByNetwork =
+ Maps.newConcurrentMap();
+ private final Map<NetworkId, Map<Key, IntentData>> pendingByNetwork =
+ Maps.newConcurrentMap();
+
+ @Activate
+ public void activate() {
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("Stopped");
+ }
+
+
+ @Override
+ public long getIntentCount(NetworkId networkId) {
+ return getCurrentMap(networkId).size();
+ }
+
+ @Override
+ public Iterable<Intent> getIntents(NetworkId networkId) {
+ return getCurrentMap(networkId).values().stream()
+ .map(IntentData::intent)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public Iterable<IntentData> getIntentData(NetworkId networkId,
+ boolean localOnly, long olderThan) {
+ if (localOnly || olderThan > 0) {
+ long older = System.nanoTime() - olderThan * 1_000_000; //convert ms to ns
+ final SystemClockTimestamp time = new SystemClockTimestamp(older);
+ return getCurrentMap(networkId).values().stream()
+ .filter(data -> data.version().isOlderThan(time) &&
+ (!localOnly || isMaster(networkId, data.key())))
+ .collect(Collectors.toList());
+ }
+ return Lists.newArrayList(getCurrentMap(networkId).values());
+ }
+
+ @Override
+ public IntentState getIntentState(NetworkId networkId, Key intentKey) {
+ IntentData data = getCurrentMap(networkId).get(intentKey);
+ return (data != null) ? data.state() : null;
+ }
+
+ @Override
+ public List<Intent> getInstallableIntents(NetworkId networkId, Key intentKey) {
+ IntentData data = getCurrentMap(networkId).get(intentKey);
+ if (data != null) {
+ return data.installables();
+ }
+ return null;
+ }
+
+ @Override
+ public void write(NetworkId networkId, IntentData newData) {
+ checkNotNull(newData);
+
+ synchronized (this) {
+ // TODO this could be refactored/cleaned up
+ IntentData currentData = getCurrentMap(networkId).get(newData.key());
+ IntentData pendingData = getPendingMap(networkId).get(newData.key());
+
+ if (IntentData.isUpdateAcceptable(currentData, newData)) {
+ if (pendingData != null) {
+ if (pendingData.state() == PURGE_REQ) {
+ getCurrentMap(networkId).remove(newData.key(), newData);
+ } else {
+ getCurrentMap(networkId).put(newData.key(), IntentData.copy(newData));
+ }
+
+ if (pendingData.version().compareTo(newData.version()) <= 0) {
+ // pendingData version is less than or equal to newData's
+ // Note: a new update for this key could be pending (it's version will be greater)
+ getPendingMap(networkId).remove(newData.key());
+ }
+ }
+ IntentEvent.getEvent(newData).ifPresent(e -> notifyDelegate(networkId, e));
+ }
+ }
+ }
+
+ @Override
+ public void batchWrite(NetworkId networkId, Iterable<IntentData> updates) {
+ for (IntentData data : updates) {
+ write(networkId, data);
+ }
+ }
+
+ @Override
+ public Intent getIntent(NetworkId networkId, Key key) {
+ IntentData data = getCurrentMap(networkId).get(key);
+ return (data != null) ? data.intent() : null;
+ }
+
+ @Override
+ public IntentData getIntentData(NetworkId networkId, Key key) {
+ IntentData currentData = getCurrentMap(networkId).get(key);
+ if (currentData == null) {
+ return null;
+ }
+ return IntentData.copy(currentData);
+ }
+
+ @Override
+ public void addPending(NetworkId networkId, IntentData data) {
+ if (data.version() == null) { // recompiled intents will already have a version
+ data = new IntentData(data.intent(), data.state(), new SystemClockTimestamp());
+ }
+ synchronized (this) {
+ IntentData existingData = getPendingMap(networkId).get(data.key());
+ if (existingData == null ||
+ // existing version is strictly less than data's version
+ // Note: if they are equal, we already have the update
+ // TODO maybe we should still make this <= to be safe?
+ existingData.version().compareTo(data.version()) < 0) {
+ getPendingMap(networkId).put(data.key(), data);
+
+ checkNotNull(delegateMap.get(networkId), "Store delegate is not set")
+ .process(IntentData.copy(data));
+ IntentEvent.getEvent(data).ifPresent(e -> notifyDelegate(networkId, e));
+ } else {
+ log.debug("IntentData {} is older than existing: {}",
+ data, existingData);
+ }
+ //TODO consider also checking the current map at this point
+ }
+ }
+
+ @Override
+ public boolean isMaster(NetworkId networkId, Key intentKey) {
+ return true;
+ }
+
+ @Override
+ public Iterable<Intent> getPending(NetworkId networkId) {
+ return getPendingMap(networkId).values().stream()
+ .map(IntentData::intent)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public Iterable<IntentData> getPendingData(NetworkId networkId) {
+ return Lists.newArrayList(getPendingMap(networkId).values());
+ }
+
+ @Override
+ public IntentData getPendingData(NetworkId networkId, Key intentKey) {
+ return getPendingMap(networkId).get(intentKey);
+ }
+
+ @Override
+ public Iterable<IntentData> getPendingData(NetworkId networkId,
+ boolean localOnly, long olderThan) {
+ long older = System.nanoTime() - olderThan * 1_000_000; //convert ms to ns
+ final SystemClockTimestamp time = new SystemClockTimestamp(older);
+ return getPendingMap(networkId).values().stream()
+ .filter(data -> data.version().isOlderThan(time) &&
+ (!localOnly || isMaster(networkId, data.key())))
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Returns the current intent map for a specific virtual network.
+ *
+ * @param networkId a virtual network identifier
+ * @return the current map for the requested virtual network
+ */
+ private Map<Key, IntentData> getCurrentMap(NetworkId networkId) {
+ currentByNetwork.computeIfAbsent(networkId,
+ n -> Maps.newConcurrentMap());
+ return currentByNetwork.get(networkId);
+ }
+
+ /**
+ * Returns the pending intent map for a specific virtual network.
+ *
+ * @param networkId a virtual network identifier
+ * @return the pending intent map for the requested virtual network
+ */
+ private Map<Key, IntentData> getPendingMap(NetworkId networkId) {
+ pendingByNetwork.computeIfAbsent(networkId,
+ n -> Maps.newConcurrentMap());
+ return pendingByNetwork.get(networkId);
+ }
+
+ public class SystemClockTimestamp implements Timestamp {
+
+ private final long nanoTimestamp;
+
+ public SystemClockTimestamp() {
+ nanoTimestamp = System.nanoTime();
+ }
+
+ public SystemClockTimestamp(long timestamp) {
+ nanoTimestamp = timestamp;
+ }
+
+ @Override
+ public int compareTo(Timestamp o) {
+ checkArgument(o instanceof SystemClockTimestamp,
+ "Must be SystemClockTimestamp", o);
+ SystemClockTimestamp that = (SystemClockTimestamp) o;
+
+ return ComparisonChain.start()
+ .compare(this.nanoTimestamp, that.nanoTimestamp)
+ .result();
+ }
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualMastershipStore.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualMastershipStore.java
new file mode 100644
index 0000000..68d8ed8
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualMastershipStore.java
@@ -0,0 +1,538 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import org.onlab.packet.IpAddress;
+import org.onosproject.cluster.ClusterEventListener;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.DefaultControllerNode;
+import org.onosproject.cluster.Node;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.cluster.RoleInfo;
+import org.onosproject.core.Version;
+import org.onosproject.core.VersionService;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkMastershipStore;
+import org.onosproject.mastership.MastershipEvent;
+import org.onosproject.mastership.MastershipInfo;
+import org.onosproject.mastership.MastershipStoreDelegate;
+import org.onosproject.mastership.MastershipTerm;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+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.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.onosproject.mastership.MastershipEvent.Type.BACKUPS_CHANGED;
+import static org.onosproject.mastership.MastershipEvent.Type.MASTER_CHANGED;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of the virtual network mastership store to manage inventory of
+ * mastership using trivial in-memory implementation.
+ */
+@Component(immediate = true, service = VirtualNetworkMastershipStore.class)
+public class SimpleVirtualMastershipStore
+ extends AbstractVirtualStore<MastershipEvent, MastershipStoreDelegate>
+ implements VirtualNetworkMastershipStore {
+
+ private final Logger log = getLogger(getClass());
+
+ private static final int NOTHING = 0;
+ private static final int INIT = 1;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected ClusterService clusterService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected VersionService versionService;
+
+ //devices mapped to their masters, to emulate multiple nodes
+ protected final Map<NetworkId, Map<DeviceId, NodeId>> masterMapByNetwork =
+ new HashMap<>();
+ //emulate backups with pile of nodes
+ protected final Map<NetworkId, Map<DeviceId, List<NodeId>>> backupsByNetwork =
+ new HashMap<>();
+ //terms
+ protected final Map<NetworkId, Map<DeviceId, AtomicInteger>> termMapByNetwork =
+ new HashMap<>();
+
+ @Activate
+ public void activate() {
+ if (clusterService == null) {
+ clusterService = createFakeClusterService();
+ }
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("Stopped");
+ }
+
+ @Override
+ public CompletableFuture<MastershipRole> requestRole(NetworkId networkId,
+ DeviceId deviceId) {
+ //query+possible reelection
+ NodeId node = clusterService.getLocalNode().id();
+ MastershipRole role = getRole(networkId, node, deviceId);
+
+ Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
+
+ switch (role) {
+ case MASTER:
+ return CompletableFuture.completedFuture(MastershipRole.MASTER);
+ case STANDBY:
+ if (getMaster(networkId, deviceId) == null) {
+ // no master => become master
+ masterMap.put(deviceId, node);
+ incrementTerm(networkId, deviceId);
+ // remove from backup list
+ removeFromBackups(networkId, deviceId, node);
+ notifyDelegate(networkId, new MastershipEvent(MASTER_CHANGED, deviceId,
+ getMastership(networkId, deviceId)));
+ return CompletableFuture.completedFuture(MastershipRole.MASTER);
+ }
+ return CompletableFuture.completedFuture(MastershipRole.STANDBY);
+ case NONE:
+ if (getMaster(networkId, deviceId) == null) {
+ // no master => become master
+ masterMap.put(deviceId, node);
+ incrementTerm(networkId, deviceId);
+ notifyDelegate(networkId, new MastershipEvent(MASTER_CHANGED, deviceId,
+ getMastership(networkId, deviceId)));
+ return CompletableFuture.completedFuture(MastershipRole.MASTER);
+ }
+ // add to backup list
+ if (addToBackup(networkId, deviceId, node)) {
+ notifyDelegate(networkId, new MastershipEvent(BACKUPS_CHANGED, deviceId,
+ getMastership(networkId, deviceId)));
+ }
+ return CompletableFuture.completedFuture(MastershipRole.STANDBY);
+ default:
+ log.warn("unknown Mastership Role {}", role);
+ }
+ return CompletableFuture.completedFuture(role);
+ }
+
+ @Override
+ public MastershipRole getRole(NetworkId networkId, NodeId nodeId, DeviceId deviceId) {
+ Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
+ Map<DeviceId, List<NodeId>> backups = getBackups(networkId);
+
+ //just query
+ NodeId current = masterMap.get(deviceId);
+ MastershipRole role;
+
+ if (current != null && current.equals(nodeId)) {
+ return MastershipRole.MASTER;
+ }
+
+ if (backups.getOrDefault(deviceId, Collections.emptyList()).contains(nodeId)) {
+ role = MastershipRole.STANDBY;
+ } else {
+ role = MastershipRole.NONE;
+ }
+ return role;
+ }
+
+ @Override
+ public NodeId getMaster(NetworkId networkId, DeviceId deviceId) {
+ Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
+ return masterMap.get(deviceId);
+ }
+
+ @Override
+ public RoleInfo getNodes(NetworkId networkId, DeviceId deviceId) {
+ Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
+ Map<DeviceId, List<NodeId>> backups = getBackups(networkId);
+
+ return new RoleInfo(masterMap.get(deviceId),
+ backups.getOrDefault(deviceId, ImmutableList.of()));
+ }
+
+ @Override
+ public MastershipInfo getMastership(NetworkId networkId, DeviceId deviceId) {
+ Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
+ Map<DeviceId, AtomicInteger> termMap = getTermMap(networkId);
+ Map<DeviceId, List<NodeId>> backups = getBackups(networkId);
+ ImmutableMap.Builder<NodeId, MastershipRole> roleBuilder = ImmutableMap.builder();
+ NodeId master = masterMap.get(deviceId);
+ if (master != null) {
+ roleBuilder.put(master, MastershipRole.MASTER);
+ }
+ backups.getOrDefault(deviceId, Collections.emptyList())
+ .forEach(nodeId -> roleBuilder.put(nodeId, MastershipRole.STANDBY));
+ clusterService.getNodes().stream()
+ .filter(node -> !masterMap.containsValue(node.id()))
+ .filter(node -> !backups.get(deviceId).contains(node.id()))
+ .forEach(node -> roleBuilder.put(node.id(), MastershipRole.NONE));
+ return new MastershipInfo(
+ termMap.getOrDefault(deviceId, new AtomicInteger(NOTHING)).get(),
+ Optional.ofNullable(master),
+ roleBuilder.build());
+ }
+
+ @Override
+ public Set<DeviceId> getDevices(NetworkId networkId, NodeId nodeId) {
+ Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
+
+ Set<DeviceId> ids = new HashSet<>();
+ for (Map.Entry<DeviceId, NodeId> d : masterMap.entrySet()) {
+ if (Objects.equals(d.getValue(), nodeId)) {
+ ids.add(d.getKey());
+ }
+ }
+ return ids;
+ }
+
+ @Override
+ public synchronized CompletableFuture<MastershipEvent> setMaster(NetworkId networkId,
+ NodeId nodeId, DeviceId deviceId) {
+ Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
+
+ MastershipRole role = getRole(networkId, nodeId, deviceId);
+ switch (role) {
+ case MASTER:
+ // no-op
+ return CompletableFuture.completedFuture(null);
+ case STANDBY:
+ case NONE:
+ NodeId prevMaster = masterMap.put(deviceId, nodeId);
+ incrementTerm(networkId, deviceId);
+ removeFromBackups(networkId, deviceId, nodeId);
+ addToBackup(networkId, deviceId, prevMaster);
+ break;
+ default:
+ log.warn("unknown Mastership Role {}", role);
+ return null;
+ }
+
+ return CompletableFuture.completedFuture(
+ new MastershipEvent(MASTER_CHANGED, deviceId, getMastership(networkId, deviceId)));
+ }
+
+ @Override
+ public MastershipTerm getTermFor(NetworkId networkId, DeviceId deviceId) {
+ Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
+ Map<DeviceId, AtomicInteger> termMap = getTermMap(networkId);
+
+ if ((termMap.get(deviceId) == null)) {
+ return MastershipTerm.of(masterMap.get(deviceId), NOTHING);
+ }
+ return MastershipTerm.of(
+ masterMap.get(deviceId), termMap.get(deviceId).get());
+ }
+
+ @Override
+ public CompletableFuture<MastershipEvent> setStandby(NetworkId networkId,
+ NodeId nodeId, DeviceId deviceId) {
+ Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
+
+ MastershipRole role = getRole(networkId, nodeId, deviceId);
+ switch (role) {
+ case MASTER:
+ NodeId backup = reelect(networkId, deviceId, nodeId);
+ if (backup == null) {
+ // no master alternative
+ masterMap.remove(deviceId);
+ // TODO: Should there be new event type for no MASTER?
+ return CompletableFuture.completedFuture(
+ new MastershipEvent(MASTER_CHANGED, deviceId,
+ getMastership(networkId, deviceId)));
+ } else {
+ NodeId prevMaster = masterMap.put(deviceId, backup);
+ incrementTerm(networkId, deviceId);
+ addToBackup(networkId, deviceId, prevMaster);
+ return CompletableFuture.completedFuture(
+ new MastershipEvent(MASTER_CHANGED, deviceId,
+ getMastership(networkId, deviceId)));
+ }
+
+ case STANDBY:
+ case NONE:
+ boolean modified = addToBackup(networkId, deviceId, nodeId);
+ if (modified) {
+ return CompletableFuture.completedFuture(
+ new MastershipEvent(BACKUPS_CHANGED, deviceId,
+ getMastership(networkId, deviceId)));
+ }
+ break;
+
+ default:
+ log.warn("unknown Mastership Role {}", role);
+ }
+ return null;
+ }
+
+
+ /**
+ * Dumbly selects next-available node that's not the current one.
+ * emulate leader election.
+ *
+ * @param networkId a virtual network identifier
+ * @param deviceId a virtual device identifier
+ * @param nodeId a nod identifier
+ * @return Next available node as a leader
+ */
+ private synchronized NodeId reelect(NetworkId networkId, DeviceId deviceId,
+ NodeId nodeId) {
+ Map<DeviceId, List<NodeId>> backups = getBackups(networkId);
+
+ List<NodeId> stbys = backups.getOrDefault(deviceId, Collections.emptyList());
+ NodeId backup = null;
+ for (NodeId n : stbys) {
+ if (!n.equals(nodeId)) {
+ backup = n;
+ break;
+ }
+ }
+ stbys.remove(backup);
+ return backup;
+ }
+
+ @Override
+ public synchronized CompletableFuture<MastershipEvent>
+ relinquishRole(NetworkId networkId, NodeId nodeId, DeviceId deviceId) {
+ Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
+
+ MastershipRole role = getRole(networkId, nodeId, deviceId);
+ switch (role) {
+ case MASTER:
+ NodeId backup = reelect(networkId, deviceId, nodeId);
+ masterMap.put(deviceId, backup);
+ incrementTerm(networkId, deviceId);
+ return CompletableFuture.completedFuture(
+ new MastershipEvent(MASTER_CHANGED, deviceId,
+ getMastership(networkId, deviceId)));
+
+ case STANDBY:
+ if (removeFromBackups(networkId, deviceId, nodeId)) {
+ return CompletableFuture.completedFuture(
+ new MastershipEvent(BACKUPS_CHANGED, deviceId,
+ getMastership(networkId, deviceId)));
+ }
+ break;
+
+ case NONE:
+ break;
+
+ default:
+ log.warn("unknown Mastership Role {}", role);
+ }
+ return CompletableFuture.completedFuture(null);
+ }
+
+ @Override
+ public void relinquishAllRole(NetworkId networkId, NodeId nodeId) {
+ Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
+ Map<DeviceId, List<NodeId>> backups = getBackups(networkId);
+
+ List<CompletableFuture<MastershipEvent>> eventFutures = new ArrayList<>();
+ Set<DeviceId> toRelinquish = new HashSet<>();
+
+ masterMap.entrySet().stream()
+ .filter(entry -> nodeId.equals(entry.getValue()))
+ .forEach(entry -> toRelinquish.add(entry.getKey()));
+
+ backups.entrySet().stream()
+ .filter(entry -> entry.getValue().contains(nodeId))
+ .forEach(entry -> toRelinquish.add(entry.getKey()));
+
+ toRelinquish.forEach(deviceId -> eventFutures.add(
+ relinquishRole(networkId, nodeId, deviceId)));
+
+ eventFutures.forEach(future -> {
+ future.whenComplete((event, error) -> notifyDelegate(networkId, event));
+ });
+ }
+
+ /**
+ * Increase the term for a device, and store it.
+ *
+ * @param networkId a virtual network identifier
+ * @param deviceId a virtual device identifier
+ */
+ private synchronized void incrementTerm(NetworkId networkId, DeviceId deviceId) {
+ Map<DeviceId, AtomicInteger> termMap = getTermMap(networkId);
+
+ AtomicInteger term = termMap.getOrDefault(deviceId, new AtomicInteger(NOTHING));
+ term.incrementAndGet();
+ termMap.put(deviceId, term);
+ }
+
+ /**
+ * Remove backup node for a device.
+ *
+ * @param networkId a virtual network identifier
+ * @param deviceId a virtual device identifier
+ * @param nodeId a node identifier
+ * @return True if success
+ */
+ private synchronized boolean removeFromBackups(NetworkId networkId,
+ DeviceId deviceId, NodeId nodeId) {
+ Map<DeviceId, List<NodeId>> backups = getBackups(networkId);
+
+ List<NodeId> stbys = backups.getOrDefault(deviceId, new ArrayList<>());
+ boolean modified = stbys.remove(nodeId);
+ backups.put(deviceId, stbys);
+ return modified;
+ }
+
+ /**
+ * add to backup if not there already, silently ignores null node.
+ *
+ * @param networkId a virtual network identifier
+ * @param deviceId a virtual device identifier
+ * @param nodeId a node identifier
+ * @return True if success
+ */
+ private synchronized boolean addToBackup(NetworkId networkId,
+ DeviceId deviceId, NodeId nodeId) {
+ Map<DeviceId, List<NodeId>> backups = getBackups(networkId);
+
+ boolean modified = false;
+ List<NodeId> stbys = backups.getOrDefault(deviceId, new ArrayList<>());
+ if (nodeId != null && !stbys.contains(nodeId)) {
+ stbys.add(nodeId);
+ backups.put(deviceId, stbys);
+ modified = true;
+ }
+ return modified;
+ }
+
+ /**
+ * Returns deviceId-master map for a specified virtual network.
+ *
+ * @param networkId a virtual network identifier
+ * @return DeviceId-master map of a given virtual network.
+ */
+ private Map<DeviceId, NodeId> getMasterMap(NetworkId networkId) {
+ return masterMapByNetwork.computeIfAbsent(networkId, k -> new HashMap<>());
+ }
+
+ /**
+ * Returns deviceId-backups map for a specified virtual network.
+ *
+ * @param networkId a virtual network identifier
+ * @return DeviceId-backups map of a given virtual network.
+ */
+ private Map<DeviceId, List<NodeId>> getBackups(NetworkId networkId) {
+ return backupsByNetwork.computeIfAbsent(networkId, k -> new HashMap<>());
+ }
+
+ /**
+ * Returns deviceId-terms map for a specified virtual network.
+ *
+ * @param networkId a virtual network identifier
+ * @return DeviceId-terms map of a given virtual network.
+ */
+ private Map<DeviceId, AtomicInteger> getTermMap(NetworkId networkId) {
+ return termMapByNetwork.computeIfAbsent(networkId, k -> new HashMap<>());
+ }
+
+ /**
+ * Returns a fake cluster service for a test purpose only.
+ *
+ * @return a fake cluster service
+ */
+ private ClusterService createFakeClusterService() {
+ // just for ease of unit test
+ final ControllerNode instance =
+ new DefaultControllerNode(new NodeId("local"),
+ IpAddress.valueOf("127.0.0.1"));
+
+ ClusterService faceClusterService = new ClusterService() {
+
+ private final Instant creationTime = Instant.now();
+
+ @Override
+ public ControllerNode getLocalNode() {
+ return instance;
+ }
+
+ @Override
+ public Set<ControllerNode> getNodes() {
+ return ImmutableSet.of(instance);
+ }
+
+ @Override
+ public Set<Node> getConsensusNodes() {
+ return ImmutableSet.of();
+ }
+
+ @Override
+ public ControllerNode getNode(NodeId nodeId) {
+ if (instance.id().equals(nodeId)) {
+ return instance;
+ }
+ return null;
+ }
+
+ @Override
+ public ControllerNode.State getState(NodeId nodeId) {
+ if (instance.id().equals(nodeId)) {
+ return ControllerNode.State.ACTIVE;
+ } else {
+ return ControllerNode.State.INACTIVE;
+ }
+ }
+
+ @Override
+ public Version getVersion(NodeId nodeId) {
+ if (instance.id().equals(nodeId)) {
+ return versionService.version();
+ }
+ return null;
+ }
+
+ @Override
+ public Instant getLastUpdatedInstant(NodeId nodeId) {
+ return creationTime;
+ }
+
+ @Override
+ public void addListener(ClusterEventListener listener) {
+ }
+
+ @Override
+ public void removeListener(ClusterEventListener listener) {
+ }
+ };
+ return faceClusterService;
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualMeterStore.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualMeterStore.java
new file mode 100644
index 0000000..5add89d
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualMeterStore.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl;
+
+import com.google.common.collect.Collections2;
+import com.google.common.collect.Maps;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkMeterStore;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.meter.DefaultMeter;
+import org.onosproject.net.meter.Meter;
+import org.onosproject.net.meter.MeterEvent;
+import org.onosproject.net.meter.MeterFailReason;
+import org.onosproject.net.meter.MeterFeatures;
+import org.onosproject.net.meter.MeterFeaturesKey;
+import org.onosproject.net.meter.MeterKey;
+import org.onosproject.net.meter.MeterOperation;
+import org.onosproject.net.meter.MeterStoreDelegate;
+import org.onosproject.net.meter.MeterStoreResult;
+import org.onosproject.store.service.StorageException;
+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.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import java.util.Collection;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import static org.onosproject.net.meter.MeterFailReason.TIMEOUT;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of the virtual meter store for a single instance.
+ */
+//TODO: support distributed meter store for virtual networks
+@Component(immediate = true, service = VirtualNetworkMeterStore.class)
+public class SimpleVirtualMeterStore
+ extends AbstractVirtualStore<MeterEvent, MeterStoreDelegate>
+ implements VirtualNetworkMeterStore {
+
+ private Logger log = getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected ClusterService clusterService;
+
+ private ConcurrentMap<NetworkId, ConcurrentMap<MeterKey, MeterData>> meterMap =
+ Maps.newConcurrentMap();
+
+ private NodeId local;
+
+ private ConcurrentMap<NetworkId, ConcurrentMap<MeterFeaturesKey, MeterFeatures>>
+ meterFeatureMap = Maps.newConcurrentMap();
+
+ private ConcurrentMap<NetworkId,
+ ConcurrentMap<MeterKey, CompletableFuture<MeterStoreResult>>> futuresMap =
+ Maps.newConcurrentMap();
+
+ @Activate
+ public void activate() {
+ log.info("Started");
+ local = clusterService.getLocalNode().id();
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("Stopped");
+ }
+
+ private ConcurrentMap<MeterKey, MeterData> getMetersByNetwork(NetworkId networkId) {
+ meterMap.computeIfAbsent(networkId, m -> new ConcurrentHashMap<>());
+ return meterMap.get(networkId);
+ }
+
+ private ConcurrentMap<MeterFeaturesKey, MeterFeatures>
+ getMeterFeaturesByNetwork(NetworkId networkId) {
+ meterFeatureMap.computeIfAbsent(networkId, f -> new ConcurrentHashMap<>());
+ return meterFeatureMap.get(networkId);
+ }
+
+ private ConcurrentMap<MeterKey, CompletableFuture<MeterStoreResult>>
+ getFuturesByNetwork(NetworkId networkId) {
+ futuresMap.computeIfAbsent(networkId, f -> new ConcurrentHashMap<>());
+ return futuresMap.get(networkId);
+ }
+
+ @Override
+ public CompletableFuture<MeterStoreResult> storeMeter(NetworkId networkId, Meter meter) {
+
+ ConcurrentMap<MeterKey, MeterData> meters = getMetersByNetwork(networkId);
+
+ ConcurrentMap<MeterKey, CompletableFuture<MeterStoreResult>> futures =
+ getFuturesByNetwork(networkId);
+
+ CompletableFuture<MeterStoreResult> future = new CompletableFuture<>();
+ MeterKey key = MeterKey.key(meter.deviceId(), meter.id());
+ futures.put(key, future);
+ MeterData data = new MeterData(meter, null, local);
+
+ try {
+ meters.put(key, data);
+ } catch (StorageException e) {
+ future.completeExceptionally(e);
+ }
+
+ return future;
+ }
+
+ @Override
+ public CompletableFuture<MeterStoreResult> deleteMeter(NetworkId networkId, Meter meter) {
+ ConcurrentMap<MeterKey, MeterData> meters = getMetersByNetwork(networkId);
+
+ ConcurrentMap<MeterKey, CompletableFuture<MeterStoreResult>> futures =
+ getFuturesByNetwork(networkId);
+
+ CompletableFuture<MeterStoreResult> future = new CompletableFuture<>();
+ MeterKey key = MeterKey.key(meter.deviceId(), meter.id());
+ futures.put(key, future);
+
+ MeterData data = new MeterData(meter, null, local);
+
+ // update the state of the meter. It will be pruned by observing
+ // that it has been removed from the dataplane.
+ try {
+ if (meters.computeIfPresent(key, (k, v) -> data) == null) {
+ future.complete(MeterStoreResult.success());
+ }
+ } catch (StorageException e) {
+ future.completeExceptionally(e);
+ }
+
+ return future;
+ }
+
+ @Override
+ public MeterStoreResult storeMeterFeatures(NetworkId networkId, MeterFeatures meterfeatures) {
+ ConcurrentMap<MeterFeaturesKey, MeterFeatures> meterFeatures
+ = getMeterFeaturesByNetwork(networkId);
+
+ MeterStoreResult result = MeterStoreResult.success();
+ MeterFeaturesKey key = MeterFeaturesKey.key(meterfeatures.deviceId());
+ try {
+ meterFeatures.putIfAbsent(key, meterfeatures);
+ } catch (StorageException e) {
+ result = MeterStoreResult.fail(TIMEOUT);
+ }
+ return result;
+ }
+
+ @Override
+ public MeterStoreResult deleteMeterFeatures(NetworkId networkId, DeviceId deviceId) {
+ ConcurrentMap<MeterFeaturesKey, MeterFeatures> meterFeatures
+ = getMeterFeaturesByNetwork(networkId);
+
+ MeterStoreResult result = MeterStoreResult.success();
+ MeterFeaturesKey key = MeterFeaturesKey.key(deviceId);
+ try {
+ meterFeatures.remove(key);
+ } catch (StorageException e) {
+ result = MeterStoreResult.fail(TIMEOUT);
+ }
+ return result;
+ }
+
+ @Override
+ public CompletableFuture<MeterStoreResult> updateMeter(NetworkId networkId, Meter meter) {
+ ConcurrentMap<MeterKey, MeterData> meters = getMetersByNetwork(networkId);
+ ConcurrentMap<MeterKey, CompletableFuture<MeterStoreResult>> futures =
+ getFuturesByNetwork(networkId);
+
+ CompletableFuture<MeterStoreResult> future = new CompletableFuture<>();
+ MeterKey key = MeterKey.key(meter.deviceId(), meter.id());
+ futures.put(key, future);
+
+ MeterData data = new MeterData(meter, null, local);
+ try {
+ if (meters.computeIfPresent(key, (k, v) -> data) == null) {
+ future.complete(MeterStoreResult.fail(MeterFailReason.INVALID_METER));
+ }
+ } catch (StorageException e) {
+ future.completeExceptionally(e);
+ }
+ return future;
+ }
+
+ @Override
+ public void updateMeterState(NetworkId networkId, Meter meter) {
+ ConcurrentMap<MeterKey, MeterData> meters = getMetersByNetwork(networkId);
+
+ MeterKey key = MeterKey.key(meter.deviceId(), meter.id());
+ meters.computeIfPresent(key, (k, v) -> {
+ DefaultMeter m = (DefaultMeter) v.meter();
+ m.setState(meter.state());
+ m.setProcessedPackets(meter.packetsSeen());
+ m.setProcessedBytes(meter.bytesSeen());
+ m.setLife(meter.life());
+ // TODO: Prune if drops to zero.
+ m.setReferenceCount(meter.referenceCount());
+ return new MeterData(m, null, v.origin());
+ });
+ }
+
+ @Override
+ public Meter getMeter(NetworkId networkId, MeterKey key) {
+ ConcurrentMap<MeterKey, MeterData> meters = getMetersByNetwork(networkId);
+
+ MeterData data = meters.get(key);
+ return data == null ? null : data.meter();
+ }
+
+ @Override
+ public Collection<Meter> getAllMeters(NetworkId networkId) {
+ ConcurrentMap<MeterKey, MeterData> meters = getMetersByNetwork(networkId);
+
+ return Collections2.transform(meters.values(), MeterData::meter);
+ }
+
+ @Override
+ public void failedMeter(NetworkId networkId, MeterOperation op, MeterFailReason reason) {
+ ConcurrentMap<MeterKey, MeterData> meters = getMetersByNetwork(networkId);
+
+ MeterKey key = MeterKey.key(op.meter().deviceId(), op.meter().id());
+ meters.computeIfPresent(key, (k, v) ->
+ new MeterData(v.meter(), reason, v.origin()));
+ }
+
+ @Override
+ public void deleteMeterNow(NetworkId networkId, Meter m) {
+ ConcurrentMap<MeterKey, MeterData> meters = getMetersByNetwork(networkId);
+ ConcurrentMap<MeterKey, CompletableFuture<MeterStoreResult>> futures =
+ getFuturesByNetwork(networkId);
+
+ MeterKey key = MeterKey.key(m.deviceId(), m.id());
+ futures.remove(key);
+ meters.remove(key);
+ }
+
+ @Override
+ public long getMaxMeters(NetworkId networkId, MeterFeaturesKey key) {
+ ConcurrentMap<MeterFeaturesKey, MeterFeatures> meterFeatures
+ = getMeterFeaturesByNetwork(networkId);
+
+ MeterFeatures features = meterFeatures.get(key);
+ return features == null ? 0L : features.maxMeter();
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualPacketStore.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualPacketStore.java
new file mode 100644
index 0000000..e0bb7c6
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualPacketStore.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2016-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.incubator.net.virtual.store.impl;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkPacketStore;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketEvent;
+import org.onosproject.net.packet.PacketRequest;
+import org.onosproject.net.packet.PacketStoreDelegate;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.slf4j.Logger;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Simple single instance implementation of the virtual packet store.
+ */
+//TODO: support distributed packet store for virtual networks
+
+@Component(immediate = true, service = VirtualNetworkPacketStore.class)
+public class SimpleVirtualPacketStore
+ extends AbstractVirtualStore<PacketEvent, PacketStoreDelegate>
+ implements VirtualNetworkPacketStore {
+
+ private final Logger log = getLogger(getClass());
+
+ private Map<NetworkId, Map<TrafficSelector, Set<PacketRequest>>> requests
+ = Maps.newConcurrentMap();
+
+ @Activate
+ public void activate() {
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("Stopped");
+ }
+
+ @Override
+ public void emit(NetworkId networkId, OutboundPacket packet) {
+ notifyDelegate(networkId, new PacketEvent(PacketEvent.Type.EMIT, packet));
+ }
+
+ @Override
+ public void requestPackets(NetworkId networkId, PacketRequest request) {
+ requests.computeIfAbsent(networkId, k -> Maps.newConcurrentMap());
+
+ requests.get(networkId).compute(request.selector(), (s, existingRequests) -> {
+ if (existingRequests == null) {
+ if (hasDelegate(networkId)) {
+ delegateMap.get(networkId).requestPackets(request);
+ }
+ return ImmutableSet.of(request);
+ } else if (!existingRequests.contains(request)) {
+ if (hasDelegate(networkId)) {
+ delegateMap.get(networkId).requestPackets(request);
+ }
+ return ImmutableSet.<PacketRequest>builder()
+ .addAll(existingRequests)
+ .add(request)
+ .build();
+ } else {
+ return existingRequests;
+ }
+ });
+ }
+
+ @Override
+ public void cancelPackets(NetworkId networkId, PacketRequest request) {
+ requests.get(networkId).computeIfPresent(request.selector(), (s, existingRequests) -> {
+ if (existingRequests.contains(request)) {
+ HashSet<PacketRequest> newRequests = Sets.newHashSet(existingRequests);
+ newRequests.remove(request);
+ if (hasDelegate(networkId)) {
+ delegateMap.get(networkId).cancelPackets(request);
+ }
+ if (newRequests.size() > 0) {
+ return ImmutableSet.copyOf(newRequests);
+ } else {
+ return null;
+ }
+ } else {
+ return existingRequests;
+ }
+ });
+ }
+
+ @Override
+ public List<PacketRequest> existingRequests(NetworkId networkId) {
+ List<PacketRequest> list = Lists.newArrayList();
+ if (requests.get(networkId) != null) {
+ requests.get(networkId).values().forEach(list::addAll);
+ list.sort((o1, o2) -> o1.priority().priorityValue() - o2.priority().priorityValue());
+ }
+ return list;
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/package-info.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/package-info.java
new file mode 100644
index 0000000..99b5d82
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015-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.
+ */
+
+/**
+ * Implementation of virtual network stores.
+ */
+package org.onosproject.incubator.net.virtual.store.impl;
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/VirtualDeviceId.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/VirtualDeviceId.java
new file mode 100644
index 0000000..6a38661
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/VirtualDeviceId.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl.primitives;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.DeviceId;
+
+import java.util.Objects;
+
+/**
+ * A wrapper class to isolate device id from other virtual networks.
+ */
+public class VirtualDeviceId {
+
+ NetworkId networkId;
+ DeviceId deviceId;
+
+ public VirtualDeviceId(NetworkId networkId, DeviceId deviceId) {
+ this.networkId = networkId;
+ this.deviceId = deviceId;
+ }
+
+ public NetworkId networkId() {
+ return networkId;
+ }
+
+ public DeviceId deviceId() {
+ return deviceId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(networkId, deviceId);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj instanceof VirtualDeviceId) {
+ VirtualDeviceId that = (VirtualDeviceId) obj;
+ return this.deviceId.equals(that.deviceId) &&
+ this.networkId.equals(that.networkId);
+ }
+ return false;
+ }
+}
+
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/VirtualFlowEntry.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/VirtualFlowEntry.java
new file mode 100644
index 0000000..2a00883
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/VirtualFlowEntry.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl.primitives;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.flow.FlowEntry;
+
+import java.util.Objects;
+
+/**
+ * A wrapper class to encapsulate flow entry.
+ */
+public class VirtualFlowEntry {
+ NetworkId networkId;
+ FlowEntry flowEntry;
+
+ public VirtualFlowEntry(NetworkId networkId, FlowEntry flowEntry) {
+ this.networkId = networkId;
+ this.flowEntry = flowEntry;
+ }
+
+ public NetworkId networkId() {
+ return networkId;
+ }
+
+ public FlowEntry flowEntry() {
+ return flowEntry;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(networkId, flowEntry);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (other instanceof VirtualFlowEntry) {
+ VirtualFlowEntry that = (VirtualFlowEntry) other;
+ return this.networkId.equals(that.networkId) &&
+ this.flowEntry.equals(that.flowEntry);
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/VirtualFlowRule.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/VirtualFlowRule.java
new file mode 100644
index 0000000..ea64904
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/VirtualFlowRule.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl.primitives;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.flow.FlowRule;
+
+import java.util.Objects;
+
+/**
+ * A wrapper class to encapsulate flow rule.
+ */
+public class VirtualFlowRule {
+ NetworkId networkId;
+ FlowRule rule;
+
+ public VirtualFlowRule(NetworkId networkId, FlowRule rule) {
+ this.networkId = networkId;
+ this.rule = rule;
+ }
+
+ public NetworkId networkId() {
+ return networkId;
+ }
+
+ public FlowRule rule() {
+ return rule;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(networkId, rule);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (other instanceof VirtualFlowRule) {
+ VirtualFlowRule that = (VirtualFlowRule) other;
+ return this.networkId.equals(that.networkId) &&
+ this.rule.equals(that.rule);
+ } else {
+ return false;
+ }
+ }
+}
+
+
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/VirtualFlowRuleBatchEvent.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/VirtualFlowRuleBatchEvent.java
new file mode 100644
index 0000000..d1b5d57
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/VirtualFlowRuleBatchEvent.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl.primitives;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEvent;
+
+import java.util.Objects;
+
+/**
+ * A wrapper class to encapsulate flow rule batch event.
+ */
+public class VirtualFlowRuleBatchEvent {
+ NetworkId networkId;
+ FlowRuleBatchEvent event;
+
+ public VirtualFlowRuleBatchEvent(NetworkId networkId, FlowRuleBatchEvent event) {
+ this.networkId = networkId;
+ this.event = event;
+ }
+
+ public NetworkId networkId() {
+ return networkId;
+ }
+
+ public FlowRuleBatchEvent event() {
+ return event;
+ }
+
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(networkId, event);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (other instanceof VirtualFlowRuleBatchEvent) {
+ VirtualFlowRuleBatchEvent that = (VirtualFlowRuleBatchEvent) other;
+ return this.networkId.equals(that.networkId) &&
+ this.event.equals(that.event);
+ } else {
+ return false;
+ }
+ }
+}
+
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/VirtualFlowRuleBatchOperation.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/VirtualFlowRuleBatchOperation.java
new file mode 100644
index 0000000..8db1801
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/VirtualFlowRuleBatchOperation.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl.primitives;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
+
+import java.util.Objects;
+
+/**
+ * A wrapper class to encapsulate flow rule batch operation.
+ */
+public class VirtualFlowRuleBatchOperation {
+ NetworkId networkId;
+ FlowRuleBatchOperation operation;
+
+ public VirtualFlowRuleBatchOperation(NetworkId networkId,
+ FlowRuleBatchOperation operation) {
+ this.networkId = networkId;
+ this.operation = operation;
+ }
+
+ public NetworkId networkId() {
+ return networkId;
+ }
+
+ public FlowRuleBatchOperation operation() {
+ return operation;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(networkId, operation);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (other instanceof VirtualFlowRuleBatchOperation) {
+ VirtualFlowRuleBatchOperation that = (VirtualFlowRuleBatchOperation) other;
+ return this.networkId.equals(that.networkId) &&
+ this.operation.equals(that.operation);
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/package-info.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/package-info.java
new file mode 100644
index 0000000..ee0de54
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/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.
+ */
+
+/**
+ * Implementation of distributed virtual network store primitives.
+ */
+package org.onosproject.incubator.net.virtual.store.impl.primitives;