[cvp-web] Show some SUT hardware info in 'My Results' 05/46405/8
authorgrakiss <grakiss.wanglei@huawei.com>
Mon, 30 Oct 2017 07:26:37 +0000 (07:26 +0000)
committerLeo wang <grakiss.wanglei@huawei.com>
Wed, 1 Nov 2017 08:44:36 +0000 (08:44 +0000)
[cvp-web] Show SUT endpoint in web page

JIRA: DOVETAIL-541
JIRA: DOVETAIL-547

Show some SUT hardware info that dovetail concern in 'My Results' web page.
Show the info of all endpoints get from Dovetail tool in the web page.

Change-Id: Ibc465396e17b7c22678e3948fa2f659cbff6f323
Signed-off-by: grakiss <grakiss.wanglei@huawei.com>
cvp/3rd_party/static/testapi-ui/app.js
cvp/3rd_party/static/testapi-ui/components/results/results.html
cvp/3rd_party/static/testapi-ui/components/results/resultsController.js
cvp/3rd_party/static/testapi-ui/components/sut/sut.html [new file with mode: 0644]
cvp/3rd_party/static/testapi-ui/components/sut/sutController.js [new file with mode: 0644]
cvp/3rd_party/static/testapi-ui/index.html
cvp/docker/nginx/sites-enabled/default
cvp/opnfv_testapi/resources/sut_handlers.py [new file with mode: 0644]
cvp/opnfv_testapi/resources/sut_models.py [new file with mode: 0644]
cvp/opnfv_testapi/router/url_mappings.py

index 5a3cc06..eea8e38 100644 (file)
                 templateUrl: '/testapi-ui/components/products/cloud.html',
                 controller: 'ProductController as ctrl'
             }).
-           state('application', {
+            state('sut', {
+                url: '/suts/:testID',
+                templateUrl: '/testapi-ui/components/sut/sut.html',
+                controller: 'SutController as ctrl'
+            }).
+            state('application', {
                 url: '/application',
                 templateUrl: '/testapi-ui/components/application/application.html',
                 controller: 'ApplicationController as ctrl'
index e1d05b2..cd38ad7 100644 (file)
@@ -25,6 +25,7 @@
                 <th>Label</th>
                 <th>Status</th>
                 <th>Log</th>
+                <th>SUT</th>
                 <th class="col-md-2">Operation</th>
                 <th class="col-md-2">Share List</th>
             </tr>
 
         <tbody style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
             <tr ng-repeat="(index, result) in ctrl.data.tests">
-                <td>{{ result.upload_date }}</td>
+                <td>{{ result.upload_date | limitTo:19}}</td>
                 <td><a ng-click="ctrl.gotoResultDetail(result.id, result._id)">{{ result.id }}</a></td>
                 <td>{{ result.filename || "None"}}</td>
                 <td><div class="popover-wrapper"><a editable-theme="bs3" onbeforesave="ctrl.toggleCheck(result, 'label', $data)" editable-text="result.label"> {{ result.label || "None" }}</a></div></td>
                 <td>{{ result.status }}</td>
                 <td><a ng-click="ctrl.downloadLogs(result.id)">logs</a></td>
-               <td>
+                <td><a ng-click="ctrl.gotoSUT(result.id)">info</a></td>
+                <td>
                     <div class="btn-group" uib-dropdown>
                         <a id="single-button" type="button" class="nectar-button medium accent-color regular-button" uib-dropdown-toggle>
                             Operation<span class="caret"></span>
index 2ece86f..86f7c5d 100644 (file)
@@ -59,6 +59,7 @@
         ctrl.associateProductVersion = associateProductVersion;
         ctrl.getProductVersions = getProductVersions;
         ctrl.prepVersionEdit = prepVersionEdit;
+        ctrl.gotoSUT = gotoSUT;
         ctrl.gotoResultDetail = gotoResultDetail;
         ctrl.toggleCheck = toggleCheck;
         ctrl.toReview = toReview;
             $state.go('resultsDetail', {'testID': testId, 'innerID': innerID});
         }
 
+        function gotoSUT(testId) {
+            $state.go('sut', {'testID': testId});
+        }
+
     }
 })();
diff --git a/cvp/3rd_party/static/testapi-ui/components/sut/sut.html b/cvp/3rd_party/static/testapi-ui/components/sut/sut.html
new file mode 100644 (file)
index 0000000..d9692b8
--- /dev/null
@@ -0,0 +1,40 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+
+<link rel="stylesheet" href="testapi-ui/assets/css/combine.css" />
+
+<h2>Endpoints</h2>
+<div  class="results-table" style="margin-top:30px;overflow:scroll">
+    <table class="table table-striped table-hover">
+        <thead>
+            <tr>
+                <th>Service Name</th>
+                <th>Service Type</th>
+                <th>URL</th>
+                <th>Enabled</th>
+            </tr>
+        </thead>
+        <tbody style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
+            <tr ng-repeat="endpoint in ctrl.sutData.endpoint_info">
+                <td>{{ endpoint['Service Name'] }}</td>
+                <td>{{ endpoint['Service Type'] }}</td>
+                <td>{{ endpoint['URL'] }}</td>
+                <td>{{ endpoint['Enabled'] }}</td>
+            </tr>
+        </tbody>
+    </table>
+</div>
+
+<h2>Hosts</h2>
+<div ng-repeat="(host, info) in ctrl.sutData.hardware_info">
+    <div  class="results-table" style="margin-top:30px;overflow:scroll">
+        <table class="table table-striped table-hover">
+            <tbody style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
+                <tr ng-repeat="record in info">
+                    <td>{{ record[0] }}</td>
+                    <td>{{ record[1] }}</td>
+                </tr>
+            </tbody>
+        </table>
+    </div>
+</div>
diff --git a/cvp/3rd_party/static/testapi-ui/components/sut/sutController.js b/cvp/3rd_party/static/testapi-ui/components/sut/sutController.js
new file mode 100644 (file)
index 0000000..3d662ff
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function () {
+    'use strict';
+
+    angular
+        .module('testapiApp')
+        .controller('SutController', SutController);
+
+    SutController.$inject = [
+        '$http', '$stateParams', 'testapiApiUrl'
+    ];
+
+    /**
+     */
+    function SutController($http, $stateParams, testapiApiUrl) {
+
+        var ctrl = this;
+
+        function init(){
+            ctrl.sutData = {"hardware_info": {}, "endpoint_info": {}};
+            ctrl.testID = $stateParams.testID;
+            ctrl.getSutData();
+        }
+
+        ctrl.getSutData = function(){
+            $http.get(testapiApiUrl + "/suts/hardware/" + ctrl.testID).then(function(resp){
+                ctrl.sutData = resp.data;
+            }, function(error){
+                alert('Error when get SUT data');
+            });
+        }
+
+        init();
+    }
+})();
index b6bdb5a..8ca6e63 100644 (file)
@@ -51,7 +51,8 @@
         <script src="testapi-ui/components/guidelines/guidelinesController.js"></script>
         <script src="testapi-ui/components/results/resultsController.js"></script>
         <script src="testapi-ui/components/results-report/resultsReportController.js"></script>
-       <script src="testapi-ui/components/application/applicationController.js"></script>
+        <script src="testapi-ui/components/sut/sutController.js"></script>
+        <script src="testapi-ui/components/application/applicationController.js"></script>
         <script src="testapi-ui/components/profile/profileController.js"></script>
         <script src="testapi-ui/components/auth-failure/authFailureController.js"></script>
         <script src="testapi-ui/components/logout/logoutController.js"></script>
index b6b6a7e..e2e718c 100644 (file)
@@ -101,6 +101,12 @@ server {
             proxy_set_header Host $host;
         }
 
+        location ~* /api/v1/suts/hardware/([a-zA-Z0-9\-]+) {
+            proxy_pass http://cvpapi/api/v1/suts/hardware/$1;
+            proxy_set_header X-Real-IP $remote_addr;
+            proxy_set_header Host $host;
+        }
+
         location /api/v1/ {
             proxy_pass http://testapi/api/v1/;
             proxy_set_header X-Real-IP $remote_addr;
diff --git a/cvp/opnfv_testapi/resources/sut_handlers.py b/cvp/opnfv_testapi/resources/sut_handlers.py
new file mode 100644 (file)
index 0000000..16c50b8
--- /dev/null
@@ -0,0 +1,112 @@
+##############################################################################
+# Copyright (c) 2017
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+import logging
+import json
+import os
+
+from opnfv_testapi.resources import handlers
+from opnfv_testapi.resources import sut_models
+from opnfv_testapi.tornado_swagger import swagger
+
+LOG = logging.getLogger(__name__)
+LOG.setLevel(logging.DEBUG)
+
+RESULT_PATH = '/home/testapi/logs/{}/results'
+
+
+class GenericSutHandler(handlers.GenericApiHandler):
+    def __init__(self, application, request, **kwargs):
+        super(GenericSutHandler, self).__init__(application,
+                                                request,
+                                                **kwargs)
+        self.table = "suts"
+        self.table_cls = sut_models.Sut
+
+
+class HardwareHandler(GenericSutHandler):
+    @swagger.operation(nickname="getHardwareById")
+    def get(self, id):
+        endpoint_info = self._read_endpoint_info(id)
+        LOG.debug('Endpoint info: %s', endpoint_info)
+
+        all_info = self._read_sut_info(id)
+        LOG.debug('All SUT info: %s', all_info)
+
+        hardware_info = {k: self._get_single_host_info(v)
+                         for k, v in all_info.items()}
+        LOG.debug('SUT info: %s', hardware_info)
+
+        data = {
+            'endpoint_info': endpoint_info,
+            'hardware_info': hardware_info
+        }
+
+        self.write(data)
+
+    def _read_endpoint_info(self, id):
+        path = os.path.join(RESULT_PATH.format(id), 'endpoint_info.json')
+        try:
+            with open(path) as f:
+                endpoint_info = json.load(f)
+        except Exception:
+            endpoint_info = []
+
+        return endpoint_info
+
+    def _read_sut_info(self, id):
+        path = os.path.join(RESULT_PATH.format(id), 'all_hosts_info.json')
+        try:
+            with open(path) as f:
+                all_info = json.load(f)
+        except Exception:
+            all_info = {}
+        return all_info
+
+    def _get_single_host_info(self, single_info):
+        info = []
+        facts = single_info.get('ansible_facts', {})
+
+        info.append(['hostname', facts.get('ansible_hostname')])
+
+        info.append(['product_name', facts.get('ansible_product_name')])
+        info.append(['product_version', facts.get('ansible_product_version')])
+
+        processors = facts.get('ansible_processor', [])
+        try:
+            processor_type = '{} {}'.format(processors[0], processors[1])
+        except IndexError:
+            LOG.exception('No Processor in SUT data')
+            processor_type = None
+        info.append(['processor_type', processor_type])
+        info.append(['architecture', facts.get('ansible_architecture')])
+        info.append(['processor_cores', facts.get('ansible_processor_cores')])
+        info.append(['processor_vcpus', facts.get('ansible_processor_vcpus')])
+
+        memory = facts.get('ansible_memtotal_mb')
+        memory = round(memory * 1.0 / 1024, 2) if memory else None
+        info.append(['memory', '{} GB'.format(memory)])
+
+        devices = facts.get('ansible_devices', {})
+        info.extend([self._get_device_info(k, v) for k, v in devices.items()])
+
+        lsb_description = facts.get('ansible_lsb', {}).get('description')
+        info.append(['OS', lsb_description])
+
+        interfaces = facts.get('ansible_interfaces')
+        info.append(['interfaces', interfaces])
+        info.extend([self._get_interface_info(facts, i) for i in interfaces])
+        info = [i for i in info if i]
+
+        return info
+
+    def _get_interface_info(self, facts, name):
+        mac = facts.get('ansible_{}'.format(name), {}).get('macaddress')
+        return [name, mac] if mac else []
+
+    def _get_device_info(self, name, info):
+        return ['disk_{}'.format(name), info.get('size')]
diff --git a/cvp/opnfv_testapi/resources/sut_models.py b/cvp/opnfv_testapi/resources/sut_models.py
new file mode 100644 (file)
index 0000000..b4a869b
--- /dev/null
@@ -0,0 +1,31 @@
+##############################################################################
+# Copyright (c) 2017
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+from opnfv_testapi.resources import models
+from opnfv_testapi.tornado_swagger import swagger
+
+
+@swagger.model()
+class Sut(models.ModelBase):
+    """
+    """
+    def __init__(self):
+        pass
+
+
+@swagger.model()
+class Suts(models.ModelBase):
+    """
+        @property suts:
+        @ptype tests: C{list} of L{Sut}
+    """
+    def __init__(self):
+        self.suts = list()
+
+    @staticmethod
+    def attr_parser():
+        return {'suts': Sut}
index 73c771f..83190ee 100644 (file)
@@ -11,6 +11,7 @@ from opnfv_testapi.resources import handlers
 from opnfv_testapi.resources import result_handlers
 from opnfv_testapi.resources import test_handlers
 from opnfv_testapi.resources import application_handlers
+from opnfv_testapi.resources import sut_handlers
 from opnfv_testapi.ui.auth import sign
 from opnfv_testapi.ui.auth import user
 
@@ -27,6 +28,7 @@ mappings = [
     (r"/api/v1/cvp/applications/([^/]+)",
      application_handlers.ApplicationsGURHandler),
 
+    (r"/api/v1/suts/hardware/([^/]+)", sut_handlers.HardwareHandler),
 
 
     (r'/api/v1/auth/signin', sign.SigninHandler),