This section shows the steps to Add a Service, called ExampleService, to XOS.
We have also used ExampleService in a tutorial video and an accompanying slide deck, which are available online at:
Note that there are some differences between this section and the video/slides, most notably, this section uses the “devel” configuration, while the video uses the “cord-pod” configuration. Either will work, the main difference being that the former includes only ExampleService, while latter includes other CORD services in addition to ExampleService.
ExampleService is multi-tenant. It instantiates a VM instance on behalf of each tenant, and runs an Apache web server in that VM. This web server is then configured to serve a tenant-specified message (a string), where the tenant is able to set this message using the XOS administrative interface.
ExampleService has two major code components:
A Django data model and administrative interface. These elements run in the XOS core Docker container.
A Synchronizer that instantiates and configures the VM instance from the Tenant blueprint. This Synchronizer runs in it’s own Docker container.
For this particular example, there are two unique additions to the base Service model:
service_message, a string that contains a message to display for the
service as a whole (i.e., to all tenants of the service).
tenant_message, a string that is displayed for a specific Tenant.
When a user creates a new Slice and Tenant in the Admin website (constructed using Django), the data describing the service is stored in a database.
The Synchronizer runs on a recurring basis, obtains the
tenant_message, and additional needed information from the database, and uses
it to run an Ansible playbook
that applies the configuration to the Instance.
In order to get started, you need a OpenStack host to deploy XOS on.
Once you have that set up, do the following:
Create a working copy of the XOS
repository. You can do this locally, or
by forking the repo. You will also need a copy of this on your OpenStack
host, so either do development on that host or on another system and then
git pull your changes onto the OpenStack host.
On the OpenStack host, in your working copy of XOS, go to
make. This will create an XOS installation using the
configuration on the OpenStack host by installing prerequisites and creating
and starting the XOS Docker containers.
http://<ip_or_dns_name_of_host>:9999 and verify that you can
login with the default XOS credentials, which are username:
email@example.com and password:
Optional: Check to make sure you can run the provided
code. This is not enabled by default, and you’ll need to follow the steps
in Install the Service in Django and
Create a Docker container to run the
synchronizer to enable
Once you’ve prepared your development environment as described above, the change/build/test development loop for service development on XOS is as follows:
Make changes to your copy of XOS and propagate them to your OpenStack host.
On your OpenStack host in XOS’s
xos/configurations/devel directory, run:
make rm, which will stop XOS and delete the Docker containers.
make containers. This will rebuild all the Docker containers to
include your changes. Initially this will take a long time as everything
is rebuilt, but will take less time during subsequent runs as Docker saves
make in the same directory to start XOS running with the newly built
Test and verify your changes.
Once you’re done testing, go back to step #1.
The process to do the above can be done with this command:
assuming you pull your changes from a development git repo.
XOS services are located in the
xos/services directory in the XOS source
tree. Create a new directory for your service.
In this example, the service will be named
exampleservice and the example
code is in the XOS repo at
In your service directory, create an empty
__init.py__ file. This is
required for Python to recognize your service directory as a
package, in order to
avoid namespace conflicts.
Create a file named models.py in your service directory. The start of this file:
brings in the two XOS Core classes that will be extended by our Service, defines the model, and lets us atomically update the database on instance creation/removal.
The following uniquely identify and provide human readable names for the service in the admin web UI. For your own service, change these.
We extend XOS Core’s Service class as follows:
XOS uses the
KIND variable to uniquely identify each service (which is done internally using the
The Meta options
verbose_name are used on the admin GUI.
In some cases, if you have no additional model fields you may want to add
proxy = True to the class Meta, so it can use it’s super’s data model, per Django’s
We’re not using
proxy in this example because we’re adding the following additional fields:
This uses Django’s
Models to create a
CharField in the data model. This field stores the message all Tenants of this Service will see. Think of this as a service-wide configuration parameter.
We extend XOS Core’s TenantWithContainer class, which is a Tenant that creates a VM instance:
as in Extending Service.
The following is the message that will be displayed on a per-Tenant basis:
Think of this as a tenant-specific (service intance specific) parameter.
When creating the Tenant, provide a default value of the first service available in the UI.
On save, you may need to create an Instance, which is done by calling the
model_policy function (see below).
On delete, you need to delete the instance created by this Tenant, which is done by
Finally, if a TenantWithContainer is updated, call
create or destroy the appropriate VMs.
Create a file named admin.py in your service directory. This file implements a graphical interface for your service. The start of this file:
Import the classes to extend, as well as other needed functions.
Also import the model we created, and the _NAME_ variables.
Specify that this Form will use the Service model we defined before.
When creating the Form, set initial values for the fields as follows:
Save the validated data, for who created this Tenant and the message.
Similar to Extending Service:
and have this use the
ExampleServiceForm defined above.
Display the Slice tab.
Columns to display for the list of ExampleService objects, in the Admin GUI at
and rows displayed when viewing an ExampleService at
/admin/exampleservice/exampleservice/<service id>/ with field privileges.
Render this page for Service admin users:
Order of the tabs, and additional Suit form includes are specified as:
List only a user’s service objects in the Suit form_tabs:
ExampleServiceAdmin with Django:
Specify that this Form will use the Tenant model we defined before:
Create a field later used to assign a user to this Tenant:
When creating the Form, set initial values for the fields:
Do the same as for
ExampleServiceForm, but now for
See notes above on
ExampleServiceAdmin – this configures the fields for the Tenant Admin GUI.
So that Django loads your Service, you need to add it to the list of INSTALLED_APPS.
This is set in xos/xos/settings.py:
Next, so any data migrations get run if the data model of your service changes, you need to tell XOS to run that migration when it comes up. This is done by adding a line to xos/tools/xos-manage.
Go through the development loop to include your
service in XOS. During the final
make step, you may want to run
-f devel_xos_1 and look out for any errors which may occur when you first run
the code. If so, fix them and restart the loop.
Once XOS is up, go to
http://<ip_or_dns_name_of_host>:9999/admin/exampleservice, and you should see
the Admin interface:
Select “Change” next to “Example Services”, and you’ll see list of Example Services (empty for now):
Click on “Add Example Service”, and you’ll see options for configuring a service.
Fill in the “Name:”, “VersionNumber:”, and “Service Message”, fields, then click the “Slices” tab at top, then “Add another slice”.
Fill in the slice name, then select “mysite” in the Site popdown, then click “Save”.
You should see a message similar to this saying that adding the service was successful.
The slice configuration may not set a default OS image to be created for
instances of this slice. To set this, go to
Slices in the left side
navigation, select the slice you created, and next to
Default Image select
trusty-server-multi-nic, which is an ubuntu VM created for use with XOS
Go back to the main ExampleService admin page at
next to “ExampleTenants” click “Add”.
Fill in a “Tenant Message”, then click Save. You should then see a message that “Success! The Example Tenant “exampleservice-tenant-1” was added successfully.”, and a list of Tenants with your message listed.
Synchronizers are processes that run continuously, checking for changes to the Tenant model and applying them to the running Instances. In this case, we’re using TenantWithContainer, which creates a Virtual Machine Instance for us.
XOS Synchronizers are located in the xos/synchronizers directory in the XOS source tree. It’s customary to name the synchronizer directory with the same name as your service. The example code given below is in the XOS repo at xos/synchronizers/exampleservice.
Create a file named
model-deps with the contents:
NOTE: This is used to track model dependencies using
tools/dmdot, but that tool currently isn’t working.
Create a file named
To configure this module, create a file named
specifies various configuration and logging options:
NOTE: Historically, synchronizers were named “observers”, so
s/observer/synchronizer/ when you come upon this term in the XOS code/docs.
Create a directory within your synchronizer directory named
steps, create a file named
Bring in some basic prerequities, Q to perform complex queries, and F to get the value of the model field. Also include the models created earlier, and SyncInstanceUsingAnsible which will run the Ansible playbook in the Instance VM.
Used by XOSObserver : sync_steps to determine dependencies.
The Tenant that is synchronized.
Name of the ansible playbook to run.
Path to the SSH key used by Ansible.
Determine if there are Tenants that need to be updated by running the Ansible playbook.
Find the ExampleService that this Tenant belongs to, by calling
with the object’s
service_message variables, to pass to the Ansible playbook.
In the same
steps directory, create an Ansible
exampletenant_playbook.yml which is the “master playbook” for this set of
This sets some basic configuration, specifies the host this Instance will run on, and the two variables that we’re passing to the playbook.
In this case, there are two roles, one which installs Apache, and one which
index.html file from a Jinja2
Create a directory named
steps, then create two directories named for your roles,
install_apache, create a directory named
tasks, then within that
directory, a file named
main.yml. This will contain the set of plays for the
install_apache role. To that file add the following:
This will use the Ansible apt module to install Apache.
create_index, create two directories,
In templates, create a file named
index.html.j2, with the contents:
These Jinja2 Expressions will be replaced with the values of the variables set in the master playbook.
tasks directory, create a file named
main.yml, with the contents:
This uses the Ansible template
module to load and
process the Jinja2 template then put it in the
dest location. Note that there
is no path given for the
src parameter - Ansible knows to look in the
templates directory for templates used within a role.
As a final step, you can check your playbooks for best practices with ansible-lint if you have it available.
Synchronizers run in their own Docker containers, and these containers are defined in the Docker Compose files in each configuration. For the devel configuration, we’ll need to modify xos/configurations/devel/docker-compose.yml.
xos_synchronizer_openstack as an example, create a new section as
We’ll use the same synchronizer image,
as it is suitable in most cases. The
command is a path to the Synchronizer
and it’s config file. The
org.xosproject.target label should be updated as
For Ansible to communicate with the VM, it requires an SSH key in order to communicate with the
the Instance VM. This is added read-only as a Docker volume:
Remember to rebuild your docker containers (
make rm && make containers &&
make) after making these changes. Then verify that your new container is
sudo docker ps, in addition to the other 3 devel configuration
In the Admin web UI, navigate to the Slice ->
<slicename> -> Instances, and
find an IP address starting with
10.11.X.X in the Addresses column (this
address is the “nat” network for the slice, the other address is for the
curl <10.11.X.X address>, and you should see the display message you
entered when creating the ExampleTenant.
After verifying that the text is shown, change the message in the “Example
Tenant” and “Example Service” sections of the Admin UI, wait a bit for the
Synchronizer to run, and then the message that
curl returns should be
Verify that the docker containers for XOS are running with:
sudo docker ps
If you need to see log messages for a container:
sudo docker logs <docker_container>
There’s also a shortcut in the makefile to view logs for all the containers:
If you want to delete the containers, including the database, and start over, run:
Which will delete the containers.
This is most likely Django reporting a problem in
Django’s debug log is located in in
/var/log/django_debug.log on the
xos container, so run
make enter-xos and then look at the end of that logfile.
The logs messages for when the Synchronizer runs Ansible are located in
/opt/xos/synchronizers/<servicename>/sys in its synchronizer container.
There are multiple files for each Tenant instance, including the processed
playbook and stdout/err files . You can run a shell in the docker container
with this command to access those files:
sudo docker exec -it devel_xos_synchronizer_<servicename>_1 bash
Ansible log messages for the OpenStack Synchronizer are put in
/opt/openstack/*, if you’re seeing failures in the