Merge "Expand baremetal port coverage"
diff --git a/tempest/api/baremetal/base.py b/tempest/api/baremetal/base.py
index 2e745f8..021adaf 100644
--- a/tempest/api/baremetal/base.py
+++ b/tempest/api/baremetal/base.py
@@ -106,17 +106,20 @@
 
     @classmethod
     @creates('port')
-    def create_port(cls, node_id, address=None):
+    def create_port(cls, node_id, address, extra=None, uuid=None):
         """
         Wrapper utility for creating test ports.
 
-        :param address: MAC address of the port. If not supplied, a random
-            value will be generated.
+        :param address: MAC address of the port.
+        :param extra: Meta data of the port. If not supplied, an empty
+            dictionary will be created.
+        :param uuid: UUID of the port.
         :return: Created port.
 
         """
-        address = address or data_utils.rand_mac_address()
-        resp, body = cls.client.create_port(address=address, node_id=node_id)
+        extra = extra or {}
+        resp, body = cls.client.create_port(address=address, node_id=node_id,
+                                            extra=extra, uuid=uuid)
 
         return {'port': body, 'response': resp}
 
@@ -170,3 +173,12 @@
             cls.created_objects['port'].remove(port_id)
 
         return resp
+
+    def validate_self_link(self, resource, uuid, link):
+        """Check whether the given self link formatted correctly."""
+        expected_link = "{base}/{pref}/{res}/{uuid}".format(
+                        base=self.client.base_url,
+                        pref=self.client.uri_prefix,
+                        res=resource,
+                        uuid=uuid)
+        self.assertEqual(expected_link, link)
diff --git a/tempest/api/baremetal/test_ports.py b/tempest/api/baremetal/test_ports.py
index fb2acc7..8b76811 100644
--- a/tempest/api/baremetal/test_ports.py
+++ b/tempest/api/baremetal/test_ports.py
@@ -30,54 +30,268 @@
         node_id = self.node['uuid']
         address = data_utils.rand_mac_address()
 
-        port = self.create_port(node_id=node_id, address=address)['port']
+        result = self.create_port(node_id=node_id, address=address)
 
-        self.assertEqual(port['address'], address)
-        self.assertEqual(port['node_uuid'], node_id)
+        port = result['port']
+
+        resp, body = self.client.show_port(port['uuid'])
+
+        self.assertEqual(200, resp.status)
+        self.assertEqual(port['uuid'], body['uuid'])
+        self.assertEqual(address, body['address'])
+        self.assertEqual({}, body['extra'])
+        self.assertEqual(node_id, body['node_uuid'])
+
+    @test.attr(type='smoke')
+    def test_create_port_specifying_uuid(self):
+        node_id = self.node['uuid']
+        address = data_utils.rand_mac_address()
+        uuid = data_utils.rand_uuid()
+
+        self.create_port(node_id=node_id, address=address, uuid=uuid)
+
+        resp, body = self.client.show_port(uuid)
+
+        self.assertEqual(200, resp.status)
+        self.assertEqual(uuid, body['uuid'])
+        self.assertEqual(address, body['address'])
+        self.assertEqual({}, body['extra'])
+        self.assertEqual(node_id, body['node_uuid'])
+
+    @test.attr(type='smoke')
+    def test_create_port_with_extra(self):
+        node_id = self.node['uuid']
+        address = data_utils.rand_mac_address()
+        extra = {'key': 'value'}
+
+        result = self.create_port(node_id=node_id, address=address,
+                                  extra=extra)
+        port = result['port']
+
+        resp, body = self.client.show_port(port['uuid'])
+
+        self.assertEqual(200, resp.status)
+        self.assertEqual(port['uuid'], body['uuid'])
+        self.assertEqual(address, body['address'])
+        self.assertEqual(extra, body['extra'])
+        self.assertEqual(node_id, body['node_uuid'])
 
     @test.attr(type='smoke')
     def test_delete_port(self):
         node_id = self.node['uuid']
-        port_id = self.create_port(node_id=node_id)['port']['uuid']
+        address = data_utils.rand_mac_address()
+        port_id = self.create_port(node_id=node_id, address=address)['port'][
+            'uuid']
 
         resp = self.delete_port(port_id)
 
-        self.assertEqual(resp['status'], '204')
+        self.assertEqual(204, resp.status)
         self.assertRaises(exc.NotFound, self.client.show_port, port_id)
 
     @test.attr(type='smoke')
     def test_show_port(self):
         node_id = self.node['uuid']
         address = data_utils.rand_mac_address()
+        extra = {'key': 'value'}
 
-        port_id = self.create_port(node_id=node_id,
-                                   address=address)['port']['uuid']
+        port_id = self.create_port(node_id=node_id, address=address,
+                                   extra=extra)['port']['uuid']
 
         resp, port = self.client.show_port(port_id)
 
-        self.assertEqual(port['uuid'], port_id)
-        self.assertEqual(port['address'], address)
+        self.assertEqual(200, resp.status)
+        self.assertEqual(port_id, port['uuid'])
+        self.assertEqual(address, port['address'])
+        self.assertEqual(extra, port['extra'])
+
+    @test.attr(type='smoke')
+    def test_show_port_with_links(self):
+        node_id = self.node['uuid']
+        address = data_utils.rand_mac_address()
+
+        port_id = self.create_port(node_id=node_id, address=address)['port'][
+            'uuid']
+
+        resp, body = self.client.show_port(port_id)
+
+        self.assertEqual(200, resp.status)
+        self.assertIn('links', body.keys())
+        self.assertEqual(2, len(body['links']))
+        self.assertIn(port_id, body['links'][0]['href'])
 
     @test.attr(type='smoke')
     def test_list_ports(self):
         node_id = self.node['uuid']
 
-        uuids = [self.create_port(node_id=node_id)['port']['uuid']
-                 for i in range(0, 5)]
+        uuids = [self.create_port(node_id=node_id,
+                                  address=data_utils.rand_mac_address())
+                 ['port']['uuid'] for i in xrange(5)]
 
         resp, body = self.client.list_ports()
+        self.assertEqual(200, resp.status)
         loaded_uuids = [p['uuid'] for p in body['ports']]
 
-        for u in uuids:
-            self.assertIn(u, loaded_uuids)
+        for uuid in uuids:
+            self.assertIn(uuid, loaded_uuids)
+
+        # Verify self links.
+        for port in body['ports']:
+            self.validate_self_link('ports', port['uuid'],
+                                    port['links'][0]['href'])
 
     @test.attr(type='smoke')
-    def test_update_port(self):
+    def test_list_with_limit(self):
         node_id = self.node['uuid']
-        port_id = self.create_port(node_id=node_id)['port']['uuid']
+
+        for i in xrange(5):
+            self.create_port(node_id=node_id,
+                             address=data_utils.rand_mac_address())
+
+        resp, body = self.client.list_ports(limit=3)
+        self.assertEqual(200, resp.status)
+        self.assertEqual(3, len(body['ports']))
+
+        next_marker = body['ports'][-1]['uuid']
+        self.assertIn(next_marker, body['next'])
+
+    def test_list_ports_details(self):
+        node_id = self.node['uuid']
+
+        uuids = [
+            self.create_port(node_id=node_id,
+                             address=data_utils.rand_mac_address())
+            ['port']['uuid'] for i in range(0, 5)]
+
+        resp, body = self.client.list_ports_detail()
+        self.assertEqual(200, resp.status)
+
+        ports_dict = {port['uuid']: port for port in body['ports']
+                      if port['uuid'] in uuids}
+
+        for uuid in uuids:
+            self.assertIn(uuid, ports_dict)
+            port = ports_dict[uuid]
+            self.assertIn('extra', port)
+            self.assertIn('node_uuid', port)
+            # never expose the node_id
+            self.assertNotIn('node_id', port)
+            # Verify self link.
+            self.validate_self_link('ports', port['uuid'],
+                                    port['links'][0]['href'])
+
+    @test.attr(type='smoke')
+    def test_update_port_replace(self):
+        node_id = self.node['uuid']
+        address = data_utils.rand_mac_address()
+        extra = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}
+
+        port_id = self.create_port(node_id=node_id, address=address,
+                                   extra=extra)['port']['uuid']
 
         new_address = data_utils.rand_mac_address()
-        self.client.update_port(port_id, address=new_address)
+        new_extra = {'key1': 'new-value1', 'key2': 'new-value2',
+                     'key3': 'new-value3'}
+
+        patch = [{'path': '/address',
+                  'op': 'replace',
+                  'value': new_address},
+                 {'path': '/extra/key1',
+                  'op': 'replace',
+                  'value': new_extra['key1']},
+                 {'path': '/extra/key2',
+                  'op': 'replace',
+                  'value': new_extra['key2']},
+                 {'path': '/extra/key3',
+                  'op': 'replace',
+                  'value': new_extra['key3']}]
+
+        self.client.update_port(port_id, patch)
 
         resp, body = self.client.show_port(port_id)
-        self.assertEqual(body['address'], new_address)
+        self.assertEqual(200, resp.status)
+        self.assertEqual(new_address, body['address'])
+        self.assertEqual(new_extra, body['extra'])
+
+    @test.attr(type='smoke')
+    def test_update_port_remove(self):
+        node_id = self.node['uuid']
+        address = data_utils.rand_mac_address()
+        extra = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}
+
+        port_id = self.create_port(node_id=node_id, address=address,
+                                   extra=extra)['port']['uuid']
+
+        # Removing one item from the collection
+        resp, _ = self.client.update_port(port_id, [{'path': '/extra/key2',
+                                                     'op': 'remove'}])
+        self.assertEqual(200, resp.status)
+        extra.pop('key2')
+        resp, body = self.client.show_port(port_id)
+        self.assertEqual(200, resp.status)
+        self.assertEqual(extra, body['extra'])
+
+        # Removing the collection
+        resp, _ = self.client.update_port(port_id, [{'path': '/extra',
+                                                     'op': 'remove'}])
+        self.assertEqual(200, resp.status)
+        resp, body = self.client.show_port(port_id)
+        self.assertEqual(200, resp.status)
+        self.assertEqual({}, body['extra'])
+
+        # Assert nothing else was changed
+        self.assertEqual(node_id, body['node_uuid'])
+        self.assertEqual(address, body['address'])
+
+    @test.attr(type='smoke')
+    def test_update_port_add(self):
+        node_id = self.node['uuid']
+        address = data_utils.rand_mac_address()
+
+        port_id = self.create_port(node_id=node_id, address=address)['port'][
+            'uuid']
+
+        extra = {'key1': 'value1', 'key2': 'value2'}
+
+        patch = [{'path': '/extra/key1',
+                  'op': 'add',
+                  'value': extra['key1']},
+                 {'path': '/extra/key2',
+                  'op': 'add',
+                  'value': extra['key2']}]
+
+        self.client.update_port(port_id, patch)
+
+        resp, body = self.client.show_port(port_id)
+        self.assertEqual(200, resp.status)
+        self.assertEqual(extra, body['extra'])
+
+    @test.attr(type='smoke')
+    def test_update_port_mixed_ops(self):
+        node_id = self.node['uuid']
+        address = data_utils.rand_mac_address()
+        extra = {'key1': 'value1', 'key2': 'value2'}
+
+        port_id = self.create_port(node_id=node_id, address=address,
+                                   extra=extra)['port']['uuid']
+
+        new_address = data_utils.rand_mac_address()
+        new_extra = {'key1': 'new-value1', 'key3': 'new-value3'}
+
+        patch = [{'path': '/address',
+                  'op': 'replace',
+                  'value': new_address},
+                 {'path': '/extra/key1',
+                  'op': 'replace',
+                  'value': new_extra['key1']},
+                 {'path': '/extra/key2',
+                  'op': 'remove'},
+                 {'path': '/extra/key3',
+                  'op': 'add',
+                  'value': new_extra['key3']}]
+
+        self.client.update_port(port_id, patch)
+
+        resp, body = self.client.show_port(port_id)
+        self.assertEqual(200, resp.status)
+        self.assertEqual(new_address, body['address'])
+        self.assertEqual(new_extra, body['extra'])
diff --git a/tempest/api/baremetal/test_ports_negative.py b/tempest/api/baremetal/test_ports_negative.py
index 6cb8812..4cbe00e 100644
--- a/tempest/api/baremetal/test_ports_negative.py
+++ b/tempest/api/baremetal/test_ports_negative.py
@@ -25,16 +25,346 @@
         chassis = self.create_chassis()['chassis']
         self.node = self.create_node(chassis['uuid'])['node']
 
-    @test.attr(type='negative')
-    def test_create_port_invalid_mac(self):
+    @test.attr(type=['negative', 'smoke'])
+    def test_create_port_malformed_mac(self):
         node_id = self.node['uuid']
-        address = 'not an uuid'
+        address = 'malformed:mac'
 
         self.assertRaises(exc.BadRequest,
                           self.create_port, node_id=node_id, address=address)
 
-    @test.attr(type='negative')
-    def test_create_port_wrong_node_id(self):
-        node_id = str(data_utils.rand_uuid())
+    @test.attr(type=['negative', 'smoke'])
+    def test_create_port_malformed_extra(self):
+        node_id = self.node['uuid']
+        address = data_utils.rand_mac_address()
+        extra = {'key': 0.123}
+        self.assertRaises(exc.BadRequest,
+                          self.create_port, node_id=node_id,
+                          address=address, extra=extra)
 
-        self.assertRaises(exc.BadRequest, self.create_port, node_id=node_id)
+    @test.attr(type=['negative', 'smoke'])
+    def test_create_port_nonexsistent_node_id(self):
+        node_id = str(data_utils.rand_uuid())
+        address = data_utils.rand_mac_address()
+        self.assertRaises(exc.BadRequest, self.create_port, node_id=node_id,
+                          address=address)
+
+    @test.attr(type=['negative', 'smoke'])
+    def test_show_port_malformed_uuid(self):
+        self.assertRaises(exc.BadRequest, self.client.show_port,
+                          'malformed:uuid')
+
+    @test.attr(type=['negative', 'smoke'])
+    def test_show_port_nonexistent_uuid(self):
+        self.assertRaises(exc.NotFound, self.client.show_port,
+                          data_utils.rand_uuid())
+
+    @test.attr(type=['negative', 'smoke'])
+    def test_show_port_by_mac_not_allowed(self):
+        self.assertRaises(exc.BadRequest, self.client.show_port,
+                          data_utils.rand_mac_address())
+
+    @test.attr(type=['negative', 'smoke'])
+    def test_create_port_duplicated_port_uuid(self):
+        node_id = self.node['uuid']
+        address = data_utils.rand_mac_address()
+        uuid = data_utils.rand_uuid()
+
+        self.create_port(node_id=node_id, address=address, uuid=uuid)
+        self.assertRaises(exc.Conflict, self.create_port, node_id=node_id,
+                          address=address, uuid=uuid)
+
+    @test.attr(type=['negative', 'smoke'])
+    def test_create_port_no_mandatory_field_node_id(self):
+        address = data_utils.rand_mac_address()
+
+        self.assertRaises(exc.BadRequest, self.create_port, node_id=None,
+                          address=address)
+
+    @test.attr(type=['negative', 'smoke'])
+    def test_create_port_no_mandatory_field_mac(self):
+        node_id = self.node['uuid']
+
+        self.assertRaises(exc.BadRequest, self.create_port, node_id=node_id,
+                          address=None)
+
+    @test.attr(type=['negative', 'smoke'])
+    def test_create_port_malformed_port_uuid(self):
+        node_id = self.node['uuid']
+        address = data_utils.rand_mac_address()
+        uuid = 'malformed:uuid'
+
+        self.assertRaises(exc.BadRequest, self.create_port, node_id=node_id,
+                          address=address, uuid=uuid)
+
+    @test.attr(type=['negative', 'smoke'])
+    def test_create_port_malformed_node_id(self):
+        address = data_utils.rand_mac_address()
+        self.assertRaises(exc.BadRequest, self.create_port,
+                          node_id='malformed:nodeid', address=address)
+
+    @test.attr(type=['negative', 'smoke'])
+    def test_create_port_duplicated_mac(self):
+        node_id = self.node['uuid']
+        address = data_utils.rand_mac_address()
+        self.create_port(node_id=node_id, address=address)
+        self.assertRaises(exc.Conflict,
+                          self.create_port, node_id=node_id,
+                          address=address)
+
+    @test.attr(type=['negative', 'smoke'])
+    def test_update_port_by_mac_not_allowed(self):
+        node_id = self.node['uuid']
+        address = data_utils.rand_mac_address()
+        extra = {'key': 'value'}
+
+        self.create_port(node_id=node_id, address=address, extra=extra)
+
+        patch = [{'path': '/extra/key',
+                  'op': 'replace',
+                  'value': 'new-value'}]
+
+        self.assertRaises(exc.BadRequest,
+                          self.client.update_port, address,
+                          patch)
+
+    @test.attr(type=['negative', 'smoke'])
+    def test_update_port_nonexistent(self):
+        node_id = self.node['uuid']
+        address = data_utils.rand_mac_address()
+        extra = {'key': 'value'}
+
+        port_id = self.create_port(node_id=node_id, address=address,
+                                   extra=extra)['port']['uuid']
+        self.client.delete_port(port_id)
+
+        patch = [{'path': '/extra/key',
+                  'op': 'replace',
+                  'value': 'new-value'}]
+        self.assertRaises(exc.NotFound,
+                          self.client.update_port, port_id, patch)
+
+    @test.attr(type=['negative', 'smoke'])
+    def test_update_port_malformed_port_uuid(self):
+        node_id = self.node['uuid']
+        address = data_utils.rand_mac_address()
+
+        self.create_port(node_id=node_id, address=address)
+
+        new_address = data_utils.rand_mac_address()
+        self.assertRaises(exc.BadRequest, self.client.update_port,
+                          uuid='malformed:uuid',
+                          patch=[{'path': '/address', 'op': 'replace',
+                                  'value': new_address}])
+
+    @test.attr(type=['negative', 'smoke'])
+    def test_update_port_add_malformed_extra(self):
+        node_id = self.node['uuid']
+        address = data_utils.rand_mac_address()
+
+        port_id = self.create_port(node_id=node_id, address=address)['port'][
+            'uuid']
+
+        self.assertRaises(exc.BadRequest, self.client.update_port, port_id,
+                          [{'path': '/extra/key', ' op': 'add',
+                            'value': 0.123}])
+
+    @test.attr(type=['negative', 'smoke'])
+    def test_update_port_add_whole_malformed_extra(self):
+        node_id = self.node['uuid']
+        address = data_utils.rand_mac_address()
+
+        port_id = self.create_port(node_id=node_id, address=address)['port'][
+            'uuid']
+
+        self.assertRaises(exc.BadRequest, self.client.update_port, port_id,
+                          [{'path': '/extra',
+                            'op': 'add',
+                            'value': [1, 2, 3, 4, 'a']}])
+
+    @test.attr(type=['negative', 'smoke'])
+    def test_update_port_add_nonexistent_property(self):
+        node_id = self.node['uuid']
+        address = data_utils.rand_mac_address()
+
+        port_id = self.create_port(node_id=node_id, address=address)['port'][
+            'uuid']
+        self.assertRaises(exc.BadRequest, self.client.update_port, port_id,
+                          [{'path': '/nonexistent', ' op': 'add',
+                            'value': 'value'}])
+
+    @test.attr(type=['negative', 'smoke'])
+    def test_update_port_replace_node_id_with_malformed(self):
+        node_id = self.node['uuid']
+        address = data_utils.rand_mac_address()
+
+        port_id = self.create_port(node_id=node_id,
+                                   address=address)['port']['uuid']
+
+        patch = [{'path': '/node_uuid',
+                  'op': 'replace',
+                  'value': 'malformed:node_uuid'}]
+        self.assertRaises(exc.BadRequest,
+                          self.client.update_port, port_id, patch)
+
+    @test.attr(type=['negative', 'smoke'])
+    def test_update_port_replace_mac_with_duplicated(self):
+        node_id = self.node['uuid']
+        address1 = data_utils.rand_mac_address()
+        address2 = data_utils.rand_mac_address()
+
+        self.create_port(node_id=node_id, address=address1)
+        port_id = self.create_port(node_id=node_id,
+                                   address=address2)['port']['uuid']
+        patch = [{'path': '/address',
+                  'op': 'replace',
+                  'value': address1}]
+        self.assertRaises(exc.Conflict,
+                          self.client.update_port, port_id, patch)
+
+    @test.attr(type=['negative', 'smoke'])
+    def test_update_port_replace_node_id_with_nonexistent(self):
+        node_id = self.node['uuid']
+        address = data_utils.rand_mac_address()
+
+        port_id = self.create_port(node_id=node_id,
+                                   address=address)['port']['uuid']
+
+        patch = [{'path': '/node_uuid',
+                  'op': 'replace',
+                  'value': data_utils.rand_uuid()}]
+        self.assertRaises(exc.BadRequest,
+                          self.client.update_port, port_id, patch)
+
+    @test.attr(type=['negative', 'smoke'])
+    def test_update_port_replace_mac_with_malformed(self):
+        node_id = self.node['uuid']
+        address = data_utils.rand_mac_address()
+
+        port_id = self.create_port(node_id=node_id,
+                                   address=address)['port']['uuid']
+        patch = [{'path': '/address',
+                  'op': 'replace',
+                  'value': 'malformed:mac'}]
+
+        self.assertRaises(exc.BadRequest,
+                          self.client.update_port, port_id, patch)
+
+    @test.attr(type=['negative', 'smoke'])
+    def test_update_port_replace_extra_item_with_malformed(self):
+        node_id = self.node['uuid']
+        address = data_utils.rand_mac_address()
+        extra = {'key': 'value'}
+
+        port_id = self.create_port(node_id=node_id,
+                                   address=address,
+                                   extra=extra)['port']['uuid']
+        patch = [{'path': '/extra/key',
+                  'op': 'replace',
+                  'value': 0.123}]
+
+        self.assertRaises(exc.BadRequest,
+                          self.client.update_port, port_id, patch)
+
+    @test.attr(type=['negative', 'smoke'])
+    def test_update_port_replace_whole_extra_with_malformed(self):
+        node_id = self.node['uuid']
+        address = data_utils.rand_mac_address()
+        extra = {'key': 'value'}
+
+        port_id = self.create_port(node_id=node_id,
+                                   address=address,
+                                   extra=extra)['port']['uuid']
+        patch = [{'path': '/extra',
+                  'op': 'replace',
+                  'value': [1, 2, 3, 4, 'a']}]
+
+        self.assertRaises(exc.BadRequest,
+                          self.client.update_port, port_id, patch)
+
+    @test.attr(type=['negative', 'smoke'])
+    def test_update_port_replace_nonexistent_property(self):
+        node_id = self.node['uuid']
+        address = data_utils.rand_mac_address()
+
+        port_id = self.create_port(node_id=node_id,
+                                   address=address)['port']['uuid']
+
+        patch = [{'path': '/nonexistent', ' op': 'replace', 'value': 'value'}]
+
+        self.assertRaises(exc.BadRequest,
+                          self.client.update_port, port_id, patch)
+
+    @test.attr(type=['negative', 'smoke'])
+    def test_update_port_remove_mandatory_field_mac(self):
+        node_id = self.node['uuid']
+        address = data_utils.rand_mac_address()
+
+        port_id = self.create_port(node_id=node_id, address=address)['port'][
+            'uuid']
+        self.assertRaises(exc.BadRequest, self.client.update_port, port_id,
+                          [{'path': '/address', 'op': 'remove'}])
+
+    @test.attr(type=['negative', 'smoke'])
+    def test_update_port_remove_mandatory_field_port_uuid(self):
+        node_id = self.node['uuid']
+        address = data_utils.rand_mac_address()
+
+        port_id = self.create_port(node_id=node_id, address=address)['port'][
+            'uuid']
+        self.assertRaises(exc.BadRequest, self.client.update_port, port_id,
+                          [{'path': '/uuid', 'op': 'remove'}])
+
+    @test.attr(type=['negative', 'smoke'])
+    def test_update_port_remove_nonexistent_property(self):
+        node_id = self.node['uuid']
+        address = data_utils.rand_mac_address()
+
+        port_id = self.create_port(node_id=node_id, address=address)['port'][
+            'uuid']
+        self.assertRaises(exc.BadRequest, self.client.update_port, port_id,
+                          [{'path': '/nonexistent', 'op': 'remove'}])
+
+    @test.attr(type=['negative', 'smoke'])
+    def test_delete_port_by_mac_not_allowed(self):
+        node_id = self.node['uuid']
+        address = data_utils.rand_mac_address()
+
+        self.create_port(node_id=node_id, address=address)
+        self.assertRaises(exc.BadRequest, self.client.delete_port, address)
+
+    @test.attr(type=['negative', 'smoke'])
+    def test_update_port_mixed_ops_integrity(self):
+        node_id = self.node['uuid']
+        address = data_utils.rand_mac_address()
+        extra = {'key1': 'value1', 'key2': 'value2'}
+
+        port_id = self.create_port(node_id=node_id, address=address,
+                                   extra=extra)['port']['uuid']
+
+        new_address = data_utils.rand_mac_address()
+        new_extra = {'key1': 'new-value1', 'key3': 'new-value3'}
+
+        patch = [{'path': '/address',
+                  'op': 'replace',
+                  'value': new_address},
+                 {'path': '/extra/key1',
+                  'op': 'replace',
+                  'value': new_extra['key1']},
+                 {'path': '/extra/key2',
+                  'op': 'remove'},
+                 {'path': '/extra/key3',
+                  'op': 'add',
+                  'value': new_extra['key3']},
+                 {'path': '/nonexistent',
+                  'op': 'replace',
+                  'value': 'value'}]
+
+        self.assertRaises(exc.BadRequest, self.client.update_port, port_id,
+                          patch)
+
+        # patch should not be applied
+        resp, body = self.client.show_port(port_id)
+        self.assertEqual(200, resp.status)
+        self.assertEqual(address, body['address'])
+        self.assertEqual(extra, body['extra'])
diff --git a/tempest/services/baremetal/base.py b/tempest/services/baremetal/base.py
index 5f6b513..2af287f 100644
--- a/tempest/services/baremetal/base.py
+++ b/tempest/services/baremetal/base.py
@@ -12,6 +12,7 @@
 
 import functools
 import json
+import urllib
 
 import six
 
@@ -103,16 +104,19 @@
 
         return patch
 
-    def _list_request(self, resource, permanent=False):
+    def _list_request(self, resource, permanent=False, **kwargs):
         """
         Get the list of objects of the specified type.
 
         :param resource: The name of the REST resource, e.g., 'nodes'.
+        "param **kw: Parameters for the request.
         :return: A tuple with the server response and deserialized JSON list
                  of objects
 
         """
         uri = self._get_uri(resource, permanent=permanent)
+        if kwargs:
+            uri += "?%s" % urllib.urlencode(kwargs)
 
         resp, body = self.get(uri)
 
diff --git a/tempest/services/baremetal/v1/base_v1.py b/tempest/services/baremetal/v1/base_v1.py
index 3f4c509..ec531a1 100644
--- a/tempest/services/baremetal/v1/base_v1.py
+++ b/tempest/services/baremetal/v1/base_v1.py
@@ -37,9 +37,14 @@
         return self._list_request('chassis')
 
     @base.handle_errors
-    def list_ports(self):
+    def list_ports(self, **kwargs):
         """List all existing ports."""
-        return self._list_request('ports')
+        return self._list_request('ports', **kwargs)
+
+    @base.handle_errors
+    def list_ports_detail(self):
+        """Details list all existing ports."""
+        return self._list_request('/ports/detail')
 
     @base.handle_errors
     def show_node(self, uuid):
@@ -116,12 +121,20 @@
         Create a port with the specified parameters.
 
         :param node_id: The ID of the node which owns the port.
-        :param address: MAC address of the port. Default: 01:23:45:67:89:0A.
+        :param address: MAC address of the port.
+        :param extra: Meta data of the port. Default: {'foo': 'bar'}.
+        :param uuid: UUID of the port.
         :return: A tuple with the server response and the created port.
 
         """
-        port = {'address': kwargs.get('address', '01:23:45:67:89:0A'),
-                'node_uuid': node_id}
+        port = {'extra': kwargs.get('extra', {'foo': 'bar'}),
+                'uuid': kwargs['uuid']}
+
+        if node_id is not None:
+            port['node_uuid'] = node_id
+
+        if kwargs['address'] is not None:
+            port['address'] = kwargs['address']
 
         return self._create_request('ports', 'port', port)
 
@@ -192,15 +205,14 @@
         return self._patch_request('chassis', uuid, patch)
 
     @base.handle_errors
-    def update_port(self, uuid, **kwargs):
+    def update_port(self, uuid, patch):
         """
         Update the specified port.
 
         :param uuid: The unique identifier of the port.
+        :param patch: List of dicts representing json patches.
         :return: A tuple with the server response and the updated port.
 
         """
-        port_attributes = ('address',)
-        patch = self._make_patch(port_attributes, **kwargs)
 
         return self._patch_request('ports', uuid, patch)