def test03(self, steps): with steps.start(f"Re-run test to collect Post-state data", continue_=True) as step: # Create empty dictionary for storing all route results self.post_dic = {} # Loop over device dictionary for self.name, self.dev_name in self.devices.items(): log.info( f'******* Learning and Processing details for {self.name} *******' ) # create empty list to store route entries emdeded within complete_dic dictionary acl_entries = [] # create enbedded dictionary entry per device self.post_dic.update({self.name: []}) # learn routes from device acls = self.dev_name.learn('acl') try: acl_entries.append(acls.info) # Add group of routes to dictionary per-device self.post_dic[self.name] = acl_entries except AttributeError: pass with steps.start( f"Compare Pre-state to Post-state to very routes haven't changed", continue_=True) as step: #Verification # perfrom a pre vs post ACL compare diff = Diff(self.pre_dic, self.post_dic, exclude="statistics") diff.findDiff() diff = str(diff) if not diff: log.info(f'No ACL changes detected - Test Passed') else: log.info(f'ACL changes detected {diff}') for dev in self.devices.keys(): log.info(f'ACL Change Summary for device - {dev}') pre_list_of_acl = self.pre_dic[dev] post_list_of_acl = self.post_dic[dev] self.pre_acl_names = {} self.post_acl_names = {} # Start Pre state validation for acl_set in pre_list_of_acl: if 'acls' in acl_set: acls = acl_set['acls'] for acl in acls.keys(): self.pre_aces = [] self.acl_type = acls[acl]['type'] try: aces = acls[acl]['aces'] except KeyError: print( f"ACL {acl} doesn't have any entries") self.pre_acl_names.update({ acls[acl]['name']: { 'type': acls[acl]['type'], 'aces': None } }) continue for ace in aces.keys(): seq = aces[ace]['name'] self.pre_aces.append(aces[ace]) self.pre_acl_names.update({ acls[acl]['name']: { 'type': acls[acl]['type'], 'aces': self.pre_aces } }) else: self.pre_acl_names.update({ 'name': None, 'type': None, 'aces': None }) #Start Post state validation for acl_set in post_list_of_acl: if 'acls' in acl_set: acls = acl_set['acls'] for acl in acls.keys(): self.post_aces = [] self.acl_type = acls[acl]['type'] try: aces = acls[acl]['aces'] except KeyError: print( f"ACL {acl} doesn't have any entries") self.post_acl_names.update({ acls[acl]['name']: { 'type': acls[acl]['type'], 'aces': None } }) continue for ace in aces.keys(): seq = aces[ace]['name'] self.post_aces.append(aces[ace]) self.post_acl_names.update({ acls[acl]['name']: { 'type': acls[acl]['type'], 'aces': self.post_aces } }) else: self.post_acl_names.update({ 'name': None, 'type': None, 'aces': None }) # Start comparision #List of ACLs that were removed missing_acls = { x: y for x, y in self.pre_acl_names.items() if x not in self.post_acl_names.keys() } if missing_acls: for miss_acl in missing_acls.keys(): log.info( f"Hostname: {dev} --- ACL {miss_acl} is missing" ) else: pass # List of ACLs that were added added_acls = { x: y for x, y in self.post_acl_names.items() if x not in self.pre_acl_names.keys() } if added_acls: for add_acl in added_acls.keys(): log.info( f" Hostname: {dev} --- ACL {add_acl} was added" ) else: pass #Check for modified ACLs #Loop thru pre ACLs as primary for pre_acl_name in self.pre_acl_names.keys(): try: # process each pre ACE individually and compare to post pre_aces_list = self.pre_acl_names[pre_acl_name][ 'aces'] nested_lookup.nested_delete(pre_aces_list, 'statistics', in_place=True) #use pre-acl name as key to ensure we're comparing the same ACL name post_aces_list = self.post_acl_names[pre_acl_name][ 'aces'] nested_lookup.nested_delete(post_aces_list, 'statistics', in_place=True) #if ACL is removed and empty KeyError is thrown. except KeyError: continue if pre_aces_list and post_aces_list: for pre_acl in pre_aces_list: if pre_acl in post_aces_list: pass else: print(( f"Hostname: {dev} --- ACL {pre_acl_name} seq {pre_acl['name']} has been been modified" )) # Check for modified ACLs # Loop thru post ACLs as primary for post_acl_name in self.post_acl_names.keys(): try: # process each pre ACE individually and compare to post post_aces_list = self.post_acl_names[ post_acl_name]['aces'] nested_lookup.nested_delete(post_aces_list, 'statistics', in_place=True) # use pre-acl name as key to ensure we're comparing the same ACL name pre_aces_list = self.pre_acl_names[post_acl_name][ 'aces'] nested_lookup.nested_delete(pre_aces_list, 'statistics', in_place=True) #If ACL is removed/empty then KeyError is thrown except KeyError: continue if post_aces_list and pre_aces_list: for post_acl in post_aces_list: if post_acl in pre_aces_list: pass else: log.info(( f"Hostname: {dev} --- ACL {post_acl_name} seq {post_acl['name']} has been been modified" )) step.failed()
def restore_configuration(self, device, method, abstract, iteration=10, interval=60, compare=False, compare_exclude=[]): if method == 'checkpoint': # Enable the feature for i in range(1,iteration): try: out = self.rollback_checkpoint(device=device, name=self.ckname) except Exception as e: raise Exception('Unable to rollback config') if out and 'Rollback Done' in out: break else: log.info('Rollback configuration failed: sleeping {} ' 'seconds and retrying...'.format(interval)) time.sleep(interval) else: raise Exception('Unable to rollback config') # Delete the checkpoint self.create_delete_checkpoint(device=device, name=self.ckname, abstract=abstract, action='delete') # Check if checkpoint is successfully deleted self.check_checkpoint_status(device=device, name=self.ckname, expect='delete', abstract=abstract) elif method == 'local': # reover the deivce with whole running-config device.configure(self.run_config) elif method == 'config_replace': # delete the archive file dialog = Dialog([ Statement(pattern=r'This will apply all necessary.*', action='sendline(Y)', loop_continue=True, continue_timer=False), Statement(pattern=r'less than running config.*', action='sendline(Y)', loop_continue=True, continue_timer=False), ]) for i in range(1,iteration): # configure replace location:<filename> out = device.execute('configure replace {}'.\ format(self.to_url), reply=dialog, error_pattern=[]) if out and 'Rollback Done' in out: break else: log.info('Config replace failed: sleeping {} seconds before' ' retrying.'.format(interval)) time.sleep(interval) else: raise Exception('Unable to execute config replace') # Compare restored configuration to details in file if compare: log.info("Comparing current running-config with config-replace file") # Default exclude = ['device', 'maker', 'diff_ignore', 'callables', '(Current configuration.*)', '(.*Building configuration.*)', '(.*Load for.*)', '(.*Time source.*)'] if compare_exclude: if isinstance(compare_exclude, str): exclude.extend([compare_exclude]) else: exclude.extend(compare_exclude) # show run show_run_output = device.execute('show running-config') show_run_config = Config(show_run_output) show_run_config.tree() # location:<filename> contents more_file = device.execute('more {}'.format(self.to_url)) more_file_config = Config(more_file) more_file_config.tree() # Diff 'show run' and config replace file contents diff = Diff(show_run_config.config, more_file_config.config, exclude=exclude) diff.findDiff() # Check for differences if len(diff.diffs): log.error("Differences observed betweenrunning-config and " "config-replce file:'{f}' for device {d}:".\ format(f=self.to_url, d=device.name)) log.error(str(diff.diffs)) raise Exception("Comparison between running-config and " "config-replace file '{f}' failed for device" " {d}".format(f=self.to_url, d=device.name)) else: log.info("Comparison between running-config and config-replace" "file '{f}' passed for device {d}".\ format(f=self.to_url, d=device.name)) # Delete location:<filename> self.filetransfer = FileUtils.from_device(device) self.filename = self.to_url self.filetransfer.deletefile(target=self.to_url, device=device) # Verify location:<filename> deleted dir_output = self.filetransfer.dir(target=self.to_url,device=device) for file in dir_output: if self.filename in file: break else: log.info("Successfully deleted '{}'".format(self.to_url)) return raise Exception("Unable to delete '{}'".format(self.to_url)) else: # modify the device via callable function # using Conf object self.modify_func(device=device, conf=self.conf, values_dict=self.conf_argument, recover=True, **self.specific_args)
def ops_diff(self, ops_learn, ops_compare, exclude=None, ops_modified=None, conf_argument=None): '''Diff two ops object with ignoring the keys from the exclude list Args: Mandatory: ops_learn (`obj`) : Ops object. ops_compare (`obj`) : Ops object. Optional: exclude (`list`) : Keys/attributs to ignore in the diff. mock (`list`) : List of items, which contain a list of keys strucure of dict, and the value needs to be mocked. Returns: None Raises: AssertionError: When diff is found Example: >>> ops_diff(ops_learn = <bgp_ops_obj>, ops_compare = <bgp_ops_obj>, exclude = ['up_time', 'keepalive', 'maker'], mock = [['info', 'instance', '{}', 'vrf', '{}', 'neighbor', '{}', 'remote_as', '900']]) ''' if ops_modified and conf_argument: # Some section of ops_learn needs to be # modified as its value was modified. # First verify the R requirement to make sure they are valid. for r in ops_modified: # Modify r to only verify that one which were modified. for argument, value in conf_argument.items(): if argument in r.args[0]: loc = r.args[0].index(argument) r.args[0][loc + 1] = value ret = find([ops_learn], r, filter_=False) if not ret: raise Exception("'{r} does not exists in new " "snapshot".format(r=r)) # Good this exists, but it will fail the comparaison with the # next snapshot, even though its valid. So let's take the value # of the snap and use it for this snapshot comparaison as we've # already verified it was valid osnap = ops_compare learn = ops_learn for item in r.args[0][:-2]: # item could be either attr or dit try: osnap = osnap[item] learn = learn[item] except (KeyError, TypeError) as e: try: osnap = getattr(osnap, item) learn = getattr(learn, item) except AttributeError: raise KeyError("'{item}' does not exists in the " "snapshots".format(item=item)) else: learn[r.args[0][-2]] = osnap[r.args[0][-2]] pass diff = Diff(ops_compare, ops_learn, exclude=exclude) diff.findDiff() if str(diff): log.info("The output is not same with diff\n{}".format(str(diff))) raise AssertionError("The output is not same with diff\n{}".format( str(diff)))
def get_ops_diff(new, original, exclude=None, modified_path=None, keys=None): '''Diff two ops object with ignoring the keys from the exclude list Args: Mandatory: new (`obj`) : Ops object. original (`obj`) : Ops object. Optional: exclude (`list`) : Keys/attributs to ignore in the diff. modified_path (`list`) : List of items that needs to be checked. The item is following the ops attributes path in a list. keys (`list`) : List of items that contains the key values for the 'modified_path' regexp items. Returns: None Raises: AssertionError: When diff is found ValueError: When required attributes are not in the ops Example: >>> ops_diff(new = <bgp_ops_obj>, original = <bgp_ops_obj>, exclude = ['up_time', 'keepalive', 'maker'], modified_path = [['info', 'instance', '(?P<instance>.*)', 'vrf', '(?P<vrf>.*)', 'neighbor', '(?P<neighbor>.*)', 'remote_as', '900']], keys = [{'instance': '1', 'vrf': 'default', 'neighbor': '1.1.1.1'}, {'instance': '1', 'vrf': 'VRF1', 'neighbor': '2.2.2.2'},]) ''' def _modify_ops_snapshot(original, current, path): # First does path exists in original, except the value r = R(path[:-1] + ['(.*)']) ret = find([original], r, filter_=False) if not ret: raise ValueError( "'{p}' does not exist on original snapshot " "as per the original trigger requirement".format(p=path)) _modify_value(current, path[:-1], ret[0][0]) def _modify_value(snapshot, path, value): for p in path[:-1]: try: snapshot = snapshot[p] except (TypeError): snapshot = getattr(snapshot, p) if isinstance(snapshot, dict): snapshot[path[-1]] = value else: setattr(snapshot, path[-1], value) if modified_path and keys: mapping = Mapping() for req in modified_path: # use mapping internal function to populate the path with learned values req = mapping._populate_path([req], new.device, keys=keys) rs = [R(requirement) for requirement in req] # want to print one by one for rs_item in rs: ret = find([new], rs_item, filter_=False, all_keys=True) if not ret: # return rs_item.args raise ValueError("'{req}' does not exists in " "'{o}'".format(req=rs_item.args, o=new)) # Let's modify the ops value to be equal to the original # snapshot. This will allow for comparing the other keys for require in req: try: _modify_ops_snapshot(original=original, current=new, path=require) except Exception as e: return diff = Diff(original, new, exclude=(exclude or []) + ['maker', 'callables', 'device', 'diff_ignore']) diff.findDiff() if diff.diffs: log.error("Current ops is not equal to the initial Snapshot " "taken on device {d}.\n{e}".format(e=str(diff), d=getattr( new, 'device', 'name'))) raise AssertionError( "Current ops is not equal to the initial Snapshot " "taken on device {d}.\n{e}".format(e=str(diff), d=getattr( new, 'device', 'name')))
def compare_profile(self, pts, pts_compare, devices): '''Compare system profiles taken as snapshots during the run''' if os.path.isfile(pts): compare1 = unpickle(pts) else: compare1 = self.testscript.parameters[pts] if os.path.isfile(pts_compare): compare2 = unpickle(pts_compare) else: compare2 = self.testscript.parameters[pts_compare] exclude_list = [ 'device', 'maker', 'diff_ignore', 'callables', '(Current configuration.*)', 'ops_schema' ] try: if 'exclude' in self.pts_datafile: exclude_list.extend(self.pts_datafile['exclude']) except AttributeError: pass msg = [] for fet in compare1: failed = [] feature_exclude_list = exclude_list.copy() # Get the information too from the pts_data try: feature_exclude_list.extend(self.pts_datafile[fet]['exclude']) except (KeyError, AttributeError): pass for dev in compare1[fet]: # Only compare for the specified devices if dev not in devices: continue dev_exclude = feature_exclude_list.copy() try: dev_exclude.extend(compare1[fet][dev].exclude) # TODO - better fix, dev_exclude.remove(None) except (AttributeError, ValueError): pass diff = Diff(compare1[fet][dev], compare2[fet][dev], exclude=dev_exclude) diff.findDiff() if len(diff.diffs): failed.append((dev, diff)) if failed: msg.append('\n' + '*' * 10) msg.append("Comparison between {pts} and " "{OPS} is different for feature '{f}' " "for device:\n".format(pts=pts, OPS=pts_compare, f=fet)) for device, diff in failed: msg.append("'{d}'\n{diff}".format(d=device, diff=diff)) else: message = "Comparison between {pts} and "\ "{OPS} is identical\n".format(pts=pts, OPS=pts_compare) # print out message log.info(message) if msg: self.builtin.fail('\n'.join(msg)) message = 'All Feature were identical on all devices' self.builtin.pass_execution(message)
def test04(self, steps): with steps.start(f"Compare pre-state to post interface states", continue_=True) as step: # Verification diff = Diff(self.pre_dic, self.post_dic) diff.findDiff() diff = str(diff) print(diff) print("pre and post....") print(self.pre_dic) print(self.post_dic) if not diff: log.info(f'No HSRP changes detected - Test Passed') else: log.info(f'HSRP changes detected - Test Failed') for device in self.devices.keys(): missing_hsrp_int = [ x for x in self.pre_dic[device].keys() if x not in self.post_dic[device].keys() ] if missing_hsrp_int: for interface in missing_hsrp_int: log.info( f"{device} -- Interface {interface} no longer has HSRP enabled " ) added_hsrp_int = [ x for x in self.post_dic[device].keys() if x not in self.pre_dic[device].keys() ] if added_hsrp_int: for interface in added_hsrp_int: log.info( f"{device} -- HSRP enabled on new interface - {interface}" ) common_hsrp_ints = [ x for x in self.post_dic[device].keys() if x in self.pre_dic[device].keys() ] for pre_int in common_hsrp_ints: for post_int in common_hsrp_ints: if 'ipv4' in self.pre_dic[device][pre_int].keys( ) and 'ipv4' not in self.post_dic[device][ pre_int].keys(): log.info( f"{device} -- IPv4 no longer enabled on interface {pre_int}" ) if 'ipv6' in self.pre_dic[device][pre_int].keys( ) and 'ipv6' not in self.post_dic[device][ pre_int].keys(): log.info( f"{device} -- IPv6 no longer enabled on interface {pre_int}" ) if 'ipv4' in self.post_dic[device][pre_int].keys( ) and 'ipv4' not in self.pre_dic[device][ pre_int].keys(): log.info( f"{device} -- IPv4 now enabled on interface {pre_int}" ) if 'ipv6' in self.post_dic[device][pre_int].keys( ) and 'ipv6' not in self.pre_dic[device][ pre_int].keys(): log.info( f"{device} -- IPv6 now enabled on interface {pre_int}" ) #execute ipv4 flow if 'ipv4' in self.post_dic[device][pre_int].keys( ) and 'ipv4' in self.pre_dic[device][pre_int].keys( ): missing_hsrp_group = [ x for x in self.pre_dic[device][pre_int] ['ipv4'].keys() if x not in self.post_dic[device][pre_int] ['ipv4'].keys() ] #identify missing HSRP groups if missing_hsrp_group: for group in missing_hsrp_group: log.info( f"{device} -- IPv4 HSRP group {group} has been removed" ) #identify added HSRP groups added_hsrp_group = [ x for x in self.post_dic[device][pre_int] ['ipv4'].keys() if x not in self.pre_dic[device][pre_int] ['ipv4'].keys() ] if added_hsrp_group: for group in missing_hsrp_group: log.info( f"{device} -- IPv4 HSRP group {group} has been added" ) common_hsrp_groups = [ x for x in self.post_dic[device][pre_int] ['ipv4'].keys() if x in self.pre_dic[device][pre_int] ['ipv4'].keys() ] for indi_group in common_hsrp_groups: pre_priority = self.pre_dic[device][ pre_int]['ipv4'][indi_group][ 'priority'] pre_vmac = self.pre_dic[device][pre_int][ 'ipv4'][indi_group]['v_mac'] pre_state = self.pre_dic[device][pre_int][ 'ipv4'][indi_group]['state'] pre_pri_router = self.pre_dic[device][ pre_int]['ipv4'][indi_group][ 'active_router'] post_priority = self.post_dic[device][ pre_int]['ipv4'][indi_group][ 'priority'] post_vmac = self.post_dic[device][pre_int][ 'ipv4'][indi_group]['v_mac'] post_state = self.post_dic[device][ pre_int]['ipv4'][indi_group]['state'] post_pri_router = self.post_dic[device][ pre_int]['ipv4'][indi_group][ 'active_router'] if pre_priority != post_priority: log.info( f"{device} -- IPv4 HSRP group {indi_group} priority changed from {pre_priority} to {post_priority}" ) if pre_vmac != post_vmac: log.info( f"{device} -- IPv4 HSRP group {indi_group} virtual mac changed from {pre_vmac} to {post_vmac}" ) if pre_state != post_state: log.info( f"{device} -- IPv4 HSRP group {indi_group} HSRP state changed from {pre_state} to {post_state}" ) if pre_pri_router != post_pri_router: log.info( f"{device} -- IPv4 HSRP group {indi_group} primary router changed from {pre_pri_router} to {post_pri_router}" ) # execute ipv6 flow if 'ipv6' in self.post_dic[device][pre_int].keys() and 'ipv6' in \ self.pre_dic[device][pre_int].keys(): missing_hsrp_group = [ x for x in self.pre_dic[device][pre_int] ['ipv6'].keys() if x not in self.post_dic[device][pre_int] ['ipv6'].keys() ] # identify missing HSRP groups if missing_hsrp_group: for group in missing_hsrp_group: log.info( f"{device} -- IPv6 HSRP group {group} has been removed" ) # identify added HSRP groups added_hsrp_group = [ x for x in self.post_dic[device][pre_int] ['ipv6'].keys() if x not in self.pre_dic[device][pre_int] ['ipv6'].keys() ] if added_hsrp_group: for group in missing_hsrp_group: log.info( f"{device} -- IPv6 HSRP group {group} has been added" ) common_hsrp_groups = [ x for x in self.post_dic[device][pre_int] ['ipv6'].keys() if x in self.pre_dic[device][pre_int] ['ipv6'].keys() ] for indi_group in common_hsrp_groups: pre_priority = self.pre_dic[device][ pre_int]['ipv6'][indi_group][ 'priority'] pre_vmac = self.pre_dic[device][pre_int][ 'ipv6'][indi_group]['v_mac'] pre_state = self.pre_dic[device][pre_int][ 'ipv6'][indi_group]['state'] pre_pri_router = self.pre_dic[device][ pre_int]['ipv6'][indi_group][ 'active_router'] post_priority = self.post_dic[device][ pre_int]['ipv6'][indi_group][ 'priority'] post_vmac = self.post_dic[device][pre_int][ 'ipv6'][indi_group]['v_mac'] post_state = self.post_dic[device][ pre_int]['ipv6'][indi_group]['state'] post_pri_router = self.post_dic[device][ pre_int]['ipv6'][indi_group][ 'active_router'] if pre_priority != post_priority: log.info( f"{device} -- IPv6 HSRP group {indi_group} priority changed from {pre_priority} to {post_priority}" ) if pre_vmac != post_vmac: log.info( f"{device} -- IPv6 HSRP group {indi_group} virtual mac changed from {pre_vmac} to {post_vmac}" ) if pre_state != post_state: log.info( f"{device} -- IPv6 HSRP group {indi_group} HSRP state changed from {pre_state} to {post_state}" ) if pre_pri_router != post_pri_router: log.info( f"{device} -- IPv6 HSRP group {indi_group} primary router changed from {pre_pri_router} to {post_pri_router}" ) step.failed()
def verify_process(self, repeat_restart, steps, timeout): '''Verify the process has been restarted Args: uut (`obj`): Device object. steps (`step obj`): aetest step object Returns: None ''' if self.helper: device = self.helper else: device = self.device with steps.start('Verify process has restarted correctly') as step: temp = TempResult(container=step) while timeout.iterate(): output = self.abstract.parser.show_system.\ ShowSystemInternalSysmgrServiceName(device=\ device).parse(process=self.process) if 'instance' not in output: temp.failed("No output for 'show system internal sysmgr " "service name {p}'".format(p=self.process)) timeout.sleep() continue # Check the if the process has changed pid try: pid = output['instance'][self.instance]['tag'][ self.tag]['pid'] sap = output['instance'][self.instance]['tag'][ self.tag]['sap'] restart_count = output['instance'][self.instance]['tag'][ self.tag]['restart_count'] last_restart = output['instance'][self.instance]['tag'][ self.tag]['last_restart_date'] last_restart_time = datetime.datetime.strptime( last_restart, '%a %b %d %H:%M:%S %Y') except Exception as e: temp.failed("Issue retrieving information about " "'{p}' process".format(p=self.process), from_exception=e) # Make sure time has changed if not self.last_restart_time < last_restart_time: temp.failed("The restart time has not changed for " "process '{p}'".format(p=self.process)) timeout.sleep() continue # Make sure the pid has changed if self.process not in self.reconnect and pid == self.previous_pid: temp.failed("The pid has not changed for process '{p}'" "\nprevious pid: {pp}" "\ncurrent pid: " "{cp}".format(p=self.process, pp=self.previous_pid, cp=pid)) timeout.sleep() continue # Verify the restart_count has increased if self.process not in self.reconnect and\ self.previous_restart_count + repeat_restart != restart_count: temp.failed('Restart count has not increased by {rr}' '\nprevious count: {pc}' '\ncurrent count: {cc}'.format( rr=repeat_restart, pc=self.previous_restart_count, cc=restart_count)) timeout.sleep() continue # exclude sap when the value is not in range [0, 1023] if sap > 1023: self.verify_exclude.append('sap') # Modify the original output so it does not fail the compare self.previous_output['instance'][self.instance]['tag'][self.tag]['restart_count'] =\ restart_count self.previous_output['instance'][self.instance]['tag'][self.tag]['pid'] =\ pid diff = Diff(self.previous_output, output, exclude=self.verify_exclude) diff.findDiff() if diff.diffs: temp.failed( "The device output has changed in an unexpected " "way\n{d}".format(d=str(diff.diffs))) timeout.sleep() continue break else: temp.result()
# Learn feature 'BGP' for XE device after config change # ----------------------------------------------------- log.info(banner("Learn feature 'BGP' for XE device '{}' after config change".\ format(dev_xe.name))) post_bgp_ops1 = dev_xe.learn("bgp") log.info("\nPASS: Successfully learnt feature 'bgp' for XE device '{}' after config change\n".\ format(dev_xe.name)) # -------------------------------- # Use Genie Diff to compare states # -------------------------------- log.info(banner("Use Genie Diff to verify BGP neighbor is shutdown on XE device '{}'".\ format(dev_xe.name))) bgp_diff = Diff(pre_bgp_ops.info, post_bgp_ops1.info) bgp_diff.findDiff() log.info("Genie Diffs observed, BGP neighbor is shutdown/missing:\n\n" + str(bgp_diff) + "\n") # ----------------------------------- # Restore 'BGP' neighbor on XE device # ----------------------------------- log.info(banner("Restore 'BGP' neighbor on XE device '{}'".format( dev_xe.name))) dev_xe.configure("router bgp 65000\n" " no neighbor 10.2.2.2 shutdown") log.info("\nPASS: Successfully restored BGP neighbor on XE device '{}'\n".\ format(dev_xe.name)) # -----
from genie.utils.diff import Diff diff output1 output2 --output diff with open ('output1') as a: output = a.read() with open ('output2') as b: output1 = b.read() diff = Diff(output.info, output1.info) diff.findDiff() print(diff)
def test04(self, steps): with steps.start(f"Compare pre-state to post interface states", continue_=True) as step: # Verification diff = Diff(self.pre_dic, self.post_dic) diff.findDiff() diff = str(diff) print(diff) print("pre and post....") print(self.pre_dic) print(self.post_dic) if not diff: log.info(f'No BGP neighbor changes detected - Test Passed') else: log.info(f'BGP neighbor changes detected - Test Failed') for device in self.devices.keys(): #identify missing peers missing_peer = [ x for x in self.pre_dic[device].keys() if x not in self.post_dic[device].keys() ] if missing_peer: for x in missing_peer: log.info( f"{device} -- BGP neighbor {x} is missing") #identify new peers new_peer = [ x for x in self.post_dic[device].keys() if x not in self.pre_dic[device].keys() ] if new_peer: for x in new_peer: log.info( f"{device} -- BGP neighbor {x} has been added") #Identify existing peers with changes in state common_neighbors = [ x for x in self.pre_dic[device].keys() if x in self.post_dic[device].keys() ] for neighbor in common_neighbors: #ccheck for changes in peering state if self.pre_dic[device][neighbor][ 'state'] != self.post_dic[device][neighbor][ 'state']: log.info( f"{device} -- Change in BGP peering state detected. State change from {self.pre_dic[device][neighbor]['state']} to {self.post_dic[device][neighbor]['state']}" ) #check for local AS change if self.pre_dic[device][neighbor][ 'local_as'] != self.post_dic[device][neighbor][ 'local_as']: log.info( f"{device} -- Local BGP AS changed. Changed from {self.pre_dic[device][neighbor]['state']} to {self.post_dic[device][neighbor]['state']}" ) #Check for neighbor AS change if self.pre_dic[device][neighbor][ 'peer_as'] != self.post_dic[device][ neighbor]['peer_as']: log.info( f"{device} -- The AS number of neighbor {neighbor} has changed. change from {self.pre_dic[device][neighbor]['state']} to {self.post_dic[device][neighbor]['state']}" ) step.failed()
def parse(self, testbed, section, steps): """ Testcase Setup section """ # --------------------------------------- # Loop over devices # --------------------------------------- for device in testbed: # --------------------------------------- # Load Data Model # --------------------------------------- with open("data_models/%s.yaml" % device.alias) as stream: data_model = yaml.safe_load(stream) # --------------------------------------- # Genie learn('config').info for pre-change state # --------------------------------------- print(Panel.fit(Text.from_markup(LEARN))) self.learned_config = ParseConfigFunction.parse_learn( steps, device, "config") original_learned_config = self.learned_config #---------------------------------------- # Write Pre-Change File #---------------------------------------- with steps.start('Store Original Golden Image', continue_=True) as step: print(Panel.fit(Text.from_markup(WRITING))) original_config_filename = "%s_Original_Golden_Image_%s.json" % ( timestr, device.alias) # Write Original Learned Config as JSON if hasattr(self, 'learned_config'): with open( "Camelot/Cisco/DevNet_Sandbox/Lancelot/Golden_Image/%s" % original_config_filename, "w") as fid: json.dump(self.learned_config, fid, indent=4, sort_keys=True) fid.close() # --------------------------------------- # Create Intent from Template and Data Models # --------------------------------------- with steps.start('Generating Intent From Data Model and Template', continue_=True) as step: print(Panel.fit(Text.from_markup(RUNNING))) intended_config_template = env.get_template( 'intended_config.j2') rendered_intended_config = intended_config_template.render( host_data_model=data_model) with open( "Camelot/Cisco/DevNet_Sandbox/Lancelot/Intended_Config/%s_Intended_Config.txt" % timestr, "w") as fid: fid.write(rendered_intended_config) fid.close() # --------------------------------------- # Push Intent to Device # --------------------------------------- with steps.start('Push Intent', continue_=True) as step: print(Panel.fit(Text.from_markup(PUSH_INTENT))) device.configure(rendered_intended_config) # --------------------------------------- # Re-capture state # --------------------------------------- print(Panel.fit(Text.from_markup(LEARN))) self.learned_config = ParseConfigFunction.parse_learn( steps, device, "config") new_learned_config = self.learned_config # --------------------------------------- # Write post-change state # --------------------------------------- with steps.start('Store New Golden Image', continue_=True) as step: print(Panel.fit(Text.from_markup(WRITING))) new_config_filename = "%s_Golden_Image_%s.json" % ( timestr, device.alias) # Write Original Learned Config as JSON if hasattr(self, 'learned_config'): with open( "Camelot/Cisco/DevNet_Sandbox/Lancelot/Golden_Image/%s" % new_config_filename, "w") as fid: json.dump(self.learned_config, fid, indent=4, sort_keys=True) fid.close() # --------------------------------------- # Show the differential # --------------------------------------- with steps.start('Show Differential', continue_=True) as step: config_diff = Diff(original_learned_config, new_learned_config) config_diff.findDiff() if config_diff.diffs: print(Panel.fit(Text.from_markup(DIFF))) print(config_diff) with open( 'Camelot/Cisco/DevNet_Sandbox/Lancelot/Changes/%s_Changes.txt' % timestr, 'w') as f: with redirect_stdout(f): print(config_diff) f.close() else: print(Panel.fit(Text.from_markup(NO_DIFF))) with open( 'Camelot/Cisco/DevNet_Sandbox/Lancelot/Changes/%s_Changes.txt' % timestr, 'w') as f: f.write("IDEMPOTENT - NO CHANGES") f.close()
# Importing a new library! from genie.utils.diff import Diff import json import os # Step 0: Opening the two files with open('{cwd}/config_modified.json'.format(cwd=os.path.dirname(__file__)), 'r') as config_file: config_modified = json.load(config_file) with open('{cwd}/config_original.json'.format(cwd=os.path.dirname(__file__)), 'r') as config_file: config_original = json.load(config_file) # Step 1: Printting the differences dd = Diff(config_original, config_modified) dd.findDiff() print(dd)
def main(): argument_spec = dict( command=dict(type='str', required=True), parse_command=dict(type='str', required=False), prompt=dict(type='list', required=False), answer=dict(type='list', required=False), compare=dict(type='dict', required=False), sendonly=dict(type='bool', default=False, required=False), # newline=dict(type='bool', default=True, required=False), # check_all=dict(type='bool', default=False, required=False), ) required_together = [['prompt', 'answer']] module = AnsibleModule(argument_spec=argument_spec, required_together=required_together, supports_check_mode=True) if not PY3: module.fail_json(msg="pyATS/Genie requires Python 3") if not HAS_GENIE: module.fail_json(msg="Genie not found. Run 'pip install genie'") if not HAS_PYATS: module.fail_json(msg="pyATS not found. Run 'pip install pyats'") if module.check_mode and not module.params['command'].startswith('show'): module.fail_json( msg='Only show commands are supported when using check_mode, not ' 'executing %s' % module.params['command']) warnings = list() result = {'changed': False, 'warnings': warnings} connection = Connection(module._socket_path) capabilities = json.loads(connection.get_capabilities()) if capabilities['device_info']['network_os'] == 'ios': genie_os = 'iosxe' else: genie_os = capabilities['device_info']['network_os'] compare = module.params.pop('compare') parse_command = module.params.pop('parse_command') if not parse_command: parse_command = module.params['command'] response = '' try: response = connection.get(**module.params) except ConnectionError as exc: module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) device = Device("uut", os=genie_os) device.custom.setdefault("abstraction", {})["order"] = ["os"] device.cli = AttrDict({"execute": None}) try: get_parser(parse_command, device) except Exception as e: module.fail_json( msg="Unable to find parser for command '{0}' ({1})".format( parse_command, e)) try: parsed_output = device.parse(parse_command, output=response) except Exception as e: module.fail_json( msg="Unable to parse output for command '{0}' ({1})".format( parse_command, e)) # import sys; # sys.stdin = open('/dev/tty') # import pdb; # pdb.set_trace() if compare: diff = Diff(parsed_output, compare, exclude=get_parser_exclude(parse_command, device)) diff.findDiff() else: diff = None if not module.params['sendonly']: try: result['json'] = module.from_json(response) except ValueError: pass result.update({ 'stdout': response, 'structured': parsed_output, 'diff': "{0}".format(diff), 'exclude': get_parser_exclude(parse_command, device), }) module.exit_json(**result)
def restore_configuration(self, device, method, abstract, iteration=10, interval=60, compare=False, compare_exclude=[], reload_timeout=None, delete_after_restore=True): if method == 'checkpoint': # Enable the feature dialog = Dialog([ Statement(pattern=r'\[no\]', action='sendline(y)', loop_continue=True, continue_timer=False) ]) for i in range(1, iteration): # replace config with checkpoint cfg = 'load disk0:{name}\n'\ 'commit replace'.format(name=self.ckname) output = device.configure(cfg, reply=dialog) if 'fail' not in output: break elif i == iteration - 1: raise Exception('Failed to rollback config to checkpoint') else: log.info('Rollback checkpoint failed: sleeping {} seconds ' 'and retrying...'.format(interval)) time.sleep(interval) if delete_after_restore: # need to delete the config file on the device dialog = Dialog([ Statement(pattern=r'\[confirm\]', action='sendline(y)', loop_continue=True, continue_timer=False) ]) device.execute('delete disk0:{name}'.format(name=self.ckname), reply=dialog) # Keeping them for later enhancement elif method == 'local': pass elif method == 'config_replace': for i in range(1, iteration): # Execute commit replace cmd = "load {}\n"\ "commit replace".format(self.to_url) output = device.configure(cmd) if 'Failed to commit' not in output: break elif i == iteration - 1: raise Exception('Unable to execute commit replace') else: log.info( 'Commit replace failed: sleeping {} seconds before' ' retrying.'.format(interval)) device.execute('show configuration failed') time.sleep(interval) # Compare restored configuration to details in file if compare: log.info( "Comparing current running-config with config-replace file" ) # Default exclude = [ 'device', 'maker', 'diff_ignore', 'callables', '(Current configuration.*)' ] if compare_exclude: if isinstance(compare_exclude, str): exclude.extend([compare_exclude]) else: exclude.extend(compare_exclude) # show run show_run_output = device.execute('show running-config') show_run_config = Config(show_run_output) show_run_config.tree() # location:<filename> contents more_file = device.execute('more {}'.format(self.to_url)) more_file_config = Config(more_file) more_file_config.tree() # Diff 'show run' and config replace file contents diff = Diff(show_run_config.config, more_file_config.config, exclude=exclude) diff.findDiff() # Check for differences if len(diff.diffs): log.error( "Differences observed betweenrunning-config and " "config-replce file:'{f}' for device {d}:".format( f=self.to_url, d=device.name)) log.error(str(diff.diffs)) raise Exception( "Comparison between running-config and " "config-replace file '{f}' failed for device" " {d}".format(f=self.to_url, d=device.name)) else: log.info( "Comparison between running-config and config-replace" "file '{f}' passed for device {d}".format( f=self.to_url, d=device.name)) if delete_after_restore: # Delete location:<filename> self.filetransfer = FileUtils.from_device(device) self.filename = self.to_url self.filetransfer.deletefile(target=self.to_url, device=device) # Verify location:<filename> deleted dir_output = self.filetransfer.dir(target=self.to_url, device=device) for file in dir_output: if self.filename in file: break else: log.info("Successfully deleted '{}'".format(self.to_url)) return raise Exception("Unable to delete '{}'".format(self.to_url)) else: pass
def run_module(): # define available arguments/parameters a user can pass to the module module_args = dict( host=dict(type="str", required=True), port=dict(type="int", required=False), protocol=dict(type="str", required=False), username=dict(type="str", required=True), password=dict(type="str", required=True, no_log=True), os=dict(type="str", required=True), feature=dict(type="str", required=True), compare_to=dict(type="raw", required=False), exclude=dict(type="list", required=False), no_default_exclusion=dict(type="bool", required=False), colors=dict(type="bool", required=False) ) # TODO: Add protocol so Unicon can use anything # print(type(module_args['compare_to'])) # seed the result dict in the object # we primarily care about changed and state # change is if this module effectively modified the target # state will include any data that you want your module to pass back # for consumption, for example, in a subsequent task result = dict(changed=False) # the AnsibleModule object will be our abstraction working with Ansible # this includes instantiation, a couple of common attr would be the # args/params passed to the execution, as well as if the module # supports check mode module = AnsibleModule(argument_spec=module_args, supports_check_mode=True) host = module.params["host"] if module.params.get("port") is not None: port = module.params["port"] else: port = 22 username = module.params["username"] password = module.params["password"] os = module.params["os"] feature = module.params["feature"] if module.params.get("compare_to"): # compare_to = json.loads(module.params.get("compare_to")) compare_to = module.params.get("compare_to") if module.params.get("exclude"): excluded_keys = module.params.get("exclude") if module.params.get("no_default_exclusion") is False: no_default_exclusion = False elif module.params.get("no_default_exclusion") is None: no_default_exclusion = False else: no_default_exclusion = True if module.params.get("colors") is False: colors = False elif module.params.get("colors") is not None: colors = True else: colors = True if module.params.get("protocol") == "telnet": protocol = "telnet" else: protocol = "ssh" # if the user is working with this module in only check mode we do not # want to make any changes to the environment, just return the current # state with no modifications if module.check_mode: module.exit_json(**result) # Check user input for k, v in module.params.items(): if k == "port" and v is not None: if not isinstance(v, int) and v not in range(1-65535): raise AnsibleError( "The {} parameter must be an integer between 0-65535".format(k) ) elif k == "compare_to": pass elif k == "port": pass elif k == "exclude": pass elif k == "colors": pass elif k == "no_default_exclusion": pass elif k == "protocol": pass else: if not isinstance(v, string_types): raise AnsibleError( "The {} parameter must be a string such as a hostname or IP address.".format( k ) ) # Did the user pass in a feature that is supported on a given platform genie_ops = importlib.util.find_spec("genie.libs.ops") ops_file_obj = Path(genie_ops.origin).parent.joinpath("ops.json") with open(ops_file_obj, "r") as f: ops_json = json.load(f) supported_features = [k for k, _ in ops_json.items()] # Load in default exclusions for diffs for all features for genie learn # genie_yamls = importlib.util.find_spec("genie.libs.sdk.genie_yamls") # genie_excludes = Path(genie_yamls.origin).parent.joinpath("pts_datafile.yaml") # with open(genie_excludes, "r") as f: # diff_excludes = json.load(f) # supported_features = [k for k, _ in ops_json.items()] default_excludes = {} from importlib import import_module for i in supported_features: modulename = "genie.libs.ops.{}.{}".format(i, i) package_name = i.capitalize() try: this_module = import_module(modulename, package_name) this_class = getattr(this_module, package_name) this_excludes = this_class.exclude default_excludes.update({i: this_excludes}) except AttributeError: default_excludes.update({i: []}) # this_module = __import__(modulename) # default_excludes.append({i: this_module.i) # from genie.libs.ops.i.i import Interface # Is the feature even supported? if feature not in supported_features: raise AnsibleError( "The feature entered is not supported on the current version of Genie.\nCurrently supported features: {0}\n{1}".format( to_native(supported_features), "https://pubhub.devnetcloud.com/media/genie-feature-browser/docs/#/models", ) ) # Is the feature supported on the OS that was provided from the user? for f in ops_json.items(): if feature == f[0]: if os not in [k for k, _ in f[1].items()]: raise AnsibleError( "The {0} feature entered is not supported on {1}.\nCurrently supported features & platforms:\n{2}".format( feature, os, "https://pubhub.devnetcloud.com/media/genie-feature-browser/docs/#/models", ) ) testbed = { "devices": { host: { "ip": host, "port": port, "protocol": protocol, "username": username, "password": password, "os": os, } } } tb = load(testbed) dev = tb.devices[host] dev.connect(log_stdout=False, learn_hostname=True) output = dev.learn(feature) # Do diff if compare_to was provided if module.params.get("compare_to"): # do genie diff # print(type(compare_to['genie'][feature])) # print(type(output.info)) # dd = Diff({"a": "yes"}, {"a": "no"}) # with open('/tmp/compare_to.txt', 'w') as f: # f.write(json.dumps(compare_to['genie'][feature])) # with open('/tmp/output.txt', 'w') as f: # f.write(json.dumps(output.info)) before = compare_to['genie'][feature] current = json.dumps(output.info) current = json.loads(current) # current = eval(str(output.info)) try: excluded_keys if no_default_exclusion: merged_exclusions = excluded_keys else: merged_exclusions = list(set().union(excluded_keys, default_excludes[feature])) dd = Diff(before, current, exclude=merged_exclusions) except NameError: if len(default_excludes[feature]) > 0: if no_default_exclusion: dd = Diff(before, current) else: dd = Diff(before, current, exclude=default_excludes[feature]) else: dd = Diff(before, current) dd.findDiff() if colors: result.update({"diff": {"prepared": '\n'.join(color_diff(str(dd)))}}) else: result.update({"diff": {"prepared": str(dd)}}) module._diff = True if len(str(dd)) > 0: result['changed'] = True feature_data = { feature: output.info } result.update({"genie": feature_data}) # use whatever logic you need to determine whether or not this module # made any modifications to your target # if module.params['new']: # result['changed'] = True # during the execution of the module, if there is an exception or a # conditional state that effectively causes a failure, run # AnsibleModule.fail_json() to pass in the message and the result # if module.params['name'] == 'fail me': # module.fail_json(msg='You requested this to fail', **result) # in the event of a successful module execution, you will want to # simple AnsibleModule.exit_json(), passing the key/value results module.exit_json(**result)