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:
parent
40b0ec4715
commit
00fb06262a
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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}}})
|
||||
|
|
Loading…
Reference in New Issue