implement modify/reset instance attribute. part 1.

implement only one attribute - disableApiTermination.

Change-Id: Ida88bcb047bcadd6cfe18c76f3af4777a431dad2
This commit is contained in:
Andrey Pavlov 2015-05-09 11:36:34 +03:00
parent 21e63c3309
commit 95c8930bfb
4 changed files with 263 additions and 9 deletions

View File

@ -131,8 +131,6 @@ Image related:
Instance related:
- DescribeInstanceStatus
- ReportInstanceStatus
- ModifyInstanceAttribute
- ResetInstanceAttribute
- productCodes Instance property
- sourceDestCheck Instance property
- ebsOptimized Instance property
@ -143,7 +141,6 @@ Instance related:
- publicDnsName Instance property
- stateTransitionReason Instance property
- architecture Instance property
- ebsOptimized Instance property
- hypervisor Instance property
- iamInstanceProfile Instance property
- instanceLifecycle Instance property
@ -151,7 +148,6 @@ Instance related:
- stateReason Instance property
- virtualizationType Instance property
- instanceInitiatedShutdownBehavior Instance attribute
- disableApiTermination Instance attribute
- attachTime EbsInstanceBlockDevice property
Network interface related:

View File

@ -596,6 +596,101 @@ class CloudController(object):
Specified attribute.
"""
@module_and_param_types(instance, 'i_id', 'str',
'dummy', 'bool',
'dummy',
'bool',
'bool', 'sg_ids',
'str',
'str', 'str',
'str', 'str',
'str')
def modify_instance_attribute(self, context, instance_id, attribute=None,
value=None, source_dest_check=None,
block_device_mapping=None,
disable_api_termination=None,
ebs_optimized=None, group_id=None,
instance_initiated_shutdown_behavior=None,
instance_type=None, kernel=None,
ramdisk=None, sriov_net_support=None,
user_data=None):
"""Modifies the specified attribute of the specified instance.
Args:
context (RequestContext): The request context.
instance_id (str): The ID of the instance.
attribute (str): The name of the attribute.
value: The value of the attribute being modified.
source_dest_check: Indicates whether source/destination checking is
enabled. A value of true means checking is enabled, and false
means checking is disabled.
This value must be false for a NAT instance to perform NAT.
Unsupported now.
block_device_mapping (list of dict):
Modifies the DeleteOnTermination attribute for volumes that are
currently attached. The volume must be owned by the caller. If
no value is specified for DeleteOnTermination, the default is
true and the volume is deleted when the instance is terminated.
Dict can contain:
device_name (str): The device name exposed to the instance
(for example, /dev/sdh or xvdh).
virtual_name (str): The virtual device name (ephemeral[0..3]).
ebs (dict): Dict can contain:
volume_id (str): The ID of the volume (Nova extension).
delete_on_termination (bool): Indicates whether to delete
the volume on instance termination.
no_device (str): Suppresses the device mapping.
Unsupported now.
disable_api_termination (boolean): If the value is true, you can't
terminate the instance using the Amazon EC2 console, CLI, or
API; otherwise, you can.
ebs_optimized (boolean): Whether the instance is optimized for EBS.
Unsupported now.
group_id (list of str): [EC2-VPC] Changes the security
groups of the instance. You must specify at least one security
group, even if it's just the default security group for the
VPC. You must specify the security group ID, not the security
group name.
Unsupported now.
instance_initiated_shutdown_behavior (str): Indicates whether an
instance stops or terminates when you initiate shutdown from
the instance.
Unsupported now.
instance_type (str): Changes the instance type to the specified
value. For more information, see Instance Types. If the
instance type is not valid, the error returned is
InvalidInstanceAttributeValue.
Unsupported now.
kernel (str): Changes the instance's kernel to the specified value.
Unsupported now.
ramdisk (str): Changes the instance's RAM disk.
Unsupported now.
sriov_net_support (str): SR-IOV mode for networking.
Unsupported now.
user_data (str): Changes the instance's user data.
Unsupported now.
Returns:
true if the request succeeds.
"""
@module_and_param_types(instance, 'i_id', 'str')
def reset_instance_attribute(self, context, instance_id, attribute):
"""Resets an attribute of an instance to its default value.
To reset the kernel or ramdisk, the instance must be in a stopped
state. To reset the SourceDestCheck, the instance can be either
running or stopped.
Args:
context (RequestContext): The request context.
instance_id (str): The ID of the instance.
attribute (str): The attribute to reset.
Returns:
true if the request succeeds.
"""
@module_and_param_types(key_pair, 'str255s', 'filter')
def describe_key_pairs(self, context, key_name=None, filter=None):
"""Describes one or more of your key pairs.

View File

@ -167,6 +167,8 @@ def run_instances(context, image_id, min_count, max_count,
'launch_index': launch_index}
if client_token:
instance['client_token'] = client_token
if disable_api_termination:
instance['disable_api_termination'] = disable_api_termination
instance = db_api.add_item(context, 'i', instance)
cleaner.addCleanup(db_api.delete_item, context, instance['id'])
@ -194,6 +196,12 @@ def terminate_instances(context, instance_id):
nova = clients.nova(context)
state_changes = []
for instance in instances:
if instance.get('disable_api_termination'):
message = _("The instance '%s' may not be terminated. Modify its "
"'disableApiTermination' instance attribute and try "
"again.") % instance['id']
raise exception.OperationNotPermitted(message=message)
for instance in instances:
try:
os_instance = nova.servers.get(instance['os_id'])
@ -502,8 +510,8 @@ def describe_instance_attribute(context, instance_id, attribute):
context, getattr(os_instance, 'security_groups', []))
def _format_attr_instance_type(result):
result['instanceType'] = {'value': _cloud_format_instance_type(
context, os_instance)}
result['instanceType'] = {
'value': _cloud_format_instance_type(context, os_instance)}
def _format_attr_kernel(result):
value = _cloud_format_kernel_id(context, os_instance)
@ -527,7 +535,8 @@ def describe_instance_attribute(context, instance_id, attribute):
result['userData'] = {'value': user_data}
def _format_attr_disable_api_termination(result):
result['disableApiTermination'] = {'value': False}
result['disableApiTermination'] = {
'value': instance.get('disable_api_termination', False)}
attribute_formatter = {
'blockDeviceMapping': _format_attr_block_device_mapping,
@ -542,14 +551,69 @@ def describe_instance_attribute(context, instance_id, attribute):
fn = attribute_formatter.get(attribute)
if fn is None:
# TODO(ft): clarify an exact AWS error
raise exception.InvalidAttribute(attr=attribute)
raise exception.InvalidParameterValue(value=attribute,
parameter='attribute',
reason='Unknown attribute.')
result = {'instance_id': instance_id}
fn(result)
return result
def modify_instance_attribute(context, instance_id, attribute=None,
value=None, source_dest_check=None,
block_device_mapping=None,
disable_api_termination=None,
ebs_optimized=None, group_id=None,
instance_initiated_shutdown_behavior=None,
instance_type=None, kernel=None,
ramdisk=None, sriov_net_support=None,
user_data=None):
# NOTE(andrey-mp): other parameters can be added in same way
if attribute is not None:
if attribute == 'userData':
if user_data is not None:
raise exception.InvalidParameterCombination()
elif attribute == 'disableApiTermination':
if disable_api_termination is not None:
raise exception.InvalidParameterCombination()
else:
raise exception.InvalidParameterValue(value=attribute,
parameter='attribute',
reason='Unknown attribute.')
if value is None:
raise exception.MissingParameter(param='value')
params_count = (
int(source_dest_check is not None) +
int(group_id is not None) + int(instance_type is not None) +
int(disable_api_termination is not None) + int(user_data is not None))
if (params_count > 1
or (attribute is not None and params_count == 1)
or (params_count == 0 and attribute is None)):
raise exception.InvalidParameterCombination()
if attribute == 'userData':
user_data = value
elif attribute == 'disableApiTermination':
disable_api_termination = value
if disable_api_termination is not None:
instance = ec2utils.get_db_item(context, instance_id)
instance['disable_api_termination'] = value
db_api.update_item(context, instance)
return True
raise exception.InvalidParameterCombination()
def reset_instance_attribute(context, instance_id, attribute):
raise exception.InvalidParameterValue(value=attribute,
parameter='attribute',
reason='Unknown attribute.')
def _format_reservation(context, reservation, formatted_instances, os_groups):
return {
'reservationId': reservation['id'],

View File

@ -315,3 +315,102 @@ class InstanceTest(base.EC2TestCase):
self.client.terminate_instances(InstanceIds=[instance_id])
self.cancelResourceCleanUp(res_clean)
self.get_instance_waiter().wait_delete(instance_id)
@testtools.skipUnless(CONF.aws.image_id, "image id is not defined")
def test_disable_api_termination_attribute(self):
instance_type = CONF.aws.instance_type
image_id = CONF.aws.image_id
data = self.client.run_instances(MinCount=1, MaxCount=1,
ImageId=image_id, InstanceType=instance_type,
Placement={'AvailabilityZone': CONF.aws.aws_zone},
DisableApiTermination=True)
instance_id = data['Instances'][0]['InstanceId']
res_clean = self.addResourceCleanUp(self.client.terminate_instances,
InstanceIds=[instance_id])
self.addResourceCleanUp(self.client.modify_instance_attribute,
InstanceId=instance_id,
DisableApiTermination={'Value': True})
self.assertEqual(1, len(data['Instances']))
self.get_instance_waiter().wait_available(instance_id,
final_set=('running'))
data = self.client.describe_instance_attribute(
InstanceId=instance_id, Attribute='disableApiTermination')
self.assertIn('DisableApiTermination', data)
self.assertIn('Value', data['DisableApiTermination'])
self.assertTrue(data['DisableApiTermination']['Value'])
data = self.client.modify_instance_attribute(InstanceId=instance_id,
Attribute='disableApiTermination', Value='False')
data = self.client.describe_instance_attribute(
InstanceId=instance_id, Attribute='disableApiTermination')
self.assertFalse(data['DisableApiTermination']['Value'])
data = self.client.modify_instance_attribute(InstanceId=instance_id,
Attribute='disableApiTermination', Value='True')
data = self.client.describe_instance_attribute(
InstanceId=instance_id, Attribute='disableApiTermination')
self.assertTrue(data['DisableApiTermination']['Value'])
self.assertRaises('OperationNotPermitted',
self.client.terminate_instances,
InstanceIds=[instance_id])
data = self.client.modify_instance_attribute(InstanceId=instance_id,
DisableApiTermination={'Value': False})
data = self.client.describe_instance_attribute(
InstanceId=instance_id, Attribute='disableApiTermination')
self.assertFalse(data['DisableApiTermination']['Value'])
self.client.terminate_instances(InstanceIds=[instance_id])
self.cancelResourceCleanUp(res_clean)
self.get_instance_waiter().wait_delete(instance_id)
@testtools.skipUnless(CONF.aws.image_id, "image id is not defined")
def test_instance_attributes_negative(self):
instance_type = CONF.aws.instance_type
image_id = CONF.aws.image_id
data = self.client.run_instances(MinCount=1, MaxCount=1,
ImageId=image_id, InstanceType=instance_type,
Placement={'AvailabilityZone': CONF.aws.aws_zone})
instance_id = data['Instances'][0]['InstanceId']
res_clean = self.addResourceCleanUp(self.client.terminate_instances,
InstanceIds=[instance_id])
self.assertEqual(1, len(data['Instances']))
self.get_instance_waiter().wait_available(instance_id,
final_set=('running'))
self.assertRaises('InvalidParameterValue',
self.client.describe_instance_attribute,
InstanceId=instance_id, Attribute='fake_attribute')
self.assertRaises('InvalidInstanceID.NotFound',
self.client.describe_instance_attribute,
InstanceId='i-0', Attribute='disableApiTermination')
self.assertRaises('InvalidParameterValue',
self.client.modify_instance_attribute,
InstanceId=instance_id, Attribute='fake_attribute')
self.assertRaises('MissingParameter',
self.client.modify_instance_attribute,
InstanceId=instance_id, Attribute='disableApiTermination')
self.assertRaises('InvalidParameterCombination',
self.client.modify_instance_attribute,
InstanceId=instance_id)
self.assertRaises('InvalidParameterCombination',
self.client.modify_instance_attribute,
InstanceId=instance_id, Attribute='disableApiTermination',
Value='True', DisableApiTermination={'Value': False})
self.assertRaises('InvalidParameterValue',
self.client.reset_instance_attribute,
InstanceId=instance_id, Attribute='fake_attribute')
self.assertRaises('InvalidParameterValue',
self.client.reset_instance_attribute,
InstanceId=instance_id, Attribute='disableApiTermination')
self.assertRaises('InvalidParameterValue',
self.client.reset_instance_attribute,
InstanceId='i-0', Attribute='disableApiTermination')
self.client.terminate_instances(InstanceIds=[instance_id])
self.cancelResourceCleanUp(res_clean)
self.get_instance_waiter().wait_delete(instance_id)