diff options
Diffstat (limited to 'lib/northbound.c')
| -rw-r--r-- | lib/northbound.c | 251 |
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); } |
