Merge pull request #207 from DreadPirateShawn/NetworkInterfacesAttachDetach
Network Interfaces: Added attach/detach support.
This commit is contained in:
commit
579a6fc398
@ -80,6 +80,14 @@ class InvalidNetworkInterfaceIdError(EC2ClientError):
|
|||||||
.format(eni_id))
|
.format(eni_id))
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidNetworkAttachmentIdError(EC2ClientError):
|
||||||
|
def __init__(self, attachment_id):
|
||||||
|
super(InvalidNetworkAttachmentIdError, self).__init__(
|
||||||
|
"InvalidAttachmentID.NotFound",
|
||||||
|
"The network interface attachment ID '{0}' does not exist"
|
||||||
|
.format(attachment_id))
|
||||||
|
|
||||||
|
|
||||||
class InvalidSecurityGroupDuplicateError(EC2ClientError):
|
class InvalidSecurityGroupDuplicateError(EC2ClientError):
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
super(InvalidSecurityGroupDuplicateError, self).__init__(
|
super(InvalidSecurityGroupDuplicateError, self).__init__(
|
||||||
|
@ -27,6 +27,7 @@ from .exceptions import (
|
|||||||
InvalidVPCIdError,
|
InvalidVPCIdError,
|
||||||
InvalidSubnetIdError,
|
InvalidSubnetIdError,
|
||||||
InvalidNetworkInterfaceIdError,
|
InvalidNetworkInterfaceIdError,
|
||||||
|
InvalidNetworkAttachmentIdError,
|
||||||
InvalidSecurityGroupDuplicateError,
|
InvalidSecurityGroupDuplicateError,
|
||||||
InvalidSecurityGroupNotFoundError,
|
InvalidSecurityGroupNotFoundError,
|
||||||
InvalidPermissionNotFoundError,
|
InvalidPermissionNotFoundError,
|
||||||
@ -91,13 +92,17 @@ class NetworkInterface(object):
|
|||||||
self.private_ip_address = private_ip_address
|
self.private_ip_address = private_ip_address
|
||||||
self.subnet = subnet
|
self.subnet = subnet
|
||||||
self.instance = None
|
self.instance = None
|
||||||
|
self.attachment_id = None
|
||||||
|
|
||||||
self.public_ip = None
|
self.public_ip = None
|
||||||
self.public_ip_auto_assign = public_ip_auto_assign
|
self.public_ip_auto_assign = public_ip_auto_assign
|
||||||
self.start()
|
self.start()
|
||||||
|
|
||||||
self.attachments = []
|
self.attachments = []
|
||||||
self.group_set = []
|
|
||||||
|
# Local set to the ENI. When attached to an instance, @property group_set
|
||||||
|
# returns groups for both self and the attached instance.
|
||||||
|
self._group_set = []
|
||||||
|
|
||||||
group = None
|
group = None
|
||||||
if group_ids:
|
if group_ids:
|
||||||
@ -108,7 +113,7 @@ class NetworkInterface(object):
|
|||||||
group = SecurityGroup(group_id, group_id, group_id, vpc_id=subnet.vpc_id)
|
group = SecurityGroup(group_id, group_id, group_id, vpc_id=subnet.vpc_id)
|
||||||
ec2_backend.groups[subnet.vpc_id][group_id] = group
|
ec2_backend.groups[subnet.vpc_id][group_id] = group
|
||||||
if group:
|
if group:
|
||||||
self.group_set.append(group)
|
self._group_set.append(group)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json):
|
def create_from_cloudformation_json(cls, resource_name, cloudformation_json):
|
||||||
@ -139,11 +144,12 @@ class NetworkInterface(object):
|
|||||||
if self.public_ip_auto_assign:
|
if self.public_ip_auto_assign:
|
||||||
self.public_ip = random_public_ip()
|
self.public_ip = random_public_ip()
|
||||||
|
|
||||||
def attach(self, instance_id, device_index):
|
@property
|
||||||
attachment = {'attachmentId': random_eni_attach_id(),
|
def group_set(self):
|
||||||
'instanceId': instance_id,
|
if self.instance and self.instance.security_groups:
|
||||||
'deviceIndex': device_index}
|
return set(self._group_set) | set(self.instance.security_groups)
|
||||||
self.attachments.append(attachment)
|
else:
|
||||||
|
return self._group_set
|
||||||
|
|
||||||
|
|
||||||
class NetworkInterfaceBackend(object):
|
class NetworkInterfaceBackend(object):
|
||||||
@ -189,65 +195,27 @@ class NetworkInterfaceBackend(object):
|
|||||||
ec2_backend.raise_not_implemented_error("The filter '{0}' for DescribeNetworkInterfaces".format(_filter))
|
ec2_backend.raise_not_implemented_error("The filter '{0}' for DescribeNetworkInterfaces".format(_filter))
|
||||||
return enis
|
return enis
|
||||||
|
|
||||||
|
def attach_network_interface(self, eni_id, instance_id, device_index):
|
||||||
|
eni = self.get_network_interface(eni_id)
|
||||||
|
instance = self.get_instance(instance_id)
|
||||||
|
return instance.attach_eni(eni, device_index)
|
||||||
|
|
||||||
|
def detach_network_interface(self, attachment_id):
|
||||||
|
found_eni = None
|
||||||
|
|
||||||
|
for eni in self.enis.values():
|
||||||
|
if eni.attachment_id == attachment_id:
|
||||||
|
found_eni = eni
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise InvalidNetworkAttachmentIdError(attachment_id)
|
||||||
|
|
||||||
|
found_eni.instance.detach_eni(found_eni)
|
||||||
|
|
||||||
def modify_network_interface_attribute(self, eni_id, group_id):
|
def modify_network_interface_attribute(self, eni_id, group_id):
|
||||||
eni = self.get_network_interface(eni_id)
|
eni = self.get_network_interface(eni_id)
|
||||||
group = self.get_security_group_from_id(group_id)
|
group = self.get_security_group_from_id(group_id)
|
||||||
eni.group_set = [group]
|
eni._group_set = [group]
|
||||||
|
|
||||||
def prep_nics_for_instance(self, instance, nic_spec, subnet_id=None, private_ip=None, associate_public_ip=None):
|
|
||||||
nics = {}
|
|
||||||
|
|
||||||
# Primary NIC defaults
|
|
||||||
primary_nic = {'SubnetId': subnet_id,
|
|
||||||
'PrivateIpAddress': private_ip,
|
|
||||||
'AssociatePublicIpAddress': associate_public_ip}
|
|
||||||
primary_nic = dict((k,v) for k, v in primary_nic.items() if v)
|
|
||||||
|
|
||||||
# If empty NIC spec but primary NIC values provided, create NIC from them.
|
|
||||||
if primary_nic and not nic_spec:
|
|
||||||
nic_spec[0] = primary_nic
|
|
||||||
nic_spec[0]['DeviceIndex'] = 0
|
|
||||||
|
|
||||||
# Flesh out data structures and associations
|
|
||||||
for nic in nic_spec.values():
|
|
||||||
use_eni = None
|
|
||||||
security_group_ids = []
|
|
||||||
|
|
||||||
device_index = int(nic.get('DeviceIndex'))
|
|
||||||
|
|
||||||
nic_id = nic.get('NetworkInterfaceId', None)
|
|
||||||
if nic_id:
|
|
||||||
# If existing NIC found, use it.
|
|
||||||
use_nic = ec2_backend.get_network_interface(nic_id)
|
|
||||||
use_nic.device_index = device_index
|
|
||||||
use_nic.public_ip_auto_assign = False
|
|
||||||
|
|
||||||
else:
|
|
||||||
# If primary NIC values provided, use them for the primary NIC.
|
|
||||||
if device_index == 0 and primary_nic:
|
|
||||||
nic.update(primary_nic)
|
|
||||||
|
|
||||||
subnet = ec2_backend.get_subnet(nic['SubnetId'])
|
|
||||||
|
|
||||||
group_id = nic.get('SecurityGroupId',None)
|
|
||||||
group_ids = [group_id] if group_id else []
|
|
||||||
|
|
||||||
use_nic = ec2_backend.create_network_interface(subnet,
|
|
||||||
nic.get('PrivateIpAddress',None),
|
|
||||||
device_index=device_index,
|
|
||||||
public_ip_auto_assign=nic.get('AssociatePublicIpAddress',False),
|
|
||||||
group_ids=group_ids)
|
|
||||||
|
|
||||||
use_nic.instance = instance # This is used upon associate/disassociate public IP.
|
|
||||||
|
|
||||||
if use_nic.instance.security_groups:
|
|
||||||
use_nic.group_set.extend(use_nic.instance.security_groups)
|
|
||||||
|
|
||||||
use_nic.attach(instance.id, device_index)
|
|
||||||
|
|
||||||
nics[device_index] = use_nic
|
|
||||||
|
|
||||||
return nics
|
|
||||||
|
|
||||||
|
|
||||||
class Instance(BotoInstance, TaggedEC2Instance):
|
class Instance(BotoInstance, TaggedEC2Instance):
|
||||||
@ -281,8 +249,7 @@ class Instance(BotoInstance, TaggedEC2Instance):
|
|||||||
# string will have a "u" prefix -- need to get rid of it
|
# string will have a "u" prefix -- need to get rid of it
|
||||||
self.user_data[0] = self.user_data[0].encode('utf-8')
|
self.user_data[0] = self.user_data[0].encode('utf-8')
|
||||||
|
|
||||||
self.nics = ec2_backend.prep_nics_for_instance(self,
|
self.prep_nics(kwargs.get("nics", {}),
|
||||||
kwargs.get("nics", {}),
|
|
||||||
subnet_id=kwargs.get("subnet_id",None),
|
subnet_id=kwargs.get("subnet_id",None),
|
||||||
private_ip=kwargs.get("private_ip",None),
|
private_ip=kwargs.get("private_ip",None),
|
||||||
associate_public_ip=kwargs.get("associate_public_ip",None))
|
associate_public_ip=kwargs.get("associate_public_ip",None))
|
||||||
@ -349,6 +316,68 @@ class Instance(BotoInstance, TaggedEC2Instance):
|
|||||||
else:
|
else:
|
||||||
return self.security_groups
|
return self.security_groups
|
||||||
|
|
||||||
|
def prep_nics(self, nic_spec, subnet_id=None, private_ip=None, associate_public_ip=None):
|
||||||
|
self.nics = {}
|
||||||
|
|
||||||
|
# Primary NIC defaults
|
||||||
|
primary_nic = {'SubnetId': subnet_id,
|
||||||
|
'PrivateIpAddress': private_ip,
|
||||||
|
'AssociatePublicIpAddress': associate_public_ip}
|
||||||
|
primary_nic = dict((k,v) for k, v in primary_nic.items() if v)
|
||||||
|
|
||||||
|
# If empty NIC spec but primary NIC values provided, create NIC from them.
|
||||||
|
if primary_nic and not nic_spec:
|
||||||
|
nic_spec[0] = primary_nic
|
||||||
|
nic_spec[0]['DeviceIndex'] = 0
|
||||||
|
|
||||||
|
# Flesh out data structures and associations
|
||||||
|
for nic in nic_spec.values():
|
||||||
|
use_eni = None
|
||||||
|
security_group_ids = []
|
||||||
|
|
||||||
|
device_index = int(nic.get('DeviceIndex'))
|
||||||
|
|
||||||
|
nic_id = nic.get('NetworkInterfaceId', None)
|
||||||
|
if nic_id:
|
||||||
|
# If existing NIC found, use it.
|
||||||
|
use_nic = ec2_backend.get_network_interface(nic_id)
|
||||||
|
use_nic.device_index = device_index
|
||||||
|
use_nic.public_ip_auto_assign = False
|
||||||
|
|
||||||
|
else:
|
||||||
|
# If primary NIC values provided, use them for the primary NIC.
|
||||||
|
if device_index == 0 and primary_nic:
|
||||||
|
nic.update(primary_nic)
|
||||||
|
|
||||||
|
subnet = ec2_backend.get_subnet(nic['SubnetId'])
|
||||||
|
|
||||||
|
group_id = nic.get('SecurityGroupId',None)
|
||||||
|
group_ids = [group_id] if group_id else []
|
||||||
|
|
||||||
|
use_nic = ec2_backend.create_network_interface(subnet,
|
||||||
|
nic.get('PrivateIpAddress',None),
|
||||||
|
device_index=device_index,
|
||||||
|
public_ip_auto_assign=nic.get('AssociatePublicIpAddress',False),
|
||||||
|
group_ids=group_ids)
|
||||||
|
|
||||||
|
self.attach_eni(use_nic, device_index)
|
||||||
|
|
||||||
|
def attach_eni(self, eni, device_index):
|
||||||
|
device_index = int(device_index)
|
||||||
|
self.nics[device_index] = eni
|
||||||
|
|
||||||
|
eni.instance = self # This is used upon associate/disassociate public IP.
|
||||||
|
eni.attachment_id = random_eni_attach_id()
|
||||||
|
eni.device_index = device_index
|
||||||
|
|
||||||
|
return eni.attachment_id
|
||||||
|
|
||||||
|
def detach_eni(self, eni):
|
||||||
|
self.nics.pop(eni.device_index,None)
|
||||||
|
eni.instance = None
|
||||||
|
eni.attachment_id = None
|
||||||
|
eni.device_index = None
|
||||||
|
|
||||||
|
|
||||||
class InstanceBackend(object):
|
class InstanceBackend(object):
|
||||||
|
|
||||||
|
@ -7,9 +7,6 @@ from moto.ec2.utils import sequence_from_querystring, filters_from_querystring
|
|||||||
|
|
||||||
|
|
||||||
class ElasticNetworkInterfaces(BaseResponse):
|
class ElasticNetworkInterfaces(BaseResponse):
|
||||||
def attach_network_interface(self):
|
|
||||||
raise NotImplementedError('ElasticNetworkInterfaces(AmazonVPC).attach_network_interface is not yet implemented')
|
|
||||||
|
|
||||||
def create_network_interface(self):
|
def create_network_interface(self):
|
||||||
subnet_id = self.querystring.get('SubnetId')[0]
|
subnet_id = self.querystring.get('SubnetId')[0]
|
||||||
private_ip_address = self.querystring.get('PrivateIpAddress', [None])[0]
|
private_ip_address = self.querystring.get('PrivateIpAddress', [None])[0]
|
||||||
@ -35,8 +32,19 @@ class ElasticNetworkInterfaces(BaseResponse):
|
|||||||
template = Template(DESCRIBE_NETWORK_INTERFACES_RESPONSE)
|
template = Template(DESCRIBE_NETWORK_INTERFACES_RESPONSE)
|
||||||
return template.render(enis=enis)
|
return template.render(enis=enis)
|
||||||
|
|
||||||
|
def attach_network_interface(self):
|
||||||
|
eni_id = self.querystring.get('NetworkInterfaceId')[0]
|
||||||
|
instance_id = self.querystring.get('InstanceId')[0]
|
||||||
|
device_index = self.querystring.get('DeviceIndex')[0]
|
||||||
|
attachment_id = ec2_backend.attach_network_interface(eni_id, instance_id, device_index)
|
||||||
|
template = Template(ATTACH_NETWORK_INTERFACE_RESPONSE)
|
||||||
|
return template.render(attachment_id=attachment_id)
|
||||||
|
|
||||||
def detach_network_interface(self):
|
def detach_network_interface(self):
|
||||||
raise NotImplementedError('ElasticNetworkInterfaces(AmazonVPC).detach_network_interface is not yet implemented')
|
attachment_id = self.querystring.get('AttachmentId')[0]
|
||||||
|
ec2_backend.detach_network_interface(attachment_id)
|
||||||
|
template = Template(DETACH_NETWORK_INTERFACE_RESPONSE)
|
||||||
|
return template.render()
|
||||||
|
|
||||||
def modify_network_interface_attribute(self):
|
def modify_network_interface_attribute(self):
|
||||||
#Currently supports modifying one and only one security group
|
#Currently supports modifying one and only one security group
|
||||||
@ -115,17 +123,17 @@ DESCRIBE_NETWORK_INTERFACES_RESPONSE = """<DescribeNetworkInterfacesResponse xml
|
|||||||
</item>
|
</item>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</groupSet>
|
</groupSet>
|
||||||
{% for attachment in eni.attachments %}
|
{% if eni.instance %}
|
||||||
<attachment>
|
<attachment>
|
||||||
<attachmentId>{{ attachment['attachmentId'] }}</attachmentId>
|
<attachmentId>{{ eni.attachment_id }}</attachmentId>
|
||||||
<instanceId>{{ attachment['instanceId'] }}</instanceId>
|
<instanceId>{{ eni.instance.id }}</instanceId>
|
||||||
<instanceOwnerId>190610284047</instanceOwnerId>
|
<instanceOwnerId>190610284047</instanceOwnerId>
|
||||||
<deviceIndex>{{ attachment['deviceIndex'] }}</deviceIndex>
|
<deviceIndex>{{ eni.device_index }}</deviceIndex>
|
||||||
<status>attached</status>
|
<status>attached</status>
|
||||||
<attachTime>2013-10-04T17:38:53.000Z</attachTime>
|
<attachTime>2013-10-04T17:38:53.000Z</attachTime>
|
||||||
<deleteOnTermination>true</deleteOnTermination>
|
<deleteOnTermination>true</deleteOnTermination>
|
||||||
</attachment>
|
</attachment>
|
||||||
{% endfor %}
|
{% endif %}
|
||||||
<association>
|
<association>
|
||||||
<publicIp>{{ eni.public_ip }}</publicIp>
|
<publicIp>{{ eni.public_ip }}</publicIp>
|
||||||
<publicDnsName>ec2-54-200-86-47.us-west-2.compute.amazonaws.com</publicDnsName>
|
<publicDnsName>ec2-54-200-86-47.us-west-2.compute.amazonaws.com</publicDnsName>
|
||||||
@ -155,6 +163,16 @@ DESCRIBE_NETWORK_INTERFACES_RESPONSE = """<DescribeNetworkInterfacesResponse xml
|
|||||||
</networkInterfaceSet>
|
</networkInterfaceSet>
|
||||||
</DescribeNetworkInterfacesResponse>"""
|
</DescribeNetworkInterfacesResponse>"""
|
||||||
|
|
||||||
|
ATTACH_NETWORK_INTERFACE_RESPONSE = """<AttachNetworkInterfaceResponse xmlns="http://ec2.amazonaws.com/doc/2014-06-15/">
|
||||||
|
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
|
||||||
|
<attachmentId>{{ attachment_id }}</attachmentId>
|
||||||
|
</AttachNetworkInterfaceResponse>"""
|
||||||
|
|
||||||
|
DETACH_NETWORK_INTERFACE_RESPONSE = """<DetachNetworkInterfaceResponse xmlns="http://ec2.amazonaws.com/doc/2014-06-15/">
|
||||||
|
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
|
||||||
|
<return>true</return>
|
||||||
|
</DetachNetworkInterfaceResponse>"""
|
||||||
|
|
||||||
MODIFY_NETWORK_INTERFACE_ATTRIBUTE_RESPONSE = """<ModifyNetworkInterfaceAttributeResponse xmlns="http://ec2.amazonaws.com/doc/2012-12-01/">
|
MODIFY_NETWORK_INTERFACE_ATTRIBUTE_RESPONSE = """<ModifyNetworkInterfaceAttributeResponse xmlns="http://ec2.amazonaws.com/doc/2012-12-01/">
|
||||||
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
|
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
|
||||||
<return>true</return>
|
<return>true</return>
|
||||||
|
@ -244,7 +244,7 @@ EC2_RUN_INSTANCES = """<RunInstancesResponse xmlns="http://ec2.amazonaws.com/doc
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</groupSet>
|
</groupSet>
|
||||||
<attachment>
|
<attachment>
|
||||||
<attachmentId>eni-attach-1a2b3c4d</attachmentId>
|
<attachmentId>{{ nic.attachment_id }}</attachmentId>
|
||||||
<deviceIndex>{{ nic.device_index }}</deviceIndex>
|
<deviceIndex>{{ nic.device_index }}</deviceIndex>
|
||||||
<status>attached</status>
|
<status>attached</status>
|
||||||
<attachTime>YYYY-MM-DDTHH:MM:SS+0000</attachTime>
|
<attachTime>YYYY-MM-DDTHH:MM:SS+0000</attachTime>
|
||||||
@ -385,7 +385,7 @@ EC2_DESCRIBE_INSTANCES = """<DescribeInstancesResponse xmlns='http://ec2.amazona
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</groupSet>
|
</groupSet>
|
||||||
<attachment>
|
<attachment>
|
||||||
<attachmentId>eni-attach-1a2b3c4d</attachmentId>
|
<attachmentId>{{ nic.attachment_id }}</attachmentId>
|
||||||
<deviceIndex>{{ nic.device_index }}</deviceIndex>
|
<deviceIndex>{{ nic.device_index }}</deviceIndex>
|
||||||
<status>attached</status>
|
<status>attached</status>
|
||||||
<attachTime>YYYY-MM-DDTHH:MM:SS+0000</attachTime>
|
<attachTime>YYYY-MM-DDTHH:MM:SS+0000</attachTime>
|
||||||
|
@ -11,6 +11,7 @@ from boto.exception import EC2ResponseError
|
|||||||
import sure # noqa
|
import sure # noqa
|
||||||
|
|
||||||
from moto import mock_ec2
|
from moto import mock_ec2
|
||||||
|
from tests.helpers import requires_boto_gte
|
||||||
|
|
||||||
|
|
||||||
################ Test Readme ###############
|
################ Test Readme ###############
|
||||||
@ -360,6 +361,61 @@ def test_run_instance_with_nic_preexisting():
|
|||||||
instance_eni.private_ip_addresses[0].private_ip_address.should.equal(private_ip)
|
instance_eni.private_ip_addresses[0].private_ip_address.should.equal(private_ip)
|
||||||
|
|
||||||
|
|
||||||
|
@requires_boto_gte("2.32.0")
|
||||||
|
@mock_ec2
|
||||||
|
def test_instance_with_nic_attach_detach():
|
||||||
|
conn = boto.connect_vpc('the_key', 'the_secret')
|
||||||
|
vpc = conn.create_vpc("10.0.0.0/16")
|
||||||
|
subnet = conn.create_subnet(vpc.id, "10.0.0.0/18")
|
||||||
|
|
||||||
|
security_group1 = conn.create_security_group('test security group #1', 'this is a test security group')
|
||||||
|
security_group2 = conn.create_security_group('test security group #2', 'this is a test security group')
|
||||||
|
|
||||||
|
reservation = conn.run_instances('ami-1234abcd', security_group_ids=[security_group1.id])
|
||||||
|
instance = reservation.instances[0]
|
||||||
|
|
||||||
|
eni = conn.create_network_interface(subnet.id, groups=[security_group2.id])
|
||||||
|
|
||||||
|
# Check initial instance and ENI data
|
||||||
|
instance.interfaces.should.have.length_of(0)
|
||||||
|
|
||||||
|
eni.groups.should.have.length_of(1)
|
||||||
|
set([group.id for group in eni.groups]).should.equal(set([security_group2.id]))
|
||||||
|
|
||||||
|
# Attach
|
||||||
|
conn.attach_network_interface(eni.id, instance.id, device_index=0)
|
||||||
|
|
||||||
|
# Check attached instance and ENI data
|
||||||
|
instance.update()
|
||||||
|
instance.interfaces.should.have.length_of(1)
|
||||||
|
instance_eni = instance.interfaces[0]
|
||||||
|
instance_eni.id.should.equal(eni.id)
|
||||||
|
instance_eni.groups.should.have.length_of(2)
|
||||||
|
set([group.id for group in instance_eni.groups]).should.equal(set([security_group1.id,security_group2.id]))
|
||||||
|
|
||||||
|
eni = conn.get_all_network_interfaces(eni.id)[0]
|
||||||
|
eni.groups.should.have.length_of(2)
|
||||||
|
set([group.id for group in eni.groups]).should.equal(set([security_group1.id,security_group2.id]))
|
||||||
|
|
||||||
|
# Detach
|
||||||
|
conn.detach_network_interface(instance_eni.attachment.id)
|
||||||
|
|
||||||
|
# Check detached instance and ENI data
|
||||||
|
instance.update()
|
||||||
|
instance.interfaces.should.have.length_of(0)
|
||||||
|
|
||||||
|
eni = conn.get_all_network_interfaces(eni.id)[0]
|
||||||
|
eni.groups.should.have.length_of(1)
|
||||||
|
set([group.id for group in eni.groups]).should.equal(set([security_group2.id]))
|
||||||
|
|
||||||
|
# Detach with invalid attachment ID
|
||||||
|
with assert_raises(EC2ResponseError) as cm:
|
||||||
|
conn.detach_network_interface('eni-attach-1234abcd')
|
||||||
|
cm.exception.code.should.equal('InvalidAttachmentID.NotFound')
|
||||||
|
cm.exception.status.should.equal(400)
|
||||||
|
cm.exception.request_id.should_not.be.none
|
||||||
|
|
||||||
|
|
||||||
@mock_ec2
|
@mock_ec2
|
||||||
def test_run_instance_with_keypair():
|
def test_run_instance_with_keypair():
|
||||||
conn = boto.connect_ec2('the_key', 'the_secret')
|
conn = boto.connect_ec2('the_key', 'the_secret')
|
||||||
|
Loading…
Reference in New Issue
Block a user