2
0
mirror of https://github.com/openvswitch/ovs synced 2025-08-22 01:51:26 +00:00
ovs/ovsdb/mutation.c

543 lines
14 KiB
C
Raw Normal View History

/* Copyright (c) 2009, 2010, 2011, 2012 Nicira, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <config.h>
#include "mutation.h"
#include <float.h>
#include <limits.h>
#include "column.h"
#include "ovsdb-error.h"
#include "openvswitch/json.h"
#include "row.h"
#include <string.h>
#include "table.h"
#include "util.h"
struct ovsdb_error *
ovsdb_mutator_from_string(const char *name, enum ovsdb_mutator *mutator)
{
#define OVSDB_MUTATOR(ENUM, NAME) \
if (!strcmp(name, NAME)) { \
*mutator = ENUM; \
return NULL; \
}
OVSDB_MUTATORS;
#undef OVSDB_MUTATOR
return ovsdb_syntax_error(NULL, "unknown mutator",
"No mutator named %s.", name);
}
const char *
ovsdb_mutator_to_string(enum ovsdb_mutator mutator)
{
switch (mutator) {
#define OVSDB_MUTATOR(ENUM, NAME) case ENUM: return NAME;
OVSDB_MUTATORS;
#undef OVSDB_MUTATOR
}
return NULL;
}
static OVS_WARN_UNUSED_RESULT struct ovsdb_error *
type_mismatch(const struct ovsdb_mutation *m, const struct json *json)
{
struct ovsdb_error *error;
char *s;
s = ovsdb_type_to_english(&m->column->type);
error = ovsdb_syntax_error(
json, NULL, "Type mismatch: \"%s\" operator may not be "
"applied to column %s of type %s.",
ovsdb_mutator_to_string(m->mutator), m->column->name, s);
free(s);
return error;
}
static OVS_WARN_UNUSED_RESULT struct ovsdb_error *
ovsdb_mutation_from_json(const struct ovsdb_table_schema *ts,
const struct json *json,
struct ovsdb_symbol_table *symtab,
struct ovsdb_mutation *m)
{
struct ovsdb_error *error;
const char *mutator_name;
const char *column_name;
if (json->type != JSON_ARRAY
|| json_array_size(json) != 3
|| json_array_at(json, 0)->type != JSON_STRING
|| json_array_at(json, 1)->type != JSON_STRING) {
return ovsdb_syntax_error(json, NULL, "Parse error in mutation.");
}
column_name = json_string(json_array_at(json, 0));
m->column = ovsdb_table_schema_get_column(ts, column_name);
if (!m->column) {
return ovsdb_syntax_error(json, "unknown column",
"No column %s in table %s.",
column_name, ts->name);
}
if (!m->column->mutable) {
return ovsdb_syntax_error(json, "constraint violation",
"Cannot mutate immutable column %s in "
"table %s.", column_name, ts->name);
}
2010-02-08 14:09:36 -08:00
ovsdb_type_clone(&m->type, &m->column->type);
mutator_name = json_string(json_array_at(json, 1));
error = ovsdb_mutator_from_string(mutator_name, &m->mutator);
if (error) {
2010-02-08 14:09:36 -08:00
goto exit;
}
/* Type-check and relax restrictions on 'type' if appropriate. */
switch (m->mutator) {
case OVSDB_M_ADD:
case OVSDB_M_SUB:
case OVSDB_M_MUL:
case OVSDB_M_DIV:
case OVSDB_M_MOD:
if ((!ovsdb_type_is_scalar(&m->type) && !ovsdb_type_is_set(&m->type))
2010-02-08 14:09:36 -08:00
|| (m->type.key.type != OVSDB_TYPE_INTEGER
&& m->type.key.type != OVSDB_TYPE_REAL)
|| (m->mutator == OVSDB_M_MOD
2010-02-08 14:09:36 -08:00
&& m->type.key.type == OVSDB_TYPE_REAL)) {
return type_mismatch(m, json);
}
2010-02-08 14:09:36 -08:00
ovsdb_base_type_clear_constraints(&m->type.key);
m->type.n_min = m->type.n_max = 1;
error = ovsdb_datum_from_json(&m->arg, &m->type,
json_array_at(json, 2), symtab);
2010-02-08 14:09:36 -08:00
break;
case OVSDB_M_INSERT:
case OVSDB_M_DELETE:
if (!ovsdb_type_is_set(&m->type) && !ovsdb_type_is_map(&m->type)) {
return type_mismatch(m, json);
}
m->type.n_min = 0;
if (m->mutator == OVSDB_M_DELETE) {
m->type.n_max = UINT_MAX;
}
error = ovsdb_datum_from_json(&m->arg, &m->type,
json_array_at(json, 2), symtab);
if (error && ovsdb_type_is_map(&m->type)
&& m->mutator == OVSDB_M_DELETE) {
ovsdb_error_destroy(error);
ovsdb-server: fix memory leak while deleting zone memory leak was detected by valgrind during execution of "database commands -- positive checks" test. leaked memory was allocated in ovsdb_execute_mutate() function while parsing mutations from the apparent json entity: ==19563== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==19563== by 0x4652D0: xmalloc (util.c:138) ==19563== by 0x46539E: xmemdup0 (util.c:168) ==19563== by 0x4653F7: xstrdup (util.c:177) ==19563== by 0x450379: ovsdb_base_type_clone (ovsdb-types.c:208) ==19563== by 0x450F8D: ovsdb_type_clone (ovsdb-types.c:550) ==19563== by 0x428C3F: ovsdb_mutation_from_json (mutation.c:108) ==19563== by 0x428F6B: ovsdb_mutation_set_from_json (mutation.c:187) ==19563== by 0x42578D: ovsdb_execute_mutate (execution.c:573) ==19563== by 0x4246B0: ovsdb_execute_compose (execution.c:171) ==19563== by 0x41CDE5: ovsdb_trigger_try (trigger.c:204) ==19563== by 0x41C8DF: ovsdb_trigger_init (trigger.c:61) ==19563== by 0x40E93C: ovsdb_jsonrpc_trigger_create (jsonrpc-server.c:1135) ==19563== by 0x40E20C: ovsdb_jsonrpc_session_got_request (jsonrpc-server.c:1002) ==19563== by 0x40D1C2: ovsdb_jsonrpc_session_run (jsonrpc-server.c:561) ==19563== by 0x40D31E: ovsdb_jsonrpc_session_run_all (jsonrpc-server.c:591) ==19563== by 0x40CD6E: ovsdb_jsonrpc_server_run (jsonrpc-server.c:406) ==19563== by 0x40627E: main_loop (ovsdb-server.c:209) ==19563== by 0x406E66: main (ovsdb-server.c:460) This memory is usually freed at the end of ovsdb_execute_mutate() however in the aforementioned test case this does not happen. Namely in case of delete mutator and in case of error while calling ovsdb_datum_from_json() apparent mutation was marked as invalid, what prevents freeing problematic memory. Memory leak can be reproduced quickly with the following command sequence: ovs-vsctl --no-wait -vreconnect:emer add-zone-tp netdev zone=1 icmp_first=1 icmp_reply=2 ovs-vsctl --no-wait -vreconnect:emer del-zone-tp netdev zone=1 Signed-off-by: Damijan Skvarc <damjan.skvarc@gmail.com> Signed-off-by: Ben Pfaff <blp@ovn.org>
2019-11-12 12:32:35 +01:00
ovsdb_base_type_destroy(&m->type.value);
m->type.value.enum_ = NULL;
2010-02-08 14:09:36 -08:00
m->type.value.type = OVSDB_TYPE_VOID;
error = ovsdb_datum_from_json(&m->arg, &m->type,
json_array_at(json, 2), symtab);
}
2010-02-08 14:09:36 -08:00
break;
default:
OVS_NOT_REACHED();
}
2010-02-08 14:09:36 -08:00
exit:
if (error) {
ovsdb_type_destroy(&m->type);
}
return error;
}
static void
ovsdb_mutation_free(struct ovsdb_mutation *m)
{
ovsdb_datum_destroy(&m->arg, &m->type);
2010-02-08 14:09:36 -08:00
ovsdb_type_destroy(&m->type);
}
struct ovsdb_error *
ovsdb_mutation_set_from_json(const struct ovsdb_table_schema *ts,
const struct json *json,
struct ovsdb_symbol_table *symtab,
struct ovsdb_mutation_set *set)
{
size_t i, n = json_array_size(json);
set->mutations = xmalloc(n * sizeof *set->mutations);
set->n_mutations = 0;
for (i = 0; i < n; i++) {
struct ovsdb_error *error;
error = ovsdb_mutation_from_json(ts, json_array_at(json, i), symtab,
&set->mutations[i]);
if (error) {
ovsdb_mutation_set_destroy(set);
set->mutations = NULL;
set->n_mutations = 0;
return error;
}
set->n_mutations++;
}
return NULL;
}
static struct json *
ovsdb_mutation_to_json(const struct ovsdb_mutation *m)
{
return json_array_create_3(
json_string_create(m->column->name),
json_string_create(ovsdb_mutator_to_string(m->mutator)),
ovsdb_datum_to_json(&m->arg, &m->type));
}
struct json *
ovsdb_mutation_set_to_json(const struct ovsdb_mutation_set *set)
{
struct json **mutations;
size_t i;
mutations = xmalloc(set->n_mutations * sizeof *mutations);
for (i = 0; i < set->n_mutations; i++) {
mutations[i] = ovsdb_mutation_to_json(&set->mutations[i]);
}
return json_array_create(mutations, set->n_mutations);
}
void
ovsdb_mutation_set_destroy(struct ovsdb_mutation_set *set)
{
size_t i;
for (i = 0; i < set->n_mutations; i++) {
ovsdb_mutation_free(&set->mutations[i]);
}
free(set->mutations);
}
2010-02-08 14:09:36 -08:00
enum ovsdb_mutation_scalar_error {
ME_OK,
ME_DOM,
ME_RANGE,
ME_NOTSUP
2010-02-08 14:09:36 -08:00
};
struct ovsdb_scalar_mutation {
int (*mutate_integer)(int64_t *x, int64_t y);
int (*mutate_real)(double *x, double y);
enum ovsdb_mutator mutator;
};
static const struct ovsdb_scalar_mutation add_mutation;
static const struct ovsdb_scalar_mutation sub_mutation;
static const struct ovsdb_scalar_mutation mul_mutation;
static const struct ovsdb_scalar_mutation div_mutation;
static const struct ovsdb_scalar_mutation mod_mutation;
static struct ovsdb_error *
ovsdb_mutation_scalar_error(enum ovsdb_mutation_scalar_error error,
enum ovsdb_mutator mutator)
{
switch (error) {
case ME_OK:
return OVSDB_BUG("unexpected success");
case ME_DOM:
return ovsdb_error("domain error", "Division by zero.");
case ME_RANGE:
return ovsdb_error("range error",
"Result of \"%s\" operation is out of range.",
ovsdb_mutator_to_string(mutator));
case ME_NOTSUP:
return ovsdb_error(NULL, "Operation not supported.");
2010-02-08 14:09:36 -08:00
default:
return OVSDB_BUG("unexpected error");
}
}
static int
check_real_range(double x)
{
return x >= -DBL_MAX && x <= DBL_MAX ? 0 : ME_RANGE;
}
static struct ovsdb_error *
mutate_scalar(const struct ovsdb_type *dst_type, struct ovsdb_datum *dst,
const union ovsdb_atom *arg,
const struct ovsdb_scalar_mutation *mutation)
{
const struct ovsdb_base_type *base = &dst_type->key;
struct ovsdb_error *error;
unsigned int i;
ovsdb_datum_unshare(dst, dst_type);
2010-02-08 14:09:36 -08:00
if (base->type == OVSDB_TYPE_INTEGER) {
int64_t y = arg->integer;
for (i = 0; i < dst->n; i++) {
enum ovsdb_mutation_scalar_error me;
me = (mutation->mutate_integer)(&dst->keys[i].integer, y);
if (me != ME_OK) {
return ovsdb_mutation_scalar_error(me, mutation->mutator);
}
}
} else if (base->type == OVSDB_TYPE_REAL) {
double y = arg->real;
for (i = 0; i < dst->n; i++) {
double *x = &dst->keys[i].real;
enum ovsdb_mutation_scalar_error me;
me = (mutation->mutate_real)(x, y);
if (me == ME_OK) {
me = check_real_range(*x);
}
if (me != ME_OK) {
return ovsdb_mutation_scalar_error(me, mutation->mutator);
}
}
} else {
OVS_NOT_REACHED();
2010-02-08 14:09:36 -08:00
}
for (i = 0; i < dst->n; i++) {
error = ovsdb_atom_check_constraints(&dst->keys[i], base);
if (error) {
return error;
}
}
error = ovsdb_datum_sort(dst, dst_type);
2010-02-08 14:09:36 -08:00
if (error) {
ovsdb_error_destroy(error);
return ovsdb_error("constraint violation",
"Result of \"%s\" operation contains duplicates.",
ovsdb_mutator_to_string(mutation->mutator));
}
return NULL;
}
static struct ovsdb_error *
ovsdb_mutation_check_count(struct ovsdb_datum *dst,
const struct ovsdb_type *dst_type)
{
if (!ovsdb_datum_conforms_to_type(dst, dst_type)) {
char *s = ovsdb_type_to_english(dst_type);
struct ovsdb_error *e = ovsdb_error(
"constraint violation",
2010-02-08 14:09:36 -08:00
"Attempted to store %u elements in %s.", dst->n, s);
free(s);
return e;
}
return NULL;
}
struct ovsdb_error *
ovsdb_mutation_set_execute(struct ovsdb_row *row,
const struct ovsdb_mutation_set *set)
{
size_t i;
for (i = 0; i < set->n_mutations; i++) {
const struct ovsdb_mutation *m = &set->mutations[i];
struct ovsdb_datum *dst = &row->fields[m->column->index];
const struct ovsdb_type *dst_type = &m->column->type;
const struct ovsdb_datum *arg = &set->mutations[i].arg;
const struct ovsdb_type *arg_type = &m->type;
struct ovsdb_error *error;
switch (m->mutator) {
case OVSDB_M_ADD:
error = mutate_scalar(dst_type, dst, &arg->keys[0], &add_mutation);
break;
case OVSDB_M_SUB:
error = mutate_scalar(dst_type, dst, &arg->keys[0], &sub_mutation);
break;
case OVSDB_M_MUL:
error = mutate_scalar(dst_type, dst, &arg->keys[0], &mul_mutation);
break;
case OVSDB_M_DIV:
error = mutate_scalar(dst_type, dst, &arg->keys[0], &div_mutation);
break;
case OVSDB_M_MOD:
error = mutate_scalar(dst_type, dst, &arg->keys[0], &mod_mutation);
break;
2010-02-08 14:09:36 -08:00
case OVSDB_M_INSERT:
ovsdb-data: Optimize union of sets. Current algorithm of ovsdb_datum_union looks like this: for-each atom in b: if not bin_search(a, atom): push(a, clone(atom)) quicksort(a) So, the complexity looks like this: Nb * log2(Na) + Nb + (Na + Nb) * log2(Na + Nb) Comparisons clones Comparisons for quicksort for search ovsdb_datum_union() is heavily used in database transactions while new element is added to a set. For example, if new logical switch port is added to a logical switch in OVN. This is a very common use case where CMS adds one new port to an existing switch that already has, let's say, 100 ports. For this case ovsdb-server will have to perform: 1 * log2(100) + 1 clone + 101 * log2(101) Comparisons Comparisons for for search quicksort. ~7 1 ~707 Roughly 714 comparisons of atoms and 1 clone. Since binary search can give us position, where new atom should go (it's the 'low' index after the search completion) for free, the logic can be re-worked like this: copied = 0 for-each atom in b: desired_position = bin_search(a, atom) push(result, a[ copied : desired_position - 1 ]) copied = desired_position push(result, clone(atom)) push(result, a[ copied : Na ]) swap(a, result) Complexity of this schema: Nb * log2(Na) + Nb + Na Comparisons clones memory copy on push for search 'swap' is just a swap of a few pointers. 'push' is not a 'clone', but a simple memory copy of 'union ovsdb_atom'. In general, this schema substitutes complexity of a quicksort with complexity of a memory copy of Na atom structures, where we're not even copying strings that these atoms are pointing to. Complexity in the example above goes down from 714 comparisons to 7 comparisons and memcpy of 100 * sizeof (union ovsdb_atom) bytes. General complexity of a memory copy should always be lower than complexity of a quicksort, especially because these copies usually performed in bulk, so this new schema should work faster for any input. All in all, this change allows to execute several times more transactions per second for transactions that adds new entries to sets. Alternatively, union can be implemented as a linear merge of two sorted arrays, but this will result in O(Na) comparisons, which is more than Nb * log2(Na) in common case, since Na is usually far bigger than Nb. Linear merge will also mean per-atom memory copies instead of copying in bulk. 'replace' functionality of ovsdb_datum_union() had no users, so it just removed. But it can easily be added back if needed in the future. Signed-off-by: Ilya Maximets <i.maximets@ovn.org> Acked-by: Han Zhou <hzhou@ovn.org> Acked-by: Mark D. Gray <mark.d.gray@redhat.com>
2021-09-23 01:47:22 +02:00
ovsdb_datum_union(dst, arg, dst_type);
2010-02-08 14:09:36 -08:00
error = ovsdb_mutation_check_count(dst, dst_type);
break;
case OVSDB_M_DELETE:
ovsdb_datum_subtract(dst, dst_type, arg, arg_type);
error = ovsdb_mutation_check_count(dst, dst_type);
break;
default:
OVS_NOT_REACHED();
2010-02-08 14:09:36 -08:00
}
if (error) {
return error;
}
}
return NULL;
}
static int
add_int(int64_t *x, int64_t y)
{
/* Check for overflow. See _Hacker's Delight_ pp. 27. */
int64_t z = ~(*x ^ y) & INT64_MIN;
if ((~(*x ^ y) & ~(((*x ^ z) + y) ^ y)) >> 63) {
return ME_RANGE;
} else {
*x += y;
return 0;
}
}
static int
sub_int(int64_t *x, int64_t y)
{
/* Check for overflow. See _Hacker's Delight_ pp. 27. */
int64_t z = (*x ^ y) & INT64_MIN;
if (((*x ^ y) & (((*x ^ z) - y) ^ y)) >> 63) {
return ME_RANGE;
} else {
*x -= y;
return 0;
}
}
static int
mul_int(int64_t *x, int64_t y)
{
/* Check for overflow. See _Hacker's Delight_ pp. 30. */
if (*x > 0
? (y > 0
? *x >= INT64_MAX / y
: y < INT64_MIN / *x)
: (y > 0
? *x < INT64_MIN / y
: *x != 0 && y < INT64_MAX / y)) {
return ME_RANGE;
} else {
*x *= y;
return 0;
}
}
static int
check_int_div(int64_t x, int64_t y)
{
/* Check for overflow. See _Hacker's Delight_ pp. 32. */
if (!y) {
return ME_DOM;
} else if (x == INT64_MIN && y == -1) {
return ME_RANGE;
} else {
return 0;
}
}
static int
div_int(int64_t *x, int64_t y)
{
int error = check_int_div(*x, y);
if (!error) {
*x /= y;
}
return error;
}
static int
mod_int(int64_t *x, int64_t y)
{
int error = check_int_div(*x, y);
if (!error) {
*x %= y;
}
return error;
}
static int
add_double(double *x, double y)
{
*x += y;
return 0;
}
static int
sub_double(double *x, double y)
{
*x -= y;
return 0;
}
static int
mul_double(double *x, double y)
{
*x *= y;
return 0;
}
static int
div_double(double *x, double y)
{
if (y == 0) {
return ME_DOM;
} else {
*x /= y;
return 0;
}
}
static int
mod_double(double *x OVS_UNUSED, double y OVS_UNUSED)
{
return ME_NOTSUP;
}
2010-02-08 14:09:36 -08:00
static const struct ovsdb_scalar_mutation add_mutation = {
add_int, add_double, OVSDB_M_ADD
};
2010-02-08 14:09:36 -08:00
static const struct ovsdb_scalar_mutation sub_mutation = {
sub_int, sub_double, OVSDB_M_SUB
};
2010-02-08 14:09:36 -08:00
static const struct ovsdb_scalar_mutation mul_mutation = {
mul_int, mul_double, OVSDB_M_MUL
};
2010-02-08 14:09:36 -08:00
static const struct ovsdb_scalar_mutation div_mutation = {
div_int, div_double, OVSDB_M_DIV
};
2010-02-08 14:09:36 -08:00
static const struct ovsdb_scalar_mutation mod_mutation = {
mod_int, mod_double, OVSDB_M_MOD
2010-02-08 14:09:36 -08:00
};