feat(stock): dynamic VPS inventory driven by live hypervisor capacity
Opt-in per product via WHMCS's native tblproducts.stockcontrol toggle.
When enabled, the module overwrites tblproducts.qty with the number of
VPSes the panel can still actually provision, derived from two
authoritative sources:
- GET /packages/{id} for the per-VPS resource footprint (memory,
cpuCores, primaryStorage, primaryStorageProfile, enabled)
- GET /compute/hypervisors/groups/{id}/resources for live
free/allocated data per hypervisor in the group
Algorithm sums min(memory, cpu, storage) across eligible hypervisors
(enabled AND commissioned AND !prohibit) for every group the product
can be placed in (default configoption1 plus every numeric value of a
Location configurable option), capped by the group-level IPv4 pool
taken as max() within a group to avoid double-counting. Storage
matching is strict against package.primaryStorageProfile; hypervisors
without the named pool contribute 0.
FAIL-SAFE INVARIANT: transient API failures return null from
Module::fetchPackage / Module::fetchGroupResources, and the orchestrator
leaves tblproducts.qty UNCHANGED in that case. Confirmed-missing
conditions (HTTP 404, package.enabled=false) return qty=0. Without this
tri-state contract the module would either zero out inventory during
API blips, or show inventory for packages that have been deleted.
Triggers:
- AfterModuleCreate: refresh + auto-accept pending order
- AfterModuleTerminate: refresh (capacity came back)
- AfterCronJob: every-2-hour safety net for out-of-band panel changes
- ClientAreaPageCart: opportunistic per-product refresh in order flow
- admin.php?action=stockRecalculate: on-demand full recalc
Shared 30s rate-limit (stockrefresh:event) coalesces provision bursts;
60s per-product limit (stockrefresh:{pid}) caps cart-page refreshes;
grpres:{id} 120s TTL caps upstream API reads per group regardless of
how often hooks fire.
Auto-accept: AfterModuleCreate calls WHMCS AcceptOrder with
autosetup=false when the parent order is still Pending. Idempotent;
already-accepted orders are skipped via strcasecmp status check.
New per-product config option stockSafetyBufferPct (configoption7,
default 10) reserves X% of each resource's max before computing fits.
Blank falls back to 10% so existing products get headroom without any
config change. Ignored for unlimited resources (max=0) and for IPv4
(no per-hypervisor max in the response).
TestConnection now probes /compute/hypervisors/groups to surface
missing compute:read scope at config time instead of as unexplained
nightly silence.
This commit is contained in:
@@ -114,6 +114,13 @@ function VirtFusionDirect_ConfigOptions()
|
||||
'Description' => 'Credit amount to add when auto top-off triggers.',
|
||||
'Default' => '100',
|
||||
],
|
||||
'stockSafetyBufferPct' => [
|
||||
'FriendlyName' => 'Stock Safety Buffer (%)',
|
||||
'Type' => 'text',
|
||||
'Size' => '5',
|
||||
'Description' => 'Reserved headroom applied per resource when calculating stock. Only effective when the WHMCS Stock Control toggle is enabled on this product. 0-100; ignored for resources with no quota set in VirtFusion. Default is 10% if left blank.',
|
||||
'Default' => '10',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -135,6 +142,28 @@ function VirtFusionDirect_TestConnection(array $params)
|
||||
$httpCode = $request->getRequestInfo('http_code');
|
||||
|
||||
if ($httpCode == 200) {
|
||||
// Probe the compute scope: stock control depends on read access to
|
||||
// /compute/hypervisors/groups. A token scoped only to /servers will pass the
|
||||
// /connect check above but silently break nightly stock recalculation, so we
|
||||
// surface the missing scope at config time rather than a week later.
|
||||
$groupsProbe = $module->initCurl($password);
|
||||
$groupsProbe->get($url . '/compute/hypervisors/groups?results=1');
|
||||
$groupsHttp = (int) $groupsProbe->getRequestInfo('http_code');
|
||||
|
||||
if ($groupsHttp === 401 || $groupsHttp === 403) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'VirtFusion OK but API token lacks read access to /compute/hypervisors/groups (HTTP ' . $groupsHttp . '). Stock Control will not work — re-issue the token with compute:read scope.',
|
||||
];
|
||||
}
|
||||
|
||||
if ($groupsHttp !== 200) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'VirtFusion OK but /compute/hypervisors/groups returned HTTP ' . $groupsHttp . '. Stock Control may not work correctly.',
|
||||
];
|
||||
}
|
||||
|
||||
// Also verify PowerDNS health when the DNS addon is activated, so the
|
||||
// admin's Test Connection button reflects the full provisioning path.
|
||||
if (PowerDnsConfig::isEnabled()) {
|
||||
|
||||
Reference in New Issue
Block a user