From efa0d2777b0063fcdc1dc96a19202b02f9d6af79 Mon Sep 17 00:00:00 2001 From: Roey Chen Date: Thu, 4 May 2017 05:00:42 -0700 Subject: [PATCH] NSXv Admin util: BGP GW edges deployment and configuration E.g: - Create a BGP gateway edge: $ nsxadmin -r bgp-gw-edge -o create \ --property name=GW-EDGE1 \ --property local-as=65002 \ --property external-iface=network-16:192.168.111.16/24 \ --property internal-iface=virtualwire-223:20.20.20.16/24 - Delete edge: $ nsxadmin -r bgp-gw-edge -o delete \ --property edge-id=edge-321 - Creates a rule and add it to both edges, if prefix is not given then default prefix ('0.0.0.0/0') is used: $ nsxadmin -r routing-redistribution-rule -o create \ --property gw-edge-ids=edge-375,edge-376 \ --property learner-protocol=bgp \ --property learn-from=static \ --property action=permit - Delete all rules created with default prefix on specified edges: $ nsxadmin -r routing-redistribution-rule -o delete \ --property gw-edge-ids=edge-1,edge-2 - Add a BGP neighbour to specified edges: $ nsxadmin -r bgp-neighbour -o create \ --property gw-edge-ids=edge-1,edge-2 \ --property ip-address=192.168.1.1 \ --property remote-as=65002 \ --property password=Password12# - Remove a BGP neighbour from specified edges: $ nsxadmin -r bgp-neighbour -o delete \ --property gw-edge-ids=edge-1,edge-2 \ --property ip-address=192.168.1.1 Change-Id: Ic18f9657735285de288da8996cc77ae50685f1c4 --- doc/source/admin_util.rst | 27 ++ .../vshield/edge_dynamic_routing_driver.py | 16 +- .../services/dynamic_routing/nsx_v/driver.py | 39 +- .../shell/admin/plugins/common/constants.py | 3 + .../admin/plugins/nsxv/resources/gw_edges.py | 354 ++++++++++++++++++ vmware_nsx/shell/resources.py | 15 +- 6 files changed, 427 insertions(+), 27 deletions(-) create mode 100644 vmware_nsx/shell/admin/plugins/nsxv/resources/gw_edges.py diff --git a/doc/source/admin_util.rst b/doc/source/admin_util.rst index 02299079a6..d782895c3a 100644 --- a/doc/source/admin_util.rst +++ b/doc/source/admin_util.rst @@ -347,6 +347,33 @@ Client Certificate nsxadmin -r certificate -o nsx-list + +BGP GW edges +~~~~~~~~~~~~ +- Create new BGP GW edge:: + + nsxadmin -r bgp-gw-edge -o create --property name= --property local-as= --property external-iface=: --property internal-iface=: + +- Delete BGP GW edge:: + + nsxadmin -r bgp-gw-edge -o delete --property gw-edge-id= + +- Add a redistribution rule to a BGP GW edges:: + + nsxadmin -r routing-redistribution-rule -o create --property edge-ids=[,...] [--property prefix=] --property learner-protocol= --property learn-from=ospf,bgp,connected,static --property action= + +- Remove a redistribution rule from BGP GW edges:: + + nsxadmin -r routing-redistribution-rule -o delete --property gw-edge-ids=[,...] [--property prefix-name=] + +- Add a new BGP neighbour to BGP GW edges:: + + nsxadmin -r bgp-neighbour -o create --property gw-edge-ids=[,...] --property ip-address= --property remote-as= --property --password= + +- Remove BGP neighbour from BGP GW edges:: + + nsxadmin -r bgp-neighbour -o delete --property gw-edge-ids=[,...] --property ip-address= + Config ~~~~~~ diff --git a/vmware_nsx/plugins/nsx_v/vshield/edge_dynamic_routing_driver.py b/vmware_nsx/plugins/nsx_v/vshield/edge_dynamic_routing_driver.py index 1a19e4043b..deb7560693 100644 --- a/vmware_nsx/plugins/nsx_v/vshield/edge_dynamic_routing_driver.py +++ b/vmware_nsx/plugins/nsx_v/vshield/edge_dynamic_routing_driver.py @@ -105,6 +105,9 @@ class EdgeDynamicRoutingDriver(object): bgp_config['bgp']['enabled'] = True + if 'default_originate' in kwargs: + bgp_config['bgp']['defaultOriginate'] = kwargs['default_originate'] + if 'local_as' in kwargs: bgp_config['bgp']['localAS'] = kwargs['local_as'] @@ -131,16 +134,17 @@ class EdgeDynamicRoutingDriver(object): def add_bgp_speaker_config(self, edge_id, prot_router_id, local_as, enabled, bgp_neighbours, - prefixes, redistribution_rules): + prefixes, redistribution_rules, + default_originate=False): with locking.LockManager.get_lock(str(edge_id)): self._update_routing_config(edge_id, router_id=prot_router_id, prefixes_to_add=prefixes) - self._update_bgp_routing_config(edge_id, enabled=enabled, - local_as=local_as, - neighbours_to_add=bgp_neighbours, - prefixes_to_add=prefixes, - rules_to_add=redistribution_rules) + self._update_bgp_routing_config( + edge_id, enabled=enabled, local_as=local_as, + neighbours_to_add=bgp_neighbours, prefixes_to_add=prefixes, + rules_to_add=redistribution_rules, + default_originate=default_originate) def delete_bgp_speaker_config(self, edge_id): with locking.LockManager.get_lock(str(edge_id)): diff --git a/vmware_nsx/services/dynamic_routing/nsx_v/driver.py b/vmware_nsx/services/dynamic_routing/nsx_v/driver.py index f640e82b69..8bf55b1ce2 100644 --- a/vmware_nsx/services/dynamic_routing/nsx_v/driver.py +++ b/vmware_nsx/services/dynamic_routing/nsx_v/driver.py @@ -53,21 +53,8 @@ def redistribution_rule(advertise_static_routes, prefix_name, action='permit'): return {'rule': rule} -def bgp_neighbour(bgp_peer): - bgp_filter = {'bgpFilter': [{'direction': 'out', 'action': 'permit'}]} - nbr = { - 'ipAddress': bgp_peer['peer_ip'], - 'remoteAS': bgp_peer['remote_as'], - 'bgpFilters': bgp_filter, - 'password': bgp_peer['password'], - 'holdDownTimer': cfg.CONF.nsxv.bgp_neighbour_hold_down_timer, - 'keepAliveTimer': cfg.CONF.nsxv.bgp_neighbour_keep_alive_timer - } - return {'bgpNeighbour': nbr} - - -def gw_bgp_neighbour(ip_address, remote_as, password): - bgp_filter = {'bgpFilter': [{'direction': 'in', 'action': 'permit'}]} +def _get_bgp_neighbour(ip_address, remote_as, password, direction): + bgp_filter = {'bgpFilter': [{'direction': direction, 'action': 'permit'}]} nbr = { 'ipAddress': ip_address, 'remoteAS': remote_as, @@ -79,6 +66,18 @@ def gw_bgp_neighbour(ip_address, remote_as, password): return {'bgpNeighbour': nbr} +def bgp_neighbour_from_peer(bgp_peer): + return _get_bgp_neighbour(bgp_peer['peer_ip'], + bgp_peer['remote_as'], + bgp_peer['password'], + direction='out') + + +def gw_bgp_neighbour(ip_address, remote_as, password): + return _get_bgp_neighbour(ip_address, remote_as, password, + direction='in') + + class NSXvBgpDriver(object): """Class driver to address the neutron_dynamic_routing API""" @@ -270,7 +269,7 @@ class NSXvBgpDriver(object): bgp_peer_id) # Update the password for the old bgp peer and update NSX old_bgp_peer['password'] = password - neighbour = bgp_neighbour(old_bgp_peer) + neighbour = bgp_neighbour_from_peer(old_bgp_peer) for bgp_speaker_id in bgp_speaker_ids: with locking.LockManager.get_lock(bgp_speaker_id): peers = self._plugin.get_bgp_peers_by_bgp_speaker( @@ -300,7 +299,8 @@ class NSXvBgpDriver(object): bgp_peer_id = self._plugin._get_id_for(bgp_peer_info, 'bgp_peer_id') bgp_peer_obj = self._plugin.get_bgp_peer(context, bgp_peer_id) - nbr = bgp_neighbour(bgp_peer_obj) + + nbr = bgp_neighbour_from_peer(bgp_peer_obj) bgp_bindings = nsxv_db.get_nsxv_bgp_speaker_bindings(context.session, bgp_speaker_id) self._validate_bgp_peer(context, bgp_speaker_id, bgp_peer_obj['id']) @@ -335,7 +335,7 @@ class NSXvBgpDriver(object): def remove_bgp_peer(self, context, bgp_speaker_id, bgp_peer_info): bgp_peer_id = bgp_peer_info['bgp_peer_id'] bgp_peer_obj = self._plugin.get_bgp_peer(context, bgp_peer_id) - nbr = bgp_neighbour(bgp_peer_obj) + nbr = bgp_neighbour_from_peer(bgp_peer_obj) bgp_bindings = nsxv_db.get_nsxv_bgp_speaker_bindings( context.session, bgp_speaker_id) speaker = self._plugin.get_bgp_speaker(context, bgp_speaker_id) @@ -443,7 +443,8 @@ class NSXvBgpDriver(object): prefixes, redis_rules = self._get_prefixes_and_redistribution_rules( subnets, advertise_static_routes) - bgp_neighbours = [bgp_neighbour(bgp_peer) for bgp_peer in bgp_peers] + bgp_neighbours = [bgp_neighbour_from_peer(bgp_peer) + for bgp_peer in bgp_peers] try: self._nsxv.add_bgp_speaker_config(edge_id, bgp_identifier, local_as, enabled_state, diff --git a/vmware_nsx/shell/admin/plugins/common/constants.py b/vmware_nsx/shell/admin/plugins/common/constants.py index 084cf98863..dce0352bce 100644 --- a/vmware_nsx/shell/admin/plugins/common/constants.py +++ b/vmware_nsx/shell/admin/plugins/common/constants.py @@ -47,3 +47,6 @@ METADATA = 'metadata' MISSING_NETWORKS = 'missing-networks' ORPHANED_NETWORKS = 'orphaned-networks' LBAAS = 'lbaas' +BGP_GW_EDGE = 'bgp-gw-edge' +ROUTING_REDIS_RULE = 'routing-redistribution-rule' +BGP_NEIGHBOUR = 'bgp-neighbour' diff --git a/vmware_nsx/shell/admin/plugins/nsxv/resources/gw_edges.py b/vmware_nsx/shell/admin/plugins/nsxv/resources/gw_edges.py new file mode 100644 index 0000000000..e90bf331ee --- /dev/null +++ b/vmware_nsx/shell/admin/plugins/nsxv/resources/gw_edges.py @@ -0,0 +1,354 @@ +# 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 netaddr + +from neutron_dynamic_routing.services.bgp.common import constants as bgp_const +from neutron_lib.callbacks import registry +from oslo_config import cfg +from oslo_log import log as logging + +from vmware_nsx.common import nsxv_constants +from vmware_nsx.plugins.nsx_v import availability_zones as nsx_az +from vmware_nsx.plugins.nsx_v.vshield.common import constants as vcns_const +from vmware_nsx.plugins.nsx_v.vshield.common import exceptions +from vmware_nsx.plugins.nsx_v.vshield import vcns_driver +from vmware_nsx.services.dynamic_routing.nsx_v import driver as nsxv_bgp +from vmware_nsx.shell.admin.plugins.common import constants +from vmware_nsx.shell.admin.plugins.common import formatters +from vmware_nsx.shell.admin.plugins.common import utils as admin_utils +from vmware_nsx.shell import resources as shell + +LOG = logging.getLogger(__name__) +nsxv = vcns_driver.VcnsDriver([]) + + +def get_ip_prefix(name, ip_address): + return {'ipPrefix': {'name': name, 'ipAddress': ip_address}} + + +def get_redistribution_rule(prefix_name, learner_protocol, from_bgp, from_ospf, + from_static, from_connected, action): + rule = { + 'action': action, + 'from': { + 'ospf': from_ospf, + 'bgp': from_bgp, + 'connected': from_connected, + 'static': from_static + } + } + if prefix_name: + rule['prefixName'] = prefix_name + return {'rule': rule} + + +def _validate_asn(asn): + if not bgp_const.MIN_ASNUM <= int(asn) <= bgp_const.MAX_ASNUM: + msg = "Invalid AS number, expecting an integer value (1 - 65535)." + LOG.error(msg) + return False + return True + + +def _extract_interface_info(info): + portgroup, address = info.split(':') + try: + network = netaddr.IPNetwork(address) + except Exception: + LOG.error("Invalid IP address given: '%s'.", address) + return None + + subnet_mask = str(network.netmask) + ip_address = str(network.ip) + return portgroup, ip_address, subnet_mask + + +def _assemble_gw_edge(name, size, + external_iface_info, internal_iface_info, az): + edge = nsxv._assemble_edge( + name, datacenter_moid=az.datacenter_moid, + deployment_container_id=az.datastore_id, + appliance_size=size, + remote_access=False, edge_ha=az.edge_ha) + appliances = [nsxv._assemble_edge_appliance( + az.resource_pool, az.datastore_id)] + edge['appliances']['appliances'] = appliances + + portgroup, ip_address, subnet_mask = external_iface_info + vnic_external = nsxv._assemble_edge_vnic(vcns_const.EXTERNAL_VNIC_NAME, + vcns_const.EXTERNAL_VNIC_INDEX, + portgroup, + primary_address=ip_address, + subnet_mask=subnet_mask, + type="uplink") + + portgroup, gateway_ip, subnet_mask = internal_iface_info + vnic_internal = nsxv._assemble_edge_vnic(vcns_const.INTERNAL_VNIC_NAME, + vcns_const.INTERNAL_VNIC_INDEX, + portgroup, + primary_address=gateway_ip, + subnet_mask=subnet_mask, + type="internal") + + if (cfg.CONF.nsxv.edge_appliance_user and + cfg.CONF.nsxv.edge_appliance_password): + edge['cliSettings'].update({ + 'userName': cfg.CONF.nsxv.edge_appliance_user, + 'password': cfg.CONF.nsxv.edge_appliance_password}) + + edge['vnics']['vnics'].append(vnic_external) + edge['vnics']['vnics'].append(vnic_internal) + + header = nsxv.vcns.deploy_edge(edge)[0] + edge_id = header.get('location', '/').split('/')[-1] + disable_fw_req = {'featureType': 'firewall_4.0', + 'enabled': False} + nsxv.vcns.update_firewall(edge_id, disable_fw_req) + return edge_id, gateway_ip + + +@admin_utils.output_header +def create_bgp_gw(resource, event, trigger, **kwargs): + """Creates a new BGP GW edge""" + usage = ("nsxadmin -r bgp-gw-edge -o create " + "--property name= " + "--property local-as= " + "--property external-iface=: " + "--property internal-iface=: " + "[--property az-hint=] " + "[--property size=compact,large,xlarge,quadlarge]") + required_params = ('name', 'local-as', + 'internal-iface', 'external-iface') + properties = admin_utils.parse_multi_keyval_opt(kwargs.get('property', [])) + if not properties or not set(required_params) <= set(properties.keys()): + LOG.error(usage) + return + + local_as = properties['local-as'] + if not _validate_asn(local_as): + return + + size = properties.get('size', nsxv_constants.LARGE) + if size not in vcns_const.ALLOWED_EDGE_SIZES: + msg = ("Property 'size' takes one of the following values: %s." + % ','.join(vcns_const.ALLOWED_EDGE_SIZES)) + LOG.error(msg) + return + + external_iface_info = _extract_interface_info(properties['external-iface']) + internal_iface_info = _extract_interface_info(properties['internal-iface']) + if not (external_iface_info and internal_iface_info): + return + + az_hint = properties.get('az-hint') + az = nsx_az.NsxVAvailabilityZones().get_availability_zone(az_hint) + + edge_id, gateway_ip = _assemble_gw_edge(properties['name'], + size, + external_iface_info, + internal_iface_info, + az) + nsxv.add_bgp_speaker_config(edge_id, gateway_ip, local_as, + True, [], [], [], default_originate=True) + + res = {'name': properties['name'], + 'edge_id': edge_id, + 'size': size, + 'availability_zone': az.name, + 'bgp_identifier': gateway_ip, + 'local_as': local_as} + headers = ['name', 'edge_id', 'size', 'bgp_identifier', + 'availability_zone', 'local_as'] + LOG.info(formatters.output_formatter('BGP GW Edge', [res], headers)) + + +def delete_bgp_gw(resource, event, trigger, **kwargs): + usage = ("nsxadmin -r bgp-gw-edge -o delete " + "--property gw-edge-id=") + required_params = ('gw-edge-id', ) + properties = admin_utils.parse_multi_keyval_opt(kwargs.get('property', [])) + if not properties or not set(required_params) <= set(properties.keys()): + LOG.error(usage) + return + edge_id = properties['gw-edge-id'] + try: + nsxv.vcns.delete_edge(edge_id) + except exceptions.ResourceNotFound: + LOG.error("Edge %s was not found", edge_id) + return + + +@admin_utils.output_header +def create_redis_rule(resource, event, trigger, **kwargs): + usage = ("nsxadmin -r routing-redistribution-rule -o create " + "--property gw-edge-ids=[,...] " + "[--property prefix=] " + "--property learner-protocol= " + "--property learn-from=ospf,bgp,connected,static " + "--property action=") + required_params = ('gw-edge-ids', 'learner-protocol', + 'learn-from', 'action') + properties = admin_utils.parse_multi_keyval_opt(kwargs.get('property', [])) + if not properties or not set(required_params) <= set(properties.keys()): + LOG.error(usage) + return + + prefix = properties.get('prefix') + if prefix: + prefix_name, cidr = prefix.split(':') + prefixes = [get_ip_prefix(prefix_name, cidr)] if cidr else [] + else: + prefix_name = None + prefixes = [] + + learn_from = properties['learn-from'].split(',') + + rule = get_redistribution_rule(prefix_name, + properties['learner-protocol'], + 'bgp' in learn_from, + 'ospf' in learn_from, + 'static' in learn_from, + 'connected' in learn_from, + properties['action']) + + edge_ids = properties['gw-edge-ids'].split(',') + for edge_id in edge_ids: + try: + nsxv.add_bgp_redistribution_rules(edge_id, prefixes, [rule]) + except exceptions.ResourceNotFound: + LOG.error("Edge %s was not found", edge_id) + return + + res = [{'edge_id': edge_id, + 'prefix': prefix_name if prefix_name else 'ANY', + 'learner-protocol': properties['learner-protocol'], + 'learn-from': ', '.join(set(learn_from)), + 'action': properties['action']} for edge_id in edge_ids] + + headers = ['edge_id', 'prefix', 'learner-protocol', + 'learn-from', 'action'] + LOG.info(formatters.output_formatter( + 'Routing redistribution rule', res, headers)) + + +def delete_redis_rule(resource, event, trigger, **kwargs): + usage = ("nsxadmin -r routing-redistribution-rule -o delete " + "--property gw-edge-ids=[,...]" + "[--property prefix-name=]") + required_params = ('gw-edge-ids', ) + properties = admin_utils.parse_multi_keyval_opt(kwargs.get('property', [])) + if not properties or not set(required_params) <= set(properties.keys()): + LOG.error(usage) + return + + edge_ids = properties['gw-edge-ids'].split(',') + # If no prefix-name is given then remove rules configured with default + # prefix. + prefixes = [properties.get('prefix-name')] + for edge_id in edge_ids: + try: + nsxv.remove_bgp_redistribution_rules(edge_id, prefixes) + except exceptions.ResourceNotFound: + LOG.error("Edge %s was not found", edge_id) + return + + +@admin_utils.output_header +def add_bgp_neighbour(resource, event, trigger, **kwargs): + usage = ("nsxadmin -r bgp-neighbour -o create " + "--property gw-edge-ids=[,...] " + "--property ip-address= " + "--property remote-as= " + "--property password=") + required_params = ('gw-edge-ids', 'ip-address', 'remote-as', 'password') + properties = admin_utils.parse_multi_keyval_opt(kwargs.get('property', [])) + if not properties or not set(required_params) <= set(properties.keys()): + LOG.error(usage) + return + + remote_as = properties['remote-as'] + if not _validate_asn(remote_as): + return + + peer = { + 'peer_ip': properties['ip-address'], + 'remote_as': properties['remote-as'], + 'password': properties['password'] + } + nbr = nsxv_bgp.bgp_neighbour(peer) + + edge_ids = properties['gw-edge-ids'].split(',') + for edge_id in edge_ids: + try: + nsxv.add_bgp_neighbours(edge_id, [nbr]) + except exceptions.ResourceNotFound: + LOG.error("Edge %s was not found", edge_id) + return + + res = [{'edge_id': edge_id, + 'ip_address': properties['ip-address'], + 'remote_as': properties['remote-as'], + 'hold_down_timer': cfg.CONF.nsxv.bgp_neighbour_hold_down_timer, + 'keep_alive_timer': cfg.CONF.nsxv.bgp_neighbour_keep_alive_timer} + for edge_id in edge_ids] + headers = ['edge_id', 'ip_address', 'remote_as', + 'hold_down_timer', 'keep_alive_timer'] + LOG.info(formatters.output_formatter('New BPG neighbour', + res, headers)) + + +def remove_bgp_neighbour(resource, event, trigger, **kwargs): + usage = ("nsxadmin -r bgp-neighbour -o delete " + "--property gw-edge-ids=[,...] " + "--property ip-address=") + required_params = ('gw-edge-ids', 'ip-address') + properties = admin_utils.parse_multi_keyval_opt(kwargs.get('property', [])) + if not properties or not set(required_params) <= set(properties.keys()): + LOG.error(usage) + return + + peer = { + 'peer_ip': properties['ip-address'], + 'remote_as': '', + 'password': '' + } + nbr = nsxv_bgp.bgp_neighbour(peer) + + edge_ids = properties['gw-edge-ids'].split(',') + for edge_id in edge_ids: + try: + nsxv.remove_bgp_neighbours(edge_id, [nbr]) + except exceptions.ResourceNotFound: + LOG.error("Edge %s was not found", edge_id) + return + + +registry.subscribe(create_bgp_gw, + constants.BGP_GW_EDGE, + shell.Operations.CREATE.value) +registry.subscribe(delete_bgp_gw, + constants.BGP_GW_EDGE, + shell.Operations.DELETE.value) +registry.subscribe(create_redis_rule, + constants.ROUTING_REDIS_RULE, + shell.Operations.CREATE.value) +registry.subscribe(delete_redis_rule, + constants.ROUTING_REDIS_RULE, + shell.Operations.DELETE.value) +registry.subscribe(add_bgp_neighbour, + constants.BGP_NEIGHBOUR, + shell.Operations.CREATE.value) +registry.subscribe(remove_bgp_neighbour, + constants.BGP_NEIGHBOUR, + shell.Operations.DELETE.value) diff --git a/vmware_nsx/shell/resources.py b/vmware_nsx/shell/resources.py index a5346bf4d7..d425cbb2d2 100644 --- a/vmware_nsx/shell/resources.py +++ b/vmware_nsx/shell/resources.py @@ -34,6 +34,8 @@ class Operations(enum.Enum): LIST = 'list' CLEAN = 'clean' CLEAN_ALL = 'clean-all' + CREATE = 'create' + DELETE = 'delete' LIST_MISMATCHES = 'list-mismatches' FIX_MISMATCH = 'fix-mismatch' @@ -104,7 +106,7 @@ nsxv3_resources = { Operations.IMPORT.value, Operations.NSX_LIST.value]), constants.CONFIG: Resource(constants.CONFIG, - [Operations.VALIDATE.value]) + [Operations.VALIDATE.value]), } # Add supported NSX-V resources in this dictionary @@ -160,7 +162,16 @@ nsxv_resources = { constants.ROUTERS: Resource(constants.ROUTERS, [Operations.NSX_RECREATE.value]), constants.CONFIG: Resource(constants.CONFIG, - [Operations.VALIDATE.value]) + [Operations.VALIDATE.value]), + constants.BGP_GW_EDGE: Resource(constants.BGP_GW_EDGE, + [Operations.CREATE.value, + Operations.DELETE.value]), + constants.ROUTING_REDIS_RULE: Resource(constants.ROUTING_REDIS_RULE, + [Operations.CREATE.value, + Operations.DELETE.value]), + constants.BGP_NEIGHBOUR: Resource(constants.BGP_NEIGHBOUR, + [Operations.CREATE.value, + Operations.DELETE.value]) } nsxv3_resources_names = list(nsxv3_resources.keys())