[openstack-dev] [Heat] HOT Software configuration proposal

Steve Baker sbaker at redhat.com
Mon Oct 28 22:43:02 UTC 2013


On 10/26/2013 05:25 AM, Clint Byrum wrote:
> Excerpts from Angus Salkeld's message of 2013-10-24 18:48:16 -0700:
>> On 24/10/13 11:54 +0200, Patrick Petit wrote:
>>> Hi Clint,
>>> Thank you! I have few replies/questions in-line.
>>> Cheers,
>>> Patrick
>>> On 10/23/13 8:36 PM, Clint Byrum wrote:
>>>> I think this fits into something that I want for optimizing
>>>> os-collect-config as well (our in-instance Heat-aware agent). That is
>>>> a way for us to wait for notification of changes to Metadata without
>>>> polling.
>>> Interesting... If I understand correctly that's kinda replacement of 
>>> cfn-hup... Do you have a blueprint pointer or something more 
>>> specific? While I see the benefits of it, in-instance notifications 
>>> is not really what we are looking for. We are looking for a 
>>> notification service that exposes an API whereby listeners can 
>>> register for Heat notifications. AWS Alarming / CloudFormation has 
>>> that. Why not Ceilometer / Heat? That would be extremely valuable for 
>>> those who build PaaS-like solutions above Heat. To say it bluntly, 
>>> I'd like to suggest we explore ways to integrate Heat with Marconi.
>> Yeah, I am trying to do a PoC of this now. I'll let you know how
>> it goes.
>>
>> I am trying to implement the following:
>>
>> heat_template_version: 2013-05-23
>> parameters:
>>    key_name:
>>      type: String
>>    flavor:
>>      type: String
>>      default: m1.small
>>    image:
>>      type: String
>>      default: fedora-19-i386-heat-cfntools
>> resources:
>>    config_server:
>>      type: OS::Marconi::QueueServer
>>      properties:
>>        image: {get_param: image}
>>        flavor: {get_param: flavor}
>>        key_name: {get_param: key_name}
>>
>>    configA:
>>      type: OS::Heat::OrderedConfig
>>      properties:
>>        marconi_server: {get_attr: [config_server, url]}
>>        hosted_on: {get_resource: serv1}
>>        script: |
>>          #!/bin/bash
>>          logger "1. hello from marconi"
>>
>>    configB:
>>      type: OS::Heat::OrderedConfig
>>      properties:
>>        marconi_server: {get_attr: [config_server, url]}
>>        hosted_on: {get_resource: serv1}
>>        depends_on: {get_resource: configA}
>>        script: |
>>          #!/bin/bash
>>          logger "2. hello from marconi"
>>
>>    serv1:
>>      type: OS::Nova::Server
>>      properties:
>>        image: {get_param: image}
>>        flavor: {get_param: flavor}
>>        key_name: {get_param: key_name}
>>        user_data: |
>>          #!/bin/sh
>>          # poll <marconi url>/v1/queues/{hostname}/messages
>>          # apply config
>>          # post a response message with any outputs
>>          # delete request message
>>
> If I may diverge this a bit, I'd like to consider the impact of
> hosted_on on reusability in templates. hosted_on feels like an
> anti-pattern, and I've never seen anything quite like it. It feels wrong
> for a well contained component to then reach out and push itself onto
> something else which has no mention of it.
>
> I'll rewrite your template as I envision it working:
>
> resources:
>    config_server:
>      type: OS::Marconi::QueueServer
>      properties:
>        image: {get_param: image}
>        flavor: {get_param: flavor}
>        key_name: {get_param: key_name}
>
>    configA:
>      type: OS::Heat::OrderedConfig
>      properties:
>        marconi_server: {get_attr: [config_server, url]}
>        script: |
>          #!/bin/bash
>          logger "1. hello from marconi"
>
>    configB:
>      type: OS::Heat::OrderedConfig
>      properties:
>        marconi_server: {get_attr: [config_server, url]}
>        depends_on: {get_resource: configA}
>        script: |
>          #!/bin/bash
>          logger "2. hello from marconi"
>
>    serv1:
>      type: OS::Nova::Server
>      properties:
>        image: {get_param: image}
>        flavor: {get_param: flavor}
>        key_name: {get_param: key_name}
>        components:
>          - configA
>          - configB
>        user_data: |
>          #!/bin/sh
>          # poll <marconi url>/v1/queues/{hostname}/messages
>          # apply config
>          # post a response message with any outputs
>          # delete request message
>
> This only becomes obvious why it is important when you want to do this:
>
>     configC:
>       type: OS::Heat::OrderedConfig
>       properties:
>         script: |
>           #!/bin/bash
>           logger "?. I can race with A, no dependency needed"
>
>     serv2:
>       type: OS::Nova::Server
>       properties:
>       ...
>       components:
>         - configA
>         - configC
>
> This is proper composition, where the caller defines the components, not
> the callee. Now you can re-use configA with a different component in the
> same template. As we get smarter we can have these configs separate from
> the template and reusable across templates.
>
> Anyway, I'd like to see us stop talking about hosted_on, and if it has
> been implemented, that it be deprecated and eventually removed, as it is
> just plain confusing.
>
My components proposals had no hosted_on, but I've been thinking about
the implications of implementing software configuration as resources,
and one of the natural consequences might be that hosted_on is the best
way of establishing the relationship with compute and its
configurations. Let me elaborate.

Lets say that Heat has resource types for software configuration, with
the following behaviours:
* like other resources, a config resource goes into CREATE IN_PROGRESS
as soon as its dependencies are satisfied (these dependencies may be
values signalled from other config resources)
* a config resource goes to state CREATE COMPLETE when it receives a
signal that configuration on the compute resource is complete (by some
mechanism; wait condition, marconi message, whatevs)
* the relationship between config resources and compute resources are
achieved with existing intrinsic functions (get_resource, get_attr)

This lifecycle behaviour means that a configuration resource can only
run on a single compute resource, and that relationship needs to be
established somehow. Config resources will have a quirk in that they
need to provide 2 sources of configuration data at different times:
1) cloud-init config-data (or other boot-only mechanism), which must be
available when the compute resource goes into CREATE IN_PROGRESS
2) the actual configuration data (oac metadata, puppet manifest, chef
recipe) which the compute resource needs to be able to fetch and execute
whenever it becomes available.

The data in 1) implies that the compute needs to depend on the config,
but then all concurrency is lost (this won't matter for a
cloud-init-only config resource).  Either way, the data for 1) will need
to be available when the config resource is still in state INIT
COMPLETE, which may impose limitations on how that is defined (ie
get_resource, get_attr not allowed).

So, 2 concrete examples for handling config/compute dependencies:

hosted_on
---------
resources:
  configA
    type: Heat::ScriptConfig
    properties:
       hosted_on:
         get_resource: the_server
       script: |
         #!/bin/bash
         logger "1. hello config A"
  configB
    type: Heat::ScriptConfig
    properties:
       hosted_on:
         get_resource: the_server
       script: |
         #!/bin/bash
         logger "1. hello config B"
  the_server:
    type: OS::Nova::Server

Here, configA and configB go into CREATE IN_PROGRESS as soon as
the_server is CREATE COMPLETE. configA and configB go into CREATE
COMPLETE when heat-engine receives a signal that they are complete. This
signal may include values that other resources depend on to start their
creation.

OS::Nova::Server config_resources
---------------------------------
resources:
  configA
    type: Heat::ScriptConfig
    properties:
       script: |
         #!/bin/bash
         logger "1. hello config A"
  configB
    type: Heat::ScriptConfig
    properties:
       script: |
         #!/bin/bash
         logger "1. hello config B"
  the_server:
    type: OS::Nova::Server
    properties:
      config_resources:
        - {get_resource: configA}
        - {get_resource: configB}

Here there would need to be some bypassing of dependency calculation to
allow configA and configB to be created after the_server, maybe by:
* special treatment of config_resources to prevent dependencies being
created
* a get_resource variant which doesn't create a hard dependency
(get_resource_deferred?)

Neither the hosted_on nor the config_resources behaviours are ideal, but
I'm leaning towards hosted_on at the moment since it doesn't require any
new soft-dependency mechanism.

As for composability, what *actually* needs to be composable is the
contents of the script (manifest, recipe) property. Everything else in a
config resource is just stack-specific wiring.  There are a couple of
ways this composability could be achieved:
1) resource provider in the user environment which specifies the script
property
2) __include__ or some equivalent client-side inclusion mechanism

With either of these options, intrinsic function calls need to be moved
out of the script property and into a variables property. Any
configuration management tool that supports variables passed as inputs
can use them. If a given tool doesn't support variables then heat-engine
could do the substitution when building the configuration data.

Using resource providers has another interesting possibility. With a
sufficiently advanced software-config base resource type, it should be
possible to write a resource provider which adds support for a
configuration management tool that heat otherwise knows nothing about.



More information about the OpenStack-dev mailing list