Merge "Adds placement trait api calls"
diff --git a/releasenotes/notes/add-placement-traits-api-calls-087061f5455f0b12.yaml b/releasenotes/notes/add-placement-traits-api-calls-087061f5455f0b12.yaml
new file mode 100644
index 0000000..77d0b38
--- /dev/null
+++ b/releasenotes/notes/add-placement-traits-api-calls-087061f5455f0b12.yaml
@@ -0,0 +1,4 @@
+---
+features:
+  - |
+    Adds API calls for traits in PlacementClient.
diff --git a/tempest/lib/services/placement/placement_client.py b/tempest/lib/services/placement/placement_client.py
index 216ac08..f272cbf 100644
--- a/tempest/lib/services/placement/placement_client.py
+++ b/tempest/lib/services/placement/placement_client.py
@@ -49,3 +49,39 @@
         self.expected_success(200, resp.status)
         body = json.loads(body)
         return rest_client.ResponseBody(resp, body)
+
+    def list_traits(self, **params):
+        """API ref https://docs.openstack.org/api-ref/placement/#traits
+        """
+        url = "/traits"
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_trait(self, name, **params):
+        url = "/traits"
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+            resp, body = self.get(url)
+            body = json.loads(body)
+            self.expected_success(200, resp.status)
+            return rest_client.ResponseBody(resp, body)
+        url = f"{url}/{name}"
+        resp, _ = self.get(url)
+        self.expected_success(204, resp.status)
+        return resp.status
+
+    def create_trait(self, name, **params):
+        url = f"/traits/{name}"
+        json_body = json.dumps(params)
+        resp, _ = self.put(url, body=json_body)
+        return resp.status
+
+    def delete_trait(self, name):
+        url = f"/traits/{name}"
+        resp, _ = self.delete(url)
+        self.expected_success(204, resp.status)
+        return resp.status
diff --git a/tempest/tests/lib/services/placement/test_placement_client.py b/tempest/tests/lib/services/placement/test_placement_client.py
index 1396a85..bb57bb0 100644
--- a/tempest/tests/lib/services/placement/test_placement_client.py
+++ b/tempest/tests/lib/services/placement/test_placement_client.py
@@ -87,3 +87,77 @@
 
     def test_list_allocations_with_bytes_body(self):
         self._test_list_allocations(bytes_body=True)
+
+    FAKE_ALL_TRAITS = {
+        "traits": [
+            "CUSTOM_HW_FPGA_CLASS1",
+            "CUSTOM_HW_FPGA_CLASS2",
+            "CUSTOM_HW_FPGA_CLASS3"
+        ]
+    }
+
+    FAKE_ASSOCIATED_TRAITS = {
+        "traits": [
+            "CUSTOM_HW_FPGA_CLASS1",
+            "CUSTOM_HW_FPGA_CLASS2"
+        ]
+    }
+
+    def test_list_traits(self):
+        self.check_service_client_function(
+            self.client.list_traits,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_ALL_TRAITS)
+
+        self.check_service_client_function(
+            self.client.list_traits,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_ASSOCIATED_TRAITS,
+            **{
+                "associated": "true"
+            })
+
+        self.check_service_client_function(
+            self.client.list_traits,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_ALL_TRAITS,
+            **{
+                "associated": "true",
+                "name": "startswith:CUSTOM_HW_FPGPA"
+            })
+
+    def test_show_traits(self):
+        self.check_service_client_function(
+            self.client.show_trait,
+            'tempest.lib.common.rest_client.RestClient.get',
+            204, status=204,
+            name="CUSTOM_HW_FPGA_CLASS1")
+
+        self.check_service_client_function(
+            self.client.show_trait,
+            'tempest.lib.common.rest_client.RestClient.get',
+            404, status=404,
+            # trait with this name does not exists
+            name="CUSTOM_HW_FPGA_CLASS4")
+
+    def test_create_traits(self):
+        self.check_service_client_function(
+            self.client.create_trait,
+            'tempest.lib.common.rest_client.RestClient.put',
+            204, status=204,
+            # try to create trait with existing name
+            name="CUSTOM_HW_FPGA_CLASS1")
+
+        self.check_service_client_function(
+            self.client.create_trait,
+            'tempest.lib.common.rest_client.RestClient.put',
+            201, status=201,
+            # create new trait
+            name="CUSTOM_HW_FPGA_CLASS4")
+
+    def test_delete_traits(self):
+        self.check_service_client_function(
+            self.client.delete_trait,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            204, status=204,
+            name="CUSTOM_HW_FPGA_CLASS1")