Provisioning Logic

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:

Application

An application that imports VRD must implement the VendorContract APS type as explained in the Tracking 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:

  1. 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
    
  2. Add the provision method to create the auxiliary folder called deleted:

    public function provision() {
         $deletedDir = "deleted";
         if (!file_exists($deletedDir))
         {
             mkdir($deletedDir);
         }
    }
    
  3. (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";
      

VPS

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.

  1. 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.

  2. 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");
    }
    
  3. 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);
    }
    

Context

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.

Common Part Definition

For the whole project, the following changes in the scripts/contexts.php script file are the most important:

  1. 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
    {
       # ...
    }
    
  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

  7. 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.

All Levels Model

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” header. Continue editing the exportRatedData after the Common Part Definition steps are done.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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;
    
  7. 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.

  8. 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.

  9. 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;
    }
    

Conclusion

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.