Skip to content

[WIP] Add opt-in Usage Reporter (call-home) for the CloudStack project - #13985

Draft
wido wants to merge 4 commits into
apache:mainfrom
wido:reporter
Draft

[WIP] Add opt-in Usage Reporter (call-home) for the CloudStack project#13985
wido wants to merge 4 commits into
apache:mainfrom
wido:reporter

Conversation

@wido

@wido wido commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Description

This is a WIP / RFC to gather feedback before it is finished. Not intended to be merged as-is.

This proposes an opt-in Usage Reporter ("call-home") for CloudStack. The goal is to give the project insight into how CloudStack is actually deployed in the wild: which hypervisors, storage types, network offerings and versions are in use, and how large environments typically are. Today we simply do not know, which makes it hard to decide what to prioritise, what to deprecate and what to test.

Two parts are included:

  1. Management Server side (UsageReporter): periodically collects aggregated counters and POSTs them as JSON to an endpoint of the CloudStack project over HTTPS.
  2. Collector side (reporter/): a small Python Flask/WSGI application that validates incoming reports and stores them as JSON files on disk, one directory per environment, with the receive timestamp as filename. Submissions are rate limited and bounded per environment, and reports with an unexpected structure are rejected.

Opt-in and privacy

  • Disabled by default: usage.report.interval = 0. An operator has to explicitly set an interval (7 days recommended) and restart the Management Server.
  • usage.report.uri defaults to https://reporting.cloudstack.org/report and can be pointed elsewhere. Only HTTPS is accepted; plain HTTP is refused.
  • Only aggregated statistics are sent: counts per hypervisor type, host/cluster types, storage pool type/provider/scope, zone network and service providers, instance states/types, disk offering characteristics, and the version upgrade history. No hostnames, IP addresses, account names, UUIDs of resources, tags or any other free-form data.
  • The environment is identified by a SHA-256 hash derived from the version table, so reports from the same environment can be correlated over time without identifying it.

Open points for discussion

  • Is the set of collected metrics the right one — anything missing, anything that should not be there?
  • Where and how should the collector be hosted, and who has access to the collected reports?
  • Do we need a documented retention and publication policy for the aggregated data before this is enabled anywhere?
  • The reporter/ collector may be better off in a separate repository rather than in the main tree.
  • Documentation and tests are not written yet.

Types of changes

  • Breaking change (fix or feature that would cause existing functionality to change)
  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • Enhancement (improves an existing feature and functionality)
  • Cleanup (Code refactoring and cleanup, that may add test cases)
  • build/CI
  • test (unit or integration test code)

How Has This Been Tested?

Manually against a local Management Server with usage.report.interval set to a low value and the collector running locally behind HTTPS. Verified that nothing is sent with the default configuration.

The Management Server periodically sends an anonymous usage report to
an API endpoint of the CloudStack project. This is opt-in and disabled
by default (usage.report.interval = 0); only aggregated statistics are
sent, nothing that can directly identify an environment. Reports are
only sent over HTTPS.

Includes the server-side collector, a Python Flask/WSGI application
which validates incoming reports and stores them as JSON files on the
local filesystem, in a directory per environment with the receive
timestamp as filename. Submissions are rate limited and bounded per
environment and reports with unexpected structure are rejected.
@boring-cyborg boring-cyborg Bot added the Python Warning... Python code Ahead! label Aug 26, 2026
@wido wido added this to the 24.0.0 milestone Aug 26, 2026
@IvetPM

IvetPM commented Aug 27, 2026

Copy link
Copy Markdown

This is a really nice idea and will help us understand better the CloudStack users, their stack and environment specifics. IMO the information shall be limited to PMCs only, as it opens a bit of security gap for proprietary vendors to target the CloudStack users. But we can create Qly reports and publish them as blog posts on the ACS website. I was hoping for years to get such information, so really nice job!

…enses

Pin down the JSON that the management server POSTs to usage.report.uri so
any change to the wire format has to be a deliberate one.

UsageReporterTest mocks the seven DAOs the report is built from and drives
the real report builders through the real AtomicGsonAdapter, comparing the
result against a checked-in fixture, usage-report-expected.json. Keys are
sorted on both sides before comparing: AtomicLongMap is backed by a
ConcurrentHashMap and the report itself by a HashMap, so key order on the
wire is not deterministic and must not be part of the contract. Sorting
also makes a mismatch print a readable diff.

The tests document three things that are not obvious from reading the code:

 - provisioning_type keys are lowercase ("thin"/"fat"). Storage.Provisio-
   ningType overrides toString() and the adapter keys on String.valueOf(),
   so the payload does not carry the enum constant names that Gson would
   emit by default.
 - boolean counters reach the wire as the string keys "true" and "false",
   used by ha_enabled, dynamically_scalable, compute_only and
   use_local_storage.
 - a report from an empty install still carries all eight sections with
   empty counter objects, and avg_disk_size falls back to 0 rather than
   dividing by zero.

AtomicGsonAdapterTest covers the adapter on its own: null and empty maps,
counts as numbers, boolean and enum keys, and that read() consumes a null.

Also add the ASF license headers that apache-rat flags on reporter/README.md
and reporter/requirements.txt.
@codecov

codecov Bot commented Aug 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 65.70513% with 107 lines in your changes missing coverage. Please review.
✅ Project coverage is 19.81%. Comparing base (158fe4f) to head (881ea52).
⚠️ Report is 23 commits behind head on main.

Files with missing lines Patch % Lines
...va/org/apache/cloudstack/report/UsageReporter.java 65.44% 85 Missing and 9 partials ⚠️
...ain/java/com/cloud/upgrade/dao/VersionDaoImpl.java 0.00% 10 Missing ⚠️
...loudstack/utils/identity/InstallationIdentity.java 76.92% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main   #13985      +/-   ##
============================================
+ Coverage     19.74%   19.81%   +0.06%     
- Complexity    19960    20031      +71     
============================================
  Files          6371     6374       +3     
  Lines        575784   576211     +427     
  Branches      70478    70536      +58     
============================================
+ Hits         113665   114148     +483     
+ Misses       449765   449624     -141     
- Partials      12354    12439      +85     
Flag Coverage Δ
uitests 3.53% <ø> (+0.11%) ⬆️
unittests 21.08% <65.70%> (+0.06%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@shwstppr

Copy link
Copy Markdown
Contributor

It would be definitely useful but I'm not sure how many users would want to share it. It would be useful to some control on toggling configs on the first login screen.

@DaanHoogland DaanHoogland left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code looks generally good, but prove is in the eating of the pudding.

@DaanHoogland

Copy link
Copy Markdown
Contributor

I think if we do not disclose the information to the general public we should abandon this project. It is definitely not for PMC only, or even restricted to any apache circle. Its use is for any contributor from anywhere.

…n identity util

Address the first points of the architectural review on the usage reporter.

The reporting destination is no longer a Global Setting. The point of
telemetry is to give the Apache CloudStack project authoritative project-wide
statistics, so "telemetry enabled" has to mean the data reaches the project
rather than wherever an operator points it. The endpoint is now the constant
https://call-home.cloudstack.org/report, with DNS providing whatever
indirection the receiving infrastructure needs.

Rename usage.report.interval to telemetry.interval. The usage.* prefix
collides with the Usage Server / usage records subsystem and reads like an
accounting setting. Zero still means disabled.

The installation identity stays derived rather than generated: the version
the database was created with plus the moment it was created already
identifies an installation, is shared by every Management Server on that
database, and survives restarts and upgrades without anything being stored.
Move the derivation out of UsageReporter into InstallationIdentity in the
utils module, fed by a new VersionDao.getInitialVersion(). That removes the
raw JDBC from UsageReporter and makes both halves unit testable.

Two changes to the hash follow from taking the timestamp as a Date instead of
whatever string the JDBC driver returned: it is formatted as UTC, so
Management Servers in different timezones sharing a database derive the same
identity, and the two inputs are separated so different pairs cannot
concatenate into the same string. The derived value therefore differs from
the previous implementation, which is a one-time change for anyone already
running this branch and has no effect otherwise.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@DaanHoogland DaanHoogland moved this from Backlog to conflict/waiting in CloudStack Testing Sep 1, 2026
@andrijapanicsb

Copy link
Copy Markdown
Contributor

I think if we do not disclose the information to the general public we should abandon this project. It is definitely not for PMC only, or even restricted to any apache circle. Its use is for any contributor from anywhere.

Agree @DaanHoogland 100%.

Once you put your business/comercial hat on, you will support PMC-only idea @DaanHoogland 😉

In the meantime, it's for the whole Project, IMO. Sorry @IvetPM

@DaanHoogland

Copy link
Copy Markdown
Contributor

Once you put your business/comercial hat on, you will support PMC-only idea @DaanHoogland 😉

There you are wrong there @andrijapanicsb. I made OSS my business and I know the conditions it requires for that to work. 🤑

@andrijapanicsb

Copy link
Copy Markdown
Contributor

That was a joke @DaanHoogland - and that why I value you so much!

@andrijapanicsb

Copy link
Copy Markdown
Contributor

Thanks @wido, I think this is going in a much better direction.
(btw, 53 lines only, I really gave my best)

I like the change to telemetry.*, having one fixed reporting endpoint, moving the installation ID logic into a separate utility, and especially the tests around the actual JSON payload. This makes it much easier to understand exactly what is being sent and also makes future changes to the format more obvious.

I don't want to repeat my previous very long review here :) but I also don't want it to look like the remaining points are no longer important. Since this is still WIP, I think there are a few important things we should keep in mind before this gets anywhere close to merge-ready:

  1. Installation ID

    I am OK with keeping the current derived ID approach for now, but I don't think we should say that it is guaranteed to be unique for every installation. Two installations could in theory have the same initial version and timestamp, and cloning a CloudStack DB would also produce the same ID.

    I think the important thing here is to clearly define what we consider an "installation" and what we expect to happen with cloned/restored/reinstalled databases.

  2. Making sure reports are genuine

    The collector still has no way to know if a report really came from CloudStack. Anybody can read the source code, generate a random ID and send valid-looking JSON directly to the endpoint.

    Apart from possible DoS/storage abuse, this also means somebody could pollute the statistics with fake installations. If we later use these statistics to make decisions about features, hypervisors, storage support, deprecations, etc., we need to have reasonable confidence that the data is real.

    I think this is one of the main architectural points still to solve.

  3. Actual report schema validation

    The new client-side payload tests are very useful.

    However, as far as I can see, the collector itself still validates only that the request is valid/sane JSON: depth, number/length of keys, value types, etc. It does not actually validate that the JSON matches the expected CloudStack telemetry structure.

    For example, an unrelated JSON object with valid types would still pass the current validation.

    I think we should have an explicit schema version in the report and validate the received payload against the expected schema.

  4. Current vs historical VM statistics

    One thing I think is especially important here is vm_instance.

    CloudStack normally keeps VM rows in the database after the VM has been removed, so simply reading all VMInstanceVO objects can give us statistics about VMs which existed during the lifetime of the cloud, rather than the current size/state of the cloud.

    Both numbers can actually be useful, but they mean very different things. I think we should explicitly separate current/non-removed VMs from historical/lifetime numbers.

  5. Privacy on the receiver side

    I don't have a problem with the actual report payload from a privacy point of view. The information being sent is aggregated and does not directly identify the operator.

    My remaining concern is the HTTP side. The receiving infrastructure will see the source IP, and if normal HTTP/proxy access logging is enabled we can end up with a persistent installation ID associated with the public IP of the operator.

    That could potentially identify the company/provider even though there is nothing identifying them inside the JSON itself.

    So I think we need to define how source IPs, proxy headers, HTTP logs and retention are handled before the real collector is put into production. This probably also needs to be checked with ASF privacy/infra rather than only decided inside the CloudStack PMC.

There are still some other points from the previous review: only one MS should probably send the report in an HA setup, doing the aggregation in SQL instead of loading all objects into Java, active/stale installation tracking, actual volume statistics vs configured disk offerings, collector storage, and making the opt-in/report preview visible to operators.

I don't think all of those need to be solved in the same commit though. They can be handled step by step while this is still WIP.

Overall, definitely +1 from me on continuing with this and thx again for such a great initiative!
The latest changes already address some of the previous concerns nicely. I would mainly make sure we agree on the points above before we consider the design stable and start moving towards merge-ready.

Address the next round of the architectural review on the telemetry feature.

Rename the reporter/ directory to telemetry/ to match what the feature is
called since the telemetry.* settings rename.

The report now carries a schema_version and the collector validates every
payload against the exact schema belonging to that version, on top of the
existing generic sanity limits: all sections and counters must be present, no
other keys are accepted anywhere and every value must have the expected type.
An arbitrary well-formed JSON object no longer makes it to disk, and any
change to the report structure requires incrementing the version and teaching
the collector the new schema.

Rows of removed Instances are kept in the database, so both current and
lifetime statistics can be reported, and they mean different things. The
instances section is now split in two: "current" covers the non-removed rows
in any state, Destroyed Instances that have not been expunged yet included,
while "lifetime" also counts the removed rows and therefore describes every
Instance which ever existed in the cloud, as total/removed counts plus
hypervisor and type counters.

Document in the README that the installation ID is not guaranteed globally
unique but a collision is negligible (two databases created with the same
version in the same second), that a cloned or restored database intentionally
reports as the same installation, and that the collector cannot verify a
report was genuinely produced by a Management Server: the source is open, so
the statistics are best-effort by nature, with validation, rate limiting and
the per-ID storage bound limiting abuse.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@wido

wido commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @wido, I think this is going in a much better direction. (btw, 53 lines only, I really gave my best)

I like the change to telemetry.*, having one fixed reporting endpoint, moving the installation ID logic into a separate utility, and especially the tests around the actual JSON payload. This makes it much easier to understand exactly what is being sent and also makes future changes to the format more obvious.

I don't want to repeat my previous very long review here :) but I also don't want it to look like the remaining points are no longer important. Since this is still WIP, I think there are a few important things we should keep in mind before this gets anywhere close to merge-ready:

1. **Installation ID**
   I am OK with keeping the current derived ID approach for now, but I don't think we should say that it is guaranteed to be unique for every installation. Two installations could in theory have the same initial version and timestamp, and cloning a CloudStack DB would also produce the same ID.
   I think the important thing here is to clearly define what we consider an "installation" and what we expect to happen with cloned/restored/reinstalled databases.

2. **Making sure reports are genuine**
   The collector still has no way to know if a report really came from CloudStack. Anybody can read the source code, generate a random ID and send valid-looking JSON directly to the endpoint.
   Apart from possible DoS/storage abuse, this also means somebody could pollute the statistics with fake installations. If we later use these statistics to make decisions about features, hypervisors, storage support, deprecations, etc., we need to have reasonable confidence that the data is real.
   I think this is one of the main architectural points still to solve.

3. **Actual report schema validation**
   The new client-side payload tests are very useful.
   However, as far as I can see, the collector itself still validates only that the request is valid/sane JSON: depth, number/length of keys, value types, etc. It does not actually validate that the JSON matches the expected CloudStack telemetry structure.
   For example, an unrelated JSON object with valid types would still pass the current validation.
   I think we should have an explicit schema version in the report and validate the received payload against the expected schema.

4. **Current vs historical VM statistics**
   One thing I think is especially important here is `vm_instance`.
   CloudStack normally keeps VM rows in the database after the VM has been removed, so simply reading all `VMInstanceVO` objects can give us statistics about VMs which existed during the lifetime of the cloud, rather than the current size/state of the cloud.
   Both numbers can actually be useful, but they mean very different things. I think we should explicitly separate current/non-removed VMs from historical/lifetime numbers.

5. **Privacy on the receiver side**
   I don't have a problem with the actual report payload from a privacy point of view. The information being sent is aggregated and does not directly identify the operator.
   My remaining concern is the HTTP side. The receiving infrastructure will see the source IP, and if normal HTTP/proxy access logging is enabled we can end up with a persistent installation ID associated with the public IP of the operator.
   That could potentially identify the company/provider even though there is nothing identifying them inside the JSON itself.
   So I think we need to define how source IPs, proxy headers, HTTP logs and retention are handled before the real collector is put into production. This probably also needs to be checked with ASF privacy/infra rather than only decided inside the CloudStack PMC.

There are still some other points from the previous review: only one MS should probably send the report in an HA setup, doing the aggregation in SQL instead of loading all objects into Java, active/stale installation tracking, actual volume statistics vs configured disk offerings, collector storage, and making the opt-in/report preview visible to operators.

I don't think all of those need to be solved in the same commit though. They can be handled step by step while this is still WIP.

Overall, definitely +1 from me on continuing with this and thx again for such a great initiative! The latest changes already address some of the previous concerns nicely. I would mainly make sure we agree on the points above before we consider the design stable and start moving towards merge-ready.

Thanks for the review! Addressed in the latest push:

  1. The chance of a collision is practically zero (same version + same creation second), and a cloned DB deriving the same ID is intended, it's the same cloud. Documented this in the README.
  2. Agreed, but we can't prevent this: the source is open, anyone can craft a valid report. Schema validation and rate limiting bound the abuse; the statistics are best-effort by nature.
  3. Done: the report now carries a schema_version and the collector validates the payload against the exact schema for that version, unexpected keys, missing sections or wrong value types are rejected.
  4. Good point, done: instances is now split into current (non-removed, including Destroyed) and lifetime (all rows ever, with total/removed counts), using searchIncludingRemoved().
  5. Agreed. We have to write a privacy statement on this. We will receive the IP submitting the report. We don't see any problems with this at the Ceph project. We should however never store which IP submitted which report. Timestamps could be matched, that is true. We should try to log as minimal as possible. ASF infra will not host this for us btw.

The directory has also been renamed from reporter/ to telemetry/ to match the feature name.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component:management-server Python Warning... Python code Ahead!

Projects

Status: conflict/waiting

Development

Successfully merging this pull request may close these issues.

5 participants