def test_group_create_maxEntites_lt_minEntities_invalid_400(self): """ minEntities > maxEntities results in a 400. """ expected_config = { "name": "group", "minEntities": 20, "maxEntities": 10, "cooldown": 10, "metadata": {} } rval = { 'groupConfiguration': expected_config, 'launchConfiguration': launch_examples()[0], 'scalingPolicies': {}, 'id': '1' } self.mock_store.create_scaling_group.return_value = defer.succeed(rval) invalid = { 'groupConfiguration': expected_config, 'launchConfiguration': launch_examples()[0], 'scalingPolicies': [], } resp_body = self.assert_status_code(400, self.endpoint, 'POST', json.dumps(invalid)) resp = json.loads(resp_body) self.assertEqual(resp['type'], 'InvalidMinEntities', resp['message'])
def test_create_group_normalizes_launch_config_null_server_metadata(self): """ If the user passes in null for server metadata in the launch config, create group first normalizes it before calling the model """ config = config_examples()[0] launch = launch_examples()[0] launch['args']['server']['metadata'] = None expected_launch = launch_examples()[0] expected_launch['args']['server'].pop('metadata', None) rval = { 'groupConfiguration': config, 'launchConfiguration': expected_launch, 'id': '1', 'state': GroupState('11111', '2', '', {}, {}, None, {}, False), 'scalingPolicies': [] } self.mock_store.create_scaling_group.return_value = defer.succeed(rval) self.assert_status_code( 201, None, 'POST', json.dumps({ 'groupConfiguration': config, 'launchConfiguration': launch }), '/v1.0/11111/groups/1/') self.mock_store.create_scaling_group.assert_called_once_with( mock.ANY, mock.ANY, mock.ANY, expected_launch, None)
def test_update_launch_config_success(self): """ If the update succeeds, the data is updated and a 204 is returned """ self.mock_group.update_launch_config.return_value = defer.succeed(None) response_body = self.assert_status_code( 204, method='PUT', body=json.dumps(launch_examples()[0])) self.assertEqual(response_body, "") self.mock_store.get_scaling_group.assert_called_once_with(mock.ANY, '11111', '1') self.mock_group.update_launch_config.assert_called_once_with( launch_examples()[0])
def test_update_launch_config_success(self): """ If the update succeeds, the data is updated and a 204 is returned """ self.mock_group.update_launch_config.return_value = defer.succeed(None) response_body = self.assert_status_code(204, method='PUT', body=json.dumps( launch_examples()[0])) self.assertEqual(response_body, "") self.mock_store.get_scaling_group.assert_called_once_with( mock.ANY, '11111', '1') self.mock_group.update_launch_config.assert_called_once_with( launch_examples()[0])
def test_update_launch_config_null_server_metadata(self): """ Updating a launch config with null as the server metadata causes the update to happen with effectively no metadata. """ launch = launch_examples()[0] launch['args']['server']['metadata'] = None expected_launch = launch_examples()[0] expected_launch['args']['server'].pop('metadata', None) self.mock_group.update_launch_config.return_value = defer.succeed(None) self.assert_status_code(204, method='PUT', body=json.dumps(launch)) self.mock_group.update_launch_config.assert_called_once_with( expected_launch)
def test_get_launch_config_succeeds(self): """ If getting the group does succeed, an attempt to get the launch config returns a 200 and the actual group config """ self.mock_group.view_launch_config.return_value = defer.succeed( launch_examples()[0]) response_body = self.assert_status_code(200) resp = json.loads(response_body) validate(resp, rest_schemas.view_launch_config) self.assertEqual(resp, {'launchConfiguration': launch_examples()[0]}) self.mock_store.get_scaling_group.assert_called_once_with(mock.ANY, '11111', '1') self.mock_group.view_launch_config.assert_called_once_with()
def test_update_group_config_404(self): """ If you try to modify a not-found object it fails with a 404 not found """ self.mock_group.update_launch_config.return_value = defer.fail( NoSuchScalingGroupError('11111', 'one')) response_body = self.assert_status_code( 404, method='PUT', body=json.dumps(launch_examples()[0])) resp = json.loads(response_body) self.mock_group.update_launch_config.assert_called_once_with( launch_examples()[0]) self.assertEqual(resp['error']['type'], 'NoSuchScalingGroupError') self.flushLoggedErrors(NoSuchScalingGroupError)
def test_view_manifest(self, url_root): """ Viewing the manifest of an existant group returns whatever the implementation's `view_manifest()` method returns, in string format """ manifest = { 'groupConfiguration': config_examples()[0], 'launchConfiguration': launch_examples()[0], 'scalingPolicies': { "5": policy_examples()[0] }, 'id': 'one' } self.mock_group.view_manifest.return_value = defer.succeed(manifest) response_body = self.assert_status_code(200, method="GET") resp = json.loads(response_body) validate(resp, rest_schemas.create_and_manifest_response) expected_policy = policy_examples()[0] expected_policy.update({ "id": "5", "links": [ { "href": "/v1.0/11111/groups/one/policies/5/", "rel": "self" }, ] }) expected = { 'group': { 'groupConfiguration': config_examples()[0], 'launchConfiguration': launch_examples()[0], 'scalingPolicies': [expected_policy], "id": "one", "links": [{ "href": "/v1.0/11111/groups/one/", "rel": "self" }] } } self.assertEqual(resp, expected) self.mock_store.get_scaling_group.assert_called_once_with( mock.ANY, '11111', 'one') self.mock_group.view_manifest.assert_called_once_with()
def test_update_with_clb_and_no_servicenet_returns_400(self): """ If the launch config has one or more cloud load balancers attached, but disabled ServiceNet on the server, the launch config fails to validate when editing a launch config. """ no_servicenet = launch_examples()[0] no_servicenet['args']['server']['networks'] = [{ 'uuid': "00000000-0000-0000-0000-000000000000" }] no_servicenet['args']["loadBalancers"] = [{ 'loadBalancerId': 1, 'port': 80 }] response_body = self.assert_status_code(400, method='PUT', body=json.dumps(no_servicenet)) resp = json.loads(response_body) self.assertEquals(resp['error']['type'], 'ValidationError') self.assertEquals( resp['error']['message'], "ServiceNet network must be present if one or more Cloud Load " "Balancers are configured.")
def test_update_launch_config_null_server_metadata(self): """ Updating a launch config with null as the server metadata causes the update to happen with effectively no metadata. """ launch = launch_examples()[0] launch['args']['server']['metadata'] = None expected_launch = launch_examples()[0] expected_launch['args']['server'].pop('metadata', None) self.mock_group.update_launch_config.return_value = defer.succeed(None) self.assert_status_code( 204, method='PUT', body=json.dumps(launch)) self.mock_group.update_launch_config.assert_called_once_with( expected_launch)
def test_get_launch_config_succeeds(self): """ If getting the group does succeed, an attempt to get the launch config returns a 200 and the actual group config """ self.mock_group.view_launch_config.return_value = defer.succeed( launch_examples()[0]) response_body = self.assert_status_code(200) resp = json.loads(response_body) validate(resp, rest_schemas.view_launch_config) self.assertEqual(resp, {'launchConfiguration': launch_examples()[0]}) self.mock_store.get_scaling_group.assert_called_once_with( mock.ANY, '11111', '1') self.mock_group.view_launch_config.assert_called_once_with()
def test_create_group_propagates_modify_state_errors(self): """ If there is an error when modify state is called, even if the group creation succeeds, a 500 is returned. """ self.mock_group.modify_state.side_effect = AssertionError config = config_examples()[0] launch = launch_examples()[0] self.mock_store.create_scaling_group.return_value = defer.succeed({ 'groupConfiguration': config, 'launchConfiguration': launch, 'scalingPolicies': {}, 'id': '1' }) self.assert_status_code(500, None, 'POST', body=json.dumps({ 'groupConfiguration': config, 'launchConfiguration': launch })) self.flushLoggedErrors(AssertionError)
def test_update_launch_config_fail_500(self): """ If the update fails for some strange reason, a 500 is returned """ self.mock_group.update_launch_config.return_value = defer.fail( DummyException()) response_body = self.assert_status_code( 500, method="PUT", body=json.dumps(launch_examples()[0])) resp = json.loads(response_body) self.mock_store.get_scaling_group.assert_called_once_with(mock.ANY, '11111', '1') self.mock_group.update_launch_config.assert_called_once_with( launch_examples()[0]) self.assertEqual(resp['error']['type'], 'InternalError') self.flushLoggedErrors(DummyException)
def test_update_group_config_404(self): """ If you try to modify a not-found object it fails with a 404 not found """ self.mock_group.update_launch_config.return_value = defer.fail( NoSuchScalingGroupError('11111', 'one')) response_body = self.assert_status_code(404, method='PUT', body=json.dumps( launch_examples()[0])) resp = json.loads(response_body) self.mock_group.update_launch_config.assert_called_once_with( launch_examples()[0]) self.assertEqual(resp['type'], 'NoSuchScalingGroupError') self.flushLoggedErrors(NoSuchScalingGroupError)
def test_view_manifest(self): """ Viewing the manifest of an existant group returns whatever the implementation's `view_manifest()` method returns, in string format """ manifest = { 'groupConfiguration': config_examples()[0], 'launchConfiguration': launch_examples()[0], 'id': 'one', 'state': GroupState('11111', '1', '', {}, {}, None, {}, False), 'scalingPolicies': [dict(id="5", **policy_examples()[0])] } self.mock_group.view_manifest.return_value = defer.succeed(manifest) response_body = self.assert_status_code(200, method="GET") resp = json.loads(response_body) validate(resp, rest_schemas.create_and_manifest_response) expected_policy = policy_examples()[0] expected_policy.update({ "id": "5", "links": [ {"href": "/v1.0/11111/groups/one/policies/5/", "rel": "self"}, ] }) expected = { 'group': { 'groupConfiguration': config_examples()[0], 'launchConfiguration': launch_examples()[0], 'scalingPolicies': [expected_policy], "id": "one", "links": [ {"href": "/v1.0/11111/groups/one/", "rel": "self"} ], 'scalingPolicies_links': [ {"href": "/v1.0/11111/groups/one/policies/", "rel": "policies"} ], 'state': manifest['state'] } } self.assertEqual(resp, expected) self.mock_store.get_scaling_group.assert_called_once_with( mock.ANY, '11111', 'one') self.mock_group.view_manifest.assert_called_once_with(False)
def test_group_create_many_policies(self): """ Tries to create a scaling group """ self._test_successful_create({ 'groupConfiguration': config_examples()[0], 'launchConfiguration': launch_examples()[0], 'scalingPolicies': policy_examples() })
def test_group_create_no_scaling_policies(self): """ Tries to create a scaling group, but if no scaling policy is provided the the interface is called with None in place of scaling policies """ self._test_successful_create({ 'groupConfiguration': config_examples()[0], 'launchConfiguration': launch_examples()[0], })
def test_update_launch_config_fail_500(self): """ If the update fails for some strange reason, a 500 is returned """ self.mock_group.update_launch_config.return_value = defer.fail( DummyException()) response_body = self.assert_status_code(500, method="PUT", body=json.dumps( launch_examples()[0])) resp = json.loads(response_body) self.mock_store.get_scaling_group.assert_called_once_with( mock.ANY, '11111', '1') self.mock_group.update_launch_config.assert_called_once_with( launch_examples()[0]) self.assertEqual(resp['type'], 'InternalError') self.flushLoggedErrors(DummyException)
def test_group_create_one_policy(self): """ Tries to create a scaling group """ self._test_successful_create({ 'groupConfiguration': config_examples()[0], 'launchConfiguration': launch_examples()[0], 'scalingPolicies': [policy_examples()[0]] })
def test_update_launch_config_invalid_server_metadata(self): """ If invalid server metadata is provided, updating the launch config will fail with a 400 """ launch = launch_examples()[0] launch['args']['server']['metadata'] = "invalid" self.mock_group.update_launch_config.return_value = defer.succeed(None) self.assert_status_code(400, method='PUT', body=json.dumps(launch)) self.assertFalse(self.mock_store.get_scaling_group.called) self.assertFalse(self.mock_group.update_launch_config.called)
def test_group_create_default_maxentities(self): """ A scaling group without maxentities defaults to configured maxEntities """ self._test_successful_create({ 'groupConfiguration': { "name": "group", "minEntities": 10, "cooldown": 10, "metadata": {} }, 'launchConfiguration': launch_examples()[0] })
def test_create_invalid_launch_config(self): """ Invalid launch configuration raises 400 """ self.supervisor.validate_launch_config.return_value = defer.fail( InvalidLaunchConfiguration('meh')) request_body = { 'groupConfiguration': config_examples()[0], 'launchConfiguration': launch_examples()[0] } self.assert_status_code(400, method='POST', body=json.dumps(request_body)) self.flushLoggedErrors()
def test_update_invalid_launch_config_fail_400(self): """ Invalid launch configuration gives 400 error """ self.supervisor.validate_launch_config.return_value = defer.fail( InvalidLaunchConfiguration('hmph')) response_body = self.assert_status_code( 400, method="PUT", body=json.dumps(launch_examples()[0])) resp = json.loads(response_body) self.assertEqual(resp['error']['message'], 'hmph') self.flushLoggedErrors(InvalidLaunchConfiguration)
def test_create_unknown_error_is_500(self): """ If an unexpected exception is raised, endpoint returns a 500. """ error = DummyException('what') request_body = { 'groupConfiguration': config_examples()[0], 'launchConfiguration': launch_examples()[0] } self.mock_store.create_scaling_group.return_value = defer.fail(error) self.assert_status_code(500, method="POST", body=json.dumps(request_body)) self.flushLoggedErrors()
def test_update_launch_config_invalid_server_metadata(self): """ If invalid server metadata is provided, updating the launch config will fail with a 400 """ launch = launch_examples()[0] launch['args']['server']['metadata'] = "invalid" self.mock_group.update_launch_config.return_value = defer.succeed(None) self.assert_status_code( 400, method='PUT', body=json.dumps(launch)) self.assertFalse(self.mock_store.get_scaling_group.called) self.assertFalse(self.mock_group.update_launch_config.called)
def test_create_invalid_server_metadata_in_launch_config(self): """ Invalid launch configuration raises 400 """ launch = launch_examples()[0] launch['args']['server']['metadata'] = "invalid" request_body = { 'groupConfiguration': config_examples()[0], 'launchConfiguration': launch } self.assert_status_code(400, method='POST', body=json.dumps(request_body)) self.flushLoggedErrors()
def test_group_create_maxEntities_eq_minEntities_valid(self): """ A scaling group in which the minEntities == maxEntities validates """ self._test_successful_create({ 'groupConfiguration': { "name": "group", "minEntities": 10, "maxEntities": 10, "cooldown": 10, "metadata": {} }, 'launchConfiguration': launch_examples()[0] })
def test_update_invalid_launch_config_fail_400(self): """ Invalid launch configuration gives 400 error """ self.supervisor.validate_launch_config.return_value = defer.fail( InvalidLaunchConfiguration('hmph')) response_body = self.assert_status_code(400, method="PUT", body=json.dumps( launch_examples()[0])) resp = json.loads(response_body) self.assertEqual(resp['error']['message'], 'hmph') self.flushLoggedErrors(InvalidLaunchConfiguration)
def test_group_create_bobby(self, create_group, get_url_root): """ A scaling group is created and calls over to Bobby """ request_body = { 'groupConfiguration': { "name": "group", "minEntities": 1, "maxEntities": 10, "cooldown": 10, "metadata": {} }, 'launchConfiguration': launch_examples()[0] } config = request_body['groupConfiguration'] launch = request_body['launchConfiguration'] policies = request_body.get('scalingPolicies', []) expected_config = config.copy() rval = { 'groupConfiguration': expected_config, 'launchConfiguration': launch, 'scalingPolicies': dict( zip([str(i) for i in range(len(policies))], [p.copy() for p in policies])), 'id': '1' } self.mock_store.create_scaling_group.return_value = defer.succeed(rval) self.assert_status_code(201, None, 'POST', json.dumps(request_body), '/v1.0/11111/groups/1/') self.mock_store.create_scaling_group.assert_called_once_with( mock.ANY, '11111', expected_config, launch, policies or None) create_group.assert_called_once_with('11111', '1')
def test_update_with_clb_and_no_servicenet_returns_400(self): """ If the launch config has one or more cloud load balancers attached, but disabled ServiceNet on the server, the launch config fails to validate when editing a launch config. """ no_servicenet = launch_examples()[0] no_servicenet['args']['server']['networks'] = [ {'uuid': "00000000-0000-0000-0000-000000000000"}] no_servicenet['args']["loadBalancers"] = [ {'loadBalancerId': 1, 'port': 80}] response_body = self.assert_status_code( 400, method='PUT', body=json.dumps(no_servicenet)) resp = json.loads(response_body) self.assertEquals(resp['error']['type'], 'ValidationError') self.assertEquals( resp['error']['message'], "ServiceNet network must be present if one or more Cloud Load " "Balancers are configured.")
def test_group_create_calls_obey_config_changes(self): """ If the group creation succeeds, ``obey_config_change`` is called with the updated log, transaction id, config, group, and state """ config = config_examples()[0] expected_config = config.copy() expected_config.setdefault('maxEntities', 25) expected_config.setdefault('metadata', {}) manifest = { 'groupConfiguration': expected_config, 'launchConfiguration': launch_examples()[0], } self.mock_store.create_scaling_group.return_value = defer.succeed(manifest) self._test_successful_create(manifest) self.mock_group.modify_state.assert_called_once_with(mock.ANY) self.mock_controller.obey_config_change.assert_called_once_with( mock.ANY, "transaction-id", expected_config, self.mock_group, self.mock_state)
def test_group_create_bobby(self, create_group, get_url_root): """ A scaling group is created and calls over to Bobby """ request_body = { 'groupConfiguration': { "name": "group", "minEntities": 1, "maxEntities": 10, "cooldown": 10, "metadata": {} }, 'launchConfiguration': launch_examples()[0] } config = request_body['groupConfiguration'] launch = request_body['launchConfiguration'] policies = request_body.get('scalingPolicies', []) expected_config = config.copy() rval = { 'groupConfiguration': expected_config, 'launchConfiguration': launch, 'scalingPolicies': policies, 'id': '1', 'state': GroupState('11111', '1', '', {}, {}, None, {}, False) } self.mock_store.create_scaling_group.return_value = defer.succeed(rval) self.assert_status_code( 201, None, 'POST', json.dumps(request_body), '/v1.0/11111/groups/1/') self.mock_store.create_scaling_group.assert_called_once_with( mock.ANY, '11111', expected_config, launch, policies or None) create_group.assert_called_once_with('11111', '1')
def test_group_create_calls_obey_config_changes(self): """ If the group creation succeeds, ``obey_config_change`` is called with the updated log, transaction id, config, group, and state """ config = config_examples()[0] expected_config = config.copy() expected_config.setdefault('maxEntities', 25) expected_config.setdefault('metadata', {}) manifest = { 'groupConfiguration': expected_config, 'launchConfiguration': launch_examples()[0], } self.mock_store.create_scaling_group.return_value = defer.succeed( manifest) self._test_successful_create(manifest) self.mock_group.modify_state.assert_called_once_with(mock.ANY) self.mock_controller.obey_config_change.assert_called_once_with( mock.ANY, "transaction-id", expected_config, self.mock_group, self.mock_state)
def test_view_manifest_with_webhooks(self): """ `view_manifest` gives webhooks information in policies if query args contains ?webhooks=true """ manifest = { 'groupConfiguration': config_examples()[0], 'launchConfiguration': launch_examples()[0], 'id': 'one', 'state': GroupState('11111', '1', '', {}, {}, None, {}, False), 'scalingPolicies': [dict(id="5", **policy_examples()[0]), dict(id="6", **policy_examples()[1])] } webhooks = [ [ { 'id': '3', 'name': 'three', 'metadata': {}, 'capability': {"version": "1", 'hash': 'xxx'} }, { 'id': '4', 'name': 'four', 'metadata': {}, 'capability': {"version": "1", 'hash': 'yyy'} } ], [ { 'id': '5', 'name': 'five', 'metadata': {}, 'capability': {"version": "1", 'hash': 'xxx'} }, { 'id': '6', 'name': 'six', 'metadata': {}, 'capability': {"version": "1", 'hash': 'yyy'} } ] ] webhooks_internal_links = [ [[{"href": '/v1.0/11111/groups/one/policies/5/webhooks/3/', "rel": "self"}, {"href": '/v1.0/execute/1/xxx/', "rel": "capability"}], [{"href": '/v1.0/11111/groups/one/policies/5/webhooks/4/', "rel": "self"}, {"href": '/v1.0/execute/1/yyy/', "rel": "capability"}]], [[{"href": '/v1.0/11111/groups/one/policies/6/webhooks/5/', "rel": "self"}, {"href": '/v1.0/execute/1/xxx/', "rel": "capability"}], [{"href": '/v1.0/11111/groups/one/policies/6/webhooks/6/', "rel": "self"}, {"href": '/v1.0/execute/1/yyy/', "rel": "capability"}]] ] webhooks_links = [ [{'href': '/v1.0/11111/groups/one/policies/5/webhooks/', 'rel': 'webhooks'}], [{'href': '/v1.0/11111/groups/one/policies/6/webhooks/', 'rel': 'webhooks'}] ] manifest['scalingPolicies'][0]['webhooks'] = webhooks[0] manifest['scalingPolicies'][1]['webhooks'] = webhooks[1] self.mock_group.view_manifest.return_value = defer.succeed(manifest) response_body = self.assert_status_code( 200, endpoint="{0}?webhooks=true".format(self.endpoint), method="GET") resp = json.loads(response_body) validate(resp, rest_schemas.create_and_manifest_response) exp_policies = deepcopy(manifest['scalingPolicies']) for i in [0, 1]: exp_policies[i]['webhooks'] = deepcopy(webhooks[i]) for j, webhook in enumerate(exp_policies[i]['webhooks']): exp_policies[i]['webhooks'][j]['links'] = webhooks_internal_links[i][j] exp_policies[i]['webhooks_links'] = webhooks_links[i] self.assertEqual(resp['group']['scalingPolicies'], exp_policies)