diff --git a/lib/automake.mk b/lib/automake.mk index 66bfbff6432..5dfce7fd184 100644 --- a/lib/automake.mk +++ b/lib/automake.mk @@ -176,6 +176,8 @@ lib_libopenvswitch_la_SOURCES = \ lib/ovsdb-idl-provider.h \ lib/ovsdb-idl.c \ lib/ovsdb-idl.h \ + lib/ovsdb-map-op.c \ + lib/ovsdb-map-op.h \ lib/ovsdb-parser.c \ lib/ovsdb-parser.h \ lib/ovsdb-types.c \ @@ -508,4 +510,3 @@ lib-install-data-local: $(MKDIR_P) $(DESTDIR)$(PKIDIR) $(MKDIR_P) $(DESTDIR)$(LOGDIR) $(MKDIR_P) $(DESTDIR)$(DBDIR) - diff --git a/lib/ovsdb-idl-provider.h b/lib/ovsdb-idl-provider.h index 027f79bfe85..04cf4192aac 100644 --- a/lib/ovsdb-idl-provider.h +++ b/lib/ovsdb-idl-provider.h @@ -19,6 +19,7 @@ #include "hmap.h" #include "openvswitch/list.h" #include "ovsdb-idl.h" +#include "ovsdb-map-op.h" #include "ovsdb-types.h" #include "shash.h" #include "uuid.h" @@ -36,6 +37,8 @@ struct ovsdb_idl_row { unsigned long int *prereqs; /* Bitmap of columns to verify in "old". */ unsigned long int *written; /* Bitmap of columns from "new" to write. */ struct hmap_node txn_node; /* Node in ovsdb_idl_txn's list. */ + unsigned long int *map_op_written; /* Bitmap of columns pending map ops. */ + struct map_op_list **map_op_lists; /* Per-column map operations. */ /* Tracking data */ unsigned int change_seqno[OVSDB_IDL_CHANGE_MAX]; diff --git a/lib/ovsdb-idl.c b/lib/ovsdb-idl.c index a046b6b1660..2b372cbc306 100644 --- a/lib/ovsdb-idl.c +++ b/lib/ovsdb-idl.c @@ -181,6 +181,7 @@ static struct ovsdb_idl_row *ovsdb_idl_row_create(struct ovsdb_idl_table *, const struct uuid *); static void ovsdb_idl_row_destroy(struct ovsdb_idl_row *); static void ovsdb_idl_row_destroy_postprocess(struct ovsdb_idl *); +static void ovsdb_idl_destroy_all_map_op_lists(struct ovsdb_idl_row *); static void ovsdb_idl_row_parse(struct ovsdb_idl_row *); static void ovsdb_idl_row_unparse(struct ovsdb_idl_row *); @@ -191,6 +192,12 @@ static void ovsdb_idl_row_clear_arcs(struct ovsdb_idl_row *, bool destroy_dsts); static void ovsdb_idl_txn_abort_all(struct ovsdb_idl *); static bool ovsdb_idl_txn_process_reply(struct ovsdb_idl *, const struct jsonrpc_msg *msg); +static bool ovsdb_idl_txn_extract_mutations(struct ovsdb_idl_row *, + struct json *); +static void ovsdb_idl_txn_add_map_op(struct ovsdb_idl_row *, + const struct ovsdb_idl_column *, + struct ovsdb_datum *, + enum map_op_type); static void ovsdb_idl_send_lock_request(struct ovsdb_idl *); static void ovsdb_idl_send_unlock_request(struct ovsdb_idl *); @@ -1596,6 +1603,8 @@ ovsdb_idl_row_create(struct ovsdb_idl_table *table, const struct uuid *uuid) hmap_insert(&table->rows, &row->hmap_node, uuid_hash(uuid)); row->uuid = *uuid; row->table = table; + row->map_op_written = NULL; + row->map_op_lists = NULL; return row; } @@ -1605,6 +1614,7 @@ ovsdb_idl_row_destroy(struct ovsdb_idl_row *row) if (row) { ovsdb_idl_row_clear_old(row); hmap_remove(&row->table->rows, &row->hmap_node); + ovsdb_idl_destroy_all_map_op_lists(row); if (ovsdb_idl_track_is_set(row->table)) { row->change_seqno[OVSDB_IDL_CHANGE_DELETE] = row->table->change_seqno[OVSDB_IDL_CHANGE_DELETE] @@ -1617,6 +1627,27 @@ ovsdb_idl_row_destroy(struct ovsdb_idl_row *row) } } +static void +ovsdb_idl_destroy_all_map_op_lists(struct ovsdb_idl_row *row) +{ + if (row->map_op_written) { + /* Clear Map Operation Lists */ + size_t idx, n_columns; + const struct ovsdb_idl_column *columns; + const struct ovsdb_type *type; + n_columns = row->table->class->n_columns; + columns = row->table->class->columns; + BITMAP_FOR_EACH_1 (idx, n_columns, row->map_op_written) { + type = &columns[idx].type; + map_op_list_destroy(row->map_op_lists[idx], type); + } + free(row->map_op_lists); + bitmap_free(row->map_op_written); + row->map_op_lists = NULL; + row->map_op_written = NULL; + } +} + static void ovsdb_idl_row_destroy_postprocess(struct ovsdb_idl *idl) { @@ -2144,6 +2175,7 @@ ovsdb_idl_txn_disassemble(struct ovsdb_idl_txn *txn) txn->idl->txn = NULL; HMAP_FOR_EACH_SAFE (row, next, txn_node, &txn->txn_rows) { + ovsdb_idl_destroy_all_map_op_lists(row); if (row->old) { if (row->written) { ovsdb_idl_row_unparse(row); @@ -2172,6 +2204,116 @@ ovsdb_idl_txn_disassemble(struct ovsdb_idl_txn *txn) hmap_init(&txn->txn_rows); } +static bool +ovsdb_idl_txn_extract_mutations(struct ovsdb_idl_row *row, + struct json *mutations) +{ + const struct ovsdb_idl_table_class *class = row->table->class; + size_t idx; + bool any_mutations = false; + + BITMAP_FOR_EACH_1(idx, class->n_columns, row->map_op_written) { + struct map_op_list *map_op_list; + const struct ovsdb_idl_column *column; + const struct ovsdb_datum *old_datum; + enum ovsdb_atomic_type key_type, value_type; + struct json *mutation, *map, *col_name, *mutator; + struct json *del_set, *ins_map; + bool any_del, any_ins; + + map_op_list = row->map_op_lists[idx]; + column = &class->columns[idx]; + key_type = column->type.key.type; + value_type = column->type.value.type; + old_datum = ovsdb_idl_read(row, column); + + del_set = json_array_create_empty(); + ins_map = json_array_create_empty(); + any_del = false; + any_ins = false; + + for (struct map_op *map_op = map_op_list_first(map_op_list); map_op; + map_op = map_op_list_next(map_op_list, map_op)) { + + if (map_op_type(map_op) == MAP_OP_UPDATE) { + /* Find out if value really changed. */ + struct ovsdb_datum *new_datum; + unsigned int pos; + new_datum = map_op_datum(map_op); + pos = ovsdb_datum_find_key(old_datum, + &new_datum->keys[0], + key_type); + if (ovsdb_atom_equals(&new_datum->values[0], + &old_datum->values[pos], + value_type)) { + /* No change in value. Move on to next update. */ + continue; + } + } else if (map_op_type(map_op) == MAP_OP_DELETE){ + /* Verify that there is a key to delete. */ + unsigned int pos; + pos = ovsdb_datum_find_key(old_datum, + &map_op_datum(map_op)->keys[0], + key_type); + if (pos == UINT_MAX) { + /* No key to delete. Move on to next update. */ + VLOG_WARN("Trying to delete a key that doesn't " + "exist in the map."); + continue; + } + } + + if (map_op_type(map_op) == MAP_OP_INSERT) { + map = json_array_create_2( + ovsdb_atom_to_json(&map_op_datum(map_op)->keys[0], + key_type), + ovsdb_atom_to_json(&map_op_datum(map_op)->values[0], + value_type)); + json_array_add(ins_map, map); + any_ins = true; + } else { /* MAP_OP_UPDATE or MAP_OP_DELETE */ + map = ovsdb_atom_to_json(&map_op_datum(map_op)->keys[0], + key_type); + json_array_add(del_set, map); + any_del = true; + } + + /* Generate an additional insert mutate for updates. */ + if (map_op_type(map_op) == MAP_OP_UPDATE) { + map = json_array_create_2( + ovsdb_atom_to_json(&map_op_datum(map_op)->keys[0], + key_type), + ovsdb_atom_to_json(&map_op_datum(map_op)->values[0], + value_type)); + json_array_add(ins_map, map); + any_ins = true; + } + } + + if (any_del) { + col_name = json_string_create(column->name); + mutator = json_string_create("delete"); + map = json_array_create_2(json_string_create("set"), del_set); + mutation = json_array_create_3(col_name, mutator, map); + json_array_add(mutations, mutation); + any_mutations = true; + } else { + json_destroy(del_set); + } + if (any_ins) { + col_name = json_string_create(column->name); + mutator = json_string_create("insert"); + map = json_array_create_2(json_string_create("map"), ins_map); + mutation = json_array_create_3(col_name, mutator, map); + json_array_add(mutations, mutation); + any_mutations = true; + } else { + json_destroy(ins_map); + } + } + return any_mutations; +} + /* Attempts to commit 'txn'. Returns the status of the commit operation, one * of the following TXN_* constants: * @@ -2358,6 +2500,28 @@ ovsdb_idl_txn_commit(struct ovsdb_idl_txn *txn) json_destroy(op); } } + + /* Add mutate operation, for partial map updates. */ + if (row->map_op_written) { + struct json *op, *mutations; + bool any_mutations; + + op = json_object_create(); + json_object_put_string(op, "op", "mutate"); + json_object_put_string(op, "table", class->name); + json_object_put(op, "where", where_uuid_equals(&row->uuid)); + mutations = json_array_create_empty(); + any_mutations = ovsdb_idl_txn_extract_mutations(row, mutations); + json_object_put(op, "mutations", mutations); + + if (any_mutations) { + op = substitute_uuids(op, txn); + json_array_add(operations, op); + any_updates = true; + } else { + json_destroy(op); + } + } } /* Add increment. */ @@ -3166,6 +3330,118 @@ ovsdb_idl_parse_lock_notify(struct ovsdb_idl *idl, } } +/* Inserts a new Map Operation into current transaction. */ +static void +ovsdb_idl_txn_add_map_op(struct ovsdb_idl_row *row, + const struct ovsdb_idl_column *column, + struct ovsdb_datum *datum, + enum map_op_type op_type) +{ + const struct ovsdb_idl_table_class *class; + size_t column_idx; + struct map_op *map_op; + + class = row->table->class; + column_idx = column - class->columns; + + /* Check if a map operation list exists for this column. */ + if (!row->map_op_written) { + row->map_op_written = bitmap_allocate(class->n_columns); + row->map_op_lists = xzalloc(class->n_columns * + sizeof *row->map_op_lists); + } + if (!row->map_op_lists[column_idx]) { + row->map_op_lists[column_idx] = map_op_list_create(); + } + + /* Add a map operation to the corresponding list. */ + map_op = map_op_create(datum, op_type); + bitmap_set1(row->map_op_written, column_idx); + map_op_list_add(row->map_op_lists[column_idx], map_op, &column->type); + + /* Add this row to transaction's list of rows. */ + if (hmap_node_is_null(&row->txn_node)) { + hmap_insert(&row->table->idl->txn->txn_rows, &row->txn_node, + uuid_hash(&row->uuid)); + } +} + +static bool +is_valid_partial_update(const struct ovsdb_idl_row *row, + const struct ovsdb_idl_column *column, + struct ovsdb_datum *datum) +{ + /* Verify that this column is being monitored. */ + unsigned int column_idx = column - row->table->class->columns; + if (!(row->table->modes[column_idx] & OVSDB_IDL_MONITOR)) { + VLOG_WARN("cannot partially update non-monitored column"); + return false; + } + + /* Verify that the update affects a single element. */ + if (datum->n != 1) { + VLOG_WARN("invalid datum for partial update"); + return false; + } + + return true; +} + +/* Inserts the key-value specified in 'datum' into the map in 'column' in + * 'row_'. If the key already exist in 'column', then it's value is updated + * with the value in 'datum'. The key-value in 'datum' must be of the same type + * as the keys-values in 'column'. This function takes ownership of 'datum'. + * + * Usually this function is used indirectly through one of the "update" + * functions generated by vswitch-idl. */ +void +ovsdb_idl_txn_write_partial_map(const struct ovsdb_idl_row *row_, + const struct ovsdb_idl_column *column, + struct ovsdb_datum *datum) +{ + struct ovsdb_idl_row *row = CONST_CAST(struct ovsdb_idl_row *, row_); + enum ovsdb_atomic_type key_type; + enum map_op_type op_type; + unsigned int pos; + const struct ovsdb_datum *old_datum; + + if (!is_valid_partial_update(row, column, datum)) { + ovsdb_datum_destroy(datum, &column->type); + return; + } + + /* Find out if this is an insert or an update. */ + key_type = column->type.key.type; + old_datum = ovsdb_idl_read(row, column); + pos = ovsdb_datum_find_key(old_datum, &datum->keys[0], key_type); + op_type = pos == UINT_MAX ? MAP_OP_INSERT : MAP_OP_UPDATE; + + ovsdb_idl_txn_add_map_op(row, column, datum, op_type); +} + +/* Deletes the key specified in 'datum' from the map in 'column' in 'row_'. + * The key in 'datum' must be of the same type as the keys in 'column'. + * The value in 'datum' must be NULL. This function takes ownership of + * 'datum'. + * + * Usually this function is used indirectly through one of the "update" + * functions generated by vswitch-idl. */ +void +ovsdb_idl_txn_delete_partial_map(const struct ovsdb_idl_row *row_, + const struct ovsdb_idl_column *column, + struct ovsdb_datum *datum) +{ + struct ovsdb_idl_row *row = CONST_CAST(struct ovsdb_idl_row *, row_); + + if (!is_valid_partial_update(row, column, datum)) { + struct ovsdb_type type_ = column->type; + type_.value.type = OVSDB_TYPE_VOID; + ovsdb_datum_destroy(datum, &type_); + return; + } + ovsdb_idl_txn_add_map_op(row, column, datum, MAP_OP_DELETE); +} + void ovsdb_idl_loop_destroy(struct ovsdb_idl_loop *loop) { diff --git a/lib/ovsdb-idl.h b/lib/ovsdb-idl.h index 238f5e65f34..5edffc4b617 100644 --- a/lib/ovsdb-idl.h +++ b/lib/ovsdb-idl.h @@ -265,6 +265,12 @@ void ovsdb_idl_txn_write(const struct ovsdb_idl_row *, void ovsdb_idl_txn_write_clone(const struct ovsdb_idl_row *, const struct ovsdb_idl_column *, const struct ovsdb_datum *); +void ovsdb_idl_txn_write_partial_map(const struct ovsdb_idl_row *, + const struct ovsdb_idl_column *, + struct ovsdb_datum *); +void ovsdb_idl_txn_delete_partial_map(const struct ovsdb_idl_row *, + const struct ovsdb_idl_column *, + struct ovsdb_datum *); void ovsdb_idl_txn_delete(const struct ovsdb_idl_row *); const struct ovsdb_idl_row *ovsdb_idl_txn_insert( struct ovsdb_idl_txn *, const struct ovsdb_idl_table_class *, diff --git a/lib/ovsdb-map-op.c b/lib/ovsdb-map-op.c new file mode 100644 index 00000000000..58f43dcda52 --- /dev/null +++ b/lib/ovsdb-map-op.c @@ -0,0 +1,171 @@ +/* Copyright (C) 2016 Hewlett Packard Enterprise Development LP + * All Rights Reserved. + * + * 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 +#include "ovsdb-map-op.h" +#include "util.h" +#include "hmap.h" +#include "hash.h" + +/* Map Operation: a Partial Map Update */ +struct map_op { + struct hmap_node node; + struct ovsdb_datum *datum; + enum map_op_type type; +}; + +/* List of Map Operations */ +struct map_op_list { + struct hmap hmap; +}; + +static void map_op_destroy_datum(struct map_op *, const struct ovsdb_type *); +static struct map_op *map_op_list_find(struct map_op_list *, struct map_op *, + const struct ovsdb_type *, size_t); + +struct map_op* +map_op_create(struct ovsdb_datum *datum, enum map_op_type type) +{ + struct map_op *map_op = xmalloc(sizeof *map_op); + map_op->node.hash = 0; + map_op->node.next = HMAP_NODE_NULL; + map_op->datum = datum; + map_op->type = type; + return map_op; +} + +static void +map_op_destroy_datum(struct map_op *map_op, const struct ovsdb_type *type) +{ + if (map_op->type == MAP_OP_DELETE){ + struct ovsdb_type type_ = *type; + type_.value.type = OVSDB_TYPE_VOID; + ovsdb_datum_destroy(map_op->datum, &type_); + } else { + ovsdb_datum_destroy(map_op->datum, type); + } + map_op->datum = NULL; +} + +void +map_op_destroy(struct map_op *map_op, const struct ovsdb_type *type) +{ + map_op_destroy_datum(map_op, type); + free(map_op); +} + +struct ovsdb_datum* +map_op_datum(const struct map_op *map_op) +{ + return map_op->datum; +} + +enum map_op_type +map_op_type(const struct map_op *map_op) +{ + return map_op->type; +} + +struct map_op_list* +map_op_list_create(void) +{ + struct map_op_list *list = xmalloc(sizeof *list); + hmap_init(&list->hmap); + return list; +} + +void +map_op_list_destroy(struct map_op_list *list, const struct ovsdb_type *type) +{ + struct map_op *map_op, *next; + HMAP_FOR_EACH_SAFE (map_op, next, node, &list->hmap) { + map_op_destroy(map_op, type); + } + hmap_destroy(&list->hmap); + free(list); +} + +static struct map_op* +map_op_list_find(struct map_op_list *list, struct map_op *map_op, + const struct ovsdb_type *type, size_t hash) +{ + struct map_op *found = NULL; + struct map_op *old; + HMAP_FOR_EACH_WITH_HASH(old, node, hash, &list->hmap) { + if (ovsdb_atom_equals(&old->datum->keys[0], &map_op->datum->keys[0], + type->key.type)) { + found = old; + break; + } + } + return found; +} + +/* Inserts 'map_op' into 'list'. Makes sure that any conflict with a previous + * map operation is resolved, so only one map operation is possible on each key + * per transactions. 'type' must be the type of the column over which the map + * operation will be applied. */ +void +map_op_list_add(struct map_op_list *list, struct map_op *map_op, + const struct ovsdb_type *type) +{ + /* Check if there is a previous update with the same key. */ + size_t hash; + struct map_op *prev_map_op; + + hash = ovsdb_atom_hash(&map_op->datum->keys[0], type->key.type, 0); + prev_map_op = map_op_list_find(list, map_op, type, hash); + if (prev_map_op == NULL){ + hmap_insert(&list->hmap, &map_op->node, hash); + } else { + if (prev_map_op->type == MAP_OP_INSERT && + map_op->type == MAP_OP_DELETE) { + /* These operations cancel each other out. */ + hmap_remove(&list->hmap, &prev_map_op->node); + map_op_destroy(prev_map_op, type); + map_op_destroy(map_op, type); + } else { + /* For any other case, the new update operation replaces + * the previous update operation. */ + map_op_destroy_datum(prev_map_op, type); + prev_map_op->type = map_op->type; + prev_map_op->datum = map_op->datum; + free(map_op); + } + } +} + +struct map_op* +map_op_list_first(struct map_op_list *list) +{ + struct hmap_node *node = hmap_first(&list->hmap); + if (node == NULL) { + return NULL; + } + struct map_op *map_op = CONTAINER_OF(node, struct map_op, node); + return map_op; +} + +struct map_op* +map_op_list_next(struct map_op_list *list, struct map_op *map_op) +{ + struct hmap_node *node = hmap_next(&list->hmap, &map_op->node); + if (node == NULL) { + return NULL; + } + struct map_op *next = CONTAINER_OF(node, struct map_op, node); + return next; +} diff --git a/lib/ovsdb-map-op.h b/lib/ovsdb-map-op.h new file mode 100644 index 00000000000..140b0f3889f --- /dev/null +++ b/lib/ovsdb-map-op.h @@ -0,0 +1,45 @@ +/* Copyright (C) 2016 Hewlett Packard Enterprise Development LP + * All Rights Reserved. + * + * 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. + */ + +#ifndef OVSDB_MAP_OP_H +#define OVSDB_MAP_OP_H 1 + +#include "ovsdb-data.h" + +enum map_op_type { + MAP_OP_UPDATE, + MAP_OP_INSERT, + MAP_OP_DELETE +}; + +struct map_op; /* Map Operation: a Partial Map Update */ +struct map_op_list; /* List of Map Operations */ + +/* Map Operation functions */ +struct map_op *map_op_create(struct ovsdb_datum *, enum map_op_type); +void map_op_destroy(struct map_op *, const struct ovsdb_type *); +struct ovsdb_datum *map_op_datum(const struct map_op*); +enum map_op_type map_op_type(const struct map_op*); + +/* Map Operation List functions */ +struct map_op_list *map_op_list_create(void); +void map_op_list_destroy(struct map_op_list *, const struct ovsdb_type *); +void map_op_list_add(struct map_op_list *, struct map_op *, + const struct ovsdb_type *); +struct map_op *map_op_list_first(struct map_op_list *); +struct map_op *map_op_list_next(struct map_op_list *, struct map_op *); + +#endif /* ovsdb-map-op.h */