tricircle/tricircle/common/lock_handle.py

142 lines
6.0 KiB
Python

# Copyright 2015 Huawei Technologies Co., Ltd.
# All Rights Reserved
#
# 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 datetime
import eventlet
import oslo_db.exception as db_exc
from tricircle.db import core
from tricircle.db import models
ALL_DONE = 0 # both route and bottom resource exist
RES_DONE = 1 # only bottom resource exists
NONE_DONE = 2 # neither router nor bottom resources exists
# The case only router exists is not considered, there may be some manual
# operations on bottom pod which results to this problem.
def get_or_create_route(t_ctx, q_ctx,
project_id, pod, ele, _type, list_ele_method):
# use configuration option later
route_expire_threshold = 30
_id = ele['id']
with t_ctx.session.begin():
routes = core.query_resource(
t_ctx, models.ResourceRouting,
[{'key': 'top_id', 'comparator': 'eq', 'value': _id},
{'key': 'pod_id', 'comparator': 'eq',
'value': pod['pod_id']},
{'key': 'resource_type', 'comparator': 'eq',
'value': _type}], [])
if routes:
route = routes[0]
if route['bottom_id']:
return route, ALL_DONE
else:
route_time = route['updated_at'] or route['created_at']
current_time = datetime.datetime.utcnow()
delta = current_time - route_time
if delta.seconds > route_expire_threshold:
# NOTE(zhiyuan) cannot directly remove the route, we have
# a race here that other worker is updating this route, we
# need to check if the corresponding element has been
# created by other worker
eles = list_ele_method(t_ctx, q_ctx, pod, ele, _type)
if eles:
route['bottom_id'] = eles[0]['id']
core.update_resource(t_ctx,
models.ResourceRouting,
route['id'], route)
return route, RES_DONE
try:
core.delete_resource(t_ctx,
models.ResourceRouting,
route['id'])
except db_exc.ResourceNotFound:
pass
try:
# NOTE(zhiyuan) try/except block inside a with block will cause
# problem, so move them out of the block and manually handle the
# session context
t_ctx.session.begin()
route = core.create_resource(t_ctx, models.ResourceRouting,
{'top_id': _id,
'pod_id': pod['pod_id'],
'project_id': project_id,
'resource_type': _type})
t_ctx.session.commit()
return route, NONE_DONE
except db_exc.DBDuplicateEntry:
t_ctx.session.rollback()
return None, NONE_DONE
finally:
t_ctx.session.close()
def get_or_create_element(t_ctx, q_ctx,
project_id, pod, ele, _type, body,
list_ele_method, create_ele_method):
# use configuration option later
max_tries = 5
for _ in xrange(max_tries):
route, status = get_or_create_route(
t_ctx, q_ctx, project_id, pod, ele, _type, list_ele_method)
if not route:
eventlet.sleep(0)
continue
if status == RES_DONE or status == ALL_DONE:
# in these cases, bottom_id must exist
break
if status == NONE_DONE:
try:
new_ele = create_ele_method(t_ctx, q_ctx, pod, body, _type)
except Exception:
with t_ctx.session.begin():
try:
core.delete_resource(t_ctx,
models.ResourceRouting,
route['id'])
except db_exc.ColumnError:
# NOTE(zhiyuan) this is a rare case that other worker
# considers the route expires and delete it though it
# was just created, maybe caused by out-of-sync time
pass
raise
with t_ctx.session.begin():
# NOTE(zhiyuan) it's safe to update route, the bottom network
# has been successfully created, so other worker will not
# delete this route
route['bottom_id'] = new_ele['id']
core.update_resource(t_ctx, models.ResourceRouting,
route['id'], route)
break
if not route:
raise Exception('Fail to create %s routing entry' % _type)
if not route['bottom_id']:
raise Exception('Fail to bind top and bottom %s' % _type)
# NOTE(zhiyuan) Status being ALL_DONE means that the routing entry is
# complete when we retrieve the resource, so we return False to indicate
# that we can directly use this resource safely. Status being RES_DONE and
# NONE_DONE means that the routing entry is not complete when we retrieve
# the resource but we manage to fill the entry finally, so we return True
# to indicate that we may leave some work to do.
if status == ALL_DONE:
return False, route['bottom_id']
else:
return True, route['bottom_id']