promote, demote, and migrate seem to work

master
Marek Isalski 4 years ago
parent afa7c4880a
commit cbf75bb34c

@ -56,6 +56,8 @@ loopbacks:
- 46.227.207.255/32 - 46.227.207.255/32
default_ipv4_loopback: 46.227.207.255/32 default_ipv4_loopback: 46.227.207.255/32
routing_filter_chain: automonty
``` ```
We have specified: We have specified:

@ -10,14 +10,17 @@ import dns.resolver, dns.reversename
import ipaddress import ipaddress
# comment format: # comment format:
# hostname.example.com [aM flag1 flag2 key1=value1 key2=option3:value3,option4:value4] selector.example.com # hostname.example.com [aM flag1 flag2 key1=value1 key2=option3:value3,option4:value4] selector.example.com
# #
# flags: # flags:
# static - do not auto-enable or auto-disable this address (loopbacks, linknets) # static - do not auto-enable or auto-disable this address (loopbacks, linknets)
# home - this is the normal home-location of the IP # home - this is the normal home-location of the IP
# evacuated - this has been moved from this host to elsewhere # standby - this is an active interface, but not intended as primary route
# evacuee - this is an evacuated address
# teardown - being destroyed # teardown - being destroyed
#
# keys:
# evacuated=router - this address has been moved from this host to router
# evacuee=router - this is an evacuated address originally from router
aM1 = re.compile( r"\[aM ?([^]]*)\]" ) aM1 = re.compile( r"\[aM ?([^]]*)\]" )
def parse_comment( comment ): def parse_comment( comment ):
@ -59,7 +62,7 @@ def make_comment( selector, rval, hostname ):
rvalbits.append( k ) rvalbits.append( k )
elif isinstance( v, dict ): elif isinstance( v, dict ):
rvalbits.append( "%s=%s" % ( k, ",".join( "%s:%s" % vi for vi in v.items() ) ) ) rvalbits.append( "%s=%s" % ( k, ",".join( "%s:%s" % vi for vi in v.items() ) ) )
else: elif v:
rvalbits.append( "%s=%s" % ( k, v ) ) rvalbits.append( "%s=%s" % ( k, v ) )
if selector == hostname or selector is None: if selector == hostname or selector is None:
return "%s [%s]" % ( hostname, " ".join( rvalbits ) ) return "%s [%s]" % ( hostname, " ".join( rvalbits ) )
@ -154,6 +157,11 @@ def monty_fixup( config, args, routers ):
else: else:
print( name, ":", item[ 'interface' ], new_comment ) print( name, ":", item[ 'interface' ], new_comment )
ip_address.update( **{ 'comment': new_comment, '.id': item[ '.id' ] } ) ip_address.update( **{ 'comment': new_comment, '.id': item[ '.id' ] } )
addr = ipaddress.ip_interface( item[ 'address' ] )
if item[ 'address' ] in config[ 'loopbacks' ]:
addr = ipaddress.ip_interface( item[ 'network' ] )
_adjust_filter( config, api, addr.network, hst, sel, home = rval.get( 'home', False ), standby = False, static = rval.get( 'static', False ), rval = rval )
for item in ipv6_address: for item in ipv6_address:
if item[ 'interface' ].startswith( "loop" ): if item[ 'interface' ].startswith( "loop" ):
continue continue
@ -177,6 +185,66 @@ def monty_fixup( config, args, routers ):
else: else:
print( name, ":", item[ 'interface' ], new_comment ) print( name, ":", item[ 'interface' ], new_comment )
ipv6_address.update( **{ 'comment': new_comment, '.id': item[ '.id' ] } ) ipv6_address.update( **{ 'comment': new_comment, '.id': item[ '.id' ] } )
addr = ipaddress.ip_interface( item[ 'address' ] )
_adjust_filter( config, api, addr.network, hst, sel, home = rval.get( 'home', False ), standby = False, static = rval.get( 'static', False ), rval = rval )
def _adjust_filter( config, api, network, hostname, selector = None, home = None, standby = None, static = None, delete = False, rval = None, disabled = False ):
def _make_filter_attrs( comment, disabled, home, static, standby ):
attrs = { 'action': 'accept',
'comment': comment,
'disabled': 'yes' if disabled else 'no',
}
if home or static:
attrs[ '!set-bgp-prepend' ] = ''
attrs[ 'set-bgp-local-pref' ] = 980
elif standby:
attrs[ 'set-bgp-prepend' ] = 1
attrs[ 'set-bgp-local-pref' ] = 970
else:
attrs[ 'set-bgp-prepend' ] = 2
attrs[ 'set-bgp-local-pref' ] = 960
return attrs
if isinstance( network, ( ipaddress.IPv4Interface, ipaddress.IPv6Interface ) ):
network = network.network
filters = api.path( 'routing', 'filter' )
found = False
if rval is None:
rval = {}
else:
_rval = rval
rval = {}
rval.update( _rval )
if home is not None:
rval[ 'home' ] = home
if standby is not None:
rval[ 'standby' ] = standby
if static is not None:
rval[ 'static' ] = static
comment = make_comment( selector, rval, hostname )
for f in filters:
if f[ 'chain' ] == config[ 'routing_filter_chain' ]:
match = str( network )
if isinstance( network, ipaddress.IPv4Network ) and network.prefixlen == 32:
match = match.split( "/", 1 )[ 0 ]
elif isinstance( network, ipaddress.IPv6Network ) and network.prefixlen == 128:
match = match.split( "/", 1 )[ 0 ]
if f[ 'prefix' ] == match:
if delete:
filters.delete( f[ '.id' ] )
else:
update = _make_filter_attrs( comment, disabled, home, static, standby )
update[ '.id' ] = f[ '.id' ]
filters.update( **update )
found = True
if not found and not delete:
create = _make_filter_attrs( comment, disabled, home, static, standby )
create[ 'chain' ] = config[ 'routing_filter_chain' ]
create[ 'prefix' ] = str( network )
filters.add( **create )
return rval
def monty_provision( config, args, routers ): def monty_provision( config, args, routers ):
reverse = {} reverse = {}
@ -200,6 +268,9 @@ def monty_provision( config, args, routers ):
elif name in args.home: elif name in args.home:
rval[ 'home' ] = True rval[ 'home' ] = True
disabled = 'no' disabled = 'no'
elif name in args.standby:
rval[ 'standby' ] = True
disabled = 'no'
else: else:
disabled = 'yes' disabled = 'yes'
comment = make_comment( args.selector, rval, args.hostname ) comment = make_comment( args.selector, rval, args.hostname )
@ -218,6 +289,11 @@ def monty_provision( config, args, routers ):
print( '/ipv6 address add interface="%s" address="%s" disabled=%s comment="%s" eui-64=yes advertise=yes' % ( vlan[ 'name' ], addr.network, disabled, comment ) ) print( '/ipv6 address add interface="%s" address="%s" disabled=%s comment="%s" eui-64=yes advertise=yes' % ( vlan[ 'name' ], addr.network, disabled, comment ) )
else: else:
prefix = _extract_bgp_announcement( addr[ 'address' ], addr.get( 'network', None ) )
_adjust_filter( config, api, prefix, args.hostname,
home = ( name in args.home ),
standby = ( name not in args.home ) and ( name in args.standby ),
selector = args.selector, rval = rval )
if isinstance( addr, ipaddress.IPv4Interface ): if isinstance( addr, ipaddress.IPv4Interface ):
v4addr = { 'interface': vlan[ 'name' ], v4addr = { 'interface': vlan[ 'name' ],
'disabled': disabled, 'disabled': disabled,
@ -311,6 +387,158 @@ def monty_teardown( config, args, routers ):
if args.dry_run: if args.dry_run:
print() print()
def _extract_bgp_announcement( address, network = None ):
addr = ipaddress.ip_interface( address )
if isinstance( addr, ipaddress.IPv4Interface ):
if addr.network.prefixlen == 32:
return ipaddress.ip_interface( network )
return addr.network
elif isinstance( addr, ipaddress.IPv6Interface ):
return addr.network
def monty_promote( config, args, routers ):
for new_home in args.new_home:
if new_home not in routers:
raise ValueError( 'cannot find new home "%s" in routers' % new_home )
for standby in args.standby:
if standby not in routers:
raise ValueError( 'cannot find new standby "%s" in routers' % standby )
done_work = False
for ( name, api ) in routers.items():
vlans = api.path( 'interface', 'vlan' )
ip_address = api.path( 'ip', 'address' )
ipv6_address = api.path( 'ipv6', 'address' )
spare_vlans = {}
for vlan in vlans:
if vlan[ 'vlan-id' ] == args.vlan:
spare_vlans[ vlan[ 'name' ] ] = True
for af in ( ip_address, ipv6_address ):
for addr in af:
( sel, rval, hst ) = parse_comment( addr.get( 'comment', '' ) )
if sel == args.hostname or hst == args.hostname:
if addr.get( 'disabled', False ):
if ( name in args.standby ) or ( name in args.new_home ):
address = _extract_bgp_announcement( addr[ 'address' ], addr.get( 'network', None ) )
rval = _adjust_filter( config, api, address, hst, selector = sel,
home = ( name in args.new_home ),
standby = ( name not in args.new_home ) and ( name in args.standby ),
static = args.static,
rval = rval,
)
new_comment = make_comment( sel, rval, hst )
af.update( **{ '.id': addr[ '.id' ],
'disabled': False,
'comment': new_comment,
} )
done_work = True
else:
if ( name in args.new_home ):
address = _extract_bgp_announcement( addr[ 'address' ], addr.get( 'network', None ) )
rval = _adjust_filter( config, api, address, hst, selector = sel,
home = ( name in args.new_home ),
standby = False,
static = args.static,
rval = rval,
)
new_comment = make_comment( sel, rval, hst )
af.update( **{ '.id': addr[ '.id' ],
'disabled': False,
'comment': new_comment,
} )
done_work = True
spare_vlans.pop( addr[ 'interface' ], None )
for vlan in spare_vlans:
spare.append( ( name, ip_address, ipv6_address, vlan ) )
if args.create:
for ( name, ip_address, ipv6_address, vlan ) in spare:
rval = {}
disabled = 'yes'
if name in args.new_home:
rval[ 'home' ] = True
disabled = 'no'
elif name in args.standby:
disabled = 'no'
new_comment = make_comment( args.selector, rval, args.hostname )
for addr in args.create:
rval = _adjust_filter( config, api, addr.network, args.hostname, selector = args.selector,
home = ( name in args.new_home ),
standby = ( name not in args.new_home ) and ( name in args.standby ),
static = args.static,
rval = rval,
)
if isinstance( addr, ipnetwork.IPv4Interface ):
print( "creating address on interface" )
v4addr = { 'interface': vlan,
'disabled': disabled,
'comment': new_comment,
}
if addr.network.prefixlen == 32:
v4addr[ 'address' ] = config[ 'default_ipv4_loopback' ]
v4addr[ 'network' ] = str( addr.network.network_address )
else:
v4addr[ 'address' ] = str( addr )
ip_address.create( **v4addr )
done_work = True
elif isinstance( addr, ipnetwork.IPv6Interface ):
print( "creating address on interface" )
v6addr = { 'interface': vlan,
'disabled': disabled,
'comment': comment,
}
if args.static:
v6addr[ 'address' ] = str( addr )
else:
v6addr[ 'address' ] = str( addr.network )
v6addr[ 'eui-64' ] = 'yes'
v6addr[ 'advertise' ] = 'yes'
ipv6_address.create( **v6addr )
done_work = True
return done_work
def monty_demote( config, args, routers ):
for disable in args.disable:
if disable not in routers:
raise ValueError( 'cannot find disable "%s" in routers' % disable )
for standby in args.standby:
if standby not in routers:
raise ValueError( 'cannot find standby "%s" in routers' % standby )
done_work = False
for ( name, api ) in routers.items():
vlans = api.path( 'interface', 'vlan' )
ip_address = api.path( 'ip', 'address' )
ipv6_address = api.path( 'ipv6', 'address' )
for af in ( ip_address, ipv6_address ):
for addr in af:
( sel, rval, hst ) = parse_comment( addr.get( 'comment', '' ) )
if sel == args.hostname or hst == args.hostname:
if ( name in args.standby ) or ( name in args.disable ):
address = _extract_bgp_announcement( addr[ 'address' ], addr.get( 'network', None ) )
rval = _adjust_filter( config, api, address, hst, selector = sel,
home = False,
standby = ( name in args.standby ),
rval = rval,
)
new_comment = make_comment( sel, rval, hst )
af.update( **{ '.id': addr[ '.id' ],
'disabled': ( name in args.disable ),
'comment': new_comment,
} )
done_work = True
def monty_migrate( config, args, routers ):
if monty_promote( config, args, routers ):
monty_demote( config, args, routers )
def load_configuration(): def load_configuration():
try: try:
config = open( os.path.expanduser( '~/.automonty.yaml' ), 'r' ) config = open( os.path.expanduser( '~/.automonty.yaml' ), 'r' )
@ -339,6 +567,7 @@ def main():
parser_provision = subparsers.add_parser( 'provision' ) parser_provision = subparsers.add_parser( 'provision' )
parser_provision.add_argument( '--home', action = 'append', default = [] ) parser_provision.add_argument( '--home', action = 'append', default = [] )
parser_provision.add_argument( '--standby', action = 'append', default = [] )
parser_provision.add_argument( '-s', '--static', action = 'store_true', default = False ) parser_provision.add_argument( '-s', '--static', action = 'store_true', default = False )
parser_provision.add_argument( '--selector', type = str, default = None ) parser_provision.add_argument( '--selector', type = str, default = None )
parser_provision.add_argument( 'hostname' ) parser_provision.add_argument( 'hostname' )
@ -346,6 +575,36 @@ def main():
parser_provision.add_argument( 'address', type = ipaddress.ip_interface, nargs = "+" ) parser_provision.add_argument( 'address', type = ipaddress.ip_interface, nargs = "+" )
parser_provision.set_defaults( func = monty_provision ) parser_provision.set_defaults( func = monty_provision )
parser_promote = subparsers.add_parser( 'promote' )
parser_promote.add_argument( '-e', '--enable', action = 'store_true', default = False )
parser_promote.add_argument( '-c', '--create', type = ipaddress.ip_interface, default = [], action = 'append' )
parser_promote.add_argument( '-s', '--static', action = 'store_true', default = False )
parser_promote.add_argument( '--standby', action = 'append', default = [] )
parser_promote.add_argument( '--selector', type = str, default = None )
parser_promote.add_argument( 'hostname' )
parser_promote.add_argument( 'vlan', type = int )
parser_promote.add_argument( 'new_home', nargs = "*" )
parser_promote.set_defaults( func = monty_promote )
parser_demote = subparsers.add_parser( 'demote' )
parser_demote.add_argument( '--standby', action = 'append', default = [] )
parser_demote.add_argument( 'hostname' )
parser_demote.add_argument( 'vlan', type = int )
parser_demote.add_argument( 'disable', nargs = "*" )
parser_demote.set_defaults( func = monty_demote )
parser_migrate = subparsers.add_parser( 'migrate' )
parser_migrate.add_argument( '-e', '--enable', action = 'store_true', default = False )
parser_migrate.add_argument( '-c', '--create', type = ipaddress.ip_interface, default = [], action = 'append' )
parser_migrate.add_argument( '-s', '--static', action = 'store_true', default = False )
parser_migrate.add_argument( '--selector', type = str, default = None )
parser_migrate.add_argument( '--standby', action = 'append', default = [] )
parser_migrate.add_argument( '--disable', action = 'append', default = [] )
parser_migrate.add_argument( 'hostname' )
parser_migrate.add_argument( 'vlan', type = int )
parser_migrate.add_argument( 'new_home', nargs = "*" )
parser_migrate.set_defaults( func = monty_migrate )
parser_teardown = subparsers.add_parser( 'teardown' ) parser_teardown = subparsers.add_parser( 'teardown' )
parser_teardown.add_argument( '--delete', action = 'store_true', default = False ) parser_teardown.add_argument( '--delete', action = 'store_true', default = False )
parser_teardown.add_argument( 'hostname' ) parser_teardown.add_argument( 'hostname' )

Loading…
Cancel
Save