January 11, 2011

Classes
Tags blog

Virtual Guest Ordering

<p><i>Note: This process for ordering CCIs has been deprecated. Please see the updated process <a href=http://sldn.soft

Note: This process for ordering CCIs has been deprecated. Please see the updated process here. However this blog contains current information regarding ordering complex CCI solutions and all other products.

The universe is a strange place.

Many times in my life there have been occasions where I will run into the same concept, or the same question repeatedly within a short span of time. Perhaps It's more a question of perception than cosmic coincidence, but the phenomenon itself is real. Philosophical musings aside, however, we have had several customers, from different quarters of our business, all asking the same question this past week "How do I order a virtual guest through the SoftLayer API?".

Here at the SLDN blog, we've looked at that task before. The blog post which introduces the Python client shows one method of ordering a virtual guest. In that blog post, a new virtual guest is created by cloning the setup of an existing virtual guest. It works well if you have an existing guest to clone. If you do not have an existing guest, however, the task requires a bit more background information. This post will present that background and offer a sample that orders a virtual guest from scratch.

To order a product from SoftLayer through the API, you first create an entity known as the Order Container. The order container is a structure that you will pass to the method SoftLayer_Product_Order::verifyOrder and then to SoftLayer_Product_Order::placeOrder.

Logistically, an order container is simply a structure with fields defined by its type. The goal of your code is to create one of these structures and fill out the necessary fields. The SoftLayer API allows you to work with several kinds of Order Containers. Just to name a few:

(You can get a list of these containers by searching the SLDN wiki for the phrase "SoftLayer Container Product").

The first one above, SoftLayer_Container_Product_Order_Virtual_Guest, is the one you use when ordering a virtual guest.

In this example, we will be using the softlayer_api Ruby gem. Ruby represents the structures you pass to the API as Hashes. The portion of the code that creates our order container looks like this:

$product_order = {
    'complexType' => 'SoftLayer_Container_Product_Order_Virtual_Guest',
    'quantity' => 1,      # We only want 1 virtual guest.
 
    # In this example we're creating a single virtual guest. The "virtualGuests" portion of the
    # order container gives the system important information about the guests being created.
    # in this case we provide the host and domain IDs for the virtual guest we are creating
    #
    # If you were creating more than one virtual guest, the array would contain more than
    # one entry.
    'virtualGuests' => [
      {
          'hostname' => 'test2',      # in your own code you would replace this with your own hostname
          'domain' => 'example.com'   # in your own code you would replace this with your own domain name
      }
    ],
 
    'location' => nil,
    'packageId' => nil,
    'prices' => nil,
}

First the code sets the complexType field of the structure to the string "SoftLayer_Container_Product_Order_Virtual_Guest". This hint tells the server how to interpret the fields of the structure. We only want 1 virtual guest in this example so we set the quantity field to 1. For a Virtual Guest order, the system will want to know the host and domain names of each new computing device. This is accomplished by assigning an array of structures to the virtualGuests field. In this case we are only ordering 1 guest so the array contains a single item.

The other three fields in the structure, packageId, location and prices will require just a bit more explanation.

A SoftLayer_Product_Package, or "Package" for short, is a fundamental building block of the order. The Package tells the system what kind of thing you are trying to order, be it a server, a virtual guest, a security certificate, etc. The getActivePackages method in the SoftLayer_Account service returns a list of the packages available for purchase. Virtual Guests are ordered from a package with a well known ID, 46. Our sample code takes advantage of this fact by hard-coding the ID of that package. The packageId field of our order sample is simply set to the value 46.

Virtual_Guest_Package_ID = 46
$product_order["packageId"] = Virtual_Guest_Package_ID

The location field of the order container identifies where, geographically speaking, we would like our virtual guest created. SoftLayer has a number of data centers across the globe and the system needs to know which will house your virtual guest. The Package service can tell you which of SoftLayer's data centers can house the server ordered by a particular Package. You can call the getLocations and getAvailableLocations methods of the SoftLayer_Product_Package service (along with the Package ID) to find out in which locations a given package is valid. In the sample code, we chose a fixed location, the Dallas 5 data center for our order and the location ID is hard coded to that ID:

Dallas_Location_ID = 138124
$product_order["location"] = Dallas_Location_ID;

(this code first defines the symbolic constant Dallas_Location_ID to make the code a tiny bit more readable)

The only field left to fill out in our order container is the prices field. This field must be set to an array, the entries of which are instances of the SoftLayer_Product_Item_Price data type. For the sake of this discussion, we'll shorten that name to ItemPrice.

An ItemPrice corresponds roughly to a configuration option you would like to choose for your server. There will be one ItemPrice for a server that has 2GB of RAM and a different ItemPrice for a server with 4GB of RAM. Your server will be configured based on which of these ItemPrices you include in the order container.

ItemPrices are collected together into groups called a SoftLayer_Product_Item_Category or just ItemCategory for short. To continue the example from above, the ItemPrices for the 2GB and 4GB configuration options are both in the same ItemCategory. That ItemCategory represents the collection of configuration options related to server RAM.

Each Package is aware of a list of ItemCategories that make sense for that Package. This list is called the Packages "Configuration". Within the configuration, the Package may identify certain categories that are required, meaning you must include an ItemPrice from that category in any order you make against that Package. For example, before a virtual guest package is complete, and before you can order that package, you must specify how much RAM you want to configure on the resulting Virtual Guest. The Configuration of the Virtual Guest Package will mark the RAM category as a required category. If you submit an order against that package without an ItemPrice from the RAM ItemCategory, your order will be rejected.

You can retrieve the Configuration of a Package with the SoftLayer_Product_Package::getConfiguration method. The result of making this call is an array of the ItemCategories that make up the package with certain elements of that array being identified as "required" to complete the Package. The sample code includes a block that retrieves the Configuration for the Virtual Guest package and prints out a list of the required ItemCategories:

puts "Required Categories:"
virtual_guest_configuration = $virtual_guest_package.object_mask("isRequired","itemCategory").getConfiguration()
required_categories = virtual_guest_configuration.select { |configuration_entry| 1 = = configuration_entry['isRequired'] }
required_categories.each do |configuration_entry|
  puts "\\t#{configuration_entry['itemCategory']['id']} -- #{configuration_entry['itemCategory']['name']}"
end

The result of this executing code will resemble something like:

Required Categories:
    80 -- Computing Instance
    3 -- Ram
    46 -- Remote Management
    26 -- Uplink Port Speeds
    10 -- Public Bandwidth
    13 -- Primary IP Addresses
    81 -- First Disk
    12 -- Operating System
    20 -- Monitoring
    21 -- Notification
    22 -- Response
    31 -- VPN Management - Private Network
    32 -- Vulnerability Assessments &amp; Management

This is a list of the ids and names of the required categories for the virtual guest ordering package
To complete an order for a virtual guest, you will have to select one ItemPrice from each of these required categories and put them into the "prices" field of the order container. For most orders, you will select one ItemPrice from each category in a configuration. It should be noted, however, that it is possible to select incompatible combinations of ItemPrices and wind up with an invalid order. For example, if you ask for unlimited bandwidth with a 100 Mbps uplink, but configure the virtual guest to have 10 Mbps port speeds, then verifyOrder will reject your order.
The code sample includes another block of code designed to show how ItemPrices relate to categories:

item_prices = $virtual_guest_package.object_mask("id", "item.description", "categories.id").getItemPrices()
required_categories.each do |configuration_entry|
  puts "Category \\"#{configuration_entry['itemCategory']['name']}\\":"
  category_prices = item_prices.map do |item| 
  item["categories"].map do |category| 
  item if category["id"] == configuration_entry["itemCategory"]["id"] 
    end if item.include?("categories") 
    end.flatten.compact 
  category_prices.each do |category_price|
    puts "\\t #{category_price['id']} -- #{category_price['item']['description']}"
  end
end

This code uses the SoftLayer_Product_Package::getItemPrices method to get a list of all the item prices that are valid in the virtual guest package. It then uses the required categories, returned by the previous code, to print out a list of all the valid item prices in each required category. A portion of the output of this code snippet will look something like this:

Category "Remote Management":
     905 -- Reboot / Remote Console
Category "Uplink Port Speeds":
     273 -- 100 Mbps Public & Private Networks
     272 -- 10 Mbps Public & Private Networks
     274 -- 1000 Mbps Public & Private Networks
Category "Public Bandwidth":
     125 -- Unlimited Bandwidth (100 Mbps Uplink)
     1800 -- 0 GB Bandwidth
     34 -- 2000 GB Bandwidth
     130 -- 8000 GB Bandwidth
     127 -- 4000 GB Bandwidth
     131 -- 10000 GB Bandwidth
     36 -- Unlimited Bandwidth (10 Mbps Uplink)
     2358 -- 3000 GB Bandwidth
     129 -- 6000 GB Bandwidth
     613 -- 1000 GB Bandwidth
Category "Primary IP Addresses":
     21 -- 1 IP Address
Category "First Disk":
     2202 -- 25 GB (SAN)
     1639 -- 100 GB (SAN)

Here we see each required category, listed by name, followed by the with the ItemPrices found in that category. Each ItemPrice is identified by its ID and name. The ID number of each item is what we are primarily interested in here. The code sample places these IDs in objects within the "prices" array within the Product Order Container to complete the product order.

The sample does that with the following code, just before calling verifyOrder:

$product_order["prices"] = [
  { "id" => 1641 }, # 1641 -- 2 x 2.0 GHz Cores [Computing Instance]
  { "id" => 1647 }, # 1647 -- 8 GB [Ram]
  { "id" => 905 },  # 905 -- Reboot / Remote Console [Remote Management]
  { "id" => 273 },  # 273 -- 100 Mbps Public & Private Networks [Uplink Port Speeds]
  { "id" => 125 },  # 125 -- Unlimited Bandwidth (100 Mbps Uplink) [Public Bandwidth]
  { "id" => 21 },   # 21 -- 1 IP Address [Primary IP Addresses]
  { "id" => 1639 }, # 1639 -- 100 GB (SAN)  [First Disk]
  { "id" => 1685 }, # 1685 -- CentOS 5 - Minimal Install (64 bit)  [Operating System]
  { "id" => 55 },   # 55 -- Host Ping [Monitoring]
  { "id" => 57 },   # 57 -- Email and Ticket [Notification]
  { "id" => 58 },   # 58 -- Automated Notification [Response]
  { "id" => 420 },  # 420 -- Unlimited SSL VPN Users & 1 PPTP VPN User per account [VPN Management - Private Network]
  { "id" => 418 }   # 418 -- Nessus Vulnerability Assessment & Reporting [Vulnerability Assessments & Management]
]

Note that the prices array is an array of ItemPrice objects, but we only need to specify the ID of each object. The comment after each line identifies the ID of the particular ItemPrice, it's name, and (in square brackets) the name of the category from which that ItemPrice was chosen.

With the prices field populated, the order container is complete. As mentioned when we began, you should submit the order to SoftLayer_Product_Order::verifyOrder. This method will check the order for any errors. If errors are found, it will return an error object, otherwise it will return a fully populated Order Container with all the necessary fields filled out. You may then pass this fully populated order container to SoftLayer_Product_Order::placeOrder.

Below is the complete code example. It takes includes the code above that prints out information from the configuration and ItemPrice lists. It puts together an order container and submits it to verifyOrder (but does not actually try to place the order).

This blog post has presented some of the fundamental concepts of the SoftLayer ordering system and we hope you will find the information useful in your own code.

require 'rubygems'
require 'softlayer_api'
 
# Set the API key and username using the globals so that we don't have to 
# specify them repeatedly.  Substitute your own username and API key in these spots
$SL_API_USERNAME = 'username'
$SL_API_KEY = 'deadbeefbadf00d...'
 
# These are the services we'll be using
softlayer_product_package = SoftLayer::Service.new("SoftLayer_Product_Package");
softLayer_product_item_price = SoftLayer::Service.new("SoftLayer_Product_Item_Price");
softLayer_product_order = SoftLayer::Service.new( "SoftLayer_Product_Order");
 
# Our goal for this code is to is to fill out a structure known as the SoftLayer_Container_Product_Order_Virtual_Guest
# The following hash is the template for a virtual guest order and the code that follows will modify this template
# to create a valid order. Once complete, we'll send this order to be validated. Production code could then submit the
# completed order
$product_order = {
    'complexType' => 'SoftLayer_Container_Product_Order_Virtual_Guest',  # a constant that will tell the server what type of thing we're sending it.
 
    'quantity' => 1,      # We only want 1 virtual guest.
 
    # In this example we're creating a single virtual guest. The "virtualGuests" portion of the
    # order container gives the system important information about the guests being created.
    # in this case we provide the host and domain IDs for the virtual guest we are creating
    #
    # If you were creating more than one virtual guest, the array would contain more than
    # one entry.
    'virtualGuests' => [
      {
          'hostname' => 'test2',      # in your own code you would replace this with your own hostname
          'domain' => 'example.com'   # in your own code you would replace this with your own domain name
      }
    ],
 
    # These are fields we'll fill in below with more explaination 
 
    'location' => nil,    # The geographic location where the virtual guest will be instantiated. We'll set this below.
    'packageId' => nil,   # The package we are trying to order.  Explained in more detail shortly.
    'prices' => nil,      # Filling out this array properly will account for much of the code and commentary to follow.
}
 
# Product orders are built from Packages. The Package describes the entity you are trying to order (be it a Dedicated Server 
# or Virtual Guest). You can request a list of the Packages available for purchase on a particular account by calling the 
# getActivePackages method in the SoftLayer_Account service.
#
# For our needs here, however, the package that handles Virtual Guests is a "well known" package ID, 46. So 
# we'll just use that as a hard-coded constant.
Virtual_Guest_Package_ID = 46
 
# set the package id in the product order
$product_order["packageId"] = Virtual_Guest_Package_ID
 
# This creates a proxy of the product package service with the virtual guest package ID already
# "integrated" into it.
$virtual_guest_package = softlayer_product_package.object_with_id(Virtual_Guest_Package_ID)
 
# The "locations" of a Package tell you where (geographically speaking) that package can be used. Asking the package for it's
# locations will return an array of valid locations. Each location is identified by an ID. For example, at the time of this writing
# if you ask the virtual guest package for locations and their availability:
#
# puts $virtual_guest_package.getLocations().inspect
# puts $virtual_guest_package.getAvailableLocations().inspect
# 
# You would learn that the "dal05" location (aka "Dallas 5", not to be confused with Johnny 5) has the id 138124. Other valid 
# locations are returned by the same call. For our product order we're going to order in Dallas 5 so we'll set the ID to 138124
Dallas_Location_ID = 138124
$product_order["location"] = Dallas_Location_ID;
 
# A product order is made up of ItemPrices. An ItemPrice corresponds to a particular configuration option for a server.
# for example, One ItemPrice will correspond to having two cores in a server and a separate item price will correspond to 
# having four cores in the server.
#
# ItemPrices come in collections called ItemCategories. The ItemPrice objects for 2 cores vs. 4 cores both fall within 
# the same ItemCategory (an item category that expresses the number of cores on a particular server).
#
# ItemPrices are represented in the API as instances of the SoftLayer_Product_Item_Price data type
# ItemCategories are represented as instances of the SoftLayer_Product_Item_Category data type
 
# A Configuration is the list of ItemCategories that are valid for a Package. By asking a package for it's configuration 
# you can also find out which ItemCategories are required by that package. When you submit an order, you must select one 
# ItemPrice from each of the required ItemCategories and include that in the order's price list. Of course, you may also 
# elect to include ItemPrices from categories that are not required if you would like.
#
# The list returned from calling getConfiguration is an instance of the SoftLayer_Product_Package_Order_ConfigurationArray data type
# 
# The following code asks the Virtual Guest package for it's configuration and prints out the names of all the 
# required categories.
puts "Required Categories:"
virtual_guest_configuration = $virtual_guest_package.object_mask("isRequired","itemCategory").getConfiguration()
required_categories = virtual_guest_configuration.select { |configuration_entry| 1 = = configuration_entry['isRequired'] }
required_categories.each do |configuration_entry|
  puts "\\t#{configuration_entry['itemCategory']['id']} -- #{configuration_entry['itemCategory']['name']}"
end
 
# To create a valid order, we have to include one ItemPrice from each category in the required categories list
# You can ask the Package for a list of all the ItemPrices and then examine the ItemPrices to find out which categories
# they are in.
#
# The following code grabs the list of ItemPrices for the virtual guest package and uses the required categories
# from that package to create a sorted list of installable options. By selecting one price from each required
# category and putting that price into the product order we should get a valid order so long as the price items
# are not incompatible with one another for other reasons.
#
item_prices = $virtual_guest_package.object_mask("id", "item.description", "categories.id").getItemPrices()
required_categories.each do |configuration_entry|
  puts "Category \\"#{configuration_entry['itemCategory']['name']}\\":"
    category_prices = item_prices.map do |item| 
    item["categories"].map do |category| 
    item if category["id"] == configuration_entry["itemCategory"]["id"]
      end if item.include?("categories") 
      end.flatten.compact 
  category_prices.each do |category_price|
    puts "\\t #{category_price['id']} -- #{category_price['item']['description']}"
  end
end
 
# Using the ID's taken from the lists printed by the code up to this point we can put together a virtual guest
# without having to use a previous virtual guest as a template. 
#
# In the array we have selected one ItemPrice from each of the required categories so now we can put them
# in the template. Really all we need in the template is the ItemPrice ID's and the order system can 
# pick up the actual price objects on the server side.  The comment after each price object in the array
# shows the ID and name of the ItemPrice as well as the name of the category from which it was taken.
 
$product_order["prices"] = [
  { "id" => 1641 }, #   1641 -- 2 x 2.0 GHz Cores [Computing Instance]
  { "id" => 1647 }, #   1647 -- 8 GB [Ram]
  { "id" => 905 },  #   905 -- Reboot / Remote Console [Remote Management]
  { "id" => 273 },  #   273 -- 100 Mbps Public & Private Networks [Uplink Port Speeds]
  { "id" => 125 },  #   125 -- Unlimited Bandwidth (100 Mbps Uplink) [Public Bandwidth]
  { "id" => 21 },   #   21 -- 1 IP Address [Primary IP Addresses]
  { "id" => 1639 }, #   1639 -- 100 GB (SAN)  [First Disk]
  { "id" => 1685 }, #   1685 -- CentOS 5 - Minimal Install (64 bit)  [Operating System]
  { "id" => 55 },   #   55 -- Host Ping [Monitoring]
  { "id" => 57 },   #   57 -- Email and Ticket [Notification]
  { "id" => 58 },   #   58 -- Automated Notification [Response]
  { "id" => 420 },  #   420 -- Unlimited SSL VPN Users & 1 PPTP VPN User per account [VPN Management - Private Network]
  { "id" => 418 }   #   418 -- Nessus Vulnerability Assessment & Reporting [Vulnerability Assessments & Management]
]
 
# At this point, the order should be complete. We pass it to verifyOrder to make sure
# it is valid.  Afterward, you could pass it to placeOrder to actually get the ball rolling.
begin
  result = softLayer_product_order.verifyOrder($product_order)
  puts "The order was verified successfully"
rescue => error_reason
  puts "The order could not be verified by the server #{error_reason}"
end

Feedback?

If this article contains any error, or leaves any of your questions unanswered, please help us out by opening up a github issue.
Open an issue