cancel
Showing results for 
Search instead for 
Did you mean: 
cancel
2851
Views
15
Helpful
6
Replies

When to use pre/post modification

tsiemers1
Spotlight
Spotlight

I am working on creating a service that will upgrade a certain model in our network. Ideally the service will first grab some yang data and then do the following below.

 

Is this a good use of pre/post services? Precheck copy over image if it doesn't exist and verify it, run some pre show commands. If I bundle all this together in just the service.create will it have consequences down the road if I remove the service? Or is this better suited for an Action and not a service in NSO?

 

@Service.pre_modification

Pre-Modification code:

  1. Check router boot flash for all .bin files
  2. If more than one bin file delete all but current one
  3. Copy over new boot image to boot flash
  4. Verify Image
  5. Run pre check show commands, save to do diff on post check

 

@Service.create
  1. Set boot flash via template
  2. save running config
  3. reload router

 

@Service.post_modification

Post Modification Code:

  1. wait for router to come back
  2. run post check show commands and do a diff with precheck 
  3. present diff of pre/post check to user or save to file system

 

Yang:

module XE-Upgrade-IOS {

  namespace "http://example.com/XE-Upgrade-IOS";
  prefix XE-Upgrade-IOS;

  import ietf-inet-types {
    prefix inet;
  }
  import tailf-common {
    prefix tailf;
  }
  import tailf-ncs {
    prefix ncs;
  }

  description
    "Service to upgrade ASR920 Routers to new image";

  revision 2019-05-08 {
    description
      "Initial revision.";
  }

  list XE-Upgrade-IOS {
    description "upgrade IOS image on ASR920 XE";

    uses ncs:service-data;
    ncs:servicepoint XE-Upgrade-IOS-servicepoint;

    key upgradeImage;
    leaf upgradeImage {
      tailf:info "Image to load/upgrade onto router";
      type enumeration {
        enum '16.09.03';
        enum '16.09.02';
      }
    }

    leaf upgrade {
      tailf:info "Upgrade router if true, if false just preload image onto router";
      type boolean;
      default false;
    }

    leaf deviceGroup {
      tailf:info "set to upgrade group, hidden";
      type string;
      tailf:hidden "full";
      default "920Upgrade";
    }

    leaf deviceName {
      tailf:info "Choose device within preset device-group 920Upgrades";
      type leafref {
        path "/ncs:devices/ncs:device-group[ncs:name=current()/../deviceGroup]/ncs:device-name";
      }
    }
  }
}

 

1 Accepted Solution

Accepted Solutions

Yes almost. I should have given a more complete example sorry - here is the YANG:

 

  container ios-upgrade {
    leaf target-version {
        tailf:info "IOS version string. Leave empty to skip upgrade";
        type string;
    }

    leaf image-file-path {
        tailf:info "The full path prefix including trailing / to the directory of the image to copy";
        type string;
    }

    leaf image-file-name {
        tailf:info "The image filename (will be appended to the file-path prefix";
        type string;
    }

    list request {
      tailf:info "Device to upgrade to target version";
      key device;
      leaf device {
        type leafref {
          path "/ncs:devices/ncs:device/ncs:name";
        }
      }
      leaf result {
        config false;
        tailf:cdb-oper {
          tailf:persistent true;
        }
        type enumeration {
          enum skipped;
          enum error;
          enum reloaded;
        }
      }
    }
  }

 

A subscriber registers a node in the CDB and watches for changes below that node. So in the example '/ios-upgrade/request/device' is simply a leaf in a list. When the leaf is created/modified, the subscriber is triggered. So adding a entry to the request list will cause the upgrade process to start for the device given. This entry could be written by an action, or service or directly through the CLI, WebUI etc.

 

This is the same way the resource-manager package works (if you're familiar with that).

 

In my example, I'm using the same image for every request, but you could set the image per request (which is how you have it your YANG).

 

Regarding the transaction lock, inside service create code, the transaction is already running and has a global lock, which is why you should keep this code efficient. Code in other callbacks for actions and subscribers doesn't have this lock and you create your own Maapi transactions.

 

You could do the upgrade directly in an action rather than a subscriber, the code would run the same way, but the interface you use to trigger the action (i.e. CLI, REST etc) would probably time out waiting for it run (although the timeouts can be configured in ncs.conf). By introducing the subscriber you are doing the upgrade asynchronously. It also means you can trigger the upgrade safely from service code (if it makes sense to) using the reactive fastmap design pattern.

 

View solution in original post

6 Replies 6

Michael Maddern
Cisco Employee
Cisco Employee

The pre and post mod functions are still executed inside the transaction lock, so it's not a good idea to do anything in these which takes a long time to execute (like copying a file or waiting for the router to restart).

 

Services in NSO usually create device configuration which has a lifecycle - i.e. it can be updated and deleted (NSO automatically takes care of these operations). So this probably doesn't make sense for upgrading a router image. You can use an Action, but this could still time out if you execute the upgrade commands directly in the action.

 

You could trigger the upgrade request with an action (or even a service), but use a separate CDB subscriber to do the upgrade. Here's a basic example I've used before - you'd need to update it slightly to work with your YANG model.

 

import ncs
from ncs.experimental import Subscriber
from ncs import maagic
from ncs import maapi

class IosUpgradeSubscriber(Subscriber):
    def init(self):
        self.register('/ios-upgrade/request/device')

    def pre_iterate(self):
        return []

    def iterate(self, kp, op, oldval, newval, state):
        if op is ncs.MOP_VALUE_SET:
            self.log.info('New IOS upgrade request: %s' % (str(kp[1:])))
            state.append((newval, str(kp[1:])))
        return ncs.ITER_RECURSE

    def post_iterate(self, state):
        for request in state:
            self.write_result(ios_upgrade(self.log, request[0]), request[1])

    def should_post_iterate(self, state):
        return state != []

    def write_result(self, result, kp):
        with maapi.single_write_trans('admin', 'python', db=ncs.OPERATIONAL) as th:
            root = maagic.get_root(th)
            maagic.cd(root, str(kp)).result = result
            th.apply()

def ios_upgrade(log, device_name):
    with ncs.maapi.single_read_trans('admin', 'python') as th:
        root = ncs.maagic.get_root(th)
        image_filename = root.ios_upgrade.image_file_name
        image_filepath = root.ios_upgrade.image_file_path
        ios_version = root.ios_upgrade.target_version
        device = root.devices.device[device_name]

        if ios_version is None:
            return 'skipped'

        log.info('%s - Upgrading IOS to %s' % (device.name, ios_version))
        if get_current_version(log, device) == ios_version:
            log.info('%s - IOS already at target version' % (device.name))
            return 'skipped'

        verify_result = verify_image(log, device, image_filename, image_filepath)
        if verify_result == False:
            copy_image(log, device, image_filename, image_filepath);
            verify_result = verify_image(log, device, image_filename, image_filepath)

        if verify_result == False:
            return 'error'
        else:
            update_system_boot_marker(log, device, image_filename)
            write_memory(log, device)
            reload(log, device)
            return 'reloaded'


def run_action(log, action, input_string):
    action_input = action.get_input()
    action_input.args = [input_string]
    action_output = action(action_input)

    log.info(action_output.result)
    return action_output.result

def get_current_version(log, device):
    current_version = device.live_status.ios_stats__version.version
    log.info('%s - Current IOS version: %s' % (device.name, current_version))
    return current_version

def verify_image(log, device, filename, filepath):
    log.info('%s - Verifying image file %s' % (device.name, filename))

    verify_action = device.live_status.ios_stats__exec.verify
    verify_result = run_action(log, verify_action, 'bootflash:/%s' % (filename))

    if 'Digital signature successfully verified' in verify_result:
        return True
    else:
        log.info('%s - Bad or missing image file %s' % (device.name, filename))
        return False


def copy_image(log, device, filename, filepath):
    log.info('%s - Copying %s%s to bootflash' % (device.name, filepath, filename))

    copy_action = device.live_status.ios_stats__exec.copy
    run_action(log, copy_action, '%s%s bootflash: | prompts ENTER ENTER ENTER ENTER' % (filepath, filename))

def update_system_boot_marker(log, device, image_filename):
    log.info('%s - Updating system boot marker' % (device.name))

    any_action = device.live_status.ios_stats__exec.any
    run_action(log, any_action, 'configure terminal')
    run_action(log, any_action, 'no boot system')
    run_action(log, any_action, 'boot system bootflash:/%s' % (image_filename))
    run_action(log, any_action, 'exit')

def write_memory(log, device):
    log.info('%s - Writing memory' % (device.name))
    any_action = device.live_status.ios_stats__exec.any
    run_action(log, any_action, 'write memory')

def reload(log, device):
    log.info('%s - Reloading' % (device.name))
    any_action = device.live_status.ios_stats__exec.any
    run_action(log, any_action, 'reload | prompts ENTER')

Thanks @Michael Maddern 

I will have to research the subscriber class. It did lead me to the getting started example "1-cdb".

So If I am reading this correctly you have a action named "/ios-upgrade/request/device" that the subscriber is watching. When a user kicks off the action with the yang variables below it starts the upgrade process and reports back to the subscriber?

image_filename = root.ios_upgrade.image_file_name
image_filepath = root.ios_upgrade.image_file_path
ios_version = root.ios_upgrade.target_version
device = root.devices.device[device_name]

Running it this way overrides the transaction lock and runs outside of it for the verify, upgrade, reload of the action?

Creating one and testing it now using the:

ncs-make-package --service-skeleton python SubscriberTest --subscriber-example

This does seem to be exactly what I was after, appreciate the example very helpful as a reference reading through the guide.

Yes almost. I should have given a more complete example sorry - here is the YANG:

 

  container ios-upgrade {
    leaf target-version {
        tailf:info "IOS version string. Leave empty to skip upgrade";
        type string;
    }

    leaf image-file-path {
        tailf:info "The full path prefix including trailing / to the directory of the image to copy";
        type string;
    }

    leaf image-file-name {
        tailf:info "The image filename (will be appended to the file-path prefix";
        type string;
    }

    list request {
      tailf:info "Device to upgrade to target version";
      key device;
      leaf device {
        type leafref {
          path "/ncs:devices/ncs:device/ncs:name";
        }
      }
      leaf result {
        config false;
        tailf:cdb-oper {
          tailf:persistent true;
        }
        type enumeration {
          enum skipped;
          enum error;
          enum reloaded;
        }
      }
    }
  }

 

A subscriber registers a node in the CDB and watches for changes below that node. So in the example '/ios-upgrade/request/device' is simply a leaf in a list. When the leaf is created/modified, the subscriber is triggered. So adding a entry to the request list will cause the upgrade process to start for the device given. This entry could be written by an action, or service or directly through the CLI, WebUI etc.

 

This is the same way the resource-manager package works (if you're familiar with that).

 

In my example, I'm using the same image for every request, but you could set the image per request (which is how you have it your YANG).

 

Regarding the transaction lock, inside service create code, the transaction is already running and has a global lock, which is why you should keep this code efficient. Code in other callbacks for actions and subscribers doesn't have this lock and you create your own Maapi transactions.

 

You could do the upgrade directly in an action rather than a subscriber, the code would run the same way, but the interface you use to trigger the action (i.e. CLI, REST etc) would probably time out waiting for it run (although the timeouts can be configured in ncs.conf). By introducing the subscriber you are doing the upgrade asynchronously. It also means you can trigger the upgrade safely from service code (if it makes sense to) using the reactive fastmap design pattern.

 

Hey @Michael Maddern 

 

newbie question here, I am working on getting this working in the lab. What is the correct way to register the global ios_upgrade called below:

 

def post_iterate(self, state):
for request in state:
self.write_result(ios_upgrade(self.log, request[0]), request[1])

 I am getting the following when running:

File "/var/opt/ncs/state/packages-in-use/1/IosUpgradeSubscriber/python/IosUpgradeSubscriber/main.py", line 26, in post_iterate
self.write_result(ios_upgrade(self.log, request[0]), request[1])
NameError: global name 'ios_upgrade' is not defined

 

Do I need to just do an import of the ios_upgrade class or does it need to be registered in the subscriber to call the action?

 

I tried doing a from ios_upgrade import ios_upgrade but I am not sure on the right way to call an action from within the subscriber.

 

Thanks,

In the example, ios_upgrade is a function, not a class. So it either needs to be defined in the same file as the post_iterate function (where you're calling it from) or you need to import it from the file it's defined in:

 

from file_name import ios_upgrade

This is just a simple example though. Since there are several steps for the upgrade, it would probably be better to use a class instead of a function.

Thanks, that was it. I was trying to put all the ios_upgrade functions into a separate action in NSO. Then call that action in the post_iterate, after moving all the functions into the subscriber it is working.

 

Thanks again, started to come together.