diff --git a/python/avi/sdk/samples/clone_vs.md b/python/avi/sdk/samples/clone_vs.md index 9f76d42525..9d9b22a4d0 100644 --- a/python/avi/sdk/samples/clone_vs.md +++ b/python/avi/sdk/samples/clone_vs.md @@ -72,18 +72,20 @@ Specify the VIP as a subnet/mask. This must match an auto-allocation subnet in I If the source VIP was auto-allocated, the target can simply inherit the auto-allocation network: -> -v * +> -v auto (or -v *) + +(Note: On Linux/Unix/Mac systems, use the `auto` option as `*` would need to be escaped to prevent it being treated as a filename glob) ### Specifying public/elastic/floating IP for clouds that support this (e.g. public clouds, OpenStack) -Separate the public/floating IP using a `;`. A static public/floating IP can be specified explicitly, or o auto-allocate a public IP, use the `auto` keyword: +Separate the public/floating IP using a `;`. A static public/floating IP can be specified explicitly, or to auto-allocate a public IP, use the `auto` keyword: > -v 10.10.10.0/24;203.0.113.100 > -v 10.10.10.0/24;auto ### Avi Internal IPAM -When using Avi Internal IPAM for auto-allocation, it may be necessary in some clouds (e.g. NSX-T Cloud) to supply the `-int` parameter to ensure the VsVip is populated with all the correct fields. Other clouds (e.g. vCenter Cloud) are more forgiving and usually work without specifying this parameter. +When using Avi Internal IPAM for auto-allocation, it may be necessary in some clouds (e.g. NSX-T Cloud) to supply the `-int` parameter to ensure the VsVip is populated with all the correct fields. Other clouds (e.g. vCenter Cloud) are more forgiving and generally work without specifying this parameter if there is only a single IPAM subnet specified. ## Special flags @@ -212,17 +214,17 @@ If cloning a Virtual Service between Controllers or to a different tenant, the d A WAF Policy and its referenced PSM groups can be forced cloned using the -fc flag. This supports the scenarios where the cloned VS should have its own WAF Policy rather than sharing the same WAF policy (including the case where learning is enabled). -In this case, if learning is enabled in the source WAF Policy, it will remain enabled in the cloned WAF Policy resulting in indepdent learning for the cloned VS. +In this case, if learning is enabled in the source WAF Policy, it will remain enabled in the cloned WAF Policy resulting in independent learning for the cloned VS. The below example clones a VS and forces cloning of the WAF Policy and any PSM groups also. -> clone_vs.py -c controller1.acme.com -fc vs-wafpolicy,positive-security-model vs example cloned-example -v * +> clone_vs.py -c controller1.acme.com -fc vs-wafpolicy,positive-security-model vs example cloned-example -v auto ### Disabling learning in the cloned WAF Policy It may desirable to disable learning for the cloned WAF Policy and its referenced PSM groups, for example if the source Virtual Service was used for learning and the cloned Virtual Service will be an instance of the same application, but independent learning is not desired. This can be achieved with the option `-flags disablelearning`: -> clone_vs.py -c controller1.acme.com -fc vs-wafpolicy,positive-security-model -flags disablelearning vs example cloned-example -v * +> clone_vs.py -c controller1.acme.com -fc vs-wafpolicy,positive-security-model -flags disablelearning vs example cloned-example -v auto This flag can also be used when cloning a WAF Policy individually: @@ -300,3 +302,33 @@ Changelog: 2.0.2: * Added support for flexibly handling specification of pool placement networks for cloned pools + +2.0.3: + +* Added some additional reference handling for less-common DataScript and WAF Profile configurations + +2.0.4: + +* Fixed VRF handling for cloning to/from NSX-T Cloud in certain scenarios + +2.0.5: + +* Fixed VRF handling with manual VsVip + +2.0.6: + +* Changed handling of Pool and PoolGroup references in VsDataScriptSet + * Will now try to preserve the names of Pools and PoolGroups when cloning a VsDataScriptSet if possible (e.g. when cloning to a different tenant/Controller) + * If name preservation is not possible, unique names will now be generated from the old Pool/PoolGroup name (previous behaviour was to generate names based on VsDataScript name) + * Will generate a warning that DataScript code changes may be needed, for example because a Pool or PoolGroup name could not be preserved +* Changed handling of PoolGroup cloning + * Will now try to preserve the names of Pools when cloning a PoolGroup if possible (e.g. when cloning to a different tenant/Controller) + * If name preservation is not possible, unique names will now be generated from the old Pool name (previous behaviour was to generate names based on PoolGroup name) + +2.0.7: + +* Add support for cloning HealthMonitors with SSL attributes or authentication attributes (authentication attributes must be manually re-entered) +* Add support for cloning AuthProfiles for OAuth2 +* Add support for cloning VS with OAuth2 SSO configuration +* Add support for cloning VS with service-level network/application profile overrides +* Add support for cloning DNS VS with Topology Policies diff --git a/python/avi/sdk/samples/clone_vs.py b/python/avi/sdk/samples/clone_vs.py index 3d3f8ca1fe..f2172c1a71 100644 --- a/python/avi/sdk/samples/clone_vs.py +++ b/python/avi/sdk/samples/clone_vs.py @@ -30,7 +30,7 @@ urllib3.disable_warnings() -AVICLONE_VERSION = [2, 0, 2] +AVICLONE_VERSION = [2, 0, 7] # Try to obtain the terminal width to allow spprint() to wrap output neatly. # If unable to determine, assume terminal width is 70 characters @@ -64,13 +64,23 @@ class AviClone: 'pool-pkiprofile': 'pki_profile_ref', 'pool-sslcert': 'ssl_key_and_certificate_ref', 'pool-analyticsprofile': 'analytics_profile_ref', - 'pool-autoscalepolicy': 'autoscale_policy_ref'} + 'pool-autoscalepolicy': 'autoscale_policy_ref' + } VALID_DATASCRIPT_REF_OBJECTS = { 'ds-ipgroup': 'ipgroup_refs', - 'ds-stringgroup': 'string_group_refs'} + 'ds-stringgroup': 'string_group_refs', + 'ds-protocolparser': 'protocol_parser_refs', + 'ds-sslprofile': 'ssl_profile_refs', + 'ds-pkiprofile': 'pki_profile_refs', + 'ds-geodb': 'geo_db_ref', + 'ds-ipreputation': 'ip_reputation_db_ref' + } VALID_POLICYSET_REF_OBJECTS = { 'policy-ipgroup': 'group_refs', - 'policy-stringgroup': 'string_group_refs'} + 'policy-stringgroup': 'string_group_refs', + 'policy-geodb': 'geo_db_ref', + 'policy-ipreputation': 'ip_reputation_db_ref' + } VALID_VS_REF_OBJECTS = { 'vs-appprofile': 'application_profile_ref', 'vs-networkprofile': 'network_profile_ref', @@ -84,8 +94,14 @@ class AviClone: 'vs-wafpolicy': 'waf_policy_ref', 'vs-rewritablecontent': 'content_rewrite/rewritable_content_ref', 'vs-authprofile': 'client_auth/auth_profile_ref', + 'vs-ssoauthprofile': 'sso_policy/default_auth_profile_ref', 'vs-ssopolicy': 'sso_policy_ref', - 'vs-botpolicy': 'bot_policy_ref'} + 'vs-botpolicy': 'bot_policy_ref' + } + VALID_VS_OVERRIDE_REF_OBJECTS = { + 'vsoverride-appprofile': 'override_application_profile_ref', + 'vsoverride-networkprofile': 'override_network_profile_ref', + } VALID_GS_REF_OBJECTS = { 'gs-persistency': 'application_persistence_profile_ref', 'gs-healthmonitor': 'health_monitor_refs' @@ -99,17 +115,35 @@ class AviClone: 'http_profile/compression_profile/compressible_content_ref', 'appprofile-compressibleipaddrgroup': 'ip_addrs_ref', 'appprofile-compressibledevices': 'devices_ref'} - VALID_WAFPOLICY_REF_OBJECTS = {'waf-profile': 'waf_profile_ref', - 'waf-crs': 'waf_crs_ref', - 'positive-security-model': - 'positive_security_model/group_refs'} - VALID_WAFPOLICYPSMGROUP_REF_OBJECTS = {'wafpsm-stringgroup': - 'match_value_string_group_ref'} + VALID_WAFPOLICY_REF_OBJECTS = { + 'waf-profile': 'waf_profile_ref', + 'waf-crs': 'waf_crs_ref', + 'positive-security-model': 'positive_security_model/group_refs', + 'waf-appsignatures': 'application_signatures/provider_ref' + } + VALID_WAFPOLICYPSMGROUP_REF_OBJECTS = { + 'wafpsm-stringgroup': 'match_value_string_group_ref' + } VALID_SSLCERT_REF_OBJECTS = { 'ssl-certmgmt': 'certificate_management_profile_ref', - 'ssl-hsmgroup': 'hardwaresecuritymodulegroup_ref'} + 'ssl-hsmgroup': 'hardwaresecuritymodulegroup_ref' + } VALID_SSOPOLICY_REF_OBJECTS = { - 'sso-authprofile': 'authentication_policy/default_auth_profile_ref'} + 'sso-authprofile': 'authentication_policy/default_auth_profile_ref' + } + VALID_OAUTHSETTINGS_REF_OBJECTS = { + 'oauth-authprofile': 'auth_profile_ref' + } + VALID_AUTHPROFILE_REF_OBJECTS = { + 'authprofile-pool': 'oauth_profile/pool_ref', + 'authprofile-jwtprofile': 'jwt_profile_ref' + } + VALID_HEALTHMONITOR_REF_OBJECTS = { + 'hm-sslprofile': 'https_monitor/ssl_attributes/ssl_profile_ref', + 'hm-pkiprofile': 'https_monitor/ssl_attributes/pki_profile_ref', + 'hm-sslkeyandcertificate': + 'https_monitor/ssl_attributes/ssl_key_and_certificate_ref' + } def __init__(self, source_api, dest_api=None, flags=None, tenant=None, other_tenant=None, other_cloud=None, @@ -331,7 +365,8 @@ def get_new_name(self, object_type, new_name, force_unique_name=False): return new_name def clone_object(self, old_name, new_name, object_type=None, - force_clone=None, force_unique_name=False): + force_clone=None, force_unique_name=False, + old_obj=None): """ Clones an object other than a Virtual Service or GSLB Service @@ -341,14 +376,18 @@ def clone_object(self, old_name, new_name, object_type=None, Returns a tuple: json representation of the cloned object, list of additional objects created if any and any warnings generated - :param old_name: Name of existing object (name or uri) + :param old_name: Name of existing object (name or uri), or None if + existing object is being passed via old_obj :param new_name: New name for cloned object - :param object_type: Type of object (or None to infer from name) + :param object_type: Type of object (or None to infer from old_name or + old_obj) :param force_clone: List of referenced object attributes to forcibly clone rather than re-use (for example health_monitor_refs) :param force_unique_name: Resolve destination name conflicts by appending an index number + :param old_obj: Existing object to clone - can be specified instead + of passing old_name if the object is already available :return: tuple - json representation of the cloned object, list of additional objects created if any :rtype: tuple @@ -359,35 +398,50 @@ def clone_object(self, old_name, new_name, object_type=None, created_objs = [] warnings = [] - if not object_type: - # If object_type is not specified, assume the old_name is in - # form object_type/uuid - if '/' in old_name: - object_type = old_name.split('/')[0] - else: - raise ValueError('Unable to determine object type for %s' - % object_type) - - logger.debug('Cloning %s "%s" to "%s"', object_type, - old_name, new_name) - - if old_name.startswith(object_type + '/'): - old_obj = self.api.get(old_name, tenant_uuid=self.tenant_uuid, - params=('export_key=true' - if object_type == 'sslkeyandcertificate' - else None)).json() + if old_obj: old_name = old_obj['name'] + if not object_type: + if 'url' in old_obj: + object_type = old_obj['url'].split('/api/')[1].split('/')[0] + else: + raise ValueError('Unable to determine object type for %s' + % old_name) else: - old_obj = self.api.get_object_by_name(object_type, old_name, - tenant_uuid=self.tenant_uuid, - params=({'export_key': True} - if object_type == 'sslkeyandcertificate' - else None)) + if not object_type: + # If object_type is not specified, assume the old_name is in + # form object_type/uuid + if '/' in old_name: + object_type = old_name.split('/')[0] + else: + raise ValueError('Unable to determine object type for %s' + % old_name) + + if old_name.startswith(object_type + '/'): + old_obj = self.api.get(old_name, tenant_uuid=self.tenant_uuid, + params=('export_key=true' + if object_type == 'sslkeyandcertificate' + else None)).json() + old_name = old_obj['name'] + else: + old_obj = self.api.get_object_by_name(object_type, old_name, + tenant_uuid=self.tenant_uuid, + params=({'export_key': True} + if object_type == 'sslkeyandcertificate' + else None)) if not old_obj: raise Exception('Object of type %s named %s could not be found' % (object_type, old_name)) + if not new_name: + # If new_name is not specified, assume new object should use the + # name of the source object (force_unique_name may still cause + # the new name to then be uniquified) + new_name = old_name + + logger.debug('Cloning %s "%s" to "%s"', object_type, + old_name, new_name) + new_name = self.get_new_name(object_type, new_name, force_unique_name) # Remove unique attributes and rename object @@ -566,9 +620,11 @@ def _processobject_pool(self, obj, force_clone): # Remove tier1_lr in case source was NSX-T Cloud with # overlay obj['vrf_ref'] = self.ov_obj['url'] + obj.pop('tier1_lr', None) elif self.other_vrf: # Case where target is NSX-T Cloud with overlay obj['tier1_lr'] = self.other_vrf + obj.pop('vrf_ref', None) try: valid_ref_objects = self.VALID_POOL_REF_OBJECTS @@ -600,8 +656,6 @@ def _processobject_poolgroup(self, obj, force_clone): logger.debug('Running _processobject_poolgroup') - new_pool_group_name = obj['name'] - created_objs = [] warnings = [] @@ -611,11 +665,9 @@ def _processobject_poolgroup(self, obj, force_clone): for member in obj['members']: if 'pool_ref' in member: p_path = member['pool_ref'].split('/api/')[1] - new_pool_name = '-'.join([new_pool_group_name, - 'pool', str(count)]) p_obj, p_created_objs, p_warnings = self.clone_object( - old_name=p_path, new_name=new_pool_name, + old_name=p_path, new_name=None, force_clone=force_clone, force_unique_name=True) @@ -760,6 +812,24 @@ def _processobject_authprofile(self, obj, force_clone): '%s cannot be cloned and must be re-entered ' 'manually.' % obj['name']) + try: + valid_ref_objects = self.VALID_AUTHPROFILE_REF_OBJECTS + + # Process generic references, re-using or cloning referenced + # objects as necessary + + created_objs, warnings = self._process_refs(parent_obj=obj, + refs=valid_ref_objects, + force_clone=force_clone) + + except Exception as ex: + # If an exception occurred, delete any intermediate objects we + # have created + + self.delete_objects(created_objs) + + raise + return created_objs, warnings def _processobject_ssopolicy(self, obj, force_clone): @@ -793,6 +863,44 @@ def _processobject_ssopolicy(self, obj, force_clone): return created_objs, warnings + def _processobject_healthmonitor(self, obj, force_clone): + """ + Performs healthmonitor-specific manipulations on the cloned + object + """ + + logger.debug('Running _process_healthmonitor') + + created_objs = [] + warnings = [] + + if 'authentication' in obj: + obj['authentication']['username'] = 'placeholder' + obj['authentication']['password'] = 'placeholder' + warnings.append('The authentication username and password ' + 'referenced in healthmonitor %s cannot be cloned ' + 'and must be re-entered manually.' % obj['name']) + + try: + valid_ref_objects = self.VALID_HEALTHMONITOR_REF_OBJECTS + + # Process generic references, re-using or cloning referenced + # objects as necessary + + created_objs, warnings = self._process_refs(parent_obj=obj, + refs=valid_ref_objects, + force_clone=force_clone) + + except Exception as ex: + # If an exception occurred, delete any intermediate objects we + # have created + + self.delete_objects(created_objs) + + raise + + return created_objs, warnings + def _processobject_wafpolicy(self, obj, force_clone): """ Performs wafpolicy-specific manipulations on the cloned @@ -886,11 +994,17 @@ def _processobject_sslkeyandcertificate(self, obj, force_clone): # Remove cross-tenant references logger.debug('Removing ca_certs references') obj.pop('ca_certs', None) - obj.pop('key_base64', None) - obj.pop('certificate_base64', None) + obj.pop('key_base64', None) + obj.pop('certificate_base64', None) obj.pop('ocsp_error_status', None) + if obj.get('key', '') == '': + # Workaround for an issue where CA certs may return a value + # of for a key even with export_key=true and even + # though there is no key. + obj.pop('key', None) + if 'key_passphrase' in obj: old_name = obj['_clonevs_old_name'] obj['key_passphrase'] = self.ssl_key_pps.get(old_name, '') @@ -1069,7 +1183,7 @@ def _processobject_vsdatascriptset(self, obj, force_clone): logger.debug('Running _process_vsdatascriptset') - new_vsdatascriptset_name = obj['name'] + vsdatascriptset_name = obj['name'] created_objs = [] warnings = [] @@ -1084,15 +1198,31 @@ def _processobject_vsdatascriptset(self, obj, force_clone): p_obj_url = self.clone_track[pool_ref] logger.debug('Reusing previously cloned object %s', p_obj_url) + warnings.append('VsDataScriptSet "%s" may require ' + 'code changes due to re-use of cloned ' + 'Pool.' % vsdatascriptset_name) else: # Otherwise, clone the pool p_path = pool_ref.split('/api/')[1] - p_name = '-'.join([obj['name'], 'pool']) + old_pobj = self.api.get(p_path, + tenant_uuid=self.tenant_uuid).json() + old_pname = old_pobj['name'] + p_obj, p_created_objs, p_warnings = self.clone_object( - old_name=p_path, new_name=p_name, + old_name=old_pname, new_name=old_pname, force_clone=force_clone, - force_unique_name=True) + force_unique_name=True, + old_obj=old_pobj) + + new_pname = p_obj['name'] + + if old_pname != new_pname: + warnings.append('VsDataScriptSet "%s" may require ' + 'code changes due to cloned pool ' + 'name changing from "%s" to "%s".' + % (vsdatascriptset_name, + old_pname, new_pname)) created_objs.append(p_obj) created_objs.extend(p_created_objs) @@ -1110,14 +1240,31 @@ def _processobject_vsdatascriptset(self, obj, force_clone): pg_obj_url = self.clone_track[pool_group_ref] logger.debug('Reusing previously cloned object %s', pg_obj_url) + warnings.append('VsDataScriptSet "%s" may require ' + 'code changes due to re-use of cloned ' + 'PoolGroup.' % vsdatascriptset_name) else: pg_path = pool_group_ref.split('/api/')[1] - pg_name = '-'.join([obj['name'], 'poolgroup']) + old_pgobj = self.api.get(pg_path, + tenant_uuid=self.tenant_uuid).json() + old_pgname = old_pgobj['name'] + (pg_obj, pg_created_objs, pg_warnings) = self.clone_object( - old_name=pg_path, new_name=pg_name, + old_name=old_pgname, new_name=old_pgname, force_clone=force_clone, - force_unique_name=True) + force_unique_name=True, + old_obj=old_pgobj) + + new_pgname = pg_obj['name'] + + if old_pgname != new_pgname: + warnings.append('VsDataScriptSet "%s" may require ' + 'code changes due to cloned ' + 'poolgroup name changing from ' + '"%s" to "%s".' + % (vsdatascriptset_name, + old_pgname, new_pgname)) created_objs.append(pg_obj) created_objs.extend(pg_created_objs) @@ -1135,6 +1282,11 @@ def _processobject_vsdatascriptset(self, obj, force_clone): refs=valid_ref_objects, force_clone=force_clone) + if new_objs: + warnings.append('VsDataScriptSet "%s" may require code changes ' + 'due to one or more cloned references.' + % vsdatascriptset_name) + created_objs.extend(new_objs) warnings.extend(new_warnings) @@ -2126,6 +2278,18 @@ def clone_vs(self, old_vs_name, new_vs_name, enable_vs=False, v_obj['pool_group_ref'] = pg_obj['url'] + # Handle service overrides + + if 'services' in v_obj: + valid_s_ref_objects = self.VALID_VS_OVERRIDE_REF_OBJECTS + for service in v_obj['services']: + (s_created_objs, + s_warnings) = self._process_refs(parent_obj=service, + refs=valid_s_ref_objects, + force_clone=force_clone) + created_objs.extend(s_created_objs) + warnings.extend(s_warnings) + # Remove unique atributes and rename v_obj.pop('uuid', None) @@ -2154,12 +2318,33 @@ def clone_vs(self, old_vs_name, new_vs_name, enable_vs=False, warnings.append('VS has a SAML configuration that will need to ' 'be manually updated.') + # Handle OAuth Configuration + + if 'oauth_settings' in v_obj.get('oauth_vs_config', {}): + oauth_settings = v_obj['oauth_vs_config']['oauth_settings'] + valid_o_ref_objects = self.VALID_OAUTHSETTINGS_REF_OBJECTS + for oauth_setting in oauth_settings: + + (oa_created_objs, + oa_warnings) = self._process_refs(parent_obj=oauth_setting, + refs=valid_o_ref_objects, + force_clone=force_clone) + created_objs.extend(oa_created_objs) + warnings.extend(oa_warnings) + + app_settings = oauth_setting.get('app_settings', {}) + if app_settings.get('client_secret', None): + app_settings['client_secret'] = 'placeholder' + warnings.append('VS has an OAuth App Client Secret ' + 'that will need to be manually ' + 'updated.') + # (Try to!) move the new Virtual Service and VsVip # to a different cloud if self.oc_obj: v_obj['cloud_ref'] = self.oc_obj['url'] - if vsvip_obj: + if vsvip_obj and not(manual_vsvip): vsvip_obj['cloud_ref'] = self.oc_obj['url'] v_obj.pop('cloud_type', None) @@ -2176,17 +2361,19 @@ def clone_vs(self, old_vs_name, new_vs_name, enable_vs=False, # If moving to a different cloud, Virtual Service will be moved # to the default global VRF in the target cloud unless a # specific target VRF is specified. - if vsvip_obj: + if vsvip_obj and not(manual_vsvip): vsvip_obj.pop('vrf_context_ref', None) vsvip_obj.pop('tier1_lr', None) # Update VRF or T1_LR reference if a target VRF is specified - if vsvip_obj: + if vsvip_obj and not(manual_vsvip): if self.ov_obj: vsvip_obj['vrf_context_ref'] = self.ov_obj['url'] + vsvip_obj.pop('tier1_lr', None) elif self.other_vrf: # Case where target is NSX-T Cloud with overlay vsvip_obj['tier1_lr'] = self.other_vrf + vsvip_obj.pop('vrf_context_ref', None) if new_segroup: # Locate SE group by name in the appropriate cloud @@ -2230,24 +2417,26 @@ def clone_vs(self, old_vs_name, new_vs_name, enable_vs=False, created_objs.extend(ps_created_objs) warnings.extend(ps_warnings) - # Clone any DNS policy sets referenced in the VS + # Clone any DNS / Topology Policy sets referenced in the VS - if 'dns_policies' in v_obj: - for polset in v_obj['dns_policies']: - ps_path = polset['dns_policy_ref'].split('/api/')[1] - ps_name = '-'.join([new_vs_name, - (c_obj['name'] - if self.oc_obj is None - else self.oc_obj['name']), - 'DNS-Policy']) - ps_obj, ps_created_objs, ps_warnings = self.clone_object( - old_name=ps_path, new_name=ps_name, - force_clone=force_clone, force_unique_name=True) + dns_topo_policies = (v_obj.get('dns_policies', []) + + v_obj.get('topology_policies', [])) - polset['dns_policy_ref'] = ps_obj['url'] - created_objs.append(ps_obj) - created_objs.extend(ps_created_objs) - warnings.extend(ps_warnings) + for polset in dns_topo_policies: + ps_path = polset['dns_policy_ref'].split('/api/')[1] + ps_name = '-'.join([new_vs_name, + (c_obj['name'] + if self.oc_obj is None + else self.oc_obj['name']), + 'DNS-Policy']) + ps_obj, ps_created_objs, ps_warnings = self.clone_object( + old_name=ps_path, new_name=ps_name, + force_clone=force_clone, force_unique_name=True) + + polset['dns_policy_ref'] = ps_obj['url'] + created_objs.append(ps_obj) + created_objs.extend(ps_created_objs) + warnings.extend(ps_warnings) # Clone network security policy referenced in the VS @@ -2310,6 +2499,7 @@ def clone_vs(self, old_vs_name, new_vs_name, enable_vs=False, if vsvip_obj: if manual_vsvip: v_obj['vsvip_ref'] = vsvip_obj['url'] + v_obj['vrf_context_ref'] = vsvip_obj['vrf_context_ref'] else: # Create new vsvip object @@ -2333,9 +2523,13 @@ def clone_vs(self, old_vs_name, new_vs_name, enable_vs=False, v_obj['vsvip_ref'] = new_vsvip_obj['url'] # Set VS VRF Context to match VsVip VRF Context - v_obj['vrf_context_ref'] = new_vsvip_obj['vrf_context_ref'] + if 'tier1_lr' in vsvip_obj: + # If target is NSX-T Cloud with overlay, allow + # Controller to manage VS's VRF from VsVip tier_1 config + v_obj.pop('vrf_context_ref', None) + # Try to create the new VS (possibly in a different tenant to the # source) @@ -2396,6 +2590,7 @@ def clone_vs(self, old_vs_name, new_vs_name, enable_vs=False, valid_refs = sorted( set(AviClone.VALID_VS_REF_OBJECTS.keys()) | + set(AviClone.VALID_VS_OVERRIDE_REF_OBJECTS.keys()) | set(AviClone.VALID_GS_REF_OBJECTS.keys()) | set(AviClone.VALID_WAFPOLICY_REF_OBJECTS.keys()) | set(AviClone.VALID_WAFPOLICYPSMGROUP_REF_OBJECTS.keys()) | @@ -2403,7 +2598,10 @@ def clone_vs(self, old_vs_name, new_vs_name, enable_vs=False, set(AviClone.VALID_POLICYSET_REF_OBJECTS.keys()) | set(AviClone.VALID_DATASCRIPT_REF_OBJECTS.keys()) | set(AviClone.VALID_APPLICATIONPROFILE_REF_OBJECTS.keys()) | - set(AviClone.VALID_SSLCERT_REF_OBJECTS.keys())) + set(AviClone.VALID_SSLCERT_REF_OBJECTS.keys()) | + set(AviClone.VALID_SSOPOLICY_REF_OBJECTS.keys()) | + set(AviClone.VALID_HEALTHMONITOR_REF_OBJECTS.keys()) | + set(AviClone.VALID_OAUTHSETTINGS_REF_OBJECTS.keys())) parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter,