Support deletion of keypairs from AWS
Deletion of keypairs from OpenStack does not trigger a key pair deletion from AWS. Listen to the keypair deletion notifications and delete the key from AWS when one is received. Also pulling in some of the local changes. Change-Id: Iea466533a8a12c0acccf5b6bf08d99b4e7a1b622 Closes-Bug: #1716454
This commit is contained in:
parent
92ee8cde01
commit
16d4db46e8
|
@ -47,6 +47,7 @@ class EC2DriverTestCase(test.NoDBTestCase):
|
||||||
region_name=self.region_name,
|
region_name=self.region_name,
|
||||||
group='AWS')
|
group='AWS')
|
||||||
self.flags(api_servers=['http://localhost:9292'], group='glance')
|
self.flags(api_servers=['http://localhost:9292'], group='glance')
|
||||||
|
self.flags(rabbit_port='5672')
|
||||||
self.conn = EC2Driver(None, False)
|
self.conn = EC2Driver(None, False)
|
||||||
self.type_data = None
|
self.type_data = None
|
||||||
self.project_id = 'fake'
|
self.project_id = 'fake'
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
"""
|
||||||
|
Copyright 2016 Platform9 Systems Inc.
|
||||||
|
All Rights Reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
not use this file except in compliance with the License. You may obtain
|
||||||
|
a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
License for the specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from moto import mock_ec2_deprecated
|
||||||
|
from nova import test
|
||||||
|
from nova.virt.ec2.keypair import KeyPairNotifications
|
||||||
|
import boto
|
||||||
|
import mock
|
||||||
|
|
||||||
|
|
||||||
|
class KeyPairNotificationsTestCase(test.NoDBTestCase):
|
||||||
|
@mock_ec2_deprecated
|
||||||
|
def setUp(self):
|
||||||
|
super(KeyPairNotificationsTestCase, self).setUp()
|
||||||
|
fake_access_key = 'aws_access_key'
|
||||||
|
fake_secret_key = 'aws_secret_key'
|
||||||
|
region_name = 'us-west-1'
|
||||||
|
region = boto.ec2.get_region(region_name)
|
||||||
|
self.fake_aws_conn = boto.ec2.EC2Connection(
|
||||||
|
aws_access_key_id=fake_access_key,
|
||||||
|
aws_secret_access_key=fake_secret_key,
|
||||||
|
region=region)
|
||||||
|
self.flags(rabbit_port=5672)
|
||||||
|
self.conn = KeyPairNotifications(self.fake_aws_conn,
|
||||||
|
transport='memory')
|
||||||
|
|
||||||
|
def test_handle_notification_create_event(self):
|
||||||
|
body = {'event_type': 'keypair.create.start'}
|
||||||
|
with mock.patch.object(boto.ec2.EC2Connection, 'delete_key_pair') \
|
||||||
|
as mock_delete:
|
||||||
|
self.conn.handle_notification(body, None)
|
||||||
|
mock_delete.assert_not_called()
|
||||||
|
|
||||||
|
def test_handle_notifications_no_event_type(self):
|
||||||
|
body = {}
|
||||||
|
with mock.patch.object(boto.ec2.EC2Connection, 'delete_key_pair') \
|
||||||
|
as mock_delete:
|
||||||
|
self.conn.handle_notification(body, None)
|
||||||
|
mock_delete.assert_not_called()
|
||||||
|
|
||||||
|
@mock_ec2_deprecated
|
||||||
|
def test_handle_notifications_delete_key(self):
|
||||||
|
fake_key_name = 'fake_key'
|
||||||
|
fake_key_data = 'fake_key_data'
|
||||||
|
self.fake_aws_conn.import_key_pair(fake_key_name, fake_key_data)
|
||||||
|
body = {'event_type': 'keypair.delete.start',
|
||||||
|
'payload': {
|
||||||
|
'key_name': fake_key_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.conn.handle_notification(body, None)
|
||||||
|
aws_keypairs = self.fake_aws_conn.get_all_key_pairs()
|
||||||
|
self.assertEqual(len(aws_keypairs), 0)
|
||||||
|
|
||||||
|
@mock_ec2_deprecated
|
||||||
|
def test_handle_notifications_delete_key_with_multiple_keys_in_aws(self):
|
||||||
|
fake_key_name_1 = 'fake_key_1'
|
||||||
|
fake_key_data_1 = 'fake_key_data_1'
|
||||||
|
fake_key_name_2 = 'fake_key_2'
|
||||||
|
fake_key_data_2 = 'fake_key_data_2'
|
||||||
|
self.fake_aws_conn.import_key_pair(fake_key_name_1, fake_key_data_1)
|
||||||
|
self.fake_aws_conn.import_key_pair(fake_key_name_2, fake_key_data_2)
|
||||||
|
body = {'event_type': 'keypair.delete.start',
|
||||||
|
'payload': {
|
||||||
|
'key_name': fake_key_name_1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.conn.handle_notification(body, None)
|
||||||
|
aws_keypairs = self.fake_aws_conn.get_all_key_pairs()
|
||||||
|
self.assertEqual(len(aws_keypairs), 1)
|
||||||
|
self.assertEqual(aws_keypairs[0].name, fake_key_name_2)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"""
|
"""
|
||||||
Copyright (c) 2014 Thoughtworks.
|
Copyright (c) 2014 Thoughtworks.
|
||||||
Copyright (c) 2016 Platform9 Systems Inc.
|
Copyright (c) 2017 Platform9 Systems Inc.
|
||||||
All Rights reserved
|
All Rights reserved
|
||||||
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
not use this file except in compliance with the License. You may obtain
|
not use this file except in compliance with the License. You may obtain
|
||||||
|
@ -15,6 +15,7 @@ under the License.
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import datetime
|
import datetime
|
||||||
|
import eventlet
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
|
@ -34,12 +35,14 @@ from nova.i18n import _
|
||||||
from nova.image import glance
|
from nova.image import glance
|
||||||
from nova.virt import driver
|
from nova.virt import driver
|
||||||
from nova.virt.ec2.exception_handler import Ec2ExceptionHandler
|
from nova.virt.ec2.exception_handler import Ec2ExceptionHandler
|
||||||
|
from nova.virt.ec2.keypair import KeyPairNotifications
|
||||||
from nova.virt import hardware
|
from nova.virt import hardware
|
||||||
from nova.virt import virtapi
|
from nova.virt import virtapi
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_service import loopingcall
|
from oslo_service import loopingcall
|
||||||
|
|
||||||
|
eventlet.monkey_patch()
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
aws_group = cfg.OptGroup(name='AWS',
|
aws_group = cfg.OptGroup(name='AWS',
|
||||||
|
@ -63,11 +66,12 @@ aws_opts = [
|
||||||
# 1 TB Storage
|
# 1 TB Storage
|
||||||
cfg.IntOpt('max_disk_gb',
|
cfg.IntOpt('max_disk_gb',
|
||||||
default=1024,
|
default=1024,
|
||||||
help='Max storage in GB that can be used')
|
help='Max storage in GB that can be used'),
|
||||||
|
cfg.BoolOpt('enable_keypair_notifications', default=True,
|
||||||
|
help='Listen to keypair delete notifications and act on them')
|
||||||
]
|
]
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
# CONF.import_opt('my_ip', 'nova.netconf')
|
|
||||||
|
|
||||||
CONF.register_group(aws_group)
|
CONF.register_group(aws_group)
|
||||||
CONF.register_opts(aws_opts, group=aws_group)
|
CONF.register_opts(aws_opts, group=aws_group)
|
||||||
|
@ -121,7 +125,8 @@ EC2_FLAVOR_MAP = {
|
||||||
't2.micro': {'memory_mb': 1024.0, 'vcpus': 1},
|
't2.micro': {'memory_mb': 1024.0, 'vcpus': 1},
|
||||||
't2.nano': {'memory_mb': 512.0, 'vcpus': 1},
|
't2.nano': {'memory_mb': 512.0, 'vcpus': 1},
|
||||||
't2.small': {'memory_mb': 2048.0, 'vcpus': 1},
|
't2.small': {'memory_mb': 2048.0, 'vcpus': 1},
|
||||||
'x1.32xlarge': {'memory_mb': 1998848.0, 'vcpus': 128}
|
'x1.32xlarge': {'memory_mb': 1998848.0, 'vcpus': 128},
|
||||||
|
't1.micro': {'memory_mb': 613.0, 'vcpus': 1},
|
||||||
}
|
}
|
||||||
_EC2_NODES = None
|
_EC2_NODES = None
|
||||||
|
|
||||||
|
@ -190,6 +195,9 @@ class EC2Driver(driver.ComputeDriver):
|
||||||
aws_region, aws_access_key_id=CONF.AWS.access_key,
|
aws_region, aws_access_key_id=CONF.AWS.access_key,
|
||||||
aws_secret_access_key=CONF.AWS.secret_key)
|
aws_secret_access_key=CONF.AWS.secret_key)
|
||||||
|
|
||||||
|
# Allow keypair deletion to be controlled by conf
|
||||||
|
if CONF.AWS.enable_keypair_notifications:
|
||||||
|
eventlet.spawn(KeyPairNotifications(self.ec2_conn).run)
|
||||||
LOG.info("EC2 driver init with %s region" % aws_region)
|
LOG.info("EC2 driver init with %s region" % aws_region)
|
||||||
if _EC2_NODES is None:
|
if _EC2_NODES is None:
|
||||||
set_nodes([CONF.host])
|
set_nodes([CONF.host])
|
||||||
|
@ -257,7 +265,7 @@ class EC2Driver(driver.ComputeDriver):
|
||||||
"""
|
"""
|
||||||
image_api = glance.get_default_image_service()
|
image_api = glance.get_default_image_service()
|
||||||
image_meta = image_api._client.call(context, 2, 'get',
|
image_meta = image_api._client.call(context, 2, 'get',
|
||||||
image_lacking_meta['id'])
|
image_lacking_meta.id)
|
||||||
LOG.info("Calling _get_image_ami_id_from_meta Meta: %s" % image_meta)
|
LOG.info("Calling _get_image_ami_id_from_meta Meta: %s" % image_meta)
|
||||||
try:
|
try:
|
||||||
return image_meta['aws_image_id']
|
return image_meta['aws_image_id']
|
||||||
|
@ -372,6 +380,8 @@ class EC2Driver(driver.ComputeDriver):
|
||||||
instance['metadata'].update({'ec2_id': ec2_id})
|
instance['metadata'].update({'ec2_id': ec2_id})
|
||||||
ec2_instance_obj.add_tag("Name", instance['display_name'])
|
ec2_instance_obj.add_tag("Name", instance['display_name'])
|
||||||
ec2_instance_obj.add_tag("openstack_id", instance['uuid'])
|
ec2_instance_obj.add_tag("openstack_id", instance['uuid'])
|
||||||
|
ec2_instance_obj.add_tag("openstack_project_id", context.project_id)
|
||||||
|
ec2_instance_obj.add_tag("openstack_user_id", context.user_id)
|
||||||
self._uuid_to_ec2_instance[instance.uuid] = ec2_instance_obj
|
self._uuid_to_ec2_instance[instance.uuid] = ec2_instance_obj
|
||||||
|
|
||||||
# Fetch Public IP of the instance if it has one
|
# Fetch Public IP of the instance if it has one
|
||||||
|
@ -684,13 +694,13 @@ class EC2Driver(driver.ComputeDriver):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def attach_interface(self, instance, image_meta, vif):
|
def attach_interface(self, instance, image_meta, vif):
|
||||||
LOG.debug("******* ATTTACH INTERFACE *******")
|
LOG.debug("AWS: Attaching interface", instance=instance)
|
||||||
if vif['id'] in self._interfaces:
|
if vif['id'] in self._interfaces:
|
||||||
raise exception.InterfaceAttachFailed('duplicate')
|
raise exception.InterfaceAttachFailed('duplicate')
|
||||||
self._interfaces[vif['id']] = vif
|
self._interfaces[vif['id']] = vif
|
||||||
|
|
||||||
def detach_interface(self, instance, vif):
|
def detach_interface(self, instance, vif):
|
||||||
LOG.debug("******* DETACH INTERFACE *******")
|
LOG.debug("AWS: Detaching interface", instance=instance)
|
||||||
try:
|
try:
|
||||||
del self._interfaces[vif['id']]
|
del self._interfaces[vif['id']]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
"""
|
||||||
|
Copyright (c) 2014 Thoughtworks.
|
||||||
|
Copyright (c) 2017 Platform9 Systems Inc.
|
||||||
|
All Rights reserved
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
not use this file except in compliance with the License. You may obtain
|
||||||
|
a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied. See the
|
||||||
|
License for the specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import eventlet
|
||||||
|
eventlet.monkey_patch()
|
||||||
|
from kombu import Connection
|
||||||
|
from kombu import Exchange
|
||||||
|
from kombu import Queue
|
||||||
|
from kombu.mixins import ConsumerMixin
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
rabbit_opts = [
|
||||||
|
cfg.StrOpt('rabbit_userid'),
|
||||||
|
cfg.StrOpt('rabbit_password'),
|
||||||
|
cfg.StrOpt('rabbit_host'),
|
||||||
|
cfg.StrOpt('rabbit_port'),
|
||||||
|
]
|
||||||
|
|
||||||
|
CONF.register_opts(rabbit_opts)
|
||||||
|
|
||||||
|
|
||||||
|
class KeyPairNotifications(ConsumerMixin):
|
||||||
|
nova_exchange = 'nova'
|
||||||
|
routing_key = 'notifications.info'
|
||||||
|
queue_name = 'notifications.omni.keypair'
|
||||||
|
events_of_interest = ['keypair.delete.start', 'keypair.delete.end']
|
||||||
|
|
||||||
|
def __init__(self, aws_connection, transport='amqp'):
|
||||||
|
self.ec2_conn = aws_connection
|
||||||
|
self.broker_uri = \
|
||||||
|
"{transport}://{username}:{password}@{rabbit_host}:{rabbit_port}"\
|
||||||
|
.format(transport=transport,
|
||||||
|
username=CONF.rabbit_userid,
|
||||||
|
password=CONF.rabbit_password,
|
||||||
|
rabbit_host=CONF.rabbit_host,
|
||||||
|
rabbit_port=CONF.rabbit_port)
|
||||||
|
self.connection = Connection(self.broker_uri)
|
||||||
|
|
||||||
|
def get_consumers(self, consumer, channel):
|
||||||
|
exchange = Exchange(self.nova_exchange, type="topic", durable=False)
|
||||||
|
queue = Queue(self.queue_name, exchange, routing_key=self.routing_key,
|
||||||
|
durable=False, auto_delete=True, no_ack=True)
|
||||||
|
return [consumer(queue, callbacks=[self.handle_notification])]
|
||||||
|
|
||||||
|
def handle_notification(self, body, message):
|
||||||
|
if 'event_type' in body and body['event_type'] in \
|
||||||
|
self.events_of_interest:
|
||||||
|
LOG.debug('Body: %r' % body)
|
||||||
|
key_name = body['payload']['key_name']
|
||||||
|
try:
|
||||||
|
LOG.info('Deleting %s keypair', key_name)
|
||||||
|
self.ec2_conn.delete_key_pair(key_name)
|
||||||
|
except:
|
||||||
|
LOG.exception('Could not delete %s', key_name)
|
|
@ -25,6 +25,7 @@ DIRECTORY="$WORKSPACE/openstack"
|
||||||
GCE_TEST="test_gce"
|
GCE_TEST="test_gce"
|
||||||
AWS_TEST="test_ec2"
|
AWS_TEST="test_ec2"
|
||||||
AWS_NOVA_TEST="test_ec2.EC2DriverTestCase"
|
AWS_NOVA_TEST="test_ec2.EC2DriverTestCase"
|
||||||
|
AWS_KEYPAIR_TEST="test_keypair.KeyPairNotificationsTestCase"
|
||||||
declare -A results
|
declare -A results
|
||||||
declare -i fail
|
declare -i fail
|
||||||
declare -i pass
|
declare -i pass
|
||||||
|
@ -87,7 +88,7 @@ copy_neutron_files
|
||||||
|
|
||||||
echo "============Running tests============"
|
echo "============Running tests============"
|
||||||
run_tests cinder "$GCE_TEST|$AWS_TEST" &
|
run_tests cinder "$GCE_TEST|$AWS_TEST" &
|
||||||
run_tests nova "$GCE_TEST|$AWS_NOVA_TEST" &
|
run_tests nova "$GCE_TEST|$AWS_NOVA_TEST|$AWS_KEYPAIR_TEST" &
|
||||||
run_tests glance_store "$GCE_TEST" &
|
run_tests glance_store "$GCE_TEST" &
|
||||||
run_tests neutron "$GCE_TEST|$AWS_TEST" &
|
run_tests neutron "$GCE_TEST|$AWS_TEST" &
|
||||||
wait
|
wait
|
||||||
|
|
Loading…
Reference in New Issue