Add cpu and memory_size options support for function
Allow users to specify cpu/memory_size when creating function, and allow to update their values saved in function database. This patch is api part of customized cpu/mem in qinling server and is based on patch [0]. [0]: https://review.openstack.org/#/c/562507/ Story: 2001586 Task: 14366 Change-Id: I7a245f93a445a00c2722238d3f94d3a960f16af4
This commit is contained in:
parent
c3a081f273
commit
e0e8f3d13d
|
@ -34,6 +34,7 @@ from qinling.db import api as db_api
|
||||||
from qinling import exceptions as exc
|
from qinling import exceptions as exc
|
||||||
from qinling import rpc
|
from qinling import rpc
|
||||||
from qinling.storage import base as storage_base
|
from qinling.storage import base as storage_base
|
||||||
|
from qinling.utils import common
|
||||||
from qinling.utils import constants
|
from qinling.utils import constants
|
||||||
from qinling.utils import etcd_util
|
from qinling.utils import etcd_util
|
||||||
from qinling.utils.openstack import keystone as keystone_util
|
from qinling.utils.openstack import keystone as keystone_util
|
||||||
|
@ -45,7 +46,8 @@ CONF = cfg.CONF
|
||||||
|
|
||||||
POST_REQUIRED = set(['code'])
|
POST_REQUIRED = set(['code'])
|
||||||
CODE_SOURCE = set(['package', 'swift', 'image'])
|
CODE_SOURCE = set(['package', 'swift', 'image'])
|
||||||
UPDATE_ALLOWED = set(['name', 'description', 'code', 'package', 'entry'])
|
UPDATE_ALLOWED = set(['name', 'description', 'code', 'package', 'entry',
|
||||||
|
'cpu', 'memory_size'])
|
||||||
|
|
||||||
|
|
||||||
class FunctionWorkerController(rest.RestController):
|
class FunctionWorkerController(rest.RestController):
|
||||||
|
@ -147,8 +149,22 @@ class FunctionsController(rest.RestController):
|
||||||
'runtime_id': kwargs.get('runtime_id'),
|
'runtime_id': kwargs.get('runtime_id'),
|
||||||
'code': json.loads(kwargs['code']),
|
'code': json.loads(kwargs['code']),
|
||||||
'entry': kwargs.get('entry', 'main.main'),
|
'entry': kwargs.get('entry', 'main.main'),
|
||||||
|
'cpu': kwargs.get('cpu', CONF.resource_limits.default_cpu),
|
||||||
|
'memory_size': kwargs.get(
|
||||||
|
'memory_size', CONF.resource_limits.default_memory
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Check cpu and memory_size values.
|
||||||
|
common.validate_int_in_range(
|
||||||
|
'cpu', values['cpu'], CONF.resource_limits.min_cpu,
|
||||||
|
CONF.resource_limits.max_cpu
|
||||||
|
)
|
||||||
|
common.validate_int_in_range(
|
||||||
|
'memory', values['memory_size'], CONF.resource_limits.min_memory,
|
||||||
|
CONF.resource_limits.max_memory
|
||||||
|
)
|
||||||
|
|
||||||
source = values['code'].get('source')
|
source = values['code'].get('source')
|
||||||
if not source or source not in CODE_SOURCE:
|
if not source or source not in CODE_SOURCE:
|
||||||
raise exc.InputException(
|
raise exc.InputException(
|
||||||
|
@ -302,6 +318,22 @@ class FunctionsController(rest.RestController):
|
||||||
else:
|
else:
|
||||||
source = values.get('code', {}).get('source')
|
source = values.get('code', {}).get('source')
|
||||||
md5sum = values.get('code', {}).get('md5sum')
|
md5sum = values.get('code', {}).get('md5sum')
|
||||||
|
cpu = values.get('cpu')
|
||||||
|
memory_size = values.get('memory_size')
|
||||||
|
|
||||||
|
# Check cpu and memory_size values when updating.
|
||||||
|
if cpu is not None:
|
||||||
|
common.validate_int_in_range(
|
||||||
|
'cpu', values['cpu'], CONF.resource_limits.min_cpu,
|
||||||
|
CONF.resource_limits.max_cpu
|
||||||
|
)
|
||||||
|
if memory_size is not None:
|
||||||
|
common.validate_int_in_range(
|
||||||
|
'memory', values['memory_size'],
|
||||||
|
CONF.resource_limits.min_memory,
|
||||||
|
CONF.resource_limits.max_memory
|
||||||
|
)
|
||||||
|
|
||||||
with db_api.transaction():
|
with db_api.transaction():
|
||||||
pre_func = db_api.get_function(id)
|
pre_func = db_api.get_function(id)
|
||||||
|
|
||||||
|
|
|
@ -164,6 +164,7 @@ class Function(Resource):
|
||||||
id = wtypes.text
|
id = wtypes.text
|
||||||
name = wtypes.text
|
name = wtypes.text
|
||||||
description = wtypes.text
|
description = wtypes.text
|
||||||
|
cpu = int
|
||||||
memory_size = int
|
memory_size = int
|
||||||
timeout = int
|
timeout = int
|
||||||
runtime_id = wsme.wsattr(types.uuid, readonly=True)
|
runtime_id = wsme.wsattr(types.uuid, readonly=True)
|
||||||
|
@ -181,6 +182,7 @@ class Function(Resource):
|
||||||
id='123e4567-e89b-12d3-a456-426655440000',
|
id='123e4567-e89b-12d3-a456-426655440000',
|
||||||
name='hello_world',
|
name='hello_world',
|
||||||
description='this is the first function.',
|
description='this is the first function.',
|
||||||
|
cpu=1,
|
||||||
memory_size=1,
|
memory_size=1,
|
||||||
timeout=1,
|
timeout=1,
|
||||||
runtime_id='123e4567-e89b-12d3-a456-426655440001',
|
runtime_id='123e4567-e89b-12d3-a456-426655440001',
|
||||||
|
|
|
@ -184,6 +184,40 @@ etcd_opts = [
|
||||||
cfg.PortOpt('port', default=2379, help='Etcd service port.'),
|
cfg.PortOpt('port', default=2379, help='Etcd service port.'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
RLIMITS_GROUP = 'resource_limits'
|
||||||
|
rlimits_opts = [
|
||||||
|
cfg.IntOpt(
|
||||||
|
'default_cpu',
|
||||||
|
default=100,
|
||||||
|
help='Default cpu resource(unit: millicpu).'
|
||||||
|
),
|
||||||
|
cfg.IntOpt(
|
||||||
|
'min_cpu',
|
||||||
|
default=100,
|
||||||
|
help='Minimum cpu resource(unit: millicpu).'
|
||||||
|
),
|
||||||
|
cfg.IntOpt(
|
||||||
|
'max_cpu',
|
||||||
|
default=300,
|
||||||
|
help='Maximum cpu resource(unit: millicpu).'
|
||||||
|
),
|
||||||
|
cfg.IntOpt(
|
||||||
|
'default_memory',
|
||||||
|
default=33554432,
|
||||||
|
help='Default memory resource(unit: bytes).'
|
||||||
|
),
|
||||||
|
cfg.IntOpt(
|
||||||
|
'min_memory',
|
||||||
|
default=33554432,
|
||||||
|
help='Minimum memory resource(unit: bytes).'
|
||||||
|
),
|
||||||
|
cfg.IntOpt(
|
||||||
|
'max_memory',
|
||||||
|
default=134217728,
|
||||||
|
help='Maximum memory resource(unit: bytes).'
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def list_opts():
|
def list_opts():
|
||||||
keystone_middleware_opts = auth_token.list_opts()
|
keystone_middleware_opts = auth_token.list_opts()
|
||||||
|
@ -198,6 +232,7 @@ def list_opts():
|
||||||
(STORAGE_GROUP, storage_opts),
|
(STORAGE_GROUP, storage_opts),
|
||||||
(KUBERNETES_GROUP, kubernetes_opts),
|
(KUBERNETES_GROUP, kubernetes_opts),
|
||||||
(ETCD_GROUP, etcd_opts),
|
(ETCD_GROUP, etcd_opts),
|
||||||
|
(RLIMITS_GROUP, rlimits_opts),
|
||||||
(None, [launch_opt]),
|
(None, [launch_opt]),
|
||||||
(None, default_opts),
|
(None, default_opts),
|
||||||
]
|
]
|
||||||
|
|
|
@ -17,6 +17,7 @@ import json
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
from qinling import status
|
from qinling import status
|
||||||
from qinling.tests.unit.api import base
|
from qinling.tests.unit.api import base
|
||||||
|
@ -97,6 +98,8 @@ class TestFunctionController(base.APITest):
|
||||||
"name": db_func.name,
|
"name": db_func.name,
|
||||||
'entry': 'main.main',
|
'entry': 'main.main',
|
||||||
"project_id": unit_base.DEFAULT_PROJECT_ID,
|
"project_id": unit_base.DEFAULT_PROJECT_ID,
|
||||||
|
"cpu": cfg.CONF.resource_limits.default_cpu,
|
||||||
|
"memory_size": cfg.CONF.resource_limits.default_memory,
|
||||||
}
|
}
|
||||||
|
|
||||||
resp = self.app.get('/v1/functions/%s' % db_func.id)
|
resp = self.app.get('/v1/functions/%s' % db_func.id)
|
||||||
|
@ -111,6 +114,8 @@ class TestFunctionController(base.APITest):
|
||||||
"name": db_func.name,
|
"name": db_func.name,
|
||||||
'entry': 'main.main',
|
'entry': 'main.main',
|
||||||
"project_id": unit_base.DEFAULT_PROJECT_ID,
|
"project_id": unit_base.DEFAULT_PROJECT_ID,
|
||||||
|
"cpu": cfg.CONF.resource_limits.default_cpu,
|
||||||
|
"memory_size": cfg.CONF.resource_limits.default_memory,
|
||||||
}
|
}
|
||||||
|
|
||||||
resp = self.app.get('/v1/functions')
|
resp = self.app.get('/v1/functions')
|
||||||
|
@ -173,6 +178,63 @@ class TestFunctionController(base.APITest):
|
||||||
|
|
||||||
self.assertEqual(400, resp.status_int)
|
self.assertEqual(400, resp.status_int)
|
||||||
|
|
||||||
|
def test_put_cpu_with_type_error(self):
|
||||||
|
db_func = self.create_function(runtime_id=self.runtime_id)
|
||||||
|
|
||||||
|
# Check for type of cpu values.
|
||||||
|
resp = self.app.put_json(
|
||||||
|
'/v1/functions/%s' % db_func.id, {'cpu': 'non-int'},
|
||||||
|
expect_errors=True
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(400, resp.status_int)
|
||||||
|
self.assertIn(
|
||||||
|
'Invalid cpu resource specified. An integer is required.',
|
||||||
|
resp.json['faultstring']
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_put_cpu_with_overrun_error(self):
|
||||||
|
db_func = self.create_function(runtime_id=self.runtime_id)
|
||||||
|
|
||||||
|
# Check for cpu error with input out of range.
|
||||||
|
resp = self.app.put_json(
|
||||||
|
'/v1/functions/%s' % db_func.id, {'cpu': 0},
|
||||||
|
expect_errors=True
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(400, resp.status_int)
|
||||||
|
self.assertIn(
|
||||||
|
'cpu resource limitation not within the allowable range',
|
||||||
|
resp.json['faultstring']
|
||||||
|
)
|
||||||
|
|
||||||
|
@mock.patch('qinling.utils.etcd_util.delete_function')
|
||||||
|
@mock.patch('qinling.rpc.EngineClient.delete_function')
|
||||||
|
def test_put_cpu_and_memorysize(
|
||||||
|
self, mock_delete_func, mock_etcd_del
|
||||||
|
):
|
||||||
|
# Test for updating cpu/mem with good input values.
|
||||||
|
db_func = self.create_function(runtime_id=self.runtime_id)
|
||||||
|
|
||||||
|
req_body = {
|
||||||
|
'cpu': str(cfg.CONF.resource_limits.default_cpu + 1),
|
||||||
|
'memory_size': str(cfg.CONF.resource_limits.default_memory + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = self.app.put_json('/v1/functions/%s' % db_func.id, req_body)
|
||||||
|
|
||||||
|
self.assertEqual(200, resp.status_int)
|
||||||
|
self.assertEqual(
|
||||||
|
cfg.CONF.resource_limits.default_cpu + 1,
|
||||||
|
resp.json['cpu']
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
cfg.CONF.resource_limits.default_memory + 1,
|
||||||
|
resp.json['memory_size']
|
||||||
|
)
|
||||||
|
mock_delete_func.assert_called_once_with(db_func.id)
|
||||||
|
mock_etcd_del.assert_called_once_with(db_func.id)
|
||||||
|
|
||||||
@mock.patch('qinling.utils.etcd_util.delete_function')
|
@mock.patch('qinling.utils.etcd_util.delete_function')
|
||||||
@mock.patch('qinling.rpc.EngineClient.delete_function')
|
@mock.patch('qinling.rpc.EngineClient.delete_function')
|
||||||
@mock.patch('qinling.storage.file_system.FileSystemStorage.delete')
|
@mock.patch('qinling.storage.file_system.FileSystemStorage.delete')
|
||||||
|
|
|
@ -139,6 +139,7 @@ class DbTestCase(BaseTest):
|
||||||
(config.STORAGE_GROUP, config.storage_opts),
|
(config.STORAGE_GROUP, config.storage_opts),
|
||||||
(config.KUBERNETES_GROUP, config.kubernetes_opts),
|
(config.KUBERNETES_GROUP, config.kubernetes_opts),
|
||||||
(config.ETCD_GROUP, config.etcd_opts),
|
(config.ETCD_GROUP, config.etcd_opts),
|
||||||
|
(config.RLIMITS_GROUP, config.rlimits_opts),
|
||||||
(None, [config.launch_opt]),
|
(None, [config.launch_opt]),
|
||||||
(None, config.default_opts)
|
(None, config.default_opts)
|
||||||
]
|
]
|
||||||
|
@ -193,6 +194,8 @@ class DbTestCase(BaseTest):
|
||||||
# 'auth_enable' is disabled by default, we create runtime for
|
# 'auth_enable' is disabled by default, we create runtime for
|
||||||
# default tenant.
|
# default tenant.
|
||||||
'project_id': DEFAULT_PROJECT_ID,
|
'project_id': DEFAULT_PROJECT_ID,
|
||||||
|
'cpu': cfg.CONF.resource_limits.default_cpu,
|
||||||
|
'memory_size': cfg.CONF.resource_limits.default_memory,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ import warnings
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
from qinling import exceptions as exc
|
||||||
from qinling import version
|
from qinling import version
|
||||||
|
|
||||||
|
|
||||||
|
@ -91,6 +92,22 @@ def generate_unicode_uuid(dashed=True):
|
||||||
return uuidutils.generate_uuid(dashed=dashed)
|
return uuidutils.generate_uuid(dashed=dashed)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_int_in_range(name, value, min_allowed, max_allowed):
|
||||||
|
try:
|
||||||
|
value_int = int(value)
|
||||||
|
except ValueError:
|
||||||
|
raise exc.InputException(
|
||||||
|
'Invalid %s resource specified. An integer is required.' % name
|
||||||
|
)
|
||||||
|
|
||||||
|
if (value_int < min_allowed or value_int > max_allowed):
|
||||||
|
raise exc.InputException(
|
||||||
|
'%s resource limitation not within the allowable range: '
|
||||||
|
'%s ~ %s(%s).' % (name, min_allowed, max_allowed,
|
||||||
|
'millicpu' if name == 'cpu' else 'bytes')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def disable_ssl_warnings(func):
|
def disable_ssl_warnings(func):
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
|
|
Loading…
Reference in New Issue