deployment_swift_data property for server resources

Adds a new property, deployment_swift_data for server resources. The
property is a map containing the swift container and object name to use
for storing the deployment data and generating temp url's for the
resource.

Making this a property allows it to be created externally from Heat with
known values prior to stack creation. This allows the configuration of
the os-collect-config agent on deployed servers prior to starting the
Heat stack creation.

implements blueprint split-stack-default

Change-Id: Ia07e9374a4b95bd0e74fc47fb9df4bf6ad096715
This commit is contained in:
James Slagle 2017-04-21 16:48:40 -04:00 committed by huangtianhua
parent 46e42c5184
commit c78ded7c5f
5 changed files with 347 additions and 14 deletions

View File

@ -37,9 +37,11 @@ class DeployedServer(server_base.BaseServer):
""" """
PROPERTIES = ( PROPERTIES = (
NAME, METADATA, SOFTWARE_CONFIG_TRANSPORT NAME, METADATA, SOFTWARE_CONFIG_TRANSPORT,
DEPLOYMENT_SWIFT_DATA
) = ( ) = (
'name', 'metadata', 'software_config_transport' 'name', 'metadata', 'software_config_transport',
'deployment_swift_data'
) )
_SOFTWARE_CONFIG_TRANSPORTS = ( _SOFTWARE_CONFIG_TRANSPORTS = (
@ -48,6 +50,12 @@ class DeployedServer(server_base.BaseServer):
'POLL_SERVER_CFN', 'POLL_SERVER_HEAT', 'POLL_TEMP_URL', 'ZAQAR_MESSAGE' 'POLL_SERVER_CFN', 'POLL_SERVER_HEAT', 'POLL_TEMP_URL', 'ZAQAR_MESSAGE'
) )
_DEPLOYMENT_SWIFT_DATA_KEYS = (
CONTAINER, OBJECT
) = (
'container', 'object',
)
properties_schema = { properties_schema = {
NAME: properties.Schema( NAME: properties.Schema(
properties.Schema.STRING, properties.Schema.STRING,
@ -79,6 +87,37 @@ class DeployedServer(server_base.BaseServer):
constraints.AllowedValues(_SOFTWARE_CONFIG_TRANSPORTS), constraints.AllowedValues(_SOFTWARE_CONFIG_TRANSPORTS),
] ]
), ),
DEPLOYMENT_SWIFT_DATA: properties.Schema(
properties.Schema.MAP,
_('Swift container and object to use for storing deployment data '
'for the server resource. The parameter is a map value '
'with the keys "container" and "object", and the values '
'are the corresponding container and object names. The '
'software_config_transport parameter must be set to '
'POLL_TEMP_URL for swift to be used. If not specified, '
'and software_config_transport is set to POLL_TEMP_URL, a '
'container will be automatically created from the resource '
'name, and the object name will be a generated uuid.'),
support_status=support.SupportStatus(version='9.0.0'),
default={},
update_allowed=True,
schema={
CONTAINER: properties.Schema(
properties.Schema.STRING,
_('Name of the container.'),
constraints=[
constraints.Length(min=1)
]
),
OBJECT: properties.Schema(
properties.Schema.STRING,
_('Name of the object.'),
constraints=[
constraints.Length(min=1)
]
)
}
)
} }
ATTRIBUTES = ( ATTRIBUTES = (

View File

@ -54,7 +54,7 @@ class Server(server_base.BaseServer, sh.SchedulerHintsMixin,
SCHEDULER_HINTS, METADATA, USER_DATA_FORMAT, USER_DATA, SCHEDULER_HINTS, METADATA, USER_DATA_FORMAT, USER_DATA,
RESERVATION_ID, CONFIG_DRIVE, DISK_CONFIG, PERSONALITY, RESERVATION_ID, CONFIG_DRIVE, DISK_CONFIG, PERSONALITY,
ADMIN_PASS, SOFTWARE_CONFIG_TRANSPORT, USER_DATA_UPDATE_POLICY, ADMIN_PASS, SOFTWARE_CONFIG_TRANSPORT, USER_DATA_UPDATE_POLICY,
TAGS TAGS, DEPLOYMENT_SWIFT_DATA
) = ( ) = (
'name', 'image', 'block_device_mapping', 'block_device_mapping_v2', 'name', 'image', 'block_device_mapping', 'block_device_mapping_v2',
'flavor', 'flavor_update_policy', 'image_update_policy', 'key_name', 'flavor', 'flavor_update_policy', 'image_update_policy', 'key_name',
@ -62,7 +62,7 @@ class Server(server_base.BaseServer, sh.SchedulerHintsMixin,
'scheduler_hints', 'metadata', 'user_data_format', 'user_data', 'scheduler_hints', 'metadata', 'user_data_format', 'user_data',
'reservation_id', 'config_drive', 'diskConfig', 'personality', 'reservation_id', 'config_drive', 'diskConfig', 'personality',
'admin_pass', 'software_config_transport', 'user_data_update_policy', 'admin_pass', 'software_config_transport', 'user_data_update_policy',
'tags' 'tags', 'deployment_swift_data'
) )
_BLOCK_DEVICE_MAPPING_KEYS = ( _BLOCK_DEVICE_MAPPING_KEYS = (
@ -135,6 +135,12 @@ class Server(server_base.BaseServer, sh.SchedulerHintsMixin,
'none', 'auto', 'none', 'auto',
) )
_DEPLOYMENT_SWIFT_DATA_KEYS = (
CONTAINER, OBJECT
) = (
'container', 'object',
)
ATTRIBUTES = ( ATTRIBUTES = (
NAME_ATTR, ADDRESSES, NETWORKS_ATTR, FIRST_ADDRESS, NAME_ATTR, ADDRESSES, NETWORKS_ATTR, FIRST_ADDRESS,
INSTANCE_NAME, ACCESSIPV4, ACCESSIPV6, CONSOLE_URLS, TAGS_ATTR, INSTANCE_NAME, ACCESSIPV4, ACCESSIPV6, CONSOLE_URLS, TAGS_ATTR,
@ -568,6 +574,37 @@ class Server(server_base.BaseServer, sh.SchedulerHintsMixin,
support_status=support.SupportStatus(version='8.0.0'), support_status=support.SupportStatus(version='8.0.0'),
schema=properties.Schema(properties.Schema.STRING), schema=properties.Schema(properties.Schema.STRING),
update_allowed=True update_allowed=True
),
DEPLOYMENT_SWIFT_DATA: properties.Schema(
properties.Schema.MAP,
_('Swift container and object to use for storing deployment data '
'for the server resource. The parameter is a map value '
'with the keys "container" and "object", and the values '
'are the corresponding container and object names. The '
'software_config_transport parameter must be set to '
'POLL_TEMP_URL for swift to be used. If not specified, '
'and software_config_transport is set to POLL_TEMP_URL, a '
'container will be automatically created from the resource '
'name, and the object name will be a generated uuid.'),
support_status=support.SupportStatus(version='9.0.0'),
default={},
update_allowed=True,
schema={
CONTAINER: properties.Schema(
properties.Schema.STRING,
_('Name of the container.'),
constraints=[
constraints.Length(min=1)
]
),
OBJECT: properties.Schema(
properties.Schema.STRING,
_('Name of the object.'),
constraints=[
constraints.Length(min=1)
]
)
}
) )
} }

View File

@ -45,6 +45,22 @@ class BaseServer(stack_user.StackUser):
return self.physical_resource_name() return self.physical_resource_name()
def _container_and_object_name(self, props):
deployment_swift_data = props.get(
self.DEPLOYMENT_SWIFT_DATA,
self.properties[self.DEPLOYMENT_SWIFT_DATA])
container_name = deployment_swift_data[self.CONTAINER]
if container_name is None:
container_name = self.physical_resource_name()
object_name = deployment_swift_data[self.OBJECT]
if object_name is None:
object_name = self.data().get('metadata_object_name')
if object_name is None:
object_name = str(uuid.uuid4())
return container_name, object_name
def _populate_deployments_metadata(self, meta, props): def _populate_deployments_metadata(self, meta, props):
meta['deployments'] = meta.get('deployments', []) meta['deployments'] = meta.get('deployments', [])
meta['os-collect-config'] = meta.get('os-collect-config', {}) meta['os-collect-config'] = meta.get('os-collect-config', {})
@ -94,17 +110,15 @@ class BaseServer(stack_user.StackUser):
collectors.append('cfn') collectors.append('cfn')
elif self.transport_poll_temp_url(props): elif self.transport_poll_temp_url(props):
container = self.physical_resource_name() container_name, object_name = self._container_and_object_name(
object_name = self.data().get('metadata_object_name') props)
if not object_name:
object_name = str(uuid.uuid4())
self.client('swift').put_container(container) self.client('swift').put_container(container_name)
url = self.client_plugin('swift').get_temp_url( url = self.client_plugin('swift').get_temp_url(
container, object_name, method='GET') container_name, object_name, method='GET')
put_url = self.client_plugin('swift').get_temp_url( put_url = self.client_plugin('swift').get_temp_url(
container, object_name) container_name, object_name)
self.data_set('metadata_put_url', put_url) self.data_set('metadata_put_url', put_url)
self.data_set('metadata_object_name', object_name) self.data_set('metadata_object_name', object_name)
@ -126,9 +140,10 @@ class BaseServer(stack_user.StackUser):
object_name = self.data().get('metadata_object_name') object_name = self.data().get('metadata_object_name')
if object_name: if object_name:
container = self.physical_resource_name() container_name, object_name = self._container_and_object_name(
props)
self.client('swift').put_object( self.client('swift').put_object(
container, object_name, jsonutils.dumps(meta)) container_name, object_name, jsonutils.dumps(meta))
self.attributes.reset_resolved_values() self.attributes.reset_resolved_values()
@ -278,7 +293,9 @@ class BaseServer(stack_user.StackUser):
if not object_name: if not object_name:
return return
with self.client_plugin('swift').ignore_not_found: with self.client_plugin('swift').ignore_not_found:
container = self.physical_resource_name() container = self.properties[self.DEPLOYMENT_SWIFT_DATA].get(
'container')
container = container or self.physical_resource_name()
swift = self.client('swift') swift = self.client('swift')
swift.delete_object(container, object_name) swift.delete_object(container, object_name)
headers = swift.head_container(container) headers = swift.head_container(container)

View File

@ -17,6 +17,7 @@ from oslo_serialization import jsonutils
from oslo_utils import uuidutils from oslo_utils import uuidutils
from six.moves.urllib import parse as urlparse from six.moves.urllib import parse as urlparse
from heat.common import exception
from heat.common import template_format from heat.common import template_format
from heat.engine.clients.os import heat_plugin from heat.engine.clients.os import heat_plugin
from heat.engine.clients.os import swift from heat.engine.clients.os import swift
@ -65,6 +66,66 @@ resources:
software_config_transport: ZAQAR_MESSAGE software_config_transport: ZAQAR_MESSAGE
""" """
ds_deployment_data_tmpl = """
heat_template_version: 2015-10-15
resources:
server:
type: OS::Heat::DeployedServer
properties:
software_config_transport: POLL_TEMP_URL
deployment_swift_data:
container: my-custom-container
object: my-custom-object
"""
ds_deployment_data_bad_container_tmpl = """
heat_template_version: 2015-10-15
resources:
server:
type: OS::Heat::DeployedServer
properties:
software_config_transport: POLL_TEMP_URL
deployment_swift_data:
container: ''
object: 'my-custom-object'
"""
ds_deployment_data_bad_object_tmpl = """
heat_template_version: 2015-10-15
resources:
server:
type: OS::Heat::DeployedServer
properties:
software_config_transport: POLL_TEMP_URL
deployment_swift_data:
container: 'my-custom-container'
object: ''
"""
ds_deployment_data_none_container_tmpl = """
heat_template_version: 2015-10-15
resources:
server:
type: OS::Heat::DeployedServer
properties:
software_config_transport: POLL_TEMP_URL
deployment_swift_data:
container: 0
object: 'my-custom-object'
"""
ds_deployment_data_none_object_tmpl = """
heat_template_version: 2015-10-15
resources:
server:
type: OS::Heat::DeployedServer
properties:
software_config_transport: POLL_TEMP_URL
deployment_swift_data:
container: 'my-custom-container'
object: 0
"""
class DeployedServersTest(common.HeatTestCase): class DeployedServersTest(common.HeatTestCase):
def setUp(self): def setUp(self):
@ -128,6 +189,178 @@ class DeployedServersTest(common.HeatTestCase):
sc.delete_container.assert_called_once_with(container_name) sc.delete_container.assert_called_once_with(container_name)
return metadata_url, server return metadata_url, server
def test_server_create_deployment_swift_data(self):
server_name = 'server'
stack_name = '%s_s' % server_name
(tmpl, stack) = self._setup_test_stack(
stack_name,
ds_deployment_data_tmpl)
props = tmpl.t['resources']['server']['properties']
props['software_config_transport'] = 'POLL_TEMP_URL'
self.server_props = props
resource_defns = tmpl.resource_definitions(stack)
server = deployed_server.DeployedServer(
server_name, resource_defns[server_name], stack)
sc = mock.Mock()
sc.head_account.return_value = {
'x-account-meta-temp-url-key': 'secrit'
}
sc.url = 'http://192.0.2.2'
self.patchobject(swift.SwiftClientPlugin, '_create',
return_value=sc)
scheduler.TaskRunner(server.create)()
# self._create_test_server(server_name)
metadata_put_url = server.data().get('metadata_put_url')
md = server.metadata_get()
metadata_url = md['os-collect-config']['request']['metadata_url']
self.assertNotEqual(metadata_url, metadata_put_url)
container_name = 'my-custom-container'
object_name = 'my-custom-object'
test_path = '/v1/AUTH_test_tenant_id/%s/%s' % (
container_name, object_name)
self.assertEqual(test_path, urlparse.urlparse(metadata_put_url).path)
self.assertEqual(test_path, urlparse.urlparse(metadata_url).path)
sc.put_object.assert_called_once_with(
container_name, object_name, jsonutils.dumps(md))
sc.head_container.return_value = {'x-container-object-count': '0'}
server._delete_temp_url()
sc.delete_object.assert_called_once_with(container_name, object_name)
sc.head_container.assert_called_once_with(container_name)
sc.delete_container.assert_called_once_with(container_name)
return metadata_url, server
def test_server_create_deployment_swift_data_bad_container(self):
server_name = 'server'
stack_name = '%s_s' % server_name
(tmpl, stack) = self._setup_test_stack(
stack_name,
ds_deployment_data_bad_container_tmpl)
props = tmpl.t['resources']['server']['properties']
props['software_config_transport'] = 'POLL_TEMP_URL'
self.server_props = props
resource_defns = tmpl.resource_definitions(stack)
server = deployed_server.DeployedServer(
server_name, resource_defns[server_name], stack)
self.assertRaises(exception.StackValidationFailed, server.validate)
def test_server_create_deployment_swift_data_bad_object(self):
server_name = 'server'
stack_name = '%s_s' % server_name
(tmpl, stack) = self._setup_test_stack(
stack_name,
ds_deployment_data_bad_object_tmpl)
props = tmpl.t['resources']['server']['properties']
props['software_config_transport'] = 'POLL_TEMP_URL'
self.server_props = props
resource_defns = tmpl.resource_definitions(stack)
server = deployed_server.DeployedServer(
server_name, resource_defns[server_name], stack)
self.assertRaises(exception.StackValidationFailed, server.validate)
def test_server_create_deployment_swift_data_none_container(self):
server_name = 'server'
stack_name = '%s_s' % server_name
(tmpl, stack) = self._setup_test_stack(
stack_name,
ds_deployment_data_none_container_tmpl)
props = tmpl.t['resources']['server']['properties']
props['software_config_transport'] = 'POLL_TEMP_URL'
self.server_props = props
resource_defns = tmpl.resource_definitions(stack)
server = deployed_server.DeployedServer(
server_name, resource_defns[server_name], stack)
sc = mock.Mock()
sc.head_account.return_value = {
'x-account-meta-temp-url-key': 'secrit'
}
sc.url = 'http://192.0.2.2'
self.patchobject(swift.SwiftClientPlugin, '_create',
return_value=sc)
scheduler.TaskRunner(server.create)()
# self._create_test_server(server_name)
metadata_put_url = server.data().get('metadata_put_url')
md = server.metadata_get()
metadata_url = md['os-collect-config']['request']['metadata_url']
self.assertNotEqual(metadata_url, metadata_put_url)
container_name = '0'
object_name = 'my-custom-object'
test_path = '/v1/AUTH_test_tenant_id/%s/%s' % (
container_name, object_name)
self.assertEqual(test_path, urlparse.urlparse(metadata_put_url).path)
self.assertEqual(test_path, urlparse.urlparse(metadata_url).path)
sc.put_object.assert_called_once_with(
container_name, object_name, jsonutils.dumps(md))
sc.head_container.return_value = {'x-container-object-count': '0'}
server._delete_temp_url()
sc.delete_object.assert_called_once_with(container_name, object_name)
sc.head_container.assert_called_once_with(container_name)
sc.delete_container.assert_called_once_with(container_name)
return metadata_url, server
def test_server_create_deployment_swift_data_none_object(self):
server_name = 'server'
stack_name = '%s_s' % server_name
(tmpl, stack) = self._setup_test_stack(
stack_name,
ds_deployment_data_none_object_tmpl)
props = tmpl.t['resources']['server']['properties']
props['software_config_transport'] = 'POLL_TEMP_URL'
self.server_props = props
resource_defns = tmpl.resource_definitions(stack)
server = deployed_server.DeployedServer(
server_name, resource_defns[server_name], stack)
sc = mock.Mock()
sc.head_account.return_value = {
'x-account-meta-temp-url-key': 'secrit'
}
sc.url = 'http://192.0.2.2'
self.patchobject(swift.SwiftClientPlugin, '_create',
return_value=sc)
scheduler.TaskRunner(server.create)()
# self._create_test_server(server_name)
metadata_put_url = server.data().get('metadata_put_url')
md = server.metadata_get()
metadata_url = md['os-collect-config']['request']['metadata_url']
self.assertNotEqual(metadata_url, metadata_put_url)
container_name = 'my-custom-container'
object_name = '0'
test_path = '/v1/AUTH_test_tenant_id/%s/%s' % (
container_name, object_name)
self.assertEqual(test_path, urlparse.urlparse(metadata_put_url).path)
self.assertEqual(test_path, urlparse.urlparse(metadata_url).path)
sc.put_object.assert_called_once_with(
container_name, object_name, jsonutils.dumps(md))
sc.head_container.return_value = {'x-container-object-count': '0'}
server._delete_temp_url()
sc.delete_object.assert_called_once_with(container_name, object_name)
sc.head_container.assert_called_once_with(container_name)
sc.delete_container.assert_called_once_with(container_name)
return metadata_url, server
def test_server_create_software_config_poll_temp_url(self): def test_server_create_software_config_poll_temp_url(self):
metadata_url, server = ( metadata_url, server = (
self._server_create_software_config_poll_temp_url()) self._server_create_software_config_poll_temp_url())

View File

@ -0,0 +1,7 @@
---
features:
- A new property, deployment_swift_data is added to the OS::Nova::Server
and OS::Heat::DeployedServer resources. The property is used to define
the Swift container and object name that is used for deployment data
for the server. If unset, the fallback is the previous behavior where
these values will be automatically generated.