Browse Source

Merge branch 'trunk' into branch-dev-patch-upgrade

Nate Cole 9 years ago
parent
commit
db999ae82c
34 changed files with 709 additions and 138 deletions
  1. 2 1
      ambari-admin/src/main/resources/ui/admin-web/app/scripts/app.js
  2. 2 1
      ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/NavbarCtrl.js
  3. 2 1
      ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/authentication/AuthenticationMainCtrl.js
  4. 6 1
      ambari-admin/src/main/resources/ui/admin-web/app/scripts/routes.js
  5. 1 1
      ambari-admin/src/main/resources/ui/admin-web/app/views/authentication/main.html
  6. 1 1
      ambari-admin/src/main/resources/ui/admin-web/app/views/leftNavbar.html
  7. 163 4
      ambari-common/src/main/python/pluggable_stack_definition/configs/PHD.json
  8. 4 3
      ambari-metrics/ambari-metrics-host-monitoring/conf/unix/metric_monitor.ini
  9. 28 8
      ambari-metrics/ambari-metrics-host-monitoring/src/main/python/core/config_reader.py
  10. 0 1
      ambari-metrics/ambari-metrics-host-monitoring/src/main/python/core/controller.py
  11. 22 12
      ambari-metrics/ambari-metrics-host-monitoring/src/main/python/core/emitter.py
  12. 98 0
      ambari-metrics/ambari-metrics-host-monitoring/src/main/python/core/security.py
  13. 36 29
      ambari-metrics/ambari-metrics-host-monitoring/src/test/python/core/TestEmitter.py
  14. 39 1
      ambari-server/src/main/resources/common-services/AMBARI_METRICS/0.1.0/package/scripts/ams.py
  15. 0 4
      ambari-server/src/main/resources/common-services/AMBARI_METRICS/0.1.0/package/scripts/functions.py
  16. 40 18
      ambari-server/src/main/resources/common-services/AMBARI_METRICS/0.1.0/package/scripts/metrics_grafana_util.py
  17. 39 0
      ambari-server/src/main/resources/common-services/AMBARI_METRICS/0.1.0/package/scripts/network.py
  18. 3 1
      ambari-server/src/main/resources/common-services/AMBARI_METRICS/0.1.0/package/scripts/params.py
  19. 11 13
      ambari-server/src/main/resources/common-services/AMBARI_METRICS/0.1.0/package/scripts/service_check.py
  20. 3 2
      ambari-server/src/main/resources/common-services/AMBARI_METRICS/0.1.0/package/templates/metric_monitor.ini.j2
  21. 1 1
      ambari-server/src/main/resources/common-services/AMBARI_METRICS/0.1.0/package/templates/metrics_grafana_datasource.json.j2
  22. 10 0
      ambari-server/src/test/python/stacks/2.0.6/AMBARI_METRICS/test_metrics_collector.py
  23. 11 1
      ambari-server/src/test/python/stacks/2.0.6/AMBARI_METRICS/test_metrics_grafana.py
  24. 10 0
      ambari-server/src/test/python/stacks/2.0.6/configs/default.json
  25. 8 0
      ambari-server/src/test/python/unitTests.py
  26. 4 1
      ambari-web/app/router.js
  27. 11 4
      ambari-web/app/styles/application.less
  28. 5 3
      ambari-web/app/styles/enhanced_service_dashboard.less
  29. 3 1
      ambari-web/app/templates/common/chart/linear_time.hbs
  30. 1 0
      ambari-web/app/templates/main/charts/linear_time.hbs
  31. 42 6
      ambari-web/app/views/common/chart/linear_time.js
  32. 30 0
      ambari-web/app/views/common/widget/graph_widget_view.js
  33. 2 0
      ambari-web/app/views/main/dashboard/widgets/cluster_metrics_widget.js
  34. 71 19
      ambari-web/test/views/common/chart/linear_time_test.js

+ 2 - 1
ambari-admin/src/main/resources/ui/admin-web/app/scripts/app.js

@@ -28,7 +28,8 @@ angular.module('ambariAdminConsole', [
 .constant('Settings', {
 .constant('Settings', {
 	baseUrl: '/api/v1',
 	baseUrl: '/api/v1',
   testMode: (window.location.port == 8000),
   testMode: (window.location.port == 8000),
-  mockDataPrefix: 'assets/data/'
+  mockDataPrefix: 'assets/data/',
+  isLDAPConfigurationSupported: false
 })
 })
 .config(['RestangularProvider', '$httpProvider', '$provide', function(RestangularProvider, $httpProvider, $provide) {
 .config(['RestangularProvider', '$httpProvider', '$provide', function(RestangularProvider, $httpProvider, $provide) {
   // Config Ajax-module
   // Config Ajax-module

+ 2 - 1
ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/NavbarCtrl.js

@@ -18,7 +18,7 @@
 'use strict';
 'use strict';
 
 
 angular.module('ambariAdminConsole')
 angular.module('ambariAdminConsole')
-.controller('NavbarCtrl',['$scope', 'Cluster', '$location', 'Alert', 'ROUTES', 'ConfirmationModal', '$rootScope', 'Stack', '$translate', function($scope, Cluster, $location, Alert, ROUTES, ConfirmationModal, $rootScope, Stack, $translate) {
+.controller('NavbarCtrl',['$scope', 'Cluster', '$location', 'Alert', 'ROUTES', 'ConfirmationModal', '$rootScope', 'Stack', '$translate', 'Settings', function($scope, Cluster, $location, Alert, ROUTES, ConfirmationModal, $rootScope, Stack, $translate, Settings) {
   var $t = $translate.instant;
   var $t = $translate.instant;
   $scope.cluster = null;
   $scope.cluster = null;
   $scope.totalRepos = 0;
   $scope.totalRepos = 0;
@@ -26,6 +26,7 @@ angular.module('ambariAdminConsole')
     name        : '',
     name        : '',
     editingName : false
     editingName : false
   };
   };
+  $scope.settings = Settings;
 
 
   function loadClusterData() {
   function loadClusterData() {
     Cluster.getStatus().then(function (cluster) {
     Cluster.getStatus().then(function (cluster) {

+ 2 - 1
ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/authentication/AuthenticationMainCtrl.js

@@ -18,8 +18,9 @@
 'use strict';
 'use strict';
 
 
 angular.module('ambariAdminConsole')
 angular.module('ambariAdminConsole')
-  .controller('AuthenticationMainCtrl', ['$scope', '$translate', 'Alert', function ($scope, $translate, $Alert) {
+  .controller('AuthenticationMainCtrl', ['$scope', '$translate', 'Alert', 'Settings', function ($scope, $translate, $Alert, Settings) {
     $scope.t = $translate.instant;
     $scope.t = $translate.instant;
+    $scope.settings = Settings;
 
 
     $scope.isLDAPEnabled = false;
     $scope.isLDAPEnabled = false;
     $scope.connectivity = {
     $scope.connectivity = {

+ 6 - 1
ambari-admin/src/main/resources/ui/admin-web/app/scripts/routes.js

@@ -146,7 +146,12 @@ angular.module('ambariAdminConsole')
   };
   };
   angular.forEach(ROUTES, createRoute);
   angular.forEach(ROUTES, createRoute);
 }])
 }])
-.run(['$rootScope', 'ROUTES', function($rootScope, ROUTES) {
+.run(['$rootScope', 'ROUTES', 'Settings', function($rootScope, ROUTES, Settings) {
   // Make routes available in every template and controller
   // Make routes available in every template and controller
   $rootScope.ROUTES = ROUTES;
   $rootScope.ROUTES = ROUTES;
+  $rootScope.$on('$locationChangeStart', function (e, nextUrl) {
+    if (/\/authentication$/.test(nextUrl) && !Settings.isLDAPConfigurationSupported) {
+      e.preventDefault();
+    }
+  });
 }]);
 }]);

+ 1 - 1
ambari-admin/src/main/resources/ui/admin-web/app/views/authentication/main.html

@@ -16,7 +16,7 @@
 * limitations under the License.
 * limitations under the License.
 -->
 -->
 
 
-<div class="users-pane enable-ldap">
+<div class="users-pane enable-ldap" ng-show="settings.isLDAPConfigurationSupported">
 
 
   <div class="clearfix">
   <div class="clearfix">
     <ol class="breadcrumb pull-left">
     <ol class="breadcrumb pull-left">

+ 1 - 1
ambari-admin/src/main/resources/ui/admin-web/app/views/leftNavbar.html

@@ -109,7 +109,7 @@
     <div class="panel-heading"><span class="glyphicon glyphicon-cog"></span> {{'common.settings' | translate}}</div>
     <div class="panel-heading"><span class="glyphicon glyphicon-cog"></span> {{'common.settings' | translate}}</div>
     <div class="panel-body">
     <div class="panel-body">
       <ul class="nav nav-pills nav-stacked">
       <ul class="nav nav-pills nav-stacked">
-        <li ng-class="{active: isActive('authentication.main')}"><link-to route="authentication.main">{{'common.authentication' | translate}}</link-to></li>
+        <li ng-class="{active: isActive('authentication.main')}" ng-show="settings.isLDAPConfigurationSupported"><link-to route="authentication.main">{{'common.authentication' | translate}}</link-to></li>
         <li ng-class="{active: isActive('loginActivities.loginMessage')}"><link-to route="loginActivities.loginMessage">{{'common.loginActivities.loginActivities' | translate}}</link-to></li>
         <li ng-class="{active: isActive('loginActivities.loginMessage')}"><link-to route="loginActivities.loginMessage">{{'common.loginActivities.loginActivities' | translate}}</link-to></li>
       </ul>
       </ul>
     </div>
     </div>

+ 163 - 4
ambari-common/src/main/python/pluggable_stack_definition/configs/PHD.json

@@ -203,7 +203,7 @@
       "version": "3.3",
       "version": "3.3",
       "baseVersion": "2.3",
       "baseVersion": "2.3",
       "active": "true",
       "active": "true",
-      "family": "redhat6,redhat7,suse11",
+      "family": "redhat6,amazon2015,redhat7,suse11",
       "services": [
       "services": [
         {
         {
           "name": "KERBEROS"
           "name": "KERBEROS"
@@ -239,7 +239,7 @@
               ]
               ]
             },
             },
             {
             {
-              "family": "redhat6,redhat7,suse11",
+              "family": "redhat6,amazon2015,redhat7,suse11",
               "packages": [
               "packages": [
                 "hive_${stack_version}",
                 "hive_${stack_version}",
                 "hive_${stack_version}-hcatalog",
                 "hive_${stack_version}-hcatalog",
@@ -295,7 +295,7 @@
           "name": "OOZIE",
           "name": "OOZIE",
           "packages":[
           "packages":[
             {
             {
-              "family": "redhat6,redhat7,suse11",
+              "family": "redhat6,amazon2015,redhat7,suse11",
               "packages": [
               "packages": [
                 "oozie_${stack_version}"
                 "oozie_${stack_version}"
               ]
               ]
@@ -320,7 +320,7 @@
           "name": "PIG",
           "name": "PIG",
           "packages":[
           "packages":[
             {
             {
-              "family": "redhat6,redhat7,suse11",
+              "family": "redhat6,amazon2015,redhat7,suse11",
               "packages": [
               "packages": [
                 "pig_${stack_version}"
                 "pig_${stack_version}"
               ]
               ]
@@ -354,6 +354,165 @@
           ]
           ]
         }
         }
       ]
       ]
+    },
+    {
+      "version": "3.4",
+      "baseVersion": "2.4",
+      "active": "true",
+      "family": "redhat6,amazon2015,redhat7,suse11",
+      "services": [
+        {
+          "name": "KERBEROS"
+        },
+        {
+          "name": "AMBARI_METRICS"
+        },
+        {
+          "name": "HDFS"
+        },
+        {
+          "name": "ZOOKEEPER"
+        },
+        {
+          "name": "HBASE"
+        },
+        {
+          "name": "YARN"
+        },
+        {
+          "name": "MAPREDUCE2"
+        },
+        {
+          "name": "HIVE"
+        },
+        {
+          "name": "TEZ"
+        },
+        {
+          "name": "OOZIE"
+        },
+        {
+          "name": "KNOX"
+        },
+        {
+          "name": "PIG"
+        },
+        {
+          "name": "SPARK"
+        },
+        {
+          "name": "RANGER"
+        },
+        {
+          "name": "RANGER_KMS"
+        }
+      ]
+    },
+    {
+      "version": "3.5",
+      "baseVersion": "2.5",
+      "active": "true",
+      "family": "redhat6,amazon2015,redhat7,suse11",
+      "services": [
+        {
+          "name": "KERBEROS"
+        },
+        {
+          "name": "AMBARI_METRICS"
+        },
+        {
+          "name": "HDFS"
+        },
+        {
+          "name": "ZOOKEEPER"
+        },
+        {
+          "name": "HBASE"
+        },
+        {
+          "name": "YARN"
+        },
+        {
+          "name": "MAPREDUCE2"
+        },
+        {
+          "name": "HIVE"
+        },
+        {
+          "name": "TEZ"
+        },
+        {
+          "name": "OOZIE"
+        },
+        {
+          "name": "KNOX"
+        },
+        {
+          "name": "PIG"
+        },
+        {
+          "name": "SPARK"
+        },
+        {
+          "name": "RANGER"
+        },
+        {
+          "name": "RANGER_KMS"
+        }
+      ]
+    },
+    {
+      "version": "3.6",
+      "baseVersion": "2.6",
+      "active": "true",
+      "family": "redhat6,amazon2015,redhat7,suse11",
+      "services": [
+        {
+          "name": "KERBEROS"
+        },
+        {
+          "name": "AMBARI_METRICS"
+        },
+        {
+          "name": "HDFS"
+        },
+        {
+          "name": "ZOOKEEPER"
+        },
+        {
+          "name": "HBASE"
+        },
+        {
+          "name": "YARN"
+        },
+        {
+          "name": "MAPREDUCE2"
+        },
+        {
+          "name": "HIVE"
+        },
+        {
+          "name": "TEZ"
+        },
+        {
+          "name": "OOZIE"
+        },
+        {
+          "name": "KNOX"
+        },
+        {
+          "name": "PIG"
+        },
+        {
+          "name": "SPARK"
+        },
+        {
+          "name": "RANGER"
+        },
+        {
+          "name": "RANGER_KMS"
+        }
+      ]
     }
     }
   ]
   ]
 }
 }

+ 4 - 3
ambari-metrics/ambari-metrics-host-monitoring/conf/unix/metric_monitor.ini

@@ -18,11 +18,9 @@
 
 
 [default]
 [default]
 debug_level = INFO
 debug_level = INFO
-metrics_server = localhost:{{ams_collector_port}}
-hostname = {{hostname}}
+hostname = localhost
 enable_time_threshold = false
 enable_time_threshold = false
 enable_value_threshold = false
 enable_value_threshold = false
-https_enabled = false
 
 
 [emitter]
 [emitter]
 send_interval = 60
 send_interval = 60
@@ -30,3 +28,6 @@ send_interval = 60
 [collector]
 [collector]
 collector_sleep_interval = 5
 collector_sleep_interval = 5
 max_queue_size = 5000
 max_queue_size = 5000
+host = localhost
+port = 6188
+https_enabled = false

+ 28 - 8
ambari-metrics/ambari-metrics-host-monitoring/src/main/python/core/config_reader.py

@@ -34,30 +34,38 @@ class ConfigDefaults(object):
     pass
     pass
   def get_metric_file_path(self):
   def get_metric_file_path(self):
     pass
     pass
+  def get_ca_certs_file_path(self):
+    pass
 
 
 @OsFamilyImpl(os_family=OSConst.WINSRV_FAMILY)
 @OsFamilyImpl(os_family=OSConst.WINSRV_FAMILY)
 class ConfigDefaultsWindows(ConfigDefaults):
 class ConfigDefaultsWindows(ConfigDefaults):
   def __init__(self):
   def __init__(self):
     self._CONFIG_FILE_PATH = "conf\\metric_monitor.ini"
     self._CONFIG_FILE_PATH = "conf\\metric_monitor.ini"
     self._METRIC_FILE_PATH = "conf\\metric_groups.conf"
     self._METRIC_FILE_PATH = "conf\\metric_groups.conf"
+    self._METRIC_FILE_PATH = "conf\\ca.pem"
     pass
     pass
 
 
   def get_config_file_path(self):
   def get_config_file_path(self):
     return self._CONFIG_FILE_PATH
     return self._CONFIG_FILE_PATH
   def get_metric_file_path(self):
   def get_metric_file_path(self):
     return self._METRIC_FILE_PATH
     return self._METRIC_FILE_PATH
+  def get_ca_certs_file_path(self):
+    return self._CA_CERTS_FILE_PATH
 
 
 @OsFamilyImpl(os_family=OsFamilyImpl.DEFAULT)
 @OsFamilyImpl(os_family=OsFamilyImpl.DEFAULT)
 class ConfigDefaultsLinux(ConfigDefaults):
 class ConfigDefaultsLinux(ConfigDefaults):
   def __init__(self):
   def __init__(self):
     self._CONFIG_FILE_PATH = "/etc/ambari-metrics-monitor/conf/metric_monitor.ini"
     self._CONFIG_FILE_PATH = "/etc/ambari-metrics-monitor/conf/metric_monitor.ini"
     self._METRIC_FILE_PATH = "/etc/ambari-metrics-monitor/conf/metric_groups.conf"
     self._METRIC_FILE_PATH = "/etc/ambari-metrics-monitor/conf/metric_groups.conf"
+    self._CA_CERTS_FILE_PATH = "/etc/ambari-metrics-monitor/conf/ca.pem"
     pass
     pass
 
 
   def get_config_file_path(self):
   def get_config_file_path(self):
     return self._CONFIG_FILE_PATH
     return self._CONFIG_FILE_PATH
   def get_metric_file_path(self):
   def get_metric_file_path(self):
     return self._METRIC_FILE_PATH
     return self._METRIC_FILE_PATH
+  def get_ca_certs_file_path(self):
+    return self._CA_CERTS_FILE_PATH
 
 
 configDefaults = ConfigDefaults()
 configDefaults = ConfigDefaults()
 
 
@@ -65,6 +73,7 @@ config = ConfigParser.RawConfigParser()
 
 
 CONFIG_FILE_PATH = configDefaults.get_config_file_path()
 CONFIG_FILE_PATH = configDefaults.get_config_file_path()
 METRIC_FILE_PATH = configDefaults.get_metric_file_path()
 METRIC_FILE_PATH = configDefaults.get_metric_file_path()
+CA_CERTS_FILE_PATH = configDefaults.get_ca_certs_file_path()
 
 
 OUT_DIR = os.path.join(os.sep, "var", "log", "ambari-metrics-host-monitoring")
 OUT_DIR = os.path.join(os.sep, "var", "log", "ambari-metrics-host-monitoring")
 SERVER_OUT_FILE = OUT_DIR + os.sep + "ambari-metrics-host-monitoring.out"
 SERVER_OUT_FILE = OUT_DIR + os.sep + "ambari-metrics-host-monitoring.out"
@@ -88,10 +97,8 @@ AMBARI_AGENT_CONF = '/etc/ambari-agent/conf/ambari-agent.ini'
 config_content = """
 config_content = """
 [default]
 [default]
 debug_level = INFO
 debug_level = INFO
-metrics_server = host:port
 enable_time_threshold = false
 enable_time_threshold = false
 enable_value_threshold = false
 enable_value_threshold = false
-https_enabled = false
 
 
 [emitter]
 [emitter]
 send_interval = 60
 send_interval = 60
@@ -99,6 +106,9 @@ send_interval = 60
 [collector]
 [collector]
 collector_sleep_interval = 5
 collector_sleep_interval = 5
 max_queue_size = 5000
 max_queue_size = 5000
+host = localhost
+port = 6188
+https_enabled = false
 """
 """
 
 
 metric_group_info = """
 metric_group_info = """
@@ -162,7 +172,7 @@ class Configuration:
         'process_metric_groups': []
         'process_metric_groups': []
       }
       }
     pass
     pass
-
+    self._ca_cert_file_path = CA_CERTS_FILE_PATH
     self.hostname_script = None
     self.hostname_script = None
     ambari_agent_config = ConfigParser.RawConfigParser()
     ambari_agent_config = ConfigParser.RawConfigParser()
     if os.path.exists(AMBARI_AGENT_CONF):
     if os.path.exists(AMBARI_AGENT_CONF):
@@ -193,9 +203,6 @@ class Configuration:
   def get_collector_sleep_interval(self):
   def get_collector_sleep_interval(self):
     return int(self.get("collector", "collector_sleep_interval", 5))
     return int(self.get("collector", "collector_sleep_interval", 5))
 
 
-  def get_server_address(self):
-    return self.get("default", "metrics_server")
-
   def get_hostname_config(self):
   def get_hostname_config(self):
     return self.get("default", "hostname", None)
     return self.get("default", "hostname", None)
 
 
@@ -211,5 +218,18 @@ class Configuration:
   def get_max_queue_size(self):
   def get_max_queue_size(self):
     return int(self.get("collector", "max_queue_size", 5000))
     return int(self.get("collector", "max_queue_size", 5000))
 
 
-  def get_server_https_enabled(self):
-    return "true" == str(self.get("default", "https_enabled")).lower()
+  def is_server_https_enabled(self):
+    return "true" == str(self.get("collector", "https_enabled")).lower()
+
+  def get_server_host(self):
+    return self.get("collector", "host")
+
+  def get_server_port(self):
+    try:
+      return int(self.get("collector", "port"))
+    except:
+      return 6188
+
+  def get_ca_certs(self):
+    return self._ca_cert_file_path
+

+ 0 - 1
ambari-metrics/ambari-metrics-host-monitoring/src/main/python/core/controller.py

@@ -46,7 +46,6 @@ class Controller(threading.Thread):
                                                        hostinfo.get_ip_address())
                                                        hostinfo.get_ip_address())
     self.event_queue = Queue(config.get_max_queue_size())
     self.event_queue = Queue(config.get_max_queue_size())
     self.metric_collector = MetricsCollector(self.event_queue, self.application_metric_map, hostinfo)
     self.metric_collector = MetricsCollector(self.event_queue, self.application_metric_map, hostinfo)
-    self.server_url = config.get_server_address()
     self.sleep_interval = config.get_collector_sleep_interval()
     self.sleep_interval = config.get_collector_sleep_interval()
     self._stop_handler = stop_handler
     self._stop_handler = stop_handler
     self.initialize_events_cache()
     self.initialize_events_cache()

+ 22 - 12
ambari-metrics/ambari-metrics-host-monitoring/src/main/python/core/emitter.py

@@ -20,12 +20,13 @@ limitations under the License.
 
 
 import logging
 import logging
 import threading
 import threading
-import urllib2
+
+from security import CachedHTTPSConnection, CachedHTTPConnection
 
 
 logger = logging.getLogger()
 logger = logging.getLogger()
 
 
 class Emitter(threading.Thread):
 class Emitter(threading.Thread):
-  COLLECTOR_URL = "{0}://{1}/ws/v1/timeline/metrics"
+  AMS_METRICS_POST_URL = "/ws/v1/timeline/metrics/"
   RETRY_SLEEP_INTERVAL = 5
   RETRY_SLEEP_INTERVAL = 5
   MAX_RETRY_COUNT = 3
   MAX_RETRY_COUNT = 3
   """
   """
@@ -39,8 +40,16 @@ class Emitter(threading.Thread):
     self._stop_handler = stop_handler
     self._stop_handler = stop_handler
     self.application_metric_map = application_metric_map
     self.application_metric_map = application_metric_map
     # TODO verify certificate
     # TODO verify certificate
-    protocol = 'https' if config.get_server_https_enabled() else 'http'
-    self.collector_url = self.COLLECTOR_URL.format(protocol, config.get_server_address())
+    timeout = int(self.send_interval - 10)
+    if config.is_server_https_enabled():
+      self.connection = CachedHTTPSConnection(config.get_server_host(),
+                                              config.get_server_port(),
+                                              timeout=timeout,
+                                              ca_certs=config.get_ca_certs())
+    else:
+      self.connection = CachedHTTPConnection(config.get_server_host(),
+                                             config.get_server_port(),
+                                             timeout=timeout)
 
 
   def run(self):
   def run(self):
     logger.info('Running Emitter thread: %s' % threading.currentThread().getName())
     logger.info('Running Emitter thread: %s' % threading.currentThread().getName())
@@ -75,7 +84,7 @@ class Emitter(threading.Thread):
         logger.warn('Error sending metrics to server. %s' % str(e))
         logger.warn('Error sending metrics to server. %s' % str(e))
       pass
       pass
 
 
-      if response and response.getcode() == 200:
+      if response and response.status == 200:
         retry_count = self.MAX_RETRY_COUNT
         retry_count = self.MAX_RETRY_COUNT
       else:
       else:
         logger.warn("Retrying after {0} ...".format(self.RETRY_SLEEP_INTERVAL))
         logger.warn("Retrying after {0} ...".format(self.RETRY_SLEEP_INTERVAL))
@@ -87,14 +96,15 @@ class Emitter(threading.Thread):
     pass
     pass
     # TODO verify certificate
     # TODO verify certificate
   def push_metrics(self, data):
   def push_metrics(self, data):
-    headers = {"Content-Type" : "application/json", "Accept" : "*/*"}
-    logger.info("server: %s" % self.collector_url)
+    headers = {"Content-Type" : "application/json",
+               "Accept" : "*/*",
+               "Connection":" Keep-Alive"}
     logger.debug("message to sent: %s" % data)
     logger.debug("message to sent: %s" % data)
-    req = urllib2.Request(self.collector_url, data, headers)
-    response = urllib2.urlopen(req, timeout=int(self.send_interval - 10))
+    self.connection.request("POST", self.AMS_METRICS_POST_URL, data, headers)
+    response = self.connection.getresponse()
     if response:
     if response:
-      logger.debug("POST response from server: retcode = {0}".format(response.getcode()))
+      logger.debug("POST response from server: retcode = {0}, reason = {1}"
+                   .format(response.status, response.reason))
       logger.debug(str(response.read()))
       logger.debug(str(response.read()))
-    pass
-    return response
 
 
+    return response

+ 98 - 0
ambari-metrics/ambari-metrics-host-monitoring/src/main/python/core/security.py

@@ -0,0 +1,98 @@
+#!/usr/bin/env python
+
+'''
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you 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.
+'''
+
+import logging
+import ssl
+import socket
+import httplib
+
+logger = logging.getLogger()
+
+# TODO merge this with security.py in ambari-agent and move to ambrari commons
+
+class VerifiedHTTPSConnection(httplib.HTTPSConnection):
+  """ Connecting using ssl wrapped sockets """
+
+  def __init__(self, host, port, timeout, ca_certs):
+    httplib.HTTPSConnection.__init__(self, host, port=port, timeout=timeout)
+    self.ca_certs = ca_certs
+
+  def connect(self):
+
+    try:
+      sock = self.create_connection()
+      self.sock = ssl.wrap_socket(sock, cert_reqs=ssl.CERT_REQUIRED,
+                                  ca_certs=self.ca_certs)
+      logger.info('SSL connection established.')
+    except (ssl.SSLError, AttributeError) as ex:
+      logger.info('Insecure connection to https://{0}:{1}/ failed'
+                  .format(self.host, self.port))
+
+  def create_connection(self):
+    if self.sock:
+      self.sock.close()
+    logger.info("SSL Connect being called.. connecting to https://{0}:{1}/"
+                .format(self.host, self.port))
+    sock = socket.create_connection((self.host, self.port), timeout=self.timeout)
+    sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
+    if self._tunnel_host:
+      self.sock = sock
+      self._tunnel()
+
+    return sock
+
+class CachedHTTPConnection:
+  """ Caches a socket and uses a single http connection to the server. """
+
+  def __init__(self, host, port, timeout):
+    self.connected = False
+    self.host = host
+    self.port = port
+    self.timeout = timeout
+
+  def connect(self):
+    if not self.connected:
+      self.httpconn = self.create_connection()
+      self.httpconn.connect()
+      self.connected = True
+
+  def request(self, method, url, body=None, headers={}):
+    self.connect()
+    try:
+      return self.httpconn.request(method, url, body, headers)
+    except Exception as e:
+      self.connected = False
+      raise e
+
+  def getresponse(self):
+    return self.httpconn.getresponse()
+
+  def create_connection(self):
+    return httplib.HTTPConnection(self.host, self.port, self.timeout)
+
+class CachedHTTPSConnection(CachedHTTPConnection):
+  """ Caches an ssl socket and uses a single https connection to the server. """
+
+  def __init__(self, host, port, timeout, ca_certs):
+    self.ca_certs = ca_certs
+    CachedHTTPConnection.__init__(self, host, port, timeout)
+
+  def create_connection(self):
+    return VerifiedHTTPSConnection(self.host, self.port, self.timeout, self.ca_certs)

+ 36 - 29
ambari-metrics/ambari-metrics-host-monitoring/src/test/python/core/TestEmitter.py

@@ -19,34 +19,37 @@ limitations under the License.
 '''
 '''
 
 
 import json
 import json
-import urllib2
-
 import logging
 import logging
-from unittest import TestCase
-from only_for_platform import get_platform, os_distro_value, PLATFORM_WINDOWS
-
-from ambari_commons.os_check import OSCheck
 
 
+from unittest import TestCase
+from only_for_platform import get_platform, PLATFORM_WINDOWS
 from mock.mock import patch, MagicMock
 from mock.mock import patch, MagicMock
+from security import CachedHTTPConnection
+
+if get_platform() != PLATFORM_WINDOWS:
+  os_distro_value = ('Suse','11','Final')
+else:
+  os_distro_value = ('win2012serverr2','6.3','WindowsServer')
 
 
 with patch("platform.linux_distribution", return_value = os_distro_value):
 with patch("platform.linux_distribution", return_value = os_distro_value):
   from ambari_commons import OSCheck
   from ambari_commons import OSCheck
-  from core.application_metric_map import ApplicationMetricMap
-  from core.config_reader import Configuration
-  from core.emitter import Emitter
-  from core.stop_handler import bind_signal_handlers
+  from application_metric_map import ApplicationMetricMap
+  from config_reader import Configuration
+  from emitter import Emitter
+  from stop_handler import bind_signal_handlers
 
 
 logger = logging.getLogger()
 logger = logging.getLogger()
 
 
 class TestEmitter(TestCase):
 class TestEmitter(TestCase):
 
 
   @patch.object(OSCheck, "os_distribution", new = MagicMock(return_value = os_distro_value))
   @patch.object(OSCheck, "os_distribution", new = MagicMock(return_value = os_distro_value))
-  @patch("urllib2.urlopen")
-  def testJavaHomeAvailableCheck(self, url_open_mock):
-    url_open_mock.return_value = MagicMock()
-    url_open_mock.return_value.getcode.return_value = 200
-    self.assertEqual(urllib2.urlopen(None, None).getcode(), 200)
-    url_open_mock.reset_mock()
+  @patch.object(CachedHTTPConnection, "create_connection", new = MagicMock())
+  @patch.object(CachedHTTPConnection, "request")
+  @patch.object(CachedHTTPConnection, "getresponse")
+  def test_submit_metrics(self, getresponse_mock, request_mock):
+    request_mock.return_value = MagicMock()
+    getresponse_mock.return_value = MagicMock()
+    getresponse_mock.return_value.status = 200
 
 
     stop_handler = bind_signal_handlers()
     stop_handler = bind_signal_handlers()
 
 
@@ -56,16 +59,20 @@ class TestEmitter(TestCase):
     application_metric_map.put_metric("APP1", {"metric1":1}, 1)
     application_metric_map.put_metric("APP1", {"metric1":1}, 1)
     emitter = Emitter(config, application_metric_map, stop_handler)
     emitter = Emitter(config, application_metric_map, stop_handler)
     emitter.submit_metrics()
     emitter.submit_metrics()
-    
-    self.assertEqual(url_open_mock.call_count, 1)
-    self.assertUrlData(url_open_mock)
+
+    self.assertEqual(request_mock.call_count, 1)
+    self.assertUrlData(request_mock)
 
 
 
 
   @patch.object(OSCheck, "os_distribution", new = MagicMock(return_value = os_distro_value))
   @patch.object(OSCheck, "os_distribution", new = MagicMock(return_value = os_distro_value))
-  @patch("urllib2.urlopen")
-  def testRetryFetch(self, url_open_mock):
+  @patch.object(CachedHTTPConnection, "create_connection", new = MagicMock())
+  @patch.object(CachedHTTPConnection, "getresponse", new = MagicMock())
+  @patch.object(CachedHTTPConnection, "request")
+  def testRetryFetch(self, request_mock):
     stop_handler = bind_signal_handlers()
     stop_handler = bind_signal_handlers()
 
 
+    request_mock.return_value = MagicMock()
+
     config = Configuration()
     config = Configuration()
     application_metric_map = ApplicationMetricMap("host","10.10.10.10")
     application_metric_map = ApplicationMetricMap("host","10.10.10.10")
     application_metric_map.clear()
     application_metric_map.clear()
@@ -73,15 +80,15 @@ class TestEmitter(TestCase):
     emitter = Emitter(config, application_metric_map, stop_handler)
     emitter = Emitter(config, application_metric_map, stop_handler)
     emitter.RETRY_SLEEP_INTERVAL = .001
     emitter.RETRY_SLEEP_INTERVAL = .001
     emitter.submit_metrics()
     emitter.submit_metrics()
-    
-    self.assertEqual(url_open_mock.call_count, 3)
-    self.assertUrlData(url_open_mock)
-    
-  def assertUrlData(self, url_open_mock):
-    self.assertEqual(len(url_open_mock.call_args), 2)
-    data = url_open_mock.call_args[0][0].data
+
+    self.assertEqual(request_mock.call_count, 3)
+    self.assertUrlData(request_mock)
+
+  def assertUrlData(self, request_mock):
+    self.assertEqual(len(request_mock.call_args), 2)
+    data = request_mock.call_args[0][2]
     self.assertTrue(data is not None)
     self.assertTrue(data is not None)
-    
+
     metrics = json.loads(data)
     metrics = json.loads(data)
     self.assertEqual(len(metrics['metrics']), 1)
     self.assertEqual(len(metrics['metrics']), 1)
     self.assertEqual(metrics['metrics'][0]['metricname'],'metric1')
     self.assertEqual(metrics['metrics'][0]['metricname'],'metric1')

+ 39 - 1
ambari-server/src/main/resources/common-services/AMBARI_METRICS/0.1.0/package/scripts/ams.py

@@ -333,6 +333,9 @@ def ams(name=None):
                 mode=0644
                 mode=0644
       )
       )
 
 
+    if params.metric_collector_https_enabled:
+      export_ca_certs(params.ams_collector_conf_dir)
+
     pass
     pass
 
 
   elif name == 'monitor':
   elif name == 'monitor':
@@ -384,7 +387,9 @@ def ams(name=None):
          content=InlineTemplate(params.ams_env_sh_template)
          content=InlineTemplate(params.ams_env_sh_template)
     )
     )
 
 
-    # TODO
+    if params.metric_collector_https_enabled:
+      export_ca_certs(params.ams_monitor_conf_dir)
+
     pass
     pass
   elif name == 'grafana':
   elif name == 'grafana':
 
 
@@ -415,6 +420,39 @@ def ams(name=None):
          content=InlineTemplate(params.ams_grafana_ini_template)
          content=InlineTemplate(params.ams_grafana_ini_template)
          )
          )
 
 
+    if params.metric_collector_https_enabled:
+      export_ca_certs(params.ams_grafana_conf_dir)
+
     pass
     pass
 
 
+def export_ca_certs(dir_path):
+  # export ca certificates on every restart to handle changed truststore content
+
+  import params
+  import tempfile
+
+  ca_certs_path = os.path.join(dir_path, params.metric_truststore_ca_certs)
+  truststore = params.metric_truststore_path
+
+  tmpdir = tempfile.mkdtemp()
+  truststore_p12 = os.path.join(tmpdir,'truststore.p12')
+
+  if (params.metric_truststore_type.lower() == 'jks'):
+    # Convert truststore from JKS to PKCS12
+    cmd = format("{sudo} {java64_home}/bin/keytool -importkeystore -srckeystore {metric_truststore_path} -destkeystore {truststore_p12} -deststoretype PKCS12 -srcstorepass {metric_truststore_password} -deststorepass {metric_truststore_password}")
+    Execute(cmd,
+    )
+    truststore = truststore_p12
+
+  # Export all CA certificates from the truststore to the conf directory
+  cmd = format("{sudo} openssl pkcs12 -in {truststore} -out {ca_certs_path} -cacerts -nokeys -passin pass:{metric_truststore_password}")
+  Execute(cmd,
+  )
+  Execute(('chown', params.ams_user, ca_certs_path),
+          sudo=True
+  )
+  Execute(format('{sudo} rm -rf {tmpdir}')
+  )
+
+
   pass
   pass

+ 0 - 4
ambari-server/src/main/resources/common-services/AMBARI_METRICS/0.1.0/package/scripts/functions.py

@@ -18,12 +18,8 @@ limitations under the License.
 
 
 """
 """
 
 
-import os
 import re
 import re
 import math
 import math
-import datetime
-
-from resource_management.core.shell import checked_call
 
 
 def calc_xmn_from_xms(heapsize_str, xmn_percent, xmn_max):
 def calc_xmn_from_xms(heapsize_str, xmn_percent, xmn_max):
   """
   """

+ 40 - 18
ambari-server/src/main/resources/common-services/AMBARI_METRICS/0.1.0/package/scripts/metrics_grafana_util.py

@@ -17,14 +17,15 @@ See the License for the specific language governing permissions and
 limitations under the License.
 limitations under the License.
 
 
 """
 """
+import httplib
 from resource_management.core.logger import Logger
 from resource_management.core.logger import Logger
 from resource_management.core.base import Fail
 from resource_management.core.base import Fail
 from resource_management import Template
 from resource_management import Template
 
 
-import httplib
 import time
 import time
 import socket
 import socket
 import json
 import json
+import network
 
 
 def create_ams_datasource():
 def create_ams_datasource():
 
 
@@ -33,16 +34,21 @@ def create_ams_datasource():
   GRAFANA_CONNECT_TIMEOUT = 15
   GRAFANA_CONNECT_TIMEOUT = 15
   GRAFANA_URL = "/api/datasources"
   GRAFANA_URL = "/api/datasources"
   METRICS_GRAFANA_DATASOURCE_NAME = "AMBARI_METRICS"
   METRICS_GRAFANA_DATASOURCE_NAME = "AMBARI_METRICS"
-
+  grafana_https_enabled = params.ams_grafana_protocol.lower() == 'https'
   headers = {"Content-type": "application/json"}
   headers = {"Content-type": "application/json"}
 
 
+  ams_datasource_json = Template('metrics_grafana_datasource.json.j2',
+                                 ams_datasource_name=METRICS_GRAFANA_DATASOURCE_NAME)\
+                                 .get_content()
+
   Logger.info("Checking if AMS Grafana datasource already exists")
   Logger.info("Checking if AMS Grafana datasource already exists")
   Logger.info("Connecting (GET) to %s:%s%s" % (params.hostname,
   Logger.info("Connecting (GET) to %s:%s%s" % (params.hostname,
                                                params.ams_grafana_port,
                                                params.ams_grafana_port,
                                                GRAFANA_URL))
                                                GRAFANA_URL))
-# TODO add https support
-  conn = httplib.HTTPConnection(params.hostname,
-                                int(params.ams_grafana_port))
+
+  conn = network.get_http_connection(params.hostname,
+                                     int(params.ams_grafana_port),
+                                     grafana_https_enabled)
 
 
   conn.request("GET", GRAFANA_URL)
   conn.request("GET", GRAFANA_URL)
   response = conn.getresponse()
   response = conn.getresponse()
@@ -58,7 +64,7 @@ def create_ams_datasource():
         Logger.info("Ambari Metrics Grafana datasource already present. Checking Metrics Collector URL")
         Logger.info("Ambari Metrics Grafana datasource already present. Checking Metrics Collector URL")
         datasource_url = datasources_json[i]["url"]
         datasource_url = datasources_json[i]["url"]
 
 
-        if datasource_url == (params.ams_grafana_protocol + "://"
+        if datasource_url == (params.metric_collector_protocol + "://"
                                 + params.metric_collector_host + ":"
                                 + params.metric_collector_host + ":"
                                 + params.metric_collector_port):
                                 + params.metric_collector_port):
           Logger.info("Metrics Collector URL validation succeeded. Skipping datasource creation")
           Logger.info("Metrics Collector URL validation succeeded. Skipping datasource creation")
@@ -69,10 +75,31 @@ def create_ams_datasource():
           Logger.info("Metrics Collector URL validation failed.")
           Logger.info("Metrics Collector URL validation failed.")
           datasource_id = datasources_json[i]["id"]
           datasource_id = datasources_json[i]["id"]
           Logger.info("Deleting obselete Metrics datasource.")
           Logger.info("Deleting obselete Metrics datasource.")
-          conn = httplib.HTTPConnection(params.hostname, int(params.ams_grafana_port))
-          conn.request("DELETE", GRAFANA_URL + "/" + str(datasource_id))
+          conn = network.get_http_connection(params.hostname,
+                                             int(params.ams_grafana_port),
+                                             grafana_https_enabled)
+          conn.request("PUT", GRAFANA_URL + "/" + str(datasource_id), ams_datasource_json, headers)
           response = conn.getresponse()
           response = conn.getresponse()
-          Logger.info("Http response: %s %s" % (response.status, response.reason))
+          data = response.read()
+          Logger.info("Http data: %s" % data)
+          conn.close()
+
+          if response.status == 200:
+            Logger.info("Ambari Metrics Grafana data source updated.")
+            GRAFANA_CONNECT_TRIES = 0 # No need to create datasource again
+          elif response.status == 500:
+            Logger.info("Ambari Metrics Grafana data source update failed. Not retrying.")
+            raise Fail("Ambari Metrics Grafana data source update failed. PUT request status: %s %s \n%s" %
+                       (response.status, response.reason, data))
+          else:
+            Logger.info("Ambari Metrics Grafana data source update failed.")
+            if i < GRAFANA_CONNECT_TRIES - 1:
+              time.sleep(GRAFANA_CONNECT_TIMEOUT)
+              Logger.info("Next retry in %s seconds."
+                          % (GRAFANA_CONNECT_TIMEOUT))
+            else:
+              raise Fail("Ambari Metrics Grafana data source creation failed. POST request status: %s %s \n%s" %
+                         (response.status, response.reason, data))
 
 
         break
         break
   else:
   else:
@@ -83,19 +110,15 @@ def create_ams_datasource():
 
 
   for i in xrange(0, GRAFANA_CONNECT_TRIES):
   for i in xrange(0, GRAFANA_CONNECT_TRIES):
     try:
     try:
-      ams_datasource_json = Template('metrics_grafana_datasource.json.j2',
-                             ams_datasource_name=METRICS_GRAFANA_DATASOURCE_NAME,
-                             ams_grafana_protocol=params.ams_grafana_protocol,
-                             ams_collector_host=params.metric_collector_host,
-                             ams_collector_port=params.metric_collector_port).get_content()
 
 
       Logger.info("Generated datasource:\n%s" % ams_datasource_json)
       Logger.info("Generated datasource:\n%s" % ams_datasource_json)
 
 
       Logger.info("Connecting (POST) to %s:%s%s" % (params.hostname,
       Logger.info("Connecting (POST) to %s:%s%s" % (params.hostname,
                                                     params.ams_grafana_port,
                                                     params.ams_grafana_port,
                                                     GRAFANA_URL))
                                                     GRAFANA_URL))
-      conn = httplib.HTTPConnection(params.hostname,
-                                    int(params.ams_grafana_port))
+      conn = network.get_http_connection(params.hostname,
+                                         int(params.ams_grafana_port),
+                                         grafana_https_enabled)
       conn.request("POST", GRAFANA_URL, ams_datasource_json, headers)
       conn.request("POST", GRAFANA_URL, ams_datasource_json, headers)
 
 
       response = conn.getresponse()
       response = conn.getresponse()
@@ -129,5 +152,4 @@ def create_ams_datasource():
       else:
       else:
         raise Fail("Ambari Metrics Grafana data source creation failed. POST request status: %s %s \n%s" %
         raise Fail("Ambari Metrics Grafana data source creation failed. POST request status: %s %s \n%s" %
                  (response.status, response.reason, data))
                  (response.status, response.reason, data))
-
-
+  pass

+ 39 - 0
ambari-server/src/main/resources/common-services/AMBARI_METRICS/0.1.0/package/scripts/network.py

@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+"""
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you 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.
+
+"""
+
+import httplib
+import ssl
+
+from resource_management.core.exceptions import Fail
+
+def get_http_connection(host, port, https_enabled=False, ca_certs=None):
+  if https_enabled:
+    if ca_certs:
+      check_ssl_certificate(host, port, ca_certs)
+    return httplib.HTTPSConnection(host, port)
+  else:
+    return httplib.HTTPConnection(host, port)
+
+def check_ssl_certificate(host, port, ca_certs):
+  try:
+    ssl.get_server_certificate((host, port), ssl_version=ssl.PROTOCOL_SSLv23, ca_certs=ca_certs)
+  except (ssl.SSLError) as ssl_error:
+    raise Fail("Failed to verify the SSL certificate for AMS Collector https://{0}:{1} with CA certificate in {2}"
+               .format(host, port, ca_certs))

+ 3 - 1
ambari-server/src/main/resources/common-services/AMBARI_METRICS/0.1.0/package/scripts/params.py

@@ -59,6 +59,7 @@ else:
 metric_truststore_path= default("/configurations/ams-ssl-client/ssl.client.truststore.location", "")
 metric_truststore_path= default("/configurations/ams-ssl-client/ssl.client.truststore.location", "")
 metric_truststore_type= default("/configurations/ams-ssl-client/ssl.client.truststore.type", "")
 metric_truststore_type= default("/configurations/ams-ssl-client/ssl.client.truststore.type", "")
 metric_truststore_password= default("/configurations/ams-ssl-client/ssl.client.truststore.password", "")
 metric_truststore_password= default("/configurations/ams-ssl-client/ssl.client.truststore.password", "")
+metric_truststore_ca_certs='ca.pem'
 
 
 if 'cluster-env' in config['configurations'] and \
 if 'cluster-env' in config['configurations'] and \
     'metrics_collector_vip_host' in config['configurations']['cluster-env']:
     'metrics_collector_vip_host' in config['configurations']['cluster-env']:
@@ -76,9 +77,11 @@ else:
     metric_collector_port = '6188'
     metric_collector_port = '6188'
 
 
 ams_collector_log_dir = config['configurations']['ams-env']['metrics_collector_log_dir']
 ams_collector_log_dir = config['configurations']['ams-env']['metrics_collector_log_dir']
+ams_collector_conf_dir = "/etc/ambari-metrics-collector/conf"
 ams_monitor_log_dir = config['configurations']['ams-env']['metrics_monitor_log_dir']
 ams_monitor_log_dir = config['configurations']['ams-env']['metrics_monitor_log_dir']
 
 
 ams_monitor_dir = "/usr/lib/python2.6/site-packages/resource_monitoring"
 ams_monitor_dir = "/usr/lib/python2.6/site-packages/resource_monitoring"
+ams_monitor_conf_dir = "/etc/ambari-metrics-monitor/conf"
 ams_monitor_pid_dir = status_params.ams_monitor_pid_dir
 ams_monitor_pid_dir = status_params.ams_monitor_pid_dir
 ams_monitor_script = "/usr/sbin/ambari-metrics-monitor"
 ams_monitor_script = "/usr/sbin/ambari-metrics-monitor"
 
 
@@ -177,7 +180,6 @@ else:
     zookeeper_clientPort = '2181'
     zookeeper_clientPort = '2181'
 
 
 ams_checkpoint_dir = config['configurations']['ams-site']['timeline.metrics.aggregator.checkpoint.dir']
 ams_checkpoint_dir = config['configurations']['ams-site']['timeline.metrics.aggregator.checkpoint.dir']
-hbase_pid_dir = status_params.hbase_pid_dir
 _hbase_tmp_dir = config['configurations']['ams-hbase-site']['hbase.tmp.dir']
 _hbase_tmp_dir = config['configurations']['ams-hbase-site']['hbase.tmp.dir']
 hbase_tmp_dir = substitute_vars(_hbase_tmp_dir, config['configurations']['ams-hbase-site'])
 hbase_tmp_dir = substitute_vars(_hbase_tmp_dir, config['configurations']['ams-hbase-site'])
 _zookeeper_data_dir = config['configurations']['ams-hbase-site']['hbase.zookeeper.property.dataDir']
 _zookeeper_data_dir = config['configurations']['ams-hbase-site']['hbase.zookeeper.property.dataDir']

+ 11 - 13
ambari-server/src/main/resources/common-services/AMBARI_METRICS/0.1.0/package/scripts/service_check.py

@@ -27,6 +27,7 @@ from ambari_commons import OSConst
 from ambari_commons.os_family_impl import OsFamilyFuncImpl, OsFamilyImpl
 from ambari_commons.os_family_impl import OsFamilyFuncImpl, OsFamilyImpl
 
 
 import httplib
 import httplib
+import network
 import urllib
 import urllib
 import ambari_simplejson as json # simplejson is much faster comparing to Python 2.6 json module and has the same functions set.
 import ambari_simplejson as json # simplejson is much faster comparing to Python 2.6 json module and has the same functions set.
 import os
 import os
@@ -68,6 +69,8 @@ class AMSServiceCheck(Script):
 
 
     random_value1 = random.random()
     random_value1 = random.random()
     headers = {"Content-type": "application/json"}
     headers = {"Content-type": "application/json"}
+    ca_certs = os.path.join(params.ams_collector_conf_dir,
+                            params.metric_truststore_ca_certs)
 
 
     for i in xrange(0, self.AMS_CONNECT_TRIES):
     for i in xrange(0, self.AMS_CONNECT_TRIES):
       try:
       try:
@@ -79,9 +82,10 @@ class AMSServiceCheck(Script):
         Logger.info("Connecting (POST) to %s:%s%s" % (params.metric_collector_host,
         Logger.info("Connecting (POST) to %s:%s%s" % (params.metric_collector_host,
                                                       params.metric_collector_port,
                                                       params.metric_collector_port,
                                                       self.AMS_METRICS_POST_URL))
                                                       self.AMS_METRICS_POST_URL))
-        conn = self.get_http_connection(params.metric_collector_host,
-                                        int(params.metric_collector_port),
-                                        params.metric_collector_https_enabled)
+        conn = network.get_http_connection(params.metric_collector_host,
+                                           int(params.metric_collector_port),
+                                           params.metric_collector_https_enabled,
+                                           ca_certs)
         conn.request("POST", self.AMS_METRICS_POST_URL, metric_json, headers)
         conn.request("POST", self.AMS_METRICS_POST_URL, metric_json, headers)
 
 
         response = conn.getresponse()
         response = conn.getresponse()
@@ -128,9 +132,10 @@ class AMSServiceCheck(Script):
                                                  params.metric_collector_port,
                                                  params.metric_collector_port,
                                               self.AMS_METRICS_GET_URL % encoded_get_metrics_parameters))
                                               self.AMS_METRICS_GET_URL % encoded_get_metrics_parameters))
 
 
-    conn = self.get_http_connection(params.metric_collector_host,
-                                    int(params.metric_collector_port),
-                                    params.metric_collector_https_enabled)
+    conn = network.get_http_connection(params.metric_collector_host,
+                                       int(params.metric_collector_port),
+                                       params.metric_collector_https_enabled,
+                                       ca_certs)
     conn.request("GET", self.AMS_METRICS_GET_URL % encoded_get_metrics_parameters)
     conn.request("GET", self.AMS_METRICS_GET_URL % encoded_get_metrics_parameters)
     response = conn.getresponse()
     response = conn.getresponse()
     Logger.info("Http response: %s %s" % (response.status, response.reason))
     Logger.info("Http response: %s %s" % (response.status, response.reason))
@@ -163,13 +168,6 @@ class AMSServiceCheck(Script):
 
 
     Logger.info("Ambari Metrics service check is finished.")
     Logger.info("Ambari Metrics service check is finished.")
 
 
-  def get_http_connection(self, host, port, https_enabled=False):
-    if https_enabled:
-      # TODO verify certificate
-      return httplib.HTTPSConnection(host, port)
-    else:
-      return httplib.HTTPConnection(host, port)
-
 if __name__ == "__main__":
 if __name__ == "__main__":
   AMSServiceCheck().execute()
   AMSServiceCheck().execute()
 
 

+ 3 - 2
ambari-server/src/main/resources/common-services/AMBARI_METRICS/0.1.0/package/templates/metric_monitor.ini.j2

@@ -18,11 +18,9 @@
 
 
 [default]
 [default]
 debug_level = INFO
 debug_level = INFO
-metrics_server = {{metric_collector_host}}:{{metric_collector_port}}
 hostname = {{hostname}}
 hostname = {{hostname}}
 enable_time_threshold = false
 enable_time_threshold = false
 enable_value_threshold = false
 enable_value_threshold = false
-https_enabled = {{metric_collector_https_enabled}}
 
 
 [emitter]
 [emitter]
 send_interval = {{metrics_report_interval}}
 send_interval = {{metrics_report_interval}}
@@ -30,3 +28,6 @@ send_interval = {{metrics_report_interval}}
 [collector]
 [collector]
 collector_sleep_interval = 5
 collector_sleep_interval = 5
 max_queue_size = 5000
 max_queue_size = 5000
+host = {{metric_collector_host}}
+port = {{metric_collector_port}}
+https_enabled = {{metric_collector_https_enabled}}

+ 1 - 1
ambari-server/src/main/resources/common-services/AMBARI_METRICS/0.1.0/package/templates/metrics_grafana_datasource.json.j2

@@ -20,7 +20,7 @@
   "name": "{{ams_datasource_name}}",
   "name": "{{ams_datasource_name}}",
   "type": "ambarimetrics",
   "type": "ambarimetrics",
   "access": "proxy",
   "access": "proxy",
-  "url": "{{ams_grafana_protocol}}://{{ams_collector_host}}:{{ams_collector_port}}",
+  "url": "{{metric_collector_protocol}}://{{metric_collector_host}}:{{metric_collector_port}}",
   "password": "",
   "password": "",
   "user": "",
   "user": "",
   "database": "",
   "database": "",

+ 10 - 0
ambari-server/src/test/python/stacks/2.0.6/AMBARI_METRICS/test_metrics_collector.py

@@ -21,6 +21,7 @@ limitations under the License.
 from mock.mock import MagicMock, patch
 from mock.mock import MagicMock, patch
 from stacks.utils.RMFTestCase import *
 from stacks.utils.RMFTestCase import *
 
 
+@patch("tempfile.mkdtemp", new = MagicMock(return_value='/some_tmp_dir'))
 @patch("os.path.exists", new = MagicMock(return_value=True))
 @patch("os.path.exists", new = MagicMock(return_value=True))
 @patch("platform.linux_distribution", new = MagicMock(return_value="Linux"))
 @patch("platform.linux_distribution", new = MagicMock(return_value="Linux"))
 class TestMetricsCollector(RMFTestCase):
 class TestMetricsCollector(RMFTestCase):
@@ -39,6 +40,15 @@ class TestMetricsCollector(RMFTestCase):
     self.assert_hbase_configure('master', distributed=True)
     self.assert_hbase_configure('master', distributed=True)
     self.assert_hbase_configure('regionserver', distributed=True)
     self.assert_hbase_configure('regionserver', distributed=True)
     self.assert_ams('collector', distributed=True)
     self.assert_ams('collector', distributed=True)
+    self.assertResourceCalled('Execute', 'ambari-sudo.sh /usr/jdk64/jdk1.7.0_45/bin/keytool -importkeystore -srckeystore /etc/security/clientKeys/all.jks -destkeystore /some_tmp_dir/truststore.p12 -deststoretype PKCS12 -srcstorepass bigdata -deststorepass bigdata',
+                              )
+    self.assertResourceCalled('Execute', 'ambari-sudo.sh openssl pkcs12 -in /some_tmp_dir/truststore.p12 -out /etc/ambari-metrics-collector/conf/ca.pem -cacerts -nokeys -passin pass:bigdata',
+                              )
+    self.assertResourceCalled('Execute', ('chown', u'ams', '/etc/ambari-metrics-collector/conf/ca.pem'),
+                              sudo=True
+                              )
+    self.assertResourceCalled('Execute', 'ambari-sudo.sh rm -rf /some_tmp_dir',
+                              )
     self.assertResourceCalled('Execute', '/usr/lib/ams-hbase/bin/hbase-daemon.sh --config /etc/ams-hbase/conf stop regionserver',
     self.assertResourceCalled('Execute', '/usr/lib/ams-hbase/bin/hbase-daemon.sh --config /etc/ams-hbase/conf stop regionserver',
                               on_timeout = 'ls /var/run/ambari-metrics-collector//hbase-ams-regionserver.pid >/dev/null 2>&1 && ps `cat /var/run/ambari-metrics-collector//hbase-ams-regionserver.pid` >/dev/null 2>&1 && ambari-sudo.sh -H -E kill -9 `ambari-sudo.sh cat /var/run/ambari-metrics-collector//hbase-ams-regionserver.pid`',
                               on_timeout = 'ls /var/run/ambari-metrics-collector//hbase-ams-regionserver.pid >/dev/null 2>&1 && ps `cat /var/run/ambari-metrics-collector//hbase-ams-regionserver.pid` >/dev/null 2>&1 && ambari-sudo.sh -H -E kill -9 `ambari-sudo.sh cat /var/run/ambari-metrics-collector//hbase-ams-regionserver.pid`',
                               timeout = 30,
                               timeout = 30,

+ 11 - 1
ambari-server/src/test/python/stacks/2.0.6/AMBARI_METRICS/test_metrics_grafana.py

@@ -18,10 +18,11 @@ See the License for the specific language governing permissions and
 limitations under the License.
 limitations under the License.
 '''
 '''
 
 
-from mock.mock import MagicMock, patch, call
+from mock.mock import MagicMock, patch
 from stacks.utils.RMFTestCase import *
 from stacks.utils.RMFTestCase import *
 import os, sys
 import os, sys
 
 
+@patch("tempfile.mkdtemp", new = MagicMock(return_value='/some_tmp_dir'))
 @patch("os.path.exists", new = MagicMock(return_value=True))
 @patch("os.path.exists", new = MagicMock(return_value=True))
 @patch("platform.linux_distribution", new = MagicMock(return_value="Linux"))
 @patch("platform.linux_distribution", new = MagicMock(return_value="Linux"))
 class TestMetricsGrafana(RMFTestCase):
 class TestMetricsGrafana(RMFTestCase):
@@ -47,6 +48,15 @@ class TestMetricsGrafana(RMFTestCase):
                        )
                        )
     self.maxDiff=None
     self.maxDiff=None
     self.assert_configure()
     self.assert_configure()
+    self.assertResourceCalled('Execute', 'ambari-sudo.sh /usr/jdk64/jdk1.7.0_45/bin/keytool -importkeystore -srckeystore /etc/security/clientKeys/all.jks -destkeystore /some_tmp_dir/truststore.p12 -deststoretype PKCS12 -srcstorepass bigdata -deststorepass bigdata',
+                              )
+    self.assertResourceCalled('Execute', 'ambari-sudo.sh openssl pkcs12 -in /some_tmp_dir/truststore.p12 -out /etc/ambari-metrics-grafana/conf/ca.pem -cacerts -nokeys -passin pass:bigdata',
+    )
+    self.assertResourceCalled('Execute', ('chown', u'ams', '/etc/ambari-metrics-grafana/conf/ca.pem'),
+                              sudo = True
+    )
+    self.assertResourceCalled('Execute', 'ambari-sudo.sh rm -rf /some_tmp_dir',
+                              )
     self.assertResourceCalled('Execute', '/usr/sbin/ambari-metrics-grafana stop',
     self.assertResourceCalled('Execute', '/usr/sbin/ambari-metrics-grafana stop',
                               user = 'ams'
                               user = 'ams'
                               )
                               )

+ 10 - 0
ambari-server/src/test/python/stacks/2.0.6/configs/default.json

@@ -820,6 +820,7 @@
             "content": "\n"
             "content": "\n"
         },
         },
         "ams-site": {
         "ams-site": {
+            "timeline.metrics.service.http.policy": "HTTPS_ONLY",
             "timeline.metrics.host.aggregator.minute.ttl": "604800",
             "timeline.metrics.host.aggregator.minute.ttl": "604800",
             "timeline.metrics.cluster.aggregator.daily.checkpointCutOffMultiplier": "1",
             "timeline.metrics.cluster.aggregator.daily.checkpointCutOffMultiplier": "1",
             "timeline.metrics.cluster.aggregator.daily.ttl": "63072000",
             "timeline.metrics.cluster.aggregator.daily.ttl": "63072000",
@@ -868,6 +869,11 @@
         "ams-ssl-server": {
         "ams-ssl-server": {
             "content": "\n"
             "content": "\n"
         },
         },
+        "ams-ssl-client": {
+            "ssl.client.truststore.location": "/etc/security/clientKeys/all.jks",
+            "ssl.client.truststore.type": "jks",
+            "ssl.client.truststore.password": "bigdata"
+        },
         "ams-grafana-ini": {
         "ams-grafana-ini": {
             "content": "\n"
             "content": "\n"
         }
         }
@@ -881,6 +887,7 @@
         "ams-hbase-log4j": {},
         "ams-hbase-log4j": {},
         "ams-site": {},
         "ams-site": {},
         "ams-ssl-server": {},
         "ams-ssl-server": {},
+        "ams-ssl-client": {},
         "sqoop-site": {},
         "sqoop-site": {},
         "yarn-site": {
         "yarn-site": {
         "final": {
         "final": {
@@ -966,6 +973,9 @@
         "ams-ssl-server": {
         "ams-ssl-server": {
             "tag": "version1"
             "tag": "version1"
         },
         },
+        "ams-ssl-client": {
+            "tag": "version1"
+        },
         "ams-hbase-policy": {
         "ams-hbase-policy": {
             "tag": "version1"
             "tag": "version1"
         },
         },

+ 8 - 0
ambari-server/src/test/python/unitTests.py

@@ -108,6 +108,14 @@ def stack_test_executor(base_folder, service, stack, custom_tests, executor_resu
     if os.path.split(root)[-1] in ["scripts", "files"] and service in root:
     if os.path.split(root)[-1] in ["scripts", "files"] and service in root:
       script_folders.add(root)
       script_folders.add(root)
 
 
+  # Add the common-services scripts directories to the PATH
+  base_commserv_folder = os.path.join(server_src_dir, "main", "resources", "common-services")
+  for folder, subFolders, files in os.walk(os.path.join(base_commserv_folder, service)):
+    # folder will return the versions of the services
+    scripts_dir = os.path.join(folder, "package", "scripts")
+    if os.path.exists(scripts_dir):
+      script_folders.add(scripts_dir)
+
   sys.path.extend(script_folders)
   sys.path.extend(script_folders)
 
 
   tests = get_test_files(base_folder, mask = test_mask)
   tests = get_test_files(base_folder, mask = test_mask)

+ 4 - 1
ambari-web/app/router.js

@@ -380,7 +380,10 @@ App.Router = Em.Router.extend({
             '<div class="modal-footer">' +
             '<div class="modal-footer">' +
             '<button class="btn btn-success" {{action onPrimary target="view"}}>' + buttonText + '</button>'+
             '<button class="btn btn-success" {{action onPrimary target="view"}}>' + buttonText + '</button>'+
             '</div>'
             '</div>'
-          )
+          ),
+          onPrimary: function() {
+            this.get('parentView').onPrimary();
+          }
         }),
         }),
 
 
         onPrimary: function () {
         onPrimary: function () {

+ 11 - 4
ambari-web/app/styles/application.less

@@ -2230,6 +2230,11 @@ a:focus {
       }
       }
     }
     }
   }
   }
+  .screensaver {
+    width: 100%;
+    margin-left: 0;
+    margin-right: 0;
+  }
 }
 }
 .chart-container {
 .chart-container {
   cursor: pointer;
   cursor: pointer;
@@ -2708,6 +2713,11 @@ table.graphs {
     .spinner {
     .spinner {
       margin: 55px auto;
       margin: 55px auto;
     }
     }
+    .chart-container {
+      .spinner {
+        margin: 15px auto 35px;
+      }
+    }
     .row-fluid .span2p4 {
     .row-fluid .span2p4 {
       width: 19.60%;
       width: 19.60%;
       *width: 19.60%;
       *width: 19.60%;
@@ -2807,7 +2817,7 @@ table.graphs {
         border: 1px solid silver;
         border: 1px solid silver;
         color: #ffffff;
         color: #ffffff;
         i {
         i {
-          font-size: 4em;
+          font-size: inherit;
         }
         }
       }
       }
     }
     }
@@ -4692,9 +4702,6 @@ ul.inline li {
   height: 157px;
   height: 157px;
   border: 1px solid silver;
   border: 1px solid silver;
   margin: 20px 15px 10px 15px;
   margin: 20px 15px 10px 15px;
-  i {
-    font-size: 4em;
-  }
 }
 }
 
 
 /* TIME RANGE WIDGET END */
 /* TIME RANGE WIDGET END */

+ 5 - 3
ambari-web/app/styles/enhanced_service_dashboard.less

@@ -137,9 +137,6 @@
         width: 90%;
         width: 90%;
         height: 100px;
         height: 100px;
         border: 1px solid #eee;
         border: 1px solid #eee;
-        i {
-          font-size: 4em;
-        }
       }
       }
       .chart-container{
       .chart-container{
         margin: -4px 8px 0 8px;
         margin: -4px 8px 0 8px;
@@ -282,6 +279,11 @@
       overflow-y: scroll;
       overflow-y: scroll;
     }
     }
   }
   }
+  .screensaver {
+    .spinner {
+      display: none;
+    }
+  }
 }
 }
 
 
 .chart-legend .description-line {
 .chart-legend .description-line {

+ 3 - 1
ambari-web/app/templates/common/chart/linear_time.hbs

@@ -16,7 +16,9 @@
 * limitations under the License.
 * limitations under the License.
 }}
 }}
 <div class="chart-wrapper">
 <div class="chart-wrapper">
-  <div {{bindAttr class="view.isReady:hide:show :screensaver :no-borders :chart-container"}}></div>
+  <div {{bindAttr class="view.isReady:hide:show :screensaver :no-borders :chart-container"}}>
+    {{view App.SpinnerView}}
+  </div>
   <div {{bindAttr class="view.isReady::hidden :actions-container"}}>
   <div {{bindAttr class="view.isReady::hidden :actions-container"}}>
     {{view view.timeRangeListView}}
     {{view view.timeRangeListView}}
     <a {{bindAttr class="view.isExportButtonHidden:hidden :corner-icon"}}
     <a {{bindAttr class="view.isExportButtonHidden:hidden :corner-icon"}}

+ 1 - 0
ambari-web/app/templates/main/charts/linear_time.hbs

@@ -18,6 +18,7 @@
 
 
 <div class="screensaver chart-container" {{bindAttr class="view.isReady:hide"}} >
 <div class="screensaver chart-container" {{bindAttr class="view.isReady:hide"}} >
   <div id="{{unbound view.id}}-title" class="chart-title">{{view.title}}</div>
   <div id="{{unbound view.id}}-title" class="chart-title">{{view.title}}</div>
+  {{view App.SpinnerView}}
 </div>
 </div>
 <div id="{{unbound view.id}}-container" class="chart-container hide" {{bindAttr class="view.isReady:show"}} rel="ZoomInTooltip">
 <div id="{{unbound view.id}}-container" class="chart-container hide" {{bindAttr class="view.isReady:show"}} rel="ZoomInTooltip">
   <div id="{{unbound view.id}}-yaxis" class="chart-y-axis" {{action showGraphInPopup target="view"}}></div>
   <div id="{{unbound view.id}}-yaxis" class="chart-y-axis" {{action showGraphInPopup target="view"}}></div>

+ 42 - 6
ambari-web/app/views/common/chart/linear_time.js

@@ -574,7 +574,12 @@ App.ChartLinearTimeView = Ember.View.extend(App.ExportMetricsMixin, {
     //if graph opened as modal popup
     //if graph opened as modal popup
     var popup_path = $(this.get('_popupSelector'));
     var popup_path = $(this.get('_popupSelector'));
     var graph_container = $(this.get('_containerSelector'));
     var graph_container = $(this.get('_containerSelector'));
+    var seconds = this.get('parentView.graphSeconds');
     var container;
     var container;
+    if (!Em.isNone(seconds)) {
+      this.set('timeUnitSeconds', seconds);
+      this.set('parentView.graphSeconds', null);
+    }
     if (popup_path.length) {
     if (popup_path.length) {
       popup_path.children().each(function () {
       popup_path.children().each(function () {
         $(this).children().remove();
         $(this).children().remove();
@@ -917,6 +922,14 @@ App.ChartLinearTimeView = Ember.View.extend(App.ExportMetricsMixin, {
     }
     }
 
 
     this.set('isPopup', true);
     this.set('isPopup', true);
+    if (this.get('inWidget') && !this.get('parentView.isClusterMetricsWidget')) {
+      this.setProperties({
+        currentTimeIndex: this.get('parentView.timeIndex'),
+        customStartTime: this.get('parentView.startTime'),
+        customEndTime: this.get('parentView.endTime'),
+        customDurationFormatted: this.get('parentView.durationFormatted')
+      });
+    }
     var self = this;
     var self = this;
 
 
     App.ModalPopup.show({
     App.ModalPopup.show({
@@ -1065,7 +1078,10 @@ App.ChartLinearTimeView = Ember.View.extend(App.ExportMetricsMixin, {
       reloadGraphByTime: function (index) {
       reloadGraphByTime: function (index) {
         this.set('childViews.firstObject.currentTimeRangeIndex', index);
         this.set('childViews.firstObject.currentTimeRangeIndex', index);
         this.set('currentTimeIndex', index);
         this.set('currentTimeIndex', index);
-        self.set('currentTimeIndex', index);
+        self.setProperties({
+          currentTimeIndex: index,
+          isPopupReady: false
+        });
         App.ajax.abortRequests(this.get('graph.runningPopupRequests'));
         App.ajax.abortRequests(this.get('graph.runningPopupRequests'));
       },
       },
       currentTimeIndex: self.get('currentTimeIndex'),
       currentTimeIndex: self.get('currentTimeIndex'),
@@ -1117,18 +1133,38 @@ App.ChartLinearTimeView = Ember.View.extend(App.ExportMetricsMixin, {
     });
     });
     if (index !== 8 || targetView.get('customStartTime') && targetView.get('customEndTime')) {
     if (index !== 8 || targetView.get('customStartTime') && targetView.get('customEndTime')) {
       App.ajax.abortRequests(this.get('runningRequests'));
       App.ajax.abortRequests(this.get('runningRequests'));
+      if (this.get('inWidget') && !this.get('parentView.isClusterMetricsWidget')) {
+        this.set('parentView.isLoaded', false);
+      } else {
+        this.set('isReady', false);
+      }
     }
     }
   }.observes('parentView.parentView.currentTimeRangeIndex', 'parentView.currentTimeRangeIndex', 'parentView.parentView.customStartTime', 'parentView.customStartTime', 'parentView.parentView.customEndTime', 'parentView.customEndTime'),
   }.observes('parentView.parentView.currentTimeRangeIndex', 'parentView.currentTimeRangeIndex', 'parentView.parentView.customStartTime', 'parentView.customStartTime', 'parentView.parentView.customEndTime', 'parentView.customEndTime'),
   timeUnitSeconds: 3600,
   timeUnitSeconds: 3600,
   timeUnitSecondsSetter: function () {
   timeUnitSecondsSetter: function () {
-      var index = this.get('currentTimeIndex');
+    var index = this.get('currentTimeIndex'),
+      startTime = this.get('customStartTime'),
+      endTime = this.get('customEndTime'),
+      durationFormatted = this.get('customDurationFormatted'),
+      seconds;
     if (index !== 8) {
     if (index !== 8) {
       // Preset time range is specified by user
       // Preset time range is specified by user
-      var seconds = this.get('timeStates').objectAt(this.get('currentTimeIndex')).seconds;
-      this.set('timeUnitSeconds', seconds);
-    } else if (!Em.isNone(this.get('customStartTime')) && !Em.isNone(this.get('customEndTime'))) {
+      seconds = this.get('timeStates').objectAt(this.get('currentTimeIndex')).seconds;
+    } else if (!Em.isNone(startTime) && !Em.isNone(endTime)) {
       // Custom start and end time is specified by user
       // Custom start and end time is specified by user
-      this.set('timeUnitSeconds', (this.get('customEndTime') - this.get('customStartTime')) / 1000);
+      seconds = (endTime - startTime) / 1000;
+    }
+    if (!Em.isNone(seconds)) {
+      this.set('timeUnitSeconds', seconds);
+      if (this.get('inWidget') && !this.get('parentView.isClusterMetricsWidget')) {
+        this.get('parentView').setProperties({
+          graphSeconds: seconds,
+          timeIndex: index,
+          startTime: startTime,
+          endTime: endTime,
+          durationFormatted: durationFormatted
+        });
+      }
     }
     }
   }.observes('currentTimeIndex', 'customStartTime', 'customEndTime')
   }.observes('currentTimeIndex', 'customStartTime', 'customEndTime')
 
 

+ 30 - 0
ambari-web/app/views/common/widget/graph_widget_view.js

@@ -77,6 +77,36 @@ App.GraphWidgetView = Em.View.extend(App.WidgetMixin, App.ExportMetricsMixin, {
    */
    */
   data: [],
   data: [],
 
 
+  /**
+   * time range index for graph
+   * @type {number}
+   */
+  timeIndex: 0,
+
+  /**
+   * custom start time for graph
+   * @type {number|null}
+   */
+  startTime: null,
+
+  /**
+   * custom end time for graph
+   * @type {number|null}
+   */
+  endTime: null,
+
+  /**
+   * graph time range duration in seconds
+   * @type {number|null}
+   */
+  graphSeconds: null,
+
+  /**
+   * time range duration as string
+   * @type {string|null}
+   */
+  durationFormatted: null,
+
   exportTargetView: Em.computed.alias('childViews.lastObject'),
   exportTargetView: Em.computed.alias('childViews.lastObject'),
 
 
   drawWidget: function () {
   drawWidget: function () {

+ 2 - 0
ambari-web/app/views/main/dashboard/widgets/cluster_metrics_widget.js

@@ -22,6 +22,8 @@ App.ClusterMetricsDashboardWidgetView = App.DashboardWidgetView.extend(App.Expor
 
 
   templateName: require('templates/main/dashboard/widgets/cluster_metrics'),
   templateName: require('templates/main/dashboard/widgets/cluster_metrics'),
 
 
+  isClusterMetricsWidget: true,
+
   exportTargetView: Em.computed.alias('childViews.lastObject'),
   exportTargetView: Em.computed.alias('childViews.lastObject'),
 
 
   didInsertElement: function () {
   didInsertElement: function () {

+ 71 - 19
ambari-web/test/views/common/chart/linear_time_test.js

@@ -274,31 +274,56 @@ describe('App.ChartLinearTimeView', function () {
 
 
     var view,
     var view,
       cases = [
       cases = [
-      {
-        parent: 1,
-        child: 2,
-        result: 2,
-        title: 'child and parent have currentTimeRangeIndex'
-      },
-      {
-        parent: undefined,
-        child: 2,
-        result: 2,
-        title: 'only child has currentTimeRangeIndex'
-      },
-      {
-        parent: 1,
-        child: undefined,
-        result: 1,
-        title: 'only parent has currentTimeRangeIndex'
-      }
-    ];
+        {
+          parent: 1,
+          child: 2,
+          result: 2,
+          title: 'child and parent have currentTimeRangeIndex'
+        },
+        {
+          parent: undefined,
+          child: 2,
+          result: 2,
+          title: 'only child has currentTimeRangeIndex'
+        },
+        {
+          parent: 1,
+          child: undefined,
+          result: 1,
+          title: 'only parent has currentTimeRangeIndex'
+        }
+      ],
+      isReadyCases = [
+        {
+          inWidget: true,
+          isClusterMetricsWidget: true,
+          parentViewIsLoaded: true,
+          isReady: false,
+          title: 'cluster metrics widget'
+        },
+        {
+          inWidget: true,
+          isClusterMetricsWidget: false,
+          parentViewIsLoaded: false,
+          isReady: true,
+          title: 'enhanced service widget'
+        },
+        {
+          inWidget: false,
+          isClusterMetricsWidget: false,
+          parentViewIsLoaded: true,
+          isReady: false,
+          title: 'non-widget graph'
+        }
+      ];
 
 
     beforeEach(function () {
     beforeEach(function () {
       view = App.ChartLinearTimeView.create({
       view = App.ChartLinearTimeView.create({
+        isReady: true,
         controller: {},
         controller: {},
         parentView: Em.Object.create({
         parentView: Em.Object.create({
           currentTimeRangeIndex: 1,
           currentTimeRangeIndex: 1,
+          isLoaded: true,
           parentView: Em.Object.create({
           parentView: Em.Object.create({
             currentTimeRangeIndex: 2
             currentTimeRangeIndex: 2
           })
           })
@@ -316,6 +341,33 @@ describe('App.ChartLinearTimeView', function () {
       });
       });
     });
     });
 
 
+    isReadyCases.forEach(function (item) {
+
+      describe(item.title, function () {
+
+        beforeEach(function () {
+          sinon.stub(App.ajax, 'abortRequests', Em.K);
+          view.set('inWidget', item.inWidget);
+          view.set('parentView.isClusterMetricsWidget', item.isClusterMetricsWidget);
+          view.propertyDidChange('parentView.currentTimeRangeIndex');
+        });
+
+        afterEach(function () {
+          App.ajax.abortRequests.restore();
+        });
+
+        it('parentView.isLoaded', function () {
+          expect(view.get('parentView.isLoaded')).to.eql(item.parentViewIsLoaded);
+        });
+
+        it('isReady', function () {
+          expect(view.get('isReady')).to.eql(item.isReady);
+        });
+
+      });
+
+    });
+
   });
   });
 
 
   describe('#loadDataSuccessCallback', function () {
   describe('#loadDataSuccessCallback', function () {