First cut of Network client and positive tests.

Fixes bug 946675.
1. Created Network (Quantum ) client for Tempest.
2. Added positive tests for Quantum.
3. Addressed review comments.

Change-Id: If3e27d3aadc70fbf6db722b6ae554a1188fe56e8
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index c7c403c..bfb7e59 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -126,3 +126,9 @@
 password = {$ADMIN_PASSWORD}
 # The above administrative user's tenant name
 tenant_name = {$ADMIN_TENANT_NAME}
+
+[network]
+# This section contains configuration options used when executing tests
+# against the OpenStack Network API.
+api_version = v1.1
+catalog_type = network
diff --git a/tempest/config.py b/tempest/config.py
index 0a76ce5..08e90fd 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -260,6 +260,24 @@
         return self.get("tenant_name", "demo")
 
 
+class NetworkConfig(BaseConfig):
+    """Provides configuration information for connecting to an OpenStack
+    Network Service.
+    """
+
+    SECTION_NAME = "network"
+
+    @property
+    def catalog_type(self):
+        """Catalog type of the Quantum service."""
+        return self.get("catalog_type", 'network')
+
+    @property
+    def api_version(self):
+        """Version of Quantum API"""
+        return self.get("api_version", "v1.1")
+
+
 # TODO(jaypipes): Move this to a common utils (not data_utils...)
 def singleton(cls):
     """Simple wrapper for classes that should only have a single instance"""
@@ -306,6 +324,7 @@
         self.compute_admin = ComputeAdminConfig(self._conf)
         self.identity = IdentityConfig(self._conf)
         self.images = ImagesConfig(self._conf)
+        self.network = NetworkConfig(self._conf)
 
     def load_config(self, path):
         """Read configuration from given path and return a config object."""
diff --git a/tempest/openstack.py b/tempest/openstack.py
index 6e58272..18af647 100644
--- a/tempest/openstack.py
+++ b/tempest/openstack.py
@@ -20,6 +20,7 @@
 import tempest.config
 from tempest import exceptions
 from tempest.services.image import service as image_service
+from tempest.services.network.json.network_client import NetworkClient
 from tempest.services.nova.json.images_client import ImagesClient
 from tempest.services.nova.json.flavors_client import FlavorsClient
 from tempest.services.nova.json.servers_client import ServersClient
@@ -83,6 +84,7 @@
         self.volumes_client = VolumesClient(*client_args)
         self.admin_client = AdminClient(*client_args)
         self.token_client = TokenClient(self.config)
+        self.network_client = NetworkClient(*client_args)
 
 
 class AltManager(Manager):
diff --git a/tempest/services/network/__init__.py b/tempest/services/network/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/network/__init__.py
diff --git a/tempest/services/network/json/__init__.py b/tempest/services/network/json/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/network/json/__init__.py
diff --git a/tempest/services/network/json/network_client.py b/tempest/services/network/json/network_client.py
new file mode 100644
index 0000000..49b83fe
--- /dev/null
+++ b/tempest/services/network/json/network_client.py
@@ -0,0 +1,103 @@
+import json
+from tempest.common.rest_client import RestClient
+
+
+class NetworkClient(RestClient):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(NetworkClient, self).__init__(config, username, password,
+                                           auth_url, tenant_name)
+        self.service = self.config.network.catalog_type
+
+    def list_networks(self):
+        resp, body = self.get('networks')
+        body = json.loads(body)
+        return resp, body
+
+    def create_network(self, name, key="network"):
+        post_body = {
+            key: {
+              'name': name
+             }
+        }
+        headers = {'Content-Type': 'application/json'}
+        body = json.dumps(post_body)
+        resp, body = self.post('networks',
+                                      headers=headers,
+                                      body=body)
+        body = json.loads(body)
+        return resp, body
+
+    def list_networks_details(self):
+        resp, body = self.get('networks/detail')
+        body = json.loads(body)
+        return resp, body
+
+    def get_network(self, uuid):
+        resp, body = self.get('networks/%s' % uuid)
+        body = json.loads(body)
+        return resp, body
+
+    def get_network_details(self, uuid):
+        resp, body = self.get('networks/%s/detail' % uuid)
+        body = json.loads(body)
+        return resp, body
+
+    def delete_network(self, uuid):
+        resp, body = self.delete('networks/%s' % uuid)
+        return resp, body
+
+    def create_port(self, network_id, zone, state=None, key='port'):
+        if not state:
+            state = 'ACTIVE'
+        post_body = {
+            key: {
+                'state': state,
+                'nova_id': zone
+            }
+        }
+        headers = {'Content-Type': 'application/json'}
+        body = json.dumps(post_body)
+        resp, body = self.post('networks/%s/ports.json' % network_id,
+                                        headers=headers, body=body)
+        body = json.loads(body)
+        return resp, body
+
+    def delete_port(self, network_id, port_id):
+        resp, body = self.delete('networks/%s/ports/%s.json' %
+                                            (network_id, port_id))
+        return resp, body
+
+    def list_ports(self, network_id):
+        resp, body = self.get('networks/%s/ports.json' % network_id)
+        body = json.loads(body)
+        return resp, body
+
+    def list_port_details(self, network_id):
+        url = 'networks/%s/ports/detail.json' % network_id
+        resp, body = self.get(url)
+        body = json.loads(body)
+        return resp, body
+
+    def attach_port(self, network_id, port_id, interface_id):
+        post_body = {
+            'attachment': {
+                'id': interface_id
+            }
+        }
+        headers = {'Content-Type': 'application/json'}
+        body = json.dumps(post_body)
+        url = 'networks/%s/ports/%s/attachment.json' % (network_id, port_id)
+        resp, body = self.put(url, headers=headers, body=body)
+        return resp, body
+
+    def detach_port(self, network_id, port_id):
+        url = 'networks/%s/ports/%s/attachment.json' % (network_id, port_id)
+        resp, body = self.delete(url)
+        return resp, body
+
+    def list_port_attachment(self, network_id, port_id):
+        url = 'networks/%s/ports/%s/attachment.json' % (network_id, port_id)
+        resp, body = self.get(url)
+        body = json.loads(body)
+        return resp, body
diff --git a/tempest/tests/network/__init__.py b/tempest/tests/network/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/tests/network/__init__.py
diff --git a/tempest/tests/network/test_networks.py b/tempest/tests/network/test_networks.py
new file mode 100644
index 0000000..6adbb6b
--- /dev/null
+++ b/tempest/tests/network/test_networks.py
@@ -0,0 +1,66 @@
+from nose.plugins.attrib import attr
+from tempest import openstack
+from tempest.common.utils.data_utils import rand_name
+import unittest2 as unittest
+
+
+class NetworksTest(unittest.TestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        cls.os = openstack.Manager()
+        cls.client = cls.os.network_client
+        cls.config = cls.os.config
+        cls.name = rand_name('network')
+        resp, body = cls.client.create_network(cls.name)
+        cls.network = body['network']
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.client.delete_network(cls.network['id'])
+
+    @attr(type='positive')
+    def test_create_delete_network(self):
+        """Creates and deletes a network for a tenant"""
+        name = rand_name('network')
+        resp, body = self.client.create_network(name)
+        self.assertEqual('202', resp['status'])
+        network = body['network']
+        self.assertTrue(network['id'] is not None)
+        resp, body = self.client.delete_network(network['id'])
+        self.assertEqual('204', resp['status'])
+
+    @attr(type='positive')
+    def test_show_network(self):
+        """Verifies the details of a network"""
+        resp, body = self.client.get_network(self.network['id'])
+        self.assertEqual('200', resp['status'])
+        network = body['network']
+        self.assertEqual(self.network['id'], network['id'])
+        self.assertEqual(self.name, network['name'])
+
+    @attr(type='positive')
+    def test_show_network_details(self):
+        """Verifies the full details of a network"""
+        resp, body = self.client.get_network_details(self.network['id'])
+        self.assertEqual('200', resp['status'])
+        network = body['network']
+        self.assertEqual(self.network['id'], network['id'])
+        self.assertEqual(self.name, network['name'])
+        self.assertEqual(len(network['ports']), 0)
+
+    @attr(type='positive')
+    def test_list_networks(self):
+        """Verify the network exists in the list of all networks"""
+        resp, body = self.client.list_networks()
+        networks = body['networks']
+        found = any(n for n in networks if n['id'] == self.network['id'])
+        self.assertTrue(found)
+
+    @attr(type='positive')
+    def test_list_networks_with_detail(self):
+        """Verify the network exists in the detailed list of all networks"""
+        resp, body = self.client.list_networks_details()
+        networks = body['networks']
+        found = any(n for n in networks if n['id'] == self.network['id'])
+        self.assertTrue(found)