[ONOS-6844] inject ports via net-cfg

Change-Id: I3052e8b43fd26960b111200d6e506fd91e1f01fd
diff --git a/core/net/src/main/java/org/onosproject/net/config/impl/BasicNetworkConfigs.java b/core/net/src/main/java/org/onosproject/net/config/impl/BasicNetworkConfigs.java
index 630fc79..88b656a 100644
--- a/core/net/src/main/java/org/onosproject/net/config/impl/BasicNetworkConfigs.java
+++ b/core/net/src/main/java/org/onosproject/net/config/impl/BasicNetworkConfigs.java
@@ -24,6 +24,7 @@
 import org.apache.felix.scr.annotations.Service;
 import org.onosproject.core.CoreService;
 import org.onosproject.incubator.net.config.basics.InterfaceConfig;
+import org.onosproject.incubator.net.config.basics.PortDescriptionsConfig;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.HostId;
@@ -61,6 +62,7 @@
 
     private static final String BASIC = "basic";
     private static final String INTERFACES = "interfaces";
+    private static final String PORTS = "ports";
 
     private final Logger log = LoggerFactory.getLogger(getClass());
 
@@ -121,7 +123,16 @@
                 public PortAnnotationConfig createConfig() {
                     return new PortAnnotationConfig();
                 }
+            },
+            new ConfigFactory<DeviceId, PortDescriptionsConfig>(DEVICE_SUBJECT_FACTORY,
+                    PortDescriptionsConfig.class,
+                    PORTS) {
+                @Override
+                public PortDescriptionsConfig createConfig() {
+                    return new PortDescriptionsConfig();
+                }
             }
+
     );
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
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 c0bf128..ebdb973 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
@@ -29,6 +29,7 @@
 import org.onlab.util.Tools;
 import org.onosproject.cluster.ClusterService;
 import org.onosproject.cluster.NodeId;
+import org.onosproject.incubator.net.config.basics.PortDescriptionsConfig;
 import org.onosproject.mastership.MastershipEvent;
 import org.onosproject.mastership.MastershipListener;
 import org.onosproject.mastership.MastershipService;
@@ -451,6 +452,7 @@
                 log.warn("Device {} is not allowed", deviceId);
                 return;
             }
+            PortDescriptionsConfig portConfig = networkConfigService.getConfig(deviceId, PortDescriptionsConfig.class);
             // Generate updated description and establish my Role
             deviceDescription = BasicDeviceOperator.combine(cfg, deviceDescription);
             Futures.getUnchecked(mastershipService.requestRoleFor(deviceId)
@@ -461,11 +463,23 @@
 
             DeviceEvent event = store.createOrUpdateDevice(provider().id(), deviceId,
                                                            deviceDescription);
+            if (portConfig != null) {
+                //updating the ports if configration exists
+                List<PortDescription> complete = store.getPortDescriptions(provider().id(), deviceId)
+                        .collect(Collectors.toList());
+                complete.addAll(portConfig.portDescriptions());
+                List<PortDescription> portDescriptions = complete.stream()
+                        .map(e -> applyAllPortOps(deviceId, e))
+                        .collect(Collectors.toList());
+                store.updatePorts(provider().id(), deviceId, portDescriptions);
+            }
+
             if (deviceDescription.isDefaultAvailable()) {
                 log.info("Device {} connected", deviceId);
             } else {
                 log.info("Device {} registered", deviceId);
             }
+
             if (event != null) {
                 log.trace("event: {} {}", event.type(), event);
                 post(event);
@@ -548,6 +562,11 @@
                 log.trace("Ignoring {} port updates on standby node. {}", deviceId, portDescriptions);
                 return;
             }
+            PortDescriptionsConfig portConfig = networkConfigService.getConfig(deviceId, PortDescriptionsConfig.class);
+            if (portConfig != null) {
+                //updating the ports if configration exists
+                portDescriptions.addAll(portConfig.portDescriptions());
+            }
             portDescriptions = portDescriptions.stream()
                     .map(e -> applyAllPortOps(deviceId, e))
                     .collect(Collectors.toList());
@@ -898,16 +917,17 @@
             return (event.type() == NetworkConfigEvent.Type.CONFIG_ADDED
                     || event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED)
                     && (event.configClass().equals(BasicDeviceConfig.class)
-                    || portOpsIndex.containsKey(event.configClass()));
+                    || portOpsIndex.containsKey(event.configClass())
+                    || event.configClass().equals(PortDescriptionsConfig.class));
         }
 
         @Override
         public void event(NetworkConfigEvent event) {
             DeviceEvent de = null;
+            DeviceId did = (DeviceId) event.subject();
+            DeviceProvider dp = getProvider(did);
             if (event.configClass().equals(BasicDeviceConfig.class)) {
                 log.debug("Detected device network config event {}", event.type());
-                DeviceId did = (DeviceId) event.subject();
-                DeviceProvider dp = getProvider(did);
                 BasicDeviceConfig cfg =
                         networkConfigService.getConfig(did, BasicDeviceConfig.class);
 
@@ -923,10 +943,17 @@
                     }
                 }
             }
+            if (event.configClass().equals(PortDescriptionsConfig.class) && event.config().isPresent()
+                    && getDevice(did) != null && dp != null) {
+                PortDescriptionsConfig portConfig = (PortDescriptionsConfig) event.config().get();
+                    //updating the ports if configration exists
+                    List<PortDescription> complete = store.getPortDescriptions(dp.id(), did)
+                            .collect(Collectors.toList());
+                    complete.addAll(portConfig.portDescriptions());
+                    store.updatePorts(dp.id(), did, complete);
+            }
             if (portOpsIndex.containsKey(event.configClass())) {
                 ConnectPoint cpt = (ConnectPoint) event.subject();
-                DeviceId did = cpt.deviceId();
-                DeviceProvider dp = getProvider(did);
 
                 // Note: assuming PortOperator can modify existing port,
                 //       but cannot add new port purely from Config.
diff --git a/incubator/api/src/main/java/org/onosproject/incubator/net/config/basics/PortDescriptionsConfig.java b/incubator/api/src/main/java/org/onosproject/incubator/net/config/basics/PortDescriptionsConfig.java
new file mode 100644
index 0000000..6c0b1ba
--- /dev/null
+++ b/incubator/api/src/main/java/org/onosproject/incubator/net/config/basics/PortDescriptionsConfig.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2015-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.config.basics;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableList;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.config.Config;
+import org.onosproject.net.device.DefaultPortDescription;
+import org.onosproject.net.device.PortDescription;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Configuration for Ports. Creates a list of PortDescription based on the given Json.
+ */
+@Beta
+public class PortDescriptionsConfig extends Config<DeviceId> {
+    private static Logger log = LoggerFactory.getLogger(PortDescriptionsConfig.class);
+
+    private static final String NUMBER = "number";
+    private static final String NAME = "name";
+    private static final String ENABLED = "enabled";
+    private static final String REMOVED = "removed";
+    private static final String TYPE = "type";
+    private static final String SPEED = "speed";
+    private static final String ANNOTATIONS = "annotations";
+
+    private static final String CONFIG_VALUE_ERROR = "Error parsing config value";
+
+    @Override
+    public boolean isValid() {
+        for (Iterator<Map.Entry<String, JsonNode>> it = node.fields(); it.hasNext();) {
+            JsonNode nodePort = it.next().getValue();
+            if (!hasOnlyFields((ObjectNode) nodePort, NUMBER, NAME, ENABLED, REMOVED, TYPE,
+                    SPEED, ANNOTATIONS)) {
+                return false;
+            }
+            ObjectNode obj = (ObjectNode) nodePort;
+
+            if (!(isNumber(obj, NUMBER, FieldPresence.MANDATORY) &&
+                    isString(obj, NAME, FieldPresence.OPTIONAL) &&
+                    isBoolean(obj, ENABLED, FieldPresence.OPTIONAL) &&
+                    isBoolean(obj, REMOVED, FieldPresence.OPTIONAL) &&
+                    isString(obj, TYPE, FieldPresence.OPTIONAL) &&
+                    isIntegralNumber(obj, SPEED, FieldPresence.OPTIONAL))) {
+                return false;
+            }
+
+            if (node.has(ANNOTATIONS) && !node.get(ANNOTATIONS).isObject()) {
+                log.error("Annotations must be an inner json node");
+                return false;
+            }
+
+        }
+        return true;
+    }
+
+    /**
+     * Retrieves all port descriptions.
+     *
+     * @return set of port descriptions
+     */
+    public List<PortDescription> portDescriptions() {
+
+        try {
+            ImmutableList.Builder<PortDescription> portDescriptions = ImmutableList.builder();
+            for (Iterator<Map.Entry<String, JsonNode>> it = node.fields(); it.hasNext();) {
+                JsonNode portNode = it.next().getValue();
+                long number = portNode.path(NUMBER).asLong();
+
+                String name = portNode.path(NAME).asText(null);
+
+                PortNumber portNumber = createPortNumber(number, name);
+
+                DefaultPortDescription.Builder builder = DefaultPortDescription.builder()
+                        .withPortNumer(portNumber);
+                if (portNode.has(ENABLED)) {
+                    builder.isEnabled(portNode.path(ENABLED).asBoolean());
+                }
+
+                if (portNode.has(REMOVED)) {
+                    builder.isRemoved(portNode.path(REMOVED).asBoolean());
+                }
+
+                if (portNode.has(TYPE)) {
+                    builder.type(Port.Type.valueOf(portNode.path(TYPE).asText().toUpperCase()));
+                }
+
+                if (portNode.has(SPEED)) {
+                    builder.portSpeed(portNode.path(SPEED).asLong());
+                }
+
+                if (portNode.has(ANNOTATIONS)) {
+                    DefaultAnnotations.Builder annotationsBuilder = DefaultAnnotations.builder();
+                    Iterator<Map.Entry<String, JsonNode>> annotationsIt = portNode.get(ANNOTATIONS).fields();
+                    while (it.hasNext()) {
+                        Map.Entry<String, JsonNode> entry = annotationsIt.next();
+                        annotationsBuilder.set(entry.getKey(), entry.getValue().asText());
+                    }
+                    builder.annotations(annotationsBuilder.build());
+                }
+
+                portDescriptions.add(builder.build());
+            }
+
+            return portDescriptions.build();
+
+        } catch (IllegalArgumentException e) {
+            log.error(CONFIG_VALUE_ERROR, e);
+            return ImmutableList.of();
+        }
+    }
+
+    private PortNumber createPortNumber(long number, String name) {
+        if (name == null) {
+            return PortNumber.portNumber(number);
+        }
+        return PortNumber.portNumber(number, name);
+    }
+
+
+}
diff --git a/tools/test/configs/sample-ports.json b/tools/test/configs/sample-ports.json
new file mode 100644
index 0000000..921d938
--- /dev/null
+++ b/tools/test/configs/sample-ports.json
@@ -0,0 +1,31 @@
+{
+  "devices": {
+    "null:0000000000000001": {
+      "basic": {
+        "allowed": true,
+        "owner": "Luigi"
+      },
+      "ports": {
+          "1":{
+            "number": 1,
+            "name": "test",
+            "enabled": true,
+            "removed": false,
+            "type": "copper",
+            "speed": 40000
+          },
+          "2":{
+            "number": 2,
+            "name": "test2",
+            "enabled": true,
+            "removed": false,
+            "type": "copper",
+            "speed": 40000,
+            "annotations":{
+              "testann": "testann"
+            }
+         }
+      }
+    }
+  }
+}
\ No newline at end of file