Added response data to operational datastore, refactored data validation 56/29656/1
authorRyan Vail <r.vail@cablelabs.com>
Thu, 29 Oct 2015 16:26:47 +0000 (10:26 -0600)
committerRyan Vail <r.vail@cablelabs.com>
Fri, 13 Nov 2015 15:23:43 +0000 (08:23 -0700)
Update yang file to have response data in operation datastore
Response data for ccap and qos/gate data added.
Validation code refactored into several classes to make maintenance and testing easier (Tests not yet written)
Extended MdsalUtils with read, put methods
Created AbstractDataChangeListener to help with new incoming data
Divided DataChangeListiing into two classes one for packetcable:ccaps and one for packetcable:qos
Added a few new items to the postman collection with operational DS fetches and bad puts for testing

Change-Id: I4054bbbf893476327fc39610a47bfa564024610a
Signed-off-by: Ryan Vail <r.vail@cablelabs.com>
40 files changed:
packetcable-driver/pom.xml
packetcable-policy-model/pom.xml
packetcable-policy-model/src/main/yang/packetcable.yang
packetcable-policy-server/doc/restconf-samples/ODL-PCMM.json.postman_collection
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/AbstractDataChangeListener.java [new file with mode: 0644]
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/DataChangeUtils.java [new file with mode: 0644]
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/MdsalUtils.java
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/PCMMGateReqBuilder.java
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/PCMMService.java
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/PacketcableProvider.java
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/Subnet.java
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/ValidateInstanceData.java [deleted file]
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/DataValidator.java [new file with mode: 0644]
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/ValidationException.java [new file with mode: 0644]
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/Validator.java [new file with mode: 0644]
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/ValidatorProvider.java [new file with mode: 0644]
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/ValidatorProviderFactory.java [new file with mode: 0644]
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/CcapsValidatorProviderFactory.java [new file with mode: 0644]
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/QosValidatorProviderFactory.java [new file with mode: 0644]
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/ValidatorProviderFactoryImpl.java [new file with mode: 0644]
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/ValidatorProviderImpl.java [new file with mode: 0644]
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/AbstractValidator.java [new file with mode: 0644]
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/ccaps/AmIdValidator.java [new file with mode: 0644]
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/ccaps/CcapValidator.java [new file with mode: 0644]
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/ccaps/CcapsValidator.java [new file with mode: 0644]
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/ccaps/ConnectionValidator.java [new file with mode: 0644]
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/AppValidator.java [new file with mode: 0644]
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/AppsValidator.java [new file with mode: 0644]
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/GateSpecValidatator.java [new file with mode: 0644]
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/GateValidator.java [new file with mode: 0644]
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/GatesValidator.java [new file with mode: 0644]
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/SubscriberValidator.java [new file with mode: 0644]
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/SubscribersValidator.java [new file with mode: 0644]
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/TrafficProfileValidator.java [new file with mode: 0644]
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/classifier/ClassifierValidator.java [new file with mode: 0644]
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/classifier/ExtClassifierValidator.java [new file with mode: 0644]
packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/classifier/Ipv6ClassifierValidator.java [new file with mode: 0644]
packetcable-policy-server/src/test/java/org/opendaylight/controller/packetcable/provider/PCMMServiceTest.java
packetcable-policy-server/src/test/java/org/opendaylight/controller/packetcable/provider/PacketcableProviderTest.java
packetcable-policy-server/src/test/java/org/opendaylight/controller/packetcable/provider/SubnetTest.java

index d7bf8acb9be51d52552bc15636fdbbb0b885b22d..d6a102494ef67e1887887d0385f7d14222ccf794 100644 (file)
@@ -7,6 +7,7 @@
                <groupId>org.opendaylight.packetcable</groupId>
                <artifactId>packetcable</artifactId>
                <version>1.3.0-SNAPSHOT</version>
+               <relativePath>..</relativePath>
        </parent>
        <artifactId>packetcable-driver</artifactId>
        <packaging>bundle</packaging>
index 03c9b58460b4af8856ab418b0f457b855e172f45..e1d2154594d4c411c40c7a537f9ea6bd33af0d1a 100644 (file)
             <groupId>org.opendaylight.mdsal</groupId>
             <artifactId>yang-binding</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.opendaylight.mdsal.model</groupId>
+            <artifactId>yang-ext</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.opendaylight.yangtools</groupId>
             <artifactId>yang-common</artifactId>
index d9562f491c8cdca8ef9393b774bf36486badb806..22be5d52b97a97a113bc1884250cb8125fc169c1 100644 (file)
@@ -3,12 +3,13 @@ module packetcable
     namespace "urn:packetcable";
     prefix "pcmm";
 
-    import ietf-yang-types     { prefix yang; }
-    import ietf-inet-types     { prefix inet; }
+    import ietf-yang-types     { prefix yang;  revision-date "2010-09-24"; }
+    import ietf-inet-types     { prefix inet; revision-date "2010-09-24"; }
+    import yang-ext { prefix ext; revision-date "2013-07-09"; }
 
     description "This module contains the PCMM Converged Cable Access Platform (CCAP) definitions";
     organization "OpenDaylight Project";
-       
+
     revision 2015-10-26 {
        description "Corrected pluralization of containers/lists and added containers around lists where needed";
     }
@@ -44,6 +45,13 @@ module packetcable
                description "TOS/TC byte or mask";
         }
 
+       identity ccap-context {
+               description "Identity used to mark ccap context";
+       }
+
+       identity app-context {
+               description "Identity used to mark app context";
+       }
 
        // CCAP devices
        container ccaps {
@@ -54,14 +62,58 @@ module packetcable
                                and a list of available Service Class Names.
                                ";
                    key "ccapId";
+                   ext:context-instance "ccap-context";
                        leaf ccapId {
                            type string;
-                               description "CCAP Identity";
-                           }
+                           description "CCAP Identity";
+                           mandatory true;
+                       }
                    uses ccap-attributes;
            }
     }
 
+    grouping ccap-connection {
+       leaf ipAddress {
+               type inet:ip-address;
+               description "IP Address of CCAP";
+               mandatory true;
+               }
+               leaf port {
+               type inet:port-number;
+               description "COPS session TCP port number";
+               default 3918;
+        }
+               leaf connected {
+                       config false;
+                       type boolean;
+                       description "COPS session state";
+                       mandatory true;
+               }
+               leaf-list error {
+                       config false;
+                       type string;
+                       description "Operational errors";
+               }
+//             leaf idle-detect {
+//                     type uint8;
+//                     description "COPS connection idle timer (seconds)";
+//          mandatory true;
+//             }
+//             leaf isIdle {
+//                     config false;
+//                     type boolean;
+//                     description "COPS connection idle state";
+//          mandatory true;
+//             }
+
+               leaf timestamp {
+               config false;
+               type yang:date-and-time;
+               description "Last update timestamp";
+               mandatory true;
+        }
+    }
+
        grouping ccap-attributes {
                description "
                        Each CCAP device has a COPS connection address:port,
@@ -69,24 +121,18 @@ module packetcable
                        a list of available Service Class Names.
                        ";
                container connection {
-                       leaf ipAddress {
-                       type inet:ip-address;
-                       description "IP Address of CCAP";
-               }
-               leaf port {
-                       type inet:port-number;
-                       description "COPS session TCP port number";
-                       default 3918;
-               }
+                       uses ccap-connection;
         }
         container amId {
                leaf am-tag {
                        type uint16;
                        description "Application Manager Tag -- unique for this operator";
+                       mandatory true;
                }
                leaf am-type {
                        type uint16;
                        description "Application Manager Type -- unique for this AM tag";
+                       mandatory true;
                }
         }
                leaf-list subscriber-subnets {
@@ -98,6 +144,11 @@ module packetcable
                leaf-list downstream-scns {
                        type service-class-name;
                }
+               leaf-list error {
+                       config false;
+                       type string;
+                       description "ccap data errors";
+               }
        }
 
        // PCMM QoS Gates
@@ -123,6 +174,7 @@ module packetcable
        container apps {
                list app {
                    key "appId";
+                   ext:context-instance "app-context";
                            leaf appId {
                                type string;
                                description "Application Identity";
@@ -132,7 +184,8 @@ module packetcable
                                key "subscriberId";
                                            leaf subscriberId {
                                            type string;
-                                               description "Subscriber Identity -- must be a CM or CPE IP address";
+                                                   description "Subscriber Identity -- must be a CM or CPE IP address";
+                                                   mandatory true;
                                            }
                                            container gates {
                                            list gate {
@@ -140,7 +193,9 @@ module packetcable
                                                            leaf gateId {
                                                                type string;
                                                                description "Qos Gate Identity";
+                                                               mandatory true;
                                                            }
+                                                           uses gate-operational-attributes;
                                                            uses pcmm-qos-gate-attributes;
                                            }
                                            }
@@ -150,6 +205,44 @@ module packetcable
        }
     }
 
+    grouping gate-operational-attributes {
+               leaf gatePath {
+                       config false;
+                   type string;
+                   description "FQ Gate path app/subscriber/gate";
+                   mandatory true;
+               }
+               leaf ccapId {
+                       config false;
+                   type string;
+                   description "CCAP Identity";
+                   mandatory true;
+               }
+               leaf cops-state {
+                       config false;
+                       type string;
+                       description "Gate operational COPS state";
+                       mandatory true;
+               }
+               leaf cops-gateId {
+                       config false;
+                       type string;
+                       description "Gate operational COPS Id";
+                       mandatory true;
+               }
+               leaf-list error {
+                       config false;
+                       type string;
+                       description "Gate operational error";
+               }
+               leaf timestamp {
+               config false;
+               type yang:date-and-time;
+               description "Gate operational attributes timestamp";
+               mandatory true;
+        }
+    }
+
     grouping pcmm-qos-gate-attributes {
        uses pcmm-qos-gate-spec;
        uses pcmm-qos-traffic-profile;
@@ -180,6 +273,7 @@ module packetcable
                    leaf service-class-name {
                        type service-class-name;
                        description "The Service Class Name (SCN). This SCN must be pre-provisioned on the target CCAP";
+                       mandatory true;
                    }
                }
     }
@@ -188,18 +282,22 @@ module packetcable
         leaf srcPort-start {
             type inet:port-number;
             description "TCP/UDP source port range start.";
+            mandatory true;
         }
         leaf srcPort-end {
             type inet:port-number;
             description "TCP/UDP source port range end.";
+            mandatory true;
         }
         leaf dstPort-start {
             type inet:port-number;
             description "TCP/UDP destination port range start.";
+            mandatory true;
         }
         leaf dstPort-end {
             type inet:port-number;
             description "TCP/UDP destination port range end.";
+            mandatory true;
         }
     }
 
@@ -208,30 +306,37 @@ module packetcable
                leaf srcIp {
                        type inet:ipv4-address;
                        description "Source IPv4 address (exact match)";
+                       mandatory true;
                        }
                leaf dstIp {
                        type inet:ipv4-address;
                        description "Destination IPv4 address (exact match)";
+                       mandatory true;
                        }
                        leaf tos-byte {
                                type tos-byte;
                                description "TOS/DSCP match";
+                               mandatory true;
                        }
                        leaf tos-mask {
                                type tos-byte;
                                description "TOS/DSCP mask";
+                               mandatory true;
                        }
                leaf protocol {
                        type tp-protocol;
                        description "IPv4 transport protocol";
+                       mandatory true;
                        }
                        leaf srcPort {
                        type inet:port-number;
                        description "TCP/UDP source port (exact match).";
+                       mandatory true;
                        }
                        leaf dstPort {
                        type inet:port-number;
                        description "TCP/UDP destination port (exact match).";
+                       mandatory true;
                        }
                }
     }
@@ -241,30 +346,37 @@ module packetcable
                leaf srcIp {
                        type inet:ipv4-address;
                        description "Source IPv4 address match";
+                       mandatory true;
                        }
                leaf srcIpMask {
                        type inet:ipv4-address;
                        description "Source IPv4 mask";
+                       mandatory true;
                        }
                leaf dstIp {
                        type inet:ipv4-address;
                        description "Destination IPv4 address match";
+                       mandatory true;
                        }
                leaf dstIpMask {
                        type inet:ipv4-address;
                        description "Destination IPv4 mask";
+                       mandatory true;
                        }
                        leaf tos-byte {
                                type tos-byte;
                                description "TOS/DSCP match";
+                               mandatory true;
                        }
                        leaf tos-mask {
                                type tos-byte;
                                description "TOS/DSCP mask";
+                               mandatory true;
                        }
                leaf protocol {
                        type tp-protocol;
                        description "IPv4 transport protocol";
+                       mandatory true;
                        }
                        uses tp-port-match-ranges;
                }
@@ -274,33 +386,40 @@ module packetcable
        container ipv6-classifier {
                leaf srcIp6 {
                        type inet:ipv6-prefix;
-                       description "Source IPv6 prefix match in  <address/len> notation";
+                       description "Source IPv6 prefix match in  'address/len' notation";
+                       mandatory true;
                        }
                leaf dstIp6 {
                        type inet:ipv6-prefix;
-                       description "Destination IPv6 prefix match in <address/len> notation";
+                       description "Destination IPv6 prefix match in 'address/len' notation";
+                       mandatory true;
                        }
                        leaf tc-low {
                                type tos-byte;
                                description "TC low range match";
+                               mandatory true;
                        }
                        leaf tc-high {
                                type tos-byte;
                                description "TC high range match";
+                               mandatory true;
                        }
                        leaf tc-mask {
                                type tos-byte;
                                description "TC mask";
+                               mandatory true;
                        }
                leaf next-hdr {
                        type tp-protocol;
                        description "IPv6 Next Header";
+                       mandatory true;
                        }
                        leaf flow-label {
                                type uint32 {
                                        range "0 .. 1048575";
                                }
                                description "IPv6 Flow Label (20 bits)";
+                               mandatory true;
                        }
                        uses tp-port-match-ranges;
                }
index 98b9d7e1650114588cf93cf39ca6e8a6e6c37d21..3b934c8198ac640ce7feff56d1fd3b0b2bc82732 100644 (file)
@@ -9,14 +9,14 @@
                        "name": "CCAP/CMTS",
                        "description": "Sample PUT, GET, & DELETE of CMTS into ODL",
                        "order": [
+                               "1f68f1dc-1d2b-8d04-b7be-0e6fc03fd90a",
                                "44ba6dfe-ca0c-376a-0322-b3db2af6eb2f",
                                "0c1e6f25-e33b-e3ed-fc59-02f2b569a9ed",
                                "a817933b-9398-23d1-b269-68f81ba51bc7",
                                "f403ebfd-d7d7-c94e-0ee5-3bc864098250",
                                "672adfee-68a5-5281-d638-72c2964b35cc",
                                "1142817c-9007-f0a1-92cd-644935c1fa85",
-                               "07f16f27-6ef1-b022-1921-02a3baf40d91",
-                               "1f68f1dc-1d2b-8d04-b7be-0e6fc03fd90a"
+                               "07f16f27-6ef1-b022-1921-02a3baf40d91"
                        ],
                        "owner": 0
                },
@@ -25,6 +25,9 @@
                        "name": "Gates",
                        "description": "Sample PUT, GET, DELETE for PCMM Gates",
                        "order": [
+                               "89d6f693-4b15-44dc-d330-f191ac6780b7",
+                               "b8031ee0-b6e0-dcbe-fff3-a924cbe295a4",
+                               "e69fb800-4f72-819b-2875-83eecf65dfb1",
                                "0f01c73b-c17a-b53b-3694-1d746edcd2a2",
                                "11ef539e-26d3-77ab-3828-321c3f7bf6a3",
                                "03bebe64-27ff-1df3-5b6b-9237f6c8e229",
                                "ec1b0f8c-371d-a0bd-ce5f-d30aa31237cb",
                                "dc932ee9-1acd-9efe-cc83-de07894516bb",
                                "9d80f0da-4500-e843-31c5-808c91d9249b",
-                               "b8031ee0-b6e0-dcbe-fff3-a924cbe295a4"
+                               "7b027a58-c3a7-dd6a-555f-e5bc3644286c",
+                               "e59184fc-4b70-2eb6-eaf5-001fc7f62fbc"
                        ],
                        "owner": 0,
-                       "collectionId": "ea685ec8-aa6d-22e9-e501-b608a6705634"
+                       "collectionId": "5c57b040-54e7-d5bf-296d-d5a4afdd39bc"
                }
        ],
        "timestamp": 1431957020452,
                        "tests": "",
                        "currentHelper": "normal",
                        "helperAttributes": {},
-                       "time": 1445618651730,
+                       "time": 1446221273089,
                        "name": "Add CCAP 1 - Bad",
                        "description": "tries to add a CCAP/CMTS to manage with out providing all the required fields",
                        "collectionId": "5c57b040-54e7-d5bf-296d-d5a4afdd39bc",
                        "responses": [],
-                       "rawModeData": "{\n    \"ccap\": [{\n        \"ccapId\": \"{{ccapId-1}}\",\n        \"amId\": {\n            \"am-type\": \"1\"\n        },\n        \"connection\": {\n            \"ipAddress\": \"{{ccapIp-1}}\",\n            \"port\": \"{{ccapPort-1}}\"\n        },\n        \"subscriber-subnets\": [\n            \"10.32.110.1/24\"\n        ],\n        \"downstream-scns\": [\n            \"extrm_dn\"\n        ],\n        \"upstream-scns\": [\n            \"extrm_up\"\n        ]\n    }]\n}\n"
+                       "rawModeData": "{\n    \"ccap\": [{\n        \"ccapId\": \"{{ccapId-1}}\",\n        \"amId\": {\n        },\n        \"connection\": {\n            \"ipAddress\": \"{{ccapIp-1}}\",\n            \"port\": \"{{ccapPort-1}}\"\n        },\n        \"subscriber-subnets\": [\n            \"10.32.110.1/24\"\n        ],\n        \"downstream-scns\": [\n            \"extrm_dn\"\n        ],\n        \"upstream-scns\": [\n            \"extrm_up\"\n        ]\n    }]\n}\n"
                },
                {
                        "id": "0c1e6f25-e33b-e3ed-fc59-02f2b569a9ed",
                        "collectionId": "5c57b040-54e7-d5bf-296d-d5a4afdd39bc",
                        "responses": []
                },
+               {
+                       "id": "7b027a58-c3a7-dd6a-555f-e5bc3644286c",
+                       "headers": "Authorization: Basic YWRtaW46YWRtaW4=\nContent-Type: application/json\n",
+                       "url": "http://{{odlHost}}:{{odlPort}}/restconf/config/packetcable:qos/apps/app/{{appId-classifier}}/subscribers/subscriber/{{subId-classifier}}/gates/gate/{{gateId-classifier}}/",
+                       "preRequestScript": "",
+                       "pathVariables": {},
+                       "method": "PUT",
+                       "data": [],
+                       "dataMode": "raw",
+                       "version": 2,
+                       "tests": "",
+                       "currentHelper": "normal",
+                       "helperAttributes": {},
+                       "time": 1447349054220,
+                       "name": "Bad - Gate w/ incomplete classifier",
+                       "description": "PUT gate with a standard classifier that is missing some data",
+                       "collectionId": "5c57b040-54e7-d5bf-296d-d5a4afdd39bc",
+                       "responses": [],
+                       "rawModeData": "{\n    \"gate\": [{\n        \"gateId\": \"{{gateId-classifier}}\",\n        \"gate-spec\": {\n            \"dscp-tos-overwrite\": \"0xa0\",\n            \"dscp-tos-mask\": \"0xff\"\n        },\n        \"traffic-profile\": {\n        },\n        \"classifier\": {\n            \"srcIp\": \"{{srcIp-1a}}\",\n            \"dstIp\": \"{{dstIp-1a}}\",\n            \"srcPort\": \"{{srcPort-1a}}\",\n            \"dstPort\": \"{{dstPort-1a}}\",\n            \"tos-byte\": \"0xa0\",\n            \"tos-mask\": \"0xe0\"\n        }\n    }]\n}\n"
+               },
+               {
+                       "id": "89d6f693-4b15-44dc-d330-f191ac6780b7",
+                       "headers": "Authorization: Basic YWRtaW46YWRtaW4=\nContent-Type: application/json\n",
+                       "url": "http://{{odlHost}}:{{odlPort}}/restconf/operational/packetcable:qos/",
+                       "preRequestScript": "",
+                       "pathVariables": {},
+                       "method": "GET",
+                       "data": [],
+                       "dataMode": "params",
+                       "version": 2,
+                       "tests": "",
+                       "currentHelper": "normal",
+                       "helperAttributes": {},
+                       "time": 1447349051431,
+                       "name": "Operational - All Gates",
+                       "description": "Retrieves all gates.",
+                       "collectionId": "5c57b040-54e7-d5bf-296d-d5a4afdd39bc",
+                       "responses": []
+               },
                {
                        "id": "9d80f0da-4500-e843-31c5-808c91d9249b",
                        "headers": "Authorization: Basic YWRtaW46YWRtaW4=\nContent-Type: application/json\n",
                        "collectionId": "5c57b040-54e7-d5bf-296d-d5a4afdd39bc",
                        "responses": []
                },
+               {
+                       "id": "e59184fc-4b70-2eb6-eaf5-001fc7f62fbc",
+                       "headers": "Authorization: Basic YWRtaW46YWRtaW4=\nContent-Type: application/json\n",
+                       "url": "http://{{odlHost}}:{{odlPort}}/restconf/config/packetcable:qos/apps/app/{{appId-classifier}}/subscribers/subscriber/1.2.3.4/gates/gate/{{gateId-classifier}}",
+                       "preRequestScript": "",
+                       "pathVariables": {},
+                       "method": "PUT",
+                       "data": [],
+                       "dataMode": "raw",
+                       "version": 2,
+                       "tests": "",
+                       "currentHelper": "normal",
+                       "helperAttributes": {},
+                       "time": 1447425110789,
+                       "name": "Bad - Gate w/ invalid subscriber",
+                       "description": "PUT gate with a standard classifier that formed\ncorectly but the subscriber is unknown to the CCAP.",
+                       "collectionId": "5c57b040-54e7-d5bf-296d-d5a4afdd39bc",
+                       "responses": [],
+                       "rawModeData": "{\n    \"gate\": [{\n        \"gateId\": \"{{gateId-classifier}}\",\n        \"gate-spec\": {\n            \"dscp-tos-overwrite\": \"0xa0\",\n            \"dscp-tos-mask\": \"0xff\"\n        },\n        \"traffic-profile\": {\n            \"service-class-name\": \"{{scnUp}}\"\n        },\n        \"classifier\": {\n            \"srcIp\": \"{{srcIp-1a}}\",\n            \"dstIp\": \"{{dstIp-1a}}\",\n            \"protocol\": \"0\",\n            \"srcPort\": \"{{srcPort-1a}}\",\n            \"dstPort\": \"{{dstPort-1a}}\",\n            \"tos-byte\": \"0xa0\",\n            \"tos-mask\": \"0xe0\"\n        }\n    }]\n}\n"
+               },
+               {
+                       "id": "e69fb800-4f72-819b-2875-83eecf65dfb1",
+                       "headers": "Authorization: Basic YWRtaW46YWRtaW4=\nContent-Type: application/json\n",
+                       "url": "http://{{odlHost}}:{{odlPort}}/restconf/config/packetcable:qos/",
+                       "preRequestScript": "",
+                       "pathVariables": {},
+                       "method": "DELETE",
+                       "data": [],
+                       "dataMode": "params",
+                       "version": 2,
+                       "tests": "",
+                       "currentHelper": "normal",
+                       "helperAttributes": {},
+                       "time": 1447359288221,
+                       "name": "All Gates",
+                       "description": "Deletes all apps, subscribers, and gates.",
+                       "collectionId": "5c57b040-54e7-d5bf-296d-d5a4afdd39bc",
+                       "responses": []
+               },
                {
                        "id": "ec1b0f8c-371d-a0bd-ce5f-d30aa31237cb",
                        "headers": "Authorization: Basic YWRtaW46YWRtaW4=\nContent-Type: application/json\n",
diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/AbstractDataChangeListener.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/AbstractDataChangeListener.java
new file mode 100644 (file)
index 0000000..b099471
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.packetcable.provider;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.opendaylight.controller.packetcable.provider.DataChangeUtils.collectTypeFromMap;
+import static org.opendaylight.controller.packetcable.provider.DataChangeUtils.collectTypeFromSet;
+import static org.opendaylight.controller.packetcable.provider.DataChangeUtils.logChange;
+import static org.opendaylight.controller.packetcable.provider.DataChangeUtils.relativeComplement;
+
+import com.google.common.collect.Maps;
+import java.util.Map;
+import java.util.Set;
+import org.opendaylight.controller.md.sal.binding.api.DataChangeListener;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author rvail
+ */
+public abstract class AbstractDataChangeListener<T extends DataObject> implements DataChangeListener {
+
+    protected final Logger logger = LoggerFactory.getLogger(getClass());
+
+    private final Class<T> tClass;
+
+    public AbstractDataChangeListener(Class<T> tClass) {
+        this.tClass = checkNotNull(tClass);
+    }
+
+    @Override
+    public void onDataChanged(final AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> asyncDataChangeEvent) {
+        logger.debug("############{}.onDataChanged()", getClass().getSimpleName());
+        logChange(logger, asyncDataChangeEvent);
+
+        final Map<InstanceIdentifier<?>, DataObject> allCreatedData = asyncDataChangeEvent.getCreatedData();
+        final Map<InstanceIdentifier<?>, DataObject> allOriginalData = asyncDataChangeEvent.getOriginalData();
+        final Map<InstanceIdentifier<?>, DataObject> allUpdatedData = asyncDataChangeEvent.getUpdatedData();
+
+        // UpdatedData also contains all data that was created, remove it to get the set of only updated data
+        final Map<InstanceIdentifier<?>, DataObject> trueUpdatedData =
+                relativeComplement(allCreatedData, allUpdatedData);
+        final Map<InstanceIdentifier<?>, DataObject> trueOriginalData =
+                relativeComplement(allCreatedData, allOriginalData);
+
+        if (!allCreatedData.isEmpty()) {
+            final Map<InstanceIdentifier<T>, T> createdTs = collectTypeFromMap(tClass, allCreatedData);
+
+            if (createdTs.isEmpty()) {
+                // this should not happen since this object only listens for changes in one tree
+                logger.warn("Expected created {}(s) but none were found: {}", tClass.getSimpleName(), allCreatedData);
+            }
+            else {
+                handleCreatedData(createdTs);
+            }
+        }
+
+        if (!trueUpdatedData.isEmpty()) {
+            final Map<InstanceIdentifier<T>, T> updatedTs = collectTypeFromMap(tClass, trueUpdatedData);
+            if (updatedTs.isEmpty()) {
+                // this should not happen since this object should only listen for changes in its tree
+                logger.warn("Expected updated {}(s) but none were found: {}", tClass.getSimpleName(), trueUpdatedData);
+            }
+            else {
+
+                final Map<InstanceIdentifier<T>, T> originalTs = collectTypeFromMap(tClass, trueOriginalData);
+                for (InstanceIdentifier<T> iid : updatedTs.keySet()) {
+                    if (!originalTs.containsKey(iid)) {
+                        logger.warn("No original data for updated object {}", iid);
+                    }
+                }
+
+                handleUpdatedData(updatedTs, originalTs);
+            }
+        }
+
+        final Set<InstanceIdentifier<?>> allRemovedPaths = asyncDataChangeEvent.getRemovedPaths();
+        if (!allRemovedPaths.isEmpty()) {
+            final Set<InstanceIdentifier<T>> removedTPaths = collectTypeFromSet(tClass, allRemovedPaths);
+            if (removedTPaths.isEmpty()) {
+                // this should not happen since this object should only listen for changes in its tree
+                logger.warn("Expected removed {} but none were found: {}", tClass.getSimpleName(), allRemovedPaths);
+            }
+
+            Map<InstanceIdentifier<T>, T> originalTData = Maps.newHashMapWithExpectedSize(removedTPaths.size());
+            for (InstanceIdentifier<T> iid : removedTPaths) {
+                if (allOriginalData.containsKey(iid)) {
+
+                    originalTData.put(iid, (T) allOriginalData.get(iid));
+                }
+            }
+
+            handleRemovedData(removedTPaths, originalTData);
+        }
+    }
+
+    protected abstract void handleCreatedData(final Map<InstanceIdentifier<T>, T> createdData);
+
+    protected abstract void handleUpdatedData(final Map<InstanceIdentifier<T>, T> updatedData,
+            final Map<InstanceIdentifier<T>, T> originalData);
+
+    protected abstract void handleRemovedData(final Set<InstanceIdentifier<T>> removedPaths,
+            final Map<InstanceIdentifier<T>, T> originalData);
+
+
+
+}
diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/DataChangeUtils.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/DataChangeUtils.java
new file mode 100644 (file)
index 0000000..ceac675
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.packetcable.provider;
+
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import java.util.Map;
+import java.util.Set;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.slf4j.Logger;
+
+/**
+ * @author rvail
+ */
+public class DataChangeUtils {
+
+    @SuppressWarnings("unchecked")
+    public static <T extends DataObject> Map<InstanceIdentifier<T>, T> collectTypeFromMap(Class<T> tClass,
+            Map<InstanceIdentifier<?>, DataObject> map) {
+        Map<InstanceIdentifier<T>, T> result = Maps.newHashMap();
+
+        for (Map.Entry<InstanceIdentifier<?>, DataObject> entry : map.entrySet()) {
+            if (tClass.isAssignableFrom(entry.getValue().getImplementedInterface())) {
+                final InstanceIdentifier<T> tIID = (InstanceIdentifier<T>) entry.getKey();
+                final T tObj = (T) entry.getValue();
+                result.put(tIID, tObj);
+            }
+        }
+
+        return result;
+    }
+
+    public static <T extends DataObject> Set<InstanceIdentifier<T>> collectTypeFromSet(Class<T> tClass,
+            Set<InstanceIdentifier<?>> set) {
+        Set<InstanceIdentifier<T>> result = Sets.newHashSet();
+
+        for (InstanceIdentifier<?> iid : set) {
+            if (tClass.isAssignableFrom(iid.getTargetType())) {
+                @SuppressWarnings("unchecked")
+                final InstanceIdentifier<T> tIID = (InstanceIdentifier<T>) iid;
+                result.add(tIID);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Computes the relative complement of A in B. (aka get everything in B that is not in A)
+     * @param setA The first set
+     * @param setB The second set
+     * @return the relative complement
+     */
+    public static Map<InstanceIdentifier<?>, DataObject> relativeComplement(Map<InstanceIdentifier<?>, DataObject> setA,
+            Map<InstanceIdentifier<?>, DataObject> setB){
+
+        Map<InstanceIdentifier<?>, DataObject> result = Maps.newHashMap();
+
+        for (InstanceIdentifier<?> iid: setB.keySet()) {
+            if (!setA.containsKey(iid)) {
+                result.put(iid, setB.get(iid));
+            }
+        }
+
+        return result;
+    }
+
+
+
+    public static void logChange(final Logger logger, final AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> change) {
+        if (!logger.isDebugEnabled()) {
+            return;
+        }
+
+        StringBuilder sb = new StringBuilder();
+
+        appendMap(sb, "CREATED", change.getCreatedData());
+        appendSet(sb, "REMOVED", change.getRemovedPaths());
+        appendMap(sb, "UPDATED", change.getUpdatedData());
+        appendMap(sb, "ORIGINAL", change.getOriginalData());
+
+        logger.debug("onDataChanged data:\n{}", sb.toString());
+    }
+
+
+    private static <E> void appendSet(StringBuilder sb, String name, Set<E> set) {
+        sb.append("====").append(name).append("====\n");
+        if (set == null || set.isEmpty()) {
+            sb.append("None\n");
+        } else {
+            for (E data : set) {
+                sb.append(data).append("\n");
+                sb.append("----------\n");
+            }
+        }
+        sb.append("===============\n");
+    }
+
+    private static void appendMap(StringBuilder sb, String name, Map<InstanceIdentifier<?>, DataObject> dataMap) {
+        sb.append("====").append(name).append("====\n");
+        if (dataMap == null || dataMap.isEmpty()) {
+            sb.append("None\n");
+        } else {
+            for (Map.Entry<InstanceIdentifier<?>, DataObject> entry : dataMap.entrySet()) {
+                sb.append(entry.getValue()).append("\n");
+                sb.append("----------\n");
+            }
+        }
+        sb.append("===============\n");
+    }
+}
index 8f32d0d9fc8acf9884c4c36e8dda7f0112b03a96..04739ade6e6a40ea9560a21dd7554ef04a489b31 100644 (file)
@@ -8,16 +8,19 @@
 
 package org.opendaylight.controller.packetcable.provider;
 
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.CheckedFuture;
 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.util.concurrent.CheckedFuture;
-
 public class MdsalUtils {
     private static final Logger LOG = LoggerFactory.getLogger(MdsalUtils.class);
     private DataBroker databroker = null;
@@ -25,7 +28,7 @@ public class MdsalUtils {
     /**
      * Class constructor setting the data broker.
      *
-     * @param dataBroker the {@link org.opendaylight.controller.md.sal.binding.api.DataBroker}
+     * @param dataBroker the {@link DataBroker}
      */
     public MdsalUtils(DataBroker dataBroker) {
         this.databroker = dataBroker;
@@ -39,7 +42,7 @@ public class MdsalUtils {
      * @param <D> the data object type
      * @return the result of the request
      */
-    public <D extends org.opendaylight.yangtools.yang.binding.DataObject> boolean delete(
+    public <D extends DataObject> boolean delete(
             final LogicalDatastoreType store, final InstanceIdentifier<D> path)  {
         boolean result = false;
         final WriteTransaction transaction = databroker.newWriteOnlyTransaction();
@@ -62,7 +65,7 @@ public class MdsalUtils {
      * @param <D> the data object type
      * @return the result of the request
      */
-    public <D extends org.opendaylight.yangtools.yang.binding.DataObject> boolean merge(
+    public <D extends DataObject> boolean merge(
             final LogicalDatastoreType logicalDatastoreType, final InstanceIdentifier<D> path, D data)  {
         boolean result = false;
         final WriteTransaction transaction = databroker.newWriteOnlyTransaction();
@@ -76,4 +79,49 @@ public class MdsalUtils {
         }
         return result;
     }
+
+    /**
+     * Execures a read as a blocking transaction.
+     *
+     * @param logicalDatastoreType which datastore to read from
+     * @param path The path to read
+     * @param <D> the DataObject type
+     * @return an Optional containing the object.
+     */
+    public <D extends DataObject> Optional<D> read(final LogicalDatastoreType logicalDatastoreType, final InstanceIdentifier<D> path) {
+        final ReadOnlyTransaction readOnlyTransaction = databroker.newReadOnlyTransaction();
+
+        CheckedFuture<Optional<D>, ReadFailedException> future = readOnlyTransaction.read(logicalDatastoreType, path);
+
+        try {
+            return future.checkedGet();
+        } catch (ReadFailedException e) {
+            LOG.error("Failed to read {} at path {}", logicalDatastoreType, path, e);
+        }
+
+        return Optional.absent();
+    }
+
+    /**
+     * Executes put as a blocking transaction.
+     *
+     * @param logicalDatastoreType {@link LogicalDatastoreType} which should be modified
+     * @param path {@link InstanceIdentifier} for path to read
+     * @param <D> the data object type
+     * @return the result of the request
+     */
+    public <D extends DataObject> boolean put(
+            final LogicalDatastoreType logicalDatastoreType, final InstanceIdentifier<D> path, D data)  {
+        boolean result = false;
+        final WriteTransaction transaction = databroker.newWriteOnlyTransaction();
+        transaction.put(logicalDatastoreType, path, data, true);
+        CheckedFuture<Void, TransactionCommitFailedException> future = transaction.submit();
+        try {
+            future.checkedGet();
+            result = true;
+        } catch (TransactionCommitFailedException e) {
+            LOG.warn("Failed to merge {} ", path, e);
+        }
+        return result;
+    }
 }
index a06702d0640ac1cc8be741dc47fc9ee1a013db7e..83a0380d707ece7b4fe17ac65f28500403e1d680 100644 (file)
@@ -1,8 +1,17 @@
-/**
- * Build PCMM gate requests from API QoS Gate objects
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
  */
+
 package org.opendaylight.controller.packetcable.provider;
 
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ServiceFlowDirection;
 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.TosByte;
 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ccap.attributes.AmId;
@@ -17,21 +26,22 @@ import org.pcmm.gates.IExtendedClassifier.ActivationState;
 import org.pcmm.gates.IGateSpec.Direction;
 import org.pcmm.gates.IIPv6Classifier.FlowLabel;
 import org.pcmm.gates.ITrafficProfile;
-import org.pcmm.gates.impl.*;
+import org.pcmm.gates.impl.AMID;
+import org.pcmm.gates.impl.DOCSISServiceClassNameTrafficProfile;
+import org.pcmm.gates.impl.GateID;
+import org.pcmm.gates.impl.PCMMError;
+import org.pcmm.gates.impl.PCMMGateReq;
+import org.pcmm.gates.impl.SubscriberID;
+import org.pcmm.gates.impl.TransactionID;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-
 /**
- * PacketCable data processor
+ * Build PCMM gate requests from API QoS Gate objects
  */
 public class PCMMGateReqBuilder {
 
-    private Logger logger = LoggerFactory.getLogger(PCMMGateReqBuilder.class);
+    private final Logger logger = LoggerFactory.getLogger(PCMMGateReqBuilder.class);
 
     private GateID gateID = null;
     private AMID amid = null;
index 09f481b95f772e73e22219c869b77e0ee4bd9c03..7e31a9cc4332288a064dbe1fa319ec7437801e75 100644 (file)
@@ -1,7 +1,10 @@
 /*
- * (c) 2015 Cable Television Laboratories, Inc.  All rights reserved.
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
  */
-
 package org.opendaylight.controller.packetcable.provider;
 
 import com.google.common.collect.Maps;
@@ -29,239 +32,296 @@ import org.umu.cops.stack.COPSError.ErrorTypes;
  */
 @ThreadSafe
 public class PCMMService {
-       private Logger logger = LoggerFactory.getLogger(PCMMService.class);
-
-       private final Ccap ccap;
-       private final IpAddress ipAddr;
-       private final PortNumber portNum;
-       protected final CcapClient ccapClient;
-       protected Map<String, PCMMGateReq> gateRequests = Maps.newConcurrentMap();
-
-       private final short clientType;
-
-       public PCMMService(final short clientType, final Ccap ccap) {
-               this.clientType = clientType;
-               this.ccap = ccap;
-               ipAddr = ccap.getConnection().getIpAddress();
-               portNum = ccap.getConnection().getPort();
-               ccapClient = new CcapClient(ipAddr, portNum);
-               logger.info("Attempting to add CCAP with ID {} @ {}:{}", ccap.getCcapId(), ipAddr.getIpv4Address().getValue(),
-                               portNum.getValue());
-       }
-
-       public void disconect() {
-               ccapClient.disconnect();
-       }
-
-       // TODO - try and change the return to something other than a String to be parsed to determine success
-       public String addCcap() {
-               ccapClient.connect();
-               if (ccapClient.isConnected) {
-                       logger.info("Connected to CCAP with ID - " + ccap.getCcapId());
-                       return String.format("200 OK - CCAP %s connected @ %s:%d", ccap.getCcapId(),
-                                       ipAddr.getIpv4Address().getValue(), portNum.getValue());
-               } else {
-                       return String.format("404 Not Found - CCAP %s failed to connect @ %s:%d - %s",
-                                       ccap.getCcapId(),
-                                       ipAddr.getIpv4Address().getValue(), portNum.getValue(), ccapClient.errMessage);
-               }
-       }
-
-       // TODO - Consider creating an object to return that contains a success flag, message, and gate ID or gate object
-       public String sendGateSet(final String gatePathStr, final InetAddress subId, final Gate qosGate,
-                                                         final ServiceFlowDirection scnDir) {
-               logger.info("Sending gate to CCAP with ID - " + ccap.getCcapId());
-               // assemble the gate request for this subId
-               final PCMMGateReqBuilder gateBuilder = new PCMMGateReqBuilder();
-               gateBuilder.setAmId(ccap.getAmId());
-               gateBuilder.setSubscriberId(subId);
-               // force gateSpec.Direction to align with SCN direction
-               final ServiceClassName scn = qosGate.getTrafficProfile().getServiceClassName();
-               if (scn != null) {
-                       gateBuilder.setGateSpec(qosGate.getGateSpec(), scnDir);
-               } else {
-                       // not an SCN gate
-                       gateBuilder.setGateSpec(qosGate.getGateSpec(), null);
-               }
-               gateBuilder.setTrafficProfile(qosGate.getTrafficProfile());
-
-               // pick a classifier type (only one for now)
-               if (qosGate.getClassifier() != null) {
-                       gateBuilder.setClassifier(qosGate.getClassifier());
-               } else if (qosGate.getExtClassifier() != null) {
-                       gateBuilder.setExtClassifier(qosGate.getExtClassifier());
-               } else if (qosGate.getIpv6Classifier() != null) {
-                       gateBuilder.setIpv6Classifier(qosGate.getIpv6Classifier());
-               }
-               // assemble the final gate request
-               final PCMMGateReq gateReq = gateBuilder.build();
-
-               if (gateRequests.get(gatePathStr) == null) {
-                       // and remember it
-                       gateRequests.put(gatePathStr, gateReq);
-                       // and send it to the CCAP
-                       ccapClient.sendGateSet(gateReq);
-                       // and wait for the COPS response to complete processing gate request
-                       try {
-                               // TODO - see PCMMPdpReqStateMan#processReport() gate.notify(). Should determine a better means to
-                               // TODO - handle this synchronization.
-                               // TODO - if not changing this, may want to make this timeout configurable
-                               synchronized(gateReq) {
-                                       logger.info("Waiting 5000ms for gate request to be updated");
-                                       gateReq.wait(5000);
-                                       logger.debug("Gate request error - " + gateReq.getError());
-                                       logger.debug("Gate request ID - " + gateReq.getGateID());
-                               }
-                       } catch (Exception e) {
-                               logger.error("PCMMService: sendGateSet(): gate response timeout exceeded for "
-                                               + gatePathStr + '/' + gateReq, e);
-                               return String.format("408 Request Timeout - gate response timeout exceeded for %s/%s",
-                                               ccap.getCcapId(), gatePathStr);
-                       }
-                       if (gateReq.getError() != null) {
-                               logger.error("PCMMService: sendGateSet(): returned error: {}",
-                                               gateReq.getError().toString());
-                               return String.format("404 Not Found - sendGateSet for %s/%s returned error - %s",
-                                               ccap.getCcapId(), gatePathStr, gateReq.getError().toString());
-                       } else {
-                               if (gateReq.getGateID() != null) {
-                                       logger.info(String.format("PCMMService: sendGateSet(): returned GateId %08x: ",
-                                                       gateReq.getGateID().getGateID()));
-                                       return String.format("200 OK - sendGateSet for %s/%s returned GateId %08x",
-                                                       ccap.getCcapId(), gatePathStr, gateReq.getGateID().getGateID());
-                               } else {
-                                       logger.info("PCMMService: sendGateSet(): no gateId returned:");
-                                       return String.format("404 Not Found - sendGateSet for %s/%s no gateId returned",
-                                                       ccap.getCcapId(), gatePathStr);
-                               }
-                       }
-               } else {
-                       logger.info("PCMMService: sendGateSet(): no gateId returned:");
-                       return String.format("404 Not Found - sendGateSet for %s/%s already exists",
-                                       ccap.getCcapId(), gatePathStr);
-               }
-       }
-
-       public Boolean sendGateDelete(final String gatePathStr) {
-               logger.info("sendGateDelete() - " + ccap);
-               // recover the original gate request
-               final PCMMGateReq gateReq = gateRequests.remove(gatePathStr);
-               if (gateReq != null) {
-                       ccapClient.sendGateDelete(gateReq);
-                       // and wait for the response to complete
-                       try {
-                               // TODO - see PCMMPdpReqStateMan#processReport() gate.notify(). Should determine a better means to
-                               // TODO - handle this synchronization.
-                               synchronized(gateReq) {
-                                       gateReq.wait(1000);
-                               }
-                       } catch (InterruptedException e) {
-                               logger.error("PCMMService: sendGateDelete(): gate response timeout exceeded for {}/{}",
-                                               gatePathStr, gateReq);
-                       }
-                       if (gateReq.getError() != null) {
-                               logger.warn("PCMMService: sendGateDelete(): returned error: {}", gateReq.getError().toString());
-                               return false;
-                       } else {
-                               if (gateReq.getGateID() != null) {
-                                       logger.info(String.format("PCMMService: sendGateDelete(): deleted GateId %08x: ", gateReq.getGateID().getGateID()));
-                               } else {
-                                       logger.error("PCMMService: sendGateDelete(): deleted but no gateId returned");
-                               }
-                               return true;
-                       }
-               } else {
-                       logger.warn("Attempt to delete non-existent gate with path - " + gatePathStr);
-                       return false;
-               }
-       }
-
-       /**
-        * Used to interface with a CCAP (including CMTSs)
-        */
-       protected class CcapClient {
-               public final PCMMPdpDataProcess pcmmProcess;
-               public final PCMMPdpAgent pcmmPdp;
-
-               private final String ipv4;
-               private final Integer port;
-
-               // Needs to be initialized in connect() method else would be final
-               protected transient PCMMPdpMsgSender pcmmSender;
-
-               private transient Boolean isConnected = false;
-               private transient String errMessage = null;
-
-               /**
-                * Constructor
-                * @param ccapIp - the IP of the CCAP to manage
-                * @param portNum - the port number of the CCAP to manage
-                */
-               public CcapClient(final IpAddress ccapIp, final PortNumber portNum) {
-                       ipv4 = ccapIp.getIpv4Address().getValue();
-                       if (portNum != null)  port = portNum.getValue();
-                       else port = PCMMPdpAgent.WELL_KNOWN_PDP_PORT;
-                       // TODO FIXME - if this object is not null, gate processing will not work correctly
-                       // TODO see - PCMMPdpReqStateMan#processReport() where the report type is success and the process is null
-                       //            pcmmProcess = new PCMMPdpDataProcess();
-                       pcmmProcess = null;
-                       pcmmPdp = new PCMMPdpAgent(ipv4, port, clientType, pcmmProcess);
-               }
-
-               /**
-                * Starts the connection to the CCAP
-                */
-               public void connect( ) {
-                       logger.info("Attempting to connect to host: " + ipv4 + " port: " + port);
-                       try  {
-                               pcmmPdp.connect();
-
-                               // Cannot instantiate until after pcmmPdp.connect() is called as this is where the client handle is created
-                               pcmmSender = new PCMMPdpMsgSender(clientType, pcmmPdp.getClientHandle(), pcmmPdp.getSocket());
-
-                               isConnected = true;
-                       } catch (Exception e) {
-                               isConnected = false;
-                               logger.error("Failed to connect to host: " + ipv4 + " port: " + port, e);
-                               errMessage = e.getMessage();
-                       }
-               }
-
-               public void disconnect() {
-                       logger.info("CcapClient: disconnect(): {}:{}", ipv4, port);
-                       pcmmPdp.disconnect(new COPSError(ErrorTypes.SHUTTING_DOWN, ErrorTypes.NA));
-                       isConnected = false;
-               }
-
-               // TODO - consider returning a new PCMMGateReq object or a future here instead of setting the ID on the old
-               // TODO - request by reference which makes the code more convoluted thus making issues more difficult to track down.
-               public Boolean sendGateSet(final PCMMGateReq gateReq) {
-                       logger.info("CcapClient: sendGateSet(): {}:{} => {}", ipv4, port, gateReq);
-                       try {
-                               pcmmSender.sendGateSet(gateReq);
-
-                               // TODO - determine if this is the correct place to perform this operation as this currently is the
-                               // TODO - place where the gate ID can be set on the gateReq object
-                               //                pcmmSender.handleGateReport(pcmmPdp.getSocket());
-                       } catch (COPSPdpException e) {
-                               logger.error("CcapClient: sendGateSet(): {}:{} => {} FAILED:", ipv4, port, gateReq, e);
-                       }
-                       // and save it back to the gateRequest object for gate delete later
-                       gateReq.setGateID(pcmmSender.getGateID());
-
-                       // TODO - determine why this method is always returning true???
-                       return true;
-               }
-
-               public Boolean sendGateDelete(final PCMMGateReq gateReq) {
-                       logger.info("CcapClient: sendGateDelete(): {}:{} => {}", ipv4, port, gateReq);
-                       try {
-                               pcmmSender.sendGateDelete(gateReq);
-                       } catch (COPSPdpException e) {
-                               logger.error("CcapClient: sendGateDelete(): {}:{} => {} FAILED: {}", ipv4, port, gateReq, e.getMessage());
-                       }
-                       return true;
-               }
-       }
+    private final Logger logger = LoggerFactory.getLogger(PCMMService.class);
+
+    private final Ccap ccap;
+    private final IpAddress ipAddr;
+    private final PortNumber portNum;
+    protected final CcapClient ccapClient;
+    protected Map<String, PCMMGateReq> gateRequests = Maps.newConcurrentMap();
+
+    private final short clientType;
+
+    public PCMMService(final short clientType, final Ccap ccap) {
+        this.clientType = clientType;
+        this.ccap = ccap;
+        ipAddr = ccap.getConnection().getIpAddress();
+        portNum = ccap.getConnection().getPort();
+        ccapClient = new CcapClient(ipAddr, portNum);
+        logger.info("Attempting to add CCAP with ID {} @ {}:{}", ccap.getCcapId(), ipAddr.getIpv4Address().getValue(),
+                portNum.getValue());
+    }
+
+    public void disconect() {
+        ccapClient.disconnect();
+    }
+
+    // TODO - try and change the return to something other than a String to be parsed to determine success
+    public String addCcap() {
+        ccapClient.connect();
+        if (ccapClient.isConnected) {
+            logger.info("Connected to CCAP with ID - " + ccap.getCcapId());
+            return String
+                    .format("200 OK - CCAP %s connected @ %s:%d", ccap.getCcapId(), ipAddr.getIpv4Address().getValue(),
+                            portNum.getValue());
+        } else {
+            return String.format("404 Not Found - CCAP %s failed to connect @ %s:%d - %s", ccap.getCcapId(),
+                    ipAddr.getIpv4Address().getValue(), portNum.getValue(), ccapClient.errMessage);
+        }
+    }
+
+    public class GateSetStatus {
+        private boolean didSucceed = false;
+        private String message = "";
+        private String copsGateId = "";
+
+        public boolean didSucceed() {
+            return didSucceed;
+        }
+
+        void setDidSucceed(final boolean didSucceed) {
+            this.didSucceed = didSucceed;
+        }
+
+        public String getMessage() {
+            return message;
+        }
+
+        void setMessage(final String message) {
+            this.message = message;
+        }
+
+        public String getCopsGateId() {
+            return copsGateId;
+        }
+
+        void setCopsGateId(final String copsGateId) {
+            this.copsGateId = copsGateId;
+        }
+
+    }
+
+    public GateSetStatus sendGateSet(final String gatePathStr, final InetAddress subId, final Gate qosGate,
+            final ServiceFlowDirection scnDir) {
+
+        GateSetStatus status = new GateSetStatus();
+
+        logger.info("Sending gate to CCAP with ID - " + ccap.getCcapId());
+
+        // assemble the gate request for this subId
+        final PCMMGateReqBuilder gateBuilder = new PCMMGateReqBuilder();
+        gateBuilder.setAmId(ccap.getAmId());
+        gateBuilder.setSubscriberId(subId);
+
+        // force gateSpec.Direction to align with SCN direction
+        final ServiceClassName scn = qosGate.getTrafficProfile().getServiceClassName();
+        if (scn != null) {
+            gateBuilder.setGateSpec(qosGate.getGateSpec(), scnDir);
+        } else {
+            // not an SCN gate
+            gateBuilder.setGateSpec(qosGate.getGateSpec(), null);
+        }
+        gateBuilder.setTrafficProfile(qosGate.getTrafficProfile());
+
+        // pick a classifier type (only one for now)
+        if (qosGate.getClassifier() != null) {
+            gateBuilder.setClassifier(qosGate.getClassifier());
+        } else if (qosGate.getExtClassifier() != null) {
+            gateBuilder.setExtClassifier(qosGate.getExtClassifier());
+        } else if (qosGate.getIpv6Classifier() != null) {
+            gateBuilder.setIpv6Classifier(qosGate.getIpv6Classifier());
+        }
+
+        // assemble the final gate request
+        final PCMMGateReq gateReq = gateBuilder.build();
+
+        if (gateRequests.get(gatePathStr) == null) {
+            // and remember it
+            gateRequests.put(gatePathStr, gateReq);
+            // and send it to the CCAP
+            ccapClient.sendGateSet(gateReq);
+            // and wait for the COPS response to complete processing gate request
+            try {
+                // TODO - see PCMMPdpReqStateMan#processReport() gate.notify(). Should determine a better means to
+                // TODO - handle this synchronization.
+                // TODO - if not changing this, may want to make this timeout configurable
+                synchronized (gateReq) {
+                    logger.info("Waiting 5000ms for gate request to be updated");
+                    gateReq.wait(5000);
+                    logger.debug("Gate request error - " + gateReq.getError());
+                    logger.debug("Gate request ID - " + gateReq.getGateID());
+                }
+            } catch (Exception e) {
+                logger.error(
+                        "PCMMService: sendGateSet(): gate response timeout exceeded for " + gatePathStr + '/' + gateReq,
+                        e);
+                status.setDidSucceed(false);
+                status.setMessage(String.format("408 Request Timeout - gate response timeout exceeded for %s/%s", ccap.getCcapId(),
+                        gatePathStr));
+                return status;
+            }
+
+
+            if (gateReq.getError() != null) {
+                status.setDidSucceed(false);
+                status.setMessage(
+                        String.format("404 Not Found - sendGateSet for %s/%s returned error - %s", ccap.getCcapId(),
+                                gatePathStr, gateReq.getError().toString()));
+
+                logger.error("PCMMService: sendGateSet(): returned error: {}", gateReq.getError().toString());
+            } else {
+                if (gateReq.getGateID() != null) {
+                    status.setDidSucceed(true);
+                    status.setCopsGateId(String.format("%08x", gateReq.getGateID().getGateID()));
+                    status.setMessage(String.format("200 OK - sendGateSet for %s/%s returned GateId %08x",
+                            ccap.getCcapId(), gatePathStr, gateReq.getGateID().getGateID()) );
+                    logger.info(String.format("PCMMService: sendGateSet(): returned GateId %08x: ",
+                            gateReq.getGateID().getGateID()));
+                } else {
+                    status.setDidSucceed(false);
+                    status.setMessage(
+                            String.format("404 Not Found - sendGateSet for %s/%s no gateId returned", ccap.getCcapId(),
+                                    gatePathStr));
+
+                    logger.info("PCMMService: sendGateSet(): no gateId returned:");
+                }
+            }
+        } else {
+            logger.info("PCMMService: sendGateSet(): no gateId returned:");
+            status.setMessage(String.format("404 Not Found - sendGateSet for %s/%s already exists", ccap.getCcapId(), gatePathStr));
+        }
+
+        return status;
+    }
+
+    public Boolean sendGateDelete(final String gatePathStr) {
+        logger.info("sendGateDelete() - " + ccap);
+        // recover the original gate request
+        final PCMMGateReq gateReq = gateRequests.remove(gatePathStr);
+        if (gateReq != null) {
+            ccapClient.sendGateDelete(gateReq);
+            // and wait for the response to complete
+            try {
+                // TODO - see PCMMPdpReqStateMan#processReport() gate.notify(). Should determine a better means to
+                // TODO - handle this synchronization.
+                synchronized (gateReq) {
+                    gateReq.wait(1000);
+                }
+            } catch (InterruptedException e) {
+                logger.error("PCMMService: sendGateDelete(): gate response timeout exceeded for {}/{}", gatePathStr,
+                        gateReq);
+            }
+            if (gateReq.getError() != null) {
+                logger.warn("PCMMService: sendGateDelete(): returned error: {}", gateReq.getError().toString());
+                return false;
+            } else {
+                if (gateReq.getGateID() != null) {
+                    logger.info(String.format("PCMMService: sendGateDelete(): deleted GateId %08x: ",
+                            gateReq.getGateID().getGateID()));
+                } else {
+                    logger.error("PCMMService: sendGateDelete(): deleted but no gateId returned");
+                }
+                return true;
+            }
+        } else {
+            logger.warn("Attempt to delete non-existent gate with path - " + gatePathStr);
+            return false;
+        }
+    }
+
+    /**
+     * Used to interface with a CCAP (including CMTSs)
+     */
+    protected class CcapClient {
+        public final PCMMPdpDataProcess pcmmProcess;
+        public final PCMMPdpAgent pcmmPdp;
+
+        private final String ipv4;
+        private final Integer port;
+
+        // Needs to be initialized in connect() method else would be final
+        protected transient PCMMPdpMsgSender pcmmSender;
+
+        private transient Boolean isConnected = false;
+        private transient String errMessage = null;
+
+        /**
+         * Constructor
+         *
+         * @param ccapIp
+         *         - the IP of the CCAP to manage
+         * @param portNum
+         *         - the port number of the CCAP to manage
+         */
+        public CcapClient(final IpAddress ccapIp, final PortNumber portNum) {
+            ipv4 = ccapIp.getIpv4Address().getValue();
+            if (portNum != null) {
+                port = portNum.getValue();
+            } else {
+                port = PCMMPdpAgent.WELL_KNOWN_PDP_PORT;
+            }
+            // TODO FIXME - if this object is not null, gate processing will not work correctly
+            // TODO see - PCMMPdpReqStateMan#processReport() where the report type is success and the process is null
+            //            pcmmProcess = new PCMMPdpDataProcess();
+            pcmmProcess = null;
+            pcmmPdp = new PCMMPdpAgent(ipv4, port, clientType, pcmmProcess);
+        }
+
+        /**
+         * Starts the connection to the CCAP
+         */
+        public void connect() {
+            logger.info("Attempting to connect to host: " + ipv4 + " port: " + port);
+            try {
+                pcmmPdp.connect();
+
+                // Cannot instantiate until after pcmmPdp.connect() is called as this is where the client handle is created
+                pcmmSender = new PCMMPdpMsgSender(clientType, pcmmPdp.getClientHandle(), pcmmPdp.getSocket());
+
+                isConnected = true;
+            } catch (Exception e) {
+                isConnected = false;
+                logger.error("Failed to connect to host: " + ipv4 + " port: " + port, e);
+                errMessage = e.getMessage();
+            }
+        }
+
+        public void disconnect() {
+            logger.info("CcapClient: disconnect(): {}:{}", ipv4, port);
+            pcmmPdp.disconnect(new COPSError(ErrorTypes.SHUTTING_DOWN, ErrorTypes.NA));
+            isConnected = false;
+        }
+
+        // TODO - consider returning a new PCMMGateReq object or a future here instead of setting the ID on the old
+        // TODO - request by reference which makes the code more convoluted thus making issues more difficult to track down.
+        public Boolean sendGateSet(final PCMMGateReq gateReq) {
+            logger.info("CcapClient: sendGateSet(): {}:{} => {}", ipv4, port, gateReq);
+            try {
+                pcmmSender.sendGateSet(gateReq);
+
+                // TODO - determine if this is the correct place to perform this operation as this currently is the
+                // TODO - place where the gate ID can be set on the gateReq object
+                //                pcmmSender.handleGateReport(pcmmPdp.getSocket());
+            } catch (COPSPdpException e) {
+                logger.error("CcapClient: sendGateSet(): {}:{} => {} FAILED:", ipv4, port, gateReq, e);
+            }
+            // and save it back to the gateRequest object for gate delete later
+            gateReq.setGateID(pcmmSender.getGateID());
+
+            // TODO - determine why this method is always returning true???
+            return true;
+        }
+
+        public Boolean sendGateDelete(final PCMMGateReq gateReq) {
+            logger.info("CcapClient: sendGateDelete(): {}:{} => {}", ipv4, port, gateReq);
+            try {
+                pcmmSender.sendGateDelete(gateReq);
+            } catch (COPSPdpException e) {
+                logger.error("CcapClient: sendGateDelete(): {}:{} => {} FAILED: {}", ipv4, port, gateReq,
+                        e.getMessage());
+            }
+            return true;
+        }
+    }
 }
 
index 894eccc0305e42fa5b5f428007f7fd08b7949c53..bbd960349151971d16a22167885ed4ec4a7b0a22 100644 (file)
@@ -1,20 +1,42 @@
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
 package org.opendaylight.controller.packetcable.provider;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.CheckedFuture;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import javax.annotation.Nonnull;
 import javax.annotation.concurrent.ThreadSafe;
 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
 import org.opendaylight.controller.md.sal.binding.api.DataChangeListener;
-import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
+import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import org.opendaylight.controller.packetcable.provider.validation.DataValidator;
+import org.opendaylight.controller.packetcable.provider.validation.ValidationException;
+import org.opendaylight.controller.packetcable.provider.validation.Validator;
+import org.opendaylight.controller.packetcable.provider.validation.impl.CcapsValidatorProviderFactory;
+import org.opendaylight.controller.packetcable.provider.validation.impl.QosValidatorProviderFactory;
 import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.ProviderContext;
 import org.opendaylight.controller.sal.binding.api.BindingAwareProvider;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.IpPrefix;
@@ -23,14 +45,22 @@ import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.Ccaps;
 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.Qos;
 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ServiceClassName;
 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ServiceFlowDirection;
-import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ccap.attributes.Connection;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ccap.attributes.ConnectionBuilder;
 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ccaps.Ccap;
-import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ccaps.CcapKey;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ccaps.CcapBuilder;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.Apps;
 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.App;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.AppBuilder;
 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.AppKey;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.Subscribers;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.SubscribersBuilder;
 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.subscribers.Subscriber;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.subscribers.SubscriberBuilder;
 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.subscribers.SubscriberKey;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.subscribers.subscriber.Gates;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.subscribers.subscriber.GatesBuilder;
 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.subscribers.subscriber.gates.Gate;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.subscribers.subscriber.gates.GateBuilder;
 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.subscribers.subscriber.gates.GateKey;
 import org.opendaylight.yangtools.concepts.ListenerRegistration;
 import org.opendaylight.yangtools.yang.binding.DataObject;
@@ -41,28 +71,18 @@ import org.slf4j.LoggerFactory;
 
 /**
  * Called by ODL framework to start this bundle.
- *
+ * <p>
  * This class is responsible for processing messages received from ODL's restconf interface.
  * TODO - Remove some of these state maps and move some of this into the PCMMService
  */
 @ThreadSafe
-public class PacketcableProvider implements BindingAwareProvider, DataChangeListener, AutoCloseable {
+public class PacketcableProvider implements BindingAwareProvider, AutoCloseable {
 
     private static final Logger logger = LoggerFactory.getLogger(PacketcableProvider.class);
 
-    // keys to the /restconf/config/packetcable:ccap and /restconf/config/packetcable:qos config datastore
-    public static final InstanceIdentifier<Ccaps> ccapIID = InstanceIdentifier.builder(Ccaps.class).build();
-    public static final InstanceIdentifier<Qos> qosIID = InstanceIdentifier.builder(Qos.class).build();
-
-    /**
-     * The ODL object used to broker messages throughout the framework
-     */
-    private DataBroker dataBroker;
-
-    private MdsalUtils mdsalUtils;
-
-    private ListenerRegistration<DataChangeListener> ccapDataChangeListenerRegistration;
-    private ListenerRegistration<DataChangeListener> qosDataChangeListenerRegistration;
+    // keys to the /restconf/config/packetcable:ccaps and /restconf/config/packetcable:qos config datastore
+    private static final InstanceIdentifier<Ccaps> ccapsIID = InstanceIdentifier.builder(Ccaps.class).build();
+    private static final InstanceIdentifier<Qos> qosIID = InstanceIdentifier.builder(Qos.class).build();
 
     // TODO - Revisit these maps and remove the ones no longer necessary
     private final Map<String, Ccap> ccapMap = new ConcurrentHashMap<>();
@@ -72,11 +92,26 @@ public class PacketcableProvider implements BindingAwareProvider, DataChangeList
     private final Map<ServiceClassName, List<Ccap>> downstreamScnMap = new ConcurrentHashMap<>();
     private final Map<ServiceClassName, List<Ccap>> upstreamScnMap = new ConcurrentHashMap<>();
 
+    private final Executor executor = Executors.newSingleThreadExecutor();
+
     /**
      * Holds a PCMMService object for each CCAP being managed.
      */
     private final Map<String, PCMMService> pcmmServiceMap = new ConcurrentHashMap<>();
 
+    /**
+     * The ODL object used to broker messages throughout the framework
+     */
+    private DataBroker dataBroker;
+    private MdsalUtils mdsalUtils;
+
+    // Data change listeners/registrations
+    private final CcapsDataChangeListener ccapsDataChangeListener = new CcapsDataChangeListener();
+    private final QosDataChangeListener qosDataChangeListener = new QosDataChangeListener();
+
+    private ListenerRegistration<DataChangeListener> ccapsDataChangeListenerRegistration;
+    private ListenerRegistration<DataChangeListener> qosDataChangeListenerRegistration;
+
     /**
      * Constructor
      */
@@ -87,26 +122,51 @@ public class PacketcableProvider implements BindingAwareProvider, DataChangeList
     @Override
     public void onSessionInitiated(ProviderContext session) {
         logger.info("Packetcable Session Initiated");
+        logger.info("logging levels: error={}, warn={}, info={}, debug={}, trace={}", logger.isErrorEnabled(),
+                logger.isWarnEnabled(), logger.isInfoEnabled(), logger.isDebugEnabled(), logger.isTraceEnabled());
 
-        dataBroker =  session.getSALService(DataBroker.class);
+        dataBroker = session.getSALService(DataBroker.class);
 
         mdsalUtils = new MdsalUtils(dataBroker);
 
-        ccapDataChangeListenerRegistration =
-                dataBroker.registerDataChangeListener(LogicalDatastoreType.CONFIGURATION,
-                        PacketcableProvider.ccapIID, this, DataBroker.DataChangeScope.SUBTREE );
+        ccapsDataChangeListenerRegistration = dataBroker
+                .registerDataChangeListener(LogicalDatastoreType.CONFIGURATION, ccapsIID.child(Ccap.class),
+                        ccapsDataChangeListener, DataBroker.DataChangeScope.SUBTREE);
+
+        qosDataChangeListenerRegistration = dataBroker
+                .registerDataChangeListener(LogicalDatastoreType.CONFIGURATION, PacketcableProvider.qosIID.child(Apps.class).child(App.class),
+                        qosDataChangeListener, DataBroker.DataChangeScope.SUBTREE);
+
+        // Add empty top level elements
+//        for (LogicalDatastoreType datastoreType : LogicalDatastoreType.values()) {
+//            WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
+//            writeTransaction.put(datastoreType, ccapsIID, new CcapsBuilder().build());
+//            CheckedFuture<Void, TransactionCommitFailedException> future = writeTransaction.submit();
+//            try {
+//                future.checkedGet();
+//            } catch (TransactionCommitFailedException e) {
+//                logger.error("Failed to initialise top level ccaps in datastore {}", datastoreType, e);
+//            }
+//            writeTransaction = dataBroker.newWriteOnlyTransaction();
+//            writeTransaction.put(datastoreType, qosIID, new QosBuilder().build());
+//            future = writeTransaction.submit();
+//            try {
+//                future.checkedGet();
+//            } catch (TransactionCommitFailedException e) {
+//                logger.error("Failed to initialise top level qos in datastore {}", datastoreType, e);
+//            }
+//        }
+
 
-        qosDataChangeListenerRegistration =
-                dataBroker.registerDataChangeListener(LogicalDatastoreType.CONFIGURATION,
-                        PacketcableProvider.qosIID, this, DataBroker.DataChangeScope.SUBTREE );
     }
+
     /**
      * Implemented from the AutoCloseable interface.
      */
     @Override
     public void close() throws ExecutionException, InterruptedException {
-        if (ccapDataChangeListenerRegistration != null) {
-            ccapDataChangeListenerRegistration.close();
+        if (ccapsDataChangeListenerRegistration != null) {
+            ccapsDataChangeListenerRegistration.close();
         }
 
         if (qosDataChangeListenerRegistration != null) {
@@ -114,24 +174,6 @@ public class PacketcableProvider implements BindingAwareProvider, DataChangeList
         }
     }
 
-    public InetAddress getInetAddress(final String subId){
-        try {
-            return InetAddress.getByName(subId);
-        } catch (UnknownHostException e) {
-            logger.error("getInetAddress: {} FAILED: {}", subId, e.getMessage());
-            return null;
-        }
-    }
-
-    private String getIpPrefixStr(final IpPrefix ipPrefix) {
-        final Ipv4Prefix ipv4 = ipPrefix.getIpv4Prefix();
-        if (ipv4 != null) {
-            return ipv4.getValue();
-        } else {
-            return ipPrefix.getIpv6Prefix().getValue();
-        }
-    }
-
     private void updateCcapMaps(final Ccap ccap) {
         // add ccap to the subscriberSubnets map
         for (final IpPrefix ipPrefix : ccap.getSubscriberSubnets()) {
@@ -163,33 +205,22 @@ public class PacketcableProvider implements BindingAwareProvider, DataChangeList
         }
     }
 
-    private void removeCcapFromAllMaps(final Ccap ccap) {
-        // remove the ccap from all maps
-        // subscriberSubnets map
-        for (final Map.Entry<Subnet, Ccap> entry : subscriberSubnetsMap.entrySet()) {
-            if (entry.getValue() == ccap) {
-                subscriberSubnetsMap.remove(entry.getKey());
-            }
-        }
-        // ccap to upstream SCN map
-        for (final Map.Entry<ServiceClassName, List<Ccap>> entry : upstreamScnMap.entrySet()) {
-            final List<Ccap> ccapList = entry.getValue();
-            ccapList.remove(ccap);
-            if (ccapList.isEmpty()) {
-                upstreamScnMap.remove(entry.getKey());
-            }
-        }
-        // ccap to downstream SCN map
-        for (final Map.Entry<ServiceClassName, List<Ccap>> entry : downstreamScnMap.entrySet()) {
-            final List<Ccap> ccapList = entry.getValue();
-            ccapList.remove(ccap);
-            if (ccapList.isEmpty()) {
-                downstreamScnMap.remove(entry.getKey());
-            }
+    private String getIpPrefixStr(final IpPrefix ipPrefix) {
+        final Ipv4Prefix ipv4 = ipPrefix.getIpv4Prefix();
+        if (ipv4 != null) {
+            return ipv4.getValue();
+        } else {
+            return ipPrefix.getIpv6Prefix().getValue();
         }
+    }
 
-        final PCMMService service = pcmmServiceMap.remove(ccap.getCcapId());
-        if (service != null) service.disconect();
+    public InetAddress getInetAddress(final String subId) {
+        try {
+            return InetAddress.getByName(subId);
+        } catch (UnknownHostException e) {
+            logger.error("getInetAddress: {} FAILED: {}", subId, e.getMessage());
+            return null;
+        }
     }
 
     private Ccap findCcapForSubscriberId(final InetAddress inetAddr) {
@@ -209,6 +240,9 @@ public class PacketcableProvider implements BindingAwareProvider, DataChangeList
     }
 
     private ServiceFlowDirection findScnOnCcap(final ServiceClassName scn, final Ccap ccap) {
+        checkNotNull(scn);
+        checkNotNull(ccap);
+
         if (upstreamScnMap.containsKey(scn)) {
             final List<Ccap> ccapList = upstreamScnMap.get(scn);
             if (ccapList.contains(ccap)) {
@@ -223,313 +257,586 @@ public class PacketcableProvider implements BindingAwareProvider, DataChangeList
         return null;
     }
 
+    private void removeCcapFromAllMaps(final Ccap ccap) {
+        // remove the ccap from all maps
+        // subscriberSubnets map
+        for (final Map.Entry<Subnet, Ccap> entry : subscriberSubnetsMap.entrySet()) {
+            if (entry.getValue() == ccap) {
+                subscriberSubnetsMap.remove(entry.getKey());
+            }
+        }
+        // ccap to upstream SCN map
+        for (final Map.Entry<ServiceClassName, List<Ccap>> entry : upstreamScnMap.entrySet()) {
+            final List<Ccap> ccapList = entry.getValue();
+            ccapList.remove(ccap);
+            if (ccapList.isEmpty()) {
+                upstreamScnMap.remove(entry.getKey());
+            }
+        }
+        // ccap to downstream SCN map
+        for (final Map.Entry<ServiceClassName, List<Ccap>> entry : downstreamScnMap.entrySet()) {
+            final List<Ccap> ccapList = entry.getValue();
+            ccapList.remove(ccap);
+            if (ccapList.isEmpty()) {
+                downstreamScnMap.remove(entry.getKey());
+            }
+        }
+
+        final PCMMService service = pcmmServiceMap.remove(ccap.getCcapId());
+        if (service != null) {
+            service.disconect();
+        }
+    }
+
+    // ValidationException does not need to be thrown again
+    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+    private <T extends DataObject> void saveErrors(@Nonnull Map<InstanceIdentifier<T>, ValidationException> errorMap,
+            @Nonnull Map<InstanceIdentifier<T>, T> dataMap) {
+
+        final WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
+
+
+        for (InstanceIdentifier<T> iid : errorMap.keySet()) {
+
+            final ValidationException exception = errorMap.get(iid);
+            final T badData = dataMap.get(iid);
+
+            if (!badData.getImplementedInterface().isAssignableFrom(iid.getTargetType())) {
+                // InstanceIdentifier<T> does not have the same type as the DataObject
+                logger.error("Bad InstanceIdentifier to DataObject mapping, {} : {}", iid, badData);
+                continue;
+            }
+
+            if (badData instanceof Ccap) {
+                final Ccap ccap = (Ccap) badData;
+
+                final Ccap opperationalCcap =
+                        new CcapBuilder().setCcapId(ccap.getCcapId()).setError(exception.getErrorMessages()).build();
+
+
+                // type match between iid and badData is done at start of loop
+                @SuppressWarnings("unchecked")
+                final InstanceIdentifier<Ccap> ccapIID = (InstanceIdentifier<Ccap>) iid;
+                writeTransaction.put(LogicalDatastoreType.OPERATIONAL, ccapIID, opperationalCcap);
+            }
+            else if (badData instanceof Gate) {
+                final Gate gate = (Gate) badData;
+
+                final Gate operationalGate =
+                        new GateBuilder()
+                        .setGateId(gate.getGateId())
+                        .setError(exception.getErrorMessages())
+                        .build();
+
+                final Gates operationalGates = new GatesBuilder()
+                        .setGate(Collections.singletonList(operationalGate))
+                        .build();
+
+                final SubscriberKey subscriberKey = InstanceIdentifier.keyOf(iid.firstIdentifierOf(Subscriber.class));
+                final Subscriber operationalSubscriber = new SubscriberBuilder()
+                        .setSubscriberId(subscriberKey.getSubscriberId())
+                        .setGates(operationalGates)
+                        .build();
+
+                final Subscribers operationalSubscribers = new SubscribersBuilder()
+                        .setSubscriber(Collections.singletonList(operationalSubscriber))
+                        .build();
+
+                final InstanceIdentifier<App> appIID = iid.firstIdentifierOf(App.class);
+                final AppKey appKey = InstanceIdentifier.keyOf(appIID);
+                final App operationalApp = new AppBuilder()
+                        .setAppId(appKey.getAppId())
+                        .setSubscribers(operationalSubscribers)
+                        .build();
+
+
+                writeTransaction.put(LogicalDatastoreType.OPERATIONAL, appIID, operationalApp);
+            }
+            else {
+                // If you get here a developer forgot to add a type above
+                logger.error("Unexpected type requested for error saving: {}", badData);
+                throw new IllegalStateException("Unsupported type for error saving");
+            }
+
+        }
+
+
+        CheckedFuture<Void, TransactionCommitFailedException> future = writeTransaction.submit();
+
+        try {
+            future.checkedGet();
+        } catch (TransactionCommitFailedException e) {
+            logger.error("Failed to write errors to operational datastore", e);
+        }
+    }
+
     /**
-     * Implemented from the DataChangeListener interface.
+     * Removes Ccaps if all Ccap instances are removed
      */
+    private class CcapsCleaner extends AbstractCleaner<Ccaps> {
 
-    private class InstanceData {
-        // CCAP Identity
-        public final Map<InstanceIdentifier<Ccap>, Ccap> ccapIidMap = new HashMap<>();
-        // Gate Identity
-        public String subId;
-        public final Map<String, String> gatePathMap = new HashMap<>();
-        public String gatePath;
-        public final Map<InstanceIdentifier<Gate>, Gate> gateIidMap = new HashMap<>();
-        // remove path for either CCAP or Gate
-        public final Set<String> removePathList = new HashSet<>();
-
-        public final Set<InstanceIdentifier<?>> reqCcapIds = new HashSet<>();
-
-        public InstanceData(final Map<InstanceIdentifier<?>, DataObject> thisData) {
-            // only used to parse createdData or updatedData
-            getCcaps(thisData);
-            if (ccapIidMap.isEmpty()) {
-                getGates(thisData);
-                if (! gateIidMap.isEmpty()){
-                    gatePath = gatePathMap.get("appId") + "/" + gatePathMap.get("subId");
-                }
-            }
+        public CcapsCleaner(final InstanceIdentifier<?> removedIID) {
+            super(removedIID, Ccaps.class, LogicalDatastoreType.OPERATIONAL);
         }
 
-        public InstanceData(final Set<InstanceIdentifier<?>> thisData) {
-            // only used to parse the removedData paths
-            for (final InstanceIdentifier<?> removeThis : thisData) {
-                getGatePathMap(removeThis);
-                if (gatePathMap.containsKey("ccapId")) {
-                    gatePath = gatePathMap.get("ccapId");
-                    removePathList.add(gatePath);
-                } else if (gatePathMap.containsKey("gateId")) {
-                    gatePath = gatePathMap.get("appId") + "/" + gatePathMap.get("subId") + "/" + gatePathMap.get("gateId");
-                    removePathList.add(gatePath);
-                }
-            }
+        @Override
+        protected boolean shouldClean(final Ccaps ccaps) {
+            return ccaps.getCcap().isEmpty();
         }
-        private void getGatePathMap(final InstanceIdentifier<?> thisInstance) {
-            logger.info("onDataChanged().getGatePathMap(): " + thisInstance);
-            try {
-                final InstanceIdentifier<Ccap> ccapInstance = thisInstance.firstIdentifierOf(Ccap.class);
-                if (ccapInstance != null) {
-                    final CcapKey ccapKey = InstanceIdentifier.keyOf(ccapInstance);
-                    if (ccapKey != null) {
-                        gatePathMap.put("ccapId", ccapKey.getCcapId());
-                    }
-                } else {
-                    // get the gate path keys from the InstanceIdentifier Map key set if they are there
-                    final InstanceIdentifier<App> appInstance = thisInstance.firstIdentifierOf(App.class);
-                    if (appInstance != null) {
-                        final AppKey appKey = InstanceIdentifier.keyOf(appInstance);
-                        if (appKey != null) {
-                            gatePathMap.put("appId", appKey.getAppId());
-                        }
-                    }
-                    final InstanceIdentifier<Subscriber> subInstance = thisInstance.firstIdentifierOf(Subscriber.class);
-                    if (subInstance != null) {
-                        final SubscriberKey subKey = InstanceIdentifier.keyOf(subInstance);
-                        if (subKey != null) {
-                            subId = subKey.getSubscriberId();
-                            gatePathMap.put("subId", subId);
+    }
+
+    /**
+     * Removes Subscriber if all Gate instances are removed
+     */
+    private class SubscriberCleaner extends AbstractCleaner<Subscriber> {
+
+        public SubscriberCleaner(InstanceIdentifier<Gate> removedGateIID) {
+            super(removedGateIID, Subscriber.class, LogicalDatastoreType.OPERATIONAL);
+        }
+
+        @Override
+        protected boolean shouldClean(final Subscriber subscriber) {
+            return subscriber.getGates().getGate().isEmpty();
+        }
+
+        @Override
+        protected void postRemove(InstanceIdentifier<Subscriber> subscriberIID) {
+            executor.execute(new AppCleaner(subscriberIID));
+        }
+    }
+
+
+    /**
+     * Removes App if all Subscribers are removed.
+     */
+    private class AppCleaner extends AbstractCleaner<App> {
+
+        public AppCleaner(InstanceIdentifier<Subscriber> removedSubscriberIID) {
+            super(removedSubscriberIID, App.class, LogicalDatastoreType.OPERATIONAL);
+        }
+
+        @Override
+        boolean shouldClean(final App app) {
+            return app.getSubscribers().getSubscriber().isEmpty();
+        }
+
+        @Override
+        void postRemove(final InstanceIdentifier<App> appIID) {
+            executor.execute(new AppsCleaner(appIID));
+        }
+    }
+
+
+    /**
+     * Removes Apps if all App instances are removed.
+     */
+    private class AppsCleaner extends  AbstractCleaner<Apps> {
+
+        public AppsCleaner(InstanceIdentifier<App> removedAppIID) {
+            super(removedAppIID, Apps.class, LogicalDatastoreType.OPERATIONAL);
+        }
+
+        @Override
+        protected boolean shouldClean(final Apps apps) {
+            return apps.getApp().isEmpty();
+        }
+    }
+
+
+    /**
+     * Helper class to do the heavy lifting in removing object. Lets subclasses decide with
+     *  {@link #shouldClean(DataObject)}. <br>
+     *
+     * Subclasses can react after an instance is removed by overriding {@link #postRemove(InstanceIdentifier)}
+     * @param <T> The type that will be removed
+     */
+    private abstract class AbstractCleaner <T extends DataObject> implements Runnable {
+        final InstanceIdentifier<?> removedIID;
+        final Class<T> tClass;
+        final LogicalDatastoreType datastoreType;
+
+        public AbstractCleaner(InstanceIdentifier<?> removedIID, Class<T> tClass, LogicalDatastoreType datastoreType) {
+            this.removedIID = checkNotNull(removedIID);
+            this.tClass = checkNotNull(tClass);
+            this.datastoreType = checkNotNull(datastoreType);
+        }
+
+        @Override
+        public void run() {
+            InstanceIdentifier<T> tIID = removedIID.firstIdentifierOf(tClass);
+            if (tIID != null) {
+                Optional<T> optional = mdsalUtils.read(datastoreType, tIID);
+                if (optional.isPresent()) {
+
+                    if (shouldClean(optional.get())) {
+                        if (mdsalUtils.delete(datastoreType, tIID)) {
+                            postRemove(tIID);
                         }
-                    }
-                    final InstanceIdentifier<Gate> gatesInstance = thisInstance.firstIdentifierOf(Gate.class);
-                    if (gatesInstance != null) {
-                        final GateKey gateKey = InstanceIdentifier.keyOf(gatesInstance);
-                        if (gateKey != null) {
-                            gatePathMap.put("gateId", gateKey.getGateId());
+                        else {
+                            removeFailed(tIID);
                         }
                     }
+
                 }
-            } catch (ClassCastException err) {
-                logger.warn("Unexpected exception", err);
+            }
+            else {
+                logger.error("Expected to find InstanceIdentifier<{}> but was not found: {}",
+                        tClass.getSimpleName(), removedIID);
             }
         }
 
-        private void getCcaps(final Map<InstanceIdentifier<?>, DataObject> thisData) {
-            logger.info("onDataChanged().getCcaps(): " + thisData);
-            for (final Map.Entry<InstanceIdentifier<?>, DataObject> entry : thisData.entrySet()) {
+        /**
+         * If returns true the object will be removed from the datastore
+         * @param object The object that might be removed.
+         * @return true if it should be removed.
+         */
+        abstract boolean shouldClean(final T object);
 
-                if (entry.getKey().getTargetType().equals(Ccap.class)) {
-                    Ccap ccaps = ((Ccap) entry.getValue());
-                    InstanceIdentifier<Ccap> ccapsIid = InstanceIdentifier.builder(Ccaps.class).child(Ccap.class, new CcapKey(ccaps.getCcapId())).build();
-                    ccapIidMap.put(ccapsIid, ccaps);
-                }
+        /**
+         * Called after an instance is removed.
+         * @param tIID the InstanceIdentifier of the removed object
+         */
+        void postRemove(InstanceIdentifier<T> tIID) {
 
-                if (entry.getKey().getTargetType().equals(Connection.class) ||
-                        entry.getKey().getTargetType().equals(Ccap.class)) {
-                    reqCcapIds.add(entry.getKey());
-                }
-            }
         }
 
-        private void getGates(final Map<InstanceIdentifier<?>, DataObject> thisData) {
-            logger.info("onDataChanged().getGates(): " + thisData);
-            for (final Map.Entry<InstanceIdentifier<?>, DataObject> entry : thisData.entrySet()) {
-                if (entry.getValue() instanceof Gate) {
-                    final Gate gate = (Gate)entry.getValue();
-
-                    // TODO FIXME - Potential ClassCastException thrown here!!!
-                    final InstanceIdentifier<Gate> gateIID = (InstanceIdentifier<Gate>)entry.getKey();
-                    getGatePathMap(gateIID);
-                    if (!gateIidMap.containsKey(gateIID)){
-                        gateIidMap.put(gateIID, gate);
-                    }
-                }
-                // TODO reconciliate gates
-//                if (entry.getValue() instanceof Qos) {
-//                    final Qos qos = (Qos) entry.getValue();
-//                    if (qos.getApps() != null) {
-//                        for (Apps apps : qos.getApps()) {
-//                            if (apps.getSubs() != null) {
-//                                for (Subs subs : apps.getSubs()) {
-//                                    if (subs.getGates() != null) {
-//                                        for (Gate gates : subs.getGates()) {
-//                                            final InstanceIdentifier<Gate> gateIID = (InstanceIdentifier<Gate>)entry.getKey();
-//                                            getGatePathMap(gateIID);
-//                                            if (!gateIidMap.containsKey(gateIID)){
-//                                                gateIidMap.put(gateIID, gates);
-//                                            }
-//                                        }
-//                                    }
-//                                }
-//                            }
-//                        }
-//                    }
-//                }
-            }
+        void removeFailed(InstanceIdentifier<T> tIID) {
+            logger.error("Failed to remove {}", tIID);
         }
     }
 
-    @Override
-    public void onDataChanged(final AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> change) {
-        logger.info("onDataChanged");
-        // Determine what change action took place by looking at the change object's InstanceIdentifier sets
-        // and validate all instance data
-        if (!change.getCreatedData().isEmpty()) {
-            if (!new ValidateInstanceData(mdsalUtils, change.getCreatedData()).validateYang()) {
-                // leave now -- a bad yang object has been detected and a response object has been inserted
+
+    /**
+     * Listener for the packetcable:ccaps tree
+     */
+    private class CcapsDataChangeListener extends AbstractDataChangeListener<Ccap> {
+
+        private final DataValidator ccapsDataValidator = new DataValidator(new CcapsValidatorProviderFactory().build());
+
+        private final Set<InstanceIdentifier<Ccap>> updateQueue = Sets.newConcurrentHashSet();
+
+        public CcapsDataChangeListener() {
+            super(Ccap.class);
+        }
+
+        @Override
+        protected void handleCreatedData(final Map<InstanceIdentifier<Ccap>, Ccap> createdCcaps) {
+            if (createdCcaps.isEmpty()) {
                 return;
             }
-            onCreate(new InstanceData(change.getCreatedData()));
-        } else if (!change.getRemovedPaths().isEmpty()) {
-            onRemove(new InstanceData(change.getRemovedPaths()));
-        } else if (!change.getUpdatedData().isEmpty()) {
-            onUpdate(new InstanceData(change.getUpdatedData()));
-        } else {
-            // we should not be here -- complain bitterly and return
-            logger.error("onDataChanged(): Unknown change action: " + change);
-        }
-    }
 
-    private void onCreate(final InstanceData thisData) {
-        logger.info("onCreate(): " + thisData);
+            final Map<InstanceIdentifier<Ccap>, ValidationException> errorMap =
+                    ccapsDataValidator.validateOneType(createdCcaps, Validator.Extent.NODE_AND_SUBTREE);
+
+            // validate all new objects an update operational datastore
+            if (!errorMap.isEmpty()) {
+                // bad data write errors to operational datastore
+                saveErrors(errorMap, createdCcaps);
+            }
+
+            if (createdCcaps.size() > errorMap.size()) {
+                final Map<InstanceIdentifier<Ccap>, Ccap> goodData =
+                        Maps.newHashMapWithExpectedSize(createdCcaps.size() - errorMap.size());
+                for (InstanceIdentifier<Ccap> iid : createdCcaps.keySet()) {
+                    if (!errorMap.containsKey(iid)) {
+                        goodData.put(iid, createdCcaps.get(iid));
+                    }
+                }
+                addNewCcaps(goodData);
+            }
+        }
 
-        // get the CCAP parameters
-        String message;
-        if (! thisData.reqCcapIds.isEmpty()) {
-            for (Map.Entry<InstanceIdentifier<Ccap>, Ccap> entry : thisData.ccapIidMap.entrySet()) {
-                final Ccap thisCcap = entry.getValue();
-                // get the CCAP node identity from the Instance Data
-                final String ccapId = thisCcap.getCcapId();
+        private void addNewCcaps(final Map<InstanceIdentifier<Ccap>, Ccap> goodData) {
+            for (InstanceIdentifier<Ccap> iid : goodData.keySet()) {
+                final Ccap ccap = goodData.get(iid);
 
-                if (pcmmServiceMap.get(thisCcap.getCcapId()) == null) {
-                    final PCMMService pcmmService = new PCMMService(IPCMMClient.CLIENT_TYPE, thisCcap);
-                    // TODO - may want to use the AMID but for the client type but probably not???
+                // add service
+                if (pcmmServiceMap.containsKey(ccap.getCcapId())) {
+                    logger.error("Already monitoring CCAP - " + ccap);
+                    continue;
+                }
+                final PCMMService pcmmService = new PCMMService(IPCMMClient.CLIENT_TYPE, ccap);
+                // TODO - may want to use the AMID but for the client type but probably not???
 /*
                             final PCMMService pcmmService = new PCMMService(
                                     thisCcap.getAmId().getAmType().shortValue(), thisCcap);
 */
-                    message = pcmmService.addCcap();
-                    if (message.contains("200 OK")) {
-                        pcmmServiceMap.put(thisCcap.getCcapId(), pcmmService);
-                        ccapMap.put(ccapId, thisCcap);
-                        updateCcapMaps(thisCcap);
-                        logger.info("Created CCAP: {}/{} : {}", thisData.gatePath, thisCcap, message);
-                        logger.info("Created CCAP: {} : {}", thisData.gatePath, message);
-                    } else {
-                        logger.error("Create CCAP Failed: {} : {}", thisData.gatePath, message);
-                        for (final InstanceIdentifier<?> instId : thisData.reqCcapIds) {
-                            mdsalUtils.delete(LogicalDatastoreType.CONFIGURATION, instId);
-                        }
-                        ccapMap.remove(ccapId);
-                    }
+                ConnectionBuilder connectionBuilder = new ConnectionBuilder();
+                String message = pcmmService.addCcap();
+                if (message.contains("200 OK")) {
+                    pcmmServiceMap.put(ccap.getCcapId(), pcmmService);
+                    ccapMap.put(ccap.getCcapId(), ccap);
+                    updateCcapMaps(ccap);
+                    logger.info("Created CCAP: {}/{} : {}", iid, ccap, message);
+                    logger.info("Created CCAP: {} : {}", iid, message);
+                    connectionBuilder.setConnected(true).setError(Collections.<String>emptyList());
                 } else {
-                    logger.error("Already monitoring CCAP - " + thisCcap);
-                    break;
+                    logger.error("Create CCAP Failed: {} : {}", iid, message);
+
+                    connectionBuilder.setConnected(false).setError(Collections.singletonList(message));
                 }
-            }
-        } else {
-            // get the PCMM gate parameters from the ccapId/appId/subId/gateId path in the Maps entry (if new gate)
-            for (final Map.Entry<InstanceIdentifier<Gate>, Gate> entry : thisData.gateIidMap.entrySet()) {
-                message = null;
-                final Gate gate = entry.getValue();
-                final String gateId = gate.getGateId();
-                final String gatePathStr = thisData.gatePath + "/" + gateId ;
-                final InetAddress subId = getInetAddress(thisData.subId);
-                if (subId != null) {
-                    final Ccap thisCcap = findCcapForSubscriberId(subId);
-                    if (thisCcap != null) {
-                        final String ccapId = thisCcap.getCcapId();
-                        // verify SCN exists on CCAP and force gateSpec.Direction to align with SCN direction
-                        final ServiceClassName scn = gate.getTrafficProfile().getServiceClassName();
-                        if (scn != null) {
-                            final ServiceFlowDirection scnDir = findScnOnCcap(scn, thisCcap);
-                            if (scnDir != null) {
-                                if (pcmmServiceMap.get(thisCcap.getCcapId()) != null) {
-                                    message = pcmmServiceMap.get(thisCcap.getCcapId()).sendGateSet(gatePathStr, subId, gate, scnDir);
-                                    gateMap.put(gatePathStr, gate);
-                                    gateCcapMap.put(gatePathStr, thisCcap.getCcapId());
-
-                                    if (message.contains("200 OK")) {
-                                        logger.info("Created QoS gate {} for {}/{}/{} - {}",
-                                                gateId, ccapId, gatePathStr, gate, message);
-                                        logger.info("Created QoS gate {} for {}/{} - {}",
-                                                gateId, ccapId, gatePathStr, message);
-                                    } else {
-                                        logger.info("Unable to create QoS gate {} for {}/{}/{} - {}",
-                                                gateId, ccapId, gatePathStr, gate, message);
-                                        logger.error("Unable to create QoS gate {} for {}/{} - {}",
-                                                gateId, ccapId, gatePathStr, message);
-                                    }
-                                } else {
-                                    logger.error("Unable to locate PCMM Service for CCAP - " + thisCcap);
-                                    break;
-                                }
-                            } else {
-                                logger.error("PCMMService: sendGateSet(): SCN {} not found on CCAP {} for {}/{}",
-                                        scn.getValue(), thisCcap, gatePathStr, gate);
-                                message = String.format("404 Not Found - SCN %s not found on CCAP %s for %s",
-                                        scn.getValue(), thisCcap.getCcapId(), gatePathStr);
-                            }
-                        }
-                    } else {
-                        final String subIdStr = thisData.subId;
-                        message = String.format("404 Not Found - no CCAP found for subscriber %s in %s",
-                                subIdStr, gatePathStr);
-                        logger.info("Create QoS gate {} FAILED: no CCAP found for subscriber {}: @ {}/{}",
-                                gateId, subIdStr, gatePathStr, gate);
-                        logger.error("Create QoS gate {} FAILED: no CCAP found for subscriber {}: @ {}",
-                                gateId, subIdStr, gatePathStr);
-                    }
+
+                Optional<Ccap> optionalCcap = mdsalUtils.read(LogicalDatastoreType.OPERATIONAL, iid);
+
+                final CcapBuilder responseCcapBuilder;
+                if (optionalCcap.isPresent()) {
+                    responseCcapBuilder = new CcapBuilder(optionalCcap.get());
                 } else {
-                    final String subIdStr = thisData.subId;
-                    message = String.format("400 Bad Request - subId must be a valid IP address for subscriber %s in %s",
-                            subIdStr, gatePathStr);
-                    logger.info("Create QoS gate {} FAILED: subId must be a valid IP address for subscriber {}: @ {}/{}",
-                            gateId, subIdStr, gatePathStr, gate);
-                    logger.error("Create QoS gate {} FAILED: subId must be a valid IP address for subscriber {}: @ {}",
-                            gateId, subIdStr, gatePathStr);
+                    responseCcapBuilder = new CcapBuilder();
+                    responseCcapBuilder.setCcapId(ccap.getCcapId());
                 }
-                if (!message.contains("200 OK")) {
-                    mdsalUtils.delete(LogicalDatastoreType.CONFIGURATION, entry.getKey());
+
+                responseCcapBuilder.setConnection(connectionBuilder.build());
+
+                mdsalUtils.put(LogicalDatastoreType.OPERATIONAL, iid, responseCcapBuilder.build());
+            }
+
+        }
+
+        @Override
+        protected void handleUpdatedData(final Map<InstanceIdentifier<Ccap>, Ccap> updatedCcaps,
+                final Map<InstanceIdentifier<Ccap>, Ccap> originalCcaps) {
+
+            // TODO actually support updates
+
+            // update operation not allowed -- restore the original config object and complain
+            for (final Map.Entry<InstanceIdentifier<Ccap>, Ccap> entry : updatedCcaps.entrySet()) {
+                if (!originalCcaps.containsKey(entry.getKey())) {
+                    logger.error("No original data found for supposedly updated data: {}", entry.getValue());
+                    continue;
                 }
+
+                // If this notification is coming from our modification ignore it.
+                if (updateQueue.contains(entry.getKey())) {
+                    updateQueue.remove(entry.getKey());
+                    continue;
+                }
+
+                final Ccap originalCcap = originalCcaps.get(entry.getKey());
+                //final Ccap updatedCcap = entry.getValue();
+
+                // restore the original data
+                updateQueue.add(entry.getKey());
+                mdsalUtils.put(LogicalDatastoreType.CONFIGURATION, entry.getKey(), originalCcap);
+                logger.error("CCAP update not permitted {}", entry.getKey());
             }
         }
+
+        @Override
+        protected void handleRemovedData(final Set<InstanceIdentifier<Ccap>> removedCcapPaths,
+                final Map<InstanceIdentifier<Ccap>, Ccap> originalCcaps) {
+
+            for (InstanceIdentifier<Ccap> iid : removedCcapPaths) {
+                final Ccap nukedCcap = originalCcaps.get(iid);
+                removeCcapFromAllMaps(nukedCcap);
+
+                mdsalUtils.delete(LogicalDatastoreType.OPERATIONAL, iid);
+
+                // clean up ccaps level if it is now empty
+                executor.execute(new CcapsCleaner(iid));
+            }
+
+        }
     }
 
-    private void onRemove(final InstanceData thisData) {
-        logger.info("onRemove(): " + thisData);
-        for (final String gatePathStr: thisData.removePathList) {
-            if (gateMap.containsKey(gatePathStr)) {
-                final Gate thisGate = gateMap.remove(gatePathStr);
-                final String gateId = thisGate.getGateId();
-                final String ccapId = gateCcapMap.remove(gatePathStr);
-                final Ccap thisCcap = ccapMap.get(ccapId);
-                final PCMMService service = pcmmServiceMap.get(thisCcap.getCcapId());
-                if (service != null) {
-                    service.sendGateDelete(gatePathStr);
-                    logger.info("onDataChanged(): removed QoS gate {} for {}/{}/{}: ", gateId, ccapId, gatePathStr, thisGate);
-                    logger.info("onDataChanged(): removed QoS gate {} for {}/{}: ", gateId, ccapId, gatePathStr);
-                } else
-                    logger.warn("Unable to send to locate PCMMService to send gate delete message with CCAP - "
-                            + thisCcap);
+
+    private class QosDataChangeListener extends AbstractDataChangeListener<Gate> {
+
+        private final DataValidator qosDataValidator = new DataValidator(new QosValidatorProviderFactory().build());
+        private final Set<InstanceIdentifier<Gate>> updateQueue = Sets.newConcurrentHashSet();
+
+        public QosDataChangeListener() {
+            super(Gate.class);
+        }
+
+        @Override
+        protected void handleCreatedData(final Map<InstanceIdentifier<Gate>, Gate> createdData) {
+
+            final Map<InstanceIdentifier<Gate>, ValidationException> errorMap =
+                    qosDataValidator.validateOneType(createdData, Validator.Extent.NODE_AND_SUBTREE);
+
+            // validate all new objects an update operational datastore
+            if (!errorMap.isEmpty()) {
+                // bad data write errors to operational datastore
+                saveErrors(errorMap, createdData);
+            }
+
+            if (createdData.size() > errorMap.size()) {
+                final Map<InstanceIdentifier<Gate>, Gate> goodData =
+                        Maps.newHashMapWithExpectedSize(createdData.size() - errorMap.size());
+                for (InstanceIdentifier<Gate> iid : createdData.keySet()) {
+                    if (!errorMap.containsKey(iid)) {
+                        goodData.put(iid, createdData.get(iid));
+                    }
+                }
+                addNewGates(goodData);
             }
+
         }
-        for (final String ccapIdStr: thisData.removePathList) {
-            if (ccapMap.containsKey(ccapIdStr)) {
-                final Ccap thisCcap = ccapMap.remove(ccapIdStr);
-                removeCcapFromAllMaps(thisCcap);
+
+        private void addNewGates(final Map<InstanceIdentifier<Gate>, Gate> createdGates) {
+
+            for (InstanceIdentifier<Gate> gateIID : createdGates.keySet()) {
+                final Gate newGate = createdGates.get(gateIID);
+
+                final String newGatePathStr = makeGatePathString(gateIID);
+
+                final InstanceIdentifier<Subscriber> subscriberIID = gateIID.firstIdentifierOf(Subscriber.class);
+                final SubscriberKey subscriberKey = InstanceIdentifier.keyOf(subscriberIID);
+                final InetAddress subscriberAddr = getInetAddress(subscriberKey.getSubscriberId());
+                if (subscriberAddr == null) {
+                    final String msg = String.format("subscriberId must be a valid ipaddress: %s",
+                            subscriberKey.getSubscriberId());
+                    logger.error(msg);
+                    saveGateError(gateIID, newGatePathStr, msg);
+                    continue;
+                }
+
+                final Ccap ccap = findCcapForSubscriberId(subscriberAddr);
+                if (ccap == null) {
+                    final String msg = String.format("Unable to find Ccap for subscriber %s: @ %s",
+                            subscriberKey.getSubscriberId(), newGatePathStr);
+                    logger.error(msg);
+                    saveGateError(gateIID, newGatePathStr, msg);
+                    continue;
+                }
+
+                final ServiceClassName scn = newGate.getTrafficProfile().getServiceClassName();
+                final ServiceFlowDirection scnDirection = findScnOnCcap(scn, ccap);
+                if (scnDirection == null) {
+                    final String msg = String.format("SCN %s not found on CCAP %s for %s",
+                            scn, ccap.getCcapId(), newGatePathStr);
+                    logger.error(msg);
+                    saveGateError(gateIID, newGatePathStr, msg);
+                    continue;
+                }
+
+                final PCMMService pcmmService = pcmmServiceMap.get(ccap.getCcapId());
+                if (pcmmService == null) {
+                    final String msg = String.format("Unable to locate PCMM Service for CCAP: %s ; with subscriber: %s",
+                            ccap, subscriberKey.getSubscriberId());
+                    logger.error(msg);
+                    saveGateError(gateIID, newGatePathStr, msg);
+                    continue;
+                }
+
+                PCMMService.GateSetStatus status = pcmmService.sendGateSet(newGatePathStr, subscriberAddr, newGate, scnDirection);
+                gateMap.put(newGatePathStr, newGate);
+                gateCcapMap.put(newGatePathStr, ccap.getCcapId());
+
+                final GateBuilder gateBuilder = new GateBuilder();
+                gateBuilder.setGateId(newGate.getGateId())
+                        .setGatePath(newGatePathStr)
+                        .setCcapId(ccap.getCcapId())
+                        .setCopsGateId(status.getCopsGateId())
+                        .setCopsState(status.didSucceed() ? "success" : "failure");
+                if (!status.didSucceed()) {
+                    gateBuilder.setError(Collections.singletonList(status.getMessage()));
+                }
+
+                Gate operationalGate = gateBuilder.build();
+
+                mdsalUtils.put(LogicalDatastoreType.OPERATIONAL, gateIID, operationalGate);
+
             }
+
         }
-    }
 
-    private void onUpdate(final InstanceData oldData) {
-        logger.info("onUpdate(): " + oldData);
-        // update operation not allowed -- restore the original config object and complain
-        if (! oldData.ccapIidMap.isEmpty()) {
-            for (final Map.Entry<InstanceIdentifier<Ccap>, Ccap> entry : oldData.ccapIidMap.entrySet()) {
-                final Ccap ccap = entry.getValue();
-                final String ccapId = ccap.getCcapId();
-                // restores the original data - although I don't think this is what is done here! I think the update data is put into the DS/config
-                mdsalUtils.merge(LogicalDatastoreType.CONFIGURATION, entry.getKey(), ccap);
-                logger.error("onDataChanged(): CCAP update not permitted {}/{}", ccapId, ccap);
+        private void saveGateError(@Nonnull final InstanceIdentifier<Gate> gateIID, @Nonnull final String gatePathStr,
+                @Nonnull final String error) {
+            checkNotNull(gateIID);
+            checkNotNull(error);
+
+            final GateBuilder gateBuilder = new GateBuilder();
+            gateBuilder.setGateId(InstanceIdentifier.keyOf(gateIID).getGateId())
+                    .setGatePath(gatePathStr)
+                    .setCopsGateId("")
+                    .setCopsState("N/A");
+
+                gateBuilder.setError(Collections.singletonList(error));
+
+            Gate operationalGate = gateBuilder.build();
+
+            mdsalUtils.put(LogicalDatastoreType.OPERATIONAL, gateIID, operationalGate);
+        }
+
+        @Override
+        protected void handleUpdatedData(final Map<InstanceIdentifier<Gate>, Gate> updatedData,
+                final Map<InstanceIdentifier<Gate>, Gate> originalData) {
+            // TODO actually support updates
+
+            // update operation not allowed -- restore the original config object and complain
+            for (final Map.Entry<InstanceIdentifier<Gate>, Gate> entry : updatedData.entrySet()) {
+                if (!originalData.containsKey(entry.getKey())) {
+                    logger.error("No original data found for supposedly updated data: {}", entry.getValue());
+                    continue;
+                }
+
+                // If this notification is coming from our modification ignore it.
+                if (updateQueue.contains(entry.getKey())) {
+                    updateQueue.remove(entry.getKey());
+                    continue;
+                }
+
+                final Gate originalGate = originalData.get(entry.getKey());
+
+                // restores the original data
+                updateQueue.add(entry.getKey());
+                mdsalUtils.put(LogicalDatastoreType.CONFIGURATION, entry.getKey(), originalGate);
+                logger.error("Update not permitted {}", entry.getKey());
+
             }
-        } else {
-            for (final Map.Entry<InstanceIdentifier<Gate>, Gate> entry : oldData.gateIidMap.entrySet()) {
-                final Gate gate = entry.getValue();
-                final String gatePathStr = oldData.gatePath + "/" + gate.getGateId() ;
-             // restores the original data - although I don't think this is what is done here! I think the update data is put into the DS/config
-                mdsalUtils.merge(LogicalDatastoreType.CONFIGURATION, entry.getKey(), gate);
-                logger.error("onDataChanged(): QoS Gate update not permitted: {}/{}", gatePathStr, gate);
+        }
+
+
+
+        @Override
+        protected void handleRemovedData(final Set<InstanceIdentifier<Gate>> removedPaths,
+                final Map<InstanceIdentifier<Gate>, Gate> originalData) {
+
+            for (final InstanceIdentifier<Gate> removedGateIID : removedPaths) {
+
+                mdsalUtils.delete(LogicalDatastoreType.OPERATIONAL, removedGateIID);
+                //TODO check if this was the last gate for this app/subscriber and if so delete them
+
+                executor.execute(new SubscriberCleaner(removedGateIID));
+
+                final String gatePathStr = makeGatePathString(removedGateIID);
+
+                    if (gateMap.containsKey(gatePathStr)) {
+                        final Gate thisGate = gateMap.remove(gatePathStr);
+                        final String gateId = thisGate.getGateId();
+                        final String ccapId = gateCcapMap.remove(gatePathStr);
+                        final Ccap thisCcap = ccapMap.get(ccapId);
+                        final PCMMService service = pcmmServiceMap.get(thisCcap.getCcapId());
+                        if (service != null) {
+                            service.sendGateDelete(gatePathStr);
+                            logger.info("onDataChanged(): removed QoS gate {} for {}/{}/{}: ", gateId, ccapId, gatePathStr,
+                                    thisGate);
+                        } else {
+                            logger.warn(
+                                    "Unable to send to locate PCMMService to send gate delete message with CCAP - " + thisCcap);
+                        }
+                    }
+
+
             }
+
+        }
+
+        private String makeGatePathString(InstanceIdentifier<Gate> iid) {
+            final InstanceIdentifier<App> appIID = iid.firstIdentifierOf(App.class);
+            final AppKey appKey = InstanceIdentifier.keyOf(appIID);
+
+            final InstanceIdentifier<Subscriber> subscriberIID = iid.firstIdentifierOf(Subscriber.class);
+            final SubscriberKey subscriberKey = InstanceIdentifier.keyOf(subscriberIID);
+
+            final GateKey gateKey = InstanceIdentifier.keyOf(iid);
+
+            return appKey.getAppId()
+                    + "/" + subscriberKey.getSubscriberId()
+                    + "/" + gateKey.getGateId();
         }
     }
+
 }
index b8caf655b1e74d73772be6158c00e522361e3b91..1c6b0151ba0f12b5525b46753fa6e018f510a48f 100644 (file)
@@ -1,15 +1,23 @@
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
 package org.opendaylight.controller.packetcable.provider;
-import org.apache.commons.lang3.builder.HashCodeBuilder;
 
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
 import java.math.BigInteger;
 import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
 
 /**
  * @author c3oe.de, based on snippets from Scott Plante, John Kugelmann
@@ -17,13 +25,13 @@ import static com.google.common.base.Preconditions.checkNotNull;
 public class Subnet
 {
     /** Minimum length of a v4 or v6 subnet mask */
-    private final static int MIN_MASK_BITS = 0;
+    private static final int MIN_MASK_BITS = 0;
 
     /** Maximum length of a v4 subnet mask */
-    private final static int MAX_MASK_BITS_V4 = 32;
+    private static final int MAX_MASK_BITS_V4 = 32;
 
     /** Maximum length of a v6 subnet mask */
-    private final static int MAX_MASK_BITS_V6 = 128;
+    private static final int MAX_MASK_BITS_V6 = 128;
 
     /** The length of the subnet prefix */
     private final int prefixLen;
@@ -154,7 +162,7 @@ public class Subnet
     }
 
     @Override
-    final public boolean equals( Object obj )
+    public final boolean equals( Object obj )
     {
         if (null == obj) return false;
         if (this == obj) return true;
@@ -168,7 +176,7 @@ public class Subnet
     }
 
     @Override
-    final public int hashCode()
+    public final int hashCode()
     {
         return new HashCodeBuilder(997, 311)
                 .append(prefixLen)
@@ -188,7 +196,7 @@ public class Subnet
         return buf.toString();
     }
 
-    static private void bigInteger2IpString( final StringBuilder buf, final BigInteger bigInteger, final int displayBytes )
+    private static void bigInteger2IpString( final StringBuilder buf, final BigInteger bigInteger, final int displayBytes )
     {
         final boolean isIPv4 = 4 == displayBytes;
         byte[] bytes = bigInteger.toByteArray();
@@ -211,4 +219,4 @@ public class Subnet
             buf.append( isIPv4 ? integer : Integer.toHexString( integer ) );
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/ValidateInstanceData.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/ValidateInstanceData.java
deleted file mode 100644 (file)
index 49afb8c..0000000
+++ /dev/null
@@ -1,951 +0,0 @@
-/**
- * Validate all instance data received from the config datastore via the onDataChange() notification.
- *
- * N.B. that yang typedefs are not validated when a PUT operation places them into the config datastore.
- * This means that they can arrive at onDataChange() with invalid values.
- *
- * In particular integer range values and string patterns (such as IP prefix/len) are not checked
- * and accessing these values via any object.getValue() method call will cause an exception (as yang
- * finally gets around to actually enforcing the typedef).
- */
-package org.opendaylight.controller.packetcable.provider;
-
-import java.lang.reflect.Method;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.IpAddress;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.IpPrefix;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv4Address;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv6Prefix;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.PortNumber;
-import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ServiceClassName;
-import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ServiceFlowDirection;
-import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.TosByte;
-import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.TpProtocol;
-import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ccap.attributes.AmId;
-import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ccap.attributes.AmIdBuilder;
-import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ccap.attributes.Connection;
-import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ccap.attributes.ConnectionBuilder;
-import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ccaps.Ccap;
-import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ccaps.CcapBuilder;
-import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.classifier.Classifier;
-import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.classifier.ClassifierBuilder;
-import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.ext.classifier.ExtClassifier;
-import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.ext.classifier.ExtClassifierBuilder;
-import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gate.spec.GateSpec;
-import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gate.spec.GateSpecBuilder;
-import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.subscribers.subscriber.gates.Gate;
-import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.subscribers.subscriber.gates.GateBuilder;
-import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.ipv6.classifier.Ipv6Classifier;
-import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.ipv6.classifier.Ipv6ClassifierBuilder;
-import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.traffic.profile.TrafficProfile;
-import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.traffic.profile.TrafficProfileBuilder;
-import org.opendaylight.yangtools.yang.binding.DataObject;
-import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class ValidateInstanceData {
-
-    private final static Logger logger = LoggerFactory.getLogger(ValidateInstanceData.class);
-
-    // Final members
-    private final MdsalUtils mdsalUtils;
-
-    // Gate Identities
-    private final Map<InstanceIdentifier<Gate>, Gate> gateIidMap;
-
-    // CCAP Identity
-    private transient Ccap ccap;
-    private transient InstanceIdentifier<Ccap> ccapIID;
-
-    public ValidateInstanceData(final MdsalUtils mdsalUtils, final Map<InstanceIdentifier<?>, DataObject> thisData) {
-        this.mdsalUtils = mdsalUtils;
-        getCcap(thisData);
-
-        // Must be instantiated prior to retreiving the gates below
-        gateIidMap = new ConcurrentHashMap<>();
-
-        // TODO FIXME - this value is always null???
-        if (ccap == null) {
-            getGates(thisData);
-        }
-    }
-
-    public boolean validateYang() {
-        if (ccap != null) {
-            if (! validateCcap(ccap)) {
-                logger.error("Validate CCAP {} failed - {}", ccap.getCcapId());
-                mdsalUtils.delete(LogicalDatastoreType.CONFIGURATION, ccapIID);
-                return false;
-            }
-        } else if (! gateIidMap.isEmpty()) {
-            for (Map.Entry<InstanceIdentifier<Gate>, Gate> entry : gateIidMap.entrySet()) {
-                InstanceIdentifier<Gate> gateIID = entry.getKey();
-                Gate gate = entry.getValue();
-                if (! validateGate(gate)) {
-                    logger.error("Validate Gate {} failed - {}", gate.getGateId());
-                    mdsalUtils.delete(LogicalDatastoreType.CONFIGURATION, gateIID);
-                    return false;
-                }
-            }
-        }
-        return true;
-    }
-
-    private void getCcap(final Map<InstanceIdentifier<?>, DataObject> thisData) {
-        for (final Map.Entry<InstanceIdentifier<?>, DataObject> entry : thisData.entrySet()) {
-            if (entry.getValue() instanceof Ccap) {
-                ccap = (Ccap)entry.getValue();
-                // TODO FIXME - ClassCastException waiting to occur here!!!
-                ccapIID = (InstanceIdentifier<Ccap>) entry.getKey();
-            }
-        }
-    }
-
-    private void getGates(final Map<InstanceIdentifier<?>, DataObject> thisData) {
-        for (final Map.Entry<InstanceIdentifier<?>, DataObject> entry : thisData.entrySet()) {
-            if (entry.getValue() instanceof Gate) {
-                final Gate gate = (Gate)entry.getValue();
-                // TODO FIXME - ClassCastException waiting to occur here!!!
-                final InstanceIdentifier<Gate> gateIID = (InstanceIdentifier<Gate>)entry.getKey();
-                gateIidMap.put(gateIID, gate);
-            }
-        }
-    }
-    private String validateMethod(final Class<?> thisClass, final Object thisObj, final String methodName) {
-        try {
-            final Method method = thisClass.getMethod(methodName);
-            method.invoke(thisObj);
-        } catch (IllegalArgumentException e) {
-            return e.getMessage();
-        } catch (Exception e) {
-            return " ";
-//          error = String.format("%s.%s(): Method failed: %s ", thisClass.getSimpleName(), methodName, e.getMessage());
-        }
-        return null;
-    }
-
-    private boolean validateGateSpec(final Gate gate, final GateBuilder gateBuilder) {
-        // gate-spec
-        String message = "";
-        String error;
-        boolean valid = true;
-        GateSpec gateSpec = gate.getGateSpec();
-        if (gateSpec != null) {
-            final ServiceFlowDirection dir;
-            error = validateMethod(GateSpec.class, gateSpec, "getDirection");
-            if (error == null) {
-                dir = gateSpec.getDirection();
-                if (dir != null) {
-                    if (gate.getTrafficProfile().getServiceClassName() != null) {
-                        message += " gate-spec.direction not allowed for traffic-profile.SCN;";
-                        valid = false;
-                    }
-                }
-            } else {
-                message += " gate-spec.direction invalid: must be 'us' or 'ds' -" + error;
-                dir = null;
-                valid = false;
-            }
-            final TosByte tosByte;
-            error = validateMethod(GateSpec.class, gateSpec, "getDscpTosOverwrite");
-            if (error == null) {
-                tosByte = gateSpec.getDscpTosOverwrite();
-            } else {
-                message += " gate-spec.dscp-tos-overwrite invalid: " + error;
-                tosByte = null;
-                valid = false;
-            }
-            final TosByte tosMask;
-            error = validateMethod(GateSpec.class, gateSpec, "getDscpTosMask");
-            if (error == null) {
-                tosMask = gateSpec.getDscpTosMask();
-                if (tosByte != null && tosMask == null) {
-                    message += " gate-spec.dscp-tos-mask missing;";
-                    valid = false;
-                }
-            } else {
-                message += " gate-spec.dscp-tos-mask invalid: " + error;
-                tosMask = null;
-                valid = false;
-            }
-            if (! valid) {
-                // rebuild the gateSpec with nulls replacing bad values
-                final GateSpecBuilder gateSpecBuilder = new GateSpecBuilder();
-                gateSpecBuilder.setDirection(dir);
-                gateSpecBuilder.setDscpTosOverwrite(tosByte);
-                gateSpecBuilder.setDscpTosMask(tosMask);
-                gateSpec = gateSpecBuilder.build();
-                // update the gate
-                gateBuilder.setGateSpec(gateSpec);
-            }
-        }
-        if (! valid) {
-            logger.error(message);
-        }
-        return valid;
-    }
-
-    private boolean validateTrafficProfile(final Gate gate, final GateBuilder gateBuilder) {
-        // traffic-profile
-        String message = "";
-        boolean valid = true;
-        TrafficProfile profile = gate.getTrafficProfile();
-        if (profile == null) {
-            message += " traffic-profile is required;";
-            valid = false;
-        } else {
-            final ServiceClassName scn;
-            final String error = validateMethod(TrafficProfile.class, profile, "getServiceClassName");
-            if (error == null) {
-                scn = profile.getServiceClassName();
-                if (scn == null) {
-                    message += " traffic-profile.service-class-name missing;";
-                    valid = false;
-                }
-            } else {
-                message += " traffic-profile.service-class-name invalid: must be 2-16 characters " + error;
-                scn = null;
-                valid = false;
-            }
-            if (! valid) {
-                final TrafficProfileBuilder profileBuilder = new TrafficProfileBuilder();
-                // TODO FIXME - scn is always null???
-                profileBuilder.setServiceClassName(scn);
-                profile = profileBuilder.build();
-                // update the gate
-                gateBuilder.setTrafficProfile(profile);
-            }
-        }
-        if (! valid) {
-            logger.error(message);
-        }
-        return valid;
-    }
-
-    // TODO FIXME - Break this method apart
-    private boolean validateClassifier(final Gate gate, final GateBuilder gateBuilder) {
-        // validate classifier
-        String message = "";
-        boolean valid = true;
-        int count = 0;
-        Classifier classifier = gate.getClassifier();
-        // SIP
-        final Ipv4Address sip;
-        String error = validateMethod(Classifier.class, classifier, "getSrcIp");
-        if (error == null) {
-            sip = classifier.getSrcIp();
-            count++;
-        } else {
-            message += " classifier.srcIp invalid: - " + error;
-            sip = null;
-            valid = false;
-        }
-        // DIP
-        final Ipv4Address dip;
-        error = validateMethod(Classifier.class, classifier, "getDstIp");
-        if (error == null) {
-            dip = classifier.getDstIp();
-            count++;
-        } else {
-            message += " classifier.dstIp invalid: - " + error;
-            dip = null;
-            valid = false;
-        }
-        // Protocol
-        final TpProtocol proto;
-        error = validateMethod(Classifier.class, classifier, "getProtocol");
-        if (error == null) {
-            proto = classifier.getProtocol();
-            count++;
-        } else {
-            message += " classifier.protocol invalid: - " + error;
-            proto = null;
-            valid = false;
-        }
-        // Source Port
-        final PortNumber sport;
-        error = validateMethod(Classifier.class, classifier, "getSrcPort");
-        if (error == null) {
-            sport = classifier.getSrcPort();
-            count++;
-        } else {
-            message += " classifier.srcPort invalid: - " + error;
-            sport = null;
-            valid = false;
-        }
-        // Destination Port
-        final PortNumber dport;
-        error = validateMethod(Classifier.class, classifier, "getDstPort");
-        if (error == null) {
-            dport = classifier.getDstPort();
-            count++;
-        } else {
-            message += " classifier.dstPort invalid: - " + error;
-            dport = null;
-            valid = false;
-        }
-        // TOS
-        final TosByte tosByte;
-        error = validateMethod(Classifier.class, classifier, "getTosByte");
-        if (error == null) {
-            tosByte = classifier.getTosByte();
-            count++;
-        } else {
-            message += " classifier.tosByte invalid: " + error;
-            tosByte = null;
-            valid = false;
-        }
-        final TosByte tosMask;
-        error = validateMethod(Classifier.class, classifier, "getTosMask");
-        if (error == null) {
-            tosMask = classifier.getTosMask();
-            if (tosByte != null && tosMask == null) {
-                message += " classifier.tosMask missing;";
-                valid = false;
-            }
-        } else {
-            message += " classifier.tosMask invalid: " + error;
-            tosMask = null;
-            valid = false;
-        }
-        if (count == 0) {
-            message += " classifer must have at least one match field";
-            valid = false;
-        }
-        if (! valid) {
-            final ClassifierBuilder cBuilder = new ClassifierBuilder();
-            cBuilder.setSrcIp(sip);
-            cBuilder.setDstIp(dip);
-            cBuilder.setProtocol(proto);
-            cBuilder.setSrcPort(sport);
-            cBuilder.setDstPort(dport);
-            cBuilder.setTosByte(tosByte);
-            cBuilder.setTosMask(tosMask);
-            classifier = cBuilder.build();
-            gateBuilder.setClassifier(classifier);
-            logger.error(message);
-        }
-        return valid;
-    }
-
-    // TODO FIXME - breakup this method
-    private boolean validateExtClassifier(final Gate gate, final GateBuilder gateBuilder) {
-        // validate ext-classifier
-        String message = "";
-        String error;
-        boolean valid = true;
-        int count = 0;
-        ExtClassifier extClassifier = gate.getExtClassifier();
-        // SIP & mask
-        final Ipv4Address sip;
-        error = validateMethod(ExtClassifier.class, extClassifier, "getSrcIp");
-        if (error == null) {
-            sip = extClassifier.getSrcIp();
-            count++;
-        } else {
-            message += " ext-classifier.srcIp invalid: - " + error;
-            sip = null;
-            valid = false;
-        }
-        final Ipv4Address sipMask;
-        error = validateMethod(ExtClassifier.class, extClassifier, "getSrcIpMask");
-        if (error == null) {
-            sipMask = extClassifier.getSrcIpMask();
-            count++;
-        } else {
-            message += " ext-classifier.srcIpMask invalid: - " + error;
-            sipMask = null;
-            valid = false;
-        }
-        if (sip != null && sipMask == null) {
-            message += " ext-classifier.srcIpMask missing";
-            valid = false;
-        }
-        // DIP & mask
-        final Ipv4Address dip;
-        error = validateMethod(ExtClassifier.class, extClassifier, "getDstIp");
-        if (error == null) {
-            dip = extClassifier.getDstIp();
-            count++;
-        } else {
-            message += " ext-classifier.dstIp invalid: - " + error;
-            dip = null;
-            valid = false;
-        }
-        final Ipv4Address dipMask;
-        error = validateMethod(ExtClassifier.class, extClassifier, "getDstIpMask");
-        if (error == null) {
-            dipMask = extClassifier.getDstIpMask();
-            count++;
-        } else {
-            message += " ext-classifier.srcIpMask invalid: - " + error;
-            dipMask = null;
-            valid = false;
-        }
-        if (dip != null && dipMask == null) {
-            message += " ext-classifier.dstIpMask missing;";
-            valid = false;
-        }
-        // Protocol
-        final TpProtocol proto;
-        error = validateMethod(ExtClassifier.class, extClassifier, "getProtocol");
-        if (error == null) {
-            proto = extClassifier.getProtocol();
-            count++;
-        } else {
-            message += " ext-classifier.protocol invalid: - " + error;
-            proto = null;
-            valid = false;
-        }
-        // Source port range
-        final PortNumber sportStart;
-        error = validateMethod(ExtClassifier.class, extClassifier, "getSrcPortStart");
-        if (error == null) {
-            sportStart = extClassifier.getSrcPortStart();
-            count++;
-        } else {
-            message += " ext-classifier.srcPortStart invalid: - " + error;
-            sportStart = null;
-            valid = false;
-        }
-        final PortNumber sportEnd;
-        error = validateMethod(ExtClassifier.class, extClassifier, "getSrcPortEnd");
-        if (error == null) {
-            sportEnd = extClassifier.getSrcPortEnd();
-            count++;
-        } else {
-            message += " ext-classifier.srcPortEnd invalid: - " + error;
-            sportEnd = null;
-            valid = false;
-        }
-        if (sportStart != null && sportEnd != null) {
-            if (sportStart.getValue() > sportEnd.getValue()) {
-                message += " ext-classifier.srcPortStart greater than srcPortEnd";
-                valid = false;
-            }
-        }
-        // Destination port range
-        final PortNumber dportStart;
-        error = validateMethod(ExtClassifier.class, extClassifier, "getDstPortStart");
-        if (error == null) {
-            dportStart = extClassifier.getDstPortStart();
-            count++;
-        } else {
-            message += " ext-classifier.dstPortStart invalid: - " + error;
-            dportStart = null;
-            valid = false;
-        }
-        final PortNumber dportEnd;
-        error = validateMethod(ExtClassifier.class, extClassifier, "getDstPortEnd");
-        if (error == null) {
-            dportEnd = extClassifier.getDstPortEnd();
-            count++;
-        } else {
-            message += " ext-classifier.dstPortEnd invalid: - " + error;
-            dportEnd = null;
-            valid = false;
-        }
-        if (dportStart != null && dportEnd != null) {
-            if (dportStart.getValue() > dportEnd.getValue()) {
-                message += " ext-classifier.dstPortStart greater than dstPortEnd";
-                valid = false;
-            }
-        }
-        // TOS byte
-        final TosByte tosByte;
-        error = validateMethod(ExtClassifier.class, extClassifier, "getTosByte");
-        if (error == null) {
-            tosByte = extClassifier.getTosByte();
-            count++;
-        } else {
-            message += " ext-classifier.tosByte invalid: " + error;
-            tosByte = null;
-            valid = false;
-        }
-        final TosByte tosMask;
-        error = validateMethod(ExtClassifier.class, extClassifier, "getTosMask");
-        if (error == null) {
-            tosMask = extClassifier.getTosMask();
-            if (tosByte != null && tosMask == null) {
-                message += " ext-classifier.tosMask missing;";
-                valid = false;
-            }
-        } else {
-            message += " ext-classifier.tosMask invalid: " + error;
-            tosMask = null;
-            valid = false;
-        }
-        if (count == 0) {
-            message += " ext-classifer must have at least one match field";
-            valid = false;
-        }
-        if (! valid) {
-            final ExtClassifierBuilder cBuilder = new ExtClassifierBuilder();
-            cBuilder.setSrcIp(sip);
-            cBuilder.setSrcIpMask(sipMask);
-            cBuilder.setDstIp(dip);
-            cBuilder.setDstIpMask(dipMask);
-            cBuilder.setProtocol(proto);
-            cBuilder.setSrcPortStart(sportStart);
-            cBuilder.setSrcPortEnd(sportEnd);
-            cBuilder.setDstPortStart(dportStart);
-            cBuilder.setDstPortEnd(dportEnd);
-            cBuilder.setTosByte(tosByte);
-            cBuilder.setTosMask(tosMask);
-            extClassifier = cBuilder.build();
-            gateBuilder.setExtClassifier(extClassifier);
-            logger.error(message);
-        }
-        return valid;
-    }
-
-    // TODO FIXME - break apart this method.
-    private boolean validateIpv6Classifier(final Gate gate, final GateBuilder gateBuilder) {
-        // validate ipv6-classifier
-        String message = "";
-        String error;
-        boolean valid = true;
-        int count = 0;
-        Ipv6Classifier ipv6Classifier = gate.getIpv6Classifier();
-        // Source IPv6 prefix
-        final Ipv6Prefix sip6;
-        error = validateMethod(Ipv6Classifier.class, ipv6Classifier, "getSrcIp6");
-        if (error == null) {
-            sip6 = ipv6Classifier.getSrcIp6();
-            count++;
-        } else {
-            message += " ipv6-classifier.srcIp invalid: - " + error;
-            sip6 = null;
-            valid = false;
-        }
-        // Destination IPv6 prefix
-        final Ipv6Prefix dip6;
-        error = validateMethod(Ipv6Classifier.class, ipv6Classifier, "getDstIp6");
-        if (error == null) {
-            dip6 = ipv6Classifier.getDstIp6();
-            count++;
-        } else {
-            message += " ipv6-classifier.dstIp invalid: - " + error;
-            dip6 = null;
-            valid = false;
-        }
-        // Flow label
-        Long flowLabel;
-        error = validateMethod(Ipv6Classifier.class, ipv6Classifier, "getFlowLabel");
-        if (error == null) {
-            flowLabel = ipv6Classifier.getFlowLabel();
-            if (flowLabel > 1048575) {
-                message += " ipv6-classifier.flowLabel invalid: - must be 0..1048575";
-                flowLabel = null;
-                valid = false;
-            } else {
-                count++;
-            }
-        } else {
-            message += " ipv6-classifier.flowLabel invalid: - " + error;
-            flowLabel = null;
-            valid = false;
-        }
-        // Next Hdr
-        final TpProtocol nxtHdr;
-        error = validateMethod(Ipv6Classifier.class, ipv6Classifier, "getNextHdr");
-        if (error == null) {
-            nxtHdr = ipv6Classifier.getNextHdr();
-            count++;
-        } else {
-            message += " ipv6-classifier.nextHdr invalid: - " + error;
-            nxtHdr = null;
-            valid = false;
-        }
-        // Source port range
-        final PortNumber sportStart;
-        error = validateMethod(Ipv6Classifier.class, ipv6Classifier, "getSrcPortStart");
-        if (error == null) {
-            sportStart = ipv6Classifier.getSrcPortStart();
-            count++;
-        } else {
-            message += " ipv6-classifier.srcPortStart invalid: - " + error;
-            sportStart = null;
-            valid = false;
-        }
-        final PortNumber sportEnd;
-        error = validateMethod(Ipv6Classifier.class, ipv6Classifier, "getSrcPortEnd");
-        if (error == null) {
-            sportEnd = ipv6Classifier.getSrcPortEnd();
-            count++;
-        } else {
-            message += " ipv6-classifier.srcPortEnd invalid: - " + error;
-            sportEnd = null;
-            valid = false;
-        }
-        if (sportStart != null && sportEnd != null) {
-            if (sportStart.getValue() > sportEnd.getValue()) {
-                message += " ipv6-classifier.srcPortStart greater than srcPortEnd";
-                valid = false;
-            }
-        }
-        // Destination port range
-        final PortNumber dportStart;
-        error = validateMethod(Ipv6Classifier.class, ipv6Classifier, "getDstPortStart");
-        if (error == null) {
-            dportStart = ipv6Classifier.getDstPortStart();
-            count++;
-        } else {
-            message += " ipv6-classifier.dstPortStart invalid: - " + error;
-            dportStart = null;
-            valid = false;
-        }
-        final PortNumber dportEnd;
-        error = validateMethod(Ipv6Classifier.class, ipv6Classifier, "getDstPortEnd");
-        if (error == null) {
-            dportEnd = ipv6Classifier.getDstPortEnd();
-            count++;
-        } else {
-            message += " ipv6-classifier.dstPortEnd invalid: - " + error;
-            dportEnd = null;
-            valid = false;
-        }
-        if (dportStart != null && dportEnd != null) {
-            if (dportStart.getValue() > dportEnd.getValue()) {
-                message += " ipv6-classifier.dstPortStart greater than dstPortEnd";
-                valid = false;
-            }
-        }
-        // TC byte
-        final TosByte tcLow;
-        error = validateMethod(Ipv6Classifier.class, ipv6Classifier, "getTcLow");
-        if (error == null) {
-            tcLow = ipv6Classifier.getTcLow();
-            count++;
-        } else {
-            message += " ipv6-classifier.tc-low invalid: " + error;
-            tcLow = null;
-            valid = false;
-        }
-        final TosByte tcHigh;
-        error = validateMethod(Ipv6Classifier.class, ipv6Classifier, "getTcHigh");
-        if (error == null) {
-            tcHigh = ipv6Classifier.getTcHigh();
-            count++;
-        } else {
-            message += " ipv6-classifier.tc-high invalid: " + error;
-            tcHigh = null;
-            valid = false;
-        }
-        if (tcLow != null && tcHigh != null) {
-            if (tcLow.getValue() > tcHigh.getValue()) {
-                message += " ipv6-classifier.tc-low is greater than tc-high";
-                valid = false;
-            }
-        }
-        final TosByte tcMask;
-        error = validateMethod(Ipv6Classifier.class, ipv6Classifier, "getTcMask");
-        if (error == null) {
-            tcMask = ipv6Classifier.getTcMask();
-        } else {
-            message += " ipv6-classifier.tc-mask invalid: " + error;
-            tcMask = null;
-            valid = false;
-        }
-        if (tcLow != null && tcHigh != null && tcMask == null) {
-            message += " ipv6-classifier.tc-mask missing;";
-            valid = false;
-        }
-        if (count == 0) {
-            message += " ipv6-classifer must have at least one match field";
-            valid = false;
-        }
-        // rebuild ?
-        if (! valid) {
-            final Ipv6ClassifierBuilder cBuilder = new Ipv6ClassifierBuilder();
-            cBuilder.setSrcIp6(sip6);
-            cBuilder.setDstIp6(dip6);
-            cBuilder.setFlowLabel(flowLabel);
-            cBuilder.setNextHdr(nxtHdr);
-            cBuilder.setSrcPortStart(sportStart);
-            cBuilder.setSrcPortEnd(sportEnd);
-            cBuilder.setDstPortStart(dportStart);
-            cBuilder.setDstPortEnd(dportEnd);
-            cBuilder.setTcLow(tcLow);
-            cBuilder.setTcHigh(tcHigh);
-            cBuilder.setTcMask(tcMask);
-            ipv6Classifier = cBuilder.build();
-            gateBuilder.setIpv6Classifier(ipv6Classifier);
-            logger.error(message);
-        }
-        return valid;
-    }
-
-    // TODO FIXME - Do we really want the gate parameter object to be muted by this method?
-    private boolean validateGate(Gate gate) {
-        // validate gate elements and null out invalid elements as we go
-        final GateBuilder gateBuilder = new GateBuilder();
-        String message = "";
-        boolean rebuild = false;
-        // gate-spec
-        if (! validateGateSpec(gate, gateBuilder)) {
-            rebuild = true;
-        }
-        // traffic-profile
-        if (! validateTrafficProfile(gate, gateBuilder)) {
-            rebuild = true;
-        }
-        // classifiers (one of legacy classifier, ext-classifier, or ipv6 classifier
-        final Classifier classifier = gate.getClassifier();
-        final ExtClassifier extClassifier = gate.getExtClassifier();
-        final Ipv6Classifier ipv6Classifier = gate.getIpv6Classifier();
-        int count = 0;
-        if (classifier != null) { count++; }
-        if (extClassifier != null) { count++; }
-        if (ipv6Classifier != null) { count++; }
-        if (count < 1){
-            message = " Missing classifer: must have only 1 of classifier, ext-classifier, or ipv6-classifier";
-            rebuild = true;
-        } else if (count > 1) {
-            message = "Multiple classifiers: must have only 1 of classifier, ext-classifier, or ipv6-classifier";
-            rebuild = true;
-        } else if (count == 1) {
-            if (classifier != null) {
-                // validate classifier
-                if (! validateClassifier(gate, gateBuilder)) {
-                    rebuild = true;
-                }
-            } else if (extClassifier != null) {
-                //validate ext-classifier
-                if (! validateExtClassifier(gate, gateBuilder)) {
-                    rebuild = true;
-                }
-            } else if (ipv6Classifier != null) {
-                // TODO FIXME - ipv6Classifier is always null???
-                // validate ipv6-classifier
-                if (! validateIpv6Classifier(gate, gateBuilder)) {
-                    rebuild = true;
-                }
-            }
-        }
-        // rebuild the gate object with valid data and set the response
-        if (rebuild) {
-            gateBuilder.setGateId(gate.getGateId());
-            gateBuilder.setKey(gate.getKey());
-            // TODO FIXME - the input parameter "gate" is being muted here???
-            gate = gateBuilder.build();
-            logger.error("Gate: {} - {}", gate, message);
-        }
-        return (! rebuild);
-    }
-
-    private boolean validateAmId(final Ccap ccap, final CcapBuilder ccapBuilder) {
-        // amId
-        String message = "";
-        String error;
-        boolean valid = true;
-        AmId amId = ccap.getAmId();
-        if (amId == null) {
-            message += " amId is required;";
-            valid = false;
-        } else {
-            final Integer amTag;
-            error = validateMethod(AmId.class, amId, "getAmTag");
-            if (error == null) {
-                amTag = amId.getAmTag();
-                if (amTag == null) {
-                    message += " amId.amTag missing;";
-                    valid = false;
-                }
-            } else {
-                message += " amId.amTag invalid: " + error;
-                amTag = null;
-                valid = false;
-            }
-            final Integer amType;
-            error = validateMethod(AmId.class, amId, "getAmType");
-            if (error == null) {
-                amType = amId.getAmType();
-                if (amType == null) {
-                    message += " amId.amType missing;";
-                    valid = false;
-                }
-            } else {
-                message += " amId.amType invalid: " + error;
-                amType = null;
-                valid = false;
-            }
-            if (! valid) {
-                final AmIdBuilder amIdBuilder = new AmIdBuilder();
-                amIdBuilder.setAmTag(amTag);
-                amIdBuilder.setAmType(amType);
-                amId = amIdBuilder.build();
-                ccapBuilder.setAmId(amId);
-            }
-        }
-        if (! valid) {
-            logger.error(message);
-        }
-        return valid;
-    }
-
-    private boolean validateConnection(final Ccap ccap, final CcapBuilder ccapBuilder) {
-        // connection
-        String message = "";
-        String error;
-        boolean valid = true;
-        Connection conn = ccap.getConnection();
-        if (conn == null) {
-            message += " connection is required;";
-            valid = false;
-        } else {
-            // IP address
-            final IpAddress ipAddress;
-            error = validateMethod(Connection.class, conn, "getIpAddress");
-            if (error == null) {
-                ipAddress = conn.getIpAddress();
-                if (ipAddress == null) {
-                    message += " connection.ipAddress missing;";
-                    valid = false;
-                }
-            } else {
-                message += " connection.ipAddress invalid: " + error;
-                ipAddress = null;
-                valid = false;
-            }
-            // Port number
-            final PortNumber portNum;
-            error = validateMethod(Connection.class, conn, "getPort");
-            if (error == null) {
-                portNum = conn.getPort();
-            } else {
-                message += " connection.port invalid: " + error;
-                portNum = null;
-                valid = false;
-            }
-            if (! valid) {
-                final ConnectionBuilder connBuilder = new ConnectionBuilder();
-                connBuilder.setIpAddress(ipAddress);
-                connBuilder.setPort(portNum);
-                conn = connBuilder.build();
-                ccapBuilder.setConnection(conn);
-            }
-        }
-        if (! valid) {
-            logger.error(message);
-        }
-        return valid;
-    }
-
-    private boolean validateSubscriberSubnets(final Ccap ccap, final CcapBuilder ccapBuilder) {
-        // subscriber-subnets
-        String message = "";
-        String error;
-        boolean valid = true;
-        List<IpPrefix> subnets = null;
-        error = validateMethod(Ccap.class, ccap, "getSubscriberSubnets");
-        if (error == null) {
-            subnets = ccap.getSubscriberSubnets();
-            if (subnets == null) {
-                message += " subscriber-subnets is required;";
-                valid = false;
-            }
-        } else {
-            message += " subscriber-subnets contains invalid IpPrefix - must be <ipaddress>/<prefixlen> format;" + error;
-            valid = false;
-        }
-        if (! valid) {
-            // TODO FIXME - subnets is always null???
-            ccapBuilder.setSubscriberSubnets(subnets);
-            logger.error(message);
-        }
-        return valid;
-    }
-
-    private boolean validateUpstreamScns(final Ccap ccap, final CcapBuilder ccapBuilder) {
-        // upstream-scns
-        String message = "";
-        String error;
-        boolean valid = true;
-        List<ServiceClassName> usScns = null;
-        error = validateMethod(Ccap.class, ccap, "getUpstreamScns");
-        if (error == null) {
-            usScns = ccap.getUpstreamScns();
-            if (usScns == null) {
-                message += " upstream-scns is required;";
-                valid = false;
-            }
-        } else {
-            message += " upstream-scns contains invalid SCN - must be 2-16 characters;" + error;
-            valid = false;
-        }
-        if (! valid) {
-            // TODO FIXME - usScns is always null???
-            ccapBuilder.setUpstreamScns(usScns);
-            logger.error(message);
-        }
-        return valid;
-    }
-
-    private boolean validateDownstreamScns(final Ccap ccap, final CcapBuilder ccapBuilder) {
-        // downstream-scns
-        String message = "";
-        boolean valid = true;
-        List<ServiceClassName> dsScns = null;
-        final String error = validateMethod(Ccap.class, ccap, "getDownstreamScns");
-        if (error == null) {
-            dsScns = ccap.getDownstreamScns();
-            if (dsScns == null) {
-                message += " downstream-scns is required;";
-                valid = false;
-            }
-        } else {
-            message += " downstream-scns contains invalid SCN - must be 2-16 characters;" + error;
-            valid = false;
-        }
-        if (! valid) {
-            // TODO FIXME - dsScns is always null???
-            ccapBuilder.setDownstreamScns(dsScns);
-            logger.error(message);
-        }
-        return valid;
-    }
-
-
-    // TODO FIXME - Do we really want the ccap parameter object to be muted by this method?
-    private boolean validateCcap(Ccap ccap) {
-        // validate ccap and null out invalid elements as we go
-        final CcapBuilder ccapBuilder = new CcapBuilder();
-        String message = "";
-        boolean rebuild = false;
-        // amId
-        if ( ! validateAmId(ccap, ccapBuilder))        {
-            rebuild = true;
-        }
-        // connection
-        if ( ! validateConnection(ccap, ccapBuilder))        {
-            rebuild = true;
-        }
-        // subscriber-subnets
-        if ( ! validateSubscriberSubnets(ccap, ccapBuilder))        {
-            rebuild = true;
-        }
-        // upstream-scns
-        if ( ! validateUpstreamScns(ccap, ccapBuilder))        {
-            rebuild = true;
-        }
-        // downstream-scns
-        if ( ! validateDownstreamScns(ccap, ccapBuilder))        {
-            rebuild = true;
-        }
-        // rebuild the ccap object with valid data and set the response
-        if (rebuild) {
-            ccapBuilder.setCcapId(ccap.getCcapId());
-            ccapBuilder.setKey(ccap.getKey());
-            // TODO FIXME - the input parameter "ccap" is being muted here???
-            ccap = ccapBuilder.build();
-            logger.error("Ccap: {} - {} ", ccap, message);
-        }
-        return (! rebuild);
-    }
-}
-
diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/DataValidator.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/DataValidator.java
new file mode 100644 (file)
index 0000000..906ce9a
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.packetcable.provider.validation;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.collect.Maps;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Validates restconf data that is supplied by users
+ *
+ * @author rvail
+ */
+public class DataValidator {
+    private static final Logger logger = LoggerFactory.getLogger(DataValidator.class);
+
+    private final ValidatorProvider validatorProvider;
+
+    public DataValidator(@Nonnull final ValidatorProvider validatorProvider) {
+        this.validatorProvider = checkNotNull(validatorProvider);
+    }
+
+    public Map<InstanceIdentifier<?>, ValidationException> validate(
+            @Nonnull final Map<InstanceIdentifier<?>, DataObject> dataObjectMap,
+            @Nonnull final Validator.Extent extent) {
+        checkNotNull(dataObjectMap);
+
+        Map<InstanceIdentifier<?>, ValidationException> exceptionMap = Maps.newHashMap();
+
+        for (Map.Entry<InstanceIdentifier<?>, DataObject> entry : dataObjectMap.entrySet()) {
+            final InstanceIdentifier<?> iid = entry.getKey();
+            final DataObject data = entry.getValue();
+
+            try {
+                validate(iid, data, extent);
+            } catch (ValidationException e) {
+                exceptionMap.put(iid, e);
+                logger.debug("invalid data: {}", data, e);
+            } catch (NoSuchElementException e) {
+                logger.error("Unable to find validator for data: {}", data, e);
+            }
+        }
+
+        return exceptionMap;
+    }
+
+    public void validate(@Nonnull InstanceIdentifier<?> iid, @Nonnull final DataObject dataObject,
+            @Nonnull final Validator.Extent extent) throws ValidationException {
+        checkNotNull(iid);
+        checkNotNull(dataObject);
+        validatorProvider.validate(iid.getTargetType(), dataObject, extent);
+    }
+
+    public <T extends DataObject> Map<InstanceIdentifier<T>, ValidationException> validateOneType(
+            @Nonnull final Map<InstanceIdentifier<T>, T> dataObjectMap, @Nonnull final Validator.Extent extent) {
+        checkNotNull(dataObjectMap);
+
+        Map<InstanceIdentifier<T>, ValidationException> exceptionMap = Maps.newHashMap();
+
+        for (Map.Entry<InstanceIdentifier<T>, T> entry : dataObjectMap.entrySet()) {
+            final InstanceIdentifier<T> iid = entry.getKey();
+            final T data = entry.getValue();
+
+            try {
+                validate(iid, data, extent);
+            } catch (ValidationException e) {
+                exceptionMap.put(iid, e);
+                logger.debug("invalid data: {}", data, e);
+            } catch (NoSuchElementException e) {
+                logger.error("Unable to find validator for data: {}", data, e);
+            }
+        }
+
+        return exceptionMap;
+    }
+
+
+}
diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/ValidationException.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/ValidationException.java
new file mode 100644 (file)
index 0000000..36bcaac
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.packetcable.provider.validation;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+
+/**
+ * @author rvail
+ */
+public class ValidationException extends Exception {
+
+    private final ImmutableList<String> errorMessages;
+
+    public ValidationException(final String... errorMessages) {
+        super(concat(Arrays.asList(errorMessages)));
+        this.errorMessages = ImmutableList.copyOf(errorMessages);
+    }
+
+    private static String concat(Collection<String> strings) {
+        checkNotNull(strings);
+
+        final Iterator<String> iter = strings.iterator();
+        if (!iter.hasNext()) {
+            return "";
+        }
+
+        StringBuilder sb = new StringBuilder(iter.next());
+        while (iter.hasNext()) {
+            sb.append(" : ").append(iter.next());
+        }
+
+        return sb.toString();
+    }
+
+    public ValidationException(final Collection<String> errorMessages) {
+        super(concat(errorMessages));
+        this.errorMessages = ImmutableList.copyOf(errorMessages);
+    }
+
+    public ImmutableList<String> getErrorMessages() {
+        return errorMessages;
+    }
+}
diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/Validator.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/Validator.java
new file mode 100644 (file)
index 0000000..219acef
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.packetcable.provider.validation;
+
+/**
+ * @author rvail
+ */
+public interface Validator<T> {
+
+    void validate(final T data, Extent extent) throws ValidationException;
+
+    enum Extent {
+        NODE_ONLY,
+        NODE_AND_SUBTREE
+    }
+
+}
diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/ValidatorProvider.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/ValidatorProvider.java
new file mode 100644 (file)
index 0000000..20d1d25
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.packetcable.provider.validation;
+
+import java.util.NoSuchElementException;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+
+/**
+ * Helper class to hide the casting needed to store all the validators in one collection.
+ * Types remain consistent by using generics on the put method.
+ *
+ * @author rvail
+ */
+public interface ValidatorProvider {
+
+    /**
+     * Add a new validator or replace an old validator to this provider.
+     *
+     * @param tClass
+     *         The Class of T
+     * @param validator
+     *         The validator for the Class T
+     * @param <T>
+     *         The type being validated
+     */
+    <T extends DataObject> void put(@Nonnull final Class<T> tClass, @Nonnull final Validator<T> validator);
+
+    /**
+     * Gets the validator for a particular type
+     *
+     * @param tClass
+     *         The Class of T
+     * @param <T>
+     *         The type to be validated
+     * @return a Validator instance
+     * @throws NoSuchElementException
+     *         if a Validator for the passed in type does not exist on this provider.
+     */
+    <T extends DataObject> Validator<T> validatorFor(@Nonnull final Class<T> tClass);
+
+    /**
+     * Helper method to get a validator and then call validate with the supplied DataObject.
+     *
+     * @param tClass
+     *         The class type to validate
+     * @param data
+     *         The DataObject instance to validate
+     * @param extent
+     *         The extend to validate with
+     * @param <T>
+     *         The type of tClass
+     * @throws ValidationException
+     *         if validation fails
+     * @throws IllegalArgumentException
+     *         if data is not assignable from tClass
+     */
+    <T extends DataObject> void validate(@Nonnull final Class<T> tClass, @Nonnull final DataObject data,
+            @Nonnull final Validator.Extent extent) throws ValidationException;
+}
diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/ValidatorProviderFactory.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/ValidatorProviderFactory.java
new file mode 100644 (file)
index 0000000..23bf493
--- /dev/null
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.packetcable.provider.validation;
+
+/**
+ * @author rvail
+ */
+public interface ValidatorProviderFactory {
+
+    ValidatorProvider build();
+
+}
diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/CcapsValidatorProviderFactory.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/CcapsValidatorProviderFactory.java
new file mode 100644 (file)
index 0000000..82ad94b
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.packetcable.provider.validation.impl;
+
+import org.opendaylight.controller.packetcable.provider.validation.ValidatorProvider;
+import org.opendaylight.controller.packetcable.provider.validation.ValidatorProviderFactory;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.ccaps.AmIdValidator;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.ccaps.CcapValidator;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.ccaps.CcapsValidator;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.ccaps.ConnectionValidator;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.Ccaps;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ccap.attributes.AmId;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ccap.attributes.Connection;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ccaps.Ccap;
+
+/**
+ * A ValidatorProviderFactory that can provide validators for types under packetcable:ccaps.
+ *
+ * @author rvail
+ */
+public class CcapsValidatorProviderFactory implements ValidatorProviderFactory {
+
+    @Override
+    public ValidatorProvider build() {
+        return addCcapsValidators(new ValidatorProviderImpl());
+    }
+
+    public static ValidatorProvider addCcapsValidators(ValidatorProvider provider) {
+        provider.put(Ccaps.class, new CcapsValidator());
+        provider.put(Ccap.class, new CcapValidator());
+        provider.put(AmId.class, new AmIdValidator());
+        provider.put(Connection.class, new ConnectionValidator());
+
+        return provider;
+    }
+}
diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/QosValidatorProviderFactory.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/QosValidatorProviderFactory.java
new file mode 100644 (file)
index 0000000..f8e541e
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.packetcable.provider.validation.impl;
+
+import org.opendaylight.controller.packetcable.provider.validation.ValidatorProvider;
+import org.opendaylight.controller.packetcable.provider.validation.ValidatorProviderFactory;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.qos.AppValidator;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.qos.AppsValidator;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.qos.GateValidator;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.qos.GatesValidator;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.qos.SubscriberValidator;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.qos.SubscribersValidator;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.qos.TrafficProfileValidator;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.qos.classifier.ClassifierValidator;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.qos.classifier.ExtClassifierValidator;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.qos.classifier.Ipv6ClassifierValidator;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.classifier.Classifier;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.ext.classifier.ExtClassifier;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.Apps;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.App;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.Subscribers;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.subscribers.Subscriber;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.subscribers.subscriber.Gates;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.subscribers.subscriber.gates.Gate;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.ipv6.classifier.Ipv6Classifier;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.traffic.profile.TrafficProfile;
+
+/**
+ * * A ValidatorProviderFactory that can provide validators for types under packetcable:qos.
+ *
+ * @author rvail
+ */
+public class QosValidatorProviderFactory implements ValidatorProviderFactory {
+
+    @Override
+    public ValidatorProvider build() {
+        return addQosValidators(new ValidatorProviderImpl());
+    }
+
+    public static ValidatorProvider addQosValidators(ValidatorProvider provider) {
+        provider.put(Apps.class, new AppsValidator());
+        provider.put(App.class, new AppValidator());
+
+        provider.put(Subscribers.class, new SubscribersValidator());
+        provider.put(Subscriber.class, new SubscriberValidator());
+
+        provider.put(Gates.class, new GatesValidator());
+        provider.put(Gate.class, new GateValidator());
+
+        provider.put(TrafficProfile.class, new TrafficProfileValidator());
+
+        provider.put(Classifier.class, new ClassifierValidator());
+        provider.put(ExtClassifier.class, new ExtClassifierValidator());
+        provider.put(Ipv6Classifier.class, new Ipv6ClassifierValidator());
+
+        return provider;
+    }
+}
diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/ValidatorProviderFactoryImpl.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/ValidatorProviderFactoryImpl.java
new file mode 100644 (file)
index 0000000..a013a54
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.packetcable.provider.validation.impl;
+
+import org.opendaylight.controller.packetcable.provider.validation.ValidatorProvider;
+import org.opendaylight.controller.packetcable.provider.validation.ValidatorProviderFactory;
+
+/**
+ * A ValidatorProviderFactory that returns providers that can handle all known types.
+ *
+ * @author rvail
+ */
+public class ValidatorProviderFactoryImpl implements ValidatorProviderFactory {
+
+    @Override
+    public ValidatorProvider build() {
+        ValidatorProvider provider = new ValidatorProviderImpl();
+
+        CcapsValidatorProviderFactory.addCcapsValidators(provider);
+        QosValidatorProviderFactory.addQosValidators(provider);
+
+        return provider;
+    }
+}
diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/ValidatorProviderImpl.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/ValidatorProviderImpl.java
new file mode 100644 (file)
index 0000000..c1c8f57
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.packetcable.provider.validation.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.collect.Maps;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import javax.annotation.Nonnull;
+import org.opendaylight.controller.packetcable.provider.validation.ValidationException;
+import org.opendaylight.controller.packetcable.provider.validation.Validator;
+import org.opendaylight.controller.packetcable.provider.validation.ValidatorProvider;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+
+
+/**
+ * {@inheritDoc}
+ *
+ * @author rvail
+ */
+public class ValidatorProviderImpl implements ValidatorProvider {
+
+    private final Map<Class<? extends DataObject>, Validator<? extends DataObject>> validatorMap;
+
+    public ValidatorProviderImpl() {
+        this.validatorMap = Maps.newHashMap();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public <T extends DataObject> void put(@Nonnull final Class<T> tClass, @Nonnull final Validator<T> validator) {
+        checkNotNull(tClass);
+        checkNotNull(validator);
+        validatorMap.put(tClass, validator);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public <T extends DataObject> void validate(@Nonnull final Class<T> tClass, @Nonnull final DataObject data,
+            @Nonnull final Validator.Extent extent) throws ValidationException {
+        if (!tClass.isAssignableFrom(data.getClass())) {
+            throw new IllegalArgumentException(
+                    String.format("data must be the same type as tClass, got=%s : expected=%s",
+                            data.getImplementedInterface(), tClass));
+        }
+        // We are checking the type of data above
+        @SuppressWarnings("unchecked") T tData = (T) data;
+        validatorFor(tClass).validate(tData, extent);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public <T extends DataObject> Validator<T> validatorFor(@Nonnull final Class<T> tClass) {
+        checkNotNull(tClass);
+        if (validatorMap.containsKey(tClass)) {
+            // validation is done via the put method all key/value pairs are for the same type T
+            @SuppressWarnings("unchecked") Validator<T> result = (Validator<T>) validatorMap.get(tClass);
+            return result;
+        }
+        throw new NoSuchElementException("Entry not found for key: " + tClass);
+    }
+
+
+}
diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/AbstractValidator.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/AbstractValidator.java
new file mode 100644 (file)
index 0000000..b2bd654
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.packetcable.provider.validation.impl.validators;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import javax.annotation.concurrent.NotThreadSafe;
+import org.opendaylight.controller.packetcable.provider.validation.ValidationException;
+import org.opendaylight.controller.packetcable.provider.validation.Validator;
+
+/**
+ * Helper class to help support lazy initialization of error message array.<br>
+ * This delays array creation until bad data is found.
+ * <br><br>
+ * <strong>Subclasses must call {@link #throwErrorsIfNeeded()} at the end of validate()</strong>
+ *
+ * @author rvail
+ */
+@NotThreadSafe
+public abstract class AbstractValidator<T> implements Validator<T> {
+
+    private ArrayList<String> errorMessages = null;
+
+    /**
+     * If any error messages have been added to the list returned by {@link #getErrorMessages()}
+     * then a ValidationException will be thrown with those error messages.
+     *
+     * @throws ValidationException
+     */
+    protected void throwErrorsIfNeeded() throws ValidationException {
+        if (errorMessages != null && !errorMessages.isEmpty()) {
+            ValidationException exception = new ValidationException(errorMessages);
+            resetErrorMessages();
+            throw exception;
+        }
+    }
+
+    /**
+     * sets the error message list to null.
+     */
+    protected void resetErrorMessages() {
+        errorMessages = null;
+    }
+
+    /**
+     * Checks if the passed in object is null. If it is, then an error message will be
+     * appended to the current list of errors.
+     *
+     * @param obj
+     *         The object that must not be null.
+     * @param name
+     *         The name of the object (will be used in the error message).
+     */
+    protected void mustExist(Object obj, String name) {
+        if (obj == null) {
+            getErrorMessages().add(name + " must exist");
+        }
+    }
+
+    /**
+     * Lazy initalizer of an array list of error messages.
+     *
+     * @return The array list of error messages
+     */
+    protected ArrayList<String> getErrorMessages() {
+        if (errorMessages == null) {
+            errorMessages = new ArrayList<>(2);
+        }
+        return errorMessages;
+    }
+
+    /**
+     * Checks if the passed in collection is null or empty. If it is then an error
+     * will be appended to the current list of errors.
+     *
+     * @param collection
+     *         The collection to test
+     * @param name
+     *         The name of the object (will be used in the error message)
+     */
+    protected void mustExistAndNotBeEmpty(Collection<?> collection, String name) {
+        if (collection == null) {
+            getErrorMessages().add(name + " must exist");
+        } else if (collection.isEmpty()) {
+            getErrorMessages().add(name + " must not be empty");
+        }
+    }
+
+    protected <C> void validateChild(Validator<C> validator, C child) {
+        try {
+            validator.validate(child, Extent.NODE_AND_SUBTREE);
+        } catch (ValidationException e) {
+            getErrorMessages().addAll(e.getErrorMessages());
+        }
+    }
+}
diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/ccaps/AmIdValidator.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/ccaps/AmIdValidator.java
new file mode 100644 (file)
index 0000000..c442179
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.packetcable.provider.validation.impl.validators.ccaps;
+
+import org.opendaylight.controller.packetcable.provider.validation.ValidationException;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.AbstractValidator;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ccap.attributes.AmId;
+
+/**
+ * @author rvail
+ */
+public class AmIdValidator extends AbstractValidator<AmId> {
+
+    private static final String AM_TYPE = "amId.am-type";
+    private static final String AM_TAG = "amId.am-tag";
+
+    @Override
+    public void validate(final AmId amId, Extent extent) throws ValidationException {
+
+        if (amId == null) {
+            throw new ValidationException("amId must exist");
+        }
+
+        mustExist(amId.getAmTag(), AM_TAG);
+        mustExist(amId.getAmType(), AM_TYPE);
+
+        throwErrorsIfNeeded();
+    }
+}
diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/ccaps/CcapValidator.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/ccaps/CcapValidator.java
new file mode 100644 (file)
index 0000000..faeb0c5
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.packetcable.provider.validation.impl.validators.ccaps;
+
+import org.opendaylight.controller.packetcable.provider.validation.ValidationException;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.AbstractValidator;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ccaps.Ccap;
+
+/**
+ * @author rvail
+ */
+public class CcapValidator extends AbstractValidator<Ccap> {
+
+    private static final String CCAPID = "ccap.ccapid";
+    private static final String CONNECTION = "ccap.connection";
+    private static final String AM_ID = "ccap.amId";
+    private static final String UP_STREAM_SCNS = "ccap.upstream-scns";
+    private static final String DOWN_STREAM_SCNS = "ccap.downstream-scns";
+
+    private final AmIdValidator amIdValidator = new AmIdValidator();
+    private final ConnectionValidator connectionValidator = new ConnectionValidator();
+
+    @Override
+    public void validate(final Ccap ccap, Extent extent) throws ValidationException {
+        if (ccap == null) {
+            throw new ValidationException("ccap must exist");
+        }
+
+        mustExist(ccap.getCcapId(), CCAPID);
+
+        mustExistAndNotBeEmpty(ccap.getUpstreamScns(), UP_STREAM_SCNS);
+        mustExistAndNotBeEmpty(ccap.getDownstreamScns(), DOWN_STREAM_SCNS);
+
+        if (extent == Extent.NODE_AND_SUBTREE) {
+            validateChild(amIdValidator, ccap.getAmId());
+            validateChild(connectionValidator, ccap.getConnection());
+        } else {
+            mustExist(ccap.getAmId(), AM_ID);
+            mustExist(ccap.getConnection(), CONNECTION);
+        }
+
+        throwErrorsIfNeeded();
+    }
+
+
+}
diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/ccaps/CcapsValidator.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/ccaps/CcapsValidator.java
new file mode 100644 (file)
index 0000000..535773d
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.packetcable.provider.validation.impl.validators.ccaps;
+
+import org.opendaylight.controller.packetcable.provider.validation.ValidationException;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.AbstractValidator;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.Ccaps;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ccaps.Ccap;
+
+/**
+ * @author rvail
+ */
+public class CcapsValidator extends AbstractValidator<Ccaps> {
+
+    private final CcapValidator ccapValidator = new CcapValidator();
+
+    @Override
+    public void validate(final Ccaps ccaps, Extent extent) throws ValidationException {
+        if (ccaps == null) {
+            throw new ValidationException("ccaps must exist");
+        }
+
+        if (extent == Extent.NODE_AND_SUBTREE) {
+            for (Ccap ccap : ccaps.getCcap()) {
+                validateChild(ccapValidator, ccap);
+            }
+        }
+    }
+
+}
diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/ccaps/ConnectionValidator.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/ccaps/ConnectionValidator.java
new file mode 100644 (file)
index 0000000..1a737f3
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.packetcable.provider.validation.impl.validators.ccaps;
+
+import org.opendaylight.controller.packetcable.provider.validation.ValidationException;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.AbstractValidator;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ccap.attributes.Connection;
+
+/**
+ * @author rvail
+ */
+public class ConnectionValidator extends AbstractValidator<Connection> {
+
+    private static final String IP_ADDRESS = "connection.ipAddress";
+    private static final String PORT = "connection.port";
+
+    @Override
+    public void validate(final Connection connection, Extent extent) throws ValidationException {
+        if (connection == null) {
+            throw new ValidationException("connection must exist");
+        }
+
+        mustExist(connection.getIpAddress(), IP_ADDRESS);
+
+        // Note PortNumber validates range on creation so only existence needs to be checked
+        mustExist(connection.getPort(), PORT);
+
+
+        throwErrorsIfNeeded();
+    }
+}
diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/AppValidator.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/AppValidator.java
new file mode 100644 (file)
index 0000000..e830786
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.packetcable.provider.validation.impl.validators.qos;
+
+import org.opendaylight.controller.packetcable.provider.validation.ValidationException;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.AbstractValidator;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.App;
+
+/**
+ * @author rvail
+ */
+public class AppValidator extends AbstractValidator<App> {
+
+    private static final String APP_ID = "app.appId";
+    private static final String SUBSCRIBERS = "app.subscribers";
+
+    private final SubscribersValidator subscribersValidator = new SubscribersValidator();
+
+    @Override
+    public void validate(final App app, final Extent extent) throws ValidationException {
+        if (app == null) {
+            throw new ValidationException("app must exist");
+        }
+
+        mustExist(app.getAppId(), APP_ID);
+        mustExist(app.getSubscribers(), SUBSCRIBERS);
+
+        if (extent == Extent.NODE_AND_SUBTREE) {
+            validateChild(subscribersValidator, app.getSubscribers());
+        }
+
+        throwErrorsIfNeeded();
+    }
+}
diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/AppsValidator.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/AppsValidator.java
new file mode 100644 (file)
index 0000000..dbf0633
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.packetcable.provider.validation.impl.validators.qos;
+
+import org.opendaylight.controller.packetcable.provider.validation.ValidationException;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.AbstractValidator;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.Apps;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.App;
+
+/**
+ * @author rvail
+ */
+public class AppsValidator extends AbstractValidator<Apps> {
+
+    private final AppValidator appValidator = new AppValidator();
+
+    @Override
+    public void validate(final Apps apps, final Extent extent) throws ValidationException {
+        if (apps == null) {
+            throw new ValidationException("apps must exist");
+        }
+        if (extent == Extent.NODE_AND_SUBTREE) {
+            for (App app : apps.getApp()) {
+                validateChild(appValidator, app);
+            }
+        }
+
+        throwErrorsIfNeeded();
+    }
+
+}
diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/GateSpecValidatator.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/GateSpecValidatator.java
new file mode 100644 (file)
index 0000000..7f1353b
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.packetcable.provider.validation.impl.validators.qos;
+
+import org.opendaylight.controller.packetcable.provider.validation.ValidationException;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.AbstractValidator;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gate.spec.GateSpec;
+
+/**
+ * @author rvail
+ */
+public class GateSpecValidatator extends AbstractValidator<GateSpec> {
+
+    private static final String DIRECTION = "gate-spec.direction";
+
+    @Override
+    public void validate(final GateSpec gateSpec, final Extent extent) throws ValidationException {
+        if (gateSpec == null) {
+            throw new ValidationException("gate-spec must exist");
+        }
+
+        // everything is optional
+
+//        mustExist(gateSpec.getDirection(), DIRECTION);
+//
+//        // dscp-tos-overwrite & dscp-tos-mask are optional
+        throwErrorsIfNeeded();
+    }
+
+}
diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/GateValidator.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/GateValidator.java
new file mode 100644 (file)
index 0000000..e97974c
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.packetcable.provider.validation.impl.validators.qos;
+
+import org.opendaylight.controller.packetcable.provider.validation.ValidationException;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.AbstractValidator;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.qos.classifier.ClassifierValidator;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.qos.classifier.ExtClassifierValidator;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.qos.classifier.Ipv6ClassifierValidator;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.subscribers.subscriber.gates.Gate;
+
+/**
+ * @author rvail
+ */
+public class GateValidator extends AbstractValidator<Gate> {
+
+    private static final String GATE_ID = "gate.gateId";
+    private static final String GATE_SPEC = "gate.gate-spec";
+    private static final String TRAFFIC_PROFILE = "gate.traffic-profile";
+
+//    private final GateSpecValidatator gateSpecValidatator = new GateSpecValidatator();
+    private final TrafficProfileValidator trafficProfileValidator = new TrafficProfileValidator();
+    private final ClassifierValidator classifierValidator = new ClassifierValidator();
+    private final ExtClassifierValidator extClassifierValidator = new ExtClassifierValidator();
+    private final Ipv6ClassifierValidator ipv6ClassifierValidator = new Ipv6ClassifierValidator();
+
+    @Override
+    public void validate(final Gate gate, final Extent extent) throws ValidationException {
+        if (gate == null) {
+            throw new ValidationException("gate must exist");
+        }
+
+        mustExist(gate.getGateId(), GATE_ID);
+
+        // all leafs in GateSpec are optional
+        // mustExist(gate.getGateSpec(), GATE_SPEC);
+
+        mustExist(gate.getTrafficProfile(), TRAFFIC_PROFILE);
+        if (extent == Extent.NODE_AND_SUBTREE) {
+//            validateChild(gateSpecValidatator, gate.getGateSpec());
+            validateChild(trafficProfileValidator, gate.getTrafficProfile());
+        }
+
+        // Classifiers
+
+        if (gate.getClassifier() != null) {
+
+            // classifer is not null, ext and ipv6 must be null
+
+            if (gate.getExtClassifier() != null || gate.getIpv6Classifier() != null) {
+                getErrorMessages().add("Only one type of classifier is allowed");
+            }
+            else if (extent == Extent.NODE_AND_SUBTREE) {
+                validateChild(classifierValidator, gate.getClassifier());
+            }
+
+        }
+        else if (gate.getExtClassifier() != null) {
+
+            // classifer is null; ext is not null and ipv6 must be null
+
+            if (gate.getIpv6Classifier() != null) {
+                getErrorMessages().add("Only one type of classifier is allowed");
+            }
+            else if (extent == Extent.NODE_AND_SUBTREE) {
+                validateChild(extClassifierValidator, gate.getExtClassifier());
+            }
+
+        }
+        else if (gate.getIpv6Classifier() != null) {
+
+            // classifer and ext are null; ipv6 is not
+            if (extent == Extent.NODE_AND_SUBTREE) {
+                validateChild(ipv6ClassifierValidator, gate.getIpv6Classifier());
+            }
+
+        }
+        else {
+            getErrorMessages().add("a classifer is required");
+        }
+
+        throwErrorsIfNeeded();
+    }
+}
diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/GatesValidator.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/GatesValidator.java
new file mode 100644 (file)
index 0000000..a5eecaf
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.packetcable.provider.validation.impl.validators.qos;
+
+import org.opendaylight.controller.packetcable.provider.validation.ValidationException;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.AbstractValidator;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.subscribers.subscriber.Gates;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.subscribers.subscriber.gates.Gate;
+
+/**
+ * @author rvail
+ */
+public class GatesValidator extends AbstractValidator<Gates> {
+
+    private final GateValidator gateValidator = new GateValidator();
+
+    @Override
+    public void validate(final Gates gates, final Extent extent) throws ValidationException {
+        if (gates == null) {
+            throw new ValidationException("gates must exist");
+        }
+
+        if (extent == Extent.NODE_AND_SUBTREE) {
+            for (Gate gate : gates.getGate()) {
+                validateChild(gateValidator, gate);
+            }
+        }
+
+        throwErrorsIfNeeded();
+    }
+}
diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/SubscriberValidator.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/SubscriberValidator.java
new file mode 100644 (file)
index 0000000..fa1a2a4
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.packetcable.provider.validation.impl.validators.qos;
+
+import org.opendaylight.controller.packetcable.provider.validation.ValidationException;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.AbstractValidator;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.subscribers.Subscriber;
+
+/**
+ * @author rvail
+ */
+public class SubscriberValidator extends AbstractValidator<Subscriber> {
+
+    private static final String SUBSCRIBER_ID = "subscriber.subscriberId";
+    private static final String GATES = "subscriber.gates";
+
+    private final GatesValidator gatesValidator = new GatesValidator();
+
+    @Override
+    public void validate(final Subscriber subscriber, final Extent extent) throws ValidationException {
+        if (subscriber == null) {
+            throw new ValidationException("subscriber must exist");
+        }
+
+        mustExist(subscriber.getSubscriberId(), SUBSCRIBER_ID);
+        mustExist(subscriber.getGates(), GATES);
+
+        if (extent == Extent.NODE_AND_SUBTREE) {
+            validateChild(gatesValidator, subscriber.getGates());
+        }
+
+        throwErrorsIfNeeded();
+    }
+}
diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/SubscribersValidator.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/SubscribersValidator.java
new file mode 100644 (file)
index 0000000..0bc8694
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.packetcable.provider.validation.impl.validators.qos;
+
+import org.opendaylight.controller.packetcable.provider.validation.ValidationException;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.AbstractValidator;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.Subscribers;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.subscribers.Subscriber;
+
+/**
+ * @author rvail
+ */
+public class SubscribersValidator extends AbstractValidator<Subscribers> {
+
+    private final SubscriberValidator subscriberValidator = new SubscriberValidator();
+
+    @Override
+    public void validate(final Subscribers subscribers, final Extent extent) throws ValidationException {
+        if (subscribers == null) {
+            throw new ValidationException("subscribers must exist");
+        }
+
+        if (extent == Extent.NODE_AND_SUBTREE) {
+            for (Subscriber subscriber : subscribers.getSubscriber()) {
+                validateChild(subscriberValidator , subscriber);
+            }
+        }
+
+        throwErrorsIfNeeded();
+    }
+
+}
diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/TrafficProfileValidator.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/TrafficProfileValidator.java
new file mode 100644 (file)
index 0000000..fb7a02d
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.packetcable.provider.validation.impl.validators.qos;
+
+import org.opendaylight.controller.packetcable.provider.validation.ValidationException;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.AbstractValidator;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.traffic.profile.TrafficProfile;
+
+/**
+ * @author rvail
+ */
+public class TrafficProfileValidator extends AbstractValidator<TrafficProfile> {
+
+    private static final String SCN = "service-class-name";
+
+    @Override
+    public void validate(final TrafficProfile trafficProfile, final Extent extent) throws ValidationException {
+        if (trafficProfile == null) {
+            throw new ValidationException("traffic-profile must exist");
+        }
+
+        mustExist(trafficProfile.getServiceClassName(), SCN);
+
+        throwErrorsIfNeeded();
+    }
+
+}
diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/classifier/ClassifierValidator.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/classifier/ClassifierValidator.java
new file mode 100644 (file)
index 0000000..2a6ab77
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.packetcable.provider.validation.impl.validators.qos.classifier;
+
+import org.opendaylight.controller.packetcable.provider.validation.ValidationException;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.AbstractValidator;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.classifier.Classifier;
+
+/**
+ * @author rvail
+ */
+public class ClassifierValidator extends AbstractValidator<Classifier> {
+
+    private static final String SRC_IP = "classifer.srcIp";
+    private static final String SRC_PORT = "classifer.srcPort";
+
+    private static final String DST_IP = "classifer.dstIp";
+    private static final String DST_PORT = "classifer.dstPort";
+
+    private static final String TOS_BYTE = "classifer.tos-byte";
+    private static final String TOS_MASK = "classifer.tos-mask";
+
+    private static final String PROTOCOL = "classifer.protocol";
+
+    @Override
+    public void validate(final Classifier classifier, final Extent extent) throws ValidationException {
+
+        if (classifier == null) {
+            throw new ValidationException("classifer must exist");
+        }
+
+        mustExist(classifier.getSrcIp(), SRC_IP);
+        mustExist(classifier.getSrcPort(), SRC_PORT);
+
+        mustExist(classifier.getDstIp(), DST_IP);
+        mustExist(classifier.getDstPort(), DST_PORT);
+
+        mustExist(classifier.getTosByte(), TOS_BYTE);
+        mustExist(classifier.getTosMask(), TOS_MASK);
+
+        mustExist(classifier.getProtocol(), PROTOCOL);
+
+        throwErrorsIfNeeded();
+    }
+}
diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/classifier/ExtClassifierValidator.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/classifier/ExtClassifierValidator.java
new file mode 100644 (file)
index 0000000..de8f02b
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.packetcable.provider.validation.impl.validators.qos.classifier;
+
+import org.opendaylight.controller.packetcable.provider.validation.ValidationException;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.AbstractValidator;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.ext.classifier.ExtClassifier;
+
+/**
+ * @author rvail
+ */
+public class ExtClassifierValidator extends AbstractValidator<ExtClassifier> {
+
+    private static final String SRC_IP = "ext-classifer.srcIp";
+    private static final String SRC_MASK = "ext-classifer.srcIpMask";
+
+    private static final String DST_IP = "ext-classifer.dstIp";
+    private static final String DST_MASK = "ext-classifer.dstIpMask";
+
+    private static final String TOS_BYTE = "ext-classifer.tos-byte";
+    private static final String TOS_MASK = "ext-classifer.tos-mask";
+
+    private static final String PROTOCOL = "ext-classifer.protocol";
+
+    private static final String SRC_PORT_START = "ext-classifer.srcPort-start";
+    private static final String SRC_PORT_END = "ext-classifer.srcPort-end";
+
+    private static final String DST_PORT_START = "ext-classifer.dstPort-start";
+    private static final String DST_PORT_END = "ext-classifer.dstPort-end";
+
+    @Override
+    public void validate(final ExtClassifier extClassifier, final Extent extent) throws ValidationException {
+
+        mustExist(extClassifier.getSrcIp(), SRC_IP);
+        mustExist(extClassifier.getSrcIpMask(), SRC_MASK);
+
+        mustExist(extClassifier.getDstIp(), DST_IP);
+        mustExist(extClassifier.getDstIpMask(), DST_MASK);
+
+        mustExist(extClassifier.getTosByte(), TOS_BYTE);
+        mustExist(extClassifier.getTosMask(), TOS_MASK);
+
+        mustExist(extClassifier.getProtocol(), PROTOCOL);
+
+        mustExist(extClassifier.getSrcPortStart(), SRC_PORT_START);
+        mustExist(extClassifier.getSrcPortEnd(), SRC_PORT_END);
+
+        mustExist(extClassifier.getDstPortStart(), DST_PORT_START);
+        mustExist(extClassifier.getDstPortEnd(), DST_PORT_END);
+
+        throwErrorsIfNeeded();
+    }
+}
diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/classifier/Ipv6ClassifierValidator.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/validation/impl/validators/qos/classifier/Ipv6ClassifierValidator.java
new file mode 100644 (file)
index 0000000..87d4495
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.packetcable.provider.validation.impl.validators.qos.classifier;
+
+import org.opendaylight.controller.packetcable.provider.validation.ValidationException;
+import org.opendaylight.controller.packetcable.provider.validation.impl.validators.AbstractValidator;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.ipv6.classifier.Ipv6Classifier;
+
+/**
+ * @author rvail
+ */
+public class Ipv6ClassifierValidator extends AbstractValidator<Ipv6Classifier> {
+
+
+    private static final String SRC_IP6 = "ipv6-classifer.srcIp6";
+    private static final String DST_IP6 = "ipv6-classifer.dstIp6";
+
+    private static final String TC_LOW = "ipv6-classifer.tc-low";
+    private static final String TC_HIGH = "ipv6-classifer.tc-high";
+    private static final String TC_MASK = "ipv6-classifer.tc-mask";
+
+    private static final String NEXT_HEADER = "ipv6-classifer.next-hdr";
+
+    private static final String FLOW_LABEL = "ipv6-classifer.flow-label";
+
+    private static final String SRC_PORT_START = "ipv6-classifer.srcPort-start";
+    private static final String SRC_PORT_END = "ipv6-classifer.srcPort-end";
+
+    private static final String DST_PORT_START = "ipv6-classifer.dstPort-start";
+    private static final String DST_PORT_END = "ipv6-classifer.dstPort-end";
+
+    @Override
+    public void validate(final Ipv6Classifier ipv6Classifier, final Extent extent) throws ValidationException {
+        if (ipv6Classifier == null) {
+            throw new ValidationException("ipv6-classifer must exist");
+        }
+
+        mustExist(ipv6Classifier.getSrcIp6(), SRC_IP6);
+        mustExist(ipv6Classifier.getDstIp6(), DST_IP6);
+
+        mustExist(ipv6Classifier.getTcLow(), TC_LOW);
+        mustExist(ipv6Classifier.getTcHigh(), TC_HIGH);
+        mustExist(ipv6Classifier.getTcMask(), TC_MASK);
+
+        mustExist(ipv6Classifier.getNextHdr(), NEXT_HEADER);
+
+        mustExist(ipv6Classifier.getFlowLabel(), FLOW_LABEL);
+
+        mustExist(ipv6Classifier.getSrcPortStart(), SRC_PORT_START);
+        mustExist(ipv6Classifier.getSrcPortEnd(), SRC_PORT_END);
+
+        mustExist(ipv6Classifier.getDstPortStart(), DST_PORT_START);
+        mustExist(ipv6Classifier.getDstPortEnd(), DST_PORT_END);
+
+        throwErrorsIfNeeded();
+    }
+}
index 8225e4d8271d5b4086f691d6e13d2f2d4d8e0138..4e3a9d49fa41f6bb2995c4a203fd83454292169f 100644 (file)
@@ -340,9 +340,14 @@ public class PCMMServiceTest {
                                     final String expGateSetMsgStart) {
         final Gate gate = makeGateObj(scnName, srcAddr, direction, dstAddr);
 
-        final String gateSetMsg = service.sendGateSet(gatePath, cmAddrInet, gate, direction);
-        Assert.assertNotNull(gateSetMsg);
-        Assert.assertTrue(gateSetMsg, gateSetMsg.startsWith(expGateSetMsgStart));
+//        final String gateSetMsg = service.sendGateSet(gatePath, cmAddrInet, gate, direction);
+//        Assert.assertNotNull(gateSetMsg);
+//        Assert.assertTrue(gateSetMsg, gateSetMsg.startsWith(expGateSetMsgStart));
+
+        // TODO update this method for the new GateSetStatus object
+        PCMMService.GateSetStatus status = service.sendGateSet(gatePath, cmAddrInet, gate, direction);
+        Assert.assertNotNull(status);
+        Assert.assertTrue(status.getMessage().startsWith(expGateSetMsgStart));
 
         // TODO - add validation to the PCMMGateReq contained within the map
         Assert.assertNotNull(service.gateRequests.get(gatePath));
index 64ff9a3c13f302a22ac872413d1672754abaf591..513f37b1838795946a24edf51d7661df45d87f42 100644 (file)
@@ -8,13 +8,11 @@ import static org.mockito.Mockito.when;
 
 import java.net.InetAddress;
 import java.util.concurrent.ExecutionException;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
-import org.opendaylight.controller.packetcable.provider.PacketcableProvider;
 
 @RunWith(MockitoJUnitRunner.class)
 public class PacketcableProviderTest {
index b0360ad8ac9528e682ab9412f935b4e5f2f739b9..5d52cd2c93e650a072daee171fcb678d23d763cb 100644 (file)
@@ -1,16 +1,21 @@
 package org.opendaylight.controller.packetcable.provider;
 
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
 import com.google.common.net.InetAddresses;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-
-import static org.hamcrest.CoreMatchers.*;
-import static org.junit.Assert.*;
-
 public class SubnetTest {
 
     // Various address class level prefix lengths