How to disalbe/lock the config node?

For example, I got the follow data model,

container abc {
    leaf a {type string;}
    leaf b {type int;}
    leaf lock {type boolean; default false;}
}

We want the config behavior like this:

  1. when the leaf lock value is false, we could config leaf a and b, and then commit ok.
(lock is false)

# config a x
# config b y
# commit
# OK
  1. when the leaf lock value is true, we should lock the container but the lock leaf itself, It means we could NOT config leaf a and leaf b, but we could config leaf lock anyway.
(lock is true)

# config a xx
# commit
# FAIL

#config lock false
#commit
#OK

if node have configured, suppose leaf a with value ‘va’, leaf b with value ‘vb’, now we do the following config

# config lock true
# commit
# OK
# config a xva
# commit
# FAIL
# do show abc

abc a xa
abc b xb
abc lock ture

Here we set the lock state true, so that we could not config the leaf a and b, but we want keep the values of a and b without being changed.

Both when and must does not work, tailf:display-when works but it will hide the nodes, so how to disable/lock the config node without hiding them?

What in your YANG must statement is not working as expected? E.g.

container abc {
    leaf a {type string; must "../lock = 'false'" {error-message "Locked";}}
    leaf b {type int32; must "../lock = 'false'" {error-message "Locked";}}
    leaf lock {type boolean; default false;}
}

no, it is not working as expected.

First the lock is false, then i config a, it is ok.

localhost(config)# abc a xx
localhost(config)# commit
(OK)

And then, I want to lock the config, but keep the previous data of a,

localhost(config)# abc lock true
localhost(config)# commit
Aborted: 'abc a' (value "xx"): Locked

the must statement for a means if a has value, the lock must be false, that’s why I say It doesn’t work.

Right, you need to make a validation point here and avoid the dependancy to changes of /abc/lock. I.e. when the lock value is changed the validation on the a and b leafs are not executed. The validation is done only if the a or b leaf is reconfigured.

Example:

We have a YANG model like this one:

module mtest {
  namespace "http://tail-f.com/ns/example/mtest";
  prefix mtest;

  import tailf-common {
    prefix tailf;
  }

  container abc {
        leaf a {type string; tailf:validate vp {tailf:dependency '.';}}
        leaf b {type int32; tailf:validate vp {tailf:dependency '.';}}
        leaf lock {type boolean; default false;}
  }
}

Our validator C application would look something like this (see check_lock() for the actual validation code):

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/poll.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <stdarg.h>
#include <stdio.h>

#include "confd_lib.h"
#include "confd_dp.h"
#include "confd_maapi.h"

/* include generated ns file */
#include "mtest.h"

int debuglevel = CONFD_DEBUG;

static int ctlsock;
static int workersock;
static int maapi_socket;
static struct confd_daemon_ctx *dctx;

struct confd_trans_validate_cbs vcb;
struct confd_valpoint_cb valp1;

static void OK(int rval)
{
    if (rval != CONFD_OK) {
        fprintf(stderr, "validate.c: error not CONFD_OK: %d : %s \n",
                confd_errno, confd_lasterr());
        abort();
    }
}

static int init_validation(struct confd_trans_ctx *tctx)
{
    OK(maapi_attach(maapi_socket, mtest__ns, tctx));
    confd_trans_set_fd(tctx, workersock);
    return CONFD_OK;
}

static int stop_validation(struct confd_trans_ctx *tctx)
{
    OK(maapi_detach(maapi_socket, tctx));
    return CONFD_OK;
}

static int check_lock(struct confd_trans_ctx *tctx,
                        confd_hkeypath_t *keypath,
                        confd_value_t *newval)
{
   int lockval;
   OK(maapi_get_bool_elem(maapi_socket, tctx->thandle, &lockval,
                                "/abc/lock"));
   if(lockval) {
     confd_trans_seterr(tctx, "Locked");
     return CONFD_ERR;
   }
  return CONFD_OK;
}

static int maapi_sock(int *maapi_sock)
{

    struct sockaddr_in addr;

    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_family = AF_INET;
    addr.sin_port = htons(4565);

    if ((*maapi_sock = socket(PF_INET, SOCK_STREAM, 0)) < 0 )
        confd_fatal("Failed to open socket\n");

    if (maapi_connect(*maapi_sock, (struct sockaddr*)&addr,
                      sizeof (struct sockaddr_in)) < 0)
        confd_fatal("Failed to confd_connect() to confd \n");

    return CONFD_OK;
}

int main(int argc, char **argv)
{
    int c;

    struct sockaddr_in addr;

    while ((c = getopt(argc, argv, "tdps")) != -1) {
        switch(c) {
        case 't':
            debuglevel = CONFD_TRACE;
            break;
        case 'd':
            debuglevel = CONFD_DEBUG;
            break;
        case 'p':
            debuglevel = CONFD_PROTO_TRACE;
            break;
        case 's':
            debuglevel = CONFD_SILENT;
            break;
        }
    }


    confd_init("MYNAME", stderr, debuglevel);

    if ((dctx = confd_init_daemon("mydaemon")) == NULL)
        confd_fatal("Failed to initialize confd\n");

    if ((ctlsock = socket(PF_INET, SOCK_STREAM, 0)) < 0 )
        confd_fatal("Failed to open ctlsocket\n");

    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_family = AF_INET;
    addr.sin_port = htons(CONFD_PORT);

    /* Create the first control socket, all requests to */
    /* create new transactions arrive here */
    if (confd_connect(dctx, ctlsock, CONTROL_SOCKET,
                      (struct sockaddr*)&addr,
                      sizeof (struct sockaddr_in)) < 0) {
        confd_fatal("Failed to confd_connect() to confd \n");
    }

    /* Also establish a workersocket, this is the most simple */
    /* case where we have just one ctlsock and one workersock */

    if ((workersock = socket(PF_INET, SOCK_STREAM, 0)) < 0 )
        confd_fatal("Failed to open workersocket\n");
    if (confd_connect(dctx, workersock, WORKER_SOCKET,(struct sockaddr*)&addr,
                      sizeof (struct sockaddr_in)) < 0)
        confd_fatal("Failed to confd_connect() to confd \n");

    vcb.init = init_validation;
    vcb.stop = stop_validation;
    confd_register_trans_validate_cb(dctx, &vcb);

    valp1.validate = check_lock;
    strcpy(valp1.valpoint, "vp");
    OK(confd_register_valpoint_cb(dctx, &valp1));

    OK(confd_register_done(dctx));

    OK(maapi_sock(&maapi_socket));

    while (1) {
        struct pollfd set[2];
        int ret;

        set[0].fd = ctlsock;
        set[0].events = POLLIN;
        set[0].revents = 0;

        set[1].fd = workersock;
        set[1].events = POLLIN;
        set[1].revents = 0;

        if (poll(&set[0], 2, -1) < 0) {
            perror("Poll failed:");
            continue;
        }

        if (set[0].revents & POLLIN) {
            if ((ret = confd_fd_ready(dctx, ctlsock)) == CONFD_EOF) {
                confd_fatal("Control socket closed\n");
            } else if (ret == CONFD_ERR && confd_errno != CONFD_ERR_EXTERNAL) {
                confd_fatal("Error on control socket request: %s (%d): %s\n",
                     confd_strerror(confd_errno), confd_errno, confd_lasterr());
            }
        }
        if (set[1].revents & POLLIN) {
            if ((ret = confd_fd_ready(dctx, workersock)) == CONFD_EOF) {
                confd_fatal("Worker socket closed\n");
            } else if (ret == CONFD_ERR && confd_errno != CONFD_ERR_EXTERNAL) {
                confd_fatal("Error on worker socket request: %s (%d): %s\n",
                     confd_strerror(confd_errno), confd_errno, confd_lasterr());
            }
        }
    }
}

A slightly different answer: please read the “Semantic validation” chapter of the User Guide. Validation is supposed to answer the question (a) “Is the proposed configuration valid?” It is specifically not supposed to answer the question (b) “Are the changes that led to the proposed configuration valid?”. Trying to implement (b) will lead to all kinds of problems, such as going from one valid configuration to another by means of loading a rollback or a backup being impossible. “must” expressions enforce (a), since they can only express a constraint on a valid configuration. This is a Good Thing™.

Put a different way, your desired functionality will force an operator that wishes to do

config a xx
commit

to instead do

config lock false
commit
config a xx
commit
config lock true
commit

How does this improve the quality of your product?

thank you, @cohult , making a validation point works.

@per, my desired functionality looks strange, actually, I wanna hide the lock config, and set its value to true or false by MO via MAAPI, so that the user can’t change the config that have been locked, but user can show the old configurations.

If the yang xpath could make it, it will save a lot of validation codes.

OK, then my “silly CLI” argument goes away - but if the “locked” values are ever changed, you may still run into the problem with rollback and backups. I.e. your validation callback will consider a rollback/backup with locked=true, a=xx as “invalid” if the current configuration is locked=true, a=yy.

Yang “must” can (by design) not do what you ask for, i.e. make certain changes invalid, for the reason I mentioned. The configs locked=true, a=xx and locked=true, a=yy are both valid, but you want a restriction that says “a must not be changed when locked=true”. No can do with “must”.

You’re right! I haven’t bethink of rollback/backup. Now it seems ‘must’, ‘when’, ‘display-when’ and validation callback don’t work as expected, so any other suggestions?

Well, they work according to the standard so that would be as expected I believe? :wink:

Can you describe your use-case a bit more so that we can perhaps have a better chance at helping you with other alternatives?

Sorry, I didn’t express myself clearly.
The ‘must’, ‘when’, ‘display-when’ and validation work well as they are designed, I mean they are not suitable for locking/unlocking the configurations.

I want to control the config ability of some special node, no matter it could be configured or not, user can show/view them always just like normal config nodes. We could suppose it is a read only config in some condition (eg, the value of flag leaf is locked).

You can consider making use of AAA data authorization rules to control the write permissions of leaf a and leaf b depending on the current value of the lock leaf in your backend, perhaps through a hook? However, the use of hooks to modify the AAA rules is only advisable if your AAA data model, at least the authorization part, are hidden from your northbound management systems. Hooks shouldn’t cause changes to other parts of the data model unless they are hidden.

In addition, this approach may potentially cause issues with rollback unless the user performing the rollback function always have write permissions to those leafs (a & b).

Unfortunately, there is a flaw with my previous suggestion. The AAA data rule change in the hook function won’t take effect immediately for any active user sessions. Any active user sessions will need to end and log back in before the authorization rule change takes effect.