Tutorials

Use the ProfitBricks Cloud API with Python Part 2

ProfitBricks not only offers an easy-to-use GUI – the Data Center Designer (DCD) – we also provide an API that is as capable as the DCD – the Cloud API (formerly named REST API). Via this API, you can easily automate tasks such as the resource management or the provisioning of entire data centers.

Part 1 of this series explained how to retrieve information about a virtual data center and its components. Part 2 explains how to manage a data center by focusing on the following:

  • Creating a data center
  • Provisioning
  • Adding more servers
  • Changing the properties of servers
  • Deleting servers
  • Starting and stopping servers.

Provisioning a data center

You can create a new data center with only a few lines of code:

from profitbricks.client import ProfitBricksService
from profitbricks.client import Datacenter

pbclient = ProfitBricksService(
   username='username', password='password')

dc = Datacenter(
   name='Empty DC',
   location='de/fkb'   # in Karlsruhe
   )

response = pbclient.create_datacenter(datacenter=dc)

When you create a data center in the Data Center Designer (DCD), a message box pops up when provisioning is finished. When you modify something via the API, however, a status can be retrieved right after the request has been sent. Provisioning itself is not finished at this stage, therefore you should check the provisioning status. A simple approach is that you retrieve the status of the data center as shown in this example:

dc = client.get_datacenter(datacenter_id)
state = dc['metadata']['state']

state can have the following values:

  • BUSY: changes are not finished yet
  • AVAILABLE: all changes are provisioned

Afterwards you can add further resources to the data center:

create_server(datacenter_id, server)
create_volume(datacenter_id, volume)
create_loadbalancer(datacenter_id, loadbalancer)
create_lan(datacenter_id, lan)

and then connect them to each other, e. g.

attach_volume(datacenter_id, server_id, volume_id)

An even better way to check the status of provisioning is to check the status of the request itself. This request will return the RequestID, which can then be used for checking the status:

request_id = response['requestId']
# status=True : only get status information
request_status = pbclient.get_request(request_id, status=True)
state = request_status['metadata']['status']

This method will always return a reply, even if the resource itself is not provisioned yet. In this case, the request will have the status QUEUED. The resource, however, might not have been created yet, which will lead to a request error (404, “Resource does not exist”) if you try to access it.

You can apply the following method for waiting on the request to complete:

def wait_for_request(pbclient, request_id, timeout=0, initial_wait=5, scaleup=10):
    '''
    Waits for a request to finish until timeout.
    timeout==0 is interpreted as infinite wait time.
    Returns a tuple (returncode, request status, message) where
    returncode
    0  : request successful
    1  : request failed
    -1 : timeout exceeded
    The wait_period is increased every scaleup steps
    '''
    total_wait = 0
    wait_period = initial_wait
    next_scaleup = scaleup * wait_period
    while True:
        request_status = pbclient.get_request(request_id, status=True)
        state = request_status['metadata']['status']
        if state == "DONE":
            return(0, state, request_status['metadata']['message'])
        if state == 'FAILED':
            return(1, state, request_status['metadata']['message'])
        sleep(wait_period)
        total_wait += wait_period
        if timeout != 0 and total_wait > timeout:
            return(-1, state, "request not finished before timeout")
        next_scaleup -= wait_period
        if next_scaleup == 0:
            wait_period += initial_wait
            next_scaleup = scaleup * wait_period
    # end while(wait)

If you create a data center step-by-step as described above, you have to check the request of each single action.

You can, however, create servers, storage volumes, LANs, and load balancers all at once when creating a data center. You need to add additional resources to the datacenter object and then create the data center with just one single request:

nic1 = NIC(name=’nic1’, lan=1, ips=[], dhcp=’true’)

volume1 = Volume(
   name=’volume1’, size=10,
   image=image_id, image_password='Str3ngG3he1m'
   )

server1 = Server(
   name=’MyServer’,
   ram=4096, cores=2,
   nics=[nic1],
   create_volumes=[volume1]
   )
servers = [server1]

lan1 = LAN(name=’public Lan 1’, public=True)
lans = [lan1]

dcname = ‘API-Test_’+datetime.now().isoformat()
dc = Datacenter(
   name=dcname,
   description=’Production environment’,
   location=’de/fkb’,
   servers=servers,
   volumes=[],
   lans=lans,
   loadbalancers=[]
   )

response = pbclient.create_datacenter(dc)
dc_id = response[‘id’];
result = wait_for_request(pbclient, response[‘requestId’])

As a side note, if you take a closer look at this request, you will notice that it contains each single action and their respective statuses.

When you apply this approach, you need to be aware of the following:

  • RAM is given in kilobytes instead of megabytes.
  • You can add storage volumes to a server by means of the create_volumes attribute. These storage volumes are added and attached to the server automatically, which means you do not need to create and connect servers and storage volumes in separate steps.
  • You can also add storage volumes to a data center. These storage volumes are not attached to a server. Creating a storage volume with the same name (e.g. volume1) both at the server and the data center level leads to the creation of two storage volumes, one of which is attached to the respective server; the other one is an unattached part of the data center.
  • By means of the Cloud API you can set a password for ProfitBricks images. For Linux-based ProfitBricks images, you can also use SSH keys.
  • A newly created server is not connected to the internet. This is because the server together with its interface is created first. The interface points to LAN1, which is created automatically applying the default public=False (private LAN). The action “create ‘lan1’” executed later recognizes that LAN1 has already been created and does not change it.

In order to make LAN1 public, you need to change it as follows once the data center has been created:

response = pbclient.update_lan(dc_id, nic1.lan, public=True)
result = wait_for_request(pbclient, response['requestId'])

In the DCD, the provisioned data center looks like this:

Screenshot of simple data center setup

Creating and modifying a server

In order to set up a very simple failover scenario in the data center you just created, you need to:

  • Set up another, identical server,
  • Connect both servers through an additional LAN,
  • Rename the first server,
  • Assign both servers to different availability zones.

In order to create the second server, you need to go through the same steps as for creating a data center.

Only a few lines of code are required:

first_nic = NIC(name="local", ips=[], dhcp=True, lan=2)
volume = Volume(name="volume2", size=10, image=<hdimage>)
server = Server(name="HA-2", cores=2, ram=4*1024, create_volumes=[volume], nics=[first_nic], boot_cdrom=None)

response = pbclient.create_server(dc_id, server)
wait_for_request(pbclient, response["requestId"])

For this server, the volume image has been defined, and the CDROM image boot_cdrom has not been set. You could also do it the other way around and define the CDROM image and set the volume image to None. If neither CDROM nor storage image are set for a server, a PXE boot is done via the network.

The first server has not been connected to LAN2 yet. In order to connect the servers to each other, another NIC needs to be added to the first server:

nic2 = NIC(name='nic2', lan=2, ips=[], dhcp='true')
response = pbclient.create_nic(dc_id, server1_id, nic2)
result = wait_for_request(pbclient, response['requestId'])

Now you have to rename the first server and assign the servers to different availability zones:

# change server1
response = pbclient.update_server(dc_id, server1_id, name='HA-1', availability_zone='ZONE_1')
result = wait_for_request(pbclient, response['requestId'])
# change server2
response = pbclient.update_server(dc_id, server2_id, availability_zone='ZONE_2')
result = wait_for_request(pbclient, response['requestId'])

None of these changes require a restart of the servers.

In the DCD, the provisioned data center looks like this now:

Screenshot of extended data center setup

You can not only change the properties of a server, you can also change the settings for RAM and cores. If you want to double their values, you would do the following:

# change server1
response = pbclient.update_server(dc_id, server1_id, cores=4, ram=8*1024)
result = wait_for_request(pbclient, response['requestId'])
# change server2
response = pbclient.update_server(dc_id, server2_id, cores=4, ram=8*1024)
result = wait_for_request(pbclient, response['requestId'])

None of these changes require a restart of the server as long as the values are increased. If you decrease them, you need to restart the server (see Starting and stopping a server).

For a description of how to add a new server to a data center, you can refer to the example script pb_addNewServer.py, which provides options for the most important settings:

# setup argparser settings
('-d', dest='datacenterid', help='datacenter of the new server')
('-l', dest='lanid',        help='LAN of the new server')
('-n', dest='servername',   help='name of the new server')
('-c', dest='cores',        help='CPU cores')
('-r', dest='ram',          help='RAM in GB')
('-s', dest='storage',      help='storage in GB')
('-b', dest='bootdevice',   help='boot device (HDD|CDROM)')
('-i', dest='imageid',      help='installation image')

The execution of

pb_addNewServer.py –d <datacenterid> -l 2 –n HA-2 –c 2 –r 4 –s 10 –i <imageid>

creates the server HA-2 as described above and connects it to the new LAN 2. You can find the source files in the ProfitBricks repository on GitHub.

Starting and stopping a server

You can stop and start a server as follows:

# stop server:
 pbclient.stop_server(dc_id, server['id'])
 # start server
 pbclient.start_server(dc_id, server['id'])

Additionally, you can also make use of the reboot_server() method.

Because these methods - unlike the ones mentioned before - do not return a RequestID, you need to retrieve the server status, which is contained in the following attribute:

server['metadata']['state']

If its value is INACTIVE, the server is switched off (power off). The status of the operating system is contained in the following attribute:

server['properties']['vmState']

It can have, among others, the following values:

  • RUNNING: the server has been started.
  • SHUTOFF: the server has been shut off. (This value must not be confused with the value SHUTDOWN, which means a server is in the process of shutting down.)

In summary, you need to check for the following:

  • Stopping a server: state==’INACTIVE’.
  • Starting a server: vmstate==’RUNNING’.

Please keep in mind that the methods mentioned herein equal a power off resp. power on, which means the operating system itself is not shut down.

For more details you can refer to the example script pb_controlServerState.py which provides options for the most relevant settings:

('-d', dest='dc_id',      help='datacenter ID of the server')
('-s', dest='serverid',   help='ID of the server')
('-n', dest='servername', help='name of the server')
('-a', dest='action',     help='what to do with the server')

Deleting data centers and servers

Please be careful when deleting resources by means of the Cloud API. Unlike the Data Center Designer, the API does not offer a security check to prevent you from deleting a data center accidentally.

Use one of the following methods to delete servers resp. data centers:

# Delete a server:
delete_server(datacenter_id, server_id)

# Delete a data center:
delete_datacenter(datacenter_id)

Unlike the methods mentioned before, these methods do not provide a return value. If you want to check the result, you either need to access the resource directly and check the HTTP error code (404, “Resource not found”), or check if the resource is no longer listed using list methods.

List of references