Fail to create a stack calling Fn::GetAtt on Quantum resource

Bug #1180293 reported by Simon Pasquier
6
This bug affects 1 person
Affects Status Importance Assigned to Milestone
OpenStack Heat
Fix Released
Medium
Steve Baker

Bug Description

I have a template creating a Quantum port and an instance using that port. If I want to access a property of the Quantum port (such as the MAC address of the port) in the UserData of the instance, the stack creation fails .
As a side note, it is not possible today to get the private IP address because Fn::GetAtt(port, 'fixed_ips') returns a list of map.

$ heat stack-create -f CrossReferencing_Quantum_Port.template -P "KeyName=test;NetworkUuid=c45cad0c-415b-4225-b2cb-d6baa0145259" test3
400 Bad Request

The server could not comply with the request since it is either malformed or otherwise incorrect.

 Remote error: StackValidationFailed 404 Not Found The resource could not be found. [u'Traceback (most recent call last):\n', u' File "/opt/stack/heat/heat/openstack/common/rpc/amqp.py", line 276, in _process_data\n rval = self.proxy.dispatch(ctxt, version, method, **args)\n', u' File "/opt/stack/heat/heat/openstack/common/rpc/dispatcher.py", line 133, in dispatch\n return getattr(proxyobj, method)(ctxt, **kwargs)\n', u' File "/opt/stack/heat/heat/engine/service.py", line 50, in wrapped\n return func(self, ctx, *args, **kwargs)\n', u' File "/opt/stack/heat/heat/engine/service.py", line 220, in create_stack\n stack.validate()\n', u' File "/opt/stack/heat/heat/engine/parser.py", line 251, in validate\n raise StackValidationFailed(message=str(ex))\n', u'StackValidationFailed: 404 Not Found\n\nThe resource could not be found.\n\n \n'].

Here is a template to reproduce the bug:
{
  "AWSTemplateFormatVersion" : "2010-09-09",

  "Description" : "Test Quantum resources with very simple image",

  "Parameters" : {

    "KeyName" : {
      "Description" : "Name of an existing EC2 KeyPair to enable SSH access to the instances",
      "Type" : "String"
    },

    "NetworkUuid" : {
      "Description" : "Network UUID",
      "Type" : "String"
    },

    "InstanceType" : {
      "Description" : "WebServer EC2 instance type",
      "Type" : "String",
      "Default" : "m1.nano",
      "AllowedValues" : [ "m1.nano", "t1.micro", "m1.small", "m1.large", "m1.xlarge", "m2.xlarge", "m2.2xlarge", "m2.4xlarge", "c1.medium", "c1.xlarge", "cc1.4xlarge" ],
      "ConstraintDescription" : "must be a valid EC2 instance type."
    }
  },

  "Resources" : {
    "port": {
      "Type": "OS::Quantum::Port",
      "Properties": {
        "network_id": { "Ref": "NetworkUuid" }
      }
    },

    "WebServerSecurityGroup" : {
      "Type" : "AWS::EC2::SecurityGroup",
      "Properties" : {
        "GroupDescription" : "Enable HTTP access via port 80 plus SSH access",
        "SecurityGroupIngress" : [
          {"IpProtocol" : "icmp", "FromPort" : "-1", "ToPort" : "-1", "CidrIp" : "0.0.0.0/0"},
          {"IpProtocol" : "tcp", "FromPort" : "80", "ToPort" : "80", "CidrIp" : "0.0.0.0/0"},
          {"IpProtocol" : "tcp", "FromPort" : "22", "ToPort" : "22", "CidrIp" : "0.0.0.0/0"}
        ]
      }
    },

    "WebServer": {
      "Type": "AWS::EC2::Instance",
      "DependsOn" : "port",
      "Properties": {
        "ImageId" : "cirros-0.3.1-x86_64-uec",
        "InstanceType" : { "Ref" : "InstanceType" },
        "KeyName" : { "Ref" : "KeyName" },
        "SecurityGroups" : [ {"Ref" : "WebServerSecurityGroup"} ],
        "NetworkInterfaces" : [ { "NetworkInterfaceId" : { "Ref" : "port" }, "DeviceIndex" : 0 }],
        "UserData" : { "Fn::Base64" : { "Fn::Join" : ["", [
          "#!/bin/bash -v\n",
          "echo \"Running userdata script at $(date)\" > /tmp/status\n",
          "echo ", { "Fn::GetAtt" : [ "port", "mac_address" ] }, "\n"
        ]]}}
      }
    }
  },

  "Outputs" : {
    "IP" : {
     "Value" : { "Fn::GetAtt" : [ "port", "mac_address" ] },
     "Description" : "IP"
    }
  }
}

heat-engine logs:
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser Traceback (most recent call last):
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser File "/opt/stack/heat/heat/engine/parser.py", line 248, in validate
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser result = res.validate()
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser File "/opt/stack/heat/heat/engine/resources/instance.py", line 388, in validate
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser res = super(Instance, self).validate()
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser File "/opt/stack/heat/heat/engine/resource.py", line 404, in validate
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser return self.properties.validate()
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser File "/opt/stack/heat/heat/engine/properties.py", line 179, in validate
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser self[key]
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser File "/opt/stack/heat/heat/engine/properties.py", line 201, in __getitem__
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser value = self.resolve(self.data[key])
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser File "/opt/stack/heat/heat/engine/parser.py", line 575, in resolve_runtime_data
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser return resolve_runtime_data(self.t, self.resources, snippet)
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser File "/opt/stack/heat/heat/engine/parser.py", line 605, in resolve_runtime_data
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser template.resolve_base64])
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser File "/opt/stack/heat/heat/engine/parser.py", line 614, in transform
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser data = t(data)
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser File "/opt/stack/heat/heat/engine/template.py", line 153, in resolve_attributes
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser return _resolve(lambda k, v: k == 'Fn::GetAtt', handle_getatt, s)
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser File "/opt/stack/heat/heat/engine/template.py", line 244, in _resolve
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser return dict((k, recurse(v)) for k, v in snippet.items())
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser File "/opt/stack/heat/heat/engine/template.py", line 244, in <genexpr>
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser return dict((k, recurse(v)) for k, v in snippet.items())
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser File "/opt/stack/heat/heat/engine/template.py", line 237, in <lambda>
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser recurse = lambda s: _resolve(match, handle, s)
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser File "/opt/stack/heat/heat/engine/template.py", line 244, in _resolve
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser return dict((k, recurse(v)) for k, v in snippet.items())
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser File "/opt/stack/heat/heat/engine/template.py", line 244, in <genexpr>
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser return dict((k, recurse(v)) for k, v in snippet.items())
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser File "/opt/stack/heat/heat/engine/template.py", line 237, in <lambda>
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser recurse = lambda s: _resolve(match, handle, s)
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser File "/opt/stack/heat/heat/engine/template.py", line 246, in _resolve
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser return [recurse(v) for v in snippet]
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser File "/opt/stack/heat/heat/engine/template.py", line 237, in <lambda>
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser recurse = lambda s: _resolve(match, handle, s)
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser File "/opt/stack/heat/heat/engine/template.py", line 246, in _resolve
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser return [recurse(v) for v in snippet]
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser File "/opt/stack/heat/heat/engine/template.py", line 237, in <lambda>
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser recurse = lambda s: _resolve(match, handle, s)
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser File "/opt/stack/heat/heat/engine/template.py", line 243, in _resolve
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser return handle(recurse(v))
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser File "/opt/stack/heat/heat/engine/template.py", line 148, in handle_getatt
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser return resources[resource].FnGetAtt(att)
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser File "/opt/stack/heat/heat/engine/resources/quantum/port.py", line 65, in FnGetAtt
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser self.resource_id)['port']
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser File "/opt/stack/python-quantumclient/quantumclient/v2_0/client.py", line 108, in with_params
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser ret = self.function(instance, *args, **kwargs)
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser File "/opt/stack/python-quantumclient/quantumclient/v2_0/client.py", line 264, in show_port
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser return self.get(self.port_path % (port), params=_params)
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser File "/opt/stack/python-quantumclient/quantumclient/v2_0/client.py", line 988, in get
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser headers=headers, params=params)
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser File "/opt/stack/python-quantumclient/quantumclient/v2_0/client.py", line 973, in retry_request
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser headers=headers, params=params)
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser File "/opt/stack/python-quantumclient/quantumclient/v2_0/client.py", line 915, in do_request
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser self._handle_fault_response(status_code, replybody)
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser File "/opt/stack/python-quantumclient/quantumclient/v2_0/client.py", line 895, in _handle_fault_response
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser exception_handler_v20(status_code, des_error_body)
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser File "/opt/stack/python-quantumclient/quantumclient/v2_0/client.py", line 88, in exception_handler_v20
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser message=message)
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser QuantumClientException: 404 Not Found
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser
2013-05-15 10:48:50.263 24245 TRACE heat.engine.parser The resource could not be found.

Changed in heat:
assignee: nobody → Steve Baker (steve-stevebaker)
status: New → Confirmed
importance: Undecided → Medium
milestone: none → havana-1
Revision history for this message
OpenStack Infra (hudson-openstack) wrote : Fix proposed to heat (master)

Fix proposed to branch: master
Review: https://review.openstack.org/29317

Changed in heat:
status: Confirmed → In Progress
Revision history for this message
OpenStack Infra (hudson-openstack) wrote : Fix merged to heat (master)

Reviewed: https://review.openstack.org/29317
Committed: http://github.com/openstack/heat/commit/1796f3ec0f2787738d83b62a66b5efaa8563146d
Submitter: Jenkins
Branch: master

commit 1796f3ec0f2787738d83b62a66b5efaa8563146d
Author: Steve Baker <email address hidden>
Date: Thu May 16 10:02:34 2013 +1200

    Tolerate resource lookup errors for quantum FnGetAtt.

    Fixes bug #1180293

    Change-Id: I5384456bbbf53558036573c983be0b65e8883612

Changed in heat:
status: In Progress → Fix Committed
Revision history for this message
Steve Baker (steve-stevebaker) wrote :

Please retest with the above commit and report back

Revision history for this message
Simon Pasquier (simon-pasquier) wrote :

It doesn't work with my original description because Fn::GetAtt returns None and it now fails on Fn::Join. Not sure if converting none object to empty string is the right approach though?

BTW it might be not be a bad idea to check in resolve_join() that all elements of the list are strings and raise an exception if not (right now the error message is not super obvious).

Thierry Carrez (ttx)
Changed in heat:
status: Fix Committed → Fix Released
Steven Hardy (shardy)
tags: added: grizzly-backport-potential
Thierry Carrez (ttx)
Changed in heat:
milestone: havana-1 → 2013.2
Alan Pevec (apevec)
tags: removed: grizzly-backport-potential
no longer affects: heat/grizzly
To post a comment you must log in.
This report contains Public information  
Everyone can see this information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.