diff --git a/CHANGELOG.md b/CHANGELOG.md index def33a54..d8b85100 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,13 +15,14 @@ Expected: June 2024 ### Features +* New: [Autolock](https://github.com/clicon/clixon/issues/508) * CLI configurable format: [Default format should be configurable](https://github.com/clicon/clixon-controller/issues/87) - * CLI support for multiple inline commands separated by semi-colon * New `clixon-config@2024-04-01.yang` revision * Added options: - `CLICON_NETCONF_DUPLICATE_ALLOW` - Disable duplicate check in NETCONF messages - `CLICON_CLI_OUTPUT_FORMAT` - Default CLI output format + - `CLICON_AUTOLOCK` - Implicit locks * New `clixon-lib@2024-04-01.yang` revision - Added: Default format diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 15e597f2..38c81ae0 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -175,6 +175,8 @@ ce_event_cb(clixon_handle h, /*! Unlock all db:s of a client and call user unlock calback * + * @param[in] h Clixon handle + * @param[in] id Session id * @see xmldb_unlock_all unlocks, but does not call user callbacks which is a backend thing */ static int @@ -185,8 +187,15 @@ release_all_dbs(clixon_handle h, char **keys = NULL; size_t klen; int i; + uint32_t iddb; db_elmnt *de; + if (clicon_option_bool(h, "CLICON_AUTOLOCK") && + (iddb = xmldb_islocked(h, "candidate")) == id){ + if (xmldb_copy(h, "running", "candidate") < 0) + goto done; + xmldb_modified_set(h, "candidate", 0); /* reset dirty bit */ + } /* get all db:s */ if (clicon_hash_keys(clicon_db_elmnt(h), &keys, &klen) < 0) goto done; @@ -195,6 +204,8 @@ release_all_dbs(clixon_handle h, if ((de = clicon_db_elmnt_get(h, keys[i])) != NULL && de->de_id == id){ de->de_id = 0; /* unlock */ + + clicon_db_elmnt_set(h, keys[i], de); if (clixon_plugin_lockdb_all(h, keys[i], 0, id) < 0) goto done; @@ -432,6 +443,73 @@ clixon_stats_module_get(clixon_handle h, return retval; } +/*! Do lock checks and lock + + * @param[in] h Clixon handle + * @param[out] cbret Return xml tree, eg ..., , it has already been modified, and + * these changes have not been committed or rolled back. + */ + if (strcmp(db, "candidate") == 0 && + xmldb_modified_get(h, db)){ + if (netconf_lock_denied(cbret, "0", + "Operation failed, candidate has already been modified and the changes have not been committed or rolled back (RFC 6241 7.5)") < 0) + goto done; + goto failed; + } + /* 3) The target configuration is , and another NETCONF + * session has an ongoing confirmed commit + */ + if (strcmp(db, "running") == 0 && + if_feature(yspec, "ietf-netconf", "confirmed-commit") && + confirmed_commit_state_get(h) != INACTIVE){ + if ((otherid = confirmed_commit_session_id_get(h)) != 0){ + cprintf(cbx, "%u", otherid); + if (netconf_lock_denied(cbret, cbuf_get(cbx), + "Operation failed, another session has an ongoing confirmed commit") < 0) + goto done; + goto failed; + } + } + if (xmldb_lock(h, db, id) < 0) + goto done; + /* user callback */ + if (clixon_plugin_lockdb_all(h, db, 1, id) < 0) + goto done; + retval = 1; + done: + if (cbx) + cbuf_free(cbx); + return retval; + failed: + retval = 0; + goto done; +} + /*! Loads all or part of a specified configuration to target configuration * * @param[in] h Clixon handle @@ -499,6 +577,12 @@ from_client_edit_config(clixon_handle h, goto done; goto ok; } + if (clicon_option_bool(h, "CLICON_AUTOLOCK")){ + if ((ret = do_lock(h, cbret, myid, target)) < 0) + goto done; + if (ret == 0) + goto ok; + } if (xml_nsctx_node(xn, &nsc) < 0) goto done; /* Get prefix of netconf base namespace in the incoming message */ @@ -693,6 +777,7 @@ from_client_copy_config(clixon_handle h, uint32_t myid = ce->ce_id; cbuf *cbx = NULL; /* Assist cbuf */ cbuf *cbmsg = NULL; + int ret; if ((source = netconf_db_find(xe, "source")) == NULL){ if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0) @@ -716,6 +801,12 @@ from_client_copy_config(clixon_handle h, goto done; goto ok; } + if (clicon_option_bool(h, "CLICON_AUTOLOCK")){ + if ((ret = do_lock(h, cbret, myid, target)) < 0) + goto done; + if (ret == 0) + goto ok; + } if (xmldb_copy(h, source, target) < 0){ if ((cbmsg = cbuf_new()) == NULL){ clixon_err(OE_UNIX, errno, "cbuf_new"); @@ -834,16 +925,11 @@ from_client_lock(clixon_handle h, int retval = -1; struct client_entry *ce = (struct client_entry *)arg; uint32_t id = ce->ce_id; - uint32_t iddb; - uint32_t otherid; char *db; + int ret; cbuf *cbx = NULL; /* Assist cbuf */ - yang_stmt *yspec; + uint32_t iddb; - if ((yspec = clicon_dbspec_yang(h)) == NULL){ - clixon_err(OE_YANG, ENOENT, "No yang spec"); - goto done; - } if ((db = netconf_db_find(xe, "target")) == NULL){ if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) goto done; @@ -863,35 +949,10 @@ from_client_lock(clixon_handle h, goto done; goto ok; } - /* 2) The target configuration is , it has already been modified, and - * these changes have not been committed or rolled back. - */ - if (strcmp(db, "candidate") == 0 && - xmldb_modified_get(h, db)){ - if (netconf_lock_denied(cbret, "0", - "Operation failed, candidate has already been modified and the changes have not been committed or rolled back (RFC 6241 7.5)") < 0) - goto done; + if ((ret = do_lock(h, cbret, id, db)) < 0) + goto done; + if (ret == 0) goto ok; - } - /* 3) The target configuration is , and another NETCONF - * session has an ongoing confirmed commi - */ - if (strcmp(db, "running") == 0 && - if_feature(yspec, "ietf-netconf", "confirmed-commit") && - confirmed_commit_state_get(h) != INACTIVE){ - if ((otherid = confirmed_commit_session_id_get(h)) != 0){ - cprintf(cbx, "%u", otherid); - if (netconf_lock_denied(cbret, cbuf_get(cbx), - "Operation failed, another session has an ongoing confirmed commit") < 0) - goto done; - goto ok; - } - } - if (xmldb_lock(h, db, id) < 0) - goto done; - /* user callback */ - if (clixon_plugin_lockdb_all(h, db, 1, id) < 0) - goto done; cprintf(cbret, "", NETCONF_BASE_NAMESPACE); ok: retval = 0; diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 3380cac4..c4b1805c 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -801,6 +801,16 @@ from_client_commit(clixon_handle h, goto ok; } /* Check if target locked by other client */ + iddb = xmldb_islocked(h, "candidate"); + if (iddb && myid != iddb){ + if ((cbx = cbuf_new()) == NULL){ + clixon_err(OE_XML, errno, "cbuf_new"); + goto done; + } + if (netconf_in_use(cbret, "protocol", "Operation failed, lock is already held") < 0) + goto done; + goto ok; + } iddb = xmldb_islocked(h, "running"); if (iddb && myid != iddb){ if ((cbx = cbuf_new()) == NULL){ @@ -818,6 +828,8 @@ from_client_commit(clixon_handle h, goto done; goto ok; } + if (clicon_option_bool(h, "CLICON_AUTOLOCK")) + xmldb_unlock(h, "candidate"); if (ret == 0) clixon_debug(CLIXON_DBG_BACKEND, "Commit candidate failed"); else @@ -872,6 +884,9 @@ from_client_discard_changes(clixon_handle h, goto ok; } xmldb_modified_set(h, "candidate", 0); /* reset dirty bit */ + if (clicon_option_bool(h, "CLICON_AUTOLOCK")){ + xmldb_unlock(h, "candidate"); + } cprintf(cbret, "", NETCONF_BASE_NAMESPACE); ok: retval = 0; diff --git a/yang/clixon/clixon-config@2024-04-01.yang b/yang/clixon/clixon-config@2024-04-01.yang index 50efacd1..44f5ade8 100644 --- a/yang/clixon/clixon-config@2024-04-01.yang +++ b/yang/clixon/clixon-config@2024-04-01.yang @@ -54,6 +54,7 @@ module clixon-config { "Added options: CLICON_NETCONF_DUPLICATE_ALLOW - Disable duplicate check in NETCONF messages. CLICON_CLI_OUTPUT_FORMAT - Default CLI output format + CLICON_AUTOLOCK - Implicit locks Released in Clixon 7.1"; } revision 2024-01-01 { @@ -779,8 +780,8 @@ module clixon-config { type int32; default 1; description - "Set to 0 if you want CLI to wrap to next line. - Set to 1 if you want CLI to scroll sideways when approaching + "Set to 0 if you want CLI INPUT to wrap to next line. + Set to 1 if you want CLI INPUT to scroll sideways when approaching right margin"; } leaf CLICON_CLI_LINES_DEFAULT { @@ -976,6 +977,16 @@ module clixon-config { persistent confirming commit. (consider boolean)"; } + leaf CLICON_AUTOLOCK { + type boolean; + default false; + description + "Set if all edit-config implicitly locks without the need of an explicit lock-db + In short, the lock is obtained by edit-config and copy-config and released by + discard and commit. + Also, any edits in candidate are discarded if the client closes the connection. + This effectively disables shared candidate"; + } leaf CLICON_XMLDB_DIR { type string; mandatory true;