cancel
Showing results for 
Search instead for 
Did you mean: 
cancel
1732
Views
10
Helpful
10
Replies

'Must' and other constraints on lists - learning NSO the hard way

rslaski
Spotlight
Spotlight

Hello wise guys,

 

When learning to code with NSO, I move three steps forward, just to realize I did not understand the thing, so I have to go two steps back. The same story I had with basic YANG, callbacks, XML context, and a number of topics, so now it came to polishing my YANG model with constraints, and desperately need some clarification on how 'must' can be applied to list key elements.

 

Here's a simplified version of what I am talking about:

container EVPN {
	tailf:info "TMPL EVPN services";
	tailf:cli-add-mode;
	uses TMPL-EVPN_L2;
	uses TMPL-EVPN_L3;
}
		
grouping TMPL-EVPN_L2 {
    list EVPN_L2 {
		uses ncs:service-data;
		ncs:servicepoint EVPN-L2-servicepoint;

		key name;

		leaf name {
			tailf:info "EVPN L2 service name";
			type service-name-type;

			must "../vnid" {
				error-message "VNID must be set for a service";
			}
		}
		
		leaf vnid {
			tailf:info "VXLAN VNID for an L2 service";
			type vnid-l2-type;
		}
		
		list dc {
			tailf:info "Per DC configuration mode";

			key dc-id;

			leaf dc-id {
				type leafref {
					path "/datacenters/dc/dc-id";
				}
			}
			list ac {
				tailf:info "AC services to attach";
				key name;
				leaf name {
				    type leafref {
						path "/TMPL:TMPL/TMPL:AC/TMPL:name";
					}
					must "../../../vnid" {
						error-message "VNID has not been defined for this EVPN L2 service";
					}
				}
			}
		}
	}
}

So basically we have an EVPN_L2 list keyed with name, and there's another leaf called 'vnid', plus a deeper list structure 'dc/ac' pointing to another service.

 

The requirement is that EVPN_L2 service instance is valid only when any 'vnid' has been assigned to the service. So for testing I've created two 'must' constraints:
- the first one has been applied at the EVPN_L2/name key
- the second has been applied deeper, at the EVPN_L2/dc/ac/name.

 

Now, as I test my model, at first I configure the service instance without 'vnid', but it fails to trigger the first must constraint, so the model is passed down to Python code, which catches error due to empty vnid:

rslaski@ncs(config)# TMPL EVPN EVPN_L2 TEST1
rslaski@ncs(config-EVPN_L2-TEST1)# show conf
TMPL
 EVPN
  EVPN_L2 TEST1
  !
 !
!
rslaski@ncs(config-EVPN_L2-TEST1)# commit dr
Aborted: Python cb_create error. item has a bad/wrong type (5): "1:None" is not a valid value.

However, when I configure deeper list instance (dc/ac), now the second 'must' kicks in, properly validating empty vnid:

rslaski@ncs(config-EVPN_L2-TEST1)# dc 24246 ac RSL1
rslaski@ncs(config-ac-RSL1)# show conf
TMPL
 EVPN
  EVPN_L2 TEST1
   dc 24246
    ac RSL1
    !
   !
  !
 !
!
rslaski@ncs(config-ac-RSL1)# commit dry
Aborted: 'TMPL EVPN EVPN_L2 TEST1 dc 24246 ac RSL1 name' (value "RSL1"): VNID has not been defined for this EVPN L2 service

 

Why the deeper 'must' triggered, while the first one did not?

 

Answering the question you might have: "why not to use 'mandatory true'". I personally don't like it:
- I do not like when CLI forces me to fill all 'mandatory true' objects, as I start creating the service.
- I cannot specify the error-message for better explanation of the broken constraint.

 

Another question: RFC7950 sec.1.1 states that 'when' is illegal on list keys, while I have noticed they can be applied, and they work. Where can I find a list of features the Cisco NSO has, which deviate from the RFC standard other than 'tailf:xxx' documented in the manual pages?

 

2 Accepted Solutions

Accepted Solutions

yfherzog
Cisco Employee
Cisco Employee

I don't know the exact reason for why you're seeing this behavior, but one thing you can try is to move the must statement outside of the leaf (I think it also makes more sense modeling-wise).

Something like this:

		must "./vnid" {
			error-message "VNID must be set for a service";
		}

		leaf name {
			tailf:info "EVPN L2 service name";
			type service-name-type;
		}

Regarding mandatory statement I get your point.

For the CLI prompt however, you can disable autowizard:

 

$ ncs_cli -u admin -C

admin connected from 127.0.0.1 using console on YFHERZOG-M-70RM
admin@ncs# autowizard false 

This will disable it for the current session.

You can modify ncs.conf if you want it permanent (have a look at nso_man in your doc directory under your install dir)

View solution in original post

Yes, this was a bug that was fixed quite recently. Try NSO-5.2.3, which was released just a week back. You can check the CHANGES note for that release, which says

  - cs_trans: If a service input parameter had a must statement referring to
    the service keys, NSO did not validate that before invoking the service
    callback.

The inner must statement was invoked earlier, because it was not in the key of a service - the bug was only affecting must statements in the keys of a service. (I think having must statements inside list keys is strange, especially if the statement refers to some other leaf, rather than the key's own value.)

So, similar to what was suggested earlier, I think having the must statement at the list level (rather than inside the key) makes it more readable. You said "it did not work, when placed just under list" - can you please elaborate what didn't work?

Also, in this case, the two must statements are redundant - the inner statement doesn't add any extra constraint. It is just an extra evaluation of the same constraint specified by the outer statement. So, having a single must expression at the outer level is enough.

/Ram

View solution in original post

10 Replies 10

yfherzog
Cisco Employee
Cisco Employee

I don't know the exact reason for why you're seeing this behavior, but one thing you can try is to move the must statement outside of the leaf (I think it also makes more sense modeling-wise).

Something like this:

		must "./vnid" {
			error-message "VNID must be set for a service";
		}

		leaf name {
			tailf:info "EVPN L2 service name";
			type service-name-type;
		}

Regarding mandatory statement I get your point.

For the CLI prompt however, you can disable autowizard:

 

$ ncs_cli -u admin -C

admin connected from 127.0.0.1 using console on YFHERZOG-M-70RM
admin@ncs# autowizard false 

This will disable it for the current session.

You can modify ncs.conf if you want it permanent (have a look at nso_man in your doc directory under your install dir)

Thank you for your 'autowizard false' hint. It's gives much better user experience:

rslaski@ncs(config-EVPN_L2-TUTU1)# commit dr
Aborted: 'TMPL EVPN EVPN_L2 TUTU1 vnid' is not configured

This simple message can be a little cryptic for a number of scenarios, however. And 'autowizard' is referenced as 'auto-wizard' in ncs.conf (why make life easier?)

I still, however, wonder, why 'must' did not work (also, it did not work, when placed just under list).

Which NSO version are you using?

 

It used to be the case that NSO was, by design, evaluating must statements on a later stage comparing to the service callbacks execution (as far as I'm aware, it's no longer the case on recent NSO releases).

So, transaction will fail over must statements only after the Python code ran.

 

If that is the case for the NSO version you're running, It might be that the must statement is actually working, but you don't see it as execution fails before the statement is evaluated.

 

Try committing the service with flag 'no-deploy'.

This will complete the transaction without invoking the python code.

If the must statement is working correctly, it should now fail on the must statement, rather than on the code.

 

It might be that the inner must statement is being evaluated at the same time, but your code is not failing when no value is provided to that node, so it seems the two statements behave differently, but in fact they are not.

I am using 5.2.2, and yes, with 'no-deploy', the outer 'must' is invoked:

 

 

rslaski@ncs(config-EVPN_L2-TUTU1)# commit dry-run 
Aborted: Python cb_create error. item has a bad/wrong type (5): "1:None" is not a valid value.
rslaski@ncs(config-EVPN_L2-TUTU1)# commit dry-run no-deploy 
Aborted: 'TMPL EVPN EVPN_L2 TUTU1 name' (value "TUTU1"): VNID must be set for a service

However, as the Python code is needed to generate all the stuff, so with 'no-deploy' we will not be able to verify proper template creation, unless we make a three step commit:
- commit dry-run no-deploy   # to verify all yang constraints
- commit dry-run    # to verify the template
- commit     # to actually commit the changes

 

 

This makes no sense. I also did not really understand the explanation why inner 'must' has been invoked before python callback, while outer one has been invoked after that.

 

I also do not want to build YANG model validation inside Python code, as YANG itself should do that.

no-deploy was suggested as a way to confirm the behavior - not as a workaround.

 

As mentioned, I'd expect that both the inner and outer must statements are invoked at the same time (after the python code ran).

The difference in the results you see might be due to the way that you're using the two values in your code - on the outer it causes some sort of exception (from XML?) and on the inner one it doesn't (I'd expect you're using those nodes  differently).

 

As far as I know, the behavior of service callbacks running prior to must statements being evaluated has changed on recent NSO releases. However, I don't know the exact release on which this has happened. Might be that someone reading the thread can comment on that.

 

What I can suggest in the meantime is not to implement the same validation in python, but instead, make your code not to fail when values are not provided to those nodes (e.g. only use the value when it's not None, rather than raising exceptions). You then should see the same behavior for both must statements.

With Python code checking for missing VNID first, and calling return, I do observe outer 'must' being called, and the sequence of events can be observed with debug:

rslaski@ncs(config-EVPN_L2-TUTU1)# commit dry-run | debug service | debug xpath 
 2020-04-19T15:41:12.178 xpath: evaluating: /TMPL/EVPN/EVPN_L2[name='TUTU1']/customer-name: /TMPL:TMPL/customers/customer/name
 2020-04-19T15:41:12.178 xpath: get_next(/TMPL/customers/customer) = {1}
 2020-04-19T15:41:12.178 xpath: get_elem(/TMPL/customers/customer[id='1']/name) = xxxx
 2020-04-19T15:41:12.179 xpath: get_elem(/TMPL/EVPN/EVPN_L2[name='TUTU1']/customer-name) = Malinka
 2020-04-19T15:41:12.179 xpath: get_next(/TMPL/customers/customer[id='1']) = {1001}
 2020-04-19T15:41:12.180 xpath: get_elem(/TMPL/customers/customer[id='1001']/name) = Malinka
 2020-04-19T15:41:12.180 xpath: result: /TMPL/EVPN/EVPN_L2[name='TUTU1']/customer-name: true [0.002 sec]

Service: /TMPL/EVPN/EVPN_L2[name='TUTU1']
Service done

 2020-04-19T15:41:12.386 xpath: evaluating: /TMPL/EVPN/EVPN_L2[name='TUTU1']: vnid
 2020-04-19T15:41:12.387 xpath: get_elem(/TMPL/EVPN/EVPN_L2[name='TUTU1']/vnid) = not_found
 2020-04-19T15:41:12.387 xpath: result: /TMPL/EVPN/EVPN_L2[name='TUTU1']: false [0.000 sec]
Aborted: 'TMPL EVPN EVPN_L2 TUTU1' : VNID must be set for a service

If we remove outer check, and leave only inner one, the callback is not being called:

rslaski@ncs(config-ac-RSL1)# commit dry-run | debug service | debug xpath 
 2020-04-19T15:47:04.110 xpath: evaluating: /TMPL/EVPN/EVPN_L2[name='TUTU1']/dc[dc-id='24246']/dc-id: /datacenters/dc/dc-id
 2020-04-19T15:47:04.110 xpath: exists(/datacenters/dc[dc-id='24246']) = true
 2020-04-19T15:47:04.111 xpath: result: /TMPL/EVPN/EVPN_L2[name='TUTU1']/dc[dc-id='24246']/dc-id: true [0.000 sec]
 2020-04-19T15:47:04.111 xpath: evaluating: /TMPL/EVPN/EVPN_L2[name='TUTU1']/dc[dc-id='24246']/ac[name='RSL1']/name: /TMPL:TMPL/TMPL:AC/TMPL:name
 2020-04-19T15:47:04.112 xpath: exists(/TMPL/AC[name='RSL1']) = true
 2020-04-19T15:47:04.112 xpath: result: /TMPL/EVPN/EVPN_L2[name='TUTU1']/dc[dc-id='24246']/ac[name='RSL1']/name: true [0.001 sec]
 2020-04-19T15:47:04.113 xpath: evaluating: /TMPL/EVPN/EVPN_L2[name='TUTU1']/dc[dc-id='24246']/ac[name='RSL1']/name: ../../../vnid
 2020-04-19T15:47:04.113 xpath: get_elem(/TMPL/EVPN/EVPN_L2[name='TUTU1']/vnid) = not_found
 2020-04-19T15:47:04.113 xpath: result: /TMPL/EVPN/EVPN_L2[name='TUTU1']/dc[dc-id='24246']/ac[name='RSL1']/name: false [0.000 sec]
Aborted: 'TMPL EVPN EVPN_L2 TUTU1 dc 24246 ac RSL1 name' (value "RSL1"): VNID has not been defined for this EVPN L2 service

Ok, I am close to implementing the solution right now (object presence needs to be checked inside Python), however I still do not understand why NSO applied such operation order, and which parts of YANG are processed before, and after the service create call.

Yes, this was a bug that was fixed quite recently. Try NSO-5.2.3, which was released just a week back. You can check the CHANGES note for that release, which says

  - cs_trans: If a service input parameter had a must statement referring to
    the service keys, NSO did not validate that before invoking the service
    callback.

The inner must statement was invoked earlier, because it was not in the key of a service - the bug was only affecting must statements in the keys of a service. (I think having must statements inside list keys is strange, especially if the statement refers to some other leaf, rather than the key's own value.)

So, similar to what was suggested earlier, I think having the must statement at the list level (rather than inside the key) makes it more readable. You said "it did not work, when placed just under list" - can you please elaborate what didn't work?

Also, in this case, the two must statements are redundant - the inner statement doesn't add any extra constraint. It is just an extra evaluation of the same constraint specified by the outer statement. So, having a single must expression at the outer level is enough.

/Ram

Yes, these two statements are pretty redundant, for test purposes only - in a production only outer one should be applied.

I've observed exacly the same behavior when the condition was applied at the list key level:

            list EVPN_L2 {
leaf name { tailf:info "EVPN L2 service name"; type service-name-type; must "../vnid" { error-message "VNID must be set for a service"; }
} }

and at the list itself:

        list EVPN_L2 {
            leaf name {
                tailf:info "EVPN L2 service name";
                type service-name-type;
            }
            must "vnid" {
                error-message "VNID must be set for a service";
            }   
         }

My customer is running 5.2.2.1, is there a fix planned for 5.2.2.x?

 

Guys,

I've checked the 'must' behavior with 5.2.3, and when placed either under the list or under the list key, now it works just fine, so I'll recommend upgrading the system to 5.2.3. I also tried few versions back, to 5.2.1, and the issue was there, so I just wonder why nobody noticed that before.

 

Anyhow, thank you yfherzog and ramkraja for restoring my faith in NSO again :-)

Glad to know you got it solved, and that you got your faith back :)

 

I was a bit late to realize that the two must statements were referring to the same node (for some reason, I thought they just happen to have the same name :) ). Sorry about that.

 

Getting Started

Find answers to your questions by entering keywords or phrases in the Search bar above. New here? Use these resources to familiarize yourself with the NSO Developer community: