Initial support for policy transactions

Policy PATCH API supports creating multiple objects in one call, in
transactional manner.
This patch suggest using "with NsxPolicyTransaction" syntax to
achive single batch creation for multiple objects.

For now, only top level objects and their descendants are supported,
under top level url (/infra).

Change-Id: I209e63fc41d3c4644142df587eca6295797ed6af
This commit is contained in:
Anna Khmelnitsky 2018-10-04 14:59:33 -07:00
parent 00f7b81155
commit 87c4b821ff
8 changed files with 474 additions and 73 deletions

View File

@ -121,12 +121,36 @@ def build_args(resource_type, resource_id, args, add_name=True):
return args
def create_resource(lib, resource_type, resource_id, args):
def create_resource(lib, transaction, count, resource_type, resource_id, args):
from vmware_nsxlib.v3 import policy_transaction as trans
args = build_args(resource_type, resource_id, args)
api = get_resource_api(lib, resource_type)
api.create_or_overwrite(**args)
def create_multiple():
if count == 1:
api.create_or_overwrite(**args)
else:
for i in range(1, count + 1):
new_args = copy.deepcopy(args)
print(args)
if 'name' in args:
new_args['name'] = "%s%d" % (args['name'], i)
id_marker = resource_type + '_id'
if id_marker in args:
new_args[id_marker] = "%s%d" % (args[id_marker], i)
api.create_or_overwrite(**new_args)
if transaction:
with trans.NsxPolicyTransaction():
create_multiple()
else:
create_multiple()
def update_resource(lib, resource_type, resource_id, args):
@ -199,11 +223,14 @@ def main(argv=sys.argv):
usage = "Usage: %s -o <operation> -r <resource type> " \
"-i <resource id> -a <arg name=value>" % argv[0]
try:
opts, args = getopt.getopt(argv[1:], "o:r:i:a:")
opts, args = getopt.getopt(argv[1:], "to:r:i:a:c:")
except getopt.GetoptError:
print(usage)
sys.exit(1)
transaction = False
count = 1
for opt, val in opts:
if opt in ('-o'):
op = val
@ -213,6 +240,12 @@ def main(argv=sys.argv):
elif opt in ('-p'):
policy_ip = val
elif opt in ('-t'):
transaction = True
elif opt in ('-c'):
count = val
elif opt in ('-r'):
resource_type = val
if resource_type not in RESOURCES:
@ -245,7 +278,8 @@ def main(argv=sys.argv):
print(json.dumps(result, indent=4))
elif op == 'create':
create_resource(nsxlib, resource_type, resource_id, resource_args)
create_resource(nsxlib, transaction, int(count),
resource_type, resource_id, resource_args)
elif op == 'delete':
delete_resource(nsxlib, resource_type, resource_id)
elif op == 'update':

View File

@ -0,0 +1,35 @@
# Copyright 2018 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.
#
from vmware_nsxlib.tests.unit.v3 import nsxlib_testcase
from vmware_nsxlib.v3 import client
from vmware_nsxlib.v3 import policy_defs as policy
BASE_POLICY_URI = "https://1.2.3.4/policy/api/v1/"
class TestPolicyApi(nsxlib_testcase.NsxClientTestCase):
def setUp(self):
self.client = self.new_mocked_client(client.NSX3Client,
url_prefix='policy/api/v1/')
self.policy_api = policy.NsxPolicyApi(self.client)
super(TestPolicyApi, self).setUp()
def assert_json_call(self, method, client, url, data=None):
url = BASE_POLICY_URI + url
return super(TestPolicyApi, self).assert_json_call(
method, client, url, data=data)

View File

@ -13,31 +13,13 @@
# License for the specific language governing permissions and limitations
# under the License.
#
from vmware_nsxlib.tests.unit.v3 import nsxlib_testcase
from vmware_nsxlib.v3 import client
from vmware_nsxlib.tests.unit.v3 import policy_testcase
from vmware_nsxlib.v3 import nsx_constants
from vmware_nsxlib.v3 import policy_constants
from vmware_nsxlib.v3 import policy_defs as policy
BASE_POLICY_URI = "https://1.2.3.4/policy/api/v1/"
class TestPolicyApi(nsxlib_testcase.NsxClientTestCase):
def setUp(self):
self.client = self.new_mocked_client(client.NSX3Client,
url_prefix='policy/api/v1/')
self.policy_api = policy.NsxPolicyApi(self.client)
super(TestPolicyApi, self).setUp()
def assert_json_call(self, method, client, url, data=None):
url = BASE_POLICY_URI + url
return super(TestPolicyApi, self).assert_json_call(
method, client, url, data=data)
class TestPolicyDomain(TestPolicyApi):
class TestPolicyDomain(policy_testcase.TestPolicyApi):
def test_create(self):
domain_def = policy.DomainDef(
@ -67,7 +49,7 @@ class TestPolicyDomain(TestPolicyApi):
self.assert_json_call('GET', self.client, 'infra/domains')
class TestPolicyGroup(TestPolicyApi):
class TestPolicyGroup(policy_testcase.TestPolicyApi):
def test_create(self):
group_def = policy.GroupDef(
@ -142,7 +124,7 @@ class TestPolicyGroup(TestPolicyApi):
'infra/domains/eukarya/groups/giraffe')
class TestPolicyService(TestPolicyApi):
class TestPolicyService(policy_testcase.TestPolicyApi):
def test_create(self):
service_def = policy.ServiceDef(service_id='roomservice')
@ -194,7 +176,7 @@ class TestPolicyService(TestPolicyApi):
data=expected_data)
class TestPolicyCommunicationMap(TestPolicyApi):
class TestPolicyCommunicationMap(policy_testcase.TestPolicyApi):
def setUp(self):
super(TestPolicyCommunicationMap, self).setUp()
@ -286,7 +268,7 @@ class TestPolicyCommunicationMap(TestPolicyApi):
'rules/en2')
class TestPolicyEnforcementPoint(TestPolicyApi):
class TestPolicyEnforcementPoint(policy_testcase.TestPolicyApi):
def test_create(self):
ep_def = policy.EnforcementPointDef(ep_id='ep1', name='The Point',
@ -301,7 +283,7 @@ class TestPolicyEnforcementPoint(TestPolicyApi):
data=ep_def.get_obj_dict())
class TestPolicyTransportZone(TestPolicyApi):
class TestPolicyTransportZone(policy_testcase.TestPolicyApi):
def test_get(self):
tz_def = policy.TransportZoneDef(tz_id='tz1', ep_id='default')
@ -310,7 +292,7 @@ class TestPolicyTransportZone(TestPolicyApi):
self.assert_json_call('GET', self.client, tz_path)
class TestPolicyDeploymentMap(TestPolicyApi):
class TestPolicyDeploymentMap(policy_testcase.TestPolicyApi):
def test_create(self):
map_def = policy.DeploymentMapDef(map_id='dm1',

View File

@ -821,7 +821,7 @@ class TestPolicyCommunicationMap(NsxPolicyLibTestCase):
direction = nsx_constants.IN_OUT
get_return_value = {'rules': [{'sequence_number': 1}]}
with mock.patch.object(self.policy_api,
"create_or_update") as api_call,\
"create_with_parent") as api_call,\
mock.patch.object(self.policy_api, "get",
return_value=get_return_value):
self.resourceApi.create_or_overwrite(name, domain_id,
@ -834,7 +834,7 @@ class TestPolicyCommunicationMap(NsxPolicyLibTestCase):
direction=direction,
logged=True,
tenant=TEST_TENANT)
expected_def = policy_defs.CommunicationMapDef(
map_def = policy_defs.CommunicationMapDef(
domain_id=domain_id,
map_id=map_id,
name=name,
@ -842,9 +842,8 @@ class TestPolicyCommunicationMap(NsxPolicyLibTestCase):
category=policy_constants.CATEGORY_APPLICATION,
precedence=0,
tenant=TEST_TENANT)
self.assert_called_with_def(api_call, expected_def)
expected_def = policy_defs.CommunicationMapEntryDef(
entry_def = policy_defs.CommunicationMapEntryDef(
domain_id=domain_id,
map_id=map_id,
entry_id='entry',
@ -858,7 +857,7 @@ class TestPolicyCommunicationMap(NsxPolicyLibTestCase):
direction=direction,
logged=True,
tenant=TEST_TENANT)
self.assert_called_with_def(api_call, expected_def, call_num=1)
self.assert_called_with_defs(api_call, [map_def, entry_def])
def test_create_first_seqnum(self):
domain_id = '111'
@ -871,7 +870,7 @@ class TestPolicyCommunicationMap(NsxPolicyLibTestCase):
category = 'Emergency'
get_return_value = {'rules': []}
with mock.patch.object(self.policy_api,
"create_or_update") as api_call, \
"create_with_parent") as api_call, \
mock.patch.object(self.resourceApi, "get",
return_value=get_return_value):
self.resourceApi.create_or_overwrite(name, domain_id,
@ -884,7 +883,7 @@ class TestPolicyCommunicationMap(NsxPolicyLibTestCase):
logged=False,
tenant=TEST_TENANT)
expected_def = policy_defs.CommunicationMapDef(
map_def = policy_defs.CommunicationMapDef(
domain_id=domain_id,
map_id=map_id,
name=name,
@ -892,9 +891,8 @@ class TestPolicyCommunicationMap(NsxPolicyLibTestCase):
category=category,
precedence=0,
tenant=TEST_TENANT)
self.assert_called_with_def(api_call, expected_def)
expected_def = policy_defs.CommunicationMapEntryDef(
entry_def = policy_defs.CommunicationMapEntryDef(
domain_id=domain_id,
map_id=map_id,
entry_id='entry',
@ -907,7 +905,7 @@ class TestPolicyCommunicationMap(NsxPolicyLibTestCase):
dest_groups=[dest_group],
logged=False,
tenant=TEST_TENANT)
self.assert_called_with_def(api_call, expected_def, call_num=1)
self.assert_called_with_defs(api_call, [map_def, entry_def])
def test_create_without_seqnum(self):
domain_id = '111'

View File

@ -0,0 +1,114 @@
# Copyright 2018 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.
#
from vmware_nsxlib import v3
from vmware_nsxlib.tests.unit.v3 import nsxlib_testcase
from vmware_nsxlib.tests.unit.v3 import policy_testcase
from vmware_nsxlib.v3 import policy_transaction as trans
class TestPolicyTransaction(policy_testcase.TestPolicyApi):
def setUp(self):
super(TestPolicyTransaction, self).setUp()
nsxlib_config = nsxlib_testcase.get_default_nsxlib_config()
self.policy_lib = v3.NsxPolicyLib(nsxlib_config)
self.policy_api = self.policy_lib.policy_api
self.policy_api.client = self.client
def assert_infra_patch_call(self, body):
self.assert_json_call('PATCH', self.client, 'infra',
data=body)
def test_domains_only(self):
tags = [{'scope': 'color', 'tag': 'green'}]
d1 = {'resource_type': 'Domain', 'id': 'domain1',
'display_name': 'd1', 'description': 'first domain',
'tags': tags}
d2 = {'resource_type': 'Domain', 'id': 'domain2',
'display_name': 'd2', 'description': 'no tags'}
with trans.NsxPolicyTransaction():
for d in (d1, d2):
self.policy_lib.domain.create_or_overwrite(
d['display_name'],
d['id'],
d['description'],
tags=d['tags'] if 'tags' in d else None)
expected_body = {'resource_type': 'Infra',
'children': [{'resource_type': 'ChildDomain',
'Domain': d1},
{'resource_type': 'ChildDomain',
'Domain': d2}]}
self.assert_infra_patch_call(expected_body)
def test_domains_and_groups(self):
tags = [{'scope': 'color', 'tag': 'green'}]
g1 = {'resource_type': 'Group', 'id': 'group1',
'display_name': 'g1',
'description': 'first group'}
g2 = {'resource_type': 'Group', 'id': 'group2',
'description': 'second group',
'display_name': 'g2',
'tags': tags}
g3 = {'resource_type': 'Group', 'id': 'group3',
'display_name': 'g3',
'description': 'third group'}
d1 = {'resource_type': 'Domain', 'id': 'domain1',
'display_name': 'd1', 'description': 'first domain',
'tags': tags}
d2 = {'resource_type': 'Domain', 'id': 'domain2',
'display_name': 'd2', 'description': 'no tags'}
with trans.NsxPolicyTransaction():
for d in (d1, d2):
self.policy_lib.domain.create_or_overwrite(
d['display_name'],
d['id'],
d['description'],
tags=d['tags'] if 'tags' in d else None)
d['children'] = []
for g in (g1, g2, g3):
self.policy_lib.group.create_or_overwrite(
g['display_name'],
d['id'],
g['id'],
g['description'],
tags=g['tags'] if 'tags' in g else None)
d['children'].append({'resource_type': 'ChildGroup',
'Group': g})
expected_body = {'resource_type': 'Infra',
'children': [{'resource_type': 'ChildDomain',
'Domain': d1},
{'resource_type': 'ChildDomain',
'Domain': d2}]}
self.assert_infra_patch_call(expected_body)

View File

@ -79,6 +79,9 @@ class ResourceDef(object):
def resource_type():
pass
def path_defs(self):
pass
def get_id(self):
if self.attrs and self.path_ids:
return self.attrs.get(self.path_ids[-1])
@ -170,6 +173,29 @@ class ResourceDef(object):
return len(body_args) == 0
class TenantDef(ResourceDef):
@property
def path_pattern(self):
return TENANTS_PATH_PATTERN
@staticmethod
def resource_type():
return 'Infra'
def path_defs(self):
return ()
@property
def path_ids(self):
return ('tenant',)
def get_resource_path(self):
return 'infra/'
def get_section_path(self):
return 'infra/'
class DomainDef(ResourceDef):
@property
@ -184,6 +210,9 @@ class DomainDef(ResourceDef):
def resource_type():
return 'Domain'
def path_defs(self):
return (TenantDef,)
class RouteAdvertisement(object):
@ -218,6 +247,9 @@ class RouteAdvertisement(object):
class RouterDef(ResourceDef):
def path_defs(self):
return (TenantDef,)
def get_obj_dict(self):
body = super(RouterDef, self).get_obj_dict()
@ -332,6 +364,9 @@ class Tier1SegmentDef(BaseSegmentDef):
def path_ids(self):
return ('tenant', 'tier1_id', 'segment_id')
def path_defs(self):
return (TenantDef, Tier1Def)
class SegmentDef(BaseSegmentDef):
'''These segments don't belong to particular tier1.
@ -347,6 +382,9 @@ class SegmentDef(BaseSegmentDef):
def path_ids(self):
return ('tenant', 'segment_id')
def path_defs(self):
return (TenantDef,)
def get_obj_dict(self):
body = super(SegmentDef, self).get_obj_dict()
if self.get_attr('tier1_id'):
@ -390,6 +428,9 @@ class SegmentPortDef(ResourceDef):
def resource_type():
return 'SegmentPort'
def path_defs(self):
return (TenantDef, SegmentDef)
def get_obj_dict(self):
body = super(SegmentPortDef, self).get_obj_dict()
address_bindings = self.get_attr('address_bindings')
@ -471,6 +512,9 @@ class GroupDef(ResourceDef):
def resource_type():
return 'Group'
def path_defs(self):
return (TenantDef, DomainDef)
def get_obj_dict(self):
body = super(GroupDef, self).get_obj_dict()
conds = self.get_attr('conditions')
@ -510,6 +554,9 @@ class ServiceDef(ResourceDef):
def resource_type():
return 'Service'
def path_defs(self):
return (TenantDef,)
def get_obj_dict(self):
body = super(ServiceDef, self).get_obj_dict()
entries = [entry.get_obj_dict()
@ -533,6 +580,9 @@ class ServiceEntryDef(ResourceDef):
def path_ids(self):
return ('tenant', 'service_id', 'entry_id')
def path_defs(self):
return (TenantDef, ServiceDef)
class L4ServiceEntryDef(ServiceEntryDef):
@ -595,6 +645,9 @@ class CommunicationMapDef(ResourceDef):
def resource_type():
return 'SecurityPolicy'
def path_defs(self):
return (TenantDef, DomainDef)
def get_obj_dict(self):
body = super(CommunicationMapDef, self).get_obj_dict()
for attr in ('category', 'precedence'):
@ -642,6 +695,9 @@ class CommunicationMapEntryDef(ResourceDef):
def resource_type():
return 'Rule'
def path_defs(self):
return (TenantDef, DomainDef, CommunicationMapDef)
def get_obj_dict(self):
body = super(CommunicationMapEntryDef, self).get_obj_dict()
domain_id = self.get_attr('domain_id')
@ -650,7 +706,7 @@ class CommunicationMapEntryDef(ResourceDef):
body['destination_groups'] = self.get_groups_path(
domain_id, self.get_attr('dest_groups'))
self._set_attrs_in_body(body, ['sequence_number', 'services', 'scope',
self._set_attrs_in_body(body, ['sequence_number', 'scope',
'action', 'direction', 'logged'])
service_ids = self.get_attr('service_ids')
@ -706,6 +762,9 @@ class EnforcementPointDef(ResourceDef):
def resource_type():
return 'EnforcementPoint'
def path_defs(self):
return (TenantDef,)
def get_obj_dict(self):
body = super(EnforcementPointDef, self).get_obj_dict()
body['id'] = self.get_id()
@ -761,6 +820,9 @@ class DeploymentMapDef(ResourceDef):
def resource_type():
return 'DeploymentMap'
def path_defs(self):
return (TenantDef, DomainDef)
def get_obj_dict(self):
body = super(DeploymentMapDef, self).get_obj_dict()
body['id'] = self.get_id()

View File

@ -25,6 +25,7 @@ from vmware_nsxlib.v3 import exceptions
from vmware_nsxlib.v3 import nsx_constants
from vmware_nsxlib.v3 import policy_constants
from vmware_nsxlib.v3 import policy_defs
from vmware_nsxlib.v3 import policy_transaction as policy_trans
from vmware_nsxlib.v3 import utils
LOG = logging.getLogger(__name__)
@ -154,6 +155,20 @@ class NsxPolicyResourceBase(object):
def _list(self, obj_def):
return self.policy_api.list(obj_def).get('results', [])
def _create_or_store(self, policy_def, child_def=None):
transaction = policy_trans.NsxPolicyTransaction.get_current()
if transaction:
# Store this def for batch apply for this transaction
transaction.store_def(policy_def, self.policy_api.client)
if child_def:
transaction.store_def(child_def, self.policy_api.client)
else:
# No transaction - apply now
if child_def:
self.policy_api.create_with_parent(policy_def, child_def)
else:
self.policy_api.create_or_update(policy_def)
class NsxPolicyDomainApi(NsxPolicyResourceBase):
"""NSX Policy Domain."""
@ -171,7 +186,7 @@ class NsxPolicyDomainApi(NsxPolicyResourceBase):
tags=tags,
tenant=tenant)
self.policy_api.create_or_update(domain_def)
self._create_or_store(domain_def)
return domain_id
def delete(self, domain_id, tenant=policy_constants.POLICY_INFRA_TENANT):
@ -232,7 +247,7 @@ class NsxPolicyGroupApi(NsxPolicyResourceBase):
conditions=conditions,
tags=tags,
tenant=tenant)
self.policy_api.create_or_update(group_def)
self._create_or_store(group_def)
return group_id
def build_condition(
@ -280,7 +295,8 @@ class NsxPolicyGroupApi(NsxPolicyResourceBase):
conditions=conditions,
tags=tags,
tenant=tenant)
return self.policy_api.create_or_update(group_def)
self._create_or_store(group_def)
return group_id
def delete(self, domain_id, group_id,
tenant=policy_constants.POLICY_INFRA_TENANT):
@ -416,7 +432,7 @@ class NsxPolicyL4ServiceApi(NsxPolicyServiceBase):
dest_ports=dest_ports,
tenant=tenant)
self.policy_api.create_with_parent(service_def, entry_def)
self._create_or_store(service_def, entry_def)
return service_id
def update(self, service_id,
@ -470,7 +486,7 @@ class NsxPolicyIcmpServiceApi(NsxPolicyServiceBase):
icmp_code=icmp_code,
tenant=tenant)
self.policy_api.create_with_parent(service_def, entry_def)
self._create_or_store(service_def, entry_def)
return service_id
def update(self, service_id,
@ -522,7 +538,7 @@ class NsxPolicyIPProtocolServiceApi(NsxPolicyServiceBase):
protocol_number=protocol_number,
tenant=tenant)
self.policy_api.create_with_parent(service_def, entry_def)
self._create_or_store(service_def, entry_def)
return service_id
def update(self, service_id,
@ -578,7 +594,7 @@ class NsxPolicyTier1Api(NsxPolicyResourceBase):
failover_mode=failover_mode,
route_advertisement=route_advertisement,
tenant=tenant)
self.policy_api.create_or_update(tier1_def)
self._create_or_store(tier1_def)
return tier1_id
def delete(self, tier1_id, tenant=policy_constants.POLICY_INFRA_TENANT):
@ -625,6 +641,7 @@ class NsxPolicyTier1Api(NsxPolicyResourceBase):
nat=nat,
lb_vip=lb_vip,
lb_snat=lb_snat)
tier1_def = self.entry_def(tier1_id=tier1_id,
route_adv=route_adv,
tenant=tenant)
@ -723,7 +740,7 @@ class NsxPolicyTier1SegmentApi(NsxPolicyResourceBase):
default_rule_logging=default_rule_logging,
tags=tags,
tenant=tenant)
self.policy_api.create_or_update(segment_def)
self._create_or_store(segment_def)
return segment_id
def delete(self, tier1_id, segment_id,
@ -796,7 +813,7 @@ class NsxPolicySegmentApi(NsxPolicyResourceBase):
transport_zone_id=transport_zone_id,
tags=tags,
tenant=tenant)
self.policy_api.create_or_update(segment_def)
self._create_or_store(segment_def)
return segment_id
def delete(self, segment_id,
@ -883,7 +900,7 @@ class NsxPolicySegmentPortApi(NsxPolicyResourceBase):
allocate_addresses=allocate_addresses,
tags=tags,
tenant=tenant)
self.policy_api.create_or_update(port_def)
self._create_or_store(port_def)
return port_id
def delete(self, segment_id, port_id,
@ -996,13 +1013,10 @@ class NsxPolicyCommunicationMapApi(NsxPolicyResourceBase):
seq_nums.sort()
return seq_nums[-1]
def _get_seq_num(self, sequence_number, last_sequence):
if not sequence_number:
if last_sequence < 0:
sequence_number = 1
else:
sequence_number = last_sequence + 1
return sequence_number
def _get_seq_num(self, last_sequence):
if last_sequence < 0:
return 1
return last_sequence + 1
def create_or_overwrite(self, name, domain_id, map_id=None,
description=None, precedence=0,
@ -1020,15 +1034,17 @@ class NsxPolicyCommunicationMapApi(NsxPolicyResourceBase):
this call under lock to prevent race condition where two entries
end up with same sequence number.
"""
last_sequence = -1
if map_id:
# get the next available sequence number
last_sequence = self._get_last_seq_num(domain_id, map_id,
tenant=tenant)
if not sequence_number:
# get the next available sequence number
last_sequence = self._get_last_seq_num(domain_id, map_id,
tenant=tenant)
else:
map_id = self._init_obj_uuid(map_id)
last_sequence = -1
sequence_number = self._get_seq_num(sequence_number, last_sequence)
if not sequence_number:
sequence_number = self._get_seq_num(last_sequence)
# Build the communication entry. Since we currently support only one
# it will have the same id as its parent
@ -1051,13 +1067,8 @@ class NsxPolicyCommunicationMapApi(NsxPolicyResourceBase):
domain_id=domain_id, map_id=map_id,
tenant=tenant, name=name, description=description,
precedence=precedence, category=category, tags=tags)
if last_sequence < 0:
# if communication map is absent, we need to create it
return self.policy_api.create_with_parent(map_def, entry_def)
# TODO(asarfaty) combine both calls together
self.policy_api.create_or_update(map_def)
self.policy_api.create_or_update(entry_def)
self._create_or_store(map_def, entry_def)
return map_id
def create_or_overwrite_map_only(
@ -1116,6 +1127,7 @@ class NsxPolicyCommunicationMapApi(NsxPolicyResourceBase):
tenant=tenant, name=name, description=description,
precedence=precedence, category=category, tags=tags)
# TODO(annak): support transactional create
self.policy_api.create_with_parent(map_def, entries)
return map_id
@ -1134,7 +1146,7 @@ class NsxPolicyCommunicationMapApi(NsxPolicyResourceBase):
if not sequence_number:
last_sequence = self._get_last_seq_num(domain_id, map_id,
tenant=tenant)
sequence_number = self._get_seq_num(sequence_number, last_sequence)
sequence_number = self._get_seq_num(last_sequence)
entry_id = self._init_obj_uuid(entry_id)
# Build the communication entry
@ -1153,7 +1165,7 @@ class NsxPolicyCommunicationMapApi(NsxPolicyResourceBase):
logged=logged,
tenant=tenant)
self.policy_api.create_or_update(entry_def)
self._create_or_store(entry_def)
return entry_id
def delete(self, domain_id, map_id,
@ -1300,7 +1312,7 @@ class NsxPolicyEnforcementPointApi(NsxPolicyResourceBase):
edge_cluster_id=edge_cluster_id,
transport_zone_id=transport_zone_id,
tenant=tenant)
self.policy_api.create_or_update(ep_def)
self._create_or_store(ep_def)
return ep_id
def delete(self, ep_id,
@ -1450,7 +1462,7 @@ class NsxPolicyDeploymentMapApi(NsxPolicyResourceBase):
ep_id=ep_id,
domain_id=domain_id,
tenant=tenant)
self.policy_api.create_or_update(map_def)
self._create_or_store(map_def)
return map_id
def delete(self, map_id, domain_id=None,

View File

@ -0,0 +1,164 @@
# 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 import policy_constants
from vmware_nsxlib.v3 import policy_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 = [policy_defs.TenantDef(
tenant=policy_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 _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()
parent_id = resource_def.get_attr(resource_def.path_ids[level])
# 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' in parent:
return self._find_parent_in_dict(
parent['children'], resource_def, level + 1)
# Parent not found - for now, raise an exception
# Support for this will come later
# TODO(annak): remove this when missing parent body is
# created on demand
raise NsxPolicyTransactionException(
"Transactional create is supported for infra level"
" objects and their children")
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()}
# 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:
parent_dict = body
if 'children' not in parent_dict:
parent_dict['children'] = []
resource_type = resource_def.resource_type()
parent_dict['children'].append({
'resource_type': 'Child%s' % resource_type,
resource_type: 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