Add new attributes to trunk model

This patch adds the following attributes to the trunk model:

  * name: this is added for convenience, especially to speed
    up resource lookup via CLI commands. This is also added
    for consistency with other Neutron resources.
  * admin_state_up: this is added for management needs. There
    may be maintenance situations where preventing the user
    from adding/removing subports to a trunk while in disabled
    state is desired.
  * status: this can be used to track the aggregate status
    of all the resources (parent + subports) participating
    into a trunk.

The API extension is modified accordingly, coverage added.
The hash for the trunk object is revised, while the object
version is left as is, since all of this is still unreleased.

The patch leaves the logic to handle the trunk status aggregation
to be added at a later date, whilst minor refactoring
improves readability and use of the database sessions.

Partially-implements: blueprint vlan-aware-vms

Change-Id: Ibe39378d43b1d63c2b006c2da574f52d9934a0df
diff --git a/neutron/tests/tempest/api/test_trunk.py b/neutron/tests/tempest/api/test_trunk.py
index d1fa325..05e1430 100644
--- a/neutron/tests/tempest/api/test_trunk.py
+++ b/neutron/tests/tempest/api/test_trunk.py
@@ -55,10 +55,10 @@
         trunks_cleanup(cls.client, cls.trunks)
         super(TrunkTestJSONBase, cls).resource_cleanup()
 
-    def _create_trunk_with_network_and_parent(self, subports):
+    def _create_trunk_with_network_and_parent(self, subports, **kwargs):
         network = self.create_network()
         parent_port = self.create_port(network)
-        trunk = self.client.create_trunk(parent_port['id'], subports)
+        trunk = self.client.create_trunk(parent_port['id'], subports, **kwargs)
         self.trunks.append(trunk['trunk'])
         return trunk
 
@@ -88,6 +88,20 @@
         self.client.delete_trunk(trunk_id)
         self.assertRaises(lib_exc.NotFound, self.client.show_trunk, trunk_id)
 
+    @test.idempotent_id('4ce46c22-a2b6-4659-bc5a-0ef2463cab32')
+    def test_create_update_trunk(self):
+        trunk = self._create_trunk_with_network_and_parent(None)
+        trunk_id = trunk['trunk']['id']
+        res = self.client.show_trunk(trunk_id)
+        self.assertTrue(res['trunk']['admin_state_up'])
+        self.assertEqual("", res['trunk']['name'])
+        res = self.client.update_trunk(
+            trunk_id, name='foo', admin_state_up=False)
+        self.assertFalse(res['trunk']['admin_state_up'])
+        self.assertEqual("foo", res['trunk']['name'])
+        # enable the trunk so that it can be managed
+        self.client.update_trunk(trunk_id, admin_state_up=True)
+
     @test.idempotent_id('73365f73-bed6-42cd-960b-ec04e0c99d85')
     def test_list_trunks(self):
         trunk1 = self._create_trunk_with_network_and_parent(None)
@@ -162,7 +176,6 @@
 class TrunksSearchCriteriaTest(base.BaseSearchCriteriaTest):
 
     resource = 'trunk'
-    field = 'id'
 
     @classmethod
     def skip_checks(cls):
@@ -178,7 +191,7 @@
         net = cls.create_network(network_name='trunk-search-test-net')
         for name in cls.resource_names:
             parent_port = cls.create_port(net)
-            trunk = cls.client.create_trunk(parent_port['id'], [])
+            trunk = cls.client.create_trunk(parent_port['id'], [], name=name)
             cls.trunks.append(trunk['trunk'])
 
     @classmethod
diff --git a/neutron/tests/tempest/api/test_trunk_negative.py b/neutron/tests/tempest/api/test_trunk_negative.py
index 556c374..16a9af3 100644
--- a/neutron/tests/tempest/api/test_trunk_negative.py
+++ b/neutron/tests/tempest/api/test_trunk_negative.py
@@ -128,6 +128,45 @@
                             'segmentation_id': 2}])
 
     @test.attr(type='negative')
+    @test.idempotent_id('7f132ccc-1380-42d8-9c44-50411612bd01')
+    def test_add_subport_port_id_disabled_trunk(self):
+        trunk = self._create_trunk_with_network_and_parent(
+            None, admin_state_up=False)
+        self.assertRaises(lib_exc.Conflict,
+            self.client.add_subports,
+            trunk['trunk']['id'],
+            [{'port_id': trunk['trunk']['port_id'],
+              'segmentation_type': 'vlan',
+              'segmentation_id': 2}])
+        self.client.update_trunk(
+            trunk['trunk']['id'], admin_state_up=True)
+
+    @test.attr(type='negative')
+    @test.idempotent_id('8f132ccc-1380-42d8-9c44-50411612bd01')
+    def test_remove_subport_port_id_disabled_trunk(self):
+        trunk = self._create_trunk_with_network_and_parent(
+            None, admin_state_up=False)
+        self.assertRaises(lib_exc.Conflict,
+            self.client.remove_subports,
+            trunk['trunk']['id'],
+            [{'port_id': trunk['trunk']['port_id'],
+              'segmentation_type': 'vlan',
+              'segmentation_id': 2}])
+        self.client.update_trunk(
+            trunk['trunk']['id'], admin_state_up=True)
+
+    @test.attr(type='negative')
+    @test.idempotent_id('9f132ccc-1380-42d8-9c44-50411612bd01')
+    def test_delete_trunk_disabled_trunk(self):
+        trunk = self._create_trunk_with_network_and_parent(
+            None, admin_state_up=False)
+        self.assertRaises(lib_exc.Conflict,
+            self.client.delete_trunk,
+            trunk['trunk']['id'])
+        self.client.update_trunk(
+            trunk['trunk']['id'], admin_state_up=True)
+
+    @test.attr(type='negative')
     @test.idempotent_id('00cb40bb-1593-44c8-808c-72b47e64252f')
     def test_add_subport_duplicate_segmentation_details(self):
         trunk = self._create_trunk_with_network_and_parent(None)
diff --git a/neutron/tests/tempest/services/network/json/network_client.py b/neutron/tests/tempest/services/network/json/network_client.py
index 3220604..36a7f5b 100644
--- a/neutron/tests/tempest/services/network/json/network_client.py
+++ b/neutron/tests/tempest/services/network/json/network_client.py
@@ -659,7 +659,8 @@
         body = jsonutils.loads(body)
         return service_client.ResponseBody(resp, body)
 
-    def create_trunk(self, parent_port_id, subports, tenant_id=None):
+    def create_trunk(self, parent_port_id, subports,
+                     tenant_id=None, name=None, admin_state_up=None):
         uri = '%s/trunks' % self.uri_prefix
         post_data = {
             'trunk': {
@@ -670,11 +671,24 @@
             post_data['trunk']['sub_ports'] = subports
         if tenant_id is not None:
             post_data['trunk']['tenant_id'] = tenant_id
+        if name is not None:
+            post_data['trunk']['name'] = name
+        if admin_state_up is not None:
+            post_data['trunk']['admin_state_up'] = admin_state_up
         resp, body = self.post(uri, self.serialize(post_data))
         body = self.deserialize_single(body)
         self.expected_success(201, resp.status)
         return service_client.ResponseBody(resp, body)
 
+    def update_trunk(self, trunk_id, **kwargs):
+        put_body = {'trunk': kwargs}
+        body = jsonutils.dumps(put_body)
+        uri = '%s/trunks/%s' % (self.uri_prefix, trunk_id)
+        resp, body = self.put(uri, body)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
     def show_trunk(self, trunk_id):
         uri = '%s/trunks/%s' % (self.uri_prefix, trunk_id)
         resp, body = self.get(uri)