Bootstrap and Teardown

The main reason claw was written in the first place, was to simplify the process of bootstrapping during development.

You may have many environments in which you bootstrap on, aws based environments, openstack based environments, etc... Each environment has its own set of inputs, such as different credentials, different resource names, etc... At the same time, some set of properties may be shared between environments which means duplication. You get the drift.

Similar modifications may be required in the different manager blueprints, which suffer from the same problems.

At the same time, during development, you generally want to use the tip of the master or build branch of the cloudify-manager-blueprints repository.

All these different constraints will likely cause you many headaches, sporadic failures due to some manual typing gone wrong and similar mishaps.

claw can help you keep you sanity.

Configurations

Before we delve into how you would actually bootstrap using claw, we need to discuss the concept of configurations.

When CLAW_HOME was initialized during claw init, a file named suites.yaml was generated in it. The name suites.yaml may be familiar to you from cloudify-system-tests, this is not coincidental.

claw leverages the concept of handler_configurations used by the system tests framework to configure different environments. If you are not familiar with the system tests suites.yaml, that’s OK, this guide will try not making the assumption of familiarity.

The sections in suites.yaml are:

  • variables
  • manager_blueprint_override_templates
  • inputs_override_templates
  • handler_configuration_templates
  • handler_configurations

For now, we’ll focus on the handler_configurations sections, and ignore the others.

Bootstrap and Handler Configurations

Simplest Example

For this section we’ll use the following basic suites.yaml:

handler_configurations:
  some_openstack_env:
    manager_blueprint: /path/to/my-manager-blueprint.yaml
    inputs: /path/to/my-manager-blueprint-inputs.yaml

With this configuration in place, you can run (from any directory):

$ claw bootstrap some_openstack_env

To bootstrap a manager.

The command above created a directory at $CLAW_HOME/configurations/some_openstack_env.

This directory contains:

  • A copy of the inputs.yaml supplied.
  • A directory named manager-blueprint which is a copy of the original manager blueprint directory (with the exception that the blueprint file was renamed to manager-blueprint.yaml)
  • A handler_configuration.yaml file that can be used to run system tests on the manager that was just bootstrapped. (with manager_ip properly configured)

In addition, cfy init and cfy bootstrap were executed in this directory by claw and .cloudify/config.yaml was configured so that you can see colors when running bootstrap/teardown and other workflows, which is nice.

Of course, this is not very useful and can be easily achieve directly from cfy:

$ cfy init
$ cfy bootstrap -p /path/to/my-manager-blueprint.yaml -i /path/to/my-manager-blueprint-inputs.yaml

And while this did not do all these things that claw did previously, in most cases, this may be enough. So let’s take it up a notch and start using more advanced handler_configuration features.

Inputs and Manager Blueprint Override

Now, we’ll build upon the previous example, making use of inputs_override and manager_blueprint_override:

handler_configurations:
  some_openstack_env:
    manager_blueprint: /path/to/my-needs-a-patch-manager-blueprint.yaml
    inputs: /path/to/my-partially-filled-manager-blueprint-inputs.yaml
    inputs_override:
      keystone_username: MY_USERNAME
      keystone_password: MY_PASSWORD
      keystone_tenant_name: MY_TENANT_NAME
    manager_blueprint_override:
      node_templates.management_subnet.properties.subnet.dns_nameservers: [8.8.4.4, 8.8.8.8]

Suppose that the above handler configuration uses a manager blueprint that needs a fix to the management network subnet dns configuration. Its inputs file lacks a username, a password, and a tenant name. claw enables us to override or even add properties to both the manager blueprint and the inputs file. This can be done by configuring, in addition to manager_blueprint and inputs properties, the manager_blueprint_override and inputs_override ones. Similar to the previous section, running:

$ claw bootstrap some_openstack_env

will bootstrap the manager.

The new thing here, is that the generated inputs.yaml file is not just a copy of the original inputs file, but rather a merge of its content, overridden by items specified in inputs_override. Similarly, the copy of the manager blueprint was modified so that the management_subnet node template, has the required dns_nameservers property in place.

Variables

Variables let you keep values in one place and reference them in inputs and manager blueprint overrides.

We’ll modify the previous example and extend it to use variables:

variables:
  username: MY_USERNAME
  password: MY_PASSWORD
  tenant: MY_TENANT_NAME

handler_configurations:
  some_openstack_env:
    manager_blueprint: /path/to/my-manager-blueprint.yaml
    inputs: /path/to/my-partially-filled-manager-blueprint-inputs.yaml
    inputs_override:
      keystone_username: '{{username}}'
      keystone_password: '{{password}}'
      keystone_tenant_name: '{{tenant}}'

As you can see, variables are pretty straightforward to use. Inside a string, use {{VARIABLE_NAME}} to reference a variable. Variables can also be used as part of a larger string. For example, if we have a variable named my_var we can use it inside a string like this: some_value_{{my_var}}

In addition to defining your own variables and using them in handler configurations, you can reference variables that are defined in the suites.yaml file that is located in the cloudify-system-tests repository. For example, if the system tests suites.yaml contains a variable named datacentred_openstack_centos_7_image_id, you can reference it just the same, without it being defined in your suites.yaml file:

handler_configurations:
  some_openstack_env:
    manager_blueprint: /path/to/my-manager-blueprint.yaml
    inputs: /path/to/my-partially-filled-manager-blueprint-inputs.yaml
    inputs_override:
      image_id: '{{datacentred_openstack_centos_7_image_id}}'

System Tests Fields

As mentioned previously, when claw bootstrap is called, it will generate a file named handler-configuration.yaml under $CLAW_HOME/configurations/{CONFIGURATION_NAME} that is suitable for use when running system tests locally on the bootstrapped manager.

For this file to be fully suitable, it is up to you, to add the relevant fields to the handler configuration. These fields are properties and handler.

The properties field should be a name that is specified under the handler_properties section of the system tests suites.yaml.

The handler field should be a handler that matches the environment on which the bootstrap is performed.

For example, a handler configuration for running tests on datacentred openstack might look like this:

handler_configurations:
  some_openstack_env:
    manager_blueprint: /path/to/my-manager-blueprint.yaml
    inputs: /path/to/my-manager-blueprint-inputs.yaml
    handler: openstack_handler
    properties: datacentred_openstack_properties

YAML Anchors (&), Aliases (*) and Merges (<<)

While not specific to handler configurations, usage of YAML anchors, aliases and merges can greatly reduce repetition of complex configurations and improve reusability of different components in the handler configurations.

In the following example, we’ll see how YAML anchors, aliases and merges can be used in handler configurations.

We’ll start be giving an example of a somewhat complex annotated suites.yaml, and explain what’s going on afterwards.

# Under this section, we put templates that will be used in manager
# blueprint override sections
manager_blueprint_override_templates:

  # For now ignore the key 'openstack_dns' and notice the
  # anchor (&) 'openstack_dns_servers_blueprint_override'
  openstack_dns: &openstack_dns_servers_blueprint_override
    node_templates.management_subnet.properties.subnet.dns_nameservers: [8.8.4.4, 8.8.8.8]

  # For now ignore the key 'openstack_influx_port' and notice the
  # anchor (&) 'openstack_openinflux_port_blueprint_override'
  openstack_influx_port: &openstack_openinflux_port_blueprint_override
    # The [append] means that this dict (that contains port and
    # remote_ip_prefix) will be added to the rules list in the overridden
    # manager blueprint
    node_templates.management_security_group.properties.rules[append]:
      port: 8086
      remote_ip_prefix: 0.0.0.0/0

# Under this section, we put templates that will be used in inputs
# override sections
inputs_override_templates:

  # For now ignore the key 'datacentred_openstack_env' and notice the
  # anchor (&) 'datacentred_openstack_env_inputs'
  datacentred_openstack_env: &datacentred_openstack_env_inputs
    keystone_username: MY_USERNAME
    keystone_password: MY_PASSWORD
    keystone_tenant_name: MY_TENTANT_NAME
    keystone_url: MY_KEYSTONE_URL
    external_network_name: MY_EXTERNAL_NETWORK_NAME
    image_id: MY_IMAGE_ID
    flavor_id: MY_FLAVOR_ID
    region: MY_REGION

# Under this section, we put templates that will be used in handler
# configurations
handler_configuration_templates:
  # Notice the anchor (&) 'openstack_handler_configuration'
  # also notice that in this section, templates are specified as list
  # instead of a dict like the previous template sections.
  # It is not required that this section will be a list (i.e. it can be a
  # dict as well), but it is required that the previous sections remain
  # dicts
  - &openstack_handler_configuration
    handler: openstack_handler
    inputs: ~/dev/cloudify/cloudify-manager-blueprints/openstack-manager-blueprint-inputs.yaml
    manager_blueprint: ~/dev/cloudify/cloudify-manager-blueprints/openstack-manager-blueprint.yaml

handler_configurations:

  # Notice the anchor (&) 'datacentred_handler_configuration'
  datacentred_openstack_env_plain: &datacentred_handler_configuration

    # This is the first place aliases (*) and merges (<<) are used in this
    # file. We merge into the 'datacentred_openstack_env_plain'
    # handler configuration, the content of the handler configuration
    # template whose anchor (&) is 'datacentred_openstack_env_plain'.
    # Note, that while this is the first place aliases are used here, this
    # is simply how to example is built. There is nothing stopping you
    # from using them in the templates sections to reference previously
    # defined templates.
    <<: *openstack_handler_configuration

    # we continue populating the handler configuration with regular values
    properties: datacentred_openstack_properties

    # here we use an alias (*) directly to set the value of
    # 'inputs_override' to be the dict specified by the
    # 'datacentred_openstack_env_inputs' anchor (&)
    inputs_override: *datacentred_openstack_env_inputs

  # Defining a modified datacentred handler configuration
  datacentred_openstack_env_with_modified_dns:

    # Notice that we merge (<<) the previously defined handler
    # configuration anchored (&) by 'datacentred_handler_configuration'
    <<: *datacentred_handler_configuration

    # the only modification we make in this handler configuration is
    # setting 'manager_blueprint_override' to have the value of the
    # manager blueprint template anchored (&) with
    # 'openstack_dns_servers_blueprint_override'
    manager_blueprint_override: *openstack_dns_servers_blueprint_override

  # Defining another modified datacentred handler configuration
  datacentred_openstack_env_with_modified_dns_and_openinflux:

    # Notice that we merge (<<) the previously defined handler
    # configuration anchored (&) by 'datacentred_handler_configuration'
    <<: *datacentred_handler_configuration

    manager_blueprint_override:
      # In this handler configuration, we merge (<<) both templates
      # that were defined the the manager blueprint templates sections
      <<: *openstack_dns_servers_blueprint_override
      <<: *openstack_openinflux_port_blueprint_override

Most of what is going on in the previous example, is inlined within the YAML as comments, so make sure you read through them to understand how it works.

One thing to mention is that even though it may look verbose, we now have 3 slightly different configurations all located close to each other with very little duplication. This enables us to bootstrap different (but similar) configurations as easy as:

$ claw bootstrap datacentred_openstack_env_plain
$ claw bootstrap datacentred_openstack_env_with_modified_dns
$ claw bootstrap datacentred_openstack_env_with_modified_dns_and_openinflux

(Probably not in parallel though, as they all share the same tenant and are likely to interfere with each other)

Inputs and Manager Blueprint Override as Command Line Arguments

We can now go back to the previous example, where we (not so smoothly) ignored the keys in the inputs_override_templates and manager_blueprint_override_templates.

What if we had many small override snippets in these sections? Obviously, we can’t create a configuration for each combination, as there will be too many pretty soon and the suites.yaml file will become a mess to maintain.

For that, claw accepts --inputs-override (-i) and --manager-blueprint-override (-b) as flags to the claw bootstrap command, where several overrides can be passed in a single claw bootstrap invocation. The values are the key names in the inputs_override_templates and manager_blueprint_override_templates sections.

Building on our previous example, if we only had the datacentred_openstack_env_plain handler configuration, we could do:

$ claw bootstrap datacentred_openstack_env_plain -b openstack_dns -b openstack_influx_port

To override the manager blueprints with overrides from the openstack_dns and openstack_influx_port manager blueprint templates.

Similarly, if we had an inputs override template named my_dev_branches and we wanted to bootstrap with our dev branches override we could do something like:

$ claw bootstrap datacentred_openstack_env_plain -i my_dev_branches

without having to add a new configuration only for the sake of overriding some branches.

Overrides Syntax

Internally, claw uses and extends the cosmo_tester.framework.utils:YamlPatcher to implement the overriding logic.

First we’ll go over features that are provided by the original YamlPatcher. Next, we’ll show an override feature that only exists in claw (for now).

For the following examples we’ll focus on manager blueprint overrides because they tend to get nested and require more advanced overrides, but there is nothing stopping you from applying the same methods to inputs override if your heart desires.

Path Based Overrides

Overrides are based on the path to the key/value.

Manager blueprint snippet:

node_templates:
  management_vm: ...
  management_subnet: ...
  webui: ...

If we wanted to add a full node template to the previous example we’d have an override like this:

manager_blueprint_override_templates:
  new_node_in_blueprint: &new_node
    # You would usually have a single override under an override template,
    # but there is nothing stopping you from having multiple overrides
    # under the same template if this is what you need.
    node_templates.my_new_node:
      type: cloudify.nodes.Root
      ...

The resulting YAML will look something like:

node_templates:
  management_vm: ...
  management_subnet: ...
  webui: ...
  my_new_node:
    type: cloudify.nodes.Root
    ...

(after applying the override using one of the methods described in this page)

Note

Overriding (or adding a value) that is not nested is still path based, only the path to the overridden key is simply the property name. This usually applies to inputs overrides as they are mostly not nested. (You can find examples of such of overrides in previous sections on this page)

Note

Overriding a nested path that doesn’t exist will simply create this path for you.

For example, based on this simple YAML:

node_templates:
  empty_node: {}

An override like node_template.empty_node.some.nested.path: value, will result in a YAML similar to this:

node_templates:
  empty_node:
    some:
      nested:
        path: value

Note

If an element in a path contains a dot (.), you can escape the dot using backslash (\).

For example, if we wanted to add a configure override to some node template lifecycle operation:

node_templates:
  some_node:
    interfaces:
      cloudify.interfaces.lifecycle:
        create: ...

We’d have something like:

manager_blueprint_override_templates:
  configure_lifecycle_operation: &lifecycle_operation
    node_templates.some_node.interfaces.cloudify\.interfaces\.lifecycle.configure:
      implementation: ...
      inputs: ...

And the resulting YAML will look something like:

node_templates:
  some_node:
    interfaces:
      cloudify.interfaces.lifecycle:
        create: ...
        configure:
          implementation: ..
          inputs: ...

(after applying the override using one of the methods described in this page)

Overriding Values in Lists

To override a value of some list item, you can you the [SOME_INDEX] directive.

For example, if we had this in a manager blueprint:

node_templates:
  some_node:
    relationships:
      - type: ...
        target: ...
      - type: some.relationship.type
        target: ...

And we wanted to change the type of the second relationship, we’d have an override similar to this:

manager_blueprint_override_templates:
  change_rel_type: &rel_type
    # note that indexing is zero-based (i.e. the second element is
    # referenced by index 1)
    node_templates.some_node.relationships[1].type: some.other.relationship.type

The resulting YAML will look something like this:

node_templates:
  some_node:
    relationships:
      - type: ...
        target: ...
      - type: some.other.relationship.type
        target: ...

(after applying the override using one of the methods described in this page)

If, on the other hand, we wanted to add a new relationship, we’d use the [append] directive:

manager_blueprint_override_templates:
  append_rel: &append_rel
    node_templates.some_node.relationships[append]:
        type: ...
        target: some_new_target_node

The resulting YAML will look something like this:

node_templates:
  some_node:
    relationships:
      - type: ...
        target: ...
      - type: ...
        target: ...
      - type: ...
        target: some_new_target_node

(after applying the override using one of the methods described in this page)

Function Based Overrides (Claw Feature Only)

There may be times when you need to do some advanced override that is not catered by the existing mechanism.

To enable this, claw extends the system tests YamlPatcher with an ability to specify a function that will accept the current overridden value as its first argument (or None if no current value exists) and additional optional arguments and keyword arguments.

We’ll implement a simple override function that add appends exclamation marks to the current value (we will also make it configurable)

# lives in some.example.module

def add_excitement(current_value,
                   excitement_count=3,
                   excitement_char='!'):
    assert isinstance(current_value, basestring)
    return '{0}{1}'.format(current_value,
                           excitement_char * excitement_count)

Example YAML:

node_templates:
  management_vm:
    properties:
      property1: value1
      property2: value2
      property3: value3

To use the function we just created we’ll define an override that has this structure:

func: path.to.func.module:function_name
# the following two are optional
args: [1,2,3]
kwargs: {some_kwarg: value, some_kwarg2: 2}

Let’s apply this structure to override values in our example

manager_blueprint_override_templates:
  change_props: &change_props_anchor
    node_templates.management_vm.properties.property1:
      func: some.example.module:add_excitement
    # using the args syntax
    node_templates.management_vm.properties.property2:
      func: some.example.module:add_excitement
      args: [5]
    # using the kwargs syntax
    node_templates.management_vm.properties.property3:
      func: some.example.module:add_excitement
      kwargs: {excitement_count: 2, excitement_char: ?}

The resulting YAML will look something like this:

node_templates:
  management_vm:
    properties:
      property1: value1!!!
      property2: value2!!!!!
      property3: value3??

(after applying the override using one of the methods described in this page)

Note

claw comes with 2 built-in override functions to filter values from lists and dictionaries. They can be found at claw.patcher:filter_list and claw.patcher:filter_dict. It also comes with an override function that reads values from environment variables. It can be found at claw.patcher:env.

Reset Configuration

claw bootstrap accept a --reset flag that will remove the current configuration directory. Use with care.

Teardown

There is not much to say about tearing down an environment bootstrapped by claw.

If we bootstrapped an environment based on my_handler_configuration, we can perform teardown like this:

$ claw teardown my_handler_configuration

Caution

Internally, claw teardown will pass --force and --ignore-deployments to the underlying cfy teardown command to save you some typing. You should be aware of this to avoid unfortunate accidental teardowns.