Add ability to administratively remove ports of an offline device.

Change-Id: Iaee085be1cd53f783ed80e7c277403eb65ef6d8f
diff --git a/cli/src/main/java/org/onosproject/cli/net/DevicePortsRemoveCommand.java b/cli/src/main/java/org/onosproject/cli/net/DevicePortsRemoveCommand.java
new file mode 100644
index 0000000..ec335b0
--- /dev/null
+++ b/cli/src/main/java/org/onosproject/cli/net/DevicePortsRemoveCommand.java
@@ -0,0 +1,48 @@
+/*
+ * 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.cli.net;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceAdminService;
+
+/**
+ * Removes an infrastructure device.
+ */
+@Service
+@Command(scope = "onos", name = "device-ports-remove",
+         description = "Removes ports of an infrastructure device")
+public class DevicePortsRemoveCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "uri", description = "Device ID",
+              required = true, multiValued = false)
+    @Completion(DeviceIdCompleter.class)
+    String uri = null;
+
+    @Override
+    protected void doExecute() {
+        try {
+            get(DeviceAdminService.class).removeDevicePorts(DeviceId.deviceId(uri));
+        } catch (IllegalStateException e) {
+            print("There was some issue in removing device ports, please try again");
+        }
+    }
+
+}
diff --git a/core/api/src/main/java/org/onosproject/net/device/DeviceAdminService.java b/core/api/src/main/java/org/onosproject/net/device/DeviceAdminService.java
index bfd467c..c95e7e6 100644
--- a/core/api/src/main/java/org/onosproject/net/device/DeviceAdminService.java
+++ b/core/api/src/main/java/org/onosproject/net/device/DeviceAdminService.java
@@ -40,4 +40,13 @@
      * @param enable true if port is to be enabled, false to disable
      */
     void changePortState(DeviceId deviceId, PortNumber portNumber, boolean enable);
+
+    /**
+     * Removes the ports of a device with the specified identifier. The device
+     * must be presently unavailable, i.e. offline.
+     *
+     * @param deviceId device identifier
+     */
+    default void removeDevicePorts(DeviceId deviceId) {
+    }
 }
diff --git a/core/net/src/main/java/org/onosproject/net/device/impl/DeviceManager.java b/core/net/src/main/java/org/onosproject/net/device/impl/DeviceManager.java
index e274ae8..be24991 100644
--- a/core/net/src/main/java/org/onosproject/net/device/impl/DeviceManager.java
+++ b/core/net/src/main/java/org/onosproject/net/device/impl/DeviceManager.java
@@ -392,6 +392,24 @@
         }
     }
 
+    @Override
+    public void removeDevicePorts(DeviceId deviceId) {
+        checkNotNull(deviceId, DEVICE_ID_NULL);
+        if (isAvailable(deviceId)) {
+            log.debug("Cannot remove ports of device {} while it is available.", deviceId);
+            return;
+        }
+
+        List<PortDescription> portDescriptions = ImmutableList.of();
+        List<DeviceEvent> events = store.updatePorts(getProvider(deviceId).id(),
+                                                     deviceId, portDescriptions);
+        if (events != null) {
+            for (DeviceEvent event : events) {
+                post(event);
+            }
+        }
+    }
+
     private void handlePortRequest(InternalPortUpDownEvent event) {
         DeviceId deviceId = event.deviceId();
         checkNotNull(deviceId, DEVICE_ID_NULL);
diff --git a/core/net/src/test/java/org/onosproject/net/device/impl/DeviceManagerTest.java b/core/net/src/test/java/org/onosproject/net/device/impl/DeviceManagerTest.java
index 601e6c3..3e1e4d1 100644
--- a/core/net/src/test/java/org/onosproject/net/device/impl/DeviceManagerTest.java
+++ b/core/net/src/test/java/org/onosproject/net/device/impl/DeviceManagerTest.java
@@ -246,7 +246,31 @@
         assertNotNull("device should be found", service.getDevice(DID2));
         assertEquals("incorrect device count", 1, service.getDeviceCount());
         assertEquals("incorrect available device count", 1, service.getAvailableDeviceCount());
+    }
 
+    @Test
+    public void removeDevicePorts() {
+        connectDevice(DID1, SW1);
+        List<PortDescription> pds = new ArrayList<>();
+        pds.add(DefaultPortDescription.builder().withPortNumber(P1).isEnabled(true).build());
+        pds.add(DefaultPortDescription.builder().withPortNumber(P2).isEnabled(true).build());
+        pds.add(DefaultPortDescription.builder().withPortNumber(P3).isEnabled(true).build());
+        providerService.updatePorts(DID1, pds);
+        validateEvents(DEVICE_ADDED, PORT_ADDED, PORT_ADDED, PORT_ADDED);
+
+        // Try removing ports while device is available/connected; it should be a no-op.
+        admin.removeDevicePorts(DID1);
+        assertEquals("wrong port count", 3, service.getPorts(DID1).size());
+
+        // Disconnect device
+        providerService.deviceDisconnected(DID1);
+        assertFalse("device should not be available", service.isAvailable(DID1));
+        validateEvents(DEVICE_AVAILABILITY_CHANGED);
+
+        // Now remove ports for real
+        admin.removeDevicePorts(DID1);
+        validateEvents(PORT_REMOVED, PORT_REMOVED, PORT_REMOVED);
+        assertEquals("wrong port count", 0, service.getPorts(DID1).size());
     }
 
     protected void validateEvents(Enum... types) {