VPS Management

Since VPSes are bound to offers, we need to modify the existing VPS views as illustrated in this document.

../../../../../_images/offer-step-model1.png ../../../../../_images/offer-step-meta1.png ../../../../../_images/offer-step-provision1.png ../../../../../_images/offer-step-presentation-b.png ../../../../../_images/offer-step-deploy1.png ../../../../../_images/offer-step-provisioning1.png

Overview

In the following updates we assume the following:

  • Every VPS is linked with an offer as specified by the updated resource model.

  • When creating or editing a VPS the limits on VPS parameters cannot exceed the limits set in the offer the VPS is linked with.

  • The OS for a new VPS will be defined by the selected offer. So, we do not need to have a predefined OS selection list.

Unlike the offer management in PCP, the VPSes are managed in UX1. Respectively, you will use the single page application technology in the following updates.

Continue Your Demo Project

This section continues the demo project from the previous step.

You should update the server-new-1 (server creation) and server-edit (server editor) views.

Server Creation

During the VPS creation process, we must present a list of offers for selection. Each offer has its own limits on VPS properties. Thus, the server-new-1.js file needs substantial updates.

  1. In the required and main callback function, add some more aps and dojo modules required by the updated code and also add the newoffer.json file to initiate the current offer control object:

    require([
       //... leave the existing modules intact and add the following ones:
       "dojo/when",
       "aps/ResourceStore",
       //...,
       "aps/json!app/newoffer.json" // "app" is mapped to "ui/" in bootstrapApp.html
    ], function (
       // ...,
       when,
       Store,
       //...,
       newOffer
    ) {
       var self; // A global object to keep some objects for all functions
       return declare(_View, {
          init: function() {
             self = this;
             //... function contents
          },
    
          onNext: function() { /*... function contents */ },
          onContext: function() { /*... function contents */ }
    
    });
    

    In the above code, you will also find a declaration and initialization of a global object self to keep in it some objects for all functions of the module you are developing.

  2. In the init method, change data sources as follows.

    • Remove the oses object since now an OS will be assigned through the selected offer.

    • Define a memory cache that will contain a list of offers to propose to a user:

      self.offerCache = new Memory({
         data: [],
         idProperty: "aps.id"
      });
      
    • Define an offer control object that will store a JSON representation of the current offer. Initialize the object using the values from the newoffer.json file:

      self.offerCtrl = getStateful({
         model: newOffer
      });
      

      The values of the current offer must be used as the limits to the properties of the new VPS.

  3. In the init method, add a function activated on offer change. This handler must change the values of the current offer representation and bind the new VPS with the selected offer:

    function onChangeOffer(offerID) {
       console.log("Offer ID is changing for: " + offerID); // For debugging only
       when(self.offerCache.get(offerID), function(data) {
          self.offerCtrl.set("model", data);  // Update the current offer
       });
    }
    

    When a subscriber selects an offer, the onChangeOffer() function is called with the single input argument presenting the APS ID of the selected offer. The function reads the offer from the offer cache and then updates the offer control. This updates the current limits on VPS properties.

  4. In the widget definition, add a panel with a selection list that allows a subscriber to select an offer. When an offer is selected, the widget must call the onChangeOffer handler defined earlier.

    ["aps/Panel", {id: this.genId("srvNew1_offers_panel"), title: "Offering"}, [
       ["aps/FieldSet", {id: this.genId("srvNew1_offers")}, [
          ["aps/Select", {
             id: self.genId("srvNew1_offerSelect"),
             gridSize: 'md-3 xs-12',
             store: self.offerCache,
             required: true,
             label: "Service profile:",
             onChange: onChangeOffer,
             labelAttr: "name",
             value: at(self.vpsModel.data.offer.aps, "id")
          }]
       ]]
    ]],
    
  5. In the widget definition, make the os.name property assigned from the current offer. For this, replace the aps/Select widget with the aps/Output widget as follows:

    ["aps/Output", {
       id: this.genId("new1_os"),
       label: "Operating System:",
       value: at(self.offerCtrl.model.platform.OS, "name")
    }],
    
  6. Set the maximum values of the main resources to the corresponding values in the current offer. For this, update the aps/Spinner widgets as follows.

    • For the CPU Cores:

      maximum: at(self.offerCtrl.model.hardware.CPU, "number"),
      
    • For the Disk Space:

      maximum: at(self.offerCtrl.model.hardware, "diskspace"),
      
    • For the RAM:

      maximum: at(self.offerCtrl.model.hardware, "memory"),
      
  7. In the new onContext method, add an offer store offerStore to communicate with the APS controller. Use this store to query the available offers and update the offer cache offerCache. Make the first offer from the list to be the current offer offerCtrl. Then update the offer selection widget and bind the new VPS to the current offer.

    onContext: function() {
        if (!aps.context.wizardData["http://aps-standard.org/samples/offer1p#server-new-1"]) {
             var offerStore = new Store({ // The list of offers for selection
                 apsType: "http://aps-standard.org/samples/offer1p/offer/1.0",
                 target: "/aps/2/resources/"
              });
    
              offerStore.query().then(function(offers) {
                 if (offers.length === 0) return;
    
                 self.offerCache.setData(offers);
                 self.offerCtrl.set("model", offers[0]);
    
                 self.vpsModel.data.offer.aps.set("id", self.offerCtrl.model.aps.id);
                 self.byId("srvNew1_offerSelect").set("store", self.offerCache);
              }).then(function() {
                 aps.apsc.hideLoading();
              });
        }
        else aps.apsc.hideLoading();
    }
    
  8. In the onNext handler, copy the currently selected OS name to the VPS model. So, the handler will look as follows:

    onNext: function() {
        var form = this.byId("srvNew_form");
    
        /* Validate the values assigned to widgets */
        if (!form.validate()) {
           aps.apsc.cancelProcessing();
           return;
        }
    
        self.vpsModel.data.platform.OS.set("name", self.offerCtrl.model.platform.OS.name);
        /* Proceed to the next screen */
        aps.apsc.next(self.vpsModel.data);
    },
    

The server-new-last.js file does not require any substantial changes.

Server Editor

The server-edit.js view is very similar to the view used for creating a VPS. It requires similar updates but also has some specifics:

  • The view must work with the selected VPS presented by the vps variable as specified in the navigation metadata.

  • There is no need in data wizard, thus all data is stored in the APS database on clicking the Submit button.

Update the view source as follows.

  1. In the required and main callback function, add some more aps and dojo modules required by the updated code and also add the newoffer.json file to initiate the current offer control object:

    require([
       //... leave the existing modules intact and add the following ones:
       "dojo/promise/all",
       //...,
       "aps/Memory",
       "aps/xhr",
       //...,
       "aps/json!./newoffer.json"
    ], function (
       // ...,
       all,
       //...,
       Memory,
       xhr,
       //...,
       newOffer
    ) {
       var self; // A global object to keep some objects for all functions
       return declare(View, {
          init: function() {
             self = this;
             //... function contents
          },
    
          onCancel: function() { /*... function contents */ },
          onSubmit: function() { /*... function contents */ },
          onContext(context): function() { /*... function contents */ }
    
    });
    
  2. In the init method, change data sources as follows.

    • Define a memory cache that will contain a list of offers to propose to a user:

      self.offerCache = new Memory({
         "data": [],
         idProperty: "aps.id"
      });
      
    • Define an offer control object that will store a JSON representation of the current offer. Initialize the object using the values from the newoffer.json file:

      self.offerCtrl = getStateful({
         model: newOffer
      });
      

      The values of the current offer must be used as the limits to the properties of the new VPS. The initial offer can be changed for the actual offer only in the onContext method, where the vps variable and the related offer are available.

  3. In the init method, add a handler activated on offer change. This handler must change the values of the current offer and bind the new VPS with the selected offer:

    function onChangeOffer(offerID) {
       console.log("Changed for offer: " + offerID); // For debugging only
       when(self.offerCache.get(offerID), function(data) {
          self.offerCtrl.set("model", data);  // Update the current offer
       });
    }
    

    When a subscriber selects an offer, the onChangeOffer() function is called with the single input argument presenting the APS ID of the selected offer. The function reads the offer from the offer cache and then updates the offer control. This updates the current limits on VPS properties.

  4. In the widget definition, add a panel with a selection list that allows a subscriber to select an offer. When an offer is selected, the widget must call the onChangeOffer handler defined earlier.

    ["aps/Panel", {id: this.genId("srvEdit_offers_panel"), title: "Offer"}, [
       ["aps/FieldSet", {id: this.genId("srvEdit_offers")}, [
          ["aps/Select", {
             id: this.genId("srvEdit_offerSelect"),
             gridSize: 'md-3 xs-12',
             store: self.offerCache,
             required: true,
             label: "Bundle",
             onChange: onChangeOffer,
             labelAttr: "name",
             value: at(self.vpsModel.data.offer.aps, "id")
          }]
       ]]
    ]],
    
  5. In the widget definition, make the os.name property assigned from the current offer. For this, add the aps/Output widget as follows:

    ["aps/Output", {
       id: this.genId("srvEdit_os"),
       label: "OS",
       value: at(self.offerCtrl.model.platform.OS, "name")
    }],
    
  6. Set the maximum values of the main resources to the corresponding values in the current offer. For this, update the aps/Spinner widgets as follows.

    • For the CPU Cores:

      maximum: at(self.offerCtrl.model.hardware.CPU, "number"),
      
    • For the Disk Space:

      maximum: at(self.offerCtrl.model.hardware, "diskspace"),
      
    • For the RAM:

      maximum: at(self.offerCtrl.model.hardware, "memory"),
      
  7. In the onContext(context) method, set the current values in the VPS model and in the offerCtrl model using the vps navigation variable. Add an offer store offerStore to communicate with the APS controller. Use this store to query the available offers and update the offer cache offerCache.

    var offerStore = new Store({ // The list of offers for selection
       apsType: "http://aps-standard.org/samples/offer1p/offer/1.0",
       target: "/aps/2/resources/"
    });
    
    self.vpsModel.set("data", context.vars.vps);
    self.offerCtrl.set("model", context.vars.vps.offer);
    
    offerStore.query().then(function(offers) {
       self.offerCache.setData(offers);
       self.byId("srvEdit_offerSelect").set("store", self.offerCache);
    })
    .then(function() {
       aps.apsc.hideLoading();
    });
    
  8. Rewrite completely the onSubmit button handler.

    // The following results in addition of the subscription ID as a header to xhr requests:
    aps.context.subscriptionId = aps.context.vars.vps.aps.subscription;
    
    var vpsStore = new Store({ // Interface with the APS controller
       apsType: "http://aps-standard.org/samples/offer1p/vps/1.0",
       target: "/aps/2/resources/"
    });
    
    var vps = getPlainValue(self.vpsModel.data), // New properties to save
       reqs = []; /* We may have one or two REST requests */
    
    /* If the offer has to be changed, we need to first request re-linking the VPS
       to the other offer */
    if(vps.offer.aps.id != aps.context.vars.vps.offer.aps.id)
       reqs.push(xhr("/aps/2/resources/" + vps.aps.id + "/offer",
          { method: "POST",
             data: JSON.stringify({ "aps": { "id": vps.offer.aps.id } })
          }
       ));
    vps.platform.OS.name = self.offerCtrl.model.platform.OS.name;
    reqs.push(vpsStore.put(vps)); /* And now update the VPS properties */
    all(reqs).then(function() {    /* Once the requests are completed,
                                     navigate to the "servers" view */
              aps.apsc.gotoView("servers");
       },
       displayError
    );
    

Keynotes:

  • If the VPS must be re-linked to another offer, we need to call explicitly the POST request specifying the VPS ID in the URL field and the offer ID in the request body as explained in Relinking Resources.

  • If the re-link operation is needed, the reqs list will contain two REST requests, otherwise it will have only one REST PUT request for updating the VPS properties.

Conclusion

This completes the updates of the views managing VPSes.

The project files you have updated are similar to the respective files in the sample package.