Files
vmware-nsxlib/vmware_nsxlib/v3/policy/transaction.py
Jinhao Tang 5ed31933e1 Add transaction support in SecurityPolicies and Rules
1. Enabling transaction calls in SecurityPolicies and their related rules
requires the support in create_with_entires and update_with_entries call.
This patch changed these method calls and supports adding list of resource
defs to the transaction.
2. Special logic is added to the update_with_entries since partial patch in
hierarych call for DFW rules only supports updating and adding. However, we expect
the update to be full replacement. Thus add logic to mark the marked_as_delete field
in the unwanted entries as true.

Change-Id: I59ef2e27f6a2f23a44edcd37da88bdc70fda944d
2019-11-03 10:32:35 +02:00

199 lines
7.1 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
if isinstance(resource_def, list):
self.defs.extend(resource_def)
else:
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, delete=False):
wrapper_dict = {'resource_type': 'Child%s' % resource_class,
resource_class: node}
if delete:
wrapper_dict.update({'marked_for_delete': True})
return wrapper_dict
def _find_parent_in_dict(self, d, resource_def, level=1):
res_path_defs = resource_def.path_defs()
if not res_path_defs or len(res_path_defs) <= level:
return
parent_type = resource_def.path_defs()[level]
is_leaf = (level + 1 == len(res_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 or not self.client:
# Empty transaction
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,
node,
resource_def.get_delete()))
if body:
headers = {'nsx-enable-partial-patch': 'true'}
self.client.patch(url, body, headers=headers)
@staticmethod
def get_current():
if hasattr(NsxPolicyTransaction.data, 'instance'):
return NsxPolicyTransaction.data.instance