summaryrefslogtreecommitdiff
path: root/lib/northbound.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/northbound.c')
-rw-r--r--lib/northbound.c251
1 files changed, 189 insertions, 62 deletions
diff --git a/lib/northbound.c b/lib/northbound.c
index 5e031ac2ce..dbf90c58d4 100644
--- a/lib/northbound.c
+++ b/lib/northbound.c
@@ -40,6 +40,21 @@ struct nb_config *running_config;
/* Hash table of user pointers associated with configuration entries. */
static struct hash *running_config_entries;
+/* Management lock for the running configuration. */
+static struct {
+ /* Mutex protecting this structure. */
+ pthread_mutex_t mtx;
+
+ /* Actual lock. */
+ bool locked;
+
+ /* Northbound client who owns this lock. */
+ enum nb_client owner_client;
+
+ /* Northbound user who owns this lock. */
+ const void *owner_user;
+} running_config_mgmt_lock;
+
/*
* Global lock used to prevent multiple configuration transactions from
* happening concurrently.
@@ -51,6 +66,7 @@ static int nb_callback_configuration(const enum nb_event event,
static struct nb_transaction *nb_transaction_new(struct nb_config *config,
struct nb_config_cbs *changes,
enum nb_client client,
+ const void *user,
const char *comment);
static void nb_transaction_free(struct nb_transaction *transaction);
static int nb_transaction_process(enum nb_event event,
@@ -248,6 +264,7 @@ struct nb_config *nb_config_new(struct lyd_node *dnode)
else
config->dnode = yang_dnode_new(ly_native_ctx, true);
config->version = 0;
+ pthread_rwlock_init(&config->lock, NULL);
return config;
}
@@ -256,6 +273,7 @@ void nb_config_free(struct nb_config *config)
{
if (config->dnode)
yang_dnode_free(config->dnode);
+ pthread_rwlock_destroy(&config->lock);
XFREE(MTYPE_NB_CONFIG, config);
}
@@ -266,6 +284,7 @@ struct nb_config *nb_config_dup(const struct nb_config *config)
dup = XCALLOC(MTYPE_NB_CONFIG, sizeof(*dup));
dup->dnode = yang_dnode_dup(config->dnode);
dup->version = config->version;
+ pthread_rwlock_init(&dup->lock, NULL);
return dup;
}
@@ -513,17 +532,28 @@ int nb_candidate_edit(struct nb_config *candidate,
bool nb_candidate_needs_update(const struct nb_config *candidate)
{
- if (candidate->version < running_config->version)
- return true;
+ bool ret = false;
- return false;
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ if (candidate->version < running_config->version)
+ ret = true;
+ }
+ pthread_rwlock_unlock(&running_config->lock);
+
+ return ret;
}
int nb_candidate_update(struct nb_config *candidate)
{
struct nb_config *updated_config;
- updated_config = nb_config_dup(running_config);
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ updated_config = nb_config_dup(running_config);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
+
if (nb_config_merge(updated_config, candidate, true) != NB_OK)
return NB_ERR;
@@ -575,15 +605,20 @@ int nb_candidate_validate(struct nb_config *candidate)
return NB_ERR_VALIDATION;
RB_INIT(nb_config_cbs, &changes);
- nb_config_diff(running_config, candidate, &changes);
- ret = nb_candidate_validate_changes(candidate, &changes);
- nb_config_diff_del_changes(&changes);
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ nb_config_diff(running_config, candidate, &changes);
+ ret = nb_candidate_validate_changes(candidate, &changes);
+ nb_config_diff_del_changes(&changes);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
return ret;
}
int nb_candidate_commit_prepare(struct nb_config *candidate,
- enum nb_client client, const char *comment,
+ enum nb_client client, const void *user,
+ const char *comment,
struct nb_transaction **transaction)
{
struct nb_config_cbs changes;
@@ -596,25 +631,36 @@ int nb_candidate_commit_prepare(struct nb_config *candidate,
}
RB_INIT(nb_config_cbs, &changes);
- nb_config_diff(running_config, candidate, &changes);
- if (RB_EMPTY(nb_config_cbs, &changes))
- return NB_ERR_NO_CHANGES;
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ nb_config_diff(running_config, candidate, &changes);
+ if (RB_EMPTY(nb_config_cbs, &changes)) {
+ pthread_rwlock_unlock(&running_config->lock);
+ return NB_ERR_NO_CHANGES;
+ }
- if (nb_candidate_validate_changes(candidate, &changes) != NB_OK) {
- flog_warn(EC_LIB_NB_CANDIDATE_INVALID,
- "%s: failed to validate candidate configuration",
- __func__);
- nb_config_diff_del_changes(&changes);
- return NB_ERR_VALIDATION;
- }
+ if (nb_candidate_validate_changes(candidate, &changes)
+ != NB_OK) {
+ flog_warn(
+ EC_LIB_NB_CANDIDATE_INVALID,
+ "%s: failed to validate candidate configuration",
+ __func__);
+ nb_config_diff_del_changes(&changes);
+ pthread_rwlock_unlock(&running_config->lock);
+ return NB_ERR_VALIDATION;
+ }
- *transaction = nb_transaction_new(candidate, &changes, client, comment);
- if (*transaction == NULL) {
- flog_warn(EC_LIB_NB_TRANSACTION_CREATION_FAILED,
- "%s: failed to create transaction", __func__);
- nb_config_diff_del_changes(&changes);
- return NB_ERR_LOCKED;
+ *transaction = nb_transaction_new(candidate, &changes, client,
+ user, comment);
+ if (*transaction == NULL) {
+ flog_warn(EC_LIB_NB_TRANSACTION_CREATION_FAILED,
+ "%s: failed to create transaction", __func__);
+ nb_config_diff_del_changes(&changes);
+ pthread_rwlock_unlock(&running_config->lock);
+ return NB_ERR_LOCKED;
+ }
}
+ pthread_rwlock_unlock(&running_config->lock);
return nb_transaction_process(NB_EV_PREPARE, *transaction);
}
@@ -633,7 +679,11 @@ void nb_candidate_commit_apply(struct nb_transaction *transaction,
/* Replace running by candidate. */
transaction->config->version++;
- nb_config_replace(running_config, transaction->config, true);
+ pthread_rwlock_wrlock(&running_config->lock);
+ {
+ nb_config_replace(running_config, transaction->config, true);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
/* Record transaction. */
if (save_transaction
@@ -645,13 +695,13 @@ void nb_candidate_commit_apply(struct nb_transaction *transaction,
}
int nb_candidate_commit(struct nb_config *candidate, enum nb_client client,
- bool save_transaction, const char *comment,
- uint32_t *transaction_id)
+ const void *user, bool save_transaction,
+ const char *comment, uint32_t *transaction_id)
{
struct nb_transaction *transaction = NULL;
int ret;
- ret = nb_candidate_commit_prepare(candidate, client, comment,
+ ret = nb_candidate_commit_prepare(candidate, client, user, comment,
&transaction);
/*
* Apply the changes if the preparation phase succeeded. Otherwise abort
@@ -666,6 +716,60 @@ int nb_candidate_commit(struct nb_config *candidate, enum nb_client client,
return ret;
}
+int nb_running_lock(enum nb_client client, const void *user)
+{
+ int ret = -1;
+
+ pthread_mutex_lock(&running_config_mgmt_lock.mtx);
+ {
+ if (!running_config_mgmt_lock.locked) {
+ running_config_mgmt_lock.locked = true;
+ running_config_mgmt_lock.owner_client = client;
+ running_config_mgmt_lock.owner_user = user;
+ ret = 0;
+ }
+ }
+ pthread_mutex_unlock(&running_config_mgmt_lock.mtx);
+
+ return ret;
+}
+
+int nb_running_unlock(enum nb_client client, const void *user)
+{
+ int ret = -1;
+
+ pthread_mutex_lock(&running_config_mgmt_lock.mtx);
+ {
+ if (running_config_mgmt_lock.locked
+ && running_config_mgmt_lock.owner_client == client
+ && running_config_mgmt_lock.owner_user == user) {
+ running_config_mgmt_lock.locked = false;
+ running_config_mgmt_lock.owner_client = NB_CLIENT_NONE;
+ running_config_mgmt_lock.owner_user = NULL;
+ ret = 0;
+ }
+ }
+ pthread_mutex_unlock(&running_config_mgmt_lock.mtx);
+
+ return ret;
+}
+
+int nb_running_lock_check(enum nb_client client, const void *user)
+{
+ int ret = -1;
+
+ pthread_mutex_lock(&running_config_mgmt_lock.mtx);
+ {
+ if (!running_config_mgmt_lock.locked
+ || (running_config_mgmt_lock.owner_client == client
+ && running_config_mgmt_lock.owner_user == user))
+ ret = 0;
+ }
+ pthread_mutex_unlock(&running_config_mgmt_lock.mtx);
+
+ return ret;
+}
+
static void nb_log_callback(const enum nb_event event,
enum nb_operation operation, const char *xpath,
const char *value)
@@ -812,13 +916,20 @@ int nb_callback_rpc(const struct nb_node *nb_node, const char *xpath,
return nb_node->cbs.rpc(xpath, input, output);
}
-static struct nb_transaction *nb_transaction_new(struct nb_config *config,
- struct nb_config_cbs *changes,
- enum nb_client client,
- const char *comment)
+static struct nb_transaction *
+nb_transaction_new(struct nb_config *config, struct nb_config_cbs *changes,
+ enum nb_client client, const void *user, const char *comment)
{
struct nb_transaction *transaction;
+ if (nb_running_lock_check(client, user)) {
+ flog_warn(
+ EC_LIB_NB_TRANSACTION_CREATION_FAILED,
+ "%s: running configuration is locked by another client",
+ __func__);
+ return NULL;
+ }
+
if (transaction_in_progress) {
flog_warn(
EC_LIB_NB_TRANSACTION_CREATION_FAILED,
@@ -852,40 +963,52 @@ static int nb_transaction_process(enum nb_event event,
{
struct nb_config_cb *cb;
- RB_FOREACH (cb, nb_config_cbs, &transaction->changes) {
- struct nb_config_change *change = (struct nb_config_change *)cb;
- int ret;
-
- /*
- * Only try to release resources that were allocated
- * successfully.
- */
- if (event == NB_EV_ABORT && change->prepare_ok == false)
- break;
+ /*
+ * Need to lock the running configuration since transaction->changes
+ * can contain pointers to data nodes from the running configuration.
+ */
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ RB_FOREACH (cb, nb_config_cbs, &transaction->changes) {
+ struct nb_config_change *change =
+ (struct nb_config_change *)cb;
+ int ret;
- /* Call the appropriate callback. */
- ret = nb_callback_configuration(event, change);
- switch (event) {
- case NB_EV_PREPARE:
- if (ret != NB_OK)
- return ret;
- change->prepare_ok = true;
- break;
- case NB_EV_ABORT:
- case NB_EV_APPLY:
/*
- * At this point it's not possible to reject the
- * transaction anymore, so any failure here can lead to
- * inconsistencies and should be treated as a bug.
- * Operations prone to errors, like validations and
- * resource allocations, should be performed during the
- * 'prepare' phase.
+ * Only try to release resources that were allocated
+ * successfully.
*/
- break;
- default:
- break;
+ if (event == NB_EV_ABORT && change->prepare_ok == false)
+ break;
+
+ /* Call the appropriate callback. */
+ ret = nb_callback_configuration(event, change);
+ switch (event) {
+ case NB_EV_PREPARE:
+ if (ret != NB_OK) {
+ pthread_rwlock_unlock(
+ &running_config->lock);
+ return ret;
+ }
+ change->prepare_ok = true;
+ break;
+ case NB_EV_ABORT:
+ case NB_EV_APPLY:
+ /*
+ * At this point it's not possible to reject the
+ * transaction anymore, so any failure here can
+ * lead to inconsistencies and should be treated
+ * as a bug. Operations prone to errors, like
+ * validations and resource allocations, should
+ * be performed during the 'prepare' phase.
+ */
+ break;
+ default:
+ break;
+ }
}
}
+ pthread_rwlock_unlock(&running_config->lock);
return NB_OK;
}
@@ -1531,7 +1654,7 @@ static bool running_config_entry_cmp(const void *value1, const void *value2)
return strmatch(c1->xpath, c2->xpath);
}
-static unsigned int running_config_entry_key_make(void *value)
+static unsigned int running_config_entry_key_make(const void *value)
{
return string_hash_make(value);
}
@@ -1704,6 +1827,8 @@ const char *nb_client_name(enum nb_client client)
return "ConfD";
case NB_CLIENT_SYSREPO:
return "Sysrepo";
+ case NB_CLIENT_GRPC:
+ return "gRPC";
default:
return "unknown";
}
@@ -1761,6 +1886,7 @@ void nb_init(struct thread_master *tm,
running_config_entries = hash_create(running_config_entry_key_make,
running_config_entry_cmp,
"Running Configuration Entries");
+ pthread_mutex_init(&running_config_mgmt_lock.mtx, NULL);
/* Initialize the northbound CLI. */
nb_cli_init(tm);
@@ -1778,4 +1904,5 @@ void nb_terminate(void)
hash_clean(running_config_entries, running_config_entry_free);
hash_free(running_config_entries);
nb_config_free(running_config);
+ pthread_mutex_destroy(&running_config_mgmt_lock.mtx);
}