Profile Types

In Senlin, each node is associated with a physical object created by instantiating a profile. Profiles themselves are objects instantiated from “profile types”. In other words, a profile type provides the specification for creating profiles while a profile can be used to create multiple homogeneous objects.

Profile type implementations are managed as plugins. Users can use the built-in profile types directly and they can provide their own implementation of new profile types. The plan is to have Senlin engine support dynamical loading of plugins. Currently, this can be done by adding new senlin.profiles entry in the entry_points section in the setup.cfg file followed by a reinstall (i.e. pip install) operation.

The Base Class ‘Profile’

The base class Profile provides some common logics regarding the following operations:

  • the initialization of the spec_data based on the spec_schema property and the spec input.

  • the initialization of a basic request context using the Senlin service credentials.

  • the serialization and deserialization of profile object into/from database.

  • the validation of data provided through spec field of the profile;

  • the north bound APIs that are provided as class methods, including:

    • create_object(): create an object using logic from the profile type implementation, with data from the profile object as inputs;

    • delete_object(): delete an object using the profile type implementation;

    • update_object(): update an object by invoking operation provided by a profile type implementation, with data from a different profile object as inputs;

    • get_details(): retrieve object details into a dictionary by invoking the corresponding method provided by a profile type implementation;

    • join_cluster(): a hook API that will be invoked when an object is made into a member of a cluster; the purpose is to give the profile type implementation a chance to make changes to the object accordingly;

    • leave_cluster(): a hook API that will be invoked when an object is removed from its current cluster; the purpose is to give the profile type implementation a chance to make changes to the object accordingly;

    • recover_object(): recover an object with operation given by inputs from the profile object. By default, recreate is used if no operation is provided to delete firstly then create the object.

Abstract Methods

In addition to the above logics, the base class Profile also defines some abstract methods for a profile type implementation to implement. When invoked, these methods by default return NotImplemented, a special value that indicates the method is not implemented.

  • do_create(obj): an object creation method for a profile type implementation to override;

  • do_delete(obj): an object deletion method for a profile type implementation to override;

  • do_update(obj, new_profile): an object update method for a profile type implementation to override;

  • do_check(obj): a method that is meant to do a health check over the provided object;

  • do_get_details(obj): a method that can be overridden so that the caller can get a dict that contains properties specific to the object;

  • do_join(obj): a method for implementation to override so that profile type specific changes can be made to the object when object joins a cluster.

  • do_leave(obj): a method for implementation to override so that profile type specific changes can be made to the object when object leaves its cluster.

  • do_recover(obj): an object recover method for a profile type implementation to override. Nova server, for example, overrides the recover operation by REBUILD.

The VERSIONS Property

Each profile type class has a VERSIONS class property that documents the changes to the profile type. This information is returned when users request to list all profile types supported.

The VERSIONS property is a dict with version numbers as keys. For each specific version, the value is list of support status changes made to the profile type. Each change record contains a status key whose value is one of EXPERIMENTAL, SUPPORTED, DEPRECATED or UNSUPPORTED, and a since key whose value is of format yyyy.mm where yyyy and mm are the year and month of the release that bears the change to the support status. For example, the following record indicates that the specific profile type was introduced in April, 2016 (i.e. version 1.0 release of Senlin) as an experimental feature; later, in October, 2016 (i.e. version 2.0 release of Senlin) it has graduated into a mature feature supported by the developer team.

VERSIONS = {
  '1.0': [
      {
          "status": "EXPERIMENTAL",
          "since": "2016.04"
      },
      {
          "status": "SUPPORTED",
          "since": "2016.10"
      }
  ]
}

The context Property

In the Profile class, there is a special property named context. This is the data structure containing all necessary information needed when the profile type implementation wants to authenticate with a cloud platform. Refer to authorization, Senlin makes use of the trust mechanism provided by the OpenStack Keystone service.

The dictionary in this context property by default contains the credentials for the Senlin service account. Using the trust built between the requesting user and the service account, a profile type implementation can authenticate itself with the backend Keystone service and then interact with the supporting service like Nova, Heat etc.

All profile type implementations can include a context key in their spec, the default value is an empty dictionary. A user may customize the contents when creating a profile object by specifying a region_name, for example, to enable a multi-region cluster deployment. They could even specify a different auth_url so that a cluster can be built across OpenStack clouds.

Providing New Profile Types

When released, Senlin provides some built-in profile types. However, developing new profile types for Senlin is not a difficult task.

Develop a New Profile Type

The first step is to create a new file containing a subclass of Profile. Then you will define the spec schema for the new profile which is a python dictionary named spec_schema, with property names as keys. For each property, you will specify its value to be an object of one of the schema types listed below:

  • String: A string property.

  • Boolean: A boolean property.

  • Integer: An integer property.

  • List: A property containing a list of values.

  • Map: A property containing a map of key-value pairs.

For example:

spec_schema = {
  'name': schema.String('name of object'),
  'capacity': schema.Integer('capacity of object', default=10),
  'shared': schema.Boolean('whether object is shared', default=True)
}

If a profile property is a List, you can further define the type of elements in the list, which can be a String, a Boolean, an Integer or a Map. For example:

spec_schema = {
  ...
  'addresses': schema.List(
    'address of object on each network',
    schema=schema.String('address on a network')
  ),
  ...
}

If a profile property is a Map, you can further define the “schema” of that map, which itself is another Python dictionary containing property definitions. For example:

spec_schema = {
  ...
  'dimension': schema.Map(
    'dimension of object',
    schema={
      'length': schema.Integer('length of object'),
      'width': schema.Integer('width of object')
    }
  )
  ...
}

By default, a property is not required. If a property has to be provided, you can specify required=True in the property type constructor. For example:

spec_schema = {
  ...
  'name_length': schema.Integer('length of name', required=True)
  ...
}

A property can have a default value when no value is specified. If a property has a default value, you don’t need to specify it is required. For example:

spec_schema = {
  ...
  'min_size': schema.Integer('minimum size of object', default=0)
  ...
}

After the properties are defined, you can continue to work on overriding the abstract methods inherited from the base Profile type as appropriate.

Registering a New Profile Type

For Senlin to make use of the new profile type you have just developed, you will register it to Senlin service. Currently, this is done through a manual process. In future, Senlin will provide dynamical loading support to profile type plugins.

To register a new profile type, you will add a line to the setup.cfg file that can be found at the root directory of Senlin code base. For example:

[entry_points]
senlin.profiles =
    os.heat.stack-1.0 = senlin.profiles.os.heat.stack:StackProfile
    os.nova.server-1.0 = senlin.profiles.os.nova.server:ServerProfile
    my.cool.profile-1.0 = <path to the profile module>:<profile class name>

Finally, save that file and do a reinstall of the Senlin service, followed by a restart of the senlin-engine process.

$ sudo pip install -e .

Now, when you do a openstack cluster profile type list, you will see your profile type listed along with other existing profile types.