VNFRQTS - Fix incorrect metadata usage 86/71086/3
authorLovett, Trevor <trevor.lovett@att.com>
Tue, 23 Oct 2018 18:05:15 +0000 (13:05 -0500)
committerHagop Bozawglanian <hagop.bozawglanian@att.com>
Tue, 23 Oct 2018 18:31:52 +0000 (18:31 +0000)
Change-Id: Ic1cd85ae2afc5f3443f78365789228660fc8c3a2
Issue-ID: VNFRQTS-477
Signed-off-by: Lovett, Trevor <trevor.lovett@att.com>
15 files changed:
docs/Chapter4/Modularity.rst
docs/Chapter4/Resiliency.rst
docs/Chapter5/Heat/ONAP Heat Cinder Volumes.rst
docs/Chapter5/Heat/ONAP Heat Orchestration Template Format.rst
docs/Chapter5/Heat/ONAP Heat Orchestration Templates Overview.rst
docs/Chapter5/Heat/ONAP Heat Resource ID and Parameter Naming Convention/Nova Parameters.rst
docs/Chapter5/Tosca.rst
docs/Chapter7/Monitoring-And-Management.rst
docs/Chapter7/VNF-On-boarding-and-package-management.rst
docs/Chapter8/Ansible-JSON-Key-Value-Description.rst
docs/Chapter8/Ansible-Playbook-Examples.rst
docs/Chapter8/Chef-JSON-Key-Value-Description.rst
etc/requirements.txt
fix_invalid_metadata.py [new file with mode: 0644]
gen_requirement_changes.py

index 6372342..157dccb 100644 (file)
@@ -27,8 +27,8 @@ ONAP VNF Modularity Overview
 
 With VNF Modularity, a single VNF may be composed from one or more Heat
 Orchestration Templates, each of which represents a subset of the
-overall VNF. These component parts are referred to as \ *VNF
-Modules*\ . During orchestration, these modules are deployed
+overall VNF. These component parts are referred to as "\ *VNF
+Modules*\ ". During orchestration, these modules are deployed
 incrementally to create the complete VNF.
 
 A modular Heat Orchestration Template can be either one of the following
@@ -82,7 +82,7 @@ ONAP supports a modular Heat Orchestration Template design pattern,
 referred to as *VNF Modularity.* With this approach, a single VNF may be
 composed from one or more Heat Orchestration Templates, each of which
 represents a subset of the overall VNF. These component parts are
-referred to as “\ *VNF Modules*\ ”. During orchestration, these modules
+referred to as "\ *VNF Modules*\ ". During orchestration, these modules
 are deployed incrementally to create the complete VNF.
 
 A modular Heat Orchestration Template can be either one of the following
@@ -94,8 +94,8 @@ types:
 
 3. Cinder Volume Module
 
-A VNF must be composed of one “base” module and may be composed of zero
-to many “incremental” modules. The base module must be deployed first,
+A VNF must be composed of one "base" module and may be composed of zero
+to many "incremental" modules. The base module must be deployed first,
 prior to the incremental modules.
 
 ONAP also supports the concept of an optional, independently deployed
@@ -174,8 +174,8 @@ b. Incremental modules for incremental scaling units
 
    ii. May be separated by VM type for multi-dimensional scaling
 
-With no growth units, Option 2 is equivalent to the One Heat Template
-per VNF model.
+With no growth units, Option 2 is equivalent to the "One Heat Template
+per VNF" model.
 
 Note that modularization of VNFs is not required. A single Heat
 Orchestration Template (a base module) may still define a complete VNF,
@@ -193,13 +193,13 @@ There are some rules to follow when building modular VNF templates:
    a. Must include all shared resources (e.g., private networks, server
       groups, security groups)
 
-   b. Must expose all shared resources (by UUID) as “outputs” in its
+   b. Must expose all shared resources (by UUID) as "outputs" in its
       associated Heat template (i.e., ONAP Base Module Output
       Parameters)
 
    c. May include initial set of VMs
 
-   d. May be operational as a stand-alone “minimum” configuration of the
+   d. May be operational as a stand-alone "minimum" configuration of the
       VNF
 
 2. VNFs may have one or more incremental modules which:
@@ -221,7 +221,7 @@ There are some rules to follow when building modular VNF templates:
       ii. must not be dependent on other Add-On VNF Modules
 
    e. Multiple instances of an incremental Module may be added to the
-      same VNF (e.g., incrementally grow a VNF by a fixed “add-on”
+      same VNF (e.g., incrementally grow a VNF by a fixed "add-on"
       growth units)
 
 3. Each VNF Module (base or incremental) may have (optional) an
index ab188d6..a61bd01 100644 (file)
@@ -128,7 +128,7 @@ Avoid performance-sapping data center-to-data center replication delay
 by applying techniques such as caching and persistent transaction paths
 - Eliminate replication delay impact between data centers by using a
 concept of stickiness (i.e., once a client is routed to data center "A",
-the client will stay with Data center “A” until the entire session is
+the client will stay with Data center "A" until the entire session is
 completed).
 
 Minimize Cross Data-Center Traffic Requirements
index 6ce2cad..4f2861f 100644 (file)
@@ -66,7 +66,7 @@ Parameters.
     :target: VNF
     :keyword: MUST
     :validation_mode: static
-    :updated: casablanca
+    :introduced: casablanca
 
     A VNF's Heat Orchestration Template's Cinder Volume Template **MUST**
     contain either
index bf810b7..edc3b34 100644 (file)
@@ -12,6 +12,7 @@ As stated above, Heat Orchestration templates must be defined in YAML.
     :id: R-92635
     :keyword: MUST
     :validation_mode: static
+    :introduced: casablanca
 
     A VNF's Heat Orchestration Template **MUST** be compliant with the
     OpenStack Template Guide.
@@ -130,6 +131,7 @@ attributes (e.g., type, label) defined as nested elements.
     :target: VNF
     :keyword: MUST
     :validation_mode: static
+    :updated: casablanca
 
     A VNF Heat Orchestration's template's parameter **MUST** be used
     in a resource with the exception of the parameters for the
@@ -139,6 +141,7 @@ attributes (e.g., type, label) defined as nested elements.
     :id: R-91273
     :target: VNF
     :keyword: MAY NOT
+    :updated: casablanca
 
     A VNF Heat Orchestration's template's parameter for the
     ``OS::Nova::Server`` resource property ``availability_zone``
@@ -181,6 +184,7 @@ type
     :target: VNF
     :keyword: MUST
     :validation_mode: static
+    :updated: casablanca
 
     A VNF's Heat Orchestration Template's parameter type **MUST** be one of
     the following values:
@@ -238,6 +242,7 @@ default
     :target: VNF
     :keyword: MUST
     :validation_mode: static
+    :updated: casablanca
 
     If a VNF Heat Orchestration Template parameter has a default value,
     it **MUST** be enumerated in the environment file.
@@ -275,6 +280,7 @@ that defines a list of constraints to apply to the parameter.
     :target: VNF
     :keyword: MUST
     :validation_mode: static
+    :updated: casablanca
 
     A VNF's Heat Orchestration Template's parameter defined
     in a non-nested YAML file as type
@@ -285,6 +291,7 @@ that defines a list of constraints to apply to the parameter.
     :id: R-40518
     :target: VNF
     :keyword: MAY
+    :updated: casablanca
 
     A VNF's Heat Orchestration Template's parameter defined
     in a non-nested YAML file as type
@@ -294,6 +301,7 @@ that defines a list of constraints to apply to the parameter.
     :id: R-96227
     :target: VNF
     :keyword: MAY
+    :updated: casablanca
 
     A VNF's Heat Orchestration Template's parameter defined
     in a non-nested YAML file as type
@@ -303,6 +311,7 @@ that defines a list of constraints to apply to the parameter.
     :id: R-79817
     :target: VNF
     :keyword: MAY
+    :updated: casablanca
 
     A VNF's Heat Orchestration Template's parameter defined
     in a non-nested YAML file as
@@ -312,6 +321,7 @@ that defines a list of constraints to apply to the parameter.
     :id: R-06613
     :target: VNF
     :keyword: MAY
+    :updated: casablanca
 
     A VNF's Heat Orchestration Template's parameter defined
     in a non-nested YAML file as type
@@ -322,6 +332,7 @@ that defines a list of constraints to apply to the parameter.
     :target: VNF
     :keyword: MUST NOT
     :validation_mode: static
+    :updated: casablanca
 
     A VNF's Heat Orchestration Template's parameter defined
     in a nested YAML file
@@ -448,6 +459,7 @@ resources
     :id: R-40551
     :target: VNF
     :keyword: MAY
+    :updated: casablanca
 
     A VNF's Heat Orchestration Template's Nested YAML files **MAY**
     (or **MAY NOT**) contain the section ``resources:``.
@@ -554,6 +566,7 @@ be provided in place, or via a function
     :target: VNF
     :keyword: MUST
     :validation_mode: static
+    :updated: casablanca
 
     If a VNF's Heat Orchestration Template resource attribute
     ``property:`` uses a nested ``get_param``, the nested
@@ -569,6 +582,7 @@ The resource attribute ``metadata`` is an OpenStack optional attribute.
     :target: VNF
     :keyword: MUST
     :validation_mode: static
+    :introduced: casablanca
 
     A VNF's Heat Orchestration Template's Resource **MAY** declare the
     attribute ``metadata``.
@@ -607,6 +621,7 @@ deletion_policy
     :id: R-43740
     :target: VNF
     :keyword: MAY
+    :updated: casablanca
 
     VNF's Heat Orchestration Template's Resource **MAY** declare the
     attribute ``deletion_policy:``.
@@ -627,6 +642,7 @@ external_id
     :id: R-78569
     :target: VNF
     :keyword: MAY
+    :updated: casablanca
 
     VNF's Heat Orchestration Template's Resource **MAY** declare the
     attribute ``external_id:``.
@@ -675,6 +691,7 @@ A VNF's Heat Orchestration Template's environment file is a yaml text file.
     :target: VNF
     :keyword: MUST
     :validation_mode: static
+    :updated: casablanca
 
     A VNF's Heat Orchestration template **MUST** have a
     corresponding environment file.
@@ -690,6 +707,7 @@ the mandatory parameter section.
     :target: VNF
     :keyword: MUST
     :validation_mode: static
+    :updated: casablanca
 
     A VNF's Heat Orchestration template's Environment File **MUST**
     contain the ``parameters:`` section.
@@ -698,6 +716,7 @@ the mandatory parameter section.
     :id: R-68198
     :target: VNF
     :keyword: MAY
+    :updated: casablanca
 
     A VNF's Heat Orchestration template's Environment File's
     ``parameters:`` section **MAY** (or **MAY NOT**) enumerate parameters.
index 573e16f..5e513d1 100644 (file)
@@ -32,6 +32,7 @@ deployed incrementally to create the complete VNF.
     :id: R-33132
     :target: VNF
     :keyword: MAY
+    :updated: casablanca
 
     A VNF's Heat Orchestration Template **MAY** be
      1.) Base Module Heat Orchestration Template (also referred to as a
@@ -45,6 +46,7 @@ deployed incrementally to create the complete VNF.
     :id: R-37028
     :target: VNF
     :keyword: MUST
+    :updated: casablanca
 
     A VNF **MUST** be composed of one Base Module
 
@@ -59,6 +61,7 @@ deployed incrementally to create the complete VNF.
     :id: R-20974
     :target: VNF
     :keyword: MUST
+    :updated: casablanca
 
     At orchestration time, the VNF's Base Module **MUST**
     be deployed first, prior to any incremental modules.
@@ -132,6 +135,7 @@ on another instance (e.g., during a failover activity).
     :target: VNF
     :keyword: MUST
     :validation_mode: static
+    :updated: casablanca
 
     A VNF's Cinder Volume Module, when it exists, **MUST** be 1:1
     with a Base module or Incremental module.
@@ -144,6 +148,7 @@ Module.
     :target: VNF
     :keyword: MUST
     :validation_mode: static
+    :updated: casablanca
 
     A VNF's Base Module **MUST** have a corresponding Environment File.
 
@@ -152,6 +157,7 @@ Module.
     :target: VNF
     :keyword: MUST
     :validation_mode: static
+    :updated: casablanca
 
     A VNF's Incremental Module **MUST** have a corresponding Environment File
 
@@ -160,6 +166,7 @@ Module.
     :target: VNF
     :keyword: MUST
     :validation_mode: static
+    :updated: casablanca
 
     A VNF's Cinder Volume Module **MUST** have a corresponding environment file
 
@@ -260,6 +267,7 @@ Base Modules
     :target: VNF
     :keyword: MUST
     :validation_mode: static
+    :updated: casablanca
 
     A VNF Heat Orchestration Template's Base Module file name **MUST** include
     case insensitive 'base' in the filename and
@@ -296,6 +304,7 @@ Incremental Modules
     :target: VNF
     :keyword: MUST
     :validation_mode: static
+    :updated: casablanca
 
     VNF Heat Orchestration Template's Incremental Module file name
     **MUST** contain only alphanumeric characters and underscores
@@ -331,6 +340,7 @@ Cinder Volume Modules
     :target: VNF
     :keyword: MUST
     :validation_mode: static
+    :updated: casablanca
 
     A VNF Heat Orchestration Template's Cinder Volume Module **MUST**
     be named identical to the base or incremental module it is supporting with
@@ -341,6 +351,7 @@ Cinder Volume Modules
     :target: VNF
     :keyword: MUST
     :validation_mode: static
+    :updated: casablanca
 
     VNF Heat Orchestration Template's Cinder Volume Module's Environment File
     **MUST** be named identical to the VNF Heat Orchestration Template's
@@ -355,6 +366,7 @@ Nested Heat file
     :target: VNF
     :keyword: MUST
     :validation_mode: static
+    :updated: casablanca
 
     VNF Heat Orchestration Template's Nested YAML file name **MUST** contain
     only alphanumeric characters and underscores '_' and
@@ -453,6 +465,7 @@ ONAP Volume Module Output Parameters
     :target: VNF
     :keyword: MUST
     :validation_mode: static
+    :updated: casablanca
 
     A VNF's Heat Orchestration Template's Cinder Volume Module Output
     Parameter(s)
@@ -470,6 +483,7 @@ template is associated with.
     :target: VNF
     :keyword: MUST
     :validation_mode: static
+    :updated: casablanca
 
     A VNF's Heat Orchestration Templates' Cinder Volume Module Output
     Parameter's name and type **MUST** match the input parameter name and type
index 5f16802..b5501fd 100644 (file)
@@ -31,7 +31,7 @@ Requirement R-82481 defines how the ``{vm-type}`` is used.
     :target: VNF
     :keyword: MUST
     :validation_mode: static
-    :updated: casablanca
+    :introduced: casablanca
 
     A VNF's Heat Orchestration Template's ``OS::Nova::Server`` resource's
 
@@ -70,7 +70,7 @@ Property: image
     :target: VNF
     :keyword: MUST
     :validation_mode: static
-    :updated: casablanca
+    :introduced: casablanca
 
     The VNF's Heat Orchestration Template's Resource ``OS::Nova::Server``
     property ``image`` value **MUST** be be obtained via a ``get_param``.
@@ -141,7 +141,7 @@ Property: flavor
     :target: VNF
     :keyword: MUST
     :validation_mode: static
-    :updated: casablanca
+    :introduced: casablanca
 
     The VNF's Heat Orchestration Template's Resource ``OS::Nova::Server``
     property ``flavor`` value **MUST** be be obtained via a ``get_param``.
@@ -209,7 +209,7 @@ Property: Name
     :target: VNF
     :keyword: MUST
     :validation_mode: static
-    :updated: casablanca
+    :introduced: casablanca
 
     The VNF's Heat Orchestration Template's Resource ``OS::Nova::Server``
     property ``name`` value **MUST** be be obtained via a ``get_param``.
index 9970dbc..93436a8 100644 (file)
@@ -945,7 +945,7 @@ model as described in YAML 1.1. Pending on Shitao proposal (see
 NFVIFA(17)000110 discussion paper)
 
 **[editor note]** new relationship type as suggested in Matt
-presentation. Slide 8. With specific rules of “valid\_target\_type”
+presentation. Slide 8. With specific rules of "valid\_target\_type"
 
 +---------------------------+--------------------------------------+
 | **Shorthand Name**        | VirtualStorage                       |
index 8ff1eb3..38295ab 100755 (executable)
@@ -382,7 +382,7 @@ minimizing changes to data delivery.
    :keyword: SHOULD
    :impacts: dcae
    :validation_mode: in_service
-   :introduced: casblanca
+   :introduced: casablanca
 
    The xNF **SHOULD** deliver event records that fall into the event domains
    supported by VES.
@@ -393,7 +393,7 @@ minimizing changes to data delivery.
    :keyword: MUST
    :impacts: dcae
    :validation_mode: in_service
-   :introduced: casblanca
+   :introduced: casablanca
 
    The xNF **MUST** deliver event records to ONAP using the common transport
    mechanisms and protocols defined in this document.
@@ -418,7 +418,7 @@ data sets.
    :keyword: MUST
    :impacts: dcae
    :validation_mode: none
-   :introduced: casblanca
+   :introduced: casablanca
 
    The xNF provider **MUST** reach agreement with the Service Provider on
    the selected methods for encoding, serialization and data delivery
@@ -529,6 +529,7 @@ JSON
     :id: R-19624
     :target: XNF
     :keyword: MUST
+    :updated: casablanca
 
     The xNF, when leveraging JSON for events, **MUST** encode and serialize
     content delivered to ONAP using JSON (RFC 7159) plain text format.
@@ -810,6 +811,7 @@ Asynchronous and Synchronous Data Delivery
    :keyword: SHOULD
    :impacts: dcae
    :validation_mode: in_service
+   :introduced: casablanca
 
    The xNF **SHOULD** deliver all syslog messages to the VES Collector per the
    specifications in Monitoring and Management chapter.
index bd49838..f68b129 100755 (executable)
@@ -481,6 +481,7 @@ Testing
     :id: R-43958
     :target: XNF
     :keyword: MUST
+    :updated: casablanca
 
     The xNF Package **MUST** include documentation describing
     the tests that were conducted by the xNF provider and the test results.
index 4913823..5179518 100644 (file)
@@ -44,11 +44,11 @@ Table B1. Ansible JSON File key value description
 |               | value pairs to be    |         |Attribute names (variable   |
 |               | passed to the Ansible|         |names) passed to Ansible    |
 |               | playbook. These      |         |shall follow Ansible valid  |
-|               | values would         |         |variable names: Variable   |
+|               | values would         |         |variable names: "Variable   |
 |               | correspond to        |         |names should be letters,    |
 |               | instance specific    |         |numbers, and underscores.   |
 |               | parameters that a    |         |Variables should always     |
-|               | playbook may need to |         |start with a letter.       |
+|               | playbook may need to |         |start with a letter."       |
 |               | execute an action.   |         |                            |
 +---------------+----------------------+---------+----------------------------+
 | NodeList      |Ansible inventory     | Optional|If not provided, pre-loaded |
@@ -110,7 +110,7 @@ Ansible JSON file example:
 
 In the above example, the Ansible Server will:
 
-a. Process the “FileParameters” dictionary and generate a file named
+a. Process the "FileParameters" dictionary and generate a file named
    ‘config.txt’ with contents set to the value of the ‘config.txt’ key.
 
 b. Execute the playbook named ‘<VNFCode>/<Version>/ansible/configure/site.yml’
index 9875963..5c3d5cd 100644 (file)
@@ -447,7 +447,7 @@ Optional:
  <VNF type>/<Version>/ansible/inventory/group_vars/<VNF instance name>
 
 NOTE: Default groups will be created based on VNFC type, 3 characters,
-on VNFC name. Example: “oam”, “rdb”, “dbs”, “man”, “iox”, “app”,…
+on VNFC name. Example: "oam", "rdb", "dbs", "man", "iox", "app",…
 
 Ansible Directories for other artifacts – VNF (special) other files –
 Optional – Example – License file:
@@ -520,7 +520,7 @@ Ansible Server.
    a. Includes VNF type using VNF function code 4 characters under
       /storage.
 
-   b. Includes VNF “Version” directory as part of the path to store
+   b. Includes VNF "Version" directory as part of the path to store
       playbooks for this VNF version.
 
    c. Include generic ansible root directory. Creating full directory
@@ -603,10 +603,10 @@ example:
    vm\_config\_rdb4\_hostname: vfdb9904vm006
    vm\_config\_rdb4\_provider\_ip\_address: 1xx.2yy.zzz.yyy
 
-NOTE: Please note names in this file shall use underscore “\_” not dots
-“.” or dashes “-“.
+NOTE: Please note names in this file shall use underscore "\_" not dots
+"." or dashes "-".
 
-7. Perform some basic playbook validation running with “--check” option,
+7. Perform some basic playbook validation running with "--check" option,
    running dummy playbooks or other.
 
 NOTE: Each Ansible Server or cluster of Ansible Server will have its own
index ae8972f..4beeefe 100644 (file)
@@ -63,7 +63,7 @@ Table A1. Chef JSON File key value description
 |                | as part of the desired   |         |                      |
 |                | VNF action.              |         |                      |
 +----------------+--------------------------+---------+----------------------+
-| PushJobFlag    | This field indicates     |Mandatory| If set to “True”,    |
+| PushJobFlag    | This field indicates     |Mandatory| If set to "True",    |
 |                | whether the VNF action   |         | ONAP will request a  |
 |                | requires a push Job. Push|         | push job. Ignored    |
 |                | job object will be       |         | otherwise.           |
@@ -73,7 +73,7 @@ Table A1. Chef JSON File key value description
 | CallbackCapable| This field indicates if  | Optional| If Chef cookbook is  |
 |                | the chef-client run      |         | callback capable, VNF|
 |                | invoked by push job      |         | owner is required to |
-|                | corresponding to the VNF |         | set it to “True”.    |
+|                | corresponding to the VNF |         | set it to "True".    |
 |                | action is capable of     |         | Ignored otherwise.   |
 |                | posting results on a     |         |                      |
 |                | callback URL.            |         |                      |
@@ -83,7 +83,7 @@ Table A1. Chef JSON File key value description
 |                | retrieve output generated|         | NodeObject attributes|
 |                | in a chef-client run from|         | [‘PushJobOutput’] for|
 |                | Node object attribute    |         | all nodes in NodeList|
-|                | node[‘PushJobOutput’] for|         | if set to “True”.    |
+|                | node[‘PushJobOutput’] for|         | if set to "True".    |
 |                | this VNF action (e.g., in|         | Ignored otherwise.   |
 |                | Audit).                  |         |                      |
 +----------------+--------------------------+---------+----------------------+
@@ -92,39 +92,39 @@ Chef Template example:
 
 .. code-block:: erb
 
“Environment”:{
"Environment":{
       "name": "HAR",
       "description": "VNF Chef environment for HAR",
       "json\_class": "Chef::Environment",
       "chef\_type": "environment",
       "default\_attributes": { },
       "override\_attributes": {
-            “Retry\_Time”:”50”,
-            “MemCache”: “1024”,
-            “Database\_IP”:”10.10.1.5”
+            "Retry\_Time":"50",
+            "MemCache": "1024",
+            "Database\_IP":"10.10.1.5"
       },
  }
  }
“Node”: {
-      “name” : “signal.network.com “
"Node": {
+      "name" : "signal.network.com "
       "chef\_type": "node",
       "json\_class": "Chef::Node",
       "attributes": {
-            “IPAddress1”: “192.168.1.2”,
-            “IPAddress2”:”135.16.162.5”,
-            “MyRole”:”BE”
+            "IPAddress1": "192.168.1.2",
+            "IPAddress2":"135.16.162.5",
+            "MyRole":"BE"
       },
       "override": {},
       "default": {},
-      “normal”:{},
-      “automatic”:{},
-      “chef\_environment” : “\_default”
+      "normal":{},
+      "automatic":{},
+      "chef\_environment" : "\_default"
       "run\_list": [ "configure\_signal" ]
       },
-      “NodeList”:[“node1.vnf\_a.onap.com”, “node2.vnf\_a.onap.com”],
-      “PushJobFlag”: “True”
-      “CallbackCapable”:True
-      “GetOutputFlag” : “False”
+      "NodeList":["node1.vnf\_a.onap.com", "node2.vnf\_a.onap.com"],
+      "PushJobFlag": "True"
+      "CallbackCapable":True
+      "GetOutputFlag" : "False"
  }
 
 The example JSON file provided by the VNF provider for each VNF action will be
@@ -136,8 +136,8 @@ Some points worth noting regarding the JSON fields:
 a. The JSON file must be created for each action for each VNF.
 
 b. If a VNF action involves multiple endpoints (VMs) of a VNF, ONAP will
-   replicate the “Node” JSON dictionary in the template and post it to
-   each FQDN (i.e., endpoint) in the NodeList after setting the “name”
+   replicate the "Node" JSON dictionary in the template and post it to
+   each FQDN (i.e., endpoint) in the NodeList after setting the "name"
    field in the Node object to be the respective FQDN [#8.1.1]_. Hence, it
    is required that all end points (VMs) of a VNF involved in a VNF
    action support the same set of Node Object attributes.
@@ -185,5 +185,5 @@ Table A2. JSON Dictionary to Post in Callback
 +--------------+----------------------------+---------+-----------------------+
 
 .. [#8.1.1]
-   The “name” field is a mandatory field in a valid Chef Node Object
+   The "name" field is a mandatory field in a valid Chef Node Object
    JSON dictionary.
index db5c4ac..21bd7e0 100644 (file)
@@ -32,3 +32,4 @@ sphinxcontrib-swaggerdoc
 sphinxcontrib-plantuml
 xlwt==1.3.0
 PyYAML>=3.10,<4
+pytest
diff --git a/fix_invalid_metadata.py b/fix_invalid_metadata.py
new file mode 100644 (file)
index 0000000..dbff72c
--- /dev/null
@@ -0,0 +1,306 @@
+# -*- coding: utf8 -*-
+# org.onap.vnfrqts/requirements
+# ============LICENSE_START====================================================
+# Copyright © 2018 AT&T Intellectual Property. All rights reserved.
+#
+# Unless otherwise specified, all software contained herein is licensed
+# under the Apache License, Version 2.0 (the "License");
+# you may not use this software except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#             http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Unless otherwise specified, all documentation contained herein is licensed
+# under the Creative Commons License, Attribution 4.0 Intl. (the "License");
+# you may not use this documentation except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#             https://creativecommons.org/licenses/by/4.0/
+#
+# Unless required by applicable law or agreed to in writing, documentation
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# ============LICENSE_END============================================
+
+"""
+This script will consume the `invalid_metadata.csv` file produced by
+`gen_requirement_changes.py`, then add/update any `:introduced:` or `:updated:`
+attributes that may be missing from req directives.
+"""
+import csv
+import os
+import re
+from collections import OrderedDict
+
+import pytest
+
+INPUT_FILE = "invalid_metadata.csv"
+
+
+def load_invalid_reqs(fileobj):
+    """Load the invalid requirements from the input file into a dict"""
+    reader = csv.reader(fileobj)
+    next(reader)  # skip header
+    return {row[0]: (row[1].strip(), row[2].strip()) for row in reader}
+
+
+def check(predicate, msg):
+    """Raises a RuntimeError with the given msg if the predicate is false"""
+    if not predicate:
+        raise RuntimeError(msg)
+
+
+class MetadataFixer:
+    """Takes a dict of requirement ID to expected metadata value.  The
+    NeedsVisitor will pass the requirement attributes as a a dict
+    to `__call__`.  If the requirement is one that needs to be fixed, then
+    it will add or update the attributes as needed and return it to the
+    visitor, otherwise it will return the attributes unchanged."""
+
+    def __init__(self, reqs_to_fix):
+        """Initialize the fixer with a dict of requirement ID to tuple of
+        (attr name, attr value)."""
+        self.reqs_to_fix = reqs_to_fix
+
+    def __call__(self, metadata):
+        """If metadata is for a requirement that needs to be fixed, then
+        adds or updates the attribute as needed and returns it, otherwise
+        it returns metadata unchanged."""
+        r_id = metadata[":id:"]
+        if r_id in self.reqs_to_fix:
+            attr, value = self.reqs_to_fix[r_id]
+            metadata[attr] = value
+        return metadata
+
+
+class NeedsVisitor:
+    """Walks a directory for reStructuredText files and detects needs
+    directives as defined by sphinxcontrib-needs.  When a directive is
+    found, then attributes are passed to a callback for processing if the
+    callback returns a dict of attributes, then the revised dict is used
+    instead of the attributes that were passed"""
+
+    def __init__(self, func):
+        self.directives = re.compile("\.\.\s+req::.*")
+        self.func = func
+
+    def process(self, root_dir):
+        """Walks the `root_dir` looking for rst to files to parse"""
+        for dir_path, sub_dirs, filenames in os.walk(root_dir):
+            for filename in filenames:
+                if filename.lower().endswith(".rst"):
+                    self.handle_rst_file(os.path.join(dir_path, filename))
+
+    @staticmethod
+    def read(path):
+        """Read file at `path` and return lines as list"""
+        with open(path, "r") as f:
+            print("path=", path)
+            return list(f)
+
+    @staticmethod
+    def write(path, content):
+        """Write a content to the given path"""
+        with open(path, "w") as f:
+            for line in content:
+                f.write(line)
+
+    def handle_rst_file(self, path):
+        lines = (line for line in self.read(path))
+        new_contents = []
+        for line in lines:
+            if self.is_needs_directive(line):
+                metadata_lines = self.handle_need(lines)
+                new_contents.append(line)
+                new_contents.extend(metadata_lines)
+            else:
+                new_contents.append(line)
+        self.write(path, new_contents)
+
+    def is_needs_directive(self, line):
+        """Returns True if the line denotes the start of a needs directive"""
+        return bool(self.directives.match(line))
+
+    def handle_need(self, lines):
+        """Called when a needs directive is encountered.  The lines
+        will be positioned on the line after the directive.  The attributes
+        will be read, and then passed to the visitor for processing"""
+        attributes = OrderedDict()
+        indent = 4
+        for line in lines:
+            if line.strip().startswith(":"):
+                indent = self.calc_indent(line)
+                attr, value = self.parse_attribute(line)
+                attributes[attr] = value
+            else:
+                if attributes:
+                    new_attributes = self.func(attributes)
+                    attr_lines = self.format_attributes(new_attributes, indent)
+                    return attr_lines + [line]
+                else:
+                    return [line]
+
+    @staticmethod
+    def format_attributes(attrs, indent):
+        """Converts a dict back to properly indented lines"""
+        spaces = " " * indent
+        return ["{}{} {}\n".format(spaces, k, v) for k, v in attrs.items()]
+
+    @staticmethod
+    def parse_attribute(line):
+        return re.split("\s+", line.strip(), maxsplit=1)
+
+    @staticmethod
+    def calc_indent(line):
+        return len(line) - len(line.lstrip())
+
+
+if __name__ == '__main__':
+    with open(INPUT_FILE, "r") as f:
+        invalid_reqs = load_invalid_reqs(f)
+    metadata_fixer = MetadataFixer(invalid_reqs)
+    visitor = NeedsVisitor(metadata_fixer)
+    visitor.process("docs")
+
+
+# Tests
+@pytest.fixture
+def metadata_fixer():
+    fixes = {
+        "R-1234": (":introduced:", "casablanca"),
+        "R-5678": (":updated:", "casablanca"),
+    }
+    return MetadataFixer(fixes)
+
+
+def test_check_raises_when_false():
+    with pytest.raises(RuntimeError):
+        check(False, "error")
+
+
+def test_check_does_not_raise_when_true():
+    check(True, "error")
+
+
+def test_load_invalid_req():
+    contents = [
+        "reqt_id, attribute, value",
+        "R-1234,:introduced:, casablanca",
+        "R-5678,:updated:, casablanca",
+    ]
+    result = load_invalid_reqs(contents)
+    assert len(result) == 2
+    assert result["R-1234"][0] == ":introduced:"
+    assert result["R-1234"][1] == "casablanca"
+
+
+def test_metadata_fixer_adds_when_missing(metadata_fixer):
+    attributes = {":id:": "R-5678", ":introduced:": "beijing"}
+    result = metadata_fixer(attributes)
+    assert ":updated:" in result
+    assert result[":updated:"] == "casablanca"
+
+
+def test_metadata_fixer_updates_when_incorrect(metadata_fixer):
+    attributes = {":id:": "R-5678", ":updated:": "beijing"}
+    result = metadata_fixer(attributes)
+    assert ":updated:" in result
+    assert result[":updated:"] == "casablanca"
+    assert ":introduced:" not in result
+
+
+def test_needs_visitor_process(monkeypatch):
+    v = NeedsVisitor(lambda x: x)
+    paths = []
+
+    def mock_handle_rst(path):
+        paths.append(path)
+
+    monkeypatch.setattr(v, "handle_rst_file", mock_handle_rst)
+    v.process("docs")
+
+    assert len(paths) > 1
+    assert all(path.endswith(".rst") for path in paths)
+
+
+def test_needs_visitor_is_needs_directive():
+    v = NeedsVisitor(lambda x: x)
+    assert v.is_needs_directive(".. req::")
+    assert not v.is_needs_directive("test")
+    assert not v.is_needs_directive(".. code::")
+
+
+def test_needs_visitor_format_attributes():
+    v = NeedsVisitor(lambda x: x)
+    attr = OrderedDict()
+    attr[":id:"] = "R-12345"
+    attr[":updated:"] = "casablanca"
+    lines = v.format_attributes(attr, 4)
+    assert len(lines) == 2
+    assert lines[0] == "    :id: R-12345"
+    assert lines[1] == "    :updated: casablanca"
+
+
+def test_needs_visitor_parse_attributes():
+    v = NeedsVisitor(lambda x: x)
+    assert v.parse_attribute("   :id: R-12345") == [":id:", "R-12345"]
+    assert v.parse_attribute("   :key: one two") == [":key:", "one two"]
+
+
+def test_needs_visitor_calc_indent():
+    v = NeedsVisitor(lambda x: x)
+    assert v.calc_indent("    test") == 4
+    assert v.calc_indent("   test") == 3
+    assert v.calc_indent("test") == 0
+
+
+def test_needs_visitor_no_change(monkeypatch):
+    v = NeedsVisitor(lambda x: x)
+    lines = """.. req::
+        :id: R-12345
+        :updated: casablanca
+        
+        Here's my requirement"""
+    monkeypatch.setattr(v, "read", lambda path: lines.split("\n"))
+    result = []
+    monkeypatch.setattr(v, "write", lambda _, content: result.extend(content))
+
+    v.handle_rst_file("dummy_path")
+    assert len(result) == 5
+    assert "\n".join(result) == lines
+
+
+def test_needs_visitor_with_fix(monkeypatch):
+    fixer = MetadataFixer({"R-12345": (":introduced:", "casablanca")})
+    v = NeedsVisitor(fixer)
+    lines = """.. req::
+        :id: R-12345
+
+        Here's my requirement"""
+    monkeypatch.setattr(v, "read", lambda path: lines.split("\n"))
+    result = []
+    monkeypatch.setattr(v, "write", lambda _, content: result.extend(content))
+
+    v.handle_rst_file("dummy_path")
+    assert len(result) == 5
+    assert ":introduced: casablanca" in "\n".join(result)
+
+
+def test_load_invalid_reqs():
+    input_file = [
+        "r_id,attr,value",
+        "R-12345,:updated:,casablanca"
+    ]
+    result = load_invalid_reqs(input_file)
+    assert "R-12345" in result
+    assert result["R-12345"][0] == ":updated:"
+    assert result["R-12345"][1] == "casablanca"
index 2661c59..c04ff2a 100644 (file)
@@ -36,6 +36,7 @@ This script will generate an summary of the requirements changes between
 two version's of requirements by analyzing the needs.json file.  The template
 can be customized by updating release-requirement-changes.rst.jinja2.
 """
+import csv
 from itertools import groupby, chain
 import json
 import os
@@ -279,15 +280,21 @@ def print_invalid_metadata_report(difference_finder, current_version):
     print()
     print("Requirements Added, but Missing :introduced: Attribute")
     print("----------------------------------------------------")
+    errors = [["reqt_id", "attribute", "value"]]
     for req in difference_finder.new_requirements.values():
         if "introduced" not in req or req["introduced"] != current_version:
+            errors.append([req["id"], ":introduced:", current_version])
             print(req["id"])
     print()
     print("Requirements Changed, but Missing :updated: Attribute")
     print("-----------------------------------------------------")
     for req in difference_finder.changed_requirements.values():
         if "updated" not in req or req["updated"] != current_version:
+            errors.append([req["id"], ":updated:", current_version])
             print(req["id"])
+    with open("invalid_metadata.csv", "w", newline="") as error_report:
+        error_report = csv.writer(error_report)
+        error_report.writerows(errors)
 
 
 if __name__ == "__main__":
@@ -310,3 +317,5 @@ if __name__ == "__main__":
         num_removed=len(differ.removed_requirements),
         num_changed=len(differ.changed_requirements),
     )
+
+