Anna Khmelnitsky a0da933427 Fix service creation under transaction
Currently policy errors out if service entry is not present directly
under service, enev if the entry is specified as Child in same
transational API. This patch works around the problem.

Change-Id: I6c80c9ea6d188f4d282036c5a0a00a09969f7244
2019-02-21 11:33:23 -08:00

191 lines
6.6 KiB
Python

# Copyright 2017 VMware, Inc.
# 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 threading
from vmware_nsxlib._i18n import _
from vmware_nsxlib.v3 import exceptions
from vmware_nsxlib.v3.policy import constants
from vmware_nsxlib.v3.policy import core_defs
class NsxPolicyTransactionException(exceptions.NsxLibException):
message = _("Policy Transaction Error: %(msg)s")
class NsxPolicyTransaction(object):
# stores current transaction per thread
# nested transactions not supported
data = threading.local()
def __init__(self):
# For now only infra tenant is supported
self.defs = [core_defs.TenantDef(
tenant=constants.POLICY_INFRA_TENANT)]
self.client = None
def __enter__(self):
if self.get_current():
raise NsxPolicyTransactionException(
"Nested transactions not supported")
self.data.instance = self
return self
def __exit__(self, e_type, e_value, e_traceback):
# Always reset transaction regardless of exceptions
self.data.instance = None
if e_type:
# If exception occured in the "with" block, raise it
# without applying to backend
return False
# exception might happen here and will be raised
self.apply_defs()
def store_def(self, resource_def, client):
if self.client and client != self.client:
raise NsxPolicyTransactionException(
"All operations under transaction must have same client")
self.client = client
# TODO(annak): raise exception for different tenants
self.defs.append(resource_def)
def _sort_defs(self):
sorted_defs = []
while len(self.defs):
for resource_def in self.defs:
if resource_def in sorted_defs:
continue
# We want all parents to appear before the child
if not resource_def.path_defs():
# top level resource
sorted_defs.append(resource_def)
continue
parent_type = resource_def.path_defs()[-1]
parents = [d for d in self.defs if isinstance(d, parent_type)]
missing_parents = [d for d in parents if d not in sorted_defs]
if not missing_parents:
# All parents are appended to sorted list, child can go in
sorted_defs.append(resource_def)
unsorted = [d for d in self.defs if d not in sorted_defs]
self.defs = unsorted
self.defs = sorted_defs
def _build_wrapper_dict(self, resource_class, node):
return {'resource_type': 'Child%s' % resource_class,
resource_class: node}
def _find_parent_in_dict(self, d, resource_def, level=1):
if len(resource_def.path_defs()) <= level:
return
parent_type = resource_def.path_defs()[level]
is_leaf = (level + 1 == len(resource_def.path_defs()))
resource_type = parent_type.resource_type()
resource_class = parent_type.resource_class()
parent_id = resource_def.get_attr(resource_def.path_ids[level])
def create_missing_node():
node = {'resource_type': resource_type,
'id': parent_id,
'children': []}
return self._build_wrapper_dict(resource_class, node), node
# iterate over all objects in d, and look for resource type
for child in d:
if resource_type in child and child[resource_type]:
parent = child[resource_type]
# If resource type matches, check for id
if parent['id'] == parent_id:
if is_leaf:
return parent
if 'children' not in parent:
parent['children'] = []
return self._find_parent_in_dict(
parent['children'], resource_def, level + 1)
# Parent not found - create a node for missing parent
wrapper, node = create_missing_node()
d.append(wrapper)
if is_leaf:
# This is the last parent that needs creation
return node
return self._find_parent_in_dict(node['children'], resource_def,
level + 1)
def apply_defs(self):
# TODO(annak): find longest common URL, for now always
# applying on tenant level
if not self.defs:
return
self._sort_defs()
top_def = self.defs[0]
url = top_def.get_resource_path()
body = {'resource_type': top_def.resource_type(),
'children': []}
# iterate over defs (except top level def)
for resource_def in self.defs[1:]:
parent_dict = None
if 'children' in body:
parent_dict = self._find_parent_in_dict(body['children'],
resource_def)
if not parent_dict:
# Top level resource
parent_dict = body
if 'children' not in parent_dict:
parent_dict['children'] = []
resource_class = resource_def.resource_class()
node = resource_def.get_obj_dict()
if resource_def.mandatory_child_def:
# This is a workaround for policy issue that involves required
# children (see comment on definition of mandatory_child_def)
# TODO(annak): remove when policy solves the issue
child_def = resource_def.mandatory_child_def
child_dict_key = child_def.get_last_section_dict_key
node[child_dict_key] = [child_def.get_obj_dict()]
parent_dict['children'].append(
self._build_wrapper_dict(resource_class,
resource_def.get_obj_dict()))
if body:
self.client.patch(url, body)
@staticmethod
def get_current():
if hasattr(NsxPolicyTransaction.data, 'instance'):
return NsxPolicyTransaction.data.instance