<font size=2 face="Arial">Hello,</font>
<br>
<br><font size=2 face="Arial">While adopting the latest from the software
configurations in Icehouse, we discovered an issue with the new software
configuration type and its assumptions about using the heat client to perform
behavior. </font>
<br>
<br><font size=2 face="Arial">The change was introduced in:</font>
<br>
<br><font size=2 face="Arial">commit 21f60b155e4b65396ebf77e05a0ef300e7c3c1cf</font>
<br><font size=2 face="Arial">Author: Steve Baker <sbaker@redhat.com></font>
<br><font size=2 face="Arial">Change: </font><a href=https://review.openstack.org/#/c/67621/><font size=2 face="Arial">https://review.openstack.org/#/c/67621/</font></a>
<br>
<br><font size=2 face="Arial">The net is that the software config type
in software_config.py lines 147-152 relies on the heat client to create/clone
software configuration resources in the heat database:</font>
<br>
<br><font size=2 face="Arial">    def <b>handle_create</b>(<i>self</i>):</font>
<br><font size=2 face="Arial">        props = dict(<i>self</i>.properties)</font>
<br><font size=2 face="Arial">        props[<i>self</i>.NAME]
= <i>self</i>.physical_resource_name()</font>
<br>
<br><font size=2 face="Arial">        sc = <i>self</i>.heat().software_configs.create(**props)
## HERE THE HEAT CLIENT IS CREATING A NEW SOFTWARE_CONFIG TO MAKE EACH
ONE IMMUTABLE</font>
<br><font size=2 face="Arial">        <i>self</i>.resource_id_set(sc.id)</font>
<br>
<br><font size=2 face="Arial">My concerns with this approach:</font>
<br>
<br><font size=2 face="Arial">When used in standalone mode, the Heat engine
receives headers which are used to drive authentication (X-Auth-Url, X-Auth-User,
X-Auth-Key, ..):</font>
<br>
<br><font size=2 face="Arial">curl -i -X POST -H 'X-Auth-Key: password'
-H 'Accept: application/json' -H 'Content-Type: application/json' -H 'X-Auth-Url:
</font><a href="http://10.10.0.101:5000/v2.0'"><font size=2 face="Arial">http://[host]:5000/v2.0'</font></a><font size=2 face="Arial">
-H 'X-Auth-User: admin' -H 'User-Agent: python-heatclient' -d '{...}' </font><a href=http://10.0.2.15:8004/v1/><font size=2 face="Arial">http://10.0.2.15:8004/v1/</font></a><font size=2 face="Arial">{tenant_id}</font>
<br>
<br><font size=2 face="Arial">In this mode, the heat config file indicates
standalone mode and can also indicate multicloud support:</font>
<br>
<br><font size=2 face="Arial"># /etc/heat/heat.conf</font>
<br><font size=2 face="Arial">[paste_deploy]</font>
<br><font size=2 face="Arial">flavor = standalone</font>
<br>
<br><font size=2 face="Arial">[auth_password]</font>
<br><font size=2 face="Arial">allowed_auth_uris = http://[host1]:5000/v2.0,http://[host2]:5000/v2.0</font>
<br><font size=2 face="Arial">multi_cloud = true</font>
<br>
<br><font size=2 face="Arial">Any keystone URL which is referenced is unaware
of the orchestration engine which is interacting with it. Herein lies the
design flaw.</font>
<br>
<br><font size=2 face="Arial">When software_config calls self.heat(), it
resolves clients.py's heat client:</font>
<br>
<br><font size=2 face="Arial">    def <b>heat</b>(<i>self</i>):</font>
<br><font size=2 face="Arial">        if <i>self</i>._heat:</font>
<br><font size=2 face="Arial">           
return <i>self</i>._heat</font>
<br><font size=2 face="Arial">        </font>
<br><font size=2 face="Arial">        con = <i>self</i>.context</font>
<br><font size=2 face="Arial">        if <i>self</i>.auth_token
is None:</font>
<br><font size=2 face="Arial">           
logger.error(_(<i>"Heat connection failed, no auth_token!"</i>))</font>
<br><font size=2 face="Arial">           
return None</font>
<br><font size=2 face="Arial">        # try the token</font>
<br><font size=2 face="Arial">        args = {</font>
<br><font size=2 face="Arial">           
    <i>'auth_url'</i>: con.auth_url,</font>
<br><font size=2 face="Arial">           
    <i>'token'</i>: <i>self</i>.auth_token,</font>
<br><font size=2 face="Arial">           
    <i>'<u>username</u>'</i>: None,</font>
<br><font size=2 face="Arial">           
    <i>'password'</i>: None,</font>
<br><font size=2 face="Arial">           
    <i>'ca_file'</i>: <i>self</i>._get_client_option(<i>'heat'</i>,
<i>'ca_file'</i>),</font>
<br><font size=2 face="Arial">           
    <i>'cert_file'</i>: <i>self</i>._get_client_option(<i>'heat'</i>,
<i>'cert_file'</i>),</font>
<br><font size=2 face="Arial">           
    <i>'key_file'</i>: <i>self</i>._get_client_option(<i>'heat'</i>,
<i>'key_file'</i>),</font>
<br><font size=2 face="Arial">           
    <i>'insecure'</i>: <i>self</i>._get_client_option(<i>'heat'</i>,
<i>'insecure'</i>)   </font>
<br><font size=2 face="Arial">         }</font>
<br>
<br><font size=2 face="Arial">        endpoint_type
= <i>self</i>._get_client_option(<i>'heat'</i>, <i>'endpoint_type'</i>)</font>
<br><font size=2 face="Arial">        endpoint = <i>self</i>._get_heat_url()</font>
<br><font size=2 face="Arial">        if not endpoint:</font>
<br><font size=2 face="Arial">           
endpoint = <i>self</i>.url_for(service_type=<i>'<u>orchestration</u>'</i>,</font>
<br><font size=2 face="Arial">           
                     
  endpoint_type=endpoint_type)</font>
<br><font size=2 face="Arial">        <i>self</i>._heat
= heatclient.Client(<i>'1'</i>, endpoint, **args)</font>
<br>
<br><font size=2 face="Arial">        return <i>self</i>._heat</font>
<br>
<br><font size=2 face="Arial">Here, an attempt to look up the orchestration
URL (which is already executing in the context of the heat engine) comes
up wrong because Keystone doesn't know about this remote standalone Heat
engine. </font>
<br>
<br><font size=2 face="Arial">Further, at this point, the username and
password are null, and when the auth_password standza is applied in the
config file, Heat will deny any attempts at authorization which only provide
a token. As I understand it today, that's because it doesn't have individual
keystone admin users for all remote keystone services in the list of allowed_auth_urls.
Hence, if only provided with a token, I don't think the heat engine can
validate the token against the remote keystone. </font>
<br>
<br><font size=2 face="Arial">One workaround that I've implemented locally
is to change the logic to check for standalone mode and send the username
and password. </font>
<br>
<br><font size=2 face="Arial">       flavor = <i>'default'</i></font>
<br><font size=2 face="Arial">        try:</font>
<br><font size=2 face="Arial">           
logger.info(<i>"Configuration is %s"</i> % str(cfg.CONF))</font>
<br><font size=2 face="Arial">           
flavor = cfg.CONF.paste_deploy.flavor</font>
<br><font size=2 face="Arial">        except cfg.NoSuchOptError
as <u>nsoe</u>:</font>
<br><font size=2 face="Arial">           
flavor = <i>'default'</i></font>
<br><font size=2 face="Arial">        logger.info(<i>"Flavor
is %s"</i> % flavor)</font>
<br><font size=2 face="Arial">        </font>
<br><font size=2 face="Arial">        # We really should
examine the pipeline to determine whether we're using <u>authtoken</u>
or <u>authpassword</u>.</font>
<br><font size=2 face="Arial">        if flavor ==
<i>'<u>standalone</u>'</i>:</font>
<br><font size=2 face="Arial">           
</font>
<br><font size=2 face="Arial">           
context_map = <i>self</i>.context.to_dict()</font>
<br><font size=2 face="Arial">           
</font>
<br><font size=2 face="Arial">           
if <i>'<u>username</u>'</i> in context_map.keys():</font>
<br><font size=2 face="Arial">           
    username = context_map[<i>'<u>username</u>'</i>]</font>
<br><font size=2 face="Arial">           
else:</font>
<br><font size=2 face="Arial">           
    username = None</font>
<br><font size=2 face="Arial">    </font>
<br><font size=2 face="Arial">           
if <i>'password'</i> in context_map.keys():</font>
<br><font size=2 face="Arial">           
    password = context_map[<i>'password'</i>]</font>
<br><font size=2 face="Arial">           
else:</font>
<br><font size=2 face="Arial">           
    password = None</font>
<br><font size=2 face="Arial">           
</font>
<br><font size=2 face="Arial">           
logger.info(<i>"Configuring <u>username</u>='%s' and password='%s'"</i>
% (username, password))</font>
<br><font size=2 face="Arial">           
args = {</font>
<br><font size=2 face="Arial">           
    <i>'auth_url'</i>: con.auth_url,</font>
<br><font size=2 face="Arial">           
    <i>'token'</i>: None,</font>
<br><font size=2 face="Arial">           
    <i>'<u>username</u>'</i>: username,</font>
<br><font size=2 face="Arial">           
    <i>'password'</i>: password,</font>
<br><font size=2 face="Arial">           
    <i>'ca_file'</i>: <i>self</i>._get_client_option(<i>'heat'</i>,
<i>'ca_file'</i>),</font>
<br><font size=2 face="Arial">           
    <i>'cert_file'</i>: <i>self</i>._get_client_option(<i>'heat'</i>,
<i>'cert_file'</i>),</font>
<br><font size=2 face="Arial">           
    <i>'key_file'</i>: <i>self</i>._get_client_option(<i>'heat'</i>,
<i>'key_file'</i>),</font>
<br><font size=2 face="Arial">           
    <i>'insecure'</i>: <i>self</i>._get_client_option(<i>'heat'</i>,
<i>'insecure'</i>)</font>
<br><font size=2 face="Arial">           
}  </font>
<br><font size=2 face="Arial">        else:</font>
<br><font size=2 face="Arial">           
if <i>self</i>.auth_token is None:</font>
<br><font size=2 face="Arial">           
    logger.error(_(<i>"Heat connection failed, no auth_token!"</i>))</font>
<br><font size=2 face="Arial">           
    return None</font>
<br><font size=2 face="Arial">...</font>
<br>
<br><font size=2 face="Arial">Is this a known issue? </font>
<br>
<br><font size=2 face="Arial">-M</font>
<br><font size=2 face="sans-serif"><br>
________________________________<br>
Kind Regards,<br>
<br>
Michael D. Elder<br>
<br>
STSM | Master Inventor<br>
mdelder@us.ibm.com  | linkedin.com/in/mdelder<br>
<br>
"Success is not delivering a feature; success is learning how to solve
the customer’s problem.” -Mark Cook</font>