You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by wi...@apache.org on 2014/12/01 14:07:03 UTC
git commit: updated refs/heads/reporter to 026efd3
Repository: cloudstack
Updated Branches:
refs/heads/reporter [created] 026efd344
Proposal of a Usage Reporter / call-home functionality for CloudStack
With this commit the Management Server will be default generate a anonymous Usage
report every 7 (seven) days and submit this information back to the Apache CloudStack project.
These anonymous reports do NOT contain any information about Instance names, subnets, etc. It only
contains numbers about how CloudStack is being used.
This information is vital for the project to gain more insight in how CloudStack is being used.
Users can turn the reporting off by setting usage.report.interval to 0 (zero)
Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/026efd34
Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/026efd34
Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/026efd34
Branch: refs/heads/reporter
Commit: 026efd344452cef806f2475009bf690f5c2cbf9a
Parents: 449d123
Author: Wido den Hollander <wi...@widodh.nl>
Authored: Thu Nov 20 14:43:33 2014 +0100
Committer: Wido den Hollander <wi...@widodh.nl>
Committed: Mon Dec 1 14:00:10 2014 +0100
----------------------------------------------------------------------
reporter/README.md | 18 +
reporter/usage-report-collector.py | 64 +++
server/pom.xml | 10 +
.../spring-server-core-managers-context.xml | 2 +
server/src/com/cloud/configuration/Config.java | 5 +-
.../apache/cloudstack/report/UsageReporter.java | 403 +++++++++++++++++++
setup/db/db/schema-450to460.sql | 4 +-
7 files changed, 504 insertions(+), 2 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/026efd34/reporter/README.md
----------------------------------------------------------------------
diff --git a/reporter/README.md b/reporter/README.md
new file mode 100644
index 0000000..6453fa4
--- /dev/null
+++ b/reporter/README.md
@@ -0,0 +1,18 @@
+# CloudStack Usage Report
+
+This directory contains the CloudStack reporter webservice used by the Apache CloudStack project
+to gather anonymous statistical information about CloudStack deployments.
+
+Since version <FIX ME!!> the management server sends out a anonymized Usage Report out to the
+project every 7 days.
+
+This information is used to gain information about how CloudStack is being used.
+
+Turning this Usage Reporting functionality off can be done in the Global Settings by setting
+'usage.report.interval' to 0.
+
+# The webservice
+The Python Flask application in this directory is the webservice running on https://reports.cloudstack.apache.org/ (FIX ME?)
+and stores all the incoming information in a ElasticSearch database.
+
+Since Apache CloudStack is Open Source we show not only how we generate the report, but also how we process it.
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/026efd34/reporter/usage-report-collector.py
----------------------------------------------------------------------
diff --git a/reporter/usage-report-collector.py b/reporter/usage-report-collector.py
new file mode 100755
index 0000000..500a4d2
--- /dev/null
+++ b/reporter/usage-report-collector.py
@@ -0,0 +1,64 @@
+#!/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.
+
+from flask import abort, Flask, request, Response
+from elasticsearch import Elasticsearch
+import json
+import time
+
+def json_response(response):
+ return json.dumps(response, indent=2) + "\n", 200, {'Content-Type': 'application/json; charset=utf-8'}
+
+def generate_app(config=None):
+ app = Flask(__name__)
+
+ @app.route('/report/<unique_id>', methods=['POST'])
+ def report(unique_id):
+ # We expect JSON data, so if the Content-Type doesn't match JSON data we throw an error
+ if 'Content-Type' in request.headers:
+ if request.headers['Content-Type'] != 'application/json':
+ abort(417, "No or incorrect Content-Type header was supplied")
+
+ index = "cloudstack-%s" % time.strftime("%Y.%m.%d", time.gmtime())
+ timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
+
+ es = Elasticsearch()
+ es.indices.create(index=index, ignore=400)
+
+ report = json.loads(request.data)
+ report["unique_id"] = unique_id
+ report["timestamp"] = timestamp
+
+ es.index(index=index, doc_type="usage-report", body=json.dumps(report), timestamp=timestamp, refresh=True)
+
+ response = {}
+ return json_response(response)
+
+ return app
+
+
+app = generate_app()
+
+# Only run the App if this script is invoked from a Shell
+if __name__ == '__main__':
+ app.debug = True
+ app.run(host='0.0.0.0', port=8088)
+
+# Otherwise provide a variable called 'application' for mod_wsgi
+else:
+ application = app
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/026efd34/server/pom.xml
----------------------------------------------------------------------
diff --git a/server/pom.xml b/server/pom.xml
index c293aa1..f2a2a0e 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -138,6 +138,16 @@
<artifactId>opensaml</artifactId>
<version>${cs.opensaml.version}</version>
</dependency>
+ <dependency>
+ <groupId>com.google.code.gson</groupId>
+ <artifactId>gson</artifactId>
+ <version>${cs.gson.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>${cs.guava.version}</version>
+ </dependency>
</dependencies>
<build>
<testResources>
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/026efd34/server/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml
----------------------------------------------------------------------
diff --git a/server/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml b/server/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml
index 6d8f32e..faa5286 100644
--- a/server/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml
+++ b/server/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml
@@ -222,6 +222,8 @@
<bean id="statsCollector" class="com.cloud.server.StatsCollector" />
+ <bean id="usageReporter" class="org.apache.cloudstack.report.UsageReporter" />
+
<bean id="storagePoolAutomationImpl" class="com.cloud.storage.StoragePoolAutomationImpl" />
<bean id="domainManagerImpl" class="com.cloud.user.DomainManagerImpl" />
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/026efd34/server/src/com/cloud/configuration/Config.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/configuration/Config.java b/server/src/com/cloud/configuration/Config.java
index 281d99c..d395108 100644
--- a/server/src/com/cloud/configuration/Config.java
+++ b/server/src/com/cloud/configuration/Config.java
@@ -2052,7 +2052,10 @@ public enum Config {
PublishAsynJobEvent("Advanced", ManagementServer.class, Boolean.class, "publish.async.job.events", "true", "enable or disable publishing of usage events on the event bus", null),
// StatsCollector
- StatsOutPutGraphiteHost("Advanced", ManagementServer.class, String.class, "stats.output.uri", "", "URI to additionally send StatsCollector statistics to", null);
+ StatsOutPutGraphiteHost("Advanced", ManagementServer.class, String.class, "stats.output.uri", "", "URI to additionally send StatsCollector statistics to", null),
+
+ // Usage Reporting service
+ UsageReportInterval("Advanced", ManagementServer.class, Integer.class, "usage.report.interval", "7", "Interval (days) between sending anonymous Usage Reports back to the CloudStack project", null);
private final String _category;
private final Class<?> _componentClass;
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/026efd34/server/src/org/apache/cloudstack/report/UsageReporter.java
----------------------------------------------------------------------
diff --git a/server/src/org/apache/cloudstack/report/UsageReporter.java b/server/src/org/apache/cloudstack/report/UsageReporter.java
new file mode 100644
index 0000000..e7891e9
--- /dev/null
+++ b/server/src/org/apache/cloudstack/report/UsageReporter.java
@@ -0,0 +1,403 @@
+// 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.
+package org.apache.cloudstack.report;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.net.URL;
+import java.net.HttpURLConnection;
+import java.net.SocketTimeoutException;
+import java.net.MalformedURLException;
+import java.net.ProtocolException;
+import java.io.OutputStreamWriter;
+import java.io.IOException;
+
+import javax.inject.Inject;
+
+import org.apache.log4j.Logger;
+import org.springframework.stereotype.Component;
+
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.apache.cloudstack.managed.context.ManagedContextRunnable;
+
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+
+import org.apache.commons.codec.digest.DigestUtils;
+
+import com.cloud.host.HostVO;
+import com.cloud.host.dao.HostDao;
+import com.cloud.dc.ClusterVO;
+import com.cloud.dc.dao.ClusterDao;
+import com.cloud.dc.DataCenterVO;
+import com.cloud.dc.dao.DataCenterDao;
+import com.cloud.vm.UserVmVO;
+import com.cloud.vm.dao.UserVmDao;
+import com.cloud.vm.VMInstanceVO;
+import com.cloud.vm.dao.VMInstanceDao;
+import com.cloud.utils.db.SearchCriteria;
+import com.cloud.utils.NumbersUtil;
+import com.cloud.utils.component.ManagerBase;
+import com.cloud.utils.component.ComponentMethodInterceptable;
+import com.cloud.utils.concurrency.NamedThreadFactory;
+import com.cloud.utils.db.DB;
+import com.cloud.utils.db.TransactionLegacy;
+
+import com.google.gson.Gson;
+import com.google.common.util.concurrent.AtomicLongMap;
+
+@Component
+public class UsageReporter extends ManagerBase implements ComponentMethodInterceptable {
+ public static final Logger s_logger = Logger.getLogger(UsageReporter.class.getName());
+
+ /* !FIX ME! This should point to a Apache Infra host with SSL! */
+ private String reportHost = "http://cs-report.widodh.nl:8088/report";
+
+ private String uniqueID = null;
+
+ private static UsageReporter s_instance = null;
+
+ private ScheduledExecutorService _executor = null;
+
+ @Inject
+ private ConfigurationDao _configDao;
+ @Inject
+ private HostDao _hostDao;
+ @Inject
+ private ClusterDao _clusterDao;
+ @Inject
+ private PrimaryDataStoreDao _storagePoolDao;
+ @Inject
+ private DataCenterDao _dataCenterDao;
+ @Inject
+ private UserVmDao _userVmDao;
+ @Inject
+ private VMInstanceDao _vmInstance;
+
+ int usageReportInterval = -1;
+
+ public static UsageReporter getInstance() {
+ return s_instance;
+ }
+
+ public static UsageReporter getInstance(Map<String, String> configs) {
+ s_instance.init(configs);
+ return s_instance;
+ }
+
+ public UsageReporter() {
+ s_instance = this;
+ }
+
+ @Override
+ public boolean start() {
+ init(_configDao.getConfiguration());
+ return true;
+ }
+
+ private void init(Map<String, String> configs) {
+ _executor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("UsageReporter"));
+
+ usageReportInterval = NumbersUtil.parseInt(configs.get("usage.report.interval"), 7);
+
+ if (usageReportInterval > 0) {
+ _executor.scheduleWithFixedDelay(new UsageCollector(), 7, usageReportInterval, TimeUnit.DAYS);
+ }
+
+ uniqueID = getUniqueId();
+ }
+
+ private void sendReport(String reportUri, String uniqueID, Map<String, Map> reportMap) {
+ Gson gson = new Gson();
+ String report = gson.toJson(reportMap);
+
+ int http_timeout = 15000;
+
+ try {
+ s_logger.info("Usage Report will be send to: " + reportUri);
+ s_logger.debug("REPORT: " + report);
+
+ URL url = new URL(reportUri + "/" + uniqueID);
+
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ conn.setConnectTimeout(http_timeout);
+ conn.setReadTimeout(http_timeout);
+ conn.setRequestMethod("POST");
+ conn.setDoOutput(true);
+ conn.setRequestProperty("Content-Type", "application/json");
+ conn.setRequestProperty("Accept", "application/json");
+
+ OutputStreamWriter osw = new OutputStreamWriter(conn.getOutputStream());
+ osw.write(report);
+ osw.flush();
+ osw.close();
+
+ int resp_code = conn.getResponseCode();
+
+ if (resp_code == HttpURLConnection.HTTP_OK){
+ s_logger.info("Usage Report succesfully send to: " + reportUri);
+ } else {
+ s_logger.warn("Failed to send Usage Report: " + conn.getResponseMessage());
+ }
+
+ } catch (SocketTimeoutException e) {
+ s_logger.warn("Sending Usage Report to " + reportUri + " timed out: " + e.getMessage());
+ } catch (MalformedURLException e) {
+ s_logger.warn(reportUri + " is a invalid URL for sending Usage Report to: "+ e.getMessage());
+ } catch (ProtocolException e) {
+ s_logger.warn("Sending Usage Report failed due to a invalid protocol: " + e.getMessage());
+ } catch (IOException e) {
+ s_logger.warn("Failed to write Usage Report due to a IOException: " + e.getMessage());
+ }
+ }
+
+ @DB
+ private String getUniqueId() {
+ String unique = null;
+ Connection conn = null;
+
+ try {
+ conn = TransactionLegacy.getStandaloneConnection();
+
+ PreparedStatement pstmt = conn.prepareStatement("SELECT version,updated FROM version ORDER BY id ASC LIMIT 1");
+ ResultSet rs = pstmt.executeQuery();
+ if (rs.next()) {
+ unique = DigestUtils.sha256Hex(rs.getString(1) + rs.getString(2));
+ } else {
+ s_logger.debug("No rows found in the version table. Unable to obtain unique ID for this environment");
+ }
+
+ rs.close();
+ } catch (SQLException e) {
+ s_logger.debug("Unable to get the unique ID of this environment: " + e.getMessage());
+ } finally {
+ try {
+ conn.close();
+ } catch (SQLException e) {
+ }
+ }
+
+ s_logger.debug("Usage Report Unique ID is: " + unique);
+
+ return unique;
+ }
+
+ private Map<String, AtomicLongMap> getHostReport() {
+ Map<String, AtomicLongMap> hostMap = new HashMap<String, AtomicLongMap>();
+ AtomicLongMap<Object> host_types = AtomicLongMap.create();
+ AtomicLongMap<Object> host_hypervisor_type = AtomicLongMap.create();
+ AtomicLongMap<Object> host_version = AtomicLongMap.create();
+
+ SearchCriteria<HostVO> host_sc = _hostDao.createSearchCriteria();
+ List<HostVO> hosts = _hostDao.search(host_sc, null);
+ for (HostVO host : hosts) {
+ host_types.getAndIncrement(host.getType());
+ if (host.getHypervisorType() != null) {
+ host_hypervisor_type.getAndIncrement(host.getHypervisorType());
+ }
+
+ host_version.getAndIncrement(host.getVersion());
+ }
+
+ hostMap.put("version", host_version);
+ hostMap.put("hypervisor_type", host_hypervisor_type);
+ hostMap.put("type", host_types);
+
+ return hostMap;
+ }
+
+ private Map<String, AtomicLongMap> getClusterReport() {
+ Map<String, AtomicLongMap> clusterMap = new HashMap<String, AtomicLongMap>();
+ AtomicLongMap<Object> cluster_hypervisor_type = AtomicLongMap.create();
+ AtomicLongMap<Object> cluster_types = AtomicLongMap.create();
+
+ SearchCriteria<ClusterVO> cluster_sc = _clusterDao.createSearchCriteria();
+ List<ClusterVO> clusters = _clusterDao.search(cluster_sc, null);
+ for (ClusterVO cluster : clusters) {
+ if (cluster.getClusterType() != null) {
+ cluster_types.getAndIncrement(cluster.getClusterType());
+ }
+
+ if (cluster.getHypervisorType() != null) {
+ cluster_hypervisor_type.getAndIncrement(cluster.getHypervisorType());
+ }
+ }
+
+ clusterMap.put("hypervisor_type", cluster_hypervisor_type);
+ clusterMap.put("type", cluster_types);
+
+ return clusterMap;
+ }
+
+ private Map<String, AtomicLongMap> getStoragePoolReport() {
+ Map<String, AtomicLongMap> storagePoolMap = new HashMap<String, AtomicLongMap>();
+ AtomicLongMap<Object> storage_pool_types = AtomicLongMap.create();
+ AtomicLongMap<Object> storage_pool_provider = AtomicLongMap.create();
+ AtomicLongMap<Object> storage_pool_scope = AtomicLongMap.create();
+
+ List<StoragePoolVO> storagePools = _storagePoolDao.listAll();
+ for (StoragePoolVO pool : storagePools) {
+ if (pool.getPoolType() != null) {
+ storage_pool_types.getAndIncrement(pool.getPoolType());
+ }
+
+ if (pool.getStorageProviderName() != null) {
+ storage_pool_provider.getAndIncrement(pool.getStorageProviderName());
+ }
+
+ if (pool.getScope() != null) {
+ storage_pool_scope.getAndIncrement(pool.getScope());
+ }
+ }
+
+ storagePoolMap.put("type", storage_pool_types);
+ storagePoolMap.put("provider", storage_pool_provider);
+ storagePoolMap.put("scope", storage_pool_scope);
+
+ return storagePoolMap;
+ }
+
+ private Map<String, AtomicLongMap> getDataCenterReport() {
+ Map<String, AtomicLongMap> datacenterMap = new HashMap<String, AtomicLongMap>();
+ AtomicLongMap<Object> network_type = AtomicLongMap.create();
+ AtomicLongMap<Object> dns_provider = AtomicLongMap.create();
+ AtomicLongMap<Object> dhcp_provider = AtomicLongMap.create();
+ AtomicLongMap<Object> lb_provider = AtomicLongMap.create();
+ AtomicLongMap<Object> firewall_provider = AtomicLongMap.create();
+ AtomicLongMap<Object> gateway_provider = AtomicLongMap.create();
+ AtomicLongMap<Object> userdata_provider = AtomicLongMap.create();
+ AtomicLongMap<Object> vpn_provider = AtomicLongMap.create();
+
+ List<DataCenterVO> datacenters = _dataCenterDao.listAllZones();
+ for (DataCenterVO datacenter : datacenters) {
+ if (datacenter.getNetworkType() != null) {
+ network_type.getAndIncrement(datacenter.getNetworkType());
+ }
+
+ if (datacenter.getDnsProvider() != null) {
+ dns_provider.getAndIncrement(datacenter.getDnsProvider());
+ }
+
+ if (datacenter.getDhcpProvider() != null) {
+ dhcp_provider.getAndIncrement(datacenter.getDhcpProvider());
+ }
+
+ if (datacenter.getLoadBalancerProvider() != null) {
+ lb_provider.getAndIncrement(datacenter.getLoadBalancerProvider());
+ }
+
+ if (datacenter.getFirewallProvider() != null) {
+ firewall_provider.getAndIncrement(datacenter.getFirewallProvider());
+ }
+
+ if (datacenter.getGatewayProvider() != null) {
+ gateway_provider.getAndIncrement(datacenter.getGatewayProvider());
+ }
+
+ if (datacenter.getUserDataProvider() != null) {
+ userdata_provider.getAndIncrement(datacenter.getUserDataProvider());
+ }
+
+ if (datacenter.getVpnProvider() != null) {
+ vpn_provider.getAndIncrement(datacenter.getVpnProvider());
+ }
+ }
+
+ datacenterMap.put("network_type", network_type);
+ datacenterMap.put("dns_provider", dns_provider);
+ datacenterMap.put("dhcp_provider", dhcp_provider);
+ datacenterMap.put("lb_provider", lb_provider);
+ datacenterMap.put("firewall_provider", firewall_provider);
+ datacenterMap.put("gateway_provider", gateway_provider);
+ datacenterMap.put("userdata_provider", userdata_provider);
+ datacenterMap.put("vpn_provider", vpn_provider);
+
+ return datacenterMap;
+ }
+
+ private Map<String, AtomicLongMap> getInstanceReport() {
+
+ Map<String, AtomicLongMap> instanceMap = new HashMap<String, AtomicLongMap>();
+ AtomicLongMap<Object> hypervisor_type = AtomicLongMap.create();
+ AtomicLongMap<Object> instance_state = AtomicLongMap.create();
+ AtomicLongMap<Object> instance_type = AtomicLongMap.create();
+ AtomicLongMap<Object> ha_enabled = AtomicLongMap.create();
+ AtomicLongMap<Object> dynamically_scalable = AtomicLongMap.create();
+
+ SearchCriteria<HostVO> host_sc = _hostDao.createSearchCriteria();
+ List<HostVO> hosts = _hostDao.search(host_sc, null);
+ for (HostVO host : hosts) {
+ List<UserVmVO> vms = _userVmDao.listUpByHostId(host.getId());
+ for (UserVmVO vm : vms) {
+ VMInstanceVO vmVO = _vmInstance.findById(vm.getId());
+
+ if (vmVO.getHypervisorType() != null) {
+ hypervisor_type.getAndIncrement(vmVO.getHypervisorType());
+ }
+
+ if (vmVO.getState() != null) {
+ instance_state.getAndIncrement(vmVO.getState());
+ }
+
+ if (vmVO.getType() != null) {
+ instance_type.getAndIncrement(vmVO.getType());
+ }
+
+ ha_enabled.getAndIncrement(vmVO.isHaEnabled());
+ dynamically_scalable.getAndIncrement(vmVO.isDynamicallyScalable());
+ }
+ }
+
+ instanceMap.put("hypervisor_type", hypervisor_type);
+ instanceMap.put("state", instance_state);
+ instanceMap.put("type", instance_type);
+ instanceMap.put("ha_enabled", ha_enabled);
+ instanceMap.put("dynamically_scalable", dynamically_scalable);
+
+ return instanceMap;
+ }
+
+ class UsageCollector extends ManagedContextRunnable {
+ @Override
+ protected void runInContext() {
+ try {
+ s_logger.warn("UsageReporter is running...");
+
+ Map<String, Map> reportMap = new HashMap<String, Map>();
+
+ reportMap.put("hosts", getHostReport());
+ reportMap.put("clusters", getClusterReport());
+ reportMap.put("primaryStorage", getStoragePoolReport());
+ reportMap.put("zones", getDataCenterReport());
+ reportMap.put("instances", getInstanceReport());
+
+ sendReport(reportHost, uniqueID, reportMap);
+
+ } catch (Exception e) {
+ s_logger.warn("Failed to compile Usage Report: " + e.getMessage());
+ }
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/026efd34/setup/db/db/schema-450to460.sql
----------------------------------------------------------------------
diff --git a/setup/db/db/schema-450to460.sql b/setup/db/db/schema-450to460.sql
index 8480c85..27a3d83 100644
--- a/setup/db/db/schema-450to460.sql
+++ b/setup/db/db/schema-450to460.sql
@@ -19,4 +19,6 @@
-- Schema upgrade from 4.5.0 to 4.6.0
--
-INSERT IGNORE INTO `cloud`.`configuration` VALUES ("Advanced", 'DEFAULT', 'management-server', "stats.output.uri", "", "URI to additionally send StatsCollector statistics to", "", NULL, NULL, 0);
\ No newline at end of file
+INSERT IGNORE INTO `cloud`.`configuration` VALUES ("Advanced", 'DEFAULT', 'management-server', "stats.output.uri", "", "URI to additionally send StatsCollector statistics to", "", NULL, NULL, 0);
+
+INSERT IGNORE INTO `cloud`.`configuration` VALUES ("Advanced", 'DEFAULT', 'management-server', "usage.report.interval", 7, "Interval (days) between sending anonymous Usage Reports back to the CloudStack project", "", NULL, NULL, 0);
\ No newline at end of file
Re: git commit: updated refs/heads/reporter to 026efd3
Posted by Rohit Yadav <ro...@gmail.com>.
On Mon, Dec 1, 2014 at 6:37 PM, <wi...@apache.org> wrote:
> Repository: cloudstack
> Updated Branches:
> refs/heads/reporter [created] 026efd344
>
>
> Proposal of a Usage Reporter / call-home functionality for CloudStack
>
> With this commit the Management Server will be default generate a
> anonymous Usage
> report every 7 (seven) days and submit this information back to the Apache
> CloudStack project.
>
> These anonymous reports do NOT contain any information about Instance
> names, subnets, etc. It only
> contains numbers about how CloudStack is being used.
>
> This information is vital for the project to gain more insight in how
> CloudStack is being used.
>
> Users can turn the reporting off by setting usage.report.interval to 0
> (zero)
>
Great initiative Wido! Do we have a data collection service that is already
running with ASF or you're building one?
Regards.
>
>
> Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo
> Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/026efd34
> Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/026efd34
> Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/026efd34
>
> Branch: refs/heads/reporter
> Commit: 026efd344452cef806f2475009bf690f5c2cbf9a
> Parents: 449d123
> Author: Wido den Hollander <wi...@widodh.nl>
> Authored: Thu Nov 20 14:43:33 2014 +0100
> Committer: Wido den Hollander <wi...@widodh.nl>
> Committed: Mon Dec 1 14:00:10 2014 +0100
>
> ----------------------------------------------------------------------
> reporter/README.md | 18 +
> reporter/usage-report-collector.py | 64 +++
> server/pom.xml | 10 +
> .../spring-server-core-managers-context.xml | 2 +
> server/src/com/cloud/configuration/Config.java | 5 +-
> .../apache/cloudstack/report/UsageReporter.java | 403 +++++++++++++++++++
> setup/db/db/schema-450to460.sql | 4 +-
> 7 files changed, 504 insertions(+), 2 deletions(-)
> ----------------------------------------------------------------------
>
>
>
> http://git-wip-us.apache.org/repos/asf/cloudstack/blob/026efd34/reporter/README.md
> ----------------------------------------------------------------------
> diff --git a/reporter/README.md b/reporter/README.md
> new file mode 100644
> index 0000000..6453fa4
> --- /dev/null
> +++ b/reporter/README.md
> @@ -0,0 +1,18 @@
> +# CloudStack Usage Report
> +
> +This directory contains the CloudStack reporter webservice used by the
> Apache CloudStack project
> +to gather anonymous statistical information about CloudStack deployments.
> +
> +Since version <FIX ME!!> the management server sends out a anonymized
> Usage Report out to the
> +project every 7 days.
> +
> +This information is used to gain information about how CloudStack is
> being used.
> +
> +Turning this Usage Reporting functionality off can be done in the Global
> Settings by setting
> +'usage.report.interval' to 0.
> +
> +# The webservice
> +The Python Flask application in this directory is the webservice running
> on https://reports.cloudstack.apache.org/ (FIX ME?)
> +and stores all the incoming information in a ElasticSearch database.
> +
> +Since Apache CloudStack is Open Source we show not only how we generate
> the report, but also how we process it.
>
>
> http://git-wip-us.apache.org/repos/asf/cloudstack/blob/026efd34/reporter/usage-report-collector.py
> ----------------------------------------------------------------------
> diff --git a/reporter/usage-report-collector.py
> b/reporter/usage-report-collector.py
> new file mode 100755
> index 0000000..500a4d2
> --- /dev/null
> +++ b/reporter/usage-report-collector.py
> @@ -0,0 +1,64 @@
> +#!/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.
> +
> +from flask import abort, Flask, request, Response
> +from elasticsearch import Elasticsearch
> +import json
> +import time
> +
> +def json_response(response):
> + return json.dumps(response, indent=2) + "\n", 200, {'Content-Type':
> 'application/json; charset=utf-8'}
> +
> +def generate_app(config=None):
> + app = Flask(__name__)
> +
> + @app.route('/report/<unique_id>', methods=['POST'])
> + def report(unique_id):
> + # We expect JSON data, so if the Content-Type doesn't match JSON
> data we throw an error
> + if 'Content-Type' in request.headers:
> + if request.headers['Content-Type'] != 'application/json':
> + abort(417, "No or incorrect Content-Type header was
> supplied")
> +
> + index = "cloudstack-%s" % time.strftime("%Y.%m.%d", time.gmtime())
> + timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
> +
> + es = Elasticsearch()
> + es.indices.create(index=index, ignore=400)
> +
> + report = json.loads(request.data)
> + report["unique_id"] = unique_id
> + report["timestamp"] = timestamp
> +
> + es.index(index=index, doc_type="usage-report",
> body=json.dumps(report), timestamp=timestamp, refresh=True)
> +
> + response = {}
> + return json_response(response)
> +
> + return app
> +
> +
> +app = generate_app()
> +
> +# Only run the App if this script is invoked from a Shell
> +if __name__ == '__main__':
> + app.debug = True
> + app.run(host='0.0.0.0', port=8088)
> +
> +# Otherwise provide a variable called 'application' for mod_wsgi
> +else:
> + application = app
>
>
> http://git-wip-us.apache.org/repos/asf/cloudstack/blob/026efd34/server/pom.xml
> ----------------------------------------------------------------------
> diff --git a/server/pom.xml b/server/pom.xml
> index c293aa1..f2a2a0e 100644
> --- a/server/pom.xml
> +++ b/server/pom.xml
> @@ -138,6 +138,16 @@
> <artifactId>opensaml</artifactId>
> <version>${cs.opensaml.version}</version>
> </dependency>
> + <dependency>
> + <groupId>com.google.code.gson</groupId>
> + <artifactId>gson</artifactId>
> + <version>${cs.gson.version}</version>
> + </dependency>
> + <dependency>
> + <groupId>com.google.guava</groupId>
> + <artifactId>guava</artifactId>
> + <version>${cs.guava.version}</version>
> + </dependency>
> </dependencies>
> <build>
> <testResources>
>
>
> http://git-wip-us.apache.org/repos/asf/cloudstack/blob/026efd34/server/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml
> ----------------------------------------------------------------------
> diff --git
> a/server/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml
> b/server/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml
> index 6d8f32e..faa5286 100644
> ---
> a/server/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml
> +++
> b/server/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml
> @@ -222,6 +222,8 @@
>
> <bean id="statsCollector" class="com.cloud.server.StatsCollector" />
>
> + <bean id="usageReporter"
> class="org.apache.cloudstack.report.UsageReporter" />
> +
> <bean id="storagePoolAutomationImpl"
> class="com.cloud.storage.StoragePoolAutomationImpl" />
>
> <bean id="domainManagerImpl" class="com.cloud.user.DomainManagerImpl"
> />
>
>
> http://git-wip-us.apache.org/repos/asf/cloudstack/blob/026efd34/server/src/com/cloud/configuration/Config.java
> ----------------------------------------------------------------------
> diff --git a/server/src/com/cloud/configuration/Config.java
> b/server/src/com/cloud/configuration/Config.java
> index 281d99c..d395108 100644
> --- a/server/src/com/cloud/configuration/Config.java
> +++ b/server/src/com/cloud/configuration/Config.java
> @@ -2052,7 +2052,10 @@ public enum Config {
> PublishAsynJobEvent("Advanced", ManagementServer.class,
> Boolean.class, "publish.async.job.events", "true", "enable or disable
> publishing of usage events on the event bus", null),
>
> // StatsCollector
> - StatsOutPutGraphiteHost("Advanced", ManagementServer.class,
> String.class, "stats.output.uri", "", "URI to additionally send
> StatsCollector statistics to", null);
> + StatsOutPutGraphiteHost("Advanced", ManagementServer.class,
> String.class, "stats.output.uri", "", "URI to additionally send
> StatsCollector statistics to", null),
> +
> + // Usage Reporting service
> + UsageReportInterval("Advanced", ManagementServer.class,
> Integer.class, "usage.report.interval", "7", "Interval (days) between
> sending anonymous Usage Reports back to the CloudStack project", null);
>
> private final String _category;
> private final Class<?> _componentClass;
>
>
> http://git-wip-us.apache.org/repos/asf/cloudstack/blob/026efd34/server/src/org/apache/cloudstack/report/UsageReporter.java
> ----------------------------------------------------------------------
> diff --git a/server/src/org/apache/cloudstack/report/UsageReporter.java
> b/server/src/org/apache/cloudstack/report/UsageReporter.java
> new file mode 100644
> index 0000000..e7891e9
> --- /dev/null
> +++ b/server/src/org/apache/cloudstack/report/UsageReporter.java
> @@ -0,0 +1,403 @@
> +// 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.
> +package org.apache.cloudstack.report;
> +
> +import java.util.concurrent.Executors;
> +import java.util.concurrent.ScheduledExecutorService;
> +import java.util.concurrent.TimeUnit;
> +import java.util.List;
> +import java.util.Map;
> +import java.util.HashMap;
> +import java.sql.Connection;
> +import java.sql.PreparedStatement;
> +import java.sql.ResultSet;
> +import java.sql.SQLException;
> +import java.net.URL;
> +import java.net.HttpURLConnection;
> +import java.net.SocketTimeoutException;
> +import java.net.MalformedURLException;
> +import java.net.ProtocolException;
> +import java.io.OutputStreamWriter;
> +import java.io.IOException;
> +
> +import javax.inject.Inject;
> +
> +import org.apache.log4j.Logger;
> +import org.springframework.stereotype.Component;
> +
> +import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
> +import org.apache.cloudstack.managed.context.ManagedContextRunnable;
> +
> +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
> +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
> +
> +import org.apache.commons.codec.digest.DigestUtils;
> +
> +import com.cloud.host.HostVO;
> +import com.cloud.host.dao.HostDao;
> +import com.cloud.dc.ClusterVO;
> +import com.cloud.dc.dao.ClusterDao;
> +import com.cloud.dc.DataCenterVO;
> +import com.cloud.dc.dao.DataCenterDao;
> +import com.cloud.vm.UserVmVO;
> +import com.cloud.vm.dao.UserVmDao;
> +import com.cloud.vm.VMInstanceVO;
> +import com.cloud.vm.dao.VMInstanceDao;
> +import com.cloud.utils.db.SearchCriteria;
> +import com.cloud.utils.NumbersUtil;
> +import com.cloud.utils.component.ManagerBase;
> +import com.cloud.utils.component.ComponentMethodInterceptable;
> +import com.cloud.utils.concurrency.NamedThreadFactory;
> +import com.cloud.utils.db.DB;
> +import com.cloud.utils.db.TransactionLegacy;
> +
> +import com.google.gson.Gson;
> +import com.google.common.util.concurrent.AtomicLongMap;
> +
> +@Component
> +public class UsageReporter extends ManagerBase implements
> ComponentMethodInterceptable {
> + public static final Logger s_logger =
> Logger.getLogger(UsageReporter.class.getName());
> +
> + /* !FIX ME! This should point to a Apache Infra host with SSL! */
> + private String reportHost = "http://cs-report.widodh.nl:8088/report";
> +
> + private String uniqueID = null;
> +
> + private static UsageReporter s_instance = null;
> +
> + private ScheduledExecutorService _executor = null;
> +
> + @Inject
> + private ConfigurationDao _configDao;
> + @Inject
> + private HostDao _hostDao;
> + @Inject
> + private ClusterDao _clusterDao;
> + @Inject
> + private PrimaryDataStoreDao _storagePoolDao;
> + @Inject
> + private DataCenterDao _dataCenterDao;
> + @Inject
> + private UserVmDao _userVmDao;
> + @Inject
> + private VMInstanceDao _vmInstance;
> +
> + int usageReportInterval = -1;
> +
> + public static UsageReporter getInstance() {
> + return s_instance;
> + }
> +
> + public static UsageReporter getInstance(Map<String, String> configs) {
> + s_instance.init(configs);
> + return s_instance;
> + }
> +
> + public UsageReporter() {
> + s_instance = this;
> + }
> +
> + @Override
> + public boolean start() {
> + init(_configDao.getConfiguration());
> + return true;
> + }
> +
> + private void init(Map<String, String> configs) {
> + _executor = Executors.newScheduledThreadPool(1, new
> NamedThreadFactory("UsageReporter"));
> +
> + usageReportInterval =
> NumbersUtil.parseInt(configs.get("usage.report.interval"), 7);
> +
> + if (usageReportInterval > 0) {
> + _executor.scheduleWithFixedDelay(new UsageCollector(), 7,
> usageReportInterval, TimeUnit.DAYS);
> + }
> +
> + uniqueID = getUniqueId();
> + }
> +
> + private void sendReport(String reportUri, String uniqueID,
> Map<String, Map> reportMap) {
> + Gson gson = new Gson();
> + String report = gson.toJson(reportMap);
> +
> + int http_timeout = 15000;
> +
> + try {
> + s_logger.info("Usage Report will be send to: " + reportUri);
> + s_logger.debug("REPORT: " + report);
> +
> + URL url = new URL(reportUri + "/" + uniqueID);
> +
> + HttpURLConnection conn = (HttpURLConnection)
> url.openConnection();
> + conn.setConnectTimeout(http_timeout);
> + conn.setReadTimeout(http_timeout);
> + conn.setRequestMethod("POST");
> + conn.setDoOutput(true);
> + conn.setRequestProperty("Content-Type", "application/json");
> + conn.setRequestProperty("Accept", "application/json");
> +
> + OutputStreamWriter osw = new
> OutputStreamWriter(conn.getOutputStream());
> + osw.write(report);
> + osw.flush();
> + osw.close();
> +
> + int resp_code = conn.getResponseCode();
> +
> + if (resp_code == HttpURLConnection.HTTP_OK){
> + s_logger.info("Usage Report succesfully send to: " +
> reportUri);
> + } else {
> + s_logger.warn("Failed to send Usage Report: " +
> conn.getResponseMessage());
> + }
> +
> + } catch (SocketTimeoutException e) {
> + s_logger.warn("Sending Usage Report to " + reportUri + "
> timed out: " + e.getMessage());
> + } catch (MalformedURLException e) {
> + s_logger.warn(reportUri + " is a invalid URL for sending
> Usage Report to: "+ e.getMessage());
> + } catch (ProtocolException e) {
> + s_logger.warn("Sending Usage Report failed due to a invalid
> protocol: " + e.getMessage());
> + } catch (IOException e) {
> + s_logger.warn("Failed to write Usage Report due to a
> IOException: " + e.getMessage());
> + }
> + }
> +
> + @DB
> + private String getUniqueId() {
> + String unique = null;
> + Connection conn = null;
> +
> + try {
> + conn = TransactionLegacy.getStandaloneConnection();
> +
> + PreparedStatement pstmt = conn.prepareStatement("SELECT
> version,updated FROM version ORDER BY id ASC LIMIT 1");
> + ResultSet rs = pstmt.executeQuery();
> + if (rs.next()) {
> + unique = DigestUtils.sha256Hex(rs.getString(1) +
> rs.getString(2));
> + } else {
> + s_logger.debug("No rows found in the version table.
> Unable to obtain unique ID for this environment");
> + }
> +
> + rs.close();
> + } catch (SQLException e) {
> + s_logger.debug("Unable to get the unique ID of this
> environment: " + e.getMessage());
> + } finally {
> + try {
> + conn.close();
> + } catch (SQLException e) {
> + }
> + }
> +
> + s_logger.debug("Usage Report Unique ID is: " + unique);
> +
> + return unique;
> + }
> +
> + private Map<String, AtomicLongMap> getHostReport() {
> + Map<String, AtomicLongMap> hostMap = new HashMap<String,
> AtomicLongMap>();
> + AtomicLongMap<Object> host_types = AtomicLongMap.create();
> + AtomicLongMap<Object> host_hypervisor_type =
> AtomicLongMap.create();
> + AtomicLongMap<Object> host_version = AtomicLongMap.create();
> +
> + SearchCriteria<HostVO> host_sc = _hostDao.createSearchCriteria();
> + List<HostVO> hosts = _hostDao.search(host_sc, null);
> + for (HostVO host : hosts) {
> + host_types.getAndIncrement(host.getType());
> + if (host.getHypervisorType() != null) {
> +
> host_hypervisor_type.getAndIncrement(host.getHypervisorType());
> + }
> +
> + host_version.getAndIncrement(host.getVersion());
> + }
> +
> + hostMap.put("version", host_version);
> + hostMap.put("hypervisor_type", host_hypervisor_type);
> + hostMap.put("type", host_types);
> +
> + return hostMap;
> + }
> +
> + private Map<String, AtomicLongMap> getClusterReport() {
> + Map<String, AtomicLongMap> clusterMap = new HashMap<String,
> AtomicLongMap>();
> + AtomicLongMap<Object> cluster_hypervisor_type =
> AtomicLongMap.create();
> + AtomicLongMap<Object> cluster_types = AtomicLongMap.create();
> +
> + SearchCriteria<ClusterVO> cluster_sc =
> _clusterDao.createSearchCriteria();
> + List<ClusterVO> clusters = _clusterDao.search(cluster_sc, null);
> + for (ClusterVO cluster : clusters) {
> + if (cluster.getClusterType() != null) {
> + cluster_types.getAndIncrement(cluster.getClusterType());
> + }
> +
> + if (cluster.getHypervisorType() != null) {
> +
> cluster_hypervisor_type.getAndIncrement(cluster.getHypervisorType());
> + }
> + }
> +
> + clusterMap.put("hypervisor_type", cluster_hypervisor_type);
> + clusterMap.put("type", cluster_types);
> +
> + return clusterMap;
> + }
> +
> + private Map<String, AtomicLongMap> getStoragePoolReport() {
> + Map<String, AtomicLongMap> storagePoolMap = new HashMap<String,
> AtomicLongMap>();
> + AtomicLongMap<Object> storage_pool_types = AtomicLongMap.create();
> + AtomicLongMap<Object> storage_pool_provider =
> AtomicLongMap.create();
> + AtomicLongMap<Object> storage_pool_scope = AtomicLongMap.create();
> +
> + List<StoragePoolVO> storagePools = _storagePoolDao.listAll();
> + for (StoragePoolVO pool : storagePools) {
> + if (pool.getPoolType() != null) {
> + storage_pool_types.getAndIncrement(pool.getPoolType());
> + }
> +
> + if (pool.getStorageProviderName() != null) {
> +
> storage_pool_provider.getAndIncrement(pool.getStorageProviderName());
> + }
> +
> + if (pool.getScope() != null) {
> + storage_pool_scope.getAndIncrement(pool.getScope());
> + }
> + }
> +
> + storagePoolMap.put("type", storage_pool_types);
> + storagePoolMap.put("provider", storage_pool_provider);
> + storagePoolMap.put("scope", storage_pool_scope);
> +
> + return storagePoolMap;
> + }
> +
> + private Map<String, AtomicLongMap> getDataCenterReport() {
> + Map<String, AtomicLongMap> datacenterMap = new HashMap<String,
> AtomicLongMap>();
> + AtomicLongMap<Object> network_type = AtomicLongMap.create();
> + AtomicLongMap<Object> dns_provider = AtomicLongMap.create();
> + AtomicLongMap<Object> dhcp_provider = AtomicLongMap.create();
> + AtomicLongMap<Object> lb_provider = AtomicLongMap.create();
> + AtomicLongMap<Object> firewall_provider = AtomicLongMap.create();
> + AtomicLongMap<Object> gateway_provider = AtomicLongMap.create();
> + AtomicLongMap<Object> userdata_provider = AtomicLongMap.create();
> + AtomicLongMap<Object> vpn_provider = AtomicLongMap.create();
> +
> + List<DataCenterVO> datacenters = _dataCenterDao.listAllZones();
> + for (DataCenterVO datacenter : datacenters) {
> + if (datacenter.getNetworkType() != null) {
> + network_type.getAndIncrement(datacenter.getNetworkType());
> + }
> +
> + if (datacenter.getDnsProvider() != null) {
> + dns_provider.getAndIncrement(datacenter.getDnsProvider());
> + }
> +
> + if (datacenter.getDhcpProvider() != null) {
> +
> dhcp_provider.getAndIncrement(datacenter.getDhcpProvider());
> + }
> +
> + if (datacenter.getLoadBalancerProvider() != null) {
> +
> lb_provider.getAndIncrement(datacenter.getLoadBalancerProvider());
> + }
> +
> + if (datacenter.getFirewallProvider() != null) {
> +
> firewall_provider.getAndIncrement(datacenter.getFirewallProvider());
> + }
> +
> + if (datacenter.getGatewayProvider() != null) {
> +
> gateway_provider.getAndIncrement(datacenter.getGatewayProvider());
> + }
> +
> + if (datacenter.getUserDataProvider() != null) {
> +
> userdata_provider.getAndIncrement(datacenter.getUserDataProvider());
> + }
> +
> + if (datacenter.getVpnProvider() != null) {
> + vpn_provider.getAndIncrement(datacenter.getVpnProvider());
> + }
> + }
> +
> + datacenterMap.put("network_type", network_type);
> + datacenterMap.put("dns_provider", dns_provider);
> + datacenterMap.put("dhcp_provider", dhcp_provider);
> + datacenterMap.put("lb_provider", lb_provider);
> + datacenterMap.put("firewall_provider", firewall_provider);
> + datacenterMap.put("gateway_provider", gateway_provider);
> + datacenterMap.put("userdata_provider", userdata_provider);
> + datacenterMap.put("vpn_provider", vpn_provider);
> +
> + return datacenterMap;
> + }
> +
> + private Map<String, AtomicLongMap> getInstanceReport() {
> +
> + Map<String, AtomicLongMap> instanceMap = new HashMap<String,
> AtomicLongMap>();
> + AtomicLongMap<Object> hypervisor_type = AtomicLongMap.create();
> + AtomicLongMap<Object> instance_state = AtomicLongMap.create();
> + AtomicLongMap<Object> instance_type = AtomicLongMap.create();
> + AtomicLongMap<Object> ha_enabled = AtomicLongMap.create();
> + AtomicLongMap<Object> dynamically_scalable =
> AtomicLongMap.create();
> +
> + SearchCriteria<HostVO> host_sc = _hostDao.createSearchCriteria();
> + List<HostVO> hosts = _hostDao.search(host_sc, null);
> + for (HostVO host : hosts) {
> + List<UserVmVO> vms = _userVmDao.listUpByHostId(host.getId());
> + for (UserVmVO vm : vms) {
> + VMInstanceVO vmVO = _vmInstance.findById(vm.getId());
> +
> + if (vmVO.getHypervisorType() != null) {
> +
> hypervisor_type.getAndIncrement(vmVO.getHypervisorType());
> + }
> +
> + if (vmVO.getState() != null) {
> + instance_state.getAndIncrement(vmVO.getState());
> + }
> +
> + if (vmVO.getType() != null) {
> + instance_type.getAndIncrement(vmVO.getType());
> + }
> +
> + ha_enabled.getAndIncrement(vmVO.isHaEnabled());
> +
> dynamically_scalable.getAndIncrement(vmVO.isDynamicallyScalable());
> + }
> + }
> +
> + instanceMap.put("hypervisor_type", hypervisor_type);
> + instanceMap.put("state", instance_state);
> + instanceMap.put("type", instance_type);
> + instanceMap.put("ha_enabled", ha_enabled);
> + instanceMap.put("dynamically_scalable", dynamically_scalable);
> +
> + return instanceMap;
> + }
> +
> + class UsageCollector extends ManagedContextRunnable {
> + @Override
> + protected void runInContext() {
> + try {
> + s_logger.warn("UsageReporter is running...");
> +
> + Map<String, Map> reportMap = new HashMap<String, Map>();
> +
> + reportMap.put("hosts", getHostReport());
> + reportMap.put("clusters", getClusterReport());
> + reportMap.put("primaryStorage", getStoragePoolReport());
> + reportMap.put("zones", getDataCenterReport());
> + reportMap.put("instances", getInstanceReport());
> +
> + sendReport(reportHost, uniqueID, reportMap);
> +
> + } catch (Exception e) {
> + s_logger.warn("Failed to compile Usage Report: " +
> e.getMessage());
> + }
> + }
> + }
> +}
> \ No newline at end of file
>
>
> http://git-wip-us.apache.org/repos/asf/cloudstack/blob/026efd34/setup/db/db/schema-450to460.sql
> ----------------------------------------------------------------------
> diff --git a/setup/db/db/schema-450to460.sql
> b/setup/db/db/schema-450to460.sql
> index 8480c85..27a3d83 100644
> --- a/setup/db/db/schema-450to460.sql
> +++ b/setup/db/db/schema-450to460.sql
> @@ -19,4 +19,6 @@
> -- Schema upgrade from 4.5.0 to 4.6.0
> --
>
> -INSERT IGNORE INTO `cloud`.`configuration` VALUES ("Advanced", 'DEFAULT',
> 'management-server', "stats.output.uri", "", "URI to additionally send
> StatsCollector statistics to", "", NULL, NULL, 0);
> \ No newline at end of file
> +INSERT IGNORE INTO `cloud`.`configuration` VALUES ("Advanced", 'DEFAULT',
> 'management-server', "stats.output.uri", "", "URI to additionally send
> StatsCollector statistics to", "", NULL, NULL, 0);
> +
> +INSERT IGNORE INTO `cloud`.`configuration` VALUES ("Advanced", 'DEFAULT',
> 'management-server', "usage.report.interval", 7, "Interval (days) between
> sending anonymous Usage Reports back to the CloudStack project", "", NULL,
> NULL, 0);
> \ No newline at end of file
>
>