diff --git a/releasenotes/notes/support-nova-keypair-a2cdb2da5c1511e9.yaml b/releasenotes/notes/support-nova-keypair-a2cdb2da5c1511e9.yaml new file mode 100644 index 0000000000..7e343fbd78 --- /dev/null +++ b/releasenotes/notes/support-nova-keypair-a2cdb2da5c1511e9.yaml @@ -0,0 +1,13 @@ +features: + - Added a new config option ``nova_keypair`` to specify an existing Nova + keypair name for the database instance creation, the cloud administrator is + responsible for the keypair management and configuration. It's recommended + to create Trove database instance in the admin project for security + reasons, so only the cloud administrator who has the private key can access + the database instance. With the keypair support, ssh keys are no longer + injected into Trove guest agent image at build time. +upgrade: + - Cloud administrator needs to create a Nova keypair and specify the keypair + name for config option ``nova_keypair``, the private key is used to ssh + into new database instances created. The previous private key is also + needed to ssh into the existing database instances. diff --git a/trove/common/cfg.py b/trove/common/cfg.py index 2682d54ea4..dbe824eda2 100644 --- a/trove/common/cfg.py +++ b/trove/common/cfg.py @@ -81,6 +81,10 @@ common_opts = [ help="The version of the image service client."), cfg.BoolOpt('nova_api_insecure', default=False, help="Allow to perform insecure SSL requests to nova."), + cfg.StrOpt('nova_keypair', default=None, + help="Name of a Nova keypair to inject into a database " + "instance to enable SSH access. The keypair should be " + "prior created by the cloud operator."), cfg.URIOpt('neutron_url', help='URL without the tenant segment.'), cfg.StrOpt('neutron_service_type', default='network', help='Service type to use when searching catalog.'), diff --git a/trove/taskmanager/models.py b/trove/taskmanager/models.py index 0cd297e76b..9f86ecb7ad 100755 --- a/trove/taskmanager/models.py +++ b/trove/taskmanager/models.py @@ -890,14 +890,16 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin): name = self.hostname or self.name bdmap_v2 = block_device_mapping_v2 config_drive = CONF.use_nova_server_config_drive + key_name = CONF.nova_keypair server = self.nova_client.servers.create( name, image_id, flavor_id, files=files, userdata=userdata, security_groups=security_groups, block_device_mapping_v2=bdmap_v2, availability_zone=availability_zone, nics=nics, - config_drive=config_drive, scheduler_hints=scheduler_hints) + config_drive=config_drive, scheduler_hints=scheduler_hints, + key_name=key_name) LOG.debug("Created new compute instance %(server_id)s " - "for instance %(id)s", + "for database instance %(id)s", {'server_id': server.id, 'id': self.id}) return server diff --git a/trove/tests/fakes/nova.py b/trove/tests/fakes/nova.py index 9de9e73204..5bf43685d2 100644 --- a/trove/tests/fakes/nova.py +++ b/trove/tests/fakes/nova.py @@ -101,7 +101,7 @@ class FakeServer(object): next_local_id = 0 def __init__(self, parent, owner, id, name, image_id, flavor_ref, - volumes): + volumes, key_name): self.owner = owner # This is a context. self.id = id self.parent = parent @@ -125,6 +125,7 @@ class FakeServer(object): setattr(self, 'OS-EXT-AZ:availability_zone', 'nova') self._info = {'os:volumes': info_vols} + self.key_name = key_name @property def addresses(self): @@ -268,11 +269,11 @@ class FakeServers(object): def create(self, name, image_id, flavor_ref, files=None, userdata=None, block_device_mapping_v2=None, security_groups=None, availability_zone=None, nics=None, config_drive=False, - scheduler_hints=None): + scheduler_hints=None, key_name=None): id = "FAKE_%s" % uuid.uuid4() volumes = self._get_volumes_from_bdm_v2(block_device_mapping_v2) server = FakeServer(self, self.context, id, name, image_id, flavor_ref, - volumes) + volumes, key_name) self.db[id] = server if name.endswith('SERVER_ERROR'): raise nova_exceptions.ClientException("Fake server create error.") diff --git a/trove/tests/unittests/taskmanager/test_models.py b/trove/tests/unittests/taskmanager/test_models.py index b0d136a9a0..74fb9253ef 100644 --- a/trove/tests/unittests/taskmanager/test_models.py +++ b/trove/tests/unittests/taskmanager/test_models.py @@ -79,6 +79,7 @@ class fake_Server(object): self.security_groups = None self.block_device_mapping_v2 = None self.status = 'ACTIVE' + self.key_name = None class fake_ServerManager(object): @@ -86,7 +87,7 @@ class fake_ServerManager(object): security_groups, block_device_mapping_v2=None, availability_zone=None, nics=None, config_drive=False, - scheduler_hints=None): + scheduler_hints=None, key_name=None): server = fake_Server() server.id = "server_id" server.name = name @@ -98,6 +99,8 @@ class fake_ServerManager(object): server.block_device_mapping_v2 = block_device_mapping_v2 server.availability_zone = availability_zone server.nics = nics + server.key_name = key_name + return server @@ -239,6 +242,14 @@ class FreshInstanceTasksTest(BaseFreshInstanceTasksTest): None, None, None, datastore_manager, None, None, None) self.assertEqual(server.userdata, self.userdata) + def test_create_instance_with_keypair(self): + cfg.CONF.set_override('nova_keypair', 'fake_keypair') + + server = self.freshinstancetasks._create_server( + None, None, None, None, None, None, None) + + self.assertEqual('fake_keypair', server.key_name) + @patch.object(DBInstance, 'get_by') def test_create_instance_guestconfig(self, patch_get_by): cfg.CONF.set_override('guest_config', self.guestconfig) @@ -303,15 +314,18 @@ class FreshInstanceTasksTest(BaseFreshInstanceTasksTest): self.freshinstancetasks._create_server('fake-flavor', 'fake-image', None, 'mysql', None, None, None) - mock_servers_create.assert_called_with('fake-hostname', 'fake-image', - 'fake-flavor', files={}, - userdata=None, - security_groups=None, - block_device_mapping_v2=None, - availability_zone=None, - nics=None, - config_drive=True, - scheduler_hints=None) + mock_servers_create.assert_called_with( + 'fake-hostname', 'fake-image', + 'fake-flavor', files={}, + userdata=None, + security_groups=None, + block_device_mapping_v2=None, + availability_zone=None, + nics=None, + config_drive=True, + scheduler_hints=None, + key_name=None + ) @patch.object(InstanceServiceStatus, 'find_by', return_value=fake_InstanceServiceStatus.find_by())