Add optional "name" parameter in interface configuration.

Interfaces can now be added and deleted by name. Interfaces without names
cannot be updated or deleted.

Change-Id: Icb2188b1c9abf3017724f751a93457920a53ba03
diff --git a/cli/src/main/java/org/onosproject/cli/net/InterfaceAddCommand.java b/cli/src/main/java/org/onosproject/cli/net/InterfaceAddCommand.java
index 4fd9b0d..ae4e410 100644
--- a/cli/src/main/java/org/onosproject/cli/net/InterfaceAddCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/net/InterfaceAddCommand.java
@@ -17,6 +17,7 @@
 package org.onosproject.cli.net;
 
 import com.google.common.collect.Sets;
+import org.apache.karaf.shell.commands.Argument;
 import org.apache.karaf.shell.commands.Command;
 import org.apache.karaf.shell.commands.Option;
 import org.onlab.packet.MacAddress;
@@ -36,11 +37,15 @@
         description = "Adds a new configured interface")
 public class InterfaceAddCommand extends AbstractShellCommand {
 
-    @Option(name = "-c", aliases = "--connectPoint",
+    @Argument(index = 0, name = "port",
             description = "Device port that the interface is associated with",
             required = true, multiValued = false)
     private String connectPoint = null;
 
+    @Argument(index = 1, name = "name", description = "Interface name",
+            required = true, multiValued = false)
+    private String name = null;
+
     @Option(name = "-m", aliases = "--mac",
             description = "MAC address of the interface",
             required = false, multiValued = false)
@@ -72,10 +77,13 @@
 
         VlanId vlanId = vlan == null ? VlanId.NONE : VlanId.vlanId(Short.parseShort(vlan));
 
-        Interface intf = new Interface(ConnectPoint.deviceConnectPoint(connectPoint),
+        Interface intf = new Interface(name,
+                ConnectPoint.deviceConnectPoint(connectPoint),
                 ipAddresses, macAddr, vlanId);
 
         interfaceService.add(intf);
+
+        print("Interface added");
     }
 
 }
diff --git a/cli/src/main/java/org/onosproject/cli/net/InterfaceRemoveCommand.java b/cli/src/main/java/org/onosproject/cli/net/InterfaceRemoveCommand.java
index 941a65d..0a7b5a1 100644
--- a/cli/src/main/java/org/onosproject/cli/net/InterfaceRemoveCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/net/InterfaceRemoveCommand.java
@@ -18,7 +18,6 @@
 
 import org.apache.karaf.shell.commands.Argument;
 import org.apache.karaf.shell.commands.Command;
-import org.onlab.packet.VlanId;
 import org.onosproject.cli.AbstractShellCommand;
 import org.onosproject.incubator.net.intf.InterfaceAdminService;
 import org.onosproject.net.ConnectPoint;
@@ -35,17 +34,23 @@
             required = true, multiValued = false)
     private String connectPoint = null;
 
-    @Argument(index = 1, name = "vlan",
-            description = "Interface vlan",
+    @Argument(index = 1, name = "name",
+            description = "Interface name",
             required = true, multiValued = false)
-    private String vlan = null;
+    private String name = null;
 
     @Override
     protected void execute() {
         InterfaceAdminService interfaceService = get(InterfaceAdminService.class);
 
-        interfaceService.remove(ConnectPoint.deviceConnectPoint(connectPoint),
-                VlanId.vlanId(Short.parseShort(vlan)));
+        boolean success = interfaceService.remove(
+                ConnectPoint.deviceConnectPoint(connectPoint), name);
+
+        if (success) {
+            print("Interface removed");
+        } else {
+            print("Unable to remove interface");
+        }
     }
 
 }
diff --git a/cli/src/main/java/org/onosproject/cli/net/InterfacesListCommand.java b/cli/src/main/java/org/onosproject/cli/net/InterfacesListCommand.java
index aa93eb9..bac3aed 100644
--- a/cli/src/main/java/org/onosproject/cli/net/InterfacesListCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/net/InterfacesListCommand.java
@@ -35,6 +35,9 @@
     private static final String FORMAT =
             "port=%s/%s, ips=%s, mac=%s, vlan=%s";
 
+    private static final String NAME_FORMAT =
+            "%s: port=%s/%s, ips=%s, mac=%s, vlan=%s";
+
     @Override
     protected void execute() {
         InterfaceService interfaceService = get(InterfaceService.class);
@@ -44,8 +47,14 @@
         Collections.sort(interfaces, Comparators.INTERFACES_COMPARATOR);
 
         for (Interface intf : interfaces) {
-            print(FORMAT, intf.connectPoint().deviceId(), intf.connectPoint().port(),
-                    intf.ipAddresses(), intf.mac(), intf.vlan());
+            if (intf.name().equals(Interface.NO_INTERFACE_NAME)) {
+                print(FORMAT, intf.connectPoint().deviceId(), intf.connectPoint().port(),
+                        intf.ipAddresses(), intf.mac(), intf.vlan());
+            } else {
+                print(NAME_FORMAT, intf.name(), intf.connectPoint().deviceId(),
+                        intf.connectPoint().port(), intf.ipAddresses(),
+                        intf.mac(), intf.vlan());
+            }
         }
     }
 
diff --git a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
index 1f350c5..b50c8bf 100644
--- a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -358,15 +358,16 @@
         </command>
         <command>
             <action class="org.onosproject.cli.net.InterfaceAddCommand"/>
-            <optional-completers>
-                <entry key="-c" value-ref="connectPointCompleter"/>
-                <entry key="--connectPoint" value-ref="connectPointCompleter"/>
-            </optional-completers>
+            <completers>
+                <ref component-id="connectPointCompleter" />
+                <ref component-id="placeholderCompleter" />
+            </completers>
         </command>
         <command>
             <action class="org.onosproject.cli.net.InterfaceRemoveCommand"/>
             <completers>
                 <ref component-id="connectPointCompleter"/>
+                <ref component-id="placeholderCompleter" />
             </completers>
         </command>
         <command>
@@ -505,4 +506,6 @@
     <bean id="upDownCompleter" class="org.onosproject.cli.UpDownCompleter"/>
     <bean id="encapTypeCompleter" class="org.onosproject.cli.net.EncapTypeCompleter"/>
 
+    <bean id="placeholderCompleter" class="org.onosproject.cli.PlaceholderCompleter"/>
+
 </blueprint>
diff --git a/incubator/api/src/main/java/org/onosproject/incubator/net/config/basics/InterfaceConfig.java b/incubator/api/src/main/java/org/onosproject/incubator/net/config/basics/InterfaceConfig.java
index 9f2d410..5246f31 100644
--- a/incubator/api/src/main/java/org/onosproject/incubator/net/config/basics/InterfaceConfig.java
+++ b/incubator/api/src/main/java/org/onosproject/incubator/net/config/basics/InterfaceConfig.java
@@ -28,6 +28,7 @@
 import org.onosproject.net.config.Config;
 import org.onosproject.net.host.InterfaceIpAddress;
 
+import java.util.Iterator;
 import java.util.Set;
 
 /**
@@ -35,6 +36,7 @@
  */
 @Beta
 public class InterfaceConfig extends Config<ConnectPoint> {
+    public static final String NAME = "name";
     public static final String IPS = "ips";
     public static final String MAC = "mac";
     public static final String VLAN = "vlan";
@@ -52,6 +54,8 @@
 
         try {
             for (JsonNode intfNode : array) {
+                String name = intfNode.path(NAME).asText(null);
+
                 Set<InterfaceIpAddress> ips = getIps(intfNode);
 
                 String mac = intfNode.path(MAC).asText();
@@ -59,7 +63,7 @@
 
                 VlanId vlan = getVlan(intfNode);
 
-                interfaces.add(new Interface(subject, ips, macAddr, vlan));
+                interfaces.add(new Interface(name, subject, ips, macAddr, vlan));
             }
         } catch (IllegalArgumentException e) {
             throw new ConfigException(CONFIG_VALUE_ERROR, e);
@@ -76,6 +80,8 @@
     public void addInterface(Interface intf) {
         ObjectNode intfNode = array.addObject();
 
+        intfNode.put(NAME, intf.name());
+
         if (intf.mac() != null) {
             intfNode.put(MAC, intf.mac().toString());
         }
@@ -92,12 +98,14 @@
     /**
      * Removes an interface from the config.
      *
-     * @param intf interface to remove
+     * @param name name of the interface to remove
      */
-    public void removeInterface(Interface intf) {
-        for (int i = 0; i < array.size(); i++) {
-            if (intf.vlan().equals(getVlan(node))) {
-                array.remove(i);
+    public void removeInterface(String name) {
+        Iterator<JsonNode> it = array.iterator();
+        while (it.hasNext()) {
+            JsonNode node = it.next();
+            if (node.path(NAME).asText().equals(name)) {
+                it.remove();
                 break;
             }
         }
diff --git a/incubator/api/src/main/java/org/onosproject/incubator/net/intf/Interface.java b/incubator/api/src/main/java/org/onosproject/incubator/net/intf/Interface.java
index b9d3ead..e210968 100644
--- a/incubator/api/src/main/java/org/onosproject/incubator/net/intf/Interface.java
+++ b/incubator/api/src/main/java/org/onosproject/incubator/net/intf/Interface.java
@@ -30,11 +30,13 @@
 
 /**
  * An Interface maps network configuration information (such as addresses and
- * vlans) to a port in the network. This is considered a L2/L3 network
- * interface.
+ * vlans) to a port in the network.
  */
 @Beta
 public class Interface {
+    public static final String NO_INTERFACE_NAME = "";
+
+    private final String name;
     private final ConnectPoint connectPoint;
     private final Set<InterfaceIpAddress> ipAddresses;
     private final MacAddress macAddress;
@@ -43,6 +45,25 @@
     /**
      * Creates new Interface with the provided configuration.
      *
+     * @param name name of the interface
+     * @param connectPoint the connect point this interface maps to
+     * @param ipAddresses Set of IP addresses
+     * @param macAddress MAC address
+     * @param vlan VLAN ID
+     */
+    public Interface(String name, ConnectPoint connectPoint,
+                     Set<InterfaceIpAddress> ipAddresses,
+                     MacAddress macAddress, VlanId vlan) {
+        this.name = name == null ? NO_INTERFACE_NAME : name;
+        this.connectPoint = checkNotNull(connectPoint);
+        this.ipAddresses = ipAddresses == null ? Sets.newHashSet() : ipAddresses;
+        this.macAddress = macAddress == null ? MacAddress.NONE : macAddress;
+        this.vlan = vlan == null ? VlanId.NONE : vlan;
+    }
+
+    /**
+     * Creates new Interface with the provided configuration.
+     *
      * @param connectPoint the connect point this interface maps to
      * @param ipAddresses Set of IP addresses
      * @param macAddress MAC address
@@ -51,10 +72,16 @@
     public Interface(ConnectPoint connectPoint,
                      Set<InterfaceIpAddress> ipAddresses,
                      MacAddress macAddress, VlanId vlan) {
-        this.connectPoint = checkNotNull(connectPoint);
-        this.ipAddresses = ipAddresses == null ? Sets.newHashSet() : ipAddresses;
-        this.macAddress = macAddress == null ? MacAddress.NONE : macAddress;
-        this.vlan = vlan == null ? VlanId.NONE : vlan;
+        this(NO_INTERFACE_NAME, connectPoint, ipAddresses, macAddress, vlan);
+    }
+
+    /**
+     * Retrieves the name of the interface.
+     *
+     * @return name
+     */
+    public String name() {
+        return name;
     }
 
     /**
@@ -101,7 +128,8 @@
 
         Interface otherInterface = (Interface) other;
 
-        return Objects.equals(connectPoint, otherInterface.connectPoint) &&
+        return Objects.equals(name, otherInterface.name) &&
+                Objects.equals(connectPoint, otherInterface.connectPoint) &&
                 Objects.equals(ipAddresses, otherInterface.ipAddresses) &&
                 Objects.equals(macAddress, otherInterface.macAddress) &&
                 Objects.equals(vlan, otherInterface.vlan);
@@ -109,12 +137,13 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(connectPoint, ipAddresses, macAddress, vlan);
+        return Objects.hash(connectPoint, name, ipAddresses, macAddress, vlan);
     }
 
     @Override
     public String toString() {
         return MoreObjects.toStringHelper(getClass())
+                .add("name", name)
                 .add("connectPoint", connectPoint)
                 .add("ipAddresses", ipAddresses)
                 .add("macAddress", macAddress)
diff --git a/incubator/api/src/main/java/org/onosproject/incubator/net/intf/InterfaceAdminService.java b/incubator/api/src/main/java/org/onosproject/incubator/net/intf/InterfaceAdminService.java
index 56d5aec..32d480d 100644
--- a/incubator/api/src/main/java/org/onosproject/incubator/net/intf/InterfaceAdminService.java
+++ b/incubator/api/src/main/java/org/onosproject/incubator/net/intf/InterfaceAdminService.java
@@ -16,13 +16,13 @@
 
 package org.onosproject.incubator.net.intf;
 
-import org.onlab.packet.VlanId;
 import org.onosproject.net.ConnectPoint;
 
 /**
  * Provides a means to modify the interfaces configuration.
  */
 public interface InterfaceAdminService {
+
     /**
      * Adds a new interface configuration to the system.
      *
@@ -34,7 +34,7 @@
      * Removes an interface configuration from the system.
      *
      * @param connectPoint connect point of the interface
-     * @param vlanId vlan id
+     * @param name name of the interface
      */
-    void remove(ConnectPoint connectPoint, VlanId vlanId);
+    boolean remove(ConnectPoint connectPoint, String name);
 }
diff --git a/incubator/net/src/main/java/org/onosproject/incubator/net/intf/impl/InterfaceManager.java b/incubator/net/src/main/java/org/onosproject/incubator/net/intf/impl/InterfaceManager.java
index 0439d03..bbd96de 100644
--- a/incubator/net/src/main/java/org/onosproject/incubator/net/intf/impl/InterfaceManager.java
+++ b/incubator/net/src/main/java/org/onosproject/incubator/net/intf/impl/InterfaceManager.java
@@ -18,6 +18,7 @@
 
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -39,9 +40,11 @@
 import org.slf4j.LoggerFactory;
 
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import static java.util.stream.Collectors.collectingAndThen;
 import static java.util.stream.Collectors.toSet;
@@ -145,7 +148,7 @@
 
     private void updateInterfaces(InterfaceConfig intfConfig) {
         try {
-            interfaces.put(intfConfig.subject(), intfConfig.getInterfaces());
+            interfaces.put(intfConfig.subject(), Sets.newHashSet(intfConfig.getInterfaces()));
         } catch (ConfigException e) {
             log.error("Error in interface config", e);
         }
@@ -157,18 +160,7 @@
 
     @Override
     public void add(Interface intf) {
-        if (interfaces.containsKey(intf.connectPoint())) {
-            boolean conflict = interfaces.get(intf.connectPoint()).stream()
-                    .filter(i -> i.connectPoint().equals(intf.connectPoint()))
-                    .filter(i -> i.mac().equals(intf.mac()))
-                    .filter(i -> i.vlan().equals(intf.vlan()))
-                    .findAny().isPresent();
-
-            if (conflict) {
-                log.error("Can't add interface because it conflicts with existing config");
-                return;
-            }
-        }
+        addInternal(intf);
 
         InterfaceConfig config =
                 configService.addConfig(intf.connectPoint(), CONFIG_CLASS);
@@ -178,29 +170,72 @@
         configService.applyConfig(intf.connectPoint(), CONFIG_CLASS, config.node());
     }
 
+    private void addInternal(Interface intf) {
+        interfaces.compute(intf.connectPoint(), (cp, current) -> {
+            if (current == null) {
+                return Sets.newHashSet(intf);
+            }
+
+            Iterator<Interface> it = current.iterator();
+            while (it.hasNext()) {
+                Interface i = it.next();
+                if (i.name().equals(intf.name())) {
+                    it.remove();
+                    break;
+                }
+            }
+
+            current.add(intf);
+            return current;
+        });
+    }
+
     @Override
-    public void remove(ConnectPoint connectPoint, VlanId vlanId) {
-        Optional<Interface> intf = interfaces.get(connectPoint).stream()
-                .filter(i -> i.vlan().equals(vlanId))
-                .findAny();
+    public boolean remove(ConnectPoint connectPoint, String name) {
+        boolean success = removeInternal(name, connectPoint);
 
-        if (!intf.isPresent()) {
-            log.error("Can't find interface {}/{} to remove", connectPoint, vlanId);
-            return;
-        }
-
-        InterfaceConfig config = configService.addConfig(intf.get().connectPoint(), CONFIG_CLASS);
-        config.removeInterface(intf.get());
+        InterfaceConfig config = configService.addConfig(connectPoint, CONFIG_CLASS);
+        config.removeInterface(name);
 
         try {
             if (config.getInterfaces().isEmpty()) {
                 configService.removeConfig(connectPoint, CONFIG_CLASS);
             } else {
-                configService.applyConfig(intf.get().connectPoint(), CONFIG_CLASS, config.node());
+                configService.applyConfig(connectPoint, CONFIG_CLASS, config.node());
             }
         } catch (ConfigException e) {
             log.error("Error reading interfaces JSON", e);
         }
+
+        return success;
+    }
+
+    public boolean removeInternal(String name, ConnectPoint connectPoint) {
+        AtomicBoolean removed = new AtomicBoolean(false);
+
+        interfaces.compute(connectPoint, (cp, current) -> {
+            if (current == null) {
+                return null;
+            }
+
+            Iterator<Interface> it = current.iterator();
+            while (it.hasNext()) {
+                Interface i = it.next();
+                if (i.name().equals(name)) {
+                    it.remove();
+                    removed.set(true);
+                    break;
+                }
+            }
+
+            if (current.isEmpty()) {
+                return null;
+            } else {
+                return current;
+            }
+        });
+
+        return removed.get();
     }
 
     /**