implement modify/reset instance attribute. part 1.
implement only one attribute - disableApiTermination. Change-Id: Ida88bcb047bcadd6cfe18c76f3af4777a431dad2
This commit is contained in:
parent
21e63c3309
commit
95c8930bfb
|
@ -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:
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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'],
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue