Add personality files property to Server resource

This will allow injection of user-provided files during server creation.

Blueprint native-nova-cloud-server

Change-Id: I943b85f74874fdfb92b0cadfe412c395f77884a1
This commit is contained in:
Jason Dunsmore 2014-01-23 10:12:33 -06:00
parent 40b0ec4715
commit 00fb06262a
3 changed files with 152 additions and 21 deletions

View File

@ -38,13 +38,13 @@ class Server(resource.Resource):
FLAVOR_UPDATE_POLICY, IMAGE_UPDATE_POLICY, KEY_NAME,
ADMIN_USER, AVAILABILITY_ZONE, SECURITY_GROUPS, NETWORKS,
SCHEDULER_HINTS, METADATA, USER_DATA_FORMAT, USER_DATA,
RESERVATION_ID, CONFIG_DRIVE, DISK_CONFIG,
RESERVATION_ID, CONFIG_DRIVE, DISK_CONFIG, PERSONALITY,
) = (
'name', 'image', 'block_device_mapping', 'flavor',
'flavor_update_policy', 'image_update_policy', 'key_name',
'admin_user', 'availability_zone', 'security_groups', 'networks',
'scheduler_hints', 'metadata', 'user_data_format', 'user_data',
'reservation_id', 'config_drive', 'diskConfig',
'reservation_id', 'config_drive', 'diskConfig', 'personality',
)
_BLOCK_DEVICE_MAPPING_KEYS = (
@ -231,6 +231,12 @@ class Server(resource.Resource):
constraints.AllowedValues(['AUTO', 'MANUAL']),
]
),
PERSONALITY: properties.Schema(
properties.Schema.MAP,
_('A map of files to create/overwrite on the server upon boot. '
'Keys are file names and values are the file contents.'),
default={}
)
}
attributes_schema = {
@ -268,6 +274,10 @@ class Server(resource.Resource):
return super(Server, self).physical_resource_name()
def _personality(self):
# This method is overridden by the derived CloudServer resource
return self.properties.get(self.PERSONALITY)
def handle_create(self):
security_groups = self.properties.get(self.SECURITY_GROUPS)
@ -320,7 +330,8 @@ class Server(resource.Resource):
block_device_mapping=block_device_mapping,
reservation_id=reservation_id,
config_drive=config_drive,
disk_config=disk_config)
disk_config=disk_config,
files=self._personality())
finally:
# Avoid a race condition where the thread could be cancelled
# before the ID is stored
@ -570,18 +581,39 @@ class Server(resource.Resource):
network=network[self.NETWORK_ID],
server=self.name))
# retrieve provider's absolute limits if it will be needed
metadata = self.properties.get(self.METADATA)
personality = self._personality()
if metadata is not None or personality is not None:
limits = nova_utils.absolute_limits(self.nova())
# verify that the number of metadata entries is not greater
# than the maximum number allowed in the provider's absolute
# limits
metadata = self.properties.get('metadata')
if metadata is not None:
limits = nova_utils.absolute_limits(self.nova())
if len(metadata) > limits['maxServerMeta']:
msg = _('Instance metadata must not contain greater than %s '
'entries. This is the maximum number allowed by your '
'service provider') % limits['maxServerMeta']
raise exception.StackValidationFailed(message=msg)
# verify the number of personality files and the size of each
# personality file against the provider's absolute limits
if personality is not None:
if len(personality) > limits['maxPersonality']:
msg = _("The personality property may not contain "
"greater than %s entries.") % limits['maxPersonality']
raise exception.StackValidationFailed(message=msg)
for path, contents in personality.items():
if len(bytes(contents)) > limits['maxPersonalitySize']:
msg = (_("The contents of personality file \"%(path)s\" "
"is larger than the maximum allowed personality "
"file size (%(max_size)s bytes).") %
{'path': path,
'max_size': limits['maxPersonalitySize']})
raise exception.StackValidationFailed(message=msg)
def handle_delete(self):
'''
Delete a server, blocking until it is disposed by OpenStack

View File

@ -64,6 +64,22 @@ class ServersTest(HeatTestCase):
super(ServersTest, self).setUp()
self.fc = fakes.FakeClient()
utils.setup_dummy_db()
self.limits = self.m.CreateMockAnything()
self.limits.absolute = self._limits_absolute()
def _limits_absolute(self):
max_personality = self.m.CreateMockAnything()
max_personality.name = 'maxPersonality'
max_personality.value = 5
max_personality_size = self.m.CreateMockAnything()
max_personality_size.name = 'maxPersonalitySize'
max_personality_size.value = 10240
max_server_meta = self.m.CreateMockAnything()
max_server_meta.name = 'maxServerMeta'
max_server_meta.value = 3
yield max_personality
yield max_personality_size
yield max_server_meta
def _setup_test_stack(self, stack_name):
t = template_format.parse(wp_template)
@ -106,7 +122,7 @@ class ServersTest(HeatTestCase):
userdata=mox.IgnoreArg(), scheduler_hints=None,
meta=None, nics=None, availability_zone=None,
block_device_mapping=None, config_drive=None,
disk_config=None, reservation_id=None).AndReturn(
disk_config=None, reservation_id=None, files={}).AndReturn(
return_server)
return server
@ -224,7 +240,7 @@ class ServersTest(HeatTestCase):
userdata=mox.IgnoreArg(), scheduler_hints=None,
meta=instance_meta, nics=None, availability_zone=None,
block_device_mapping=None, config_drive=None,
disk_config=None, reservation_id=None).AndReturn(
disk_config=None, reservation_id=None, files={}).AndReturn(
return_server)
self.m.StubOutWithMock(server, 'nova')
@ -380,7 +396,7 @@ class ServersTest(HeatTestCase):
userdata='wordpress', scheduler_hints=None,
meta=None, nics=None, availability_zone=None,
block_device_mapping=None, config_drive=None,
disk_config=None, reservation_id=None).AndReturn(
disk_config=None, reservation_id=None, files={}).AndReturn(
return_server)
self.m.ReplayAll()
@ -1300,13 +1316,8 @@ class ServersTest(HeatTestCase):
server = servers.Server('server_create_image_err',
t['Resources']['WebServer'], stack)
limits = self.m.CreateMockAnything()
max_server_meta = self.m.CreateMockAnything()
max_server_meta.name = 'maxServerMeta'
max_server_meta.value = 3
limits.absolute = [max_server_meta]
self.m.StubOutWithMock(self.fc.limits, 'get')
self.fc.limits.get().AndReturn(limits)
self.fc.limits.get().MultipleTimes().AndReturn(self.limits)
self.m.StubOutWithMock(server, 'nova')
server.nova().MultipleTimes().AndReturn(self.fc)
@ -1328,13 +1339,57 @@ class ServersTest(HeatTestCase):
server = servers.Server('server_create_image_err',
t['Resources']['WebServer'], stack)
limits = self.m.CreateMockAnything()
max_server_meta = self.m.CreateMockAnything()
max_server_meta.name = 'maxServerMeta'
max_server_meta.value = 3
limits.absolute = [max_server_meta]
self.m.StubOutWithMock(self.fc.limits, 'get')
self.fc.limits.get().AndReturn(limits)
self.fc.limits.get().MultipleTimes().AndReturn(self.limits)
self.m.StubOutWithMock(server, 'nova')
server.nova().MultipleTimes().AndReturn(self.fc)
self.m.ReplayAll()
self.assertIsNone(server.validate())
self.m.VerifyAll()
def test_server_validate_too_many_personality(self):
stack_name = 'srv_val'
(t, stack) = self._setup_test_stack(stack_name)
t['Resources']['WebServer']['Properties']['personality'] = \
{"/fake/path1": "fake contents1",
"/fake/path2": "fake_contents2",
"/fake/path3": "fake_contents3",
"/fake/path4": "fake_contents4",
"/fake/path5": "fake_contents5",
"/fake/path6": "fake_contents6"}
server = servers.Server('server_create_image_err',
t['Resources']['WebServer'], stack)
self.m.StubOutWithMock(self.fc.limits, 'get')
self.fc.limits.get().MultipleTimes().AndReturn(self.limits)
self.m.StubOutWithMock(server, 'nova')
server.nova().MultipleTimes().AndReturn(self.fc)
self.m.ReplayAll()
exc = self.assertRaises(exception.StackValidationFailed,
server.validate)
self.assertEqual("The personality property may not contain "
"greater than 5 entries.", str(exc))
self.m.VerifyAll()
def test_server_validate_personality_okay(self):
stack_name = 'srv_val'
(t, stack) = self._setup_test_stack(stack_name)
t['Resources']['WebServer']['Properties']['personality'] = \
{"/fake/path1": "fake contents1",
"/fake/path2": "fake_contents2",
"/fake/path3": "fake_contents3",
"/fake/path4": "fake_contents4",
"/fake/path5": "fake_contents5"}
server = servers.Server('server_create_image_err',
t['Resources']['WebServer'], stack)
self.m.StubOutWithMock(self.fc.limits, 'get')
self.fc.limits.get().MultipleTimes().AndReturn(self.limits)
self.m.StubOutWithMock(server, 'nova')
server.nova().MultipleTimes().AndReturn(self.fc)
@ -1342,3 +1397,45 @@ class ServersTest(HeatTestCase):
self.assertIsNone(server.validate())
self.m.VerifyAll()
def test_server_validate_personality_file_size_okay(self):
stack_name = 'srv_val'
(t, stack) = self._setup_test_stack(stack_name)
t['Resources']['WebServer']['Properties']['personality'] = \
{"/fake/path1": "a" * 10240}
server = servers.Server('server_create_image_err',
t['Resources']['WebServer'], stack)
self.m.StubOutWithMock(self.fc.limits, 'get')
self.fc.limits.get().MultipleTimes().AndReturn(self.limits)
self.m.StubOutWithMock(server, 'nova')
server.nova().MultipleTimes().AndReturn(self.fc)
self.m.ReplayAll()
self.assertIsNone(server.validate())
self.m.VerifyAll()
def test_server_validate_personality_file_size_too_big(self):
stack_name = 'srv_val'
(t, stack) = self._setup_test_stack(stack_name)
t['Resources']['WebServer']['Properties']['personality'] = \
{"/fake/path1": "a" * 10241}
server = servers.Server('server_create_image_err',
t['Resources']['WebServer'], stack)
self.m.StubOutWithMock(self.fc.limits, 'get')
self.fc.limits.get().MultipleTimes().AndReturn(self.limits)
self.m.StubOutWithMock(server, 'nova')
server.nova().MultipleTimes().AndReturn(self.fc)
self.m.ReplayAll()
exc = self.assertRaises(exception.StackValidationFailed,
server.validate)
self.assertEqual("The contents of personality file \"/fake/path1\" "
"is larger than the maximum allowed personality "
"file size (10240 bytes).", str(exc))
self.m.VerifyAll()

View File

@ -373,4 +373,6 @@ class FakeHTTPClient(base_client.HTTPClient):
# Limits
#
def get_limits(self, *kw):
return (200, {'limits': {'absolute': {'maxServerMeta': 3}}})
return (200, {'limits': {'absolute': {'maxServerMeta': 3,
'maxPersonalitySize': 10240,
'maxPersonality': 5}}})