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

Implement database schema versioning.

As the database schema evolves, it might be useful to have an identifier
for the particular version in use.  This commit adds that feature.
This commit is contained in:
Ben Pfaff 2010-12-27 14:26:47 -08:00
parent 538c6dfab4
commit 8159b984dc
17 changed files with 255 additions and 18 deletions

View File

@ -33,6 +33,11 @@ values. Additional notation is presented later.
<id>s that begin with _ are reserved to the implementation and may
not be used by the user.
<version>
A JSON string that contains a version number that matches
[0-9]+\.[0-9]+\.[0-9]+
<boolean>
A JSON true or false value.
@ -102,6 +107,7 @@ is represented by <database-schema>, as described below.
A JSON object with the following members:
"name": <id> required
"version": <version> required
"tables": {<id>: <table-schema>, ...} required
The "name" identifies the database as a whole. It must be
@ -109,6 +115,12 @@ is represented by <database-schema>, as described below.
operated on. The value of "tables" is a JSON object whose names
are table names and whose values are <table-schema>s.
The "version" reports the version of the database schema. Because
this is a recent addition to the schema format, OVSDB permits it
to be omitted, but future versions of OVSDB will require it to be
present. Open vSwitch semantics for "version" are described in
ovs-vswitchd.conf.db(5).
<table-schema>
A JSON object with the following members:

View File

@ -19,6 +19,8 @@ ovsdb\-client \- command-line interface to \fBovsdb-server\fR(1)
.br
\fBovsdb\-client \fR[\fIoptions\fR] \fBget\-schema\fI server database\fR
.br
\fBovsdb\-client \fR[\fIoptions\fR] \fBget\-schema\-version\fI server database\fR
.br
\fBovsdb\-client \fR[\fIoptions\fR] \fBlist\-tables\fI server database\fR
.br
\fBovsdb\-client \fR[\fIoptions\fR] \fBlist\-columns\fI server database \fR[\fItable\fR]
@ -61,6 +63,19 @@ be used for \fIdatabase\fR in the following commands.
Connects to \fIserver\fR, retrieves the schema for \fIdatabase\fR, and
prints it in JSON format.
.
.IP "\fBget\-schema\-version\fI server database\fR"
Connects to \fIserver\fR, retrieves the schema for \fIdatabase\fR, and
prints its version number on stdout. A schema version number has the form
\fIx\fB.\fIy\fB.\fIz\fR. See \fBovs\-vswitchd.conf.db\fR(5) for
details.
.IP
Schema version numbers and Open vSwitch version numbers are
independent.
.IP
If \fIdatabase\fR was created before schema versioning was introduced,
then it will not have a version number and this command will print a
blank line.
.
.IP "\fBlist\-tables\fI server database\fR"
Connects to \fIserver\fR, retrieves the schema for \fIdatabase\fR, and
prints a table listing the name of each table

View File

@ -182,6 +182,9 @@ usage(void)
" list databases available on SERVER\n"
"\n get-schema SERVER DATABASE\n"
" retrieve schema for DATABASE from SERVER\n"
"\n get-schema-version SERVER DATABASE\n"
" retrieve schema for DATABASE from SERVER and report only its\n"
" version number on stdout\n"
"\n list-tables SERVER DATABASE\n"
" list tables for DATABASE on SERVER\n"
"\n list-columns SERVER DATABASE [TABLE]\n"
@ -781,6 +784,14 @@ do_get_schema(int argc OVS_UNUSED, char *argv[])
ovsdb_schema_destroy(schema);
}
static void
do_get_schema_version(int argc OVS_UNUSED, char *argv[])
{
struct ovsdb_schema *schema = fetch_schema(argv[1], argv[2]);
puts(schema->version);
ovsdb_schema_destroy(schema);
}
static void
do_list_tables(int argc OVS_UNUSED, char *argv[])
{
@ -1344,6 +1355,7 @@ do_help(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
static const struct command all_commands[] = {
{ "list-dbs", 1, 1, do_list_dbs },
{ "get-schema", 2, 2, do_get_schema },
{ "get-schema-version", 2, 2, do_get_schema_version },
{ "list-tables", 2, 2, do_list_tables },
{ "list-columns", 2, 3, do_list_columns },
{ "transact", 2, 2, do_transact },

View File

@ -14,6 +14,15 @@ ovsdb\-tool \- Open vSwitch database management utility
.SH SYNOPSIS
\fBovsdb\-tool \fR[\fIoptions\fR] \fBcreate\fI db schema\fR
.br
\fBovsdb\-tool \fR[\fIoptions\fR] \fBcompact \fIdb \fR[\fItarget\fR]
.br
\fBovsdb\-tool \fR[\fIoptions\fR] \fBconvert\fI db schema
\fR[\fItarget\fR]
.br
\fBovsdb\-tool \fR[\fIoptions\fR] \fBdb\-version\fI db\fR
.br
\fBovsdb\-tool \fR[\fIoptions\fR] \fBschema\-version\fI schema\fR
.br
\fBovsdb\-tool \fR[\fIoptions\fR] \fBquery\fI db transaction\fR
.br
\fBovsdb\-tool \fR[\fIoptions\fR] \fBtransact\fI db transaction\fR
@ -63,6 +72,26 @@ ignored. Columns that exist in \fIschema\fR but not in \fIdb\fR are
set to their default values. All of \fIschema\fR's constraints apply
in full.
.
.IP "\fBdb\-version\fI db\fR"
Reads \fIdb\fR and prints the version number of the schema embedded
within the database on stdout. A schema version number has the form
\fIx\fB.\fIy\fB.\fIz\fR. See \fBovs\-vswitchd.conf.db\fR(5) for
details.
.IP
Schema version numbers and Open vSwitch version numbers are
independent.
.IP
If \fIdb\fR was created before schema versioning was introduced, then
it will not have a version number and this command will print a blank
line.
.
.IP "\fBschema\-version\fI schema\fR"
Reads \fIschema\fR and prints the schema's version number on stdout.
.IP
If \fIschema\fR was created before versioning was introduced, then it
does not have a version number and this command will print a blank
line.
.
.IP "\fBquery\fI db transaction\fR"
Opens \fIdb\fR, executes \fItransaction\fR on it, and prints the
results. The \fItransaction\fR must be a JSON array in the format of

View File

@ -110,6 +110,8 @@ usage(void)
" create DB SCHEMA create DB with the given SCHEMA\n"
" compact DB [DST] compact DB in-place (or to DST)\n"
" convert DB SCHEMA [DST] convert DB to SCHEMA (to DST)\n"
" db-version DB report version of schema used by DB\n"
" schema-version SCHEMA report SCHEMA's schema version\n"
" query DB TRNS execute read-only transaction on DB\n"
" transact DB TRNS execute read/write transaction on DB\n"
" show-log DB prints information about DB's log entries\n",
@ -239,6 +241,28 @@ do_convert(int argc OVS_UNUSED, char *argv[])
ovsdb_schema_destroy(new_schema);
}
static void
do_db_version(int argc OVS_UNUSED, char *argv[])
{
const char *db_file_name = argv[1];
struct ovsdb *db;
check_ovsdb_error(ovsdb_file_open(db_file_name, true, &db, NULL));
puts(db->schema->version);
ovsdb_destroy(db);
}
static void
do_schema_version(int argc OVS_UNUSED, char *argv[])
{
const char *schema_file_name = argv[1];
struct ovsdb_schema *schema;
check_ovsdb_error(ovsdb_schema_from_file(schema_file_name, &schema));
puts(schema->version);
ovsdb_schema_destroy(schema);
}
static void
transact(bool read_only, const char *db_file_name, const char *transaction)
{
@ -410,6 +434,8 @@ static const struct command all_commands[] = {
{ "create", 2, 2, do_create },
{ "compact", 1, 2, do_compact },
{ "convert", 2, 3, do_convert },
{ "db-version", 1, 1, do_db_version },
{ "schema-version", 1, 1, do_schema_version },
{ "query", 2, 2, do_query },
{ "transact", 2, 2, do_transact },
{ "show-log", 1, 1, do_show_log },

View File

@ -26,12 +26,13 @@
#include "transaction.h"
struct ovsdb_schema *
ovsdb_schema_create(const char *name)
ovsdb_schema_create(const char *name, const char *version)
{
struct ovsdb_schema *schema;
schema = xzalloc(sizeof *schema);
schema->name = xstrdup(name);
schema->version = xstrdup(version);
shash_init(&schema->tables);
return schema;
@ -43,7 +44,7 @@ ovsdb_schema_clone(const struct ovsdb_schema *old)
struct ovsdb_schema *new;
struct shash_node *node;
new = ovsdb_schema_create(old->name);
new = ovsdb_schema_create(old->name, old->version);
SHASH_FOR_EACH (node, &old->tables) {
const struct ovsdb_table_schema *ts = node->data;
@ -52,7 +53,6 @@ ovsdb_schema_clone(const struct ovsdb_schema *old)
return new;
}
void
ovsdb_schema_destroy(struct ovsdb_schema *schema)
{
@ -67,6 +67,7 @@ ovsdb_schema_destroy(struct ovsdb_schema *schema)
}
shash_destroy(&schema->tables);
free(schema->name);
free(schema->version);
free(schema);
}
@ -116,26 +117,49 @@ ovsdb_schema_check_ref_table(const struct ovsdb_column *column,
}
}
static bool
is_valid_version(const char *s)
{
int n = -1;
sscanf(s, "%*[0-9].%*[0-9].%*[0-9]%n", &n);
return n != -1 && s[n] == '\0';
}
struct ovsdb_error *
ovsdb_schema_from_json(struct json *json, struct ovsdb_schema **schemap)
{
struct ovsdb_schema *schema;
const struct json *name, *tables;
const struct json *name, *tables, *version_json;
struct ovsdb_error *error;
struct shash_node *node;
struct ovsdb_parser parser;
const char *version;
*schemap = NULL;
ovsdb_parser_init(&parser, json, "database schema");
name = ovsdb_parser_member(&parser, "name", OP_ID);
version_json = ovsdb_parser_member(&parser, "version",
OP_STRING | OP_OPTIONAL);
ovsdb_parser_member(&parser, "cksum", OP_STRING | OP_OPTIONAL);
tables = ovsdb_parser_member(&parser, "tables", OP_OBJECT);
error = ovsdb_parser_finish(&parser);
if (error) {
return error;
}
schema = ovsdb_schema_create(json_string(name));
if (version_json) {
version = json_string(version_json);
if (!is_valid_version(version)) {
return ovsdb_syntax_error(json, NULL, "schema version \"%s\" not "
"in format x.y.z", version);
}
} else {
/* Backward compatibility with old databases. */
version = "";
}
schema = ovsdb_schema_create(json_string(name), version);
SHASH_FOR_EACH (node, json_object(tables)) {
struct ovsdb_table_schema *table;
@ -190,6 +214,9 @@ ovsdb_schema_to_json(const struct ovsdb_schema *schema)
json = json_object_create();
json_object_put_string(json, "name", schema->name);
if (schema->version[0]) {
json_object_put_string(json, "version", schema->version);
}
tables = json_object_create();

View File

@ -29,10 +29,12 @@ struct uuid;
/* Database schema. */
struct ovsdb_schema {
char *name;
char *version;
struct shash tables; /* Contains "struct ovsdb_table_schema *"s. */
};
struct ovsdb_schema *ovsdb_schema_create(const char *name);
struct ovsdb_schema *ovsdb_schema_create(const char *name,
const char *version);
struct ovsdb_schema *ovsdb_schema_clone(const struct ovsdb_schema *);
void ovsdb_schema_destroy(struct ovsdb_schema *);

View File

@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import re
import sys
from ovs.db import error
@ -21,8 +22,9 @@ from ovs.db import types
class DbSchema(object):
"""Schema for an OVSDB database."""
def __init__(self, name, tables):
def __init__(self, name, version, tables):
self.name = name
self.version = version
self.tables = tables
# Validate that all ref_tables refer to the names of tables
@ -36,9 +38,16 @@ class DbSchema(object):
def from_json(json):
parser = ovs.db.parser.Parser(json, "database schema")
name = parser.get("name", ['id'])
version = parser.get_optional("version", [unicode])
parser.get_optional("cksum", [unicode])
tablesJson = parser.get("tables", [dict])
parser.finish()
if (version is not None and
not re.match('[0-9]+\.[0-9]+\.[0-9]+$', version)):
raise error.Error("schema version \"%s\" not in format x.y.z"
% version)
tables = {}
for tableName, tableJson in tablesJson.iteritems():
if tableName.startswith('_'):
@ -48,13 +57,16 @@ class DbSchema(object):
raise error.Error("name must be a valid id", json)
tables[tableName] = TableSchema.from_json(tableJson, tableName)
return DbSchema(name, tables)
return DbSchema(name, version, tables)
def to_json(self):
tables = {}
for table in self.tables.itervalues():
tables[table.name] = table.to_json()
return {"name": self.name, "tables": tables}
json = {"name": self.name, "tables": tables}
if self.version:
json["version"] = self.version
return json
def __check_ref_table(self, column, base, base_name):
if (base and base.type == types.UuidType and base.ref_table and
@ -64,8 +76,8 @@ class DbSchema(object):
tag="syntax error")
class IdlSchema(DbSchema):
def __init__(self, name, tables, idlPrefix, idlHeader):
DbSchema.__init__(self, name, tables)
def __init__(self, name, version, tables, idlPrefix, idlHeader):
DbSchema.__init__(self, name, version, tables)
self.idlPrefix = idlPrefix
self.idlHeader = idlHeader
@ -80,7 +92,8 @@ class IdlSchema(DbSchema):
del subjson["idlHeader"]
schema = DbSchema.from_json(subjson)
return IdlSchema(schema.name, schema.tables, idlPrefix, idlHeader)
return IdlSchema(schema.name, schema.version, schema.tables,
idlPrefix, idlHeader)
class TableSchema(object):
def __init__(self, name, columns, mutable=True, max_rows=sys.maxint):

View File

@ -1,5 +1,6 @@
{
"name": "idltest",
"name": "idltest",
"version": "1.2.3",
"tables": {
"link1": {
"columns": {

View File

@ -6,7 +6,8 @@ m4_define([ORDINAL_SCHEMA],
"ordinals": {
"columns": {
"number": {"type": "integer"},
"name": {"type": "string"}}}}}]])
"name": {"type": "string"}}}},
"version": "5.1.3"}]])
m4_define([CONSTRAINT_SCHEMA],
[[{"name": "constraints",

View File

@ -3,6 +3,7 @@ AT_BANNER([OVSDB -- schemas])
OVSDB_CHECK_POSITIVE_CPY([schema with valid refTables],
[[parse-schema \
'{"name": "mydb",
"version": "4.2.1",
"tables": {
"a": {
"columns": {
@ -21,7 +22,19 @@ OVSDB_CHECK_POSITIVE_CPY([schema with valid refTables],
"key": {
"type": "uuid",
"refTable": "a"}}}}}}}']],
[[{"name":"mydb","tables":{"a":{"columns":{"map":{"type":{"key":{"refTable":"b","type":"uuid"},"value":{"refTable":"a","type":"uuid"}}}}},"b":{"columns":{"aRef":{"type":{"key":{"refTable":"a","type":"uuid"}}}}}}}]])
[[{"name":"mydb","tables":{"a":{"columns":{"map":{"type":{"key":{"refTable":"b","type":"uuid"},"value":{"refTable":"a","type":"uuid"}}}}},"b":{"columns":{"aRef":{"type":{"key":{"refTable":"a","type":"uuid"}}}}}},"version":"4.2.1"}]])
dnl Schemas without version numbers are accepted for backward
dnl compatibility, but this is a deprecated feature.
OVSDB_CHECK_POSITIVE_CPY([schema without version number],
[[parse-schema \
'{"name": "mydb",
"tables": {
"x": {
"columns": {
"y": {
"type": "integer"}}}}}']],
[{"name":"mydb","tables":{"x":{"columns":{"y":{"type":"integer"}}}}}])
OVSDB_CHECK_NEGATIVE_CPY([schema with invalid refTables],
[[parse-schema \
@ -45,3 +58,14 @@ OVSDB_CHECK_NEGATIVE_CPY([schema with invalid refTables],
"type": "uuid",
"refTable": "a"}}}}}}}']],
[[syntax error: column map key refers to undefined table c]])
OVSDB_CHECK_NEGATIVE_CPY([schema with invalid version number],
[[parse-schema \
'{"name": "mydb",
"tables": {
"x": {
"columns": {
"y": {
"type": "integer"}}}},
"version": "xxx"}']],
[[schema version "xxx" not in format x.y.z]])

View File

@ -38,6 +38,17 @@ cat stdout >> output
EXECUTION_EXAMPLES
AT_SETUP([ovsdb-client get-schema-version])
AT_KEYWORDS([ovsdb server positive])
AT_DATA([schema], [ORDINAL_SCHEMA
])
AT_CHECK([ovsdb-tool create db schema], [0], [ignore], [ignore])
AT_CHECK([ovsdb-server --detach --pidfile=$PWD/pid --unixctl=$PWD/unixctl --remote=punix:socket db], [0], [ignore], [ignore])
AT_CHECK([ovsdb-client get-schema-version unix:socket ordinals], [0], [5.1.3
])
OVSDB_SERVER_SHUTDOWN
AT_CLEANUP
AT_SETUP([database multiplexing implementation])
AT_KEYWORDS([ovsdb server positive])
AT_DATA([schema], [ORDINAL_SCHEMA
@ -181,7 +192,7 @@ AT_CHECK(
[0], [stdout], [ignore], [test ! -e pid || kill `cat pid`])
dnl Check that all the crap is in fact in the database log.
AT_CHECK([[perl $srcdir/uuidfilt.pl db | grep -v ^OVSDB | sed 's/"_date":[0-9]*/"_date":0/' | test-json --multiple -]], [0],
[[{"name":"ordinals","tables":{"ordinals":{"columns":{"name":{"type":"string"},"number":{"type":"integer"}}}}}
[[{"name":"ordinals","tables":{"ordinals":{"columns":{"name":{"type":"string"},"number":{"type":"integer"}}}},"version":"5.1.3"}
{"_comment":"add row for zero 0","_date":0,"ordinals":{"<0>":{"name":"zero"}}}
{"_comment":"delete row for 0","_date":0,"ordinals":{"<0>":null}}
{"_comment":"add back row for zero 0","_date":0,"ordinals":{"<1>":{"name":"zero"}}}

View File

@ -83,7 +83,7 @@ AT_CHECK(
[0], [stdout], [ignore])
dnl Check that all the crap is in fact in the database log.
AT_CHECK([[perl $srcdir/uuidfilt.pl db | grep -v ^OVSDB | sed 's/"_date":[0-9]*/"_date":0/' | test-json --multiple -]], [0],
[[{"name":"ordinals","tables":{"ordinals":{"columns":{"name":{"type":"string"},"number":{"type":"integer"}}}}}
[[{"name":"ordinals","tables":{"ordinals":{"columns":{"name":{"type":"string"},"number":{"type":"integer"}}}},"version":"5.1.3"}
{"_comment":"add row for zero 0","_date":0,"ordinals":{"<0>":{"name":"zero"}}}
{"_comment":"delete row for 0","_date":0,"ordinals":{"<0>":null}}
{"_comment":"add back row for zero 0","_date":0,"ordinals":{"<1>":{"name":"zero"}}}
@ -270,3 +270,21 @@ _uuid name number
<5> "" 5 @&t@
])
AT_CLEANUP
AT_SETUP([ovsdb-tool schema-version])
AT_KEYWORDS([ovsdb file positive])
AT_DATA([schema], [ORDINAL_SCHEMA
])
AT_CHECK([ovsdb-tool schema-version schema], [0], [5.1.3
])
AT_CLEANUP
AT_SETUP([ovsdb-tool db-version])
AT_KEYWORDS([ovsdb file positive])
AT_DATA([schema], [ORDINAL_SCHEMA
])
touch .db.~lock~
AT_CHECK([ovsdb-tool create db schema], [0], [], [ignore])
AT_CHECK([ovsdb-tool db-version db], [0], [5.1.3
])
AT_CLEANUP

View File

@ -92,3 +92,15 @@ vswitchd/ovs-vswitchd.conf.db.5: \
$(srcdir)/vswitchd/vswitch.ovsschema \
$(srcdir)/vswitchd/vswitch.xml > $@.tmp
mv $@.tmp $@
# Version checking for vswitch.ovsschema.
ALL_LOCAL += vswitchd/vswitch.ovsschema.stamp
vswitchd/vswitch.ovsschema.stamp: vswitchd/vswitch.ovsschema
@sum=`sed '/cksum/d' $? | cksum`; \
expected=`sed -n 's/.*"cksum": "\(.*\)".*/\1/p' $?`; \
if test "X$$sum" = "X$$expected"; then \
touch $@; \
else \
ln=`sed -n '/"cksum":/=' $?`; \
echo "$?:$$ln: checksum \"$$sum\" does not match (you should probably update the version number and fix the checksum)"; \
fi

View File

@ -1,4 +1,6 @@
{"name": "Open_vSwitch",
"version": "1.0.0",
"cksum": "514853437 13985",
"tables": {
"Open_vSwitch": {
"columns": {
@ -34,6 +36,9 @@
"ovs_version": {
"type": {"key": {"type": "string"},
"min": 0, "max": 1}},
"db_version": {
"type": {"key": {"type": "string"},
"min": 0, "max": 1}},
"system_type": {
"type": {"key": {"type": "string"},
"min": 0, "max": 1}},

View File

@ -210,6 +210,26 @@
also included, e.g. <code>1.1.0pre2+build4948</code>.
</column>
<column name="db_version">
<p>
The database schema version number in the form
<code><var>major</var>.<var>minor</var>.<var>tweak</var></code>,
e.g. <code>1.2.3</code>. Whenever the database schema is changed in
a non-backward compatible way (e.g. deleting a column or a table),
<var>major</var> is incremented. When the database schema is changed
in a backward compatible way (e.g. adding a new column),
<var>minor</var> is incremented. When the database schema is changed
cosmetically (e.g. reindenting its syntax), <var>tweak</var> is
incremented.
</p>
<p>
The schema version is part of the database schema, so it can also be
retrieved by fetching the schema using the Open vSwitch database
protocol.
</p>
</column>
<column name="system_type">
<p>
An identifier for the type of system on top of which Open vSwitch

View File

@ -325,6 +325,7 @@ function start {
# Allow GRE traffic.
iptables -I INPUT -p gre -j ACCEPT
schemaver=`$ovsdb_tool schema-version "$VSWITCHD_OVSDB_SCHEMA"`
if [ ! -e "$OVSDB_SERVER_DB" ]; then
warning "$OVSDB_SERVER_DB does not exist"
install -d -m 755 -o root -g root `dirname $OVSDB_SERVER_DB`
@ -332,12 +333,20 @@ function start {
action "Creating empty database $OVSDB_SERVER_DB" true
$ovsdb_tool -vANY:console:emer create "$OVSDB_SERVER_DB" "$VSWITCHD_OVSDB_SCHEMA"
else
# If schema version changed, then back up the old version.
oldver=`$ovsdb_tool db-version "$OVSDB_SERVER_DB"`
if test "X$oldver" != "X$schemaver"; then
backup=$OVSDB_SERVER_DB.backup$oldver
action "Backing up $OVSDB_SERVER_DB in $backup before converting from schema version \"$oldver\" to \"$schemaver\"" true
cp "$OVSDB_SERVER_DB" "$backup"
fi
# Upgrade or downgrade schema and compact database.
$ovsdb_tool -vANY:console:emer convert "$OVSDB_SERVER_DB" "$VSWITCHD_OVSDB_SCHEMA"
fi
start_ovsdb_server
$vsctl --no-wait --timeout=5 init
$vsctl --no-wait --timeout=5 init -- set Open_vSwitch . db-version="$schemaver"
if [ ! -e /var/run/openvswitch.booted ]; then
touch /var/run/openvswitch.booted
for bridge in $($vsctl list-br); do