Repository: softlayer/softlayer-python
Pull request #1729 (2022-08-12 14:16:05)

New command: ipsec cancel
[#issue1725](https://github.com/softlayer/softlayer-python/issues/1725)

slcli ipsec cancel 3892 --immediate --reason test --comment testcli

![image](https://user-images.githubusercontent.com/22535437/184372232-ee760255-2ee9-4b9d-9fef-dd28cfc0789d.png)



Patch:
diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py
index 501ec660..72f0cc96 100644
--- a/SoftLayer/CLI/routes.py
+++ b/SoftLayer/CLI/routes.py
@@ -213,6 +213,7 @@
     ('ipsec:translation-update', 'SoftLayer.CLI.vpn.ipsec.translation.update:cli'),
     ('ipsec:update', 'SoftLayer.CLI.vpn.ipsec.update:cli'),
     ('ipsec:order', 'SoftLayer.CLI.vpn.ipsec.order:cli'),
+    ('ipsec:cancel', 'SoftLayer.CLI.vpn.ipsec.cancel:cli'),
 
     ('loadbal', 'SoftLayer.CLI.loadbal'),
     ('loadbal:detail', 'SoftLayer.CLI.loadbal.detail:cli'),
diff --git a/SoftLayer/CLI/vpn/ipsec/cancel.py b/SoftLayer/CLI/vpn/ipsec/cancel.py
new file mode 100644
index 00000000..e082ee9d
--- /dev/null
+++ b/SoftLayer/CLI/vpn/ipsec/cancel.py
@@ -0,0 +1,31 @@
+"""Cancel an IPSec service."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+
+import SoftLayer
+from SoftLayer.CLI import environment
+
+
+@click.command(cls=SoftLayer.CLI.command.SLCommand, )
+@click.argument('identifier')
+@click.option('--immediate',
+              is_flag=True,
+              default=False,
+              help="Cancels the service  immediately (instead of on the billing anniversary)")
+@click.option('--reason',
+              help="An optional cancellation reason. See cancel-reasons for a list of available options")
+@environment.pass_env
+def cli(env, identifier, immediate, reason):
+    """Cancel a IPSEC VPN tunnel context."""
+
+    manager = SoftLayer.IPSECManager(env.client)
+    context = manager.get_tunnel_context(identifier, mask='billingItem')
+
+    if 'billingItem' not in context:
+        raise SoftLayer.SoftLayerError("Cannot locate billing. May already be cancelled.")
+
+    result = manager.cancel_item(context['billingItem']['id'], immediate, reason)
+
+    if result:
+        env.fout("Ipsec {} was cancelled.".format(identifier))
diff --git a/SoftLayer/managers/ipsec.py b/SoftLayer/managers/ipsec.py
index 1043b2f1..a725ad58 100644
--- a/SoftLayer/managers/ipsec.py
+++ b/SoftLayer/managers/ipsec.py
@@ -299,3 +299,21 @@ def order(self, datacenter, item_package):
                                             item_keynames=item_package,
                                             complex_type=complex_type,
                                             hourly=False)
+
+    def cancel_item(self, identifier, immediate, reason):
+        """Cancels the specified billing item Ipsec.
+
+        Example::
+
+            # Cancels ipsec id 1234
+            result = mgr.cancel_item(billing_item_id=1234)
+
+        :param int billing_id: The ID of the billing item to be cancelled.
+        :param string reason: The reason code for the cancellation. This should come from
+                              :func:`get_cancellation_reasons`.
+        :param bool immediate: If set to True, will automatically update the cancelation ticket to request
+                               the resource be reclaimed asap. This request still has to be reviewed by a human
+        :returns: True on success or an exception
+        """
+        return self.client.call('SoftLayer_Billing_Item', 'cancelItem',
+                                True, immediate, reason, id=identifier)
diff --git a/docs/cli/ipsec.rst b/docs/cli/ipsec.rst
index 8bfb9905..713905a3 100644
--- a/docs/cli/ipsec.rst
+++ b/docs/cli/ipsec.rst
@@ -275,4 +275,8 @@ The following is an example of updating an existing address translation entry.
 
 .. click:: SoftLayer.CLI.vpn.ipsec.order:cli
     :prog: ipsec order
+    :show-nested:
+
+.. click:: SoftLayer.CLI.vpn.ipsec.cancel:cli
+    :prog: ipsec cancel
     :show-nested:
\ No newline at end of file


Test patch:
diff --git a/tests/CLI/modules/ipsec_tests.py b/tests/CLI/modules/ipsec_tests.py
index 5a7a6589..eb473101 100644
--- a/tests/CLI/modules/ipsec_tests.py
+++ b/tests/CLI/modules/ipsec_tests.py
@@ -7,7 +7,6 @@
 
 import json
 
-
 from SoftLayer.CLI.exceptions import ArgumentError
 from SoftLayer.CLI.exceptions import CLIHalt
 from SoftLayer.fixtures import SoftLayer_Product_Order
@@ -520,3 +519,41 @@ def test_ipsec_order(self):
         order_mock.return_value = SoftLayer_Product_Order.ipsec_placeOrder
         result = self.run_command(['ipsec', 'order', '-d', 'dal13'])
         self.assert_no_fail(result)
+
+    def test_ipsec_cancel(self):
+        mock = self.set_mock('SoftLayer_Account', 'getNetworkTunnelContexts')
+        mock.return_value = [{
+            "createDate": "2013-11-05T16:03:53-06:00",
+            "id": 445,
+            "internalPeerIpAddress": "184.172.127.9",
+            "modifyDate": "2022-07-19T09:34:53-06:00",
+            "name": "ipsec003",
+            "phaseOneAuthentication": "MD5",
+            "phaseOneDiffieHellmanGroup": 2,
+            "phaseOneEncryption": "3DES",
+            "phaseOneKeylife": 14400,
+            "phaseTwoAuthentication": "MD5",
+            "phaseTwoDiffieHellmanGroup": 2,
+            "phaseTwoEncryption": "3DES",
+            "phaseTwoKeylife": 3600,
+            "phaseTwoPerfectForwardSecrecy": 1,
+            "billingItem": {
+                "allowCancellationFlag": 1,
+                "categoryCode": "network_tunnel",
+                "createDate": "2022-07-19T09:34:52-06:00",
+                "cycleStartDate": "2022-08-03T23:07:43-06:00",
+                "description": "IPSEC - Standard",
+                "id": 977194617,
+                "lastBillDate": "2022-08-03T23:07:43-06:00",
+                "modifyDate": "2022-08-03T23:07:43-06:00",
+                "nextBillDate": "2022-09-03T23:00:00-06:00",
+                "oneTimeFee": "0",
+                "orderItemId": 932515967,
+                "recurringMonths": 1,
+                "serviceProviderId": 1,
+            }}]
+
+        mock = self.set_mock('SoftLayer_Billing_Item', 'cancelItem')
+        mock.return_value = True
+        result = self.run_command(['ipsec', 'cancel', '445', '--immediate', '--reason', 'test'])
+        self.assert_no_fail(result)
diff --git a/tests/managers/ipsec_tests.py b/tests/managers/ipsec_tests.py
index 1d86f20d..b47ad867 100644
--- a/tests/managers/ipsec_tests.py
+++ b/tests/managers/ipsec_tests.py
@@ -319,3 +319,9 @@ def test_order(self):
                  'itemId': 1092,
                  'itemPriceId': '2048'}]}}
         self.assertEqual(result, order)
+
+    def test_cancel_item(self):
+        _mock = self.set_mock('SoftLayer_Billing_Item', 'cancelItem')
+        _mock.return_value = True
+        result = self.ipsec.cancel_item(443, True, 'test')
+        self.assertEqual(result, True)