Towards a1 1.0.0; DELETE, no vector: 21/1021/7
authorTommy Carpenter <tc677g@att.com>
Thu, 26 Sep 2019 15:14:16 +0000 (11:14 -0400)
committerTommy Carpenter <tc677g@att.com>
Tue, 1 Oct 2019 13:25:23 +0000 (09:25 -0400)
* Implement instance delete
* Moves away from the status vector and now aggregates statuses
* Pop through a1s mailbox "3x as often"; on all 3 kinds of instance GET since all such calls want the latest information
* Misc cleanups in controller
* Add rmr-version.yaml for CICD jobs

Change-Id: I7290652784b2d6d297dd9e78af3051eba209651c
Signed-off-by: Tommy Carpenter <tc677g@att.com>
15 files changed:
Dockerfile
a1/controller.py
a1/data.py
a1/exceptions.py
a1/openapi.yaml
container-tag.yaml
docs/release-notes.rst
install_deps.sh
integration_tests/a1mediator/Chart.yaml
integration_tests/receiver.py
integration_tests/test_a1.tavern.yaml
rmr-version.yaml [new file with mode: 0644]
setup.py
tests/test_controller.py
tox-integration.ini

index 46739e2..4d76cff 100644 (file)
@@ -27,34 +27,36 @@ RUN git clone --branch 1.8.1 https://gerrit.o-ran-sc.org/r/ric-plt/lib/rmr \
 # a1 stage 2
 FROM python:3.7-alpine
 
+# dir that rmr routing file temp goes into
+RUN mkdir -p /opt/route/
+
+# Gevent needs gcc
+RUN apk update && apk add bash gcc musl-dev
+
+# Speed hack; we install gevent here because when building repeatedly (eg during dev) and only changing a1 code,
+# we do not need to keep compiling gevent which takes forever
+RUN pip install --upgrade pip && pip install gevent
+
 # copies
 COPY --from=0 /usr/local/lib64/libnng.so /usr/local/lib64/libnng.so
 COPY --from=0 /usr/local/lib64/librmr_nng.so /usr/local/lib64/librmr_nng.so
 COPY a1/ /tmp/a1
-COPY tests/ /tmp/tests
 COPY setup.py tox.ini /tmp/
 WORKDIR /tmp
 
-# dir that rmr routing file temp goes into
-RUN mkdir -p /opt/route/
-
-# Gevent needs gcc
-RUN apk update && apk add bash gcc musl-dev
-
 # do the actual install; this writes into /usr/local, need root
-RUN pip install --upgrade pip && pip install .
+RUN pip install .
 
 # Switch to a non-root user for security reasons.
 # a1 does not currently write into any dirs so no chowns are needed at this time.
 ENV A1USER a1user
-RUN addgroup -S $A1USER && adduser -S -G $A1USER $A1USER 
+RUN addgroup -S $A1USER && adduser -S -G $A1USER $A1USER
 USER $A1USER
 
 # misc setups
 EXPOSE 10000
 ENV LD_LIBRARY_PATH /usr/local/lib/:/usr/local/lib64
 ENV RMR_SEED_RT /opt/route/local.rt
-
 # dont buffer logging
 ENV PYTHONUNBUFFERED 1
 
index b189750..7db18a0 100644 (file)
@@ -1,3 +1,6 @@
+"""
+Main a1 controller
+"""
 # ==================================================================================
 #       Copyright (c) 2019 Nokia
 #       Copyright (c) 2018-2019 AT&T Intellectual Property.
@@ -17,8 +20,8 @@
 import json
 from flask import Response
 from jsonschema import validate
-import connexion
 from jsonschema.exceptions import ValidationError
+import connexion
 from a1 import get_module_logger
 from a1 import a1rmr, exceptions, data
 
@@ -32,30 +35,30 @@ def _try_func_return(func):
     """
     try:
         return func()
-    except ValidationError as exc:
+    except (ValidationError, exceptions.PolicyTypeAlreadyExists) as exc:
         logger.exception(exc)
         return "", 400
-    except exceptions.PolicyTypeAlreadyExists as exc:
-        logger.exception(exc)
-        return "", 400
-    except exceptions.PolicyTypeNotFound as exc:
+    except (exceptions.PolicyTypeNotFound, exceptions.PolicyInstanceNotFound) as exc:
         logger.exception(exc)
         return "", 404
-    except exceptions.PolicyInstanceNotFound as exc:
-        logger.exception(exc)
-        return "", 404
-    except exceptions.MissingManifest as exc:
-        logger.exception(exc)
-        return "A1 was unable to find the required RIC manifest. report this!", 500
-    except exceptions.MissingRmrString as exc:
-        logger.exception(exc)
-        return "A1 does not have a mapping for the desired rmr string. report this!", 500
     except BaseException as exc:
         # catch all, should never happen...
         logger.exception(exc)
         return Response(status=500)
 
 
+def _gen_body_to_handler(operation, policy_type_id, policy_instance_id, payload=None):
+    """
+    used to create the payloads that get sent to downstream policy handlers
+    """
+    return {
+        "operation": operation,
+        "policy_type_id": policy_type_id,
+        "policy_instance_id": policy_instance_id,
+        "payload": payload,
+    }
+
+
 # Healthcheck
 
 
@@ -74,7 +77,7 @@ def get_all_policy_types():
     """
     Handles GET /a1-p/policytypes
     """
-    return _try_func_return(lambda: data.get_type_list())
+    return _try_func_return(data.get_type_list)
 
 
 def create_policy_type(policy_type_id):
@@ -82,12 +85,12 @@ def create_policy_type(policy_type_id):
     Handles PUT /a1-p/policytypes/policy_type_id
     """
 
-    def _put_type_handler(policy_type_id, body):
+    def put_type_handler():
         data.store_policy_type(policy_type_id, body)
         return "", 201
 
     body = connexion.request.json
-    return _try_func_return(lambda: _put_type_handler(policy_type_id, body))
+    return _try_func_return(put_type_handler)
 
 
 def get_policy_type(policy_type_id):
@@ -101,6 +104,7 @@ def delete_policy_type(policy_type_id):
     """
     Handles DELETE /a1-p/policytypes/policy_type_id
     """
+    logger.error(policy_type_id)
     return "", 501
 
 
@@ -111,62 +115,63 @@ def get_all_instances_for_type(policy_type_id):
     """
     Handles GET /a1-p/policytypes/policy_type_id/policies
     """
-    return _try_func_return(lambda: data.get_instance_list(policy_type_id))
+
+    def get_all_instance_handler():
+        # try to clean up instances for this type
+        for policy_instance_id in data.get_instance_list(policy_type_id):
+            data.delete_policy_instance_if_applicable(policy_type_id, policy_instance_id)
+
+        # re-fetch this list as it may have changed
+        return data.get_instance_list(policy_type_id), 200
+
+    return _try_func_return(get_all_instance_handler)
 
 
 def get_policy_instance(policy_type_id, policy_instance_id):
     """
     Handles GET /a1-p/policytypes/polidyid/policies/policy_instance_id
     """
-    # 200 is automatic here
-    return _try_func_return(lambda: data.get_policy_instance(policy_type_id, policy_instance_id))
+
+    def get_instance_handler():
+        # delete if applicable (will raise if not applicable to begin with)
+        data.delete_policy_instance_if_applicable(policy_type_id, policy_instance_id)
+
+        # raise 404 now that we may have deleted, or get the instance otherwise
+        return data.get_policy_instance(policy_type_id, policy_instance_id), 200
+
+    return _try_func_return(get_instance_handler)
 
 
 def get_policy_instance_status(policy_type_id, policy_instance_id):
     """
     Handles GET /a1-p/policytypes/polidyid/policies/policy_instance_id/status
-    """
-
-    def _get_status_handler(policy_type_id, policy_instance_id):
-        """
-        Pop trough A1s mailbox, insert the latest status updates into the database, and then return the status vector
-
-        NOTE: this is done lazily. Meaning, when someone performs a GET on this API, we pop through a1s mailbox.
-        THis may not work in the future if there are "thousands" of policy acknowledgements that hit a1 before this is called,
-        because the rmr mailbox may fill. However, in the near term, we do not expect this to happen.
-        """
-        # check validity to 404 first:
-        data.type_is_valid(policy_type_id)
-        data.instance_is_valid(policy_type_id, policy_instance_id)
 
-        # pop a1s mailbox, looking for policy notifications
-        new_messages = a1rmr.dequeue_all_waiting_messages(21024)
+    Return the aggregated status. The order of rules is as follows:
+        1. If a1 has received at least one status, and *all* received statuses are "DELETED", we blow away the instance and return a 404
+        2. if a1 has received at least one status and at least one is OK, we return "IN EFFECT"
+        3. "NOT IN EFFECT" otherwise (no statuses, or none are OK but not all are deleted)
+    """
 
-        # try to parse the messages as responses. Drop those that are malformed
-        for msg in new_messages:
-            # note, we don't use the parameters "policy_type_id, policy_instance" from above here,
-            # because we are popping the whole mailbox, which might include other statuses
-            pay = json.loads(msg["payload"])
-            if "policy_type_id" in pay and "policy_instance_id" in pay and "handler_id" in pay and "status" in pay:
-                data.set_policy_instance_status(
-                    pay["policy_type_id"], pay["policy_instance_id"], pay["handler_id"], pay["status"]
-                )
-            else:
-                logger.debug("Dropping message")
-                logger.debug(pay)
+    def get_status_handler():
+        # delete if applicable (will raise if not applicable to begin with)
+        data.delete_policy_instance_if_applicable(policy_type_id, policy_instance_id)
 
-        # return the status vector
-        return data.get_policy_instance_statuses(policy_type_id, policy_instance_id)
+        vector = data.get_policy_instance_statuses(policy_type_id, policy_instance_id)
+        for i in vector:
+            if i == "OK":
+                return "IN EFFECT", 200
+        return "NOT IN EFFECT", 200
 
-    return _try_func_return(lambda: _get_status_handler(policy_type_id, policy_instance_id))
+    return _try_func_return(get_status_handler)
 
 
 def create_or_replace_policy_instance(policy_type_id, policy_instance_id):
     """
     Handles PUT /a1-p/policytypes/polidyid/policies/policy_instance_id
     """
+    instance = connexion.request.json
 
-    def _put_instance_handler(policy_type_id, policy_instance_id, instance):
+    def put_instance_handler():
         """
         Handles policy instance put
 
@@ -179,24 +184,28 @@ def create_or_replace_policy_instance(policy_type_id, policy_instance_id):
         # store the instance
         data.store_policy_instance(policy_type_id, policy_instance_id, instance)
 
-        body = {
-            "operation": "CREATE",
-            "policy_type_id": policy_type_id,
-            "policy_instance_id": policy_instance_id,
-            "payload": instance,
-        }
-
         # send rmr (best effort)
+        body = _gen_body_to_handler("CREATE", policy_type_id, policy_instance_id, payload=instance)
         a1rmr.send(json.dumps(body), message_type=policy_type_id)
 
-        return "", 201
+        return "", 202
 
-    instance = connexion.request.json
-    return _try_func_return(lambda: _put_instance_handler(policy_type_id, policy_instance_id, instance))
+    return _try_func_return(put_instance_handler)
 
 
 def delete_policy_instance(policy_type_id, policy_instance_id):
     """
     Handles DELETE /a1-p/policytypes/polidyid/policies/policy_instance_id
     """
-    return "", 501
+
+    def delete_instance_handler():
+        """
+        here we send out the DELETEs but we don't delete the instance until a GET is called where we check the statuses
+        """
+        # send rmr (best effort)
+        body = _gen_body_to_handler("DELETE", policy_type_id, policy_instance_id)
+        a1rmr.send(json.dumps(body), message_type=policy_type_id)
+
+        return "", 202
+
+    return _try_func_return(delete_instance_handler)
index 5e2690d..51ba044 100644 (file)
@@ -25,6 +25,8 @@ We use dict data structures (KV) with the expectation of having to move this int
 """
 from a1.exceptions import PolicyTypeNotFound, PolicyInstanceNotFound, PolicyTypeAlreadyExists
 from a1 import get_module_logger
+from a1 import a1rmr
+import json
 
 logger = get_module_logger(__name__)
 
@@ -35,6 +37,7 @@ I = "instances"
 H = "handlers"
 D = "data"
 
+
 # Types
 
 
@@ -99,11 +102,45 @@ def store_policy_instance(policy_type_id, policy_instance_id, instance):
     POLICY_DATA[policy_type_id][I][policy_instance_id][H] = {}
 
 
+def delete_policy_instance_if_applicable(policy_type_id, policy_instance_id):
+    """
+    delete a policy instance if all known statuses are DELETED
+
+    pops a1s waiting mailbox
+    """
+    # pop through a1s mailbox, updating a1s db of all policy statuses
+    for msg in a1rmr.dequeue_all_waiting_messages(21024):
+        # try to parse the messages as responses. Drop those that are malformed
+        # NOTE: we don't use the parameters "policy_type_id, policy_instance" from above here,
+        # because we are popping the whole mailbox, which might include other statuses
+        pay = json.loads(msg["payload"])
+        if "policy_type_id" in pay and "policy_instance_id" in pay and "handler_id" in pay and "status" in pay:
+            set_policy_instance_status(pay["policy_type_id"], pay["policy_instance_id"], pay["handler_id"], pay["status"])
+        else:
+            logger.debug("Dropping message")
+            logger.debug(pay)
+
+    # raise if not valid
+    instance_is_valid(policy_type_id, policy_instance_id)
+
+    # see if we can delete
+    vector = get_policy_instance_statuses(policy_type_id, policy_instance_id)
+    if vector != []:
+        all_deleted = True
+        for i in vector:
+            if i != "DELETED":
+                all_deleted = False
+                break  # have at least one not DELETED, do nothing
+
+        # blow away from a1 db
+        if all_deleted:
+            del POLICY_DATA[policy_type_id][I][policy_instance_id]
+
+
 def get_policy_instance(policy_type_id, policy_instance_id):
     """
     Retrieve a policy instance
     """
-    type_is_valid(policy_type_id)
     instance_is_valid(policy_type_id, policy_instance_id)
     return POLICY_DATA[policy_type_id][I][policy_instance_id][D]
 
@@ -112,17 +149,15 @@ def get_policy_instance_statuses(policy_type_id, policy_instance_id):
     """
     Retrieve the status vector for a policy instance
     """
-    type_is_valid(policy_type_id)
     instance_is_valid(policy_type_id, policy_instance_id)
 
-    return [{"handler_id": k, "status": v} for k, v in POLICY_DATA[policy_type_id][I][policy_instance_id][H].items()]
+    return [v for _, v in POLICY_DATA[policy_type_id][I][policy_instance_id][H].items()]
 
 
 def set_policy_instance_status(policy_type_id, policy_instance_id, handler_id, status):
     """
     Update the status of a handler id of a policy instance
     """
-    type_is_valid(policy_type_id)
     instance_is_valid(policy_type_id, policy_instance_id)
 
     POLICY_DATA[policy_type_id][I][policy_instance_id][H][handler_id] = status
index 1362b2a..be113ba 100644 (file)
@@ -29,15 +29,3 @@ class PolicyTypeNotFound(BaseException):
 
 class PolicyTypeAlreadyExists(BaseException):
     """a policy type already exists and replace not supported at this time"""
-
-
-class MissingRmrString(BaseException):
-    pass
-
-
-class MissingManifest(BaseException):
-    pass
-
-
-class MissingRmrMapping(BaseException):
-    pass
index 2d0cdfe..fed4b77 100644 (file)
@@ -204,9 +204,9 @@ paths:
         - A1 Mediator
       operationId: a1.controller.delete_policy_instance
       responses:
-        '204':
+        '202':
           description: >
-            policy instance successfully deleted
+            policy instance deletion initiated
         '404':
           description: >
             there is no policy instance with this policy_instance_id
@@ -234,9 +234,9 @@ paths:
               trigger_threshold: 10
 
       responses:
-        '201':
+        '202':
           description: >
-            Policy instance created
+            Policy instance creation initiated
         '400':
           description: >
             Bad PUT body for this policy instance
@@ -261,33 +261,28 @@ paths:
     get:
       description: >
         Retrieve the policy instance status across all handlers of the policy
-
+        If this endpoint returns successfully (200), it is either IN EFFECT or NOT IN EFFECT.
+        IN EFFECT is returned if at least one policy handler in the RIC is implementing the policy
+        NOT IN EFFECT is returned otherwise
+        If a policy instance is successfully deleted, this endpoint will return a 404 (not a 200)
       tags:
         - A1 Mediator
       operationId: a1.controller.get_policy_instance_status
       responses:
         '200':
           description: >
-            The policy instance status.
-            Returns a vector of statuses, where each contains a handler_id (opaque id of a RIC component that implements this policy) and the policy status as returned by that handler
+            successfully retrieved the status
           content:
-            application/json:
+            text/plain:
               schema:
-                type: array
-                items:
-                  type: object
-                  properties:
-                    handler_id:
-                      type: string
-                    status:
-                      type: string
-              example:
-                [{"handler_id": "1234-5678", "status" : "OK"}, {"handler_id": "abc-def", "status" : "NOT IMPLEMENTED"}]
+                type: string
+                enum:
+                 - IN EFFECT
+                 - NOT IN EFFECT
         '404':
           description: >
             there is no policy instance with this policy_instance_id or there is no policy type with this policy_type_id
 
-
 components:
   schemas:
     policy_type_schema:
@@ -381,6 +376,10 @@ components:
           description: >
             the status of this policy instance in this handler
           type: string
+          enum:
+            - OK
+            - ERROR
+            - DELETED
       example:
         policy_type_id: 12345678
         policy_instance_id: 3d2157af-6a8f-4a7c-810f-38c2f824bf12
index 2d835b0..2ac2520 100644 (file)
@@ -1,4 +1,4 @@
 # The Jenkins job uses this string for the tag in the image name
 # for example nexus3.o-ran-sc.org:10004/my-image-name:my-tag
 ---
-tag: 0.13.0-NOT_FOR_USE_YET
+tag: 0.14.0-NOT_FOR_USE_YET
index 8c9cc93..dd26d9b 100644 (file)
@@ -29,6 +29,15 @@ and this project adheres to `Semantic Versioning <http://semver.org/>`__.
 
     * Release 1.0.0 will be the Release A version of A1
 
+[0.14.0] - 10/1/2019
+::
+
+    * Implement instance delete
+    * Moves away from the status vector and now aggregates statuses
+    * Pop through a1s mailbox "3x as often"; on all 3 kinds of instance GET since all such calls want the latest information
+    * Misc cleanups in controller (closures ftw)
+    * Add rmr-version.yaml for CICD jobs
+
 [0.13.0] - 9/25/2019
 ::
 
index f7b3660..9ab8cae 100755 (executable)
@@ -1,5 +1,5 @@
 #!/bin/sh
-git clone https://gerrit.oran-osc.org/r/ric-plt/lib/rmr \
+git clone --branch 1.8.1 https://gerrit.oran-osc.org/r/ric-plt/lib/rmr \
     && cd rmr \
     && mkdir .build; cd .build; cmake .. -DPACK_EXTERNALS=1; sudo make install \
     && cd ../.. \
index e95e934..142094b 100644 (file)
@@ -1,4 +1,4 @@
 apiVersion: v1
 description: A1 Helm chart for Kubernetes
 name: a1mediator
-version: 0.13.0
+version: 0.14.0
index f1c324f..75294c6 100644 (file)
@@ -47,11 +47,18 @@ while True:
 
         received_payload = json.loads(summary["payload"])
 
+        op = received_payload["operation"]
+        send_payload_status = "ERROR"
+        if op == "CREATE":
+            send_payload_status = "OK"
+        elif op == "DELETE":
+            send_payload_status = "DELETED"
+
         payload = {
             "policy_type_id": received_payload["policy_type_id"],
             "policy_instance_id": received_payload["policy_instance_id"],
             "handler_id": HANDLER_ID,
-            "status": "OK",
+            "status": send_payload_status,
         }
 
         val = json.dumps(payload).encode("utf-8")
index 8510086..47662ca 100644 (file)
@@ -75,6 +75,8 @@ stages:
             - trigger_threshold
             - window_length
           additionalProperties: false
+    response:
+      status_code: 201
 
   - name: type there now
     request:
@@ -113,7 +115,8 @@ stages:
     response:
       status_code: 404
 
-  - name: put the admission control policy
+  # PUT the instance and make sure subsequent GETs return properly
+  - name: put the admission control policy instance
     request:
       url: http://localhost:10000/a1-p/policytypes/20000/policies/admission_control_policy
       method: PUT
@@ -125,7 +128,7 @@ stages:
       headers:
         content-type: application/json
     response:
-      status_code: 201
+      status_code: 202
 
   - name: test the admission control policy get
     request:
@@ -146,9 +149,7 @@ stages:
       method: GET
     response:
       status_code: 200
-      body:
-        - handler_id: test_receiver
-          status: OK
+      # tavern doesn't yet let you check string statuses!!!
 
   - name: instance list 200 and contains the instance
     request:
@@ -159,7 +160,36 @@ stages:
       body:
         - admission_control_policy
 
+  # DELETE the instance and make sure subsequent GETs return properly
+  - name: delete the instance
+    request:
+      url: http://localhost:10000/a1-p/policytypes/20000/policies/admission_control_policy
+      method: DELETE
+    response:
+      status_code: 202
 
+  - name: instance list 200 but no instance
+    request:
+      url: http://localhost:10000/a1-p/policytypes/20000/policies
+      method: GET
+    response:
+      status_code: 200
+      body: []
+
+  - name: cant get instance status
+    delay_before: 3 # give it a few seconds for rmr
+    request:
+      url: http://localhost:10000/a1-p/policytypes/20000/policies/admission_control_policy/status
+      method: GET
+    response:
+      status_code: 404
+
+  - name: cant get instance
+    request:
+      url: http://localhost:10000/a1-p/policytypes/20000/policies/admission_control_policy
+      method: GET
+    response:
+      status_code: 404
 
 ---
 
@@ -206,6 +236,8 @@ stages:
           required:
             - test
           additionalProperties: false
+    response:
+      status_code: 201
 
   - name: type there now
     request:
@@ -268,7 +300,7 @@ stages:
       headers:
         content-type: application/json
     response:
-      status_code: 201
+      status_code: 202
 
   - name: test the delay policy get
     request:
@@ -287,9 +319,7 @@ stages:
       method: GET
     response:
       status_code: 200
-      body:
-        - handler_id: delay_receiver
-          status: OK
+      # tavern doesn't let you check non json yet!
 
   - name: instance list 200 and there
     request:
@@ -333,7 +363,7 @@ stages:
       headers:
         content-type: application/json
     response:
-      status_code: 201
+      status_code: 202
 
   - name: should be no status
     delay_before: 5  # give it a few seconds for rmr ; delay reciever sleeps for 5 seconds by default
diff --git a/rmr-version.yaml b/rmr-version.yaml
new file mode 100644 (file)
index 0000000..e79b81c
--- /dev/null
@@ -0,0 +1,3 @@
+# CI script installs RMR from PackageCloud using this version
+---
+version: 1.8.1
index f9b3f11..983e130 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -18,7 +18,7 @@ from setuptools import setup, find_packages
 
 setup(
     name="a1",
-    version="0.13.0",
+    version="0.14.0",
     packages=find_packages(exclude=["tests.*", "tests"]),
     author="Tommy Carpenter",
     description="RIC A1 Mediator for policy/intent changes",
index 3ef36a6..88d6f70 100644 (file)
@@ -45,7 +45,7 @@ def client():
 
 def _fake_dequeue(_filter_type):
     """
-    for monkeypatching a1rmnr.dequeue_all_messages
+    for monkeypatching a1rmnr.dequeue_all_messages with a good status
     """
     fake_msg = {}
     pay = b'{"policy_type_id": 20000, "policy_instance_id": "admission_control_policy", "handler_id": "test_receiver", "status": "OK"}'
@@ -54,6 +54,24 @@ def _fake_dequeue(_filter_type):
     return new_messages
 
 
+def _fake_dequeue_none(_filter_type):
+    """
+    for monkeypatching a1rmnr.dequeue_all_messages with no waiting messages
+    """
+    return []
+
+
+def _fake_dequeue_deleted(_filter_type):
+    """
+    for monkeypatching a1rmnr.dequeue_all_messages with a DELETED status
+    """
+    fake_msg = {}
+    pay = b'{"policy_type_id": 20000, "policy_instance_id": "admission_control_policy", "handler_id": "test_receiver", "status": "DELETED"}'
+    fake_msg["payload"] = pay
+    new_messages = [fake_msg]
+    return new_messages
+
+
 def _test_put_patch(monkeypatch):
     rmr_mocks.patch_rmr(monkeypatch)
     monkeypatch.setattr("rmr.rmr.rmr_send_msg", rmr_mocks.send_mock_generator(0))  # good sends for this whole batch
@@ -89,6 +107,7 @@ def test_xapp_put_good(client, monkeypatch, adm_type_good, adm_instance_good):
     assert res.json == []
 
     # instance 404 because type not there yet
+    monkeypatch.setattr("a1.a1rmr.dequeue_all_waiting_messages", _fake_dequeue_none)
     res = client.get(ADM_CTRL_POLICIES)
     assert res.status_code == 404
 
@@ -118,32 +137,66 @@ def test_xapp_put_good(client, monkeypatch, adm_type_good, adm_instance_good):
     # create a good instance
     _test_put_patch(monkeypatch)
     res = client.put(ADM_CTRL_INSTANCE, json=adm_instance_good)
-    assert res.status_code == 201
+    assert res.status_code == 202
 
     # instance 200 and in list
     res = client.get(ADM_CTRL_POLICIES)
     assert res.status_code == 200
     assert res.json == [ADM_CTRL]
 
-    # get the instance
-    res = client.get(ADM_CTRL_INSTANCE)
-    assert res.status_code == 200
-    assert res.json == adm_instance_good
+    def get_instance_good(expected):
+        # get the instance
+        res = client.get(ADM_CTRL_INSTANCE)
+        assert res.status_code == 200
+        assert res.json == adm_instance_good
+
+        # get the instance status
+        res = client.get(ADM_CTRL_INSTANCE_STATUS)
+        assert res.status_code == 200
+        assert res.get_data(as_text=True) == expected
 
-    # get the instance status
+    # try a status get but pretend we didn't get any ACKs yet to test NOT IN EFFECT
+    monkeypatch.setattr("a1.a1rmr.dequeue_all_waiting_messages", _fake_dequeue_none)
+    get_instance_good("NOT IN EFFECT")
+
+    # now pretend we did get a good ACK
     monkeypatch.setattr("a1.a1rmr.dequeue_all_waiting_messages", _fake_dequeue)
-    res = client.get(ADM_CTRL_INSTANCE_STATUS)
+    get_instance_good("IN EFFECT")
+
+    # delete it
+    res = client.delete(ADM_CTRL_INSTANCE)
+    assert res.status_code == 202
+    res = client.delete(ADM_CTRL_INSTANCE)  # should be able to do multiple deletes
+    assert res.status_code == 202
+
+    # status after a delete, but there are no messages yet, should still return
+    monkeypatch.setattr("a1.a1rmr.dequeue_all_waiting_messages", _fake_dequeue)
+    get_instance_good("IN EFFECT")
+
+    # now pretend we deleted successfully
+    monkeypatch.setattr("a1.a1rmr.dequeue_all_waiting_messages", _fake_dequeue_deleted)
+    res = client.get(ADM_CTRL_INSTANCE_STATUS)  # cant get status
+    assert res.status_code == 404
+    res = client.get(ADM_CTRL_INSTANCE)  # cant get instance
+    assert res.status_code == 404
+    # list still 200 but no instance
+    res = client.get(ADM_CTRL_POLICIES)
     assert res.status_code == 200
-    assert res.json == [{"handler_id": "test_receiver", "status": "OK"}]
+    assert res.json == []
 
-    # assert that rmr bad states don't cause problems
+
+def test_xapp_put_good_bad_rmr(client, monkeypatch, adm_instance_good):
+    """
+    assert that rmr bad states don't cause problems
+    """
+    _test_put_patch(monkeypatch)
     monkeypatch.setattr("rmr.rmr.rmr_send_msg", rmr_mocks.send_mock_generator(10))
     res = client.put(ADM_CTRL_INSTANCE, json=adm_instance_good)
-    assert res.status_code == 201
+    assert res.status_code == 202
 
     monkeypatch.setattr("rmr.rmr.rmr_send_msg", rmr_mocks.send_mock_generator(5))
     res = client.put(ADM_CTRL_INSTANCE, json=adm_instance_good)
-    assert res.status_code == 201
+    assert res.status_code == 202
 
 
 def test_bad_instances(client, monkeypatch, adm_type_good):
index 1083f4c..b562f86 100644 (file)
@@ -50,7 +50,7 @@ commands=
 # run apache bench
     ab -n 100 -c 10 -u putdata -T application/json http://localhost:10000/a1-p/policytypes/20000/policies/admission_control_policy
 #    echo "log collection"
-#integration_tests/getlogs.sh
+#    integration_tests/getlogs.sh
 commands_post=
     echo "teardown"
     helm delete testreceiver