Feature 10996: Adds ns-op-cancel command

Change-Id: I349cea39231338c667da4fe46857bd0b246db908
Signed-off-by: Gabriel Cuba <gcuba@whitestack.com>
diff --git a/osmclient/cli_commands/nslcm_ops.py b/osmclient/cli_commands/nslcm_ops.py
index 46197d0..9528d08 100755
--- a/osmclient/cli_commands/nslcm_ops.py
+++ b/osmclient/cli_commands/nslcm_ops.py
@@ -149,3 +149,31 @@
             table.add_row([k, utils.wrap_text(json.dumps(v, indent=2), 100)])
     table.align = "l"
     print(table)
+
+
+@click.command(name="ns-op-cancel", short_help="cancels an ongoing NS operation")
+@click.argument("id")
+@click.option(
+    "--cancel_mode",
+    required=False,
+    default="GRACEFUL",
+    show_default=True,
+    help="Mode of cancellation, can be FORCEFUL or GRACEFUL",
+)
+@click.option(
+    "--wait",
+    required=False,
+    default=False,
+    is_flag=True,
+    help="do not return the control immediately, but keep it "
+    "until the operation is completed, or timeout",
+)
+@click.pass_context
+def ns_op_cancel(ctx, id, cancel_mode, wait):
+    """Cancels an ongoing NS operation
+
+    ID: operation identifier
+    """
+    logger.debug("")
+    utils.check_client_version(ctx.obj, ctx.command.name)
+    ctx.obj.ns.cancel_op(id, cancel_mode, wait)
diff --git a/osmclient/common/wait.py b/osmclient/common/wait.py
index d3f673c..3f5b51f 100644
--- a/osmclient/common/wait.py
+++ b/osmclient/common/wait.py
@@ -58,6 +58,8 @@
     """
     if entity == "NS" or entity == "NSI":
         return ("COMPLETED", "PARTIALLY_COMPLETED"), ("FAILED_TEMP", "FAILED")
+    elif entity == "OPCANCEL":
+        return ("FAILED_TEMP"), ("COMPLETED",)
     else:
         return ("ENABLED",), ("ERROR",)
 
@@ -72,7 +74,7 @@
     :param entity: can be NS, NSI, or other
     :return: status of the operation
     """
-    if entity == "NS" or entity == "NSI":
+    if entity in ["NS", "NSI", "OPCANCEL"]:
         return resp.get("operationState")
     else:
         return resp.get("_admin", {}).get("operationalState")
@@ -111,7 +113,7 @@
     :param entity: can be NS, NSI, or other
     :return:
     """
-    if entity in ("NS", "NSI"):
+    if entity in ("NS", "NSI", "OPCANCEL"):
         # For NS and NSI, 'detailed-status' is a JSON "root" member:
         return resp.get("detailed-status")
     else:
diff --git a/osmclient/scripts/osm.py b/osmclient/scripts/osm.py
index 74bb84b..20cddc6 100755
--- a/osmclient/scripts/osm.py
+++ b/osmclient/scripts/osm.py
@@ -187,6 +187,7 @@
 
         cli_osm.add_command(nslcm_ops.ns_op_list)
         cli_osm.add_command(nslcm_ops.ns_op_show)
+        cli_osm.add_command(nslcm_ops.ns_op_cancel)
 
         cli_osm.add_command(nslcm.ns_action)
         cli_osm.add_command(nslcm.vnf_scale)
diff --git a/osmclient/sol005/ns.py b/osmclient/sol005/ns.py
index c84d090..e8442f9 100644
--- a/osmclient/sol005/ns.py
+++ b/osmclient/sol005/ns.py
@@ -40,7 +40,7 @@
         )
 
     # NS '--wait' option
-    def _wait(self, id, wait_time, deleteFlag=False):
+    def _wait(self, id, wait_time, deleteFlag=False, entity="NS"):
         self._logger.debug("")
         # Endpoint to get operation status
         apiUrlStatus = "{}{}{}".format(
@@ -50,7 +50,7 @@
         if isinstance(wait_time, bool):
             wait_time = WaitForStatus.TIMEOUT_NS_OPERATION
         WaitForStatus.wait_for_status(
-            "NS",
+            entity,
             str(id),
             wait_time,
             apiUrlStatus,
@@ -431,6 +431,33 @@
             message = "failed to exec operation {}:\nerror:\n{}".format(name, str(exc))
             raise ClientException(message)
 
+    def cancel_op(self, operation_id, cancel_mode, wait=False):
+        """Cancels an LCM operation"""
+        self._client.get_token()
+        self._apiResource = "/ns_lcm_op_occs"
+        self._apiBase = "{}{}{}".format(
+            self._apiName, self._apiVersion, self._apiResource
+        )
+        endpoint = "{}/{}/cancel".format(self._apiBase, operation_id)
+        op_data = {"cancelMode": cancel_mode}
+        try:
+            http_code, resp = self._http.post_cmd(
+                endpoint=endpoint, postfields_dict=op_data
+            )
+            if http_code == 202:
+                if wait:
+                    self._wait(operation_id, wait, deleteFlag=True, entity="OPCANCEL")
+                else:
+                    print("Cancellation in progress")
+            else:
+                msg = resp or ""
+                raise ClientException(msg)
+        except ClientException as exc:
+            message = "failed to exec operation {}:\nerror:\n{}".format(
+                operation_id, str(exc)
+            )
+            raise ClientException(message)
+
     def scale_vnf(
         self,
         ns_name,