05-09-2019 06:53 AM - edited 05-09-2019 06:55 AM
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:
@Service.create
@Service.post_modification
Post Modification Code:
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"; } } } }
Solved! Go to Solution.
05-10-2019 11:22 AM
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.
05-10-2019 09:31 AM
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')
05-10-2019 10:35 AM
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.
05-10-2019 11:22 AM
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.
05-21-2019 07:38 AM
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,
05-21-2019 08:29 AM
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.
05-21-2019 10:16 AM
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.
Discover and save your favorite ideas. Come back to expert answers, step-by-step guides, recent topics, and more.
New here? Get started with these tips. How to use Community New member guide