Merge "The framework of dynamic pod binding"
This commit is contained in:
commit
b2c436bfca
@ -56,3 +56,8 @@ oslo.config.opts =
|
|||||||
|
|
||||||
tempest.test_plugins =
|
tempest.test_plugins =
|
||||||
trio2o_tests = trio2o.tempestplugin.plugin:Trio2oTempestPlugin
|
trio2o_tests = trio2o.tempestplugin.plugin:Trio2oTempestPlugin
|
||||||
|
|
||||||
|
trio2o.common.schedulers =
|
||||||
|
pod_manager = trio2o.common.scheduler.pod_manager:PodManager
|
||||||
|
bottom_pod_filter = trio2o.common.scheduler.filters.bottom_pod_filter:BottomPodFilter
|
||||||
|
filter_scheduler = trio2o.common.scheduler.filter_scheduler:FilterScheduler
|
@ -223,7 +223,6 @@ class BindingsController(rest.RestController):
|
|||||||
pod_b = kw['pod_binding']
|
pod_b = kw['pod_binding']
|
||||||
tenant_id = pod_b.get('tenant_id', '').strip()
|
tenant_id = pod_b.get('tenant_id', '').strip()
|
||||||
pod_id = pod_b.get('pod_id', '').strip()
|
pod_id = pod_b.get('pod_id', '').strip()
|
||||||
_uuid = uuidutils.generate_uuid()
|
|
||||||
|
|
||||||
if tenant_id == '' or pod_id == '':
|
if tenant_id == '' or pod_id == '':
|
||||||
return Response(
|
return Response(
|
||||||
@ -249,11 +248,7 @@ class BindingsController(rest.RestController):
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with context.session.begin():
|
pod_binding = db_api.create_pod_binding(context, tenant_id, pod_id)
|
||||||
pod_binding = core.create_resource(context, models.PodBinding,
|
|
||||||
{'id': _uuid,
|
|
||||||
'tenant_id': tenant_id,
|
|
||||||
'pod_id': pod_id})
|
|
||||||
except db_exc.DBDuplicateEntry:
|
except db_exc.DBDuplicateEntry:
|
||||||
return Response(_('Pod binding already exists'), 409)
|
return Response(_('Pod binding already exists'), 409)
|
||||||
except db_exc.DBConstraintError:
|
except db_exc.DBConstraintError:
|
||||||
|
@ -29,6 +29,7 @@ import trio2o.common.context as t_context
|
|||||||
from trio2o.common import httpclient as hclient
|
from trio2o.common import httpclient as hclient
|
||||||
from trio2o.common.i18n import _
|
from trio2o.common.i18n import _
|
||||||
from trio2o.common.i18n import _LE
|
from trio2o.common.i18n import _LE
|
||||||
|
from trio2o.common.scheduler import filter_scheduler
|
||||||
from trio2o.common import utils
|
from trio2o.common import utils
|
||||||
|
|
||||||
import trio2o.db.api as db_api
|
import trio2o.db.api as db_api
|
||||||
@ -42,6 +43,7 @@ class VolumeController(rest.RestController):
|
|||||||
|
|
||||||
def __init__(self, tenant_id):
|
def __init__(self, tenant_id):
|
||||||
self.tenant_id = tenant_id
|
self.tenant_id = tenant_id
|
||||||
|
self.filter_scheduler = filter_scheduler.FilterScheduler()
|
||||||
|
|
||||||
@expose(generic=True, template='json')
|
@expose(generic=True, template='json')
|
||||||
def post(self, **kw):
|
def post(self, **kw):
|
||||||
@ -52,10 +54,9 @@ class VolumeController(rest.RestController):
|
|||||||
400, _("Missing required element 'volume' in request body."))
|
400, _("Missing required element 'volume' in request body."))
|
||||||
|
|
||||||
az = kw['volume'].get('availability_zone', '')
|
az = kw['volume'].get('availability_zone', '')
|
||||||
pod, pod_az = az_ag.get_pod_by_az_tenant(
|
pod, pod_az = self.filter_scheduler.select_destination(
|
||||||
context,
|
context, az, self.tenant_id, pod_group='')
|
||||||
az_name=az,
|
|
||||||
tenant_id=self.tenant_id)
|
|
||||||
if not pod:
|
if not pod:
|
||||||
LOG.error(_LE("Pod not configured or scheduling failure"))
|
LOG.error(_LE("Pod not configured or scheduling failure"))
|
||||||
return utils.format_cinder_error(
|
return utils.format_cinder_error(
|
||||||
|
@ -147,7 +147,8 @@ def get_pod_by_az_tenant(context, az_name, tenant_id):
|
|||||||
context, models.PodBinding,
|
context, models.PodBinding,
|
||||||
{'id': uuidutils.generate_uuid(),
|
{'id': uuidutils.generate_uuid(),
|
||||||
'tenant_id': tenant_id,
|
'tenant_id': tenant_id,
|
||||||
'pod_id': pod['pod_id']})
|
'pod_id': pod['pod_id'],
|
||||||
|
'is_binding': True})
|
||||||
return pod, pod['pod_az_name']
|
return pod, pod['pod_az_name']
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error(_LE('Fail to create pod binding: %(exception)s'),
|
LOG.error(_LE('Fail to create pod binding: %(exception)s'),
|
||||||
|
0
trio2o/common/scheduler/__init__.py
Normal file
0
trio2o/common/scheduler/__init__.py
Normal file
31
trio2o/common/scheduler/driver.py
Normal file
31
trio2o/common/scheduler/driver.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
import abc
|
||||||
|
import six
|
||||||
|
|
||||||
|
from stevedore import driver
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class Scheduler(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.pod_manager = driver.DriverManager(
|
||||||
|
namespace='trio2o.common.schedulers',
|
||||||
|
name='pod_manager',
|
||||||
|
invoke_on_load=True
|
||||||
|
).driver
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def select_destination(self, context, az_name, tenant_id, spec_obj):
|
||||||
|
return None, None
|
58
trio2o/common/scheduler/filter_scheduler.py
Normal file
58
trio2o/common/scheduler/filter_scheduler.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
from trio2o.common.scheduler import driver
|
||||||
|
|
||||||
|
|
||||||
|
class FilterScheduler(driver.Scheduler):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(FilterScheduler, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def select_destination(self, context, az_name, tenant_id, pod_group):
|
||||||
|
current_binding, current_pod = \
|
||||||
|
self.pod_manager.get_current_binding_and_pod(
|
||||||
|
context, az_name, tenant_id, pod_group)
|
||||||
|
|
||||||
|
if current_binding and current_pod:
|
||||||
|
return current_pod, current_pod['pod_az_name']
|
||||||
|
else:
|
||||||
|
pods = self.pod_manager.get_available_pods(
|
||||||
|
context, az_name, pod_group)
|
||||||
|
if not pods:
|
||||||
|
return None, None
|
||||||
|
# TODO(Yipei): Weigh pods and select one whose weight
|
||||||
|
# is the maximum. Here we chose one randomly.
|
||||||
|
is_current = False
|
||||||
|
best_pod = None
|
||||||
|
# select the pod by a circle in pods
|
||||||
|
for pod in pods:
|
||||||
|
if is_current:
|
||||||
|
best_pod = pod
|
||||||
|
break
|
||||||
|
if current_binding \
|
||||||
|
and pod['pod_id'] == current_binding['pod_id']:
|
||||||
|
is_current = True
|
||||||
|
if is_current and len(pods) == 1:
|
||||||
|
return None, None
|
||||||
|
if not best_pod:
|
||||||
|
best_pod = pods[0]
|
||||||
|
|
||||||
|
if current_binding:
|
||||||
|
is_successful = self.pod_manager.update_binding(
|
||||||
|
context, current_binding, best_pod['pod_id'])
|
||||||
|
else:
|
||||||
|
is_successful = self.pod_manager.create_binding(
|
||||||
|
context, tenant_id, best_pod['pod_id'])
|
||||||
|
if not is_successful:
|
||||||
|
return None, None
|
||||||
|
return best_pod, best_pod['pod_az_name']
|
0
trio2o/common/scheduler/filters/__init__.py
Normal file
0
trio2o/common/scheduler/filters/__init__.py
Normal file
31
trio2o/common/scheduler/filters/base_filters.py
Normal file
31
trio2o/common/scheduler/filters/base_filters.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
|
class BaseFilter(object):
|
||||||
|
"""Base class for all pod filter classes."""
|
||||||
|
def _filter_one(self, obj, pod_group):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def filter_all(self, filter_obj_list, pod_group):
|
||||||
|
for obj in filter_obj_list:
|
||||||
|
if self._filter_one(obj, pod_group):
|
||||||
|
yield obj
|
||||||
|
|
||||||
|
|
||||||
|
class BasePodFilter(BaseFilter):
|
||||||
|
|
||||||
|
def _filter_one(self, obj, pod_group):
|
||||||
|
return self.is_pod_passed(obj, pod_group)
|
||||||
|
|
||||||
|
def is_pod_passed(self, pod, pod_group):
|
||||||
|
raise NotImplementedError()
|
23
trio2o/common/scheduler/filters/bottom_pod_filter.py
Normal file
23
trio2o/common/scheduler/filters/bottom_pod_filter.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
from trio2o.common.scheduler.filters import base_filters
|
||||||
|
|
||||||
|
|
||||||
|
class BottomPodFilter(base_filters.BasePodFilter):
|
||||||
|
"""Returns all bottom pods."""
|
||||||
|
|
||||||
|
def is_pod_passed(self, pod, pod_group):
|
||||||
|
flag = False
|
||||||
|
if pod['az_name'] != '':
|
||||||
|
flag = True
|
||||||
|
return flag
|
109
trio2o/common/scheduler/pod_manager.py
Normal file
109
trio2o/common/scheduler/pod_manager.py
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
from stevedore import driver
|
||||||
|
|
||||||
|
from trio2o.common.i18n import _LE
|
||||||
|
from trio2o.db import api as db_api
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PodManager(object):
|
||||||
|
def __init__(self):
|
||||||
|
filter_names = ['bottom_pod_filter']
|
||||||
|
self.default_filters = self._choose_pod_filters(filter_names)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _choose_pod_filters(filter_names):
|
||||||
|
good_filters = []
|
||||||
|
for filter_name in filter_names:
|
||||||
|
filter_ = driver.DriverManager(
|
||||||
|
'trio2o.common.schedulers',
|
||||||
|
filter_name,
|
||||||
|
invoke_on_load=True
|
||||||
|
).driver
|
||||||
|
good_filters.append(filter_)
|
||||||
|
return good_filters
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_current_binding_and_pod(context, az_name, tenant_id, pod_group):
|
||||||
|
filter_b = [{'key': 'tenant_id', 'comparator': 'eq',
|
||||||
|
'value': tenant_id}]
|
||||||
|
current_bindings = db_api.get_pod_binding_by_tenant_id(
|
||||||
|
context, filter_b)
|
||||||
|
if not current_bindings:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
has_available_pods = False
|
||||||
|
for pod_b in current_bindings:
|
||||||
|
if pod_b['is_binding']:
|
||||||
|
pod = db_api.get_pod_by_pod_id(context, pod_b['pod_id'])
|
||||||
|
if az_name and pod['az_name'] == az_name:
|
||||||
|
has_available_pods = True
|
||||||
|
elif az_name == '' and pod['az_name'] != '':
|
||||||
|
# if the az_name is not specified, a default bottom
|
||||||
|
# pod will be selected
|
||||||
|
has_available_pods = True
|
||||||
|
|
||||||
|
if has_available_pods:
|
||||||
|
# TODO(Yipei): check resource_affinity_tag
|
||||||
|
# if the resource utilization of the pod reaches the limit,
|
||||||
|
# return [], []. Considering the feature of checking
|
||||||
|
# resource utilization is not implemented, we use
|
||||||
|
# resource_affinity_tag to test the logic of updating
|
||||||
|
# a binding relationship.
|
||||||
|
if pod_group != '':
|
||||||
|
return pod_b, None
|
||||||
|
# TODO(Yipei): check resource utilization of the pod
|
||||||
|
# if the resource utilization of the pod reaches the limit,
|
||||||
|
# return pod_b, []
|
||||||
|
|
||||||
|
# If a pod passes the above checking, both the pod and its
|
||||||
|
# corresponding binding are returned.
|
||||||
|
return pod_b, pod
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_binding(context, tenant_id, pod_id):
|
||||||
|
try:
|
||||||
|
db_api.create_pod_binding(context, tenant_id, pod_id)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(_LE('Fail to create pod binding: %(exception)s'),
|
||||||
|
{'exception': e})
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def update_binding(context, current_binding, pod_id):
|
||||||
|
current_binding['is_binding'] = False
|
||||||
|
try:
|
||||||
|
db_api.change_pod_binding(
|
||||||
|
context, current_binding, pod_id)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(_LE('Fail to update pod binding: %(exception)s'),
|
||||||
|
{'exception': e})
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_available_pods(self, context, az_name, pod_group):
|
||||||
|
if az_name != '':
|
||||||
|
filter_q = [{'key': 'az_name',
|
||||||
|
'comparator': 'eq', 'value': az_name}]
|
||||||
|
else:
|
||||||
|
filter_q = None
|
||||||
|
pods = db_api.list_pods(context, filter_q)
|
||||||
|
for filter_ in self.default_filters:
|
||||||
|
objs_ = filter_.filter_all(pods, pod_group)
|
||||||
|
pods = list(objs_)
|
||||||
|
return pods
|
@ -67,12 +67,42 @@ def update_pod(context, pod_id, update_dict):
|
|||||||
return core.update_resource(context, models.Pod, pod_id, update_dict)
|
return core.update_resource(context, models.Pod, pod_id, update_dict)
|
||||||
|
|
||||||
|
|
||||||
|
def change_pod_binding(context, pod_binding, pod_id):
|
||||||
|
with context.session.begin():
|
||||||
|
core.update_resource(context, models.PodBinding,
|
||||||
|
pod_binding['id'], pod_binding)
|
||||||
|
core.create_resource(context, models.PodBinding,
|
||||||
|
{'id': uuidutils.generate_uuid(),
|
||||||
|
'tenant_id': pod_binding['tenant_id'],
|
||||||
|
'pod_id': pod_id,
|
||||||
|
'is_binding': True})
|
||||||
|
|
||||||
|
|
||||||
|
def get_pod_binding_by_tenant_id(context, filter_):
|
||||||
|
with context.session.begin():
|
||||||
|
return core.query_resource(context, models.PodBinding, filter_, [])
|
||||||
|
|
||||||
|
|
||||||
|
def get_pod_by_pod_id(context, pod_id):
|
||||||
|
with context.session.begin():
|
||||||
|
return core.get_resource(context, models.Pod, pod_id)
|
||||||
|
|
||||||
|
|
||||||
def create_pod_service_configuration(context, config_dict):
|
def create_pod_service_configuration(context, config_dict):
|
||||||
with context.session.begin():
|
with context.session.begin():
|
||||||
return core.create_resource(context, models.PodServiceConfiguration,
|
return core.create_resource(context, models.PodServiceConfiguration,
|
||||||
config_dict)
|
config_dict)
|
||||||
|
|
||||||
|
|
||||||
|
def create_pod_binding(context, tenant_id, pod_id):
|
||||||
|
with context.session.begin():
|
||||||
|
return core.create_resource(context, models.PodBinding,
|
||||||
|
{'id': uuidutils.generate_uuid(),
|
||||||
|
'tenant_id': tenant_id,
|
||||||
|
'pod_id': pod_id,
|
||||||
|
'is_binding': True})
|
||||||
|
|
||||||
|
|
||||||
def delete_pod_service_configuration(context, config_id):
|
def delete_pod_service_configuration(context, config_id):
|
||||||
with context.session.begin():
|
with context.session.begin():
|
||||||
return core.delete_resource(context, models.PodServiceConfiguration,
|
return core.delete_resource(context, models.PodServiceConfiguration,
|
||||||
|
@ -47,6 +47,7 @@ def upgrade(migrate_engine):
|
|||||||
sql.Column('id', sql.String(36), primary_key=True),
|
sql.Column('id', sql.String(36), primary_key=True),
|
||||||
sql.Column('tenant_id', sql.String(length=255), nullable=False),
|
sql.Column('tenant_id', sql.String(length=255), nullable=False),
|
||||||
sql.Column('pod_id', sql.String(length=255), nullable=False),
|
sql.Column('pod_id', sql.String(length=255), nullable=False),
|
||||||
|
sql.Column('is_binding', sql.Boolean, nullable=False),
|
||||||
sql.Column('created_at', sql.DateTime),
|
sql.Column('created_at', sql.DateTime),
|
||||||
sql.Column('updated_at', sql.DateTime),
|
sql.Column('updated_at', sql.DateTime),
|
||||||
migrate.UniqueConstraint(
|
migrate.UniqueConstraint(
|
||||||
|
@ -420,7 +420,7 @@ class PodBinding(core.ModelBase, core.DictBase, models.TimestampMixin):
|
|||||||
'tenant_id', 'pod_id',
|
'tenant_id', 'pod_id',
|
||||||
name='pod_binding0tenant_id0pod_id'),
|
name='pod_binding0tenant_id0pod_id'),
|
||||||
)
|
)
|
||||||
attributes = ['id', 'tenant_id', 'pod_id',
|
attributes = ['id', 'tenant_id', 'pod_id', 'is_binding',
|
||||||
'created_at', 'updated_at']
|
'created_at', 'updated_at']
|
||||||
|
|
||||||
id = sql.Column(sql.String(36), primary_key=True)
|
id = sql.Column(sql.String(36), primary_key=True)
|
||||||
@ -428,6 +428,7 @@ class PodBinding(core.ModelBase, core.DictBase, models.TimestampMixin):
|
|||||||
pod_id = sql.Column('pod_id', sql.String(36),
|
pod_id = sql.Column('pod_id', sql.String(36),
|
||||||
sql.ForeignKey('cascaded_pods.pod_id'),
|
sql.ForeignKey('cascaded_pods.pod_id'),
|
||||||
nullable=False)
|
nullable=False)
|
||||||
|
is_binding = sql.Column('is_binding', sql.Boolean, nullable=False)
|
||||||
|
|
||||||
|
|
||||||
# Routing Model
|
# Routing Model
|
||||||
|
@ -20,7 +20,6 @@ import six
|
|||||||
|
|
||||||
import oslo_log.log as logging
|
import oslo_log.log as logging
|
||||||
|
|
||||||
from trio2o.common import az_ag
|
|
||||||
import trio2o.common.client as t_client
|
import trio2o.common.client as t_client
|
||||||
from trio2o.common import constants
|
from trio2o.common import constants
|
||||||
import trio2o.common.context as t_context
|
import trio2o.common.context as t_context
|
||||||
@ -29,12 +28,14 @@ from trio2o.common.i18n import _
|
|||||||
from trio2o.common.i18n import _LE
|
from trio2o.common.i18n import _LE
|
||||||
import trio2o.common.lock_handle as t_lock
|
import trio2o.common.lock_handle as t_lock
|
||||||
from trio2o.common.quota import QUOTAS
|
from trio2o.common.quota import QUOTAS
|
||||||
|
from trio2o.common.scheduler import filter_scheduler
|
||||||
from trio2o.common import utils
|
from trio2o.common import utils
|
||||||
from trio2o.common import xrpcapi
|
from trio2o.common import xrpcapi
|
||||||
import trio2o.db.api as db_api
|
import trio2o.db.api as db_api
|
||||||
from trio2o.db import core
|
from trio2o.db import core
|
||||||
from trio2o.db import models
|
from trio2o.db import models
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
MAX_METADATA_KEY_LENGTH = 255
|
MAX_METADATA_KEY_LENGTH = 255
|
||||||
@ -47,6 +48,7 @@ class ServerController(rest.RestController):
|
|||||||
self.project_id = project_id
|
self.project_id = project_id
|
||||||
self.clients = {constants.TOP: t_client.Client()}
|
self.clients = {constants.TOP: t_client.Client()}
|
||||||
self.xjob_handler = xrpcapi.XJobAPI()
|
self.xjob_handler = xrpcapi.XJobAPI()
|
||||||
|
self.filter_scheduler = filter_scheduler.FilterScheduler()
|
||||||
|
|
||||||
def _get_client(self, pod_name=constants.TOP):
|
def _get_client(self, pod_name=constants.TOP):
|
||||||
if pod_name not in self.clients:
|
if pod_name not in self.clients:
|
||||||
@ -112,9 +114,9 @@ class ServerController(rest.RestController):
|
|||||||
400, _('server is not set'))
|
400, _('server is not set'))
|
||||||
|
|
||||||
az = kw['server'].get('availability_zone', '')
|
az = kw['server'].get('availability_zone', '')
|
||||||
|
pod, b_az = self.filter_scheduler.select_destination(
|
||||||
|
context, az, self.project_id, pod_group='')
|
||||||
|
|
||||||
pod, b_az = az_ag.get_pod_by_az_tenant(
|
|
||||||
context, az, self.project_id)
|
|
||||||
if not pod:
|
if not pod:
|
||||||
return utils.format_nova_error(
|
return utils.format_nova_error(
|
||||||
500, _('Pod not configured or scheduling failure'))
|
500, _('Pod not configured or scheduling failure'))
|
||||||
|
@ -613,7 +613,8 @@ class TestBindingController(API_FunctionalTest):
|
|||||||
"pod_binding":
|
"pod_binding":
|
||||||
{
|
{
|
||||||
"tenant_id": "dddddd",
|
"tenant_id": "dddddd",
|
||||||
"pod_id": "0ace0db2-ef33-43a6-a150-42703ffda643"
|
"pod_id": "0ace0db2-ef33-43a6-a150-42703ffda643",
|
||||||
|
"is_binding": "True"
|
||||||
},
|
},
|
||||||
"expected_error": 200
|
"expected_error": 200
|
||||||
},
|
},
|
||||||
@ -622,7 +623,8 @@ class TestBindingController(API_FunctionalTest):
|
|||||||
"pod_binding":
|
"pod_binding":
|
||||||
{
|
{
|
||||||
"tenant_id": "aaaaa",
|
"tenant_id": "aaaaa",
|
||||||
"pod_id": "0ace0db2-ef33-43a6-a150-42703ffda643"
|
"pod_id": "0ace0db2-ef33-43a6-a150-42703ffda643",
|
||||||
|
"is_binding": "True"
|
||||||
},
|
},
|
||||||
"expected_error": 200
|
"expected_error": 200
|
||||||
},
|
},
|
||||||
|
0
trio2o/tests/unit/common/scheduler/__init__.py
Normal file
0
trio2o/tests/unit/common/scheduler/__init__.py
Normal file
141
trio2o/tests/unit/common/scheduler/test_filter_scheduler.py
Normal file
141
trio2o/tests/unit/common/scheduler/test_filter_scheduler.py
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
from trio2o.common import context
|
||||||
|
from trio2o.common.scheduler import filter_scheduler
|
||||||
|
from trio2o.db import api
|
||||||
|
from trio2o.db import core
|
||||||
|
from trio2o.db import models
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
class FilterSchedulerTest(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
core.initialize()
|
||||||
|
core.ModelBase.metadata.create_all(core.get_engine())
|
||||||
|
self.context = context.Context()
|
||||||
|
self.project_id = 'test_fs_project'
|
||||||
|
self.az_name_1 = 'b_az_fs_1'
|
||||||
|
self.az_name_2 = 'b_az_fs_2'
|
||||||
|
self.filter_scheduler = filter_scheduler.FilterScheduler()
|
||||||
|
|
||||||
|
def _prepare_binding(self, pod_id):
|
||||||
|
binding = {'tenant_id': self.project_id,
|
||||||
|
'pod_id': pod_id,
|
||||||
|
'is_binding': True}
|
||||||
|
api.create_pod_binding(self.context, self.project_id, pod_id)
|
||||||
|
return binding
|
||||||
|
|
||||||
|
def test_select_destination(self):
|
||||||
|
b_pod_1 = {'pod_id': 'b_pod_fs_uuid_1', 'pod_name': 'b_region_fs_1',
|
||||||
|
'az_name': self.az_name_1}
|
||||||
|
api.create_pod(self.context, b_pod_1)
|
||||||
|
b_pod_2 = {'pod_id': 'b_pod_fs_uuid_2', 'pod_name': 'b_region_fs_2',
|
||||||
|
'az_name': self.az_name_2}
|
||||||
|
api.create_pod(self.context, b_pod_2)
|
||||||
|
b_pod_3 = {'pod_id': 'b_pod_fs_uuid_3', 'pod_name': 'b_region_fs_3',
|
||||||
|
'az_name': self.az_name_2}
|
||||||
|
api.create_pod(self.context, b_pod_3)
|
||||||
|
|
||||||
|
t_pod = {'pod_id': 'b_pod_fs_uuid_t_pod',
|
||||||
|
'pod_name': 'b_region_fs_t_pod',
|
||||||
|
'az_name': ''}
|
||||||
|
api.create_pod(self.context, t_pod)
|
||||||
|
self._prepare_binding(b_pod_1['pod_id'])
|
||||||
|
binding_q = core.query_resource(
|
||||||
|
self.context, models.PodBinding, [{'key': 'tenant_id',
|
||||||
|
'comparator': 'eq',
|
||||||
|
'value': self.project_id}], [])
|
||||||
|
self.assertEqual(binding_q[0]['pod_id'], b_pod_1['pod_id'])
|
||||||
|
self.assertEqual(binding_q[0]['tenant_id'], self.project_id)
|
||||||
|
self.assertEqual(binding_q[0]['is_binding'], True)
|
||||||
|
|
||||||
|
pod_1, _ = self.filter_scheduler.select_destination(
|
||||||
|
self.context, '', self.project_id, pod_group='')
|
||||||
|
self.assertEqual(pod_1['pod_id'], b_pod_1['pod_id'])
|
||||||
|
binding_q = core.query_resource(
|
||||||
|
self.context, models.PodBinding, [{'key': 'tenant_id',
|
||||||
|
'comparator': 'eq',
|
||||||
|
'value': self.project_id}], [])
|
||||||
|
self.assertEqual(len(binding_q), 1)
|
||||||
|
self.assertEqual(binding_q[0]['pod_id'], pod_1['pod_id'])
|
||||||
|
self.assertEqual(binding_q[0]['tenant_id'], self.project_id)
|
||||||
|
self.assertEqual(binding_q[0]['is_binding'], True)
|
||||||
|
|
||||||
|
pod_2, _ = self.filter_scheduler.select_destination(
|
||||||
|
self.context, '', 'new_project', pod_group='')
|
||||||
|
binding_q = core.query_resource(
|
||||||
|
self.context, models.PodBinding, [{'key': 'tenant_id',
|
||||||
|
'comparator': 'eq',
|
||||||
|
'value': 'new_project'}], [])
|
||||||
|
self.assertEqual(len(binding_q), 1)
|
||||||
|
self.assertEqual(binding_q[0]['pod_id'], pod_2['pod_id'])
|
||||||
|
self.assertEqual(binding_q[0]['tenant_id'], 'new_project')
|
||||||
|
self.assertEqual(binding_q[0]['is_binding'], True)
|
||||||
|
|
||||||
|
pod_3, _ = self.filter_scheduler.select_destination(
|
||||||
|
self.context, self.az_name_1, 'new_project', pod_group='')
|
||||||
|
binding_q = core.query_resource(
|
||||||
|
self.context, models.PodBinding, [{'key': 'tenant_id',
|
||||||
|
'comparator': 'eq',
|
||||||
|
'value': 'new_project'}], [])
|
||||||
|
self.assertEqual(len(binding_q), 1)
|
||||||
|
self.assertEqual(binding_q[0]['pod_id'], pod_3['pod_id'])
|
||||||
|
self.assertEqual(binding_q[0]['tenant_id'], 'new_project')
|
||||||
|
self.assertEqual(binding_q[0]['is_binding'], True)
|
||||||
|
|
||||||
|
pod_4, _ = self.filter_scheduler.select_destination(
|
||||||
|
self.context, self.az_name_2, 'new_project', pod_group='')
|
||||||
|
binding_q = core.query_resource(
|
||||||
|
self.context, models.PodBinding, [{'key': 'tenant_id',
|
||||||
|
'comparator': 'eq',
|
||||||
|
'value': 'new_project'}], [])
|
||||||
|
self.assertEqual(len(binding_q), 2)
|
||||||
|
self.assertEqual(binding_q[1]['pod_id'], pod_4['pod_id'])
|
||||||
|
self.assertEqual(binding_q[1]['tenant_id'], 'new_project')
|
||||||
|
self.assertEqual(binding_q[1]['is_binding'], True)
|
||||||
|
|
||||||
|
pod_5, _ = self.filter_scheduler.select_destination(
|
||||||
|
self.context, self.az_name_2, self.project_id, pod_group='')
|
||||||
|
binding_q = core.query_resource(
|
||||||
|
self.context, models.PodBinding, [{'key': 'tenant_id',
|
||||||
|
'comparator': 'eq',
|
||||||
|
'value': self.project_id}], [])
|
||||||
|
self.assertEqual(len(binding_q), 2)
|
||||||
|
self.assertEqual(pod_5['az_name'], self.az_name_2)
|
||||||
|
self.assertEqual(binding_q[1]['pod_id'], pod_5['pod_id'])
|
||||||
|
self.assertEqual(binding_q[1]['tenant_id'], self.project_id)
|
||||||
|
self.assertEqual(binding_q[1]['is_binding'], True)
|
||||||
|
|
||||||
|
pod_6, _ = self.filter_scheduler.select_destination(
|
||||||
|
self.context, self.az_name_1, self.project_id, pod_group='test')
|
||||||
|
binding_q = core.query_resource(
|
||||||
|
self.context, models.PodBinding, [{'key': 'tenant_id',
|
||||||
|
'comparator': 'eq',
|
||||||
|
'value': self.project_id}], [])
|
||||||
|
self.assertEqual(len(binding_q), 2)
|
||||||
|
self.assertEqual(pod_6, None)
|
||||||
|
|
||||||
|
pod_7, _ = self.filter_scheduler.select_destination(
|
||||||
|
self.context, self.az_name_2, self.project_id, pod_group='test')
|
||||||
|
binding_q = core.query_resource(
|
||||||
|
self.context, models.PodBinding, [{'key': 'tenant_id',
|
||||||
|
'comparator': 'eq',
|
||||||
|
'value': self.project_id}], [])
|
||||||
|
self.assertEqual(len(binding_q), 3)
|
||||||
|
self.assertEqual(pod_7['az_name'], self.az_name_2)
|
||||||
|
self.assertEqual(binding_q[1]['tenant_id'], self.project_id)
|
||||||
|
self.assertEqual(binding_q[1]['is_binding'], False)
|
||||||
|
self.assertEqual(binding_q[2]['pod_id'], pod_7['pod_id'])
|
||||||
|
self.assertEqual(binding_q[2]['tenant_id'], self.project_id)
|
||||||
|
self.assertEqual(binding_q[2]['is_binding'], True)
|
141
trio2o/tests/unit/common/scheduler/test_pod_manager.py
Normal file
141
trio2o/tests/unit/common/scheduler/test_pod_manager.py
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
from stevedore import driver
|
||||||
|
|
||||||
|
from trio2o.common import context
|
||||||
|
from trio2o.db import api
|
||||||
|
from trio2o.db import core
|
||||||
|
from trio2o.db import models
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
class PodManagerTest(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
core.initialize()
|
||||||
|
core.ModelBase.metadata.create_all(core.get_engine())
|
||||||
|
self.context = context.Context()
|
||||||
|
self.project_id = 'test_pm_project'
|
||||||
|
self.az_name_2 = 'b_az_pm_2'
|
||||||
|
self.az_name_1 = 'b_az_pm_1'
|
||||||
|
self.pod_manager = driver.DriverManager(
|
||||||
|
namespace='trio2o.common.schedulers',
|
||||||
|
name='pod_manager',
|
||||||
|
invoke_on_load=True
|
||||||
|
).driver
|
||||||
|
self.b_pod_1 = {'pod_id': 'b_pod_pm_uuid_1',
|
||||||
|
'pod_name': 'b_region_pm_1',
|
||||||
|
'az_name': self.az_name_1}
|
||||||
|
|
||||||
|
self.b_pod_2 = {'pod_id': 'b_pod_pm_uuid_2',
|
||||||
|
'pod_name': 'b_region_pm_2',
|
||||||
|
'az_name': self.az_name_2}
|
||||||
|
|
||||||
|
self.b_pod_3 = {'pod_id': 'b_pod_pm_uuid_3',
|
||||||
|
'pod_name': 'b_region_pm_3',
|
||||||
|
'az_name': self.az_name_2}
|
||||||
|
|
||||||
|
self.b_pod_4 = {'pod_id': 'b_pod_pm_uuid_4',
|
||||||
|
'pod_name': 'b_region_pm_4',
|
||||||
|
'az_name': self.az_name_2}
|
||||||
|
|
||||||
|
def test_get_current_binding_and_pod(self):
|
||||||
|
api.create_pod(self.context, self.b_pod_1)
|
||||||
|
api.create_pod_binding(
|
||||||
|
self.context, self.project_id, self.b_pod_1['pod_id'])
|
||||||
|
|
||||||
|
pod_b_1, pod_1 = self.pod_manager.get_current_binding_and_pod(
|
||||||
|
self.context, self.az_name_1, self.project_id, pod_group='')
|
||||||
|
binding_q = core.query_resource(
|
||||||
|
self.context, models.PodBinding,
|
||||||
|
[{'key': 'tenant_id',
|
||||||
|
'comparator': 'eq',
|
||||||
|
'value': self.project_id}], [])
|
||||||
|
self.assertEqual(len(binding_q), 1)
|
||||||
|
self.assertEqual(binding_q[0]['id'], pod_b_1['id'])
|
||||||
|
|
||||||
|
pod_b_2, pod_2 = self.pod_manager.get_current_binding_and_pod(
|
||||||
|
self.context, self.az_name_1, 'new_project_pm_1', pod_group='')
|
||||||
|
binding_q = core.query_resource(
|
||||||
|
self.context, models.PodBinding,
|
||||||
|
[{'key': 'tenant_id',
|
||||||
|
'comparator': 'eq',
|
||||||
|
'value': 'new_project_pm_1'}], [])
|
||||||
|
self.assertEqual(len(binding_q), 0)
|
||||||
|
self.assertEqual(pod_b_2, None)
|
||||||
|
self.assertEqual(pod_2, None)
|
||||||
|
|
||||||
|
pod_b_3, pod_3 = self.pod_manager.get_current_binding_and_pod(
|
||||||
|
self.context, 'unknown_az', self.project_id, pod_group='')
|
||||||
|
binding_q = core.query_resource(
|
||||||
|
self.context, models.PodBinding,
|
||||||
|
[{'key': 'tenant_id',
|
||||||
|
'comparator': 'eq',
|
||||||
|
'value': self.project_id}], [])
|
||||||
|
self.assertEqual(len(binding_q), 1)
|
||||||
|
self.assertEqual(pod_b_3, None)
|
||||||
|
self.assertEqual(pod_3, None)
|
||||||
|
|
||||||
|
pod_b_4, pod_4 = self.pod_manager.get_current_binding_and_pod(
|
||||||
|
self.context, self.az_name_1, self.project_id, pod_group='test')
|
||||||
|
binding_q = core.query_resource(
|
||||||
|
self.context, models.PodBinding,
|
||||||
|
[{'key': 'tenant_id',
|
||||||
|
'comparator': 'eq',
|
||||||
|
'value': self.project_id}], [])
|
||||||
|
self.assertEqual(len(binding_q), 1)
|
||||||
|
self.assertEqual(pod_b_4['id'], binding_q[0]['id'])
|
||||||
|
self.assertEqual(pod_4, None)
|
||||||
|
|
||||||
|
def test_create_binding(self):
|
||||||
|
api.create_pod(self.context, self.b_pod_2)
|
||||||
|
flag = self.pod_manager.create_binding(
|
||||||
|
self.context, 'new_project_pm_2', self.b_pod_2['pod_id'])
|
||||||
|
self.assertEqual(flag, True)
|
||||||
|
binding_q = core.query_resource(
|
||||||
|
self.context, models.PodBinding,
|
||||||
|
[{'key': 'tenant_id',
|
||||||
|
'comparator': 'eq',
|
||||||
|
'value': 'new_project_pm_2'}], [])
|
||||||
|
self.assertEqual(len(binding_q), 1)
|
||||||
|
self.assertEqual(binding_q[0]['pod_id'], self.b_pod_2['pod_id'])
|
||||||
|
self.assertEqual(binding_q[0]['tenant_id'], 'new_project_pm_2')
|
||||||
|
self.assertEqual(binding_q[0]['is_binding'], True)
|
||||||
|
|
||||||
|
def test_update_binding(self):
|
||||||
|
api.create_pod(self.context, self.b_pod_4)
|
||||||
|
api.create_pod(self.context, self.b_pod_3)
|
||||||
|
flag = self.pod_manager.create_binding(
|
||||||
|
self.context, 'new_project_pm_3', self.b_pod_3['pod_id'])
|
||||||
|
self.assertEqual(flag, True)
|
||||||
|
current_binding = core.query_resource(
|
||||||
|
self.context, models.PodBinding,
|
||||||
|
[{'key': 'tenant_id',
|
||||||
|
'comparator': 'eq',
|
||||||
|
'value': 'new_project_pm_3'}], [])
|
||||||
|
|
||||||
|
flag = self.pod_manager.update_binding(
|
||||||
|
self.context, current_binding[0], self.b_pod_4['pod_id'])
|
||||||
|
self.assertEqual(flag, True)
|
||||||
|
binding_q = core.query_resource(
|
||||||
|
self.context, models.PodBinding,
|
||||||
|
[{'key': 'tenant_id',
|
||||||
|
'comparator': 'eq',
|
||||||
|
'value': 'new_project_pm_3'}], [])
|
||||||
|
self.assertEqual(len(binding_q), 2)
|
||||||
|
self.assertEqual(binding_q[0]['pod_id'], self.b_pod_3['pod_id'])
|
||||||
|
self.assertEqual(binding_q[0]['tenant_id'], 'new_project_pm_3')
|
||||||
|
self.assertEqual(binding_q[0]['is_binding'], False)
|
||||||
|
self.assertEqual(binding_q[1]['pod_id'], self.b_pod_4['pod_id'])
|
||||||
|
self.assertEqual(binding_q[1]['tenant_id'], 'new_project_pm_3')
|
||||||
|
self.assertEqual(binding_q[1]['is_binding'], True)
|
@ -133,6 +133,7 @@ class AZAGTest(unittest.TestCase):
|
|||||||
self.assertEqual(pod2['pod_name'], FAKE_SITE_NAME)
|
self.assertEqual(pod2['pod_name'], FAKE_SITE_NAME)
|
||||||
self.assertEqual(pod2['pod_id'], FAKE_SITE_ID)
|
self.assertEqual(pod2['pod_id'], FAKE_SITE_ID)
|
||||||
self.assertEqual(pod2['az_name'], FAKE_AZ)
|
self.assertEqual(pod2['az_name'], FAKE_AZ)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.assertEqual(pod2['pod_name'], FAKE_SITE_NAME_2)
|
self.assertEqual(pod2['pod_name'], FAKE_SITE_NAME_2)
|
||||||
self.assertEqual(pod2['pod_id'], FAKE_SITE_ID_2)
|
self.assertEqual(pod2['pod_id'], FAKE_SITE_ID_2)
|
||||||
|
@ -26,6 +26,7 @@ from trio2o.common import constants
|
|||||||
from trio2o.common import context
|
from trio2o.common import context
|
||||||
import trio2o.common.exceptions as t_exceptions
|
import trio2o.common.exceptions as t_exceptions
|
||||||
from trio2o.common import lock_handle
|
from trio2o.common import lock_handle
|
||||||
|
from trio2o.common.scheduler import filter_scheduler
|
||||||
from trio2o.common import xrpcapi
|
from trio2o.common import xrpcapi
|
||||||
from trio2o.db import api
|
from trio2o.db import api
|
||||||
from trio2o.db import core
|
from trio2o.db import core
|
||||||
@ -82,6 +83,7 @@ class FakeServerController(server.ServerController):
|
|||||||
self.clients = {'t_region': FakeClient('t_region')}
|
self.clients = {'t_region': FakeClient('t_region')}
|
||||||
self.project_id = project_id
|
self.project_id = project_id
|
||||||
self.xjob_handler = xrpcapi.XJobAPI()
|
self.xjob_handler = xrpcapi.XJobAPI()
|
||||||
|
self.filter_scheduler = filter_scheduler.FilterScheduler()
|
||||||
|
|
||||||
def _get_client(self, pod_name=None):
|
def _get_client(self, pod_name=None):
|
||||||
if not pod_name:
|
if not pod_name:
|
||||||
|
Loading…
Reference in New Issue
Block a user