In this phase, the most important is to update the context management APS type and respective backend service to implement
the RatedDataSupport abstract APS type and define the exportRatedData
method.
To support the designed algorithm, you will also update the VPS APS type and service.
Continue the demo project started in the previous phase.
In this document:
An application that imports VRD must implement the VendorContract
APS type as explained
in the Managing Software Vendor Contracts document. Since the resource model of this simple application does not have
a reseller profile, the only suitable APS type to implement the required abstract APS type is the cloud
APS type.
For testing purposes, you can assign a default value to the inherited vendorContract
property.
Besides that, the APS application instance must create an auxiliary folder called deleted
, which is required by
the unprovision
method of the Context logic.
To implement all of the above, follow these steps to configure the scripts/clouds.php
file:
In the cloud
class declaration, add the implementation of the VendorContract
APS type:
/**
* Class cloud presents application and its global parameters
* @type("http://aps-standard.org/samples/vrd/cloud/1.0")
* @implements("http://aps-standard.org/types/core/application/1.0","http://odin.com/init-wizard/config/1.0","http://odin.com/aps/types/oa/vendor-contract/1.0")
*/
class cloud extends APS\ResourceBase
Add the provision
method to create the auxiliary folder called deleted
:
public function provision() {
$deletedDir = "deleted";
if (!file_exists($deletedDir))
{
mkdir($deletedDir);
}
}
(Optional, for auto-testing only) Assign a default value to the inherited vendorContract
property:
Declare this property in the class without APS annotations:
public $vendorContract;
In the provision
method, assign a default value to this property, for example:
$this->vendorContract = "1234-5678";
In accordance with the design assumptions, the VPS provisioning logic must save
all changes of VPS resource limits in JSON files inside a folder created for the VPS owner.
This logic must be implemented by the standard methods inside the scripts/vpses.php
file as follows.
The provision
method is responsible for creating a VPS. So it must also create a JSON file for every
new VPS:
public function provision()
{
$accountId = $this->context->account->aps->id;
$vpsFile = fopen($accountId."/".$this->aps->id.".json", 'w');
fwrite($vpsFile, $this);
fclose($vpsFile);
}
Keynotes:
Using the link from the VPS to the management context, and then the link from the latter to the account, the method defines the account APS ID. This is necessary to determine the account folder name.
Inside the account folder, the method creates a new JSON file and saves the VPS properties there.
The unprovision
method must remove the JSON file along with the respective VPS:
public function unprovision()
{
$accountId = $this->context->account->aps->id;
## Remove the VPS file with resource parameters:
unlink($accountId."/".$this->aps->id.".json");
}
The configure
method is called to change VPS properties. If this concerns limits on resources that are used
to calculate charges, then such properties must be updated accordingly in the VPS JSON file:
public function configure($new)
{
$accountId = $this->context->account->aps->id;
file_put_contents($accountId."/".$this->aps->id.".json", $this);
parent::configure($new);
}
The Context
APS type is used to create a singleton APS resource in every subscription. This makes that APS type
the most suitable to implement the RatedDataSupport
APS type. The following sections show the definition of the
common part of the script and then different variations, one for the CoS model and the other for
the All levels model.
For the whole project, the following changes in the scripts/contexts.php
script file are the most important:
In the declaration of the main class context
, add the implementation of the RatedDataSupport
APS type:
/**
* Class context
* @type("http://aps-standard.org/samples/vrd/context/1.0")
* @implements("http://aps-standard.org/types/core/subscription/service/1.0","http://aps-standard.org/types/core/rated-data-support/1.0")
*/
class context extends \APS\ResourceBase
{
# ...
}
Define the provision
method that must create a folder for every subscriber:
public function provision()
{
## Create a folder for the account to simulate a cloud application
if(!file_exists($this->account->aps->id)) {
mkdir($this->account->aps->id);
}
}
For every new APS resource Context
, the method will create a folder named by the account APS ID.
Start definition of the main VRD method exportRatedData
that by a request must return a list of charges:
public function exportRatedData($lastInvoiceDate)
{
# The contents will be added in the next steps.
}
The function accepts one input argument that specifies the start date ($lastInvoiceDate
)
of the requested period. It is assumed that the end of the period will be defined by the method and returned
back so that the platform built-in VRD collector will use it in the next call as the lastInvoiceDate
argument.
At the beginning of the exportRatedData
definition, define the end of the report period. In this project,
it will be always the current date:
$endTime = mktime();
$invoiceEndDate = date("Y-m-d\Th:i:s", $endTime);
The date and time is defined here with the 1 sec accuracy only for the test purposes. In production, this must be a date without time.
In the exportRatedData
definition, prepare the structure of the response in accordance with the declaration
of this method in the RatedDataSupport APS type:
$report = array(
"lastInvoiceDate" => $invoiceEndDate,
"charges" => []
);
This is a simplified algorithm that assigns the input date to the output date parameters. Typically, the application itself determines the period appropriate to the required period.
In the exportRatedData
definition, make intermediate preparations for the main resource polling cycle:
# This will accumulate charges for every VPS:
$charges = array();
# Time interval in seconds:
$timeDelta = ($endTime - strtotime($lastInvoiceDate)) / 3600;
# The rate to convert monthly price to the price for the time interval:
$usageRate = $timeDelta / (30 * 24);
# List of JSON files containing limits on VPS resources:
$files = array_diff(scandir($dir), array('..', '.'));
# List of all accounts from the "APS-Account-Hierarchy" HTTP header:
$headers = getallheaders();
$accounts = explode(", ", $headers["APS-Account-Hierarchy"]);
Pay attention to the inline comments in the above code to get some explanations.
Complete the exportRatedData
definition:
$report["charges"] = $charges;
return $report;
In the next section, you will add a code specific to the all levels billing model. That code must be placed just before the final strings in the last step above.
In the all levels model, the application must return charges for the provider, all resellers above the customer and for
the customer itself. For this purpose, in the following example, the price assigned to the provider is increased by
10% for every account downstream the resellers chain. Therefore, the type of every returned charge must be “CHARGE”
and one more outer cycle must be added to create charges for every account specified in the received
APS-Account-Hierarchy HTTP header.
Continue editing the exportRatedData
after the Common Part Definition steps are done.
Make intermediate preparations for the main resource polling cycle:
# The template to be used for creating charges:
$charge = array(
## Charging the provider whose ID goes first.
"accountId" => $accounts[0],
"chargeType" => "CHARGE",
"currencyCode" => "USD",
"chargeStartDate" => $lastInvoiceDate,
"chargeEndDate" => $invoiceEndDate
);
The $charge
template defines common parameters that will be the same in all charges returned in response
to the current call.
Start the first cycle that must collect resource usage during the specified time interval:
foreach ($files as $filename) {
$fileContent = file_get_contents($accountId . '/' . $filename) or die("Unable to open file!");
$vps = json_decode($fileContent, true);
$vpsId = pathinfo($filename)['filename']; # File name w/o extension.
}
The above code gets in a cycle the content of every JSON file with VPS properties to calculate resource usage.
Inside the cycle, every charge is calculated as explained in the following steps.
Generate a charge for a VPS as a resource provided by the application:
$vpsCharge = $charge;
$vpsCharge["unitOfMeasure"] = "item-h";
$vpsCharge["amount"] = 1 * $usageRate; # Always one VPS here
$vpsCharge["unitPrice"] = 12.5; # Fee for 1 VPS per month
$vpsCharge["totalCost"] = round($vpsCharge["amount"] * $vpsCharge["unitPrice"], 2);
$vpsCharge["skuId"] = "VPS - Container";
$vpsCharge["description"] = "Virtual private server - Container variation";
$vpsHash = md5(json_encode($vpsCharge));
$vpsCharge["hash"] = $vpsHash;
Keynotes:
The first step for every change is to make a new structure as a clone of the $charge
template.
A VPS is charged just because it exists. You are rating it proportionally to the monthly price.
The calculated hash is based on charge fields excluding the hash field itself. This makes the hash unique and thus allows the system to use it as the identifier of the charge.
Generate a charge for CPU cores used by an active VPS:
$cpuCharge = $charge;
$cpuCharge["unitOfMeasure"] = "item-h";
## Account CPU for active VPS only:
$cpuCharge["amount"] = ($vps["state"] == "Running") ? $vps["hardware"]["CPU"]["number"] * $usageRate : 0;
$cpuCharge["unitPrice"] = 4.5; # Fee for 1 CPU core per month
$cpuCharge["totalCost"] = round($cpuCharge["amount"] * $cpuCharge["unitPrice"], 2);
$cpuCharge["skuId"] = "VPS - CPU, cores";
$cpuCharge["description"] = "Virtual private server - CPU power";
$cpuHash = md5(json_encode($cpuCharge));
$cpuCharge["hash"] = $cpuHash;
The resource is charged only for the running VPSes.
Generate a charge for the memory space used by an active VPS:
$ramCharge = $charge;
$ramCharge["unitOfMeasure"] = "MB-h";
## Account RAM for active VPS only:
$ramCharge["amount"] = ($vps["state"] == "Running") ? $vps["hardware"]["memory"] * $usageRate : 0;
$ramCharge["unitPrice"] = 0.15; # Fee for 1 MB of RAM per month
$ramCharge["totalCost"] = round($ramCharge["amount"] * $ramCharge["unitPrice"], 2);
$ramCharge["skuId"] = "VPS - RAM, MB-h";
$ramCharge["description"] = "Virtual private server - Memory space";
$ramHash = md5(json_encode($ramCharge));
$ramCharge["hash"] = $ramHash;
The resource is charged only for the running VPSes.
Generate a charge for the disk space used by a VPS:
$diskCharge = $charge;
$diskCharge["unitOfMeasure"] = "GB-h";
$diskCharge["amount"] = $vps["hardware"]["diskspace"] * $usageRate;
$diskCharge["unitPrice"] = 0.55; # Fee for 1 GB of disk space per month
$diskCharge["totalCost"] = round($diskCharge["amount"] * $diskCharge["unitPrice"], 2);
$diskCharge["skuId"] = "VPS - Storage, GB-h";
$diskCharge["description"] = "Virtual private server - Disk space";
$diskHash = md5(json_encode($diskCharge));
$diskCharge["hash"] = $diskHash;
For each VPS in the cycle, a set of charges is pushed to the list of charges that
the exportRatedData
will return later.
array_push($charges, $vpsCharge, $cpuCharge, $ramCharge, $diskCharge);
The completed cycle fills the $charges
array with the set of charges for the provider.
Outside of the completed loop, add the charges addressed to the provider to the $report
object:
$report["charges"] = $charges;
The next steps must add a set of charges for every subsidiary reseller and finally for the customer.
Define the markup rate and add the second loop to add charges for all other accounts:
## Price for every lower reseller and down to the customer will be increased by 10%:
$markUpRate = 1.1;
$markUp = $markUpRate;
for ($num = 1; $num < count($accounts); $num++) {
$accountId = $accounts[$num];
foreach ($charges as $charge) {
$newcharge = $charge;
$newcharge["accountId"] = $accountId;
$newcharge["unitPrice"] = $newcharge["unitPrice"] * $markUp;
$newcharge["totalCost"] = round($newcharge["unitPrice"] * $newcharge["amount"], 2);
$hash = md5(json_encode($newcharge));
$newcharge["hash"] = $hash;
array_push($report["charges"], $newcharge);
}
$markUp = $markUp * $markUpRate;
}
This document explains the key steps in the implementation of the vendor rated data processing. The most important
is the inheritance of the RatedDataSupport
APS type and the definition of the exportRatedData
method.