Browse Source

Add simple tempest testcases for resource limiting

Change-Id: I928b80e83f3101f1a8dd0564a8fad4381deb3fef
Story: 2001586
Task: 20065
changes/66/573666/9
Hunt Xu 3 years ago
parent
commit
a8425dc7cf
  1. 40
      qinling_tempest_plugin/functions/python/test_python_cpu_limit.py
  2. 23
      qinling_tempest_plugin/functions/python/test_python_memory_limit.py
  3. 3
      qinling_tempest_plugin/services/qinling_client.py
  4. 122
      qinling_tempest_plugin/tests/api/test_executions.py

40
qinling_tempest_plugin/functions/python/test_python_cpu_limit.py

@ -0,0 +1,40 @@
# Copyright 2018 AWCloud Software Co., Ltd
#
# 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.
# Codes are from https://www.craig-wood.com/nick/articles/pi-machin/
def arctan_euler(x, one):
x_squared = x * x
x_squared_plus_1 = x_squared + 1
term = (x * one) // x_squared_plus_1
total = term
two_n = 2
while True:
divisor = (two_n + 1) * x_squared_plus_1
term *= two_n
term = term // divisor
if term == 0:
break
total += term
two_n += 2
return total
def pi_machin(one):
return 4 * (4 * arctan_euler(5, one) - arctan_euler(239, one))
def main(digit=50000, *args, **kwargs):
return str(pi_machin(10**digit))[:15]

23
qinling_tempest_plugin/functions/python/test_python_memory_limit.py

@ -0,0 +1,23 @@
# Copyright 2018 AWCloud Software Co., Ltd
#
# 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.
S = 20 * 1024 * 1024 # 20M
def main(*args, **kwargs):
L = []
for i in 'abcd':
L.append(i * S)
return len(L)

3
qinling_tempest_plugin/services/qinling_client.py

@ -85,7 +85,7 @@ class QinlingClient(client_base.QinlingClientBase):
return resp, json.loads(resp.text)
def update_function(self, function_id, package_data=None, code=None,
entry=None):
entry=None, **kwargs):
headers = {'X-Auth-Token': self.auth_provider.get_token()}
req_body = {}
@ -93,6 +93,7 @@ class QinlingClient(client_base.QinlingClientBase):
req_body['code'] = json.dumps(code)
if entry:
req_body['entry'] = entry
req_body.update(kwargs)
req_kwargs = {
'headers': headers,

122
qinling_tempest_plugin/tests/api/test_executions.py

@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from concurrent import futures
import json
import futurist
from oslo_serialization import jsonutils
@ -20,6 +21,9 @@ from tempest.lib import exceptions
from qinling_tempest_plugin.tests import base
INVOKE_ERROR = "Function execution failed because of too much resource " \
"consumption"
class ExecutionsTest(base.BaseQinlingTest):
name_prefix = 'ExecutionsTest'
@ -273,3 +277,121 @@ class ExecutionsTest(base.BaseQinlingTest):
execution_id, ignore_notfound=True)
self.assertEqual('success', body['status'])
self.assertIn('Qinling', jsonutils.loads(body['result'])['output'])
@decorators.idempotent_id('2b5f0787-b82d-4fc4-af76-cf86d389a76b')
def test_python_execution_memory_limit_non_image(self):
"""In this case, the following steps are taken:
1. Create a function that requires ~80M memory to run.
2. Create an execution using the function.
3. Verify that the execution is killed by the OOM-killer
because the function memory limit is only 32M(default).
4. Increase the function memory limit to 96M.
5. Create another execution.
6. Check the execution finished normally.
"""
# Create function
package = self.create_package(
name='python/test_python_memory_limit.py'
)
function_id = self.create_function(package_path=package)
# Invoke function
resp, body = self.client.create_execution(function_id)
execution_id = body['id']
self.addCleanup(self.client.delete_resource, 'executions',
execution_id, ignore_notfound=True)
# Check the process is killed
self.assertEqual(201, resp.status)
result = json.loads(body['result'])
output = result.get('output')
self.assertEqual(INVOKE_ERROR, output)
# Increase the memory limit to 100663296(96M).
resp, body = self.client.update_function(
function_id, memory_size=100663296)
self.assertEqual(200, resp.status_code)
# Invoke the function again
resp, body = self.client.create_execution(function_id)
execution_id = body['id']
self.addCleanup(self.client.delete_resource, 'executions',
execution_id, ignore_notfound=True)
# Check the process exited normally
self.assertEqual(201, resp.status)
result = json.loads(body['result'])
output = result.get('output')
# The function returns the length of a list containing 4 long strings.
self.assertEqual(4, output)
@decorators.idempotent_id('ed714f98-29fe-4e8d-b6ee-9730f92bddea')
def test_python_execution_cpu_limit_non_image(self):
"""In this case, the following steps are taken:
1. Create a function that takes some time to finish (calculating the
first 50000 digits of PI)
2. Create an execution using the function.
3. Store the duration of the first execution.
4. Increase the function cpu limit from 100(default) to 200 millicpu.
5. Create another execution.
6. Check whether the duration of the first execution is approximately
the double of the duration of the second one as its cpu resource is
half of the second run.
"""
# Create function
package = self.create_package(
name='python/test_python_cpu_limit.py'
)
function_id = self.create_function(package_path=package)
# Invoke function
resp, body = self.client.create_execution(function_id)
execution_id = body['id']
self.addCleanup(self.client.delete_resource, 'executions',
execution_id, ignore_notfound=True)
# Record the duration, check whether the result is correct.
self.assertEqual(201, resp.status)
result = json.loads(body['result'])
output = result.get('output')
# Only the first 15 digits are returned.
self.assertEqual('314159265358979', output)
first_duration = result.get('duration', 0)
# Increase the cpu limit
resp, body = self.client.update_function(function_id, cpu=200)
self.assertEqual(200, resp.status_code)
# Invoke the function again
resp, body = self.client.create_execution(function_id)
execution_id = body['id']
self.addCleanup(self.client.delete_resource, 'executions',
execution_id, ignore_notfound=True)
# Record the second duration, check whether the result is correct.
self.assertEqual(201, resp.status)
result = json.loads(body['result'])
output = result.get('output')
# Only the first 15 digits are returned.
self.assertEqual('314159265358979', output)
second_duration = result.get('duration', 0)
# Check whether the duration of the first execution is approximately
# the double (1.8x ~ 2.2x) of the duration of the second one.
# NOTE(huntxu): on my testbed, the result is quite near 2x. However
# it may vary in different environments, so we give a wider range
# here.
self.assertNotEqual(0, first_duration)
self.assertNotEqual(0, second_duration)
upper = second_duration * 2.2
lower = second_duration * 1.8
self.assertGreaterEqual(upper, first_duration)
self.assertLessEqual(lower, first_duration)
Loading…
Cancel
Save