url="https://www.opnfv.org",
     install_requires=["flask==0.10",
                       "flask-restful==0.3.5",
+                      "flask-restful-swagger==0.19",
                       "html2text==2016.1.8",
                       "python-cinderclient==1.6.0",
                       "python-glanceclient==1.1.0",
 
                 raise Usage(content['message'])
 
         if (report is not None):
-            print storperf.fetch_results(report)
+            print "Fetching report for %s..." % (report,)
+            response = requests.get(
+                'http://127.0.0.1:5000/api/v1.0/job?id=%s' % (report,))
+            if (response.status_code == 400):
+                content = json.loads(response.content)
+                raise Usage(content['message'])
+            content = json.loads(response.content)
+            print content
         else:
             print "Calling start..."
             response = requests.post(
 
 puppet \
 build-essential \
 python-dev \
+python-matplotlib \
 python-pip \
 --no-install-recommends
 
 RUN git config --global http.sslVerify false
 RUN git clone https://gerrit.opnfv.org/gerrit/storperf ${repos_dir}/storperf
 RUN git clone https://gerrit.opnfv.org/gerrit/releng ${repos_dir}/releng
+
+# Third party git fetches
+RUN git clone https://github.com/swagger-api/swagger-ui.git ${repos_dir}/swagger-ui
+RUN mkdir -p ${repos_dir}/storperf/storperf/resources/html/swagger
+RUN cp -r ${repos_dir}/swagger-ui/dist/* ${repos_dir}/storperf/storperf/resources/html/swagger
+RUN sed -i 's|url = "http://petstore.swagger.io/v2/swagger.json";|url = window.location.protocol+"//"+window.location.host+"/api/spec.json";|' ${repos_dir}/storperf/storperf/resources/html/swagger/index.html
+
 RUN git clone http://git.kernel.dk/fio.git ${repos_dir}/fio
 RUN cd ${repos_dir}/fio && git checkout tags/fio-2.2.10
 RUN cd ${repos_dir}/fio && make -j 6 install
+
 RUN puppet module install gdsoperations-graphite
 
 RUN chmod 600 ${repos_dir}/storperf/storperf/resources/ssh/storperf_rsa
 
 flask-restful==0.3.5
 flask-restful-swagger==0.19
 flask-swagger==0.2.12
-html2text==2016.1.8
\ No newline at end of file
+html2text==2016.1.8
 
 
 @app.route('/swagger/<path:path>')
 def send_swagger(path):
-    print "called! storperf/resources/html/swagger/" + path
     return send_from_directory('storperf/resources/html/swagger', path)
 
 
+@swagger.model
+class ConfigurationRequestModel:
+    resource_fields = {
+        'agent_count': fields.Integer,
+        'public_network': fields.String,
+        'volume_size': fields.Integer
+    }
+
+
+@swagger.model
+class ConfigurationResponseModel:
+    resource_fields = {
+        'agent_count': fields.Integer,
+        'public_network': fields.String,
+        'stack_created': fields.Boolean,
+        'stack_id': fields.String,
+        'volume_size': fields.Integer
+    }
+
+
 class Configure(Resource):
 
+    """Configuration API"""
+
     def __init__(self):
         self.logger = logging.getLogger(__name__)
 
+    @swagger.operation(
+        notes='Fetch the current agent configuration',
+        type=ConfigurationResponseModel.__name__
+    )
     def get(self):
         return jsonify({'agent_count': storperf.agent_count,
                         'public_network': storperf.public_network,
                         'stack_created': storperf.is_stack_created,
                         'stack_id': storperf.stack_id})
 
+    @swagger.operation(
+        notes='''Set the current agent configuration and create a stack in
+        the controller.  Returns once the stack request is submitted.''',
+        parameters=[
+            {
+                "name": "configuration",
+                "description": '''Configuration to be set. All parameters are
+                optional, and will retain their previous value if not
+                specified.  Volume size is in GB.
+                ''',
+                "required": True,
+                "type": "ConfigurationRequestModel",
+                "paramType": "body"
+            }
+        ],
+        type=ConfigurationResponseModel.__name__
+    )
     def post(self):
         if not request.json:
             abort(400, "ERROR: No data specified")
         except Exception as e:
             abort(400, str(e))
 
+    @swagger.operation(
+        notes='Deletes the agent configuration and the stack'
+    )
     def delete(self):
         try:
             storperf.delete_stack()
     }
 
 
+@swagger.model
+class WorkloadResponseModel:
+    resource_fields = {
+        'job_id': fields.String
+    }
+
+
 class Job(Resource):
 
+    """Job API"""
+
     def __init__(self):
         self.logger = logging.getLogger(__name__)
 
                 "paramType": "body"
             }
         ],
+        type=WorkloadResponseModel.__name__,
         responseMessages=[
             {
                 "code": 200,
-                "message": "Wordload ID found, response in JSON format"
+                "message": "Job submitted"
             },
             {
                 "code": 400,
     )
     def delete(self):
         try:
-            storperf.terminate_workloads()
-            return True
+            return jsonify({'Slaves': storperf.terminate_workloads()})
         except Exception as e:
             abort(400, str(e))
 
 
+@swagger.model
+class QuotaModel:
+
+    resource_fields = {
+        'quota': fields.Integer
+    }
+
+
 class Quota(Resource):
+    """Quota API"""
 
+    @swagger.operation(
+        notes='''Fetch the current Cinder volume quota.  This value limits
+        the number of volumes that can be created, and by extension, defines
+        the maximum number of agents that can be created for any given test
+        scenario''',
+        type=QuotaModel.__name__
+    )
     def get(self):
-        quota = storperf.get_volume_quota()
+        quota = storperf.volume_quota
         return jsonify({'quota': quota})
 
 
 
 from storperf.db.job_db import JobDB
+import calendar
 import json
 import logging
+import time
 
 import requests
 
         for io_type in ['read', 'write']:
             for workload_name, times in workload_names.iteritems():
                 workload_pattern = self.make_fullname_pattern(workload_name)
+                short_name = '.'.join(workload_name.split('.')[1:6])
+                start = times[0]
+                end = times[1]
+
+                if end is None:
+                    end = str(calendar.timegm(time.gmtime()))
+                averages[short_name + ".duration"] = \
+                    (int(end) - int(start))
+
+                key = short_name + "." + io_type
+
                 request = ("http://127.0.0.1:8000/render/?target="
                            "averageSeries(%s.jobs.1.%s.lat.mean)"
                            "&format=json"
                            "&from=%s"
                            "&until=%s" %
-                           (workload_pattern, io_type, times[0], times[1]))
+                           (workload_pattern, io_type, start, end))
+                self.logger.debug("Calling %s" % (request))
+
+                response = requests.get(request)
+                if (response.status_code == 200):
+                    averages[key + ".latency"] = \
+                        self._average_results(json.loads(response.content))
+
+                request = ("http://127.0.0.1:8000/render/?target="
+                           "averageSeries(%s.jobs.1.%s.bw)"
+                           "&format=json"
+                           "&from=%s"
+                           "&until=%s" %
+                           (workload_pattern, io_type, start, end))
+                self.logger.debug("Calling %s" % (request))
+
+                response = requests.get(request)
+                if (response.status_code == 200):
+                    averages[key + ".throughput"] = \
+                        self._average_results(json.loads(response.content))
+
+                request = ("http://127.0.0.1:8000/render/?target="
+                           "averageSeries(%s.jobs.1.%s.iops)"
+                           "&format=json"
+                           "&from=%s"
+                           "&until=%s" %
+                           (workload_pattern, io_type, start, end))
                 self.logger.debug("Calling %s" % (request))
 
                 response = requests.get(request)
                 if (response.status_code == 200):
-                    short_name = '.'.join(workload_name.split('.')[1:6])
-                    averages[short_name + "." + io_type] = \
+                    averages[key + ".iops"] = \
                         self._average_results(json.loads(response.content))
 
         return averages
 
 
     def terminate(self):
         self._terminated = True
+        terminated_hosts = []
         for workload in self._workload_executors:
             workload.terminate()
+            terminated_hosts.append(workload.remote_host)
+        return terminated_hosts
 
     def execute_workloads(self):
         self._terminated = False