@ -16,9 +16,11 @@
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
ANSIBLE_METADATA = { ' metadata_version ' : ' 1.0 ' ,
' status ' : [ ' preview ' ] ,
' supported_by ' : ' community ' }
ANSIBLE_METADATA = {
' metadata_version ' : ' 1.0 ' ,
' status ' : [ ' preview ' ] ,
' supported_by ' : ' community '
}
DOCUMENTATION = '''
@ -28,146 +30,116 @@ extends_documentation_fragment: nxos
version_added : " 2.2 "
short_description : Manages Cisco EVPN VXLAN Network Identifier ( VNI ) .
description :
- Manages Cisco Ethernet Virtual Private Network ( EVPN ) VXLAN Network
Identifier ( VNI ) configurations of a Nexus device .
- Manages Cisco Ethernet Virtual Private Network ( EVPN ) VXLAN Network
Identifier ( VNI ) configurations of a Nexus device .
author : Gabriele Gerbino ( @GGabriele )
notes :
- default , where supported , restores params default value .
- RD override is not permitted . You should set it to the default values
first and then reconfigure it .
- C ( route_target_both ) , C ( route_target_import ) and
C ( route_target_export valid ) values are a list of extended communities ,
( i . e . [ ' 1.2.3.4:5 ' , ' 33:55 ' ] ) or the keywords ' auto ' or ' default ' .
- The C ( route_target_both ) property is discouraged due to the inconsistent
behavior of the property across Nexus platforms and image versions .
For this reason it is recommended to use explicit C ( route_target_export )
and C ( route_target_import ) properties instead of C ( route_target_both ) .
- RD valid values are a string in one of the route - distinguisher formats ,
the keyword ' auto ' , or the keyword ' default ' .
- default , where supported , restores params default value .
- RD override is not permitted . You should set it to the default values
first and then reconfigure it .
- C ( route_target_both ) , C ( route_target_import ) and
C ( route_target_export valid ) values are a list of extended communities ,
( i . e . [ ' 1.2.3.4:5 ' , ' 33:55 ' ] ) or the keywords ' auto ' or ' default ' .
- The C ( route_target_both ) property is discouraged due to the inconsistent
behavior of the property across Nexus platforms and image versions .
For this reason it is recommended to use explicit C ( route_target_export )
and C ( route_target_import ) properties instead of C ( route_target_both ) .
- RD valid values are a string in one of the route - distinguisher formats ,
the keyword ' auto ' , or the keyword ' default ' .
options :
vni :
description :
- The EVPN VXLAN Network Identifier .
required : true
default : null
route_distinguisher :
description :
- The VPN Route Distinguisher ( RD ) . The RD is combined with
the IPv4 or IPv6 prefix learned by the PE router to create a
globally unique address .
required : true
default : null
route_target_both :
description :
- Enables / Disables route - target settings for both import and
export target communities using a single property .
required : false
default : null
route_target_import :
description :
- Sets the route - target ' import ' extended communities .
required : false
default : null
route_target_export :
description :
- Sets the route - target ' import ' extended communities .
required : false
default : null
state :
description :
- Determines whether the config should be present or not
on the device .
required : false
default : present
choices : [ ' present ' , ' absent ' ]
vni :
description :
- The EVPN VXLAN Network Identifier .
required : true
default : null
route_distinguisher :
description :
- The VPN Route Distinguisher ( RD ) . The RD is combined with
the IPv4 or IPv6 prefix learned by the PE router to create a
globally unique address .
required : true
default : null
route_target_both :
description :
- Enables / Disables route - target settings for both import and
export target communities using a single property .
required : false
default : null
route_target_import :
description :
- Sets the route - target ' import ' extended communities .
required : false
default : null
route_target_export :
description :
- Sets the route - target ' import ' extended communities .
required : false
default : null
state :
description :
- Determines whether the config should be present or not
on the device .
required : false
default : present
choices : [ ' present ' , ' absent ' ]
'''
EXAMPLES = '''
- nxos_evpn_vni :
- name : vni configuration
nxos_evpn_vni :
vni : 6000
route_distinguisher : " 60:10 "
route_target_import :
- " 5000:10 "
- " 4100:100 "
- " 5000:10 "
- " 4100:100 "
route_target_export : auto
route_target_both : default
username : " {{ un }} "
password : " {{ pwd }} "
host : " {{ inventory_hostname }} "
'''
RETURN = '''
proposed :
description : k / v pairs of parameters passed into module
returned : verbose mode
type : dict
sample : { " route_target_import " : [ " 5000:10 " , " 4100:100 " ,
" 5001:10 " ] , " vni " : " 6000 " }
existing :
description : k / v pairs of existing EVPN VNI configuration
returned : verbose mode
type : dict
sample : { " route_distinguisher " : " 70:10 " , " route_target_both " : [ ] ,
" route_target_export " : [ ] , " route_target_import " : [
" 4100:100 " , " 5000:10 " ] , " vni " : " 6000 " }
end_state :
description : k / v pairs of EVPN VNI configuration after module execution
returned : verbose mode
type : dict
sample : { " route_distinguisher " : " 70:10 " , " route_target_both " : [ ] ,
" route_target_export " : [ ] , " route_target_import " : [
" 4100:100 " , " 5000:10 " , " 5001:10 " ] , " vni " : " 6000 " }
updates :
commands :
description : commands sent to the device
returned : always
type : list
sample : [ " evpn " , " vni 6000 l2 " , " route-target import 5001:10 " ]
changed :
description : check to see if a change was made on the device
returned : always
type : boolean
sample : true
'''
import re
import time
from ansible . module_utils . nxos import get_config , load_config , run_commands
from ansible . module_utils . nxos import nxos_argument_spec , check_args
from ansible . module_utils . basic import AnsibleModule
from ansible . module_utils . netcfg import CustomNetworkConfig
PARAM_TO_COMMAND_KEYMAP = {
' vni ' : ' vni ' ,
' route_distinguisher ' : ' rd ' ,
' route_target_both ' : ' route-target both ' ,
' route_target_import ' : ' route-target import ' ,
' route_target_export ' : ' route-target export ' ,
' route_distinguisher ' : ' rd '
' route_target_export ' : ' route-target export '
}
WARNINGS = [ ]
import time
def invoke ( name , * args , * * kwargs ) :
func = globals ( ) . get ( name )
if func :
return func ( * args , * * kwargs )
def get_value ( arg , config , module ) :
REGEX = re . compile ( r ' (?: {0} \ s)(?P<value>.*)$ ' . format ( PARAM_TO_COMMAND_KEYMAP [ arg ] ) , re . M )
command = PARAM_TO_COMMAND_KEYMAP . get ( arg )
command_re = re . compile ( r ' (?: {0} \ s)(?P<value>.*)$ ' . format ( command ) , re . M )
value = ' '
if PARAM_TO_COMMAND_KEYMAP[ arg ] in config :
value = REGEX . search ( config ) . group ( ' value ' )
if command in config :
value = command_re . search ( config ) . group ( ' value ' )
return value
def get_route_target_value ( arg , config , module ) :
splitted_config = config . splitlines ( )
value_list = [ ]
REGEX = re . compile ( r ' (?: {0} \ s)(?P<value>.*)$ ' . format ( PARAM_TO_COMMAND_KEYMAP [ arg ] ) , re . M )
command = PARAM_TO_COMMAND_KEYMAP . get ( arg )
command_re = re . compile ( r ' (?: {0} \ s)(?P<value>.*)$ ' . format ( command ) , re . M )
for line in splitted_config :
value = ' '
if PARAM_TO_COMMAND_KEYMAP[ arg ] in line . strip ( ) :
value = REGEX . search ( line ) . group ( ' value ' )
if command in line . strip ( ) :
value = command_re . search ( line ) . group ( ' value ' )
value_list . append ( value )
return value_list
@ -197,14 +169,10 @@ def get_existing(module, args):
def apply_key_map ( key_map , table ) :
new_dict = { }
for key , value in table . items ( ) :
for key in table :
new_key = key_map . get ( key )
if new_key :
value = table . get ( key )
if value :
new_dict [ new_key ] = value
else :
new_dict [ new_key ] = value
new_dict [ new_key ] = table . get ( key )
return new_dict
@ -236,26 +204,25 @@ def state_present(module, existing, proposed):
if existing_value :
for target in existing_value :
commands . append ( ' no {0} {1} ' . format ( key , target ) )
else :
if not isinstance ( value , list ) :
value = [ value ]
for target in value :
if existing :
if target not in existing . get ( key . replace ( ' - ' , ' _ ' ) . replace ( ' ' , ' _ ' ) ) :
commands . append ( ' {0} {1} ' . format ( key , target ) )
else :
elif not isinstance ( value , list ) :
value = [ value ]
for target in value :
if existing :
if target not in existing . get ( key . replace ( ' - ' , ' _ ' ) . replace ( ' ' , ' _ ' ) ) :
commands . append ( ' {0} {1} ' . format ( key , target ) )
else :
commands . append ( ' {0} {1} ' . format ( key , target ) )
elif value == ' default ' :
existing_value = existing_commands . get ( key )
if existing_value :
commands . append ( ' no {0} {1} ' . format ( key , existing_value ) )
else :
if value == ' default ' :
existing_value = existing_commands . get ( key )
if existing_value :
commands . append ( ' no {0} {1} ' . format ( key , existing_value ) )
else :
command = ' {0} {1} ' . format ( key , value )
commands . append ( command )
command = ' {0} {1} ' . format ( key , value )
commands . append ( command )
if commands :
parents = [ ' evpn ' , ' vni {0} l2 ' . format ( module . params [ ' vni ' ] ) ]
else :
commands = [ ' vni {0} l2 ' . format ( module . params [ ' vni ' ] ) ]
parents = [ ' evpn ' ]
return commands , parents
@ -266,13 +233,6 @@ def state_absent(module, existing, proposed):
return commands , parents
def execute_config ( module , candidate ) :
result = { }
response = load_config ( module , candidate )
result . update ( response )
return result
def main ( ) :
argument_spec = dict (
vni = dict ( required = True , type = ' str ' ) ,
@ -280,8 +240,7 @@ def main():
route_target_both = dict ( required = False , type = ' list ' ) ,
route_target_import = dict ( required = False , type = ' list ' ) ,
route_target_export = dict ( required = False , type = ' list ' ) ,
state = dict ( choices = [ ' present ' , ' absent ' ] , default = ' present ' ,
required = False ) ,
state = dict ( choices = [ ' present ' , ' absent ' ] , default = ' present ' , required = False ) ,
include_defaults = dict ( default = True ) ,
config = dict ( ) ,
save = dict ( type = ' bool ' , default = False )
@ -289,26 +248,19 @@ def main():
argument_spec . update ( nxos_argument_spec )
module = AnsibleModule ( argument_spec = argument_spec ,
supports_check_mode = True )
module = AnsibleModule ( argument_spec = argument_spec , supports_check_mode = True )
warnings = list ( )
check_args ( module , warnings )
results = dict ( changed = False , warnings = warnings )
state = module . params [ ' state ' ]
args = [
' vni ' ,
' route_distinguisher ' ,
' route_target_both ' ,
' route_target_import ' ,
' route_target_export '
]
existing = invoke ( ' get_existing ' , module , args )
end_state = existing
args = PARAM_TO_COMMAND_KEYMAP . keys ( )
existing = get_existing ( module , args )
proposed_args = dict ( ( k , v ) for k , v in module . params . items ( )
if v is not None and k in args )
if v is not None and k in args )
commands = [ ]
parents = [ ]
proposed = { }
for key , value in proposed_args . items ( ) :
@ -317,48 +269,42 @@ def main():
value = True
elif value == ' false ' :
value = False
if existing . get ( key ) or ( not existing . get ( key ) and value ) :
if existing . get ( key ) != value :
proposed [ key ] = value
result = { }
if state == ' present ' or ( state == ' absent ' and existing ) :
candidate = CustomNetworkConfig ( indent = 3 )
commands , parents = invoke ( ' state_ %s ' % state , module , existing ,
proposed )
if commands :
if ( existing . get ( ' route_distinguisher ' ) and
proposed . get ( ' route_distinguisher ' ) ) :
if ( existing [ ' route_distinguisher ' ] != proposed [ ' route_distinguisher ' ] and
proposed [ ' route_distinguisher ' ] != ' default ' ) :
WARNINGS . append ( ' EVPN RD {0} was automatically removed. '
' It is highly recommended to use a task '
' (with default as value) to explicitly '
' unconfigure it. ' . format (
existing [ ' route_distinguisher ' ] ) )
remove_commands = [ ' no rd {0} ' . format (
existing [ ' route_distinguisher ' ] ) ]
candidate . add ( remove_commands , parents = parents )
result = execute_config ( module , candidate )
time . sleep ( 30 )
if state == ' present ' :
commands , parents = state_present ( module , existing , proposed )
elif state == ' absent ' and existing :
commands , parents = state_absent ( module , existing , proposed )
if commands :
if ( existing . get ( ' route_distinguisher ' ) and
proposed . get ( ' route_distinguisher ' ) ) :
if ( existing [ ' route_distinguisher ' ] != proposed [ ' route_distinguisher ' ] and
proposed [ ' route_distinguisher ' ] != ' default ' ) :
warnings . append ( ' EVPN RD {0} was automatically removed. '
' It is highly recommended to use a task '
' (with default as value) to explicitly '
' unconfigure it. ' . format ( existing [ ' route_distinguisher ' ] ) )
remove_commands = [ ' no rd {0} ' . format ( existing [ ' route_distinguisher ' ] ) ]
candidate = CustomNetworkConfig ( indent = 3 )
candidate . add ( remove_commands , parents = parents )
load_config ( module , candidate )
results [ ' changed ' ] = True
results [ ' commands ' ] = candidate . items_text ( )
time . sleep ( 30 )
else :
candidate = CustomNetworkConfig ( indent = 3 )
candidate . add ( commands , parents = parents )
result = execute_config ( module , candidate )
load_config ( module , candidate )
results [ ' changed ' ] = True
results [ ' commands ' ] = candidate . items_text ( )
else :
result [ ' updates ' ] = [ ]
if module . _verbosity > 0 :
end_state = invoke ( ' get_existing ' , module , args )
result [ ' end_state ' ] = end_state
result [ ' existing ' ] = existing
result [ ' proposed ' ] = proposed_args
if WARNINGS :
result [ ' warnings ' ] = WARNINGS
module . exit_json ( * * result )
results [ ' commands ' ] = [ ]
module . exit_json ( * * results )
if __name__ == ' __main__ ' :
main ( )