Repository navigation
Hyper V and Cluster Discovery Design
Status: built - Parts A, B and C. How to use it (Part B: building the CMDB from it) · how it is built · Part C: the four grades, how they are worked out. Moving vCenter, Proxmox and Cloud Director onto the same model is the follow-up. The design was reviewed on issue #199 and the answers are folded in below. What changed after review lists them. It shares its rules with the asset ↔ CI link (#201) - see section 12.
The request, from issue #199 (raised by @Dr4inder):
"A separate VM list alone would still leave us maintaining the same infrastructure topology manually."
The issue asks for three things, and they are different sizes:
- See Hyper-V in Assets → Servers. A small, read-only PowerShell collector on a Windows management server reads the Hyper-V hosts, the Failover Clusters and their VMs, and sends the result to FreeITSM over HTTPS. FreeITSM cannot reach in and ask - especially when it runs in Docker on Linux - so the collector pushes, and the Servers page shows when it last heard rather than offering a Sync button that could not do anything.
- Build the CMDB from it. Turn what was discovered into configuration items and relationships - this VM runs on this host, which is a member of this cluster - instead of somebody drawing it by hand. Switched on deliberately, after a preview, and never overwriting what people typed.
- A more honest "If this failed". Today, if a host fails, every VM on it is simply "affected". In a cluster that is too blunt: a highly available VM will probably be restarted on another node. The impact view should be able to say may be interrupted, redundancy reduced and unknown, not only affected - without ever claiming that something will keep working.
They are built as three phases, each useful on its own and each switched off until an administrator turns it on, and the second and third were designed for every hypervisor, not just Hyper-V. As built in 4.2.0, the impact grades work for any relationships, but building the CMDB from discovery exists for Hyper-V only - moving the other hypervisors over is a follow-up.
Dr4inder's two comments agreed the approach and sharpened it in these places:
| Point | Before | Now |
|---|---|---|
| "Redundancy reduced" | Described as "it keeps working" | Some members of a redundant group are lost - nothing more. It is never presented as an assurance of continued operation. Quorum, witness, storage, capacity and current data are not modelled, and the impact view says so every time (Part C) |
| Which VMs can fail over | Every VM on a cluster node | Only VMs that are highly available cluster roles. A VM that merely runs on a node is a hard dependency on that node |
| Combining paths | "The most certain grade" | An explicit rule: the most severe established grade wins, a possible failover never softens a hard dependency, and a missing fact stays visible beside the grade |
| Host identity | The host's name | The machine's hardware UUID, with the name as an attribute. A name change is a rename. A new identity goes to a reviewed rebinding, never an automatic merge |
| VM identity | The VM Id
|
The same, and per Microsoft's import types: register and restore keep the Id, a copy import makes a new one. A VM that comes back with a new Id goes to the reviewed rebinding, never a merge by name |
| Companies | Open question | One company per connection |
| Scale | Asked for real numbers | Synthetic reference scenarios in the repository, used for sizing and load tests. Reports can be split per cluster |
| The collector | Open question | A scheduled task on a separate Windows management host. Supported PowerShell versions and the minimum read permissions are documented (Part A), with a -TestAccess switch that shows what the account can actually read |
| Shared examples | Redacted real reports |
Fully synthetic examples ship in the repository. -Redact is still there, and its output still needs a person to check it |
| Storage (CSVs) | Open question | Not in phase 1. Its absence is listed as a limit wherever impact is shown |
| CMDB mapping | Offered to create classes | Maps onto existing classes for cluster, host, VM and role (storage later), chosen by an administrator. Manual properties and relationships are never touched |
| The inventory agent | Open question | Optional. Nothing here needs a guest agent. The asset ↔ CI link from #201 works with any inventory source |
| Point | The design said | Built as | Why |
|---|---|---|---|
| Cluster nodes | A cluster_nodes section |
A node is a host with cluster_id and node_state in the hosts section |
One host record whether it is clustered or not, so moving a host into a cluster does not make it a new object |
| References inside a report | By identifier | Hosts by name (a VM's host, a role's owner, possible owners), resolved per report | Names are unique in one report, and resolving them per report is what keeps a host rename from breaking its VMs |
| Where the collector posts | /api/v1/virtual-estate/... |
/api/v1/index.php?path=/virtual-estate/... |
Works whether or not the web server rewrites URLs |
| Report size | One report per run, split if needed |
-ReportPer Cluster recommended |
Synthetic sizing: 8,000 VMs is a 7.5 MB report taking about 9 seconds - close to PHP's default 8 MB upload limit. Per cluster it is about 0.4 MB and half a second |
| Retention | Off by default | A per-connection Remove VMs not seen for (days), empty by default | An administrator decides, per estate |
A natural extension, provided it does not become a fourth separate hypervisor integration.
FreeITSM already brings in VMware vCenter, Proxmox VE and VMware Cloud Director. Each is its own code path with its own tables, and none of them reaches the CMDB. If Hyper-V were built the same way, the CMDB mapping and the redundancy-aware impact would then have to be built once per hypervisor. So the shape of this design is:
| Part | What it is | Who benefits |
|---|---|---|
| A | A shared virtual estate model - connections, hosts, clusters, VMs - fed by sources that push (Hyper-V first) and, later, the existing ones that pull | Hyper-V now; vCenter, Proxmox and Cloud Director when they move onto it |
| B | Source-bound CIs: a CI that FreeITSM keeps in step with one discovered object, through a preview, without touching manual data | Any source in the virtual estate |
| C | Redundancy-aware impact: a small addition to the CMDB's impact rules, so membership of a redundant group is not treated like a single point of failure | Any cluster in the CMDB, discovered or drawn by hand |
The issue itself asks for this: "Reuse the existing Servers view, source identification, connection settings, permissions and background-job health infrastructure wherever appropriate", and redundancy semantics "could be reusable for other hypervisors". Moving the existing hypervisors onto the model is a follow-up and does not hold up Hyper-V.
Hypervisors (developer guide):
-
vCenter is one install-wide connection stored in settings, synced only when somebody presses Sync hypervisors (
api/assets/get_vcenter.php, intoservers). Proxmox and Cloud Director have their own connection tables, crons and VM tables (proxmox_connections/proxmox_nodes/proxmox_vms,vcloud_connections/vcloud_vms/vcloud_edge_gateways). There is no shared abstraction:api/assets/sync_hypervisors.phploops over a hard-coded list andapi/assets/get_servers.phpmerges the three for the Servers page. -
Identity is a stable source id - vCenter's moref, Proxmox's
(connection_id, vmid), Director's(connection_id, vm_uuid)- so a rename updates the name and a migration updates the host. No placement history is kept. - Removed VMs are deleted. Proxmox and Director only remove VMs from lists that came back complete, with a guard that refuses to delete if fewer than half the known VMs were seen. vCenter has no such guard.
-
No company. None of these tables has a
tenant_id; the Servers page shows every source to anyone with Assets. - Not linked to assets or the CMDB at all.
Pushed data (REST API - Assets): the 3.12.0 inventory ingest already has exactly the rules a collector needs - a named source owned by whoever first reports under it, observed_at per report, complete / partial / omitted sections, stale and replayed reports recognised, a failed section rolled back on its own (207), and a narrow asset_inventory.create permission on a company-scoped key. But it works one asset at a time, by asset id, and has no hosts, clusters or VMs.
The inventory agent (scripts/Invoke-AssetInventory.ps1, Inventory agent) shows the collector pattern already works in the field, including -OutputFile (write the report, send nothing) and certificate pinning.
Background job health (design) watches FreeITSM's own scheduled jobs. It has no idea of "a source that should have reported by now".
The CMDB (impact analysis developer guide):
- Classes, typed properties and relationship types are configurable; each relationship type says which way impact travels (
impact_direction:none,to_from,from_to). -
cmdbBlastRadius()walks containment, those relationships andspreads_impactproperties, breadth first, and marks every object it reaches as affected with its hop count. There is no notion of redundancy, quorum or severity. - Nothing creates CIs automatically, and a CI does not record where it came from - there is no external id, source or "managed by import" marker.
- Deleting a CI deletes its containment subtree (
parent_id). The issue is right that membership and placement must be relationships, not containment.
- Assets → Settings → Hyper-V (a new tab, next to Proxmox and Cloud Director): Add a connection - a name, the company it belongs to (one per connection), how often the collector should report and how late it may be before it counts as overdue. Saving shows the command to run on the Windows server and creates a narrow API key for it - one that can only submit this connection's reports.
- Assets → Servers lists the Hyper-V VMs beside vCenter, Proxmox and Cloud Director, with host, cluster, highly available and state. A Hosts and clusters view adds the hosts, the cluster nodes and their state, the cluster roles and the quorum witness type.
- For a pushed source there is no Sync button. Instead: Last report received 09:14, expected every 15 minutes, and Late or Overdue when it is not. A report that could not read part of the estate says which part, and those VMs show as not confirmed since their last good read rather than vanishing.
- Identity changes - a VM or host that came back with a new identity - are listed under the connection's Review for a person to decide (below).
- System → Background jobs lists each pushed connection beside FreeITSM's own jobs, so an overdue collector is unhealthy in the same place a stopped cron is.
scripts/Invoke-HyperVInventory.ps1, beside the existing inventory agent:
-
Read-only. It only calls
Get-*cmdlets and CIM reads - from the Hyper-V module (Get-VMHost,Get-VM,Get-VMHardDiskDrive,Get-VMNetworkAdapter), the FailoverClusters module (Get-Cluster,Get-ClusterNode,Get-ClusterGroup,Get-ClusterResource,Get-ClusterOwnerNode,Get-ClusterQuorum), andWin32_ComputerSystemProductfor each host's hardware UUID. It never starts, stops, moves or changes anything. No SCVMM, and nothing installed on the hosts. - Runs on a separate Windows management host as a scheduled task. Nothing needs installing on the FreeITSM server.
-
One report per scope. By default one report covers everything listed for the run.
-ReportPer Clustersends one report per cluster and one for the standalone hosts, so a large estate is sent in pieces, and each piece is judged on its own (The report). - A host or cluster it cannot reach is named in the report as unreadable, never left out silently.
-
-TestAccesstries each read against each host and cluster and prints what worked and what was refused, sending nothing. That is how an administrator checks the account's permissions before the first run. -
-OutputFilewrites the report and sends nothing.-Redactreplaces names, addresses, MAC addresses, file paths, notes and error text with stable placeholders. A redacted report still needs a person to read it before it is shared - identifiers, paths, error text and metadata can leak in ways no filter catches. The examples in the repository are fully synthetic, not redacted. - Certificate pinning and the same TLS handling as the inventory agent.
| Runs on | Status |
|---|---|
| Windows PowerShell 5.1 on Windows Server 2016 or later | The supported version. The Hyper-V and FailoverClusters modules are built for it |
| PowerShell 7.4 or later on Windows | Expected to work: the Hyper-V module loads natively, and FailoverClusters loads through Windows PowerShell compatibility. Not yet proven on a real estate |
| Anything on Linux or macOS | Not supported - the modules are Windows-only |
The management host needs the Hyper-V Module for Windows PowerShell and Failover Clustering Tools (Install-WindowsFeature Hyper-V-PowerShell, RSAT-Clustering-PowerShell). The script checks for both and says which one is missing.
The collector needs to read, never change. What Microsoft provides for that differs between clusters and hosts:
| Target | What to grant | Notes |
|---|---|---|
| Each Failover Cluster | Grant-ClusterAccess -Cluster <name> -User <account> -ReadOnly |
A genuine read-only cluster permission |
| Each Hyper-V host - VMs and their disks and adapters | The simplest is the host's local Hyper-V Administrators group. It also allows changes the collector never makes. A narrower grant - Enable Account and Remote Enable on the root\virtualization\v2 WMI namespace, plus Remote Management Users for WinRM - may be enough for reading. -TestAccess shows whether it is on your hosts |
Hyper-V has no built-in read-only role. The narrower grant is not yet proven on a real estate |
| Each host - hardware UUID |
Remote Enable on root\cimv2 (granted to Administrators by default), plus WinRM access as above |
Without it, the host falls back to a name-based identity (below) and says so |
A group-managed service account is the natural choice. Account names and access arrangements stay local to the installation; nothing about them is sent in a report.
⚠️ The cmdlet names, identifiers and permissions above are the ones Microsoft documents. Which identifiers hold steady through live migration, failover, export and re-import on a real cluster, and whether the narrower permissions are enough, can only be confirmed on a real estate (section 7).
POST /api/v1/virtual-estate/{connection}/reports (new; permission virtual_estate.create, which nothing else grants). It reuses the inventory ingest's rules rather than inventing new ones:
| Field | Meaning |
|---|---|
schema_version, observed_at
|
As the inventory ingest: an older report than the newest applied for the same scope is stale; the same time and content is replayed (safe to retry for ever); the same time with different content is a conflict |
scope |
Which clusters and standalone hosts this report covers. A report only ever marks objects in its own scope as not seen |
collector |
Name, version, PowerShell version and the server it ran on - shown on the connection |
sections |
clusters, cluster_nodes, cluster_roles, hosts, vms, vm_disks, vm_networks - each complete, partial or left out, as in the inventory ingest |
unreadable |
The hosts or clusters that could not be read, with the error |
Every object carries the stable identifier the source gives it:
| Object | Identifier | Notes |
|---|---|---|
| Cluster |
Get-Cluster Id (a GUID) |
|
| Host (standalone or cluster node) |
The hardware UUID from Win32_ComputerSystemProduct
|
The name is an attribute, so a rename is a rename. A missing or obviously invalid UUID (all zeros, all F, or the same UUID on two hosts in one report) falls back to name:<host name>, marked name-based identity on the page |
| VM |
Get-VM Id (a GUID) |
Kept by register and restore imports; new after a copy import |
| Cluster role |
Get-ClusterGroup Id (a GUID) |
Each VM also says whether it is highly available (IsClustered), and for one that is, its cluster role and its possible owners - the nodes the cluster may run it on. Each object carries its current owner node or host, its state, and what was readable about it (CPU, memory, disks, guest addresses). Anything the collector could not read is sent as unknown, never as empty or zero.
Removal is not deletion. A VM missing from a complete report whose scope covers its last host is marked not seen since that report, not deleted. A VM on an unreadable host is left exactly as it was, marked not confirmed. Only an administrator, or a retention setting (off by default), removes not-seen objects. This is acceptance criterion 4, and it is stricter than today's Proxmox and Director syncs on purpose.
An identifier can change when the machine has not: a copy import gives a VM a new Id, a motherboard replacement gives a host a new UUID. FreeITSM never decides that on its own, and never merges by name.
When a complete report shows an object with a new identifier and an object of the same kind and name in the same scope stops being seen, FreeITSM raises an identity change in the connection's Review:
VM PAYROLL-01 came back with a new identity. The old one was last seen at 09:14. Same machine · Different machine
- Same machine moves the old record's history - first seen, placement history and, once Part B exists, its CI binding - onto the new identity, and records who decided, when and from what. The CI keeps its relationships, owners and everything people added.
- Different machine keeps both: the old one stays not seen, the new one is a new object.
- A decision is made on what the person saw. If either object has changed since the case was raised - the old one reappeared, the new one went away - the decision is refused and the case shows the new state.
- Nothing is lost while a case waits. The new object is visible as a new VM straight away. Only the joining-up waits for a person.
- A host with a name-based identity puts every change of name into the review list, because for that host the name is all there is.
New tables named for the estate rather than for Hyper-V: virt_connections, virt_clusters, virt_hosts, virt_vms, virt_cluster_roles, virt_vm_disks, virt_vm_networks. Each has connection_id, the source's external_id (unique per connection), identity_kind (source or name), first_seen_at, last_seen_observed_at, not_seen_since, and tenant_id from the connection. A VM's current host and cluster are columns; each change of placement is a row in virt_placements, so live migrations and failovers have a history. virt_reports records each report received (scope, observed time, outcome per section), and virt_identity_reviews holds the identity changes, with the decision, who made it and when.
The existing vCenter, Proxmox and Cloud Director data stays where it is for now. Moving them onto this model is a follow-up, done one at a time, so that Parts B and C then apply to them too (section 10).
Assets → Settings → Hyper-V → (a connection) → CMDB, off by default:
- Choose the classes: which of your existing CMDB classes a cluster, a host, a VM and a cluster role become. Storage gets a slot later. If the install has no suitable class, FreeITSM offers to create one, but never chooses for you.
- Choose the relationship types: member of (host → cluster), runs on for a highly available VM (→ its current owner node), runs on for a VM that is not highly available (→ its host), and role in (cluster role → cluster). Again your existing types, or offered ones with the right impact settings. The two kinds of runs on are separate types, because only the first can fail over (Part C).
- Choose what the sync may change: whether CI names follow the source (on by default). Nothing else on a CI is ever written: discovered facts are shown, not stored (below).
- Preview. FreeITSM works out what it would do and lists it: create these CIs, keep these already bound, possible matches to confirm (an existing CI of that class with the same name), and conflicts to resolve. Nothing is written yet.
- Confirm the possible matches one by one - Bind to this CI or Create a new one. A name is a suggestion, never proof (the issue's own rule).
- Turn it on. From then on each report keeps the CMDB in step.
A source-bound CI shows a From discovery card on its page - Hyper-V - Production cluster, on NODE-B, running, highly available, last seen 09:14 - read live from the virtual estate, the same way #201's From the asset card reads the asset.
-
The binding is the identity. A new table,
cmdb_object_sources, ties a CI to one discovered object - one live binding per CI and per object. Each row is one period, never deleted, with who or what started and ended it, as in #201's link table. A rename, a live migration or a failover changes the object, not the binding, so the same CI is kept (acceptance criterion 3). A reviewed rebinding (Part A) keeps the binding, because it moves the history onto the new identity. -
Only the source's own things are changed. The sync may update the CI's name (if allowed in step 3) and manages only the relationships it created - recorded in
cmdb_relationship_sources. Owners, criticality, every property, and every relationship people added (the VM's applications and services) are never touched. Those existing downstream relationships are what make impact reach the business (criterion 6). - Discovered facts are shown, not copied. CPU, memory, state, host and cluster appear on the From discovery card, read live. No CI property is filled from discovery, so there is no second copy to drift.
- Migration moves one edge. When a VM's host changes, the sync replaces its managed runs on relationship; it never creates a second CI. A VM that becomes highly available, or stops being, has its managed relationship switched to the other runs on type.
-
Relationships, never containment. Nothing is filed under a host with
parent_id, so deleting a host's CI can never cascade to its VMs (the issue's point). - Missing is not deleted. A VM that is not seen keeps its CI, marked as such; an administrator decides. Ambiguous changes go to the review list, never resolved silently (criterion 5).
-
A person deletes a bound CI: the binding ends (
ci_deleted), and the object goes to the review list with Create a new CI or Leave unbound. It is never recreated silently on the next report. - Companies. A source-bound CI is created in the connection's company, and the CMDB's existing same-company rule applies to every relationship the sync makes.
- Who may do it. Setting up the mapping needs the Hyper-V settings permission and write access to the CMDB. Confirming matches and review cases needs the same.
- Assets stay separate. If a VM is also an asset - from the agent, Intune, a spreadsheet or a REST tool - #201's link joins the asset and the CI. Discovery never creates assets, and no guest agent is needed for anything here. Suggesting which asset a discovered VM might be is possible later, as a suggestion through #201's link service, never as an automatic link.
A two-node cluster, NodeA and NodeB. VM1 is a highly available role currently on NodeA, and the Payroll application depends on VM1. VM2 also runs on NodeA but is not clustered. Today, "If NodeA failed" says VM1, VM2 and Payroll are all affected, and says nothing about the cluster now having one node left.
Two small additions to the CMDB, both usable with or without discovery:
- A relationship type can be marked redundant membership - for member of. Losing some members of the group grades the group redundancy reduced; losing all of them grades it affected.
- A relationship type can be marked placement with failover - for runs on of a highly available role only. When the host fails, the CI placed on it is may be interrupted if its group still has another member it may run on that is not known to be down, and affected if it has none. A plain runs on (VM2) stays what it is today: a hard dependency, so VM2 is affected.
"Another member it may run on" means the members of the host's redundant group, narrowed to the role's possible owners when discovery knows them.
| Grade | Means | Example |
|---|---|---|
| Affected | Nothing in the model suggests it survives | VM2 when NodeA fails. The cluster when both nodes fail |
| May be interrupted | It may stop. The model shows another node it may restart on, and says nothing about whether that will work | VM1 when NodeA fails, with NodeB still a member |
| Redundancy reduced | One or more members of a redundant group are lost. This is not a statement that it keeps working - the model does not know quorum, the witness, storage, capacity or current data | The cluster when one of two nodes fails |
| Unknown | The model does not have the facts to say | A node whose state the last report could not read |
A CI can be reached along several paths. The rule:
- Each path gives a grade. Going downstream along an ordinary dependency, a CI takes the grade of what it depends on (if VM1 may be interrupted, Payroll may be interrupted).
- The CI's grade is the most severe established grade among its paths: affected, then may be interrupted, then redundancy reduced. A possible failover on one path never softens a hard dependency on another: if Payroll also depended directly on VM2, it would be affected.
- Unknown is never hidden by another grade. If any path to a CI depends on a missing fact, the CI shows its grade and "unknown via …" with that path. A CI reached only through missing facts is unknown.
- Every grade shows the path that gave it, as the panel already shows "reached via" today.
Whenever a redundancy rule was used, the panel ends with a Limits of this analysis line listing what the model does not have: quorum and witness, storage (Cluster Shared Volumes are not modelled yet), capacity, placement rules, and - for members with no discovered state - that their state is assumed. It is not dismissible.
CMDB → Settings → Impact:
- Members with no recorded state (a cluster drawn by hand, with no discovery): Assume available, and list the assumption (default) or Treat as unknown. Both keep the assumption visible; the second is stricter.
- The two new relationship-type flags are ticks on CMDB → Settings → Relationship types, off by default, so an upgraded install's impact results do not change until someone sets them.
What it deliberately does not do:
- Predict that a failover will succeed. It has no knowledge of capacity, placement rules or quorum maths, and says so. "May be interrupted" is as far as it goes - the issue's own words.
- Act on anything. It is a what-if view. It never opens or closes incidents, and never changes a status-page service. Modelled impact stays separate from an observed outage (criterion 6).
| # | Criterion | Where |
|---|---|---|
| 1 | Hyper-V VMs beside the other sources, with placement and freshness | Part A |
| 2 | Mapping enabled after reviewing matches, classes and relationship types | Part B, steps 1-6 |
| 3 | Imports, renames and migrations keep identities, no duplicates, manual data kept | Part A (identifiers, reviewed rebinding), Part B (The rules) |
| 4 | A failed or partial read keeps previous data as stale or unknown | Part A, Removal is not deletion |
| 5 | Updates and removals scoped to the connection and company; ambiguity to review | Parts A and B |
| 6 | Impact reaches linked applications and services, explains the path, grades the outcome | Part C |
| 7 | Tests: migration, node failure with failover, cluster-wide loss, stale reads, conflicts, isolation | Section 7 |
| 8 | Read-only collection, protected credentials, narrow ingest, nothing changes until enabled | Part A (the collector, its permissions and the key), Parts B and C (off by default) |
I do not have Hyper-V or a Failover Cluster to test against, so this is built to be testable without one:
- FreeITSM's side is tested with reports, not hosts. Every scenario in criterion 7 is a sequence of reports: a live migration (the same VM id on a new host), a node down with failover of a highly available VM, a non-clustered VM on a failed node, a cluster-wide loss, a partial read, an unreadable host, a renamed VM, a renamed host, a copy-imported VM with a new id, a host with a new hardware UUID, two companies.
-
Synthetic reference scenarios in
tests/fixtures/virtual-estate/, made by a generator so nobody's estate is involved: small (3 standalone hosts, 40 VMs), medium (4 clusters of 4 nodes plus 6 standalone hosts, 800 VMs) and large (20 clusters of 16 nodes, 8,000 VMs). The load test times ingest, the Servers page and the impact walk on each, and the results are published on the wiki. -
The collector is tested where it runs. Its reads can only be proven against a real estate.
-TestAccessand-OutputFileare there so anyone who can run it locally can check it without sending anything. Until that happens, the collector is marked not yet proven on a real estate. - Optional: a small Windows Server cluster in a cloud VM with nested virtualisation, if a live end-to-end run is ever needed.
-
vCenter's sync deletes on a partial read. If the host lookup fails,
api/assets/get_vcenter.phpswallows the error, deletes the ESXi host rows and writes VMs with an empty host. Proxmox and Director refuse to delete on a partial read; vCenter should too. A small fix in its own right. - Two wiki pages were out of date, and are now corrected: REST API - Assets said a key's company scope does not restrict assets (it does), and Background job health - design said the Proxmox and Cloud Director jobs could overlap (their crons now take a lock).
The questions this page asked, and the answers on the issue. They are design preferences, not a description of anyone's environment.
| # | Question | Answer | What it changed |
|---|---|---|---|
| 1 | Scale | Multiple standalone hosts and clusters; synthetic sizing, no real counts | Reference scenarios and load test; per-cluster reports |
| 2 | Where the collector runs | A separate Windows management host, scheduled task; document PowerShell versions and minimum read permissions |
Supported versions, minimum permissions, -TestAccess
|
| 3 | Testing and reports | Possibly local testing of a reviewed, read-only collector, not yet committed; shared examples fully synthetic; redacted output still needs manual review | Synthetic examples in the repo; -Redact documented as needing a check |
| 4 | Companies | One company per connection | Fixed in Part A |
| 5 | VM identity | Register/restore keep the Id, a copy makes a new one; changed identities to a reviewed rebinding |
Reviewed rebinding |
| 6 | Storage | Useful later; not a blocker if its absence is visible | Out of phase 1; listed in the limits |
| 7 | CMDB mapping | Configurable onto existing classes; manual data intact | Part B steps 1-3 |
| 8 | The inventory agent | Optional; external sources should be able to use the #201 link | Part B, Assets stay separate; #201 Part A |
| 9 | Grades | The four fit, with the qualifications above | Part C |
- Part A - the collector, the report endpoint, the virtual estate tables, Hyper-V in Servers, identity review, and pushed-source health. Usable on its own: the issue's first acceptance criterion.
- Part C - redundancy-aware impact. Independent of A and B (it works on CMDB relationships people draw by hand), so it comes next.
- Part B - source-bound CIs, the preview and review list. Needs Part A, and uses the patterns #201 puts in place.
- Later, one at a time: move vCenter, Proxmox and Cloud Director onto the virtual estate model, so Parts B and C apply to them too.
Each step ships in a release once it is done, and each is off until an administrator turns it on.
- Any VM or cluster control - starting, stopping, migrating, draining, failing over. Read-only, as the issue asks.
- SCVMM, and anything installed on the hosts themselves.
- Monitoring. Discovery is not a health check: it never opens, closes or changes incidents or status-page services.
- Predicting failover success - capacity, placement rules, anti-affinity, quorum arithmetic.
- Storage dependencies (Cluster Shared Volumes, Storage Spaces Direct) in phase 1 - listed as a limit instead.
- Inventing application dependencies from names or anything else. Applications and services are linked by people.
- Moving the existing hypervisors onto the new model - a follow-up, step 4 above.
#201 ties a CI to an asset; this design ties a CI to a discovered object. They follow the same rules on purpose, so building one never forces a rewrite of the other:
| Concern | #201 | This design | The shared rule |
|---|---|---|---|
| What the CI is tied to | One asset (asset_ci_links) |
One discovered object (cmdb_object_sources) |
Both are periods, never deleted, with actor and source at both ends. One live tie per side |
| Facts shown on the CI | From the asset card | From discovery card | Read live, labelled, never copied into CI properties |
| A reader without access | Restricted marker | Restricted marker (the estate lives under Assets) | The association never reveals a record the reader cannot open |
| Ambiguity | Possible matches, Replace, merge choice | Review list, reviewed rebinding | Suggestions are never proof. A decision carries the state it was made on and is refused if that changed |
| Deletion | Deleting the CI ends the link | Deleting the CI ends the binding; the object goes to review | Nothing is deleted because of the other record |
| Impact | The asset shows the CI's analysis, grades included | Adds the grades | Neither the asset nor the link is ever in the graph |
A VM's CI can have both at once: bound to the Hyper-V VM, and linked to the asset the same machine has from any inventory source.
- Proxmox and Cloud Director - Developer Guide · Proxmox VE · VMware Cloud Director · Assets
- REST API - Assets · Inventory agent · Inventory freshness · Asset review
- CMDB · CMDB - Developer Guide · CMDB impact analysis - Developer Guide
- Linking an asset to its configuration item - design (#201) · Background job health - design (#182)
- Multi-tenancy - Developer Guide
FreeITSM — an open-source IT Service Management platform · github.com/edmozley/freeitsm · MIT licence
- Installation
- ⏰ Scheduled tasks (cron jobs)
-
Architecture
- ↳ 🧹 Root folder tidy (URL routing)
- ↳ 🔗 Database Integrity (FKs & constraints)
- ↳ 🛠️ Database Verification — Developer Guide
- ↳ 🧩 Service Layer (UI + API share code)
- ↳ 🪤 Code traps (the TRAP: comments)
- ↳ 📝 Commenting for the Engineering wiki
- ↳ 🛠️ Engineering wiki scanner - Developer Guide
- ↳ 📊 Service-Layer progress
- 🤝 Contributing (read before a pull request)
- 🧪 Developer tests
- AI Providers
- Internationalisation (i18n)
- Timezones & Time Handling
- 📅 Date & Time Formats
- 💱 Currencies
- 🧭 The sidebar (optional module rail)
- 🧩 Preference templates
- 🔀 Display order (modules, Watchtower cards) — Developer Guide
- Theming & Dark Mode
- 🗂️ Recent — getting back to what you were doing
- ⌨️ Command palette (⌘K)
- 🔍 Searching inside tickets
- 📄 Attached documents
-
Mobile‑Friendly
- ↳ 🎫 Mobile: Tickets
- ↳ 💻 Mobile: Assets
- ↳ 📅 Mobile: Calendar
- ↳ 📚 Mobile: Knowledge
- ↳ 🚦 Mobile: Service Status
- ↳ 🗼 Mobile: Watchtower
- ↳ 🧩 Mobile: Problem Management
- ↳ 🔁 Mobile: Change Management
- ↳ 💿 Mobile: Software
- ↳ ✅ Mobile: Tasks
- ↳ 📝 Mobile: Forms
- ↳ 📄 Mobile: Contracts
- ↳ 📄 Mobile: Domains
- ↳ 📄 Mobile: People
- ↳ 🚀 Mobile: Projects
- ↳ 🗂️ Mobile: Files
- ↳ 🎓 Mobile: LMS
- ↳ 🗺️ Mobile: CMDB
- ↳ 🗺️ Mobile: Network Mapper
- ↳ 🧭 Mobile: Process Mapper
- ↳ ⚙️ Mobile: Workflow
- ↳ 🖥️ Mobile: System
- ↳ 📊 Mobile: Reporting
- ↳ 📖 Mobile: System Wiki
- ↳ 🙋 Mobile: Self-Service Portal
- ↳ 🧰 Mobile: Techniques & Tricks
-
Security
- Layer 1 — which modules you can enter
- ↳ 🧩 Module Access Control
- ↳ 🛠️ Module Access — Developer Guide
- ↳ 🔌 Switching modules off
- ↳ 🛠️ Module availability — Developer Guide
- ↳ 📐 Module availability — design (#217)
- Layer 2 — what you can administer
- ↳ 🎭 Roles & Permissions
- ↳ 🛠️ Roles — Developer Guide
- ↳ 🔤 Why capabilities are constants
- Layer 3 — the System module
- ↳ 🔑 Admin Access Control
- Hardening
- ↳ 📄 Security review response 2026-08
- ↳ 🛡️ Security hardening 2026-08
- ↳ 🛠️ Security hardening 2026-08 — Developer Guide
- ↳ 🛡️ Round three — plain English
- ↳ 🛠️ Round three — Developer Guide
- ↳ 🛡️ CSRF protection (S4) — Developer Guide
- Single Sign-On (SSO)
- 🗂️ LDAP & Active Directory
- 📇 CardDAV contact sync
- Browser Extension
- API Reference
-
🔌 REST API — how it works
- ↳ 📋 Proposal: custom fields, inventory import and change evidence (#194)
- ↳ 🛠️ Custom fields, evidence + inventory - review (#195-#197)
- ↳ 🛠️ Long forms in a task's Work view - review (#198)
- ↳ 🎫 REST API: Tickets
- ↳ 💻 REST API: Assets
- ↳ 🔴 REST API: Problems
- ↳ 🟠 REST API: Changes
- ↳ 📚 REST API: Knowledge
- ↳ ✅ REST API: Tasks
- ↳ 🗄️ REST API: CMDB
- ↳ 📜 REST API: Contracts
- ↳ 🗓️ REST API: Calendar
- ↳ 💿 REST API: Software
- ↳ 🌐 REST API: Domains
- ↳ 🚦 REST API: Service Status
- ↳ ☀️ REST API: Morning Checks
- ↳ 📝 REST API: Forms
- ↳ ⚙️ REST API: Workflow
- ↳ 🏷️ REST API: Cost centres
- ↳ 🧑💼 REST API: Analysts
- ↳ 🗺️ REST API: Network Mapper
- ↳ 🧭 Using the API docs page
- ↳ 📐 OpenAPI specification
- ↳ ✅ OpenAPI: kept correct
- ↳ 🛠️ Maintaining the catalogue
- Watchtower
-
Tickets
- ↳ 🛠️ Inbox.js map — Developer Guide
- ↳ 🛠️ Tickets settings — Developer Guide
- ↳ 🗂️ The ticket workspace
- ↳ 📊 The ticket table
- ↳ 📋 Rota copy and paste — Developer Deep Dive
- ↳ ✅ Checklists & SOPs
- ↳ ☑️ Mandatory fields
- ↳ 🧾 Custom ticket fields
- ↳ 🏷️ Ticket categories
- ↳ 👥 Assigning tickets to a team, and escalation
- ↳ 🏢 One board across every company
- ↳ Mailbox Authentication
- ↳ 📤 Email send log
- ↳ 📎 Attachment size limits and large emails
- ↳ Basic IMAP mailboxes
- ↳ Email rendering & images
- ↳ SLA Management
- ↳ WhatsApp channel
-
↳
✈️ Telegram channel - ↳ ⭐ CSAT company scope and filters — Developer Guide
- ↳ 👥 Microsoft Teams channel
- ↳ 🗨️ Mattermost channel
- ↳ 💬 Web chat channel
- ↳ 🟣 Slack channel
- ↳ 🔗 Linking tickets
- ↳ ⓘ Record previews
- ↳ 📝 Ticket notes: internal or shared
- ↳ 🗒️ Canned responses
- ↳ ✉️ Limiting replies to particular senders
- ↳ 📨 Telling the analyst a ticket is theirs
- ↳ ✍️ Email signatures
- ↳ 🌐 The public web address
- ↳ 🔢 Ticket numbering
- ↳ 🙋 Raising a ticket for someone else
- ↳ 🔀 Merging tickets
- ↳ 🔒 Confidential tickets
- ↳ 👥 Portal managers
- ↳ 👁 Who has seen a ticket
- ↳ 📜 Reading long tickets
- ↳ ⑂ Splitting tickets
- ↳ ✅ Selecting several tickets
- ↳ 🗂️ The folder pane
- ↳ 🔽 Just my tickets, or no closed ones
- ↳ 🛠️ Snoozing tickets — Developer Guide
- ↳ 👥 Collision detection
- ↳ ⏱️ Time tracking
- ↳ 📄 Case reports - a ticket and its work as one PDF
- ↳ 📅 Scheduled work in your own calendar
- Problem Management
- Tasks
- 🚀 Projects
-
Assets
- ↳ 🏢 Moving an asset between companies
- ↳ 📍 Shared asset locations
- ↳ 🧑💼 Assigning assets to analysts
- ↳ 📆 Warranty and lease alerts
- ↳ 📄 Leases
- ↳ 🔭 Saved table views
- ↳ 🖨️ Recording anything, and importing it
- ↳ 🧩 Asset type layouts
- ↳ 🕒 Inventory freshness
- ↳ 🔗 Linked configuration item
- ↳ 🔌 Connected assets
- ↳ 🔍 Asset review
- ↳ 🏷️ QR asset labels
- ↳ 📋 Who holds what, and handover documents
- ↳ 🖥️ The inventory agent (PowerShell)
- ↳ 🗄️ Proxmox VE servers
- ↳ ☁️ VMware Cloud Director servers
- ↳ 🪟 Hyper-V and Failover Clusters
- ↳ 🔗 Linking equipment to tickets
- ↳ ☑️ Follow-up tasks on a ticket
- Knowledge
- Change Management
- Calendar
- Morning Checks
- Reporting
- Software
-
Forms
- ↳ 🎨 The form designer — Developer Guide
- ↳ 📐 Layout & the grid — Developer Guide
- ↳ 🗂️ Collections — grouping submissions
- ↳ 📄 Submissions as PDFs
- ↳ ⚡ What happens next — a form's own actions
- ↳ 🛠️ Sections & conditional logic — Developer Guide
- ↳ 🛠️ Lookup fields — Developer Guide
- ↳ 🛡️ Catalogue request approvals
- People
- Domains
- 🗂️ Files
- Contracts
- Service Status
- 🔔 Notifications
- 🚨 War Room
- Self-Service Portal
- LMS
- Process Mapper
- CMDB
- Network Mapper
- Workflows
- Issue trackers (Jira, Azure DevOps)
- 📡 Zabbix (monitoring)
- 💬 Microsoft Teams alerts
- 📞 3CX (phone system)
- System
-
Overview
- ↳ 📊 Progress tracker
- ↳ Concepts & vocabulary
- ↳ Email routing & mailboxes
- ↳ Settings: global vs per-company
- ↳ Users & self-service
- ↳ Staff cross-company access
- ↳ 🏢 One board across every company
- ↳ Worked examples
- ↳ Pitfalls & gotchas
- ↳ Scope: what it's for
- ↳ 🛠️ Developer Guide (make a module multi-company)
- ↳ 🗄️ Case study: CMDB (a linked graph)
- ↳ 🧪 Test harness (prove it's isolated)
- What this is
-
🐞 Bugs resolved
- ↳ ⚙️ #214 A "contains" condition did not match capitals
- ↳ ⚙️ #216 A workflow email from a chosen mailbox always failed
- ↳ 🐳 The Docker image was missing PHP extensions
- ↳ 🌐 A failed DNS lookup read as a missing record
- ↳ 🔒 A link could change data
- ↳ 🔒 Access outlived its removal
- ↳ 🔒 Knowledge articles could run scripts
- ↳ 🔒 Knowledge writes ignored permissions
- ↳ 🔒 Company boundaries (Oct 2026)
- ↳ 🔒 Smaller gaps (Oct 2026)
- ↳ 🔒 Escaped text could leave its attribute
- ↳ 🔒 Task descriptions were not cleaned
- ↳ 🔒 Task documents ignored company scope
- ↳ 🔒 Repeating tasks ignored company scope
- ↳ 📬 #183 Mailbox checks overlapped
- ↳ 🌐 #188 Transfer lock shown off when unknown
- ↳ ✅ #184 Subtask names and Overdue
- ↳ 🧩 #186 Teams: the buttons did nothing
- ↳ 🔒 Text in a change could run script
- ↳ 🔒 AI settings changed without the AI permission
- ↳ 🚪 #178 Deleting an analyst failed
- ↳ 🎫 A team with no departments showed 0 tickets
- ↳ 🚀 Projects: adding a phase did nothing on Save
- ↳ 🔒 Leavers could still sign in
- ↳ 🖼️ Logo and courses broke on Apache with PHP-FPM
- ↳ 🔢 Chat tickets ignored your ticket numbering
- ↳ 📅 Dates shown as a dash, or in server time
- ↳ 🔒 Assets → Users showed people from other companies
- ↳ 🔒 Restricted analysts could read other modules' data
- ↳ 🖼️ Replies with a picture in the thread failed to send
- ↳ 📎 Reply attachments never reached the customer
- ↳ 🛠️ Outbound email attachments — Developer Guide
- ↳ 🔑 A global SSO provider was missing from the portal
- ↳ 🔀 Behind a proxy, the SSO redirect said http
- ↳ ✏️ The portal tagline moved when you saved it
- ↳ 🎨 The portal settings screen forgot what you saved
- ↳ 🛡️ The approvals inbox said "Error" and nothing else
- ↳ 📄 A table's answers were missing from the PDF
- ↳ ◉ A single-select column let you tick every option
- ↳ 📐 The portal ignored a form's field widths
- ↳ 📋 The tasks board stopped taking clicks
- ↳ 🗂️ #121 The index list is out of date after upgrading
- ↳ 📅 #133 The calendar subscription was empty
- ↳ 📋 #131 Tasks always reopened on the board
- ↳ 💥 #129 Every page returned HTTP 500 after upgrading
- ↳ 🐳 #127 A PHP warning above the System page
- ↳ 🕐 #126 Notes stamped with the server's clock
- ↳ 🌍 Storing every date in UTC
- ↳ 🚪 The portal was down for everyone signed in
- ↳ ⚙️ #120 Workflow notes could never be written
- ↳ ⚙️ #123 Three errors when running Database Verification
- ↳ 📝 #122 The description box was a stub in the corner
- ↳ 💣 Demo data deleted real accounts
- ↳ 🔐 #117 Sign-in redirected to the wrong address
- ↳ 🎨 #108 The priority dot was invisible
- ↳ ⏱️ #116 Time logged from the right-click menu
- ↳ 🔑 #114 API keys refused by our own guard
- ↳ 🗂️ #110 Assigning a task told nobody
- ↳ 🚪 #107 Signed out while still working
- ↳ 📎 #103 "Share with Requester" reached nobody
- ↳ 🔍 #102 Search found nothing for hyphens
- ↳ 🪟 #101 Source code editor opened behind
- ↳ ☑️ #88 Subtasks could not be ticked off
- ↳ 💻 #84 Asset deep link selected nothing
- ↳ 🎫 #79 A new ticket arrived with no status
- ↳ 📧 #79 A ticket from email did not say so
- ↳ 🔔 #78 Bell opened to nothing
- ↳ 📬 #77 Mail only collected from Inbox
- ↳ 🔐 #74 The default password could not be changed
- ↳ 🚦 #70 Renaming an impact level
- ↳ 📤 #67 App-only mailboxes could not send
- ↳ 📭 #45 Verify only ever worked for Microsoft
- ↳ 📭 #45 IMAP reported as not authenticated
- ↳ ✉️ An email template stopped escaping itself
- ↳ 🕐 The portal dashboard showed the wrong time
- ↳ 🔢 The folder said 99 and the list showed 96