So in the first part of this story I mentioned nested stacks would allow us to pass %index% from the OS::Heat::ResourceGroup resource type in Heat, which combined with making each server a child stack gives us some way to look up a list with that index.

But typically we don’t have one AZ for each server, we would spread the servers out over two to three AZs. How can we make Heat do that? The answer is YAQL.

YAQL is a “query language” for YAML, with a library of functions suitable for manipulating data presented as YAML objects. There’s a ton of stuff you could do with YAQL, but I’m mostly interested in the math functions.

What we need is a way of taking the index and finding the modulo of the length of the list of AZs passed to us. Just that simple!

Heat supports YAQL in a few different ways. You can use it in place of literal values (like str_replace does), or more commonly you can simplify your structure using OS::Heat::Value resources.

This is a resource that just holds a value like this:

resources:
  MyValue:
    type: OS::Heat::Value
    properties:
      value: myhostname

We can now retieve the value with resource attribute lookups in another part of the stack:

resources:
  MyInterestingResource:
    type: OS::Nova::Server
    properties:
      name: { get_attr: [ MyValue, value ] }

Now, there’s an obvious problem with using an OS::Heat::Value in a template: it only gets created once. You can’t think of it as a function call, it has to be resolved to a single value when created like any other resource. So we can’t use OS::Heat::Value in our main stack if we’re passing it %index% anyway, even if the substitution of %index% worked. Which it doesn’t.

We can however use it if we create a child stack for each and every instance we were launching, so we can put code to modulo %index% in a value. Great. How do we do this?

app_server.yaml:

paramaters:
  index:
    type: number
  az_list:
    type: comma_delimited_list
  az_count:
    type: number

resources:
  az_index:
    type: OS::Heat::Value
    properties:
      value:
        yaql:
          expression: $.data.index mod $.data.count
          data:
            index: { get_param: index }
            count: { get_param: az_count }

  AppServer:
    type: OS::Nova::Server
    properties:
      availability_zone: { get_param: [ az_list, { get_attr: [ az_index, value ] } ] }
[...]

It’s as easy as that. Kind of. We need to now pass this stack a count of the AZ, the AZ list, and the index. So we can do this in a resource group like this:

app_main.yaml:

paramaters:
  count:
    type: number
    default: 3
  az_list:
    type: comma_delimited_list
    default: "az_a,az_b"

resources:
  AppServers:
    type: OS::Heat::ResourceGroup
    properties:
      count: { get_param: count }
      resource_def:
        type: app_server.yaml
        properties:
          index: '%index%'
          az_list: { get_param: az_list }
          az_count: 
            yaql:
              expression: $.data.list_param.count()
              data:
                list_param: { get_param: az_list }

Now we just need to pass arguments to our main stack of the list of AZs, and how many servers we want, and the stack will ensure they are split over the available AZs automatically.

There’s a bunch of other things you can do with nested stacks, yaql, and resource groups. This is just an example of the power that Heat has in making it easier to write less template code and have fixed resources built out easily.