RFC 7047

The Open vSwitch Database Management Protocol

πŸ“… published December 2013, from ietf

The Open vSwitch Database Management Protocol πŸŽ€

Okay bestie, here's the tea on RFC 7047 πŸ’… Its Request for Comments number is 7047, and it was written by B. Pfaff and B. Davie (Ed.) at VMware, Inc. It's an Independent Submission, and its category is Informational. The ISSN is 2070-1721, and it was published in December 2013. The title block doesn't list anything under obsoletes or updates, so this RFC doesn't claim to replace or change any other document 🫢

Abstract ✨

Open vSwitch is an open-source software switch built to work as a vswitch (virtual switch) in virtualized server environments. A vswitch moves traffic between virtual machines (VMs) on the same physical host, and it also moves traffic between VMs and the physical network. You can extend and control Open vSwitch programmatically with OpenFlow and with the OVSDB (Open vSwitch Database) management protocol. This document defines that OVSDB management protocol πŸ’– The Open vSwitch project also ships open-source OVSDB client and server implementations.

Status of This Memo πŸ“œ

This document is not an Internet Standards Track specification. It's published for informational purposes, so no stress 🌸

This is a contribution to the RFC Series, and it's independent of any other RFC stream. The RFC Editor chose to publish it at their discretion, and they make no statement about how valuable it is for implementation or deployment. Documents the RFC Editor approves for publication are not candidates for any level of Internet Standard. See Section 2 of RFC 5741 for the details.

Information about this document's current status, any errata, and how to send feedback on it can be found at http://www.rfc-editor.org/info/rfc7047 πŸ”

Copyright (c) 2013 IETF Trust and the persons identified as the document authors. All rights reserved.

This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (http://trustee.ietf.org/license-info) as they stand on the date this document was published. Please read these documents carefully, because they explain your rights and restrictions with respect to this document πŸ™ˆ

Table of Contents 🧚

   1. Introduction ....................................................3
      1.1. Requirements Language ......................................3
      1.2. Terminology ................................................3
   2. System Overview .................................................4
   3. OVSDB Structure .................................................5
      3.1. JSON Usage .................................................6
      3.2. Schema Format ..............................................7
   4. Wire Protocol ..................................................12
      4.1. RPC Methods ...............................................12
           4.1.1. List Databases .....................................12
           4.1.2. Get Schema .........................................13
           4.1.3. Transact ...........................................13
           4.1.4. Cancel .............................................16
           4.1.5. Monitor ............................................16
           4.1.6. Update Notification ................................18
           4.1.7. Monitor Cancellation ...............................19
           4.1.8. Lock Operations ....................................19
           4.1.9. Locked Notification ................................21
           4.1.10. Stolen Notification ...............................21
           4.1.11. Echo ..............................................22
   5. Database Operations ............................................22
      5.1. Notation ..................................................22
      5.2. Operations ................................................27
           5.2.1. Insert .............................................27
           5.2.2. Select .............................................28
           5.2.3. Update .............................................29
           5.2.4. Mutate .............................................29
           5.2.5. Delete .............................................30
           5.2.6. Wait ...............................................31
           5.2.7. Commit .............................................32
           5.2.8. Abort ..............................................32
           5.2.9. Comment ............................................32
           5.2.10. Assert ............................................33
   6. IANA Considerations ............................................33
   7. Security Considerations ........................................33
   8. Acknowledgements ...............................................34
   9. References .....................................................34
      9.1. Normative References ......................................34
      9.2. Informative References ....................................34

1. Introduction πŸ¦‹

So in virtualized server environments, you basically always need a vswitch (virtual switch) to forward traffic between VMs on the same physical host, and between VMs and the physical network. Open vSwitch [OVS] is an open-source software switch made for exactly that job. It's open to programmatic extension and control through OpenFlow [OF-SPEC] and the OVSDB (Open vSwitch Database) management protocol. This document defines the OVSDB management protocol, and the Open vSwitch project includes open-source OVSDB client and server implementations 🌈

The OVSDB management protocol uses JSON [RFC4627] as its wire format, and it's built on JSON-RPC version 1.0 [JSON-RPC] ✨

The schema of the Open vSwitch database is documented in [DB-SCHEMA]. This document explains how to talk to that database to manage and configure Open vSwitch instances. The protocol here also lets you discover which schema is in use, as covered in Section 4.1.2 πŸ”

The OVSDB management protocol is meant to give programmatic access to the Open vSwitch database as documented in [DB-SCHEMA]. That database holds the configuration for one Open vSwitch daemon. As it stands, this information describes how a virtual switch behaves. It does not describe the behavior or configuration of a routing system. If the schema ever gets extended to cover parts of a routing system, implementers and operators need to know about the IETF's I2RS working group. That group is working on protocols and data models for real-time or event-driven interaction with the routing system πŸ‘‘

1.1. Requirements Language πŸ’œ

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC2119].

1.2. Terminology πŸ“

   UUID:       Universally Unique Identifier.  A 128-bit identifier that
               is unique in space and time [DCE].

   OVS:        Open vSwitch.  An open-source virtual switch.

   OVSDB:      The database that is used for the purpose of configuring
               OVS instances.

   JSON:       Javascript Object Notation [RFC4627].

   JSON-RPC:   JSON Remote Procedure Call [JSON-RPC].

   Durable:    Reliably written to non-volatile storage (e.g., disk).
               OVSDB supports the option to specify whether or not
               transactions are durable.

The JSON specification [RFC4627] gives precise definitions for a bunch of important terms, like JSON values, objects, arrays, numbers, and strings. This document uses the definitions from [RFC4627] in every case 🫢

2. System Overview 🌸

Figure 1 shows the main parts of Open vSwitch and how they connect to a control and management cluster. An OVS instance has a database server (ovsdb-server), a vswitch daemon (ovs-vswitchd), and, optionally, a module that does fast-path forwarding. The "management and control cluster" is made up of some number of managers and controllers. Managers use the OVSDB management protocol to manage OVS instances, and every OVS instance is managed by at least one manager. Controllers use OpenFlow to install flow state in OpenFlow switches. One OVS instance can run multiple logical datapaths, which are called "bridges". Each OpenFlow bridge has at least one controller 🧚

The OVSDB management interface handles management and configuration operations on the OVS instance. Compared to OpenFlow, OVSDB management operations happen on a relatively long timescale. Here are some examples of what OVSDB supports:

  • Creation, modification, and deletion of OpenFlow datapaths (bridges), of which there may be many in a single OVS instance;
  • Configuration of the set of controllers to which an OpenFlow datapath should connect;
  • Configuration of the set of managers to which the OVSDB server should connect;
  • Creation, modification, and deletion of ports on OpenFlow datapaths;
  • Creation, modification, and deletion of tunnel interfaces on OpenFlow datapaths;
  • Creation, modification, and deletion of queues;
  • Configuration of QoS (quality of service) policies and attachment of those policies to queues; and
  • Collection of statistics.

OVSDB doesn't do per-flow operations. Those are left to OpenFlow πŸ’…

          +----------------------+
          |      Control &       |
          |     Management       |
          |      Cluster         |
          +----------------------+
             |                \
             | OVSDB           \ OpenFlow
             | Mgmt             \
             |                   \
       +============================================+
       | +--------------+       +--------------+    |
       | |              |       |              |    |
       | | ovsdb-server |-------| ovs-vswitchd |    |
       | |              |       |              |    |
       | +--------------+       +--------------+    |
       |                               |            |
       |                        +----------------+  |
       |                        | Forwarding Path|  |
       |                        +----------------+  |
       +============================================+

                     Figure 1: Open vSwitch Interfaces

For more on how the OVSDB management protocol is used, check out [DB-SCHEMA] πŸ”₯

3. OVSDB Structure πŸ¦„

This section gives the big-picture layout of databases in OVSDB. As described here, the database is pretty generic. For the complete, current description of the database schema used in OVS, go to [DB-SCHEMA]. You can also see Section 4.1.2 to learn how the OVSDB management protocol can be used to discover the schema that's currently in use ✨

3.1. JSON Usage ✨

okay so OVSDB runs on JSON for BOTH its schema format and its wire protocol format πŸ’– that's the whole vibe. [RFC4627] is the JSON spec it's leaning on. BUT the JSON implementation in Open vSwitch has a couple of limitations, so bestie, pay attention:

  • Null bytes (\u0000) SHOULD NOT be used in strings. πŸ™ˆ
  • Only UTF-8 encoding is supported. 🌸

The descriptions below use some shorthand for JSON values, and the terminology follows [RFC4627]. Let's go πŸ’…

<string> is a JSON string. Any Unicode string is allowed, no cap 🌈 Implementations SHOULD disallow null bytes. 🫢

<id> is a JSON string matching [a-zA-Z_][a-zA-Z0-9_]*. Now here's the tea: <id>s that begin with _ are reserved to the implementation and MUST NOT be used by the user. Don't touch the underscore ones, they're not yours πŸ™…β€β™€οΈπŸ’œ

<version> is a JSON string that contains a version number matching [0-9]+\.[0-9]+\.[0-9]+. Think three numbers with dots, like a little version badge πŸ¦‹

<boolean> is a JSON true or false value. Very simple, very iconic ✨

<number> is just a JSON number. Easy πŸ“

<integer> is a JSON number with an integer value, within the range -(2**63)...+(2**63)-1. That's a HUGE range, it's giving big numbers only πŸ‘‘

<json-value> is any JSON value at all. Anything goes 🧚

<nonnull-json-value> is any JSON value except null. So basically, everything but the null drama πŸ’€

<error> is a JSON object with the following members:

           "error": <string>          required
           "details": <string>        optional

The value of the "error" member is a short string, specified in this document, that broadly tells you the class of the error 😭 Most "error" strings are specific to the contexts described elsewhere in this document, but the following "error" strings may show up in any context where an <error> is permitted:

"error": "resources exhausted" means the operation needs more resources (memory, disk, CPU, etc.) than the database server currently has available. The server is basically running on fumes πŸ”₯

"error": "I/O error" means problems accessing the disk, network, or other required resources stopped the operation from finishing. Something in the plumbing broke πŸ› οΈ

Database implementations MAY use "error" strings not specified in this document to indicate errors that don't fit into any of the specified categories. Totally allowed, they get to be creative πŸŽ€ Optionally, an <error> MAY include a "details" member, whose value is a string that describes the error in more detail for the benefit of a human user or administrator. This document does not specify the format or content of the "details" string, so it's up to the implementation, periodt πŸ’… An <error> MAY also have other members that describe the error in more detail. This document does not specify the names or values of these members either, so no one's gonna tell you what to name them ✨

3.2. Schema Format πŸ“‹

Okay so an Open vSwitch configuration database is basically a bunch of tables 🧚, and each table has a few columns and zero or more rows. The schema for the whole database is written up as a <database-schema>, so let's go through it bestie πŸ’–

<database-schema>
   A JSON object with the following members:

        "name": <id>                            required
        "version": <version>                    required
        "cksum": <string>                       optional
        "tables": {<id>: <table-schema>, ...}   required

The "name" identifies the database as a whole. It must be provided to most JSON-RPC requests so the server knows which database you're trying to touch ✨

The "version" tells you the version of the database schema. It is REQUIRED to be present. Open vSwitch semantics for "version" are described in [DB-SCHEMA]. Other schemas may use it differently 🌸

The "cksum" is optional and reports an implementation-defined checksum for the database schema. It's mostly a tool for schema developers, and clients SHOULD ignore it πŸ™ˆ

The value of "tables" is a JSON object. Its names are table names and its values are <table-schema>s, so it's basically a menu of tables πŸ“

<table-schema>
   A JSON object with the following members:

      "columns": {<id>: <column-schema>, ...}   required
      "maxRows": <integer>                      optional
      "isRoot": <boolean>                       optional
      "indexes": [<column-set>*]                optional

The value of "columns" is a JSON object whose names are column names and whose values are <column-schema>s πŸ’…

Every table gets these columns automatically, even though their definitions aren't in the schema:

  • "_uuid": This column holds exactly one UUID value. The database engine sets it to a random value when it creates a row. It's read-only, and its value never changes for the life of a row πŸ”’
  • "_version": Same deal as "_uuid", exactly one UUID value, random when the row is created, and read-only. The difference is that its value changes to a new random value whenever any other field in the row changes πŸ”„ It's also ephemeral. When the database is closed and reopened, or when the database process is stopped and then started again, each "_version" gets a brand new random value too 🫢

If "maxRows" is specified as a positive integer, it limits the maximum number of rows that may be present in the table. This is a "deferred" constraint, so it's only enforced at transaction commit time (see the "transact" request in Section 4.1.3). If "maxRows" isn't specified, the table size is only limited by the resources available to the database server 🌈 And "maxRows" constraints are enforced after unreferenced rows are deleted from tables with a false "isRoot".

The "isRoot" boolean decides whether rows in the table need strong references from other rows to avoid garbage collection. (See the discussion of "strong" and "weak" references below in the description of <base-type>.) If "isRoot" is true, the rows in the table exist independent of any references. They're basically part of the "root set" in a garbage collector πŸ‘‘ If "isRoot" is omitted or false, then any given row may only exist when at least one different row (in the same table or another table) has a strong reference to it. This is a "deferred" action, so unreferenced rows get deleted just before transaction commit πŸ—‘οΈ

For compatibility with schemas made before "isRoot" existed: if "isRoot" is omitted or false in every <table-schema> in a given <database-schema>, then every table is part of the root set πŸ¦‹

If "indexes" is specified, it must be an array of zero or more <column-set>s. A <column-set> is an array of one or more strings, and each string names a column. Each <column-set> is a set of columns whose values, taken together within any given row, must be unique within the table. This is a "deferred" constraint, enforced only at transaction commit time, after unreferenced rows are deleted and dangling weak references are removed. Ephemeral columns may not be part of indexes ⚠️

<column-schema>
   A JSON object with the following members:

      "type": <type>                            required
      "ephemeral": <boolean>                    optional
      "mutable": <boolean>                      optional

The "type" specifies the type of data stored in this column πŸŽ€

If "ephemeral" is true, the column's values aren't guaranteed to be durable. They may be lost when the database restarts. Now, a column whose type (key or value) is a strong reference to a table that isn't part of the root set is always durable, no matter what this says. Otherwise, restarting the database could lose entire rows, and nobody wants that 😭

If "mutable" is false, this column's values may not be modified after they're first set with the "insert" operation πŸ”

<type>
   The type of a database column.  Either an <atomic-type> or a JSON
   object that describes the type of a database column, with the
   following members:

      "key": <base-type>                 required
      "value": <base-type>               optional
      "min": <integer>                   optional
      "max": <integer> or "unlimited"    optional

If "min" or "max" isn't specified, each one defaults to 1. If "max" is "unlimited", there's no specified maximum number of elements, although the implementation will still enforce some limit πŸ™ƒ After considering the defaults, "min" must be exactly 0 or exactly 1, "max" must be at least 1, and "max" must be greater than or equal to "min".

If "min" and "max" are both 1 and "value" isn't specified, the type is the scalar type given by "key" 🍬

If "min" isn't 1, or "max" isn't 1, or both, and "value" isn't specified, the type is a set of scalar type "key" 🫧

If "value" is specified, the type is a map from type "key" to type "value" πŸ—ΊοΈ

<base-type>
   The type of a key or value in a database column.  Either an
   <atomic-type> or a JSON object with the following members:

      "type": <atomic-type>            required
      "enum": <value>                  optional
      "minInteger": <integer>          optional, integers only
      "maxInteger": <integer>          optional, integers only
      "minReal": <real>                optional, reals only
      "maxReal": <real>                optional, reals only
      "minLength": <integer>           optional, strings only
      "maxLength": <integer>           optional, strings only
      "refTable": <id>                 optional, UUIDs only
      "refType": "strong" or "weak"    optional, only with "refTable"

An <atomic-type> on its own is the same as a JSON object with one member, "type", whose value is that <atomic-type> πŸ’«

"enum" can be specified as a <value> whose type is a set of one or more values specified for the member "type". If "enum" is specified, the valid values of the <base-type> are limited to the ones in that <value> 🎯

"enum" is mutually exclusive with the following constraints:

  • If "type" is "integer", then "minInteger" or "maxInteger" or both may also be specified, restricting the valid integer range. If both are specified, then "maxInteger" must be greater than or equal to "minInteger".
  • If "type" is "real", then "minReal" or "maxReal" or both may also be specified, restricting the valid real range. If both are specified, then "maxReal" must be greater than or equal to "minReal".
  • If "type" is "string", then "minLength" and "maxLength" or both may be specified, restricting the valid length of value strings. If both are specified, then "maxLength" must be greater than or equal to "minLength". String length is measured in characters πŸ”€
  • If "type" is "uuid", then "refTable", if present, must be the name of a table within this database. If "refTable" is specified, then "refType" may also be specified. If "refTable" is set, the effect depends on "refType":
    • If "refType" is "strong" or if "refType" is omitted, the allowed UUIDs are limited to UUIDs for rows in the named table.
    • If "refType" is "weak", then any UUIDs are allowed, but UUIDs that don't correspond to rows in the named table will be automatically deleted. When this happens in a map, both the key and the value get deleted from the map πŸ’₯

"refTable" constraints are "deferred" constraints. They're only enforced at transaction commit time (see the "transact" request in Section 4.1.3). The other constraints on <base-type> are "immediate", which means each operation enforces them right away ⚑

<atomic-type>
   One of the strings "integer", "real", "boolean", "string", or
   "uuid", representing the specified scalar type.

4. Wire Protocol ✨

The database wire protocol is implemented in JSON-RPC 1.0 [JSON-RPC]. The JSON-RPC spec allows a range of transports, but implementations of this specification SHOULD operate directly over TCP πŸ¦‹ See Section 6 for discussion of the TCP port.

4.1. RPC Methods πŸ’…

The following subsections describe the RPC methods that are supported. As the JSON-RPC 1.0 spec describes, each request is made of a string with the method name, a (possibly null) array of parameters to pass to the method, and a request ID, which is used to match the response to the request πŸŽ€ Each response has a result object (non-null if the invocation succeeded), an error object (non-null if there was an error), and the ID of the matching request. More details on each method, its parameters, and its results come below πŸ“¬

An OVSDB server MUST implement all of the following methods. An OVSDB client MUST implement the "Echo" method and is otherwise free to implement whichever methods suit its needs πŸ‘‘

The operations that may be performed on the OVS database using these methods (e.g., the "transact" method) are described in Section 5 πŸ’–

4.1.1. List Databases 🌸

This operation retrieves an array whose elements are the names of the databases that can be accessed over this management protocol connection πŸ—‚οΈ

The request object contains the following members:

  • "method": "list_dbs"
  • "params": []
  • "id": <nonnull-json-value>

The response object contains the following members:

  • "result": [<db-name>,...]
  • "error": null
  • "id": same "id" as request

4.1.2. Get Schema ✨

okay so this one is the "hey server, what does this database look like?" method πŸ’… It retrieves a <database-schema> that describes the hosted database <db-name>, which is basically the blueprint for the whole thing πŸ—ΊοΈ

The request object contains the following members:

  • "method": "get_schema"
  • "params": [<db-name>]
  • "id": <nonnull-json-value>

The response object contains the following members:

  • "result": <database-schema>
  • "error": null
  • "id": same "id" as request

If the database named in the request doesn't exist πŸ™ˆ, the server sends a JSON-RPC error response that looks like this:

  • "result": null
  • "error": "unknown database"
  • "id": same "id" as request

4.1.3. Transact πŸŽ€

this is the big one, bestie πŸ”₯ The transact method tells the database server to run a whole series of operations, in the order you list them, on a given database.

The request object contains the following members:

  • "method": "transact"
  • "params": [<db-name>, <operation>*]
  • "id": <nonnull-json-value>

The value of "id" MUST be unique among all in-flight transactions within the current JSON-RPC session. Otherwise, the server may return a JSON-RPC error. 🫢

The "params" array for this method is made up of a <db-name> that says which database the transaction is for, followed by zero or more JSON objects. Each of those objects is one single database operation. Section 5 covers the valid operations πŸ“š. The server runs each operation in the order given, except if one fails, then the rest don't get run at all πŸ’€. The whole set runs as one single atomic, consistent, isolated transaction, and it only gets committed if and only if every operation succeeds. Durability of the commit isn't guaranteed unless the "commit" operation, with "durable" set to true, is included in the operation set. See Section 5 for more discussion of the database operations.

The response object contains the following members:

  • "result": [<object>*]
  • "error": null
  • "id": same "id" as request

No matter whether the database operations hit errors, the response is always a JSON-RPC response with a null "error" and a "result" member that's an array with the same number of elements as "params". Each element in "result" lines up with the same element in "params" πŸ‘‘. Here's how the "result" array elements can be read:

  • A JSON object that does not contain an "error" member means that operation completed successfully. The specific members of that object are described below in the descriptions of individual operations. Some operations don't produce any results, in which case the object has no members at all.
  • An <error> means the matching operation finished with an error.
  • A JSON null value means the operation was never attempted, because a prior operation failed.

Generally, "result" has some number of successful results, maybe followed by an error, then followed by enough JSON null values to match the number of elements in "params". There's one exception πŸ‘€: if all of the operations succeed but the results can't be committed, then "result" has one more element than "params", and that extra element is an <error>. In this case, the possible "error" strings include the following:

"error": "referential integrity violation" When the commit was attempted, a column's value referenced the UUID for a row that didn't exist in the table named by the column's <base-type> key or value "refTable" that has a "refType" of "strong". (This can be caused by inserting a row that references a nonexistent row, by deleting a row that is still referenced by another row, by specifying the UUID for a row in the wrong table, and other ways. πŸ™ƒ)

"error": "constraint violation" A bunch of situations can come up where the attempted commit would violate a constraint on the database. (See Section 3.2 for more discussion of constraints.) These situations include:

  • The number of rows in a table exceeds the maximum number permitted by the table's "maxRows" value.
  • Two or more rows in a table had the same values in the columns that make up an index.
  • A column with a <base-type> key or value "refTable" whose "refType" is "weak" became empty due to deletion(s), and this column is not allowed to be empty because its <type> has a "min" of 1. Such deletions may be the result of rows that it referenced being deleted (or never having existed, if the column's row was inserted within the transaction).

"error": "resources exhausted" The operation needs more resources (memory, disk, CPU, etc.) than the database server currently has available. 😭

"error": "I/O error" Problems accessing the disk, network, or other required resources stopped the operation from finishing.

If "params" contains one or more "wait" operations, the transaction may take an arbitrary amount of time to complete ⏳. The database implementation MUST be capable of accepting, executing, and replying to other transactions and other JSON-RPC requests while a transaction (or transactions) containing "wait" operations is outstanding, whether those are on the same or different JSON-RPC sessions. Non-negotiable, iykyk 🧚

4.1.4. Cancel πŸ¦‹

The "cancel" method is a JSON-RPC notification, which means no matching response is provided. It tells the database server to immediately complete or cancel the "transact" request whose "id" matches the notification's "params" value. The notification object has the following members:

  • "method": "cancel"
  • "params": [the "id" for an outstanding request]
  • "id": null

If the "transact" request can be completed right away, then the server sends a response in the form described for "transact" (Section 4.1.3). Otherwise, the server sends a JSON-RPC error response that looks like this:

  • "result": null
  • "error": "canceled"
  • "id": the "id" member of the canceled request.

The "cancel" notification itself has no reply. Silent but deadly 🀫

4.1.5. Monitor 🌈

The "monitor" request lets a client replicate tables, or subsets of tables, within an OVSDB database. It does this by asking for notifications about changes to those tables, and by receiving the complete initial state of a table or a subset of a table. The request object has the following members:

  • "method": "monitor"
  • "params": [<db-name>, <json-value>, <monitor-requests>]
  • "id": <nonnull-json-value>

The <json-value> parameter is used to match later update notifications (see below) back to this request. The <monitor-requests> object maps the name of the table being monitored to an array of <monitor-request> objects.

Each <monitor-request> is an object with the following members:

    "columns": [<column>*]            optional
    "select": <monitor-select>        optional

The columns, if present, define the columns within the table to be monitored. <monitor-select> is an object with the following members:

    "initial": <boolean>              optional
    "insert": <boolean>               optional
    "delete": <boolean>               optional
    "modify": <boolean>               optional

The contents of this object specify how the columns (or the table) are to be monitored, as explained in more detail below.

The response object has the following members:

  • "result": <table-updates>
  • "error": null
  • "id": same "id" as request

The <table-updates> object is described in detail in Section 4.1.6. It holds the contents of the tables for which "initial" rows are selected. If no tables' initial contents are requested, then "result" is an empty object.

After that, whenever changes to the specified tables are committed, those changes get sent to the client automatically using the "update" monitor notification (see Section 4.1.6). This monitoring keeps going until the JSON-RPC session ends, or until the client sends a "monitor_cancel" JSON-RPC request 🎬

Each <monitor-request> specifies one or more columns, plus the way those columns (or the entire table) are to be monitored. The "columns" member names the columns whose values are monitored. It MUST NOT contain duplicates. If "columns" is omitted, then all columns in the table, except for "_uuid", are monitored. Which circumstances trigger an "update" notification for a row in the table is decided by <monitor-select>:

  • If "initial" is omitted or true, every row in the table is sent as part of the response to the "monitor" request.
  • If "insert" is omitted or true, "update" notifications are sent for rows newly inserted into the table.
  • If "delete" is omitted or true, "update" notifications are sent for rows deleted from the table.
  • If "modify" is omitted or true, "update" notifications are sent whenever a row in the table is modified.

If there's more than one <monitor-request> in an array, then each <monitor-request> in the array should specify both "columns" and "select", and the "columns" MUST be non-overlapping sets. Keep those column sets separate, bestie πŸ’–

4.1.6. Update Notification πŸ’œ

The "update" notification is sent by the server to the client to report changes in tables that are being monitored after a "monitor" request, as described above. The notification has the following members:

  • "method": "update"
  • "params": [<json-value>, <table-updates>]
  • "id": null

The <json-value> in "params" is the same value that was passed as the <json-value> in "params" for the corresponding "monitor" request. <table-updates> is an object that maps from a table name to a <table-update>. A <table-update> is an object that maps from the row's UUID to a <row-update> object. A <row-update> is an object with the following members:

    "old": <row>   present for "delete" and "modify" updates
    "new": <row>   present for "initial", "insert", and "modify" updates

The format of <row> is described in Section 5.1.

Each table in which one or more rows has changed (or whose initial view is being presented) is represented in <table-updates>. Each row that has changed (or whose initial view is being presented) is represented in its <table-update> as a member, with its name taken from the row's "_uuid" member. The corresponding value is a <row-update>:

  • The "old" member is present for "delete" and "modify" updates. For "delete" updates, each monitored column is included. For "modify" updates, the prior value of each monitored column whose value has changed is included (monitored columns that have not changed are represented in "new").
  • The "new" member is present for "initial", "insert", and "modify" updates. For "initial" and "insert" updates, each monitored column is included. For "modify" updates, the new value of each monitored column is included.

Note that initial views of rows are not presented in update notifications. They show up in the response object to the monitor request instead. The formatting of the <table-updates> object, however, is the same in either case 🌸


4.1.7. Monitor Cancellation πŸ›‘

okay so monitor_cancel is how you tell the server to cancel a monitor you asked for earlier πŸ™…β€β™€οΈ The request object has these members:

  • "method": "monitor_cancel"
  • "params": [<json-value>]
  • "id": <nonnull-json-value>

The <json-value> in "params" has to match the <json-value> in "params" from the ongoing "monitor" request you want canceled. After that, no more "update" messages get sent for that table monitor. The response to this request has these members:

  • "result": {}
  • "error": null
  • "id": the request "id" member

If you send a monitor cancellation for a monitor request the server doesn't know about, you'll get an error response instead, with these members:

  • "result": null
  • "error": "unknown monitor"
  • "id": the request "id" member

4.1.8. Lock Operations πŸ”

Three RPC methods, "lock", "steal", and "unlock", let clients do locking operations on the database πŸ’… The database server supports an arbitrary number of locks, and each one is identified by a client-defined ID. At any given time, each lock may have at most one owner. How you use a lock is up to the client. For example, a group of clients might agree that a certain table can only be written by whoever owns a certain lock. OVSDB itself doesn't enforce any rules on how locks get used. It just makes sure a lock has at most one owner ✨

The RPC request objects have these members:

  • "method": "lock", "steal", or "unlock"
  • "params": [<id>]
  • "id": <nonnull-json-value>

The response depends on the request and has these members:

  • "result": {"locked": boolean} for "lock"
  • "result": {"locked": true} for "steal"
  • "result": {} for "unlock"
  • "error": null
  • "id": same "id" as request

Here's how the three methods work 🫢

  • "lock": The database assigns this client ownership of the lock as soon as it's available. When multiple clients ask for the same lock, they get it in first-come, first-served order.
  • "steal": The database assigns this client ownership of the lock right away. If there's already an owner, they lose ownership πŸ’€
  • "unlock": If the client owns the lock, this releases it. If the client has asked for ownership of the lock, this cancels the request.

(Closing or otherwise disconnecting a database client connection unlocks all of its locks.)

For any given lock, the client MUST alternate "lock" or "steal" operations with "unlock" operations. That is, if the previous operation on a lock was "lock" or "steal", it MUST be followed by an "unlock" operation, and vice versa.

For a "lock" operation, the "locked" member in the response object is true if the lock has already been acquired. It's false if another client holds the lock and your request got queued. In that second case, the client gets notified later with a "locked" message (Section 4.1.9) once acquisition succeeds πŸŽ€

These requests complete and send a response quickly, without waiting around. The "locked" and "stolen" notifications (see below) report asynchronous changes to ownership πŸ¦‹

Also, the scope of a lock is a database server, not a single database hosted by that server πŸ‘€ A client can choose a naming convention, like "<db-name>__<lock-name>", which effectively limits a lock's scope to one particular database. It's giving organized 🧚

4.1.9. Locked Notification πŸ”“

The "locked" notification tells a client that it's been granted a lock it previously asked for with the "lock" method described above. The notification has these members:

  • "method": "locked"
  • "params": [<id>]
  • "id": null

"Params" contains the name of the lock that was given in the "lock" request. The notified client now owns the lock named in "params" πŸ‘‘

The database server sends this notification after the reply to the matching "lock" request (but only if the "locked" member of that response was false), and before the reply to the client's later "unlock" request.

4.1.10. Stolen Notification 😭

The "stolen" notification tells a client that had previously obtained a lock that another client has stolen ownership of it. The notification has these members:

  • "method": "stolen"
  • "params": [<id>]
  • "id": null

The notified client no longer owns the lock named in "params". The client MUST still issue an "unlock" request before doing any later "lock" or "steal" operation on that lock πŸ™ˆ

If the client originally got the lock through a "lock" request, it'll automatically regain the lock later, once the client that stole it releases it. (The database server sends the client a "locked" notification at that point to let it know.) 🌈

If the client originally got the lock through a "steal" request, the database server won't automatically give it back when the lock becomes available later. To get ownership again, the client must "unlock" and then "lock" or "steal" the lock again πŸ₯²

4.1.11. Echo πŸ“

The "echo" method can be used by both clients and servers to check that a database connection is still alive πŸ’– It MUST be implemented by both clients and servers. The members of the request are:

  • "method": "echo"
  • "params": JSON array with any contents
  • "id": <json-value>

The response object has these members:

  • "result": same as "params"
  • "error": null
  • "id": the request "id" member

5. Database Operations πŸ—‚οΈ

This section describes the operations that may be specified in the "transact" method described in Section 4.1.3 ✨

5.1. Notation ✨

okay so bestie, this is the vocab list for the rest of section 5 πŸŽ€ we're gonna name everything before we use it, so you don't get lost later πŸ’…

  • <db-name>: an <id> that names a database πŸ—‚οΈ you can get the valid ones with a "list_dbs" request, and the <db-name> comes from the "name" member of <database-schema>.
  • <table>: an <id> that names a table 🌸
  • <column>: an <id> that names a table column πŸ’–
  • <row>: a JSON object that describes a table row, or a subset of one 🧚 each member is a column name paired with that column's <value>.
  • <value>: a JSON value for a column in a table row. It's one of <atom>, <set>, or <map> πŸ¦‹
  • <atom>: a JSON value that's a scalar for a column. It's one of <string>, <number>, <boolean>, <uuid>, or <named-uuid> πŸ“
  • <set>: either an <atom> (which is a set with exactly one element), or a 2-element JSON array that's a database set value. The first element has to be the string "set", and the second element has to be an array of zero or more <atom>s giving the values in the set. All of the <atom>s have to be the same type ✨
  • <map>: a 2-element JSON array that's a database map value. The first element has to be the string "map", and the second element has to be an array of zero or more <pair>s giving the values in the map. All of the <pair>s have to have the same key and value types 🌈

(JSON objects aren't used for <map> because JSON only allows string names in an object, so ya gotta do it this way πŸ™ˆ)

  • <pair>: a 2-element JSON array for a pair inside a database map. The first element is an <atom> for the key, and the second is an <atom> for the value πŸ’œ
  • <uuid>: a 2-element JSON array that's a UUID. The first element has to be the string "uuid", and the second has to be a 36-character string giving the UUID in the format from RFC 4122 [RFC4122]. For example, this <uuid> is the UUID 550e8400-e29b-41d4-a716-446655440000 πŸ”₯
["uuid", "550e8400-e29b-41d4-a716-446655440000"]
  • <named-uuid>: a 2-element JSON array that's the UUID of a row inserted in an "insert" operation within the same transaction πŸ‘‘ the first element has to be the string "named-uuid", and the second should be the <id> set as the "uuid-name" for an "insert" operation in the same transaction. For example, if an "insert" in this transaction has a "uuid-name" of "myrow", this <named-uuid> is the UUID that operation made:
["named-uuid", "myrow"]

A <named-uuid> can go anywhere a <uuid> is valid 🫢 this is so iconic because one transaction can insert a new row AND then point at that row using the "uuid-name" it got when it was inserted. Just note that the "uuid-name" only means something inside a single transaction, so don't try to carry it across ⚠️

  • <condition>: a 3-element JSON array shaped like [<column>, <function>, <value>] that's a test on a column value 🌸 except where it's said otherwise below, <value> MUST have the same type as <column>. What it means depends on the type of the column:
    • integer or real: <function> must be "<", "<=", "==", "!=", ">=", ">", "includes", or "excludes".

The test is true if the column's value satisfies the relation <function> <value>. Like, if the column has value 1 and <value> is 2, the test is true when <function> is "<", "<=", or "!=", but not otherwise πŸ’…

"includes" is the same as "==", and "excludes" is the same as "!=" πŸ¦‹

  • boolean or string or uuid: <function> must be "!=", "==", "includes", or "excludes".

If <function> is "==" or "includes", the test is true if the column's value equals <value>. If <function> is "!=" or "excludes", the test gets flipped πŸ”„

  • set or map: <function> must be "!=", "==", "includes", or "excludes".

If <function> is "==", the test is true if the column's value has exactly the same values (for sets) or pairs (for maps). If <function> is "!=", it gets inverted ✨

If <function> is "includes", the test is true if the column's value has all of the values (for sets) or pairs (for maps) in <value>. The column's value can also have other stuff in it, that's totally fine 🌈

If <function> is "excludes", the test is true if the column's value doesn't contain any of the values (for sets) or pairs (for maps) in <value>. The column's value can have other values or pairs that aren't in <value> πŸ™Œ

If <function> is "includes" or "excludes", then the required type of <value> gets a little relaxed, so it can have fewer than the minimum number of elements the column's type asks for. And if <function> is "excludes", it's relaxed even more, so <value> can have more than the maximum number of elements too πŸ“

  • <function>: one of "<", "<=", "==", "!=", ">=", ">", "includes", or "excludes" πŸ’œ
  • <mutation>: a 3-element JSON array shaped like [<column>, <mutator>, <value>] that's a change to a column value 🧚 except where it's said otherwise below, <value> must have the same type as <column>. What it means depends on the type of the column:
    • integer or real: <mutator> must be "+=", "-=", "*=", "/=", or (integer only) "%=". The column's value becomes the sum, difference, product, quotient, or remainder (respectively) of <column> and <value> πŸ”₯

Constraints on <column> get ignored when <value> is parsed. Bye, constraints πŸ‘‹

  • boolean, string, or uuid: no valid <mutator>s are defined for these types right now πŸ’€
  • set: any <mutator> that's valid for the set's element type can be applied to the set. When it is, the mutation gets applied to each member of the set on its own. <value> must be a scalar value of the same type as the set's element type, except that constraints are ignored when <value> is parsed 🫢

If <mutator> is "insert", then each of the values in the set in <value> gets added to <column> if it isn't already there. The required type of <value> is slightly relaxed, so it can have fewer than the minimum number of elements the column's type asks for 🌸

If <mutator> is "delete", then each of the values in the set in <value> gets removed from <column> if it's there. The required type is slightly relaxed too, so <value> can have more or less than the maximum number of elements the column's type asks for πŸ¦‹

  • map: <mutator> must be "insert" or "delete" πŸ“

If <mutator> is "insert", then each of the key-value pairs in the map in <value> gets added to <column> only if its key isn't already present. The required type of <value> is slightly relaxed, so it can have fewer than the minimum number of elements the column's type asks for ✨

If <mutator> is "delete", then <value> can be the same type as <column> (a map type), or it can be a set whose element type is the same as <column>'s key type:

  • If <value> is a map, the mutation deletes each key-value pair in <column> whose key and value both match one of the key-value pairs in <value> πŸ’–
  • If <value> is a set, the mutation deletes each key-value pair in <column> whose key matches one of the values in <value> 🌈

For "delete", <value> can have any number of elements, no matter what restrictions the number of elements in <column> has πŸ™Œ

  • <mutator>: one of "+=", "-=", "*=", "/=", "%=", "insert", or "delete" πŸ‘‘

5.2. Operations πŸ’…

okay so the operations are the things you can do as part of a "transact" RPC request (see Section 4.1.3) πŸŽ€ each one is a JSON object that can be one of the elements in the "params" array, which is one of the elements of the "transact" request. Each object's details, what it means, its results, and the errors it can throw are all in the subsections below 🫢

5.2.1. Insert 🧚

the "insert" object has these members:

"op": "insert"          required
"table": <table>        required
"row": <row>            required
"uuid-name": <id>       optional

the matching result object has this member:

"uuid": <uuid>

this op inserts "row" into "table" 🌸 if "row" doesn't set values for every column in "table", those columns get default values. The default depends on the column's type. The default for a column whose <type> has a "min" of 0 is an empty set or an empty map. Otherwise it's a single value or a single key-value pair, and its value(s) depend on its <atomic-type>:

  • "integer" or "real": 0
  • "boolean": false
  • "string": "" (the empty string)
  • "uuid": 00000000-0000-0000-0000-000000000000

The new row gets a brand-new, randomly generated UUID πŸ¦‹ if "uuid-name" is supplied, then it's an error if the <id> isn't unique among all the "uuid-name"s on every "insert" in this transaction. The row's UUID comes back as the "uuid" member of the result πŸ’–

the errors that can come back are:

"error": "duplicate uuid-name"
the same "uuid-name" shows up on another "insert" in this transaction. oof 😭
"error": "constraint violation"
one of the values in "row" doesn't meet the immediate constraints for its column's <base-type>. This one can also happen for columns that "row" never set, if the default value doesn't meet that column's constraints πŸ’€

5.2.2. Select πŸ”

the "select" object has these members:

"op": "select"                required
"table": <table>              required
"where": [<condition>*]       required
"columns": [<column>*]        optional

the matching result object has this member:

"rows": [<row>*]

this op searches "table" for rows that match all the conditions in "where" 🌈 if "where" is an empty array, then every row in "table" gets selected πŸ™Œ

the "rows" member of the result is an array of objects. Each object is a matching row, with every column named in "columns" as a member. The member name is the column's name, and the member value is its value ✨ if "columns" isn't specified, then all of the table's columns are included (even the internally generated "_uuid" and "_version" columns) 🧚 if two rows in the result have the same values for every included column, only one copy of that row is in "rows". Specifying "_uuid" inside "columns" keeps you from dropping duplicates, since every row has a unique UUID 🫢

The ordering of rows within "rows" is unspecified πŸ€·β€β™€οΈ

5.2.3. Update πŸ¦‹

The "update" object has these members, bestie:

"op": "update"                required
"table": <table>              required
"where": [<condition>*]       required
"row": <row>                  required

The result object just gives you this:

"count": <integer>

This op finds the rows in "table" that match every condition in "where" ✨ Then it sets each column named in "row" to the value that "row" gives for that column πŸ’… The "_uuid" and "_version" columns can't be directly updated with this op πŸ™ˆ Columns the schema marks as read-only are off limits too πŸ”’

The "count" in the result tells you how many rows matched πŸ“

The one error you might get is:

"error": "constraint violation"

This happens when one of the values in "row" fails the immediate constraints for its column's <base-type> πŸ’€

5.2.4. Mutate 🌸

The "mutate" object has these members:

"op":  "mutate"               required
"table": <table>              required
"where": [<condition>*]       required
"mutations": [<mutation>*]    required

The result object gives you this:

"count": <integer>

This op mutates rows in a table 🧚 It searches "table" for rows that match all the conditions in "where" πŸ‘€ Then, for each matching row, it applies every <mutation> in "mutations", in the order they're listed πŸ”₯

The "_uuid" and "_version" columns of a table can't be directly modified with this op either 🫢 Read-only columns from the schema also can't be updated.

"count" in the result is again the number of rows that matched πŸŽ€

You might run into these errors:

"error":  "domain error"

The mutation's result isn't mathematically defined, like dividing by zero πŸ™ƒ

"error":  "range error"

The mutation's result doesn't fit in the database's format. For example, an integer outside the range INT64_MIN...INT64_MAX, or a real outside the range -DBL_MAX...DBL_MAX πŸ“‰

"error": "constraint violation"

The mutation pushed a column's value into violation of a constraint. Maybe a column ended up with too many or too few values, an arithmetic op gave a set or map duplicate elements, or it broke a constraint from the column's <base-type> 😭

5.2.5. Delete πŸ—‘οΈ

The "delete" object has these members:

"op":  "delete"               required
"table": <table>              required
"where": [<condition>*]       required

The result object:

"count": <integer>

This op deletes all the rows in "table" that match all the conditions in "where" πŸ’… The "count" in the result is the number of rows that got deleted. Bye bye rows πŸ‘‹

5.2.6. Wait ⏳

The "wait" object has these members:

"op": "wait"                        required
"timeout": <integer>                optional
"table": <table>                    required
"where": [<condition>*]             required
"columns": [<column>*]              required
"until": "==" or "!="               required
"rows": [<row>*]                    required

There is no result object for this one πŸ€·β€β™€οΈ

This op waits until a condition becomes true. It's giving patience πŸ§˜β€β™€οΈ

If "until" is "==", the op checks whether the query on "table" (using "where" and "columns", evaluated the same way as it is for "select") returns the result set given by "rows". If it does, the op completes successfully πŸŽ‰ If it doesn't, the whole transaction rolls back. It gets automatically restarted later, after some change in the database makes it possible for the op to succeed. The client won't get a response until the op either permanently succeeds or permanently fails πŸ₯Ή

If "until" is "!=", the test flips. So as long as the query on "table" (with "where" and "columns") returns "rows", the transaction gets rolled back and restarted later πŸ”„

If "timeout" is specified, the transaction aborts after that many milliseconds ⏰ The transaction is guaranteed to be attempted at least once before it aborts. A "timeout" of 0 will abort the transaction on the first mismatch, so no second chances, bestie πŸ’€

The one error you might see is:

"error":  "timed out"

This means the "timeout" was reached before the transaction could finish πŸ•°οΈ

5.2.7. Commit πŸ’Ύ

The "commit" object has these members:

"op": "commit"                      required
"durable": <boolean>                required

No result object here either 🌈

If "durable" is true, then the transaction (if it commits) gets stored durably, meaning to disk, before the reply goes back to the client πŸ’– With "durable" set to false, this op is basically a no-op 😴

The one error you might get is:

"error": "not supported"

This shows up when "durable" is true and the database implementation can't do durable commits πŸ™…β€β™€οΈ

5.2.8. Abort 🚫

The "abort" object has this member:

"op":  "abort"                      required

No result object, because this op never succeeds πŸ’€

This op aborts the entire transaction with an error. It can be useful for testing πŸ§ͺ

The error that will be returned is:

"error": "aborted"

This op always fails with this error. Iconic πŸ’…

5.2.9. Comment πŸ“

The "comment" object has these members:

"op": "comment"                    required
"comment": <string>                required

There is no result object for this one 🫠

This op gives a database administrator info about why a transaction exists 🧠 For example, the ovsdb-server implementation adds comments to transactions that modify the database into the database journal. This is super helpful for debugging, especially when there are multiple clients writing to a database πŸ” You can see an example in the ovs-vsctl tool, a command line tool that talks to ovsdb-server. When it does operations on the database, it includes the command that was invoked (for instance, "ovs-vsctl add-br br0") as a comment in the transaction. That comment then shows up in the journal right next to the changes made to the tables πŸ“–βœ¨

5.2.10. Assert πŸ”

The assert object has these members:

"op": "assert"                     required
"lock": <id>                       required

The result object has no members 🫧

The assert op aborts the transaction if the client doesn't own the lock named <id> 🚨

The error that may be returned is:

"error": "not owner"

This means the client doesn't own the named lock. Not your lock, not your transaction πŸ’…


6. IANA Considerations 🌷

IANA has assigned TCP port 6640 for this protocol πŸŽ€ Earlier implementations of OVSDB used a different port number, but compliant implementations should use the IANA-assigned number πŸ’œ

IANA has updated the reference for port 6640 so it points to this document ✨

7. Security Considerations πŸ›‘οΈ

This part is serious, bestie 😀

The main security issue for the OVSDB protocol is the authentication, integrity, and privacy of communications between a client and a server that implement this protocol πŸ” To protect all of that, an OVSDB connection SHOULD be secured using Transport Layer Security (TLS) [RFC5246] πŸ”’ How clients and servers authenticate each other depends heavily on the operating environment 🌍 A lot of the time, OVSDB clients and servers live in a tightly controlled environment, like machines in a single data center that talk over an isolated management network 🏒

8. Acknowledgements 🫢

Thanks to Jeremy Stribling and Justin Pettit for their helpful input to this document πŸ’–

9. References πŸ“š

9.1. Normative References

[DCE]        "DCE: Remote Procedure Call", Open Group CAE
             Specification C309, ISBN 1-85912-041-5, August 1994.

[JSON-RPC]   "JSON-RPC Specification, Version 1.0",
             <http://json-rpc.org/wiki/specification>.

[RFC2119]    Bradner, S., "Key words for use in RFCs to Indicate
             Requirement Levels", BCP 14, RFC 2119, March 1997.

[RFC4122]    Leach, P., Mealling, M., and R. Salz, "A Universally
             Unique IDentifier (UUID) URN Namespace", RFC 4122,
             July 2005.

[RFC4627]    Crockford, D., "The application/json Media Type for
             JavaScript Object Notation (JSON)", RFC 4627, July 2006.

[RFC5246]    Dierks, T. and E. Rescorla, "The Transport Layer
             Security (TLS) Protocol Version 1.2", RFC 5246,
             August 2008.

9.2. Informative References

[DB-SCHEMA]  "Open vSwitch Database Schema",
             <http://openvswitch.org/ovs-vswitchd.conf.db.5.pdf>.

[OF-SPEC]    Open Networking Foundation, "OpenFlow Switch
             Specification, version 1.3.3", October 2013,
             <https://www.opennetworking.org>.

[OVS]        "Open vSwitch", <http://openvswitch.org/>.

Authors' Addresses 🧚

Ben Pfaff
VMware, Inc.
3401 Hillview Ave.
Palo Alto, CA  94304
USA

EMail: blp@nicira.com


Bruce Davie (editor)
VMware, Inc.
3401 Hillview Ave.
Palo Alto, CA  94304
USA

EMail: bsd@nicira.com
πŸ“¦ where this page comes from