From 10a1ee452b18b8112a5d66219fab1c97a7d2a9bf Mon Sep 17 00:00:00 2001
From: anuradha1904 <anuradha750871@gmail.com>
Date: Mon, 28 Dec 2020 23:43:47 +0530
Subject: [PATCH] Support Deploy Templates for Ironic API

1. Create Deploy Template
2. List Deploy Templates
3. Show Deploy Template Details
4. Update a Deploy Template
5. Delete Deploy Template

Story: 2008193
Task: 40957

Change-Id: I0636d810f3dac03ba18a9d5c09cda415333f80e8
---
 doc/source/user/proxies/baremetal.rst         |   8 +
 doc/source/user/resources/baremetal/index.rst |   1 +
 .../baremetal/v1/deploy_templates.rst         |  13 ++
 openstack/baremetal/v1/_proxy.py              | 106 ++++++++++
 openstack/baremetal/v1/deploy_templates.py    |  51 +++++
 openstack/tests/functional/baremetal/base.py  |  13 ++
 .../test_baremetal_deploy_templates.py        | 190 ++++++++++++++++++
 .../baremetal/v1/test_deploy_templates.py     |  75 +++++++
 ...loy-template-support-fa56005365ed6e4d.yaml |   4 +
 9 files changed, 461 insertions(+)
 create mode 100644 doc/source/user/resources/baremetal/v1/deploy_templates.rst
 create mode 100644 openstack/baremetal/v1/deploy_templates.py
 create mode 100644 openstack/tests/functional/baremetal/test_baremetal_deploy_templates.py
 create mode 100644 openstack/tests/unit/baremetal/v1/test_deploy_templates.py
 create mode 100644 releasenotes/notes/ironic-deploy-template-support-fa56005365ed6e4d.yaml

diff --git a/doc/source/user/proxies/baremetal.rst b/doc/source/user/proxies/baremetal.rst
index 350757cd8..d8978c0ff 100644
--- a/doc/source/user/proxies/baremetal.rst
+++ b/doc/source/user/proxies/baremetal.rst
@@ -77,6 +77,14 @@ Volume Target Operations
             create_volume_target, update_volume_target,
             patch_volume_target, delete_volume_target
 
+Deploy Template Operations
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+.. autoclass:: openstack.baremetal.v1._proxy.Proxy
+  :noindex:
+  :members: deploy_templates, get_deploy_template,
+            create_deploy_template, update_deploy_template,
+            patch_deploy_template, delete_deploy_template
+
 Utilities
 ---------
 
diff --git a/doc/source/user/resources/baremetal/index.rst b/doc/source/user/resources/baremetal/index.rst
index 7ccb9a292..1b284f304 100644
--- a/doc/source/user/resources/baremetal/index.rst
+++ b/doc/source/user/resources/baremetal/index.rst
@@ -12,3 +12,4 @@ Baremetal Resources
    v1/allocation
    v1/volume_connector
    v1/volume_target
+   v1/deploy_templates
diff --git a/doc/source/user/resources/baremetal/v1/deploy_templates.rst b/doc/source/user/resources/baremetal/v1/deploy_templates.rst
new file mode 100644
index 000000000..e55e63afa
--- /dev/null
+++ b/doc/source/user/resources/baremetal/v1/deploy_templates.rst
@@ -0,0 +1,13 @@
+openstack.baremetal.v1.deploy_templates
+=======================================
+
+.. automodule:: openstack.baremetal.v1.deploy_templates
+
+The DeployTemplate Class
+-------------------------
+
+The ``DeployTemplate`` class inherits
+from :class:`~openstack.resource.Resource`.
+
+.. autoclass:: openstack.baremetal.v1.deploy_templates.DeployTemplate
+   :members:
diff --git a/openstack/baremetal/v1/_proxy.py b/openstack/baremetal/v1/_proxy.py
index 356d8ee40..b395dbc6d 100644
--- a/openstack/baremetal/v1/_proxy.py
+++ b/openstack/baremetal/v1/_proxy.py
@@ -19,6 +19,7 @@ from openstack.baremetal.v1 import port as _port
 from openstack.baremetal.v1 import port_group as _portgroup
 from openstack.baremetal.v1 import volume_connector as _volumeconnector
 from openstack.baremetal.v1 import volume_target as _volumetarget
+from openstack.baremetal.v1 import deploy_templates as _deploytemplates
 from openstack import exceptions
 from openstack import proxy
 from openstack import utils
@@ -1309,3 +1310,108 @@ class Proxy(proxy.Proxy):
         """
         return self._delete(_volumetarget.VolumeTarget,
                             volume_target, ignore_missing=ignore_missing)
+
+    def deploy_templates(self, details=False, **query):
+        """Retrieve a generator of deploy_templates.
+
+        :param details: A boolean indicating whether the detailed information
+                        for every deploy_templates should be returned.
+        :param dict query: Optional query parameters to be sent to
+                           restrict the deploy_templates to be returned.
+
+        :returns: A generator of Deploy templates instances.
+        """
+        if details:
+            query['detail'] = True
+        return _deploytemplates.DeployTemplate.list(self, **query)
+
+    def create_deploy_template(self, **attrs):
+        """Create a new deploy_template from attributes.
+
+        :param dict attrs: Keyword arguments that will be used to create a
+                :class:`~openstack.baremetal.v1.deploy_templates.DeployTemplate`.
+
+        :returns: The results of deploy_template creation.
+        :rtype:
+                :class:`~openstack.baremetal.v1.deploy_templates.DeployTemplate`.
+        """
+        return self._create(_deploytemplates.DeployTemplate, **attrs)
+
+    def update_deploy_template(self, deploy_template, **attrs):
+        """Update a deploy_template.
+
+        :param deploy_template: Either the ID of a deploy_template,
+                                or an instance of
+                                :class:`~openstack.baremetal.v1.deploy_templates.DeployTemplate`.
+        :param dict attrs: The attributes to update on
+                           the deploy_template represented
+                           by the ``deploy_template`` parameter.
+
+        :returns: The updated deploy_template.
+        :rtype::class:
+                       `~openstack.baremetal.v1.deploy_templates.DeployTemplate`
+        """
+        return self._update(_deploytemplates.DeployTemplate,
+                            deploy_template, **attrs)
+
+    def delete_deploy_template(self, deploy_template,
+                               ignore_missing=True):
+        """Delete a deploy_template.
+
+        :param deploy_template:The value can be
+                               either the ID of a deploy_template or a
+        :class:`~openstack.baremetal.v1.deploy_templates.DeployTemplate`
+                instance.
+
+        :param bool ignore_missing: When set to ``False``,
+            an exception:class:`~openstack.exceptions.ResourceNotFound`
+            will be raised when the deploy_template
+            could not be found.
+            When set to ``True``, no
+            exception will be raised when attempting
+            to delete a non-existent
+            deploy_template.
+
+        :returns: The instance of the deploy_template which was deleted.
+        :rtype::class:
+                        `~openstack.baremetal.v1.deploy_templates.DeployTemplate`.
+        """
+
+        return self._delete(_deploytemplates.DeployTemplate,
+                            deploy_template, ignore_missing=ignore_missing)
+
+    def get_deploy_template(self, deploy_template, fields=None):
+        """Get a specific deployment template.
+
+        :param deploy_template: The value can be the name or ID
+            of a deployment template
+            :class:`~openstack.baremetal.v1.deploy_templates.DeployTemplate`
+            instance.
+
+        :param fields: Limit the resource fields to fetch.
+
+        :returns: One
+                  :class:`~openstack.baremetal.v1.deploy_templates.DeployTemplate`
+        :raises: :class:`~openstack.exceptions.ResourceNotFound`
+                        when no deployment template matching the name or
+                        ID could be found.
+        """
+        return self._get_with_fields(_deploytemplates.DeployTemplate,
+                                     deploy_template, fields=fields)
+
+    def patch_deploy_template(self, deploy_template, patch):
+        """Apply a JSON patch to the deploy_templates.
+
+        :param deploy_templates: The value can be the ID of a
+            deploy_template or a
+            :class:`~openstack.baremetal.v1.deploy_templates.DeployTemplate`
+            instance.
+
+        :param patch: JSON patch to apply.
+
+        :returns: The updated deploy_template.
+        :rtype::class:
+                        `~openstack.baremetal.v1.deploy_templates.DeployTemplate`
+        """
+        return self._get_resource(_deploytemplates.DeployTemplate,
+                                  deploy_template).patch(self, patch)
diff --git a/openstack/baremetal/v1/deploy_templates.py b/openstack/baremetal/v1/deploy_templates.py
new file mode 100644
index 000000000..3c9e40420
--- /dev/null
+++ b/openstack/baremetal/v1/deploy_templates.py
@@ -0,0 +1,51 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file 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.
+
+from openstack.baremetal.v1 import _common
+from openstack import resource
+
+
+class DeployTemplate(_common.ListMixin, resource.Resource):
+
+    resources_key = 'deploy_templates'
+    base_path = '/deploy_templates'
+
+    # capabilities
+    allow_create = True
+    allow_fetch = True
+    allow_commit = True
+    allow_delete = True
+    allow_list = True
+    allow_patch = True
+    commit_method = 'PATCH'
+    commit_jsonpatch = True
+
+    _query_mapping = resource.QueryParameters(
+        'detail',
+        fields={'type': _common.fields_type},
+    )
+
+    # Deploy Templates is available since 1.55
+    _max_microversion = '1.55'
+    name = resource.Body('name')
+    #: Timestamp at which the deploy_template was created.
+    created_at = resource.Body('created_at')
+    #: A set of one or more arbitrary metadata key and value pairs.
+    extra = resource.Body('extra')
+    #: A list of relative links. Includes the self and bookmark links.
+    links = resource.Body('links', type=list)
+    #: A set of physical information of the deploy_template.
+    steps = resource.Body('steps', type=list)
+    #: Timestamp at which the deploy_template was last updated.
+    updated_at = resource.Body('updated_at')
+    #: The UUID of the resource.
+    id = resource.Body('uuid', alternate_id=True)
diff --git a/openstack/tests/functional/baremetal/base.py b/openstack/tests/functional/baremetal/base.py
index 8dc00044d..948bc9dcb 100644
--- a/openstack/tests/functional/baremetal/base.py
+++ b/openstack/tests/functional/baremetal/base.py
@@ -84,3 +84,16 @@ class BaseBaremetalTest(base.BaseFunctionalTest):
             self.conn.baremetal.delete_volume_target(volume_target.id,
                                                      ignore_missing=True))
         return volume_target
+
+    def create_deploy_template(self, **kwargs):
+        """Create a new deploy_template from attributes.
+        """
+
+        deploy_template = self.conn.baremetal.create_deploy_template(
+            **kwargs)
+
+        self.addCleanup(
+            lambda: self.conn.baremetal.delete_deploy_template(
+                deploy_template.id,
+                ignore_missing=True))
+        return deploy_template
diff --git a/openstack/tests/functional/baremetal/test_baremetal_deploy_templates.py b/openstack/tests/functional/baremetal/test_baremetal_deploy_templates.py
new file mode 100644
index 000000000..eb59056dc
--- /dev/null
+++ b/openstack/tests/functional/baremetal/test_baremetal_deploy_templates.py
@@ -0,0 +1,190 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file 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.
+
+from openstack import exceptions
+from openstack.tests.functional.baremetal import base
+
+
+class TestBareMetalDeployTemplate(base.BaseBaremetalTest):
+
+    min_microversion = '1.55'
+
+    def setUp(self):
+        super(TestBareMetalDeployTemplate, self).setUp()
+
+    def test_baremetal_deploy_create_get_delete(self):
+        steps = [
+            {
+                "interface": "bios",
+                "step": "apply_configuration",
+                "args": {
+                    "settings": [
+                        {
+                            "name": "LogicalProc",
+                            "value": "Enabled"
+                        }
+                    ]
+                },
+                "priority": 150
+            }
+        ]
+        deploy_template = self.create_deploy_template(
+            name='CUSTOM_DEPLOY_TEMPLATE',
+            steps=steps)
+        loaded = self.conn.baremetal.get_deploy_template(
+            deploy_template.id)
+        self.assertEqual(loaded.id, deploy_template.id)
+        self.conn.baremetal.delete_deploy_template(deploy_template,
+                                                   ignore_missing=False)
+        self.assertRaises(exceptions.ResourceNotFound,
+                          self.conn.baremetal.get_deploy_template,
+                          deploy_template.id)
+
+    def test_baremetal_deploy_template_list(self):
+        steps = [
+            {
+                "interface": "bios",
+                "step": "apply_configuration",
+                "args": {
+                    "settings": [
+                        {
+                            "name": "LogicalProc",
+                            "value": "Enabled"
+                        }
+                    ]
+                },
+                "priority": 150
+            }
+        ]
+
+        deploy_template1 = self.create_deploy_template(
+            name='CUSTOM_DEPLOY_TEMPLATE1',
+            steps=steps)
+        deploy_template2 = self.create_deploy_template(
+            name='CUSTOM_DEPLOY_TEMPLATE2',
+            steps=steps)
+        deploy_templates = self.conn.baremetal.deploy_templates()
+        ids = [template.id for template in deploy_templates]
+        self.assertIn(deploy_template1.id, ids)
+        self.assertIn(deploy_template2.id, ids)
+
+        deploy_templates_with_details = self.conn.baremetal.deploy_templates(
+            details=True)
+        for dp in deploy_templates_with_details:
+            self.assertIsNotNone(dp.id)
+            self.assertIsNotNone(dp.name)
+
+        deploy_tempalte_with_fields = self.conn.baremetal.deploy_templates(
+            fields=['uuid'])
+        for dp in deploy_tempalte_with_fields:
+            self.assertIsNotNone(dp.id)
+            self.assertIsNone(dp.name)
+
+    def test_baremetal_deploy_list_update_delete(self):
+        steps = [
+            {
+                "interface": "bios",
+                "step": "apply_configuration",
+                "args": {
+                    "settings": [
+                        {
+                            "name": "LogicalProc",
+                            "value": "Enabled"
+                        }
+                    ]
+                },
+                "priority": 150
+            }
+        ]
+        deploy_template = self.create_deploy_template(
+            name='CUSTOM_DEPLOY_TEMPLATE4',
+            steps=steps)
+        self.assertFalse(deploy_template.extra)
+        deploy_template.extra = {'answer': 42}
+
+        deploy_template = self.conn.baremetal.update_deploy_template(
+            deploy_template)
+        self.assertEqual({'answer': 42}, deploy_template.extra)
+
+        deploy_template = self.conn.baremetal.get_deploy_template(
+            deploy_template.id)
+
+        self.conn.baremetal.delete_deploy_template(deploy_template.id,
+                                                   ignore_missing=False)
+
+    def test_baremetal_deploy_update(self):
+        steps = [
+            {
+                "interface": "bios",
+                "step": "apply_configuration",
+                "args": {
+                    "settings": [
+                        {
+                            "name": "LogicalProc",
+                            "value": "Enabled"
+                        }
+                    ]
+                },
+                "priority": 150
+            }
+        ]
+        deploy_template = self.create_deploy_template(
+            name='CUSTOM_DEPLOY_TEMPLATE4',
+            steps=steps)
+        deploy_template.extra = {'answer': 42}
+
+        deploy_template = self.conn.baremetal.update_deploy_template(
+            deploy_template)
+        self.assertEqual({'answer': 42}, deploy_template.extra)
+
+        deploy_template = self.conn.baremetal.get_deploy_template(
+            deploy_template.id)
+        self.assertEqual({'answer': 42}, deploy_template.extra)
+
+    def test_deploy_template_patch(self):
+        name = "CUSTOM_HYPERTHREADING_ON"
+        steps = [
+            {
+                "interface": "bios",
+                "step": "apply_configuration",
+                "args": {
+                    "settings": [
+                        {
+                            "name": "LogicalProc",
+                            "value": "Enabled"
+                        }
+                    ]
+                },
+                "priority": 150
+            }
+        ]
+        deploy_template = self.create_deploy_template(
+            name=name,
+            steps=steps)
+        deploy_template = self.conn.baremetal.patch_deploy_template(
+            deploy_template, dict(path='/extra/answer', op='add', value=42))
+        self.assertEqual({'answer': 42}, deploy_template.extra)
+        self.assertEqual(name,
+                         deploy_template.name)
+
+        deploy_template = self.conn.baremetal.get_deploy_template(
+            deploy_template.id)
+        self.assertEqual({'answer': 42}, deploy_template.extra)
+
+    def test_deploy_template_negative_non_existing(self):
+        uuid = "bbb45f41-d4bc-4307-8d1d-32f95ce1e920"
+        self.assertRaises(exceptions.ResourceNotFound,
+                          self.conn.baremetal.get_deploy_template, uuid)
+        self.assertRaises(exceptions.ResourceNotFound,
+                          self.conn.baremetal.delete_deploy_template, uuid,
+                          ignore_missing=False)
+        self.assertIsNone(self.conn.baremetal.delete_deploy_template(uuid))
diff --git a/openstack/tests/unit/baremetal/v1/test_deploy_templates.py b/openstack/tests/unit/baremetal/v1/test_deploy_templates.py
new file mode 100644
index 000000000..0b826f425
--- /dev/null
+++ b/openstack/tests/unit/baremetal/v1/test_deploy_templates.py
@@ -0,0 +1,75 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file 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.
+
+from openstack.tests.unit import base
+
+from openstack.baremetal.v1 import deploy_templates
+
+FAKE = {
+    "created_at": "2016-08-18T22:28:48.643434+11:11",
+    "extra": {},
+    "links": [
+       {
+           "href": """http://10.60.253.180:6385/v1/deploy_templates
+                    /bbb45f41-d4bc-4307-8d1d-32f95ce1e920""",
+           "rel": "self"
+       },
+        {
+           "href": """http://10.60.253.180:6385/deploy_templates
+                   /bbb45f41-d4bc-4307-8d1d-32f95ce1e920""",
+           "rel": "bookmark"
+       }
+    ],
+    "name": "CUSTOM_HYPERTHREADING_ON",
+    "steps": [
+            {
+                "args": {
+                    "settings": [
+                        {
+                            "name": "LogicalProc",
+                            "value": "Enabled"
+                        }
+                    ]
+                },
+                "interface": "bios",
+                "priority": 150,
+                "step": "apply_configuration"
+            }
+    ],
+    "updated_at": None,
+    "uuid": "bbb45f41-d4bc-4307-8d1d-32f95ce1e920"
+}
+
+
+class DeployTemplates(base.TestCase):
+
+    def test_basic(self):
+        sot = deploy_templates.DeployTemplate()
+        self.assertIsNone(sot.resource_key)
+        self.assertEqual('deploy_templates', sot.resources_key)
+        self.assertEqual('/deploy_templates', sot.base_path)
+        self.assertTrue(sot.allow_create)
+        self.assertTrue(sot.allow_fetch)
+        self.assertTrue(sot.allow_commit)
+        self.assertTrue(sot.allow_delete)
+        self.assertTrue(sot.allow_list)
+        self.assertEqual('PATCH', sot.commit_method)
+
+    def test_instantiate(self):
+        sot = deploy_templates.DeployTemplate(**FAKE)
+        self.assertEqual(FAKE['steps'], sot.steps)
+        self.assertEqual(FAKE['created_at'], sot.created_at)
+        self.assertEqual(FAKE['extra'], sot.extra)
+        self.assertEqual(FAKE['links'], sot.links)
+        self.assertEqual(FAKE['name'], sot.name)
+        self.assertEqual(FAKE['updated_at'], sot.updated_at)
+        self.assertEqual(FAKE['uuid'], sot.id)
diff --git a/releasenotes/notes/ironic-deploy-template-support-fa56005365ed6e4d.yaml b/releasenotes/notes/ironic-deploy-template-support-fa56005365ed6e4d.yaml
new file mode 100644
index 000000000..d8b13ea7a
--- /dev/null
+++ b/releasenotes/notes/ironic-deploy-template-support-fa56005365ed6e4d.yaml
@@ -0,0 +1,4 @@
+---
+features:
+  - |
+    Support Deploy Templates for Ironic API
\ No newline at end of file