diff --git a/Authors b/Authors index 815bbe7cf443..afcc2bd70bd6 100644 --- a/Authors +++ b/Authors @@ -115,6 +115,7 @@ Matthew Hooker Michael Gundlach Michael Still Mike Lundy +Mike Pittaro Mike Scherbakov Mikyung Kang Mohammed Naser diff --git a/nova/api/openstack/compute/servers.py b/nova/api/openstack/compute/servers.py index 8f9ee01dfd9f..57adf22ae8eb 100644 --- a/nova/api/openstack/compute/servers.py +++ b/nova/api/openstack/compute/servers.py @@ -784,7 +784,8 @@ class Controller(wsgi.Controller): if '_is_precooked' in server['server'].keys(): del server['server']['_is_precooked'] else: - server['server']['adminPass'] = password + if FLAGS.enable_instance_password: + server['server']['adminPass'] = password robj = wsgi.ResponseObject(server) @@ -1107,7 +1108,9 @@ class Controller(wsgi.Controller): view = self._view_builder.show(req, instance) # Add on the adminPass attribute since the view doesn't do it - view['server']['adminPass'] = password + # unless instance passwords are disabled + if FLAGS.enable_instance_password: + view['server']['adminPass'] = password robj = wsgi.ResponseObject(view) return self._add_location(robj) diff --git a/nova/flags.py b/nova/flags.py index 3f3560057661..3fac3a2db1a8 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -503,6 +503,10 @@ global_opts = [ cfg.BoolOpt('use_ipv6', default=False, help='use ipv6'), + cfg.BoolOpt('enable_instance_password', + default=True, + help='Allows use of instance password during ' + 'server creation'), cfg.IntOpt('password_length', default=12, help='Length of generated instance admin passwords'), diff --git a/nova/tests/api/openstack/compute/test_server_actions.py b/nova/tests/api/openstack/compute/test_server_actions.py index 6c38c3fadbc9..82dcf65e4900 100644 --- a/nova/tests/api/openstack/compute/test_server_actions.py +++ b/nova/tests/api/openstack/compute/test_server_actions.py @@ -165,7 +165,8 @@ class ServerActionsControllerTest(test.TestCase): self.service.delete_all() self.sent_to_glance = {} fakes.stub_out_glance_add_image(self.stubs, self.sent_to_glance) - self.flags(allow_instance_snapshots=True) + self.flags(allow_instance_snapshots=True, + enable_instance_password=True) self.uuid = FAKE_UUID self.url = '/v2/fake/servers/%s/action' % self.uuid self._image_href = '155d900f-4e14-4e4c-a73d-069cbf4541e6' @@ -187,6 +188,22 @@ class ServerActionsControllerTest(test.TestCase): self.assertEqual(mock_method.instance_id, self.uuid) self.assertEqual(mock_method.password, '1234pass') + def test_server_change_password_pass_disabled(self): + # run with enable_instance_password disabled to verify adminPass + # is missing from response. See lp bug 921814 + self.flags(enable_instance_password=False) + + mock_method = MockSetAdminPassword() + self.stubs.Set(nova.compute.api.API, 'set_admin_password', mock_method) + body = {'changePassword': {'adminPass': '1234pass'}} + + req = fakes.HTTPRequest.blank(self.url) + self.controller._action_change_password(req, FAKE_UUID, body) + + self.assertEqual(mock_method.instance_id, self.uuid) + # note,the mock still contains the password. + self.assertEqual(mock_method.password, '1234pass') + def test_server_change_password_not_a_string(self): body = {'changePassword': {'adminPass': 1234}} req = fakes.HTTPRequest.blank(self.url) @@ -280,6 +297,31 @@ class ServerActionsControllerTest(test.TestCase): self.assertEqual(body['server']['image']['id'], '2') self.assertEqual(len(body['server']['adminPass']), FLAGS.password_length) + + self.assertEqual(robj['location'], self_href) + + def test_rebuild_accepted_minimum_pass_disabled(self): + # run with enable_instance_password disabled to verify adminPass + # is missing from response. See lp bug 921814 + self.flags(enable_instance_password=False) + + new_return_server = return_server_with_attributes(image_ref='2') + self.stubs.Set(nova.db, 'instance_get', new_return_server) + self_href = 'http://localhost/v2/fake/servers/%s' % FAKE_UUID + + body = { + "rebuild": { + "imageRef": self._image_href, + }, + } + + req = fakes.HTTPRequest.blank(self.url) + robj = self.controller._action_rebuild(req, FAKE_UUID, body) + body = robj.obj + + self.assertEqual(body['server']['image']['id'], '2') + self.assertTrue("adminPass" not in body['server']) + self.assertEqual(robj['location'], self_href) def test_rebuild_raises_conflict_on_invalid_state(self): @@ -391,6 +433,27 @@ class ServerActionsControllerTest(test.TestCase): self.assertEqual(body['server']['image']['id'], '2') self.assertEqual(body['server']['adminPass'], 'asdf') + def test_rebuild_admin_pass_pass_disabled(self): + # run with enable_instance_password disabled to verify adminPass + # is missing from response. See lp bug 921814 + self.flags(enable_instance_password=False) + + new_return_server = return_server_with_attributes(image_ref='2') + self.stubs.Set(nova.db, 'instance_get', new_return_server) + + body = { + "rebuild": { + "imageRef": self._image_href, + "adminPass": "asdf", + }, + } + + req = fakes.HTTPRequest.blank(self.url) + body = self.controller._action_rebuild(req, FAKE_UUID, body).obj + + self.assertEqual(body['server']['image']['id'], '2') + self.assertTrue('adminPass' not in body['server']) + def test_rebuild_server_not_found(self): def server_not_found(self, instance_id): raise exception.InstanceNotFound(instance_id=instance_id) diff --git a/nova/tests/api/openstack/compute/test_servers.py b/nova/tests/api/openstack/compute/test_servers.py index c6438380eaeb..14f49684f5a2 100644 --- a/nova/tests/api/openstack/compute/test_servers.py +++ b/nova/tests/api/openstack/compute/test_servers.py @@ -1393,7 +1393,8 @@ class ServersControllerCreateTest(test.TestCase): super(ServersControllerCreateTest, self).setUp() self.maxDiff = None - self.flags(verbose=True) + self.flags(verbose=True, + enable_instance_password=True) self.config_drive = None self.instance_cache_num = 0 self.instance_cache = {} @@ -1475,6 +1476,20 @@ class ServersControllerCreateTest(test.TestCase): self.stubs.Set(nova.network.manager.VlanManager, 'allocate_fixed_ip', fake_method) + def _check_admin_pass_len(self, server_dict): + """ utility function - check server_dict for adminPass + length. + + """ + self.assertEqual(FLAGS.password_length, + len(server_dict["adminPass"])) + + def _check_admin_pass_missing(self, server_dict): + """ utility function - check server_dict for absence + of adminPass + """ + self.assertTrue("adminPass" not in server_dict) + def _test_create_instance(self): image_uuid = 'c905cedb-7281-47e4-8a62-f26bc5fc4c77' body = dict(server=dict( @@ -1487,7 +1502,7 @@ class ServersControllerCreateTest(test.TestCase): req.headers["content-type"] = "application/json" server = self.controller.create(req, body).obj['server'] - self.assertEqual(FLAGS.password_length, len(server['adminPass'])) + self._check_admin_pass_len(server) self.assertEqual(FAKE_UUID, server['id']) def test_create_multiple_instances(self): @@ -1515,7 +1530,35 @@ class ServersControllerCreateTest(test.TestCase): res = self.controller.create(req, body).obj self.assertEqual(FAKE_UUID, res["server"]["id"]) - self.assertEqual(12, len(res["server"]["adminPass"])) + self._check_admin_pass_len(res["server"]) + + def test_create_multiple_instances_pass_disabled(self): + """Test creating multiple instances but not asking for + reservation_id + """ + self.flags(enable_instance_password=False) + image_href = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6' + flavor_ref = 'http://localhost/123/flavors/3' + body = { + 'server': { + 'min_count': 2, + 'name': 'server_test', + 'imageRef': image_href, + 'flavorRef': flavor_ref, + 'metadata': {'hello': 'world', + 'open': 'stack'}, + 'personality': [] + } + } + + req = fakes.HTTPRequest.blank('/v2/fake/servers') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + res = self.controller.create(req, body).obj + + self.assertEqual(FAKE_UUID, res["server"]["id"]) + self._check_admin_pass_missing(res["server"]) def test_create_multiple_instances_resv_id_return(self): """Test creating multiple instances with asking for @@ -1678,7 +1721,47 @@ class ServersControllerCreateTest(test.TestCase): res = self.controller.create(req, body).obj server = res['server'] - self.assertEqual(FLAGS.password_length, len(server['adminPass'])) + self._check_admin_pass_len(server) + self.assertEqual(FAKE_UUID, server['id']) + + def test_create_instance_with_access_ip_pass_disabled(self): + # test with admin passwords disabled See lp bug 921814 + self.flags(enable_instance_password=False) + + # proper local hrefs must start with 'http://localhost/v2/' + image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6' + image_href = 'http://localhost/v2/fake/images/%s' % image_uuid + flavor_ref = 'http://localhost/fake/flavors/3' + access_ipv4 = '1.2.3.4' + access_ipv6 = 'fead::1234' + body = { + 'server': { + 'name': 'server_test', + 'imageRef': image_href, + 'flavorRef': flavor_ref, + 'accessIPv4': access_ipv4, + 'accessIPv6': access_ipv6, + 'metadata': { + 'hello': 'world', + 'open': 'stack', + }, + 'personality': [ + { + "path": "/etc/banner.txt", + "contents": "MQ==", + }, + ], + }, + } + + req = fakes.HTTPRequest.blank('/v2/fake/servers') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + res = self.controller.create(req, body).obj + + server = res['server'] + self._check_admin_pass_missing(server) self.assertEqual(FAKE_UUID, server['id']) def test_create_instance_bad_format_access_ip_v4(self): @@ -1768,6 +1851,7 @@ class ServersControllerCreateTest(test.TestCase): "path": "/etc/banner.txt", "contents": "MQ==", }, + ], }, } @@ -1779,7 +1863,42 @@ class ServersControllerCreateTest(test.TestCase): res = self.controller.create(req, body).obj server = res['server'] - self.assertEqual(FLAGS.password_length, len(server['adminPass'])) + self._check_admin_pass_len(server) + self.assertEqual(FAKE_UUID, server['id']) + + def test_create_instance_pass_disabled(self): + self.flags(enable_instance_password=False) + # proper local hrefs must start with 'http://localhost/v2/' + image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6' + image_href = 'http://localhost/v2/images/%s' % image_uuid + flavor_ref = 'http://localhost/123/flavors/3' + body = { + 'server': { + 'name': 'server_test', + 'imageRef': image_href, + 'flavorRef': flavor_ref, + 'metadata': { + 'hello': 'world', + 'open': 'stack', + }, + 'personality': [ + { + "path": "/etc/banner.txt", + "contents": "MQ==", + }, + + ], + }, + } + + req = fakes.HTTPRequest.blank('/v2/fake/servers') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + res = self.controller.create(req, body).obj + + server = res['server'] + self._check_admin_pass_missing(server) self.assertEqual(FAKE_UUID, server['id']) def test_create_instance_too_much_metadata(self): @@ -1835,7 +1954,7 @@ class ServersControllerCreateTest(test.TestCase): res = self.controller.create(req, body).obj self.assertEqual(FAKE_UUID, res["server"]["id"]) - self.assertEqual(12, len(res["server"]["adminPass"])) + self._check_admin_pass_len(res["server"]) def test_create_instance_invalid_flavor_href(self): image_href = 'http://localhost/v2/images/2' @@ -2043,6 +2162,28 @@ class ServersControllerCreateTest(test.TestCase): server = res['server'] self.assertEqual(server['adminPass'], body['server']['adminPass']) + def test_create_instance_admin_pass_pass_disabled(self): + self.flags(enable_instance_password=False) + image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6' + body = { + 'server': { + 'name': 'server_test', + 'imageRef': image_uuid, + 'flavorRef': 3, + 'adminPass': 'testpass', + }, + } + + req = fakes.HTTPRequest.blank('/v2/fake/servers') + req.method = 'POST' + req.body = json.dumps(body) + req.headers['content-type'] = "application/json" + res = self.controller.create(req, body).obj + + server = res['server'] + self.assertTrue('adminPass' in body['server'] ) + self.assertTrue('adminPass' not in server) + def test_create_instance_admin_pass_empty(self): body = { 'server': {