Added inventory concept docs, along with corresponding schema diagram

This commit is contained in:
Jim Baker
2016-06-07 16:38:55 -06:00
parent 99df7e488a
commit 3c55347835
2 changed files with 458 additions and 0 deletions

View File

@@ -0,0 +1,161 @@
Inventory
=========
Concepts
--------
The fundamental unit of inventory in Craton is a **device**, which has
the following characteristics:
Configurability
A device is configurable, either directly over SSH with tooling
like Ansible or indirectly via some controller.
Configuration can be further divided as follows:
* Version-controlled configuration, often captured in
playbooks. It is the responsibility of workflows to use such
configuration.
* Config data stored in the inventory schema.
In either case, the ultimate source may be manual, programmatic,
or a combination of the two.
Addressability
A device has an IP address (specifically the control plane IP
address).
Hierarchy
Devices are part of a hierarchy of regions and cells; a region in
turn is part of a project.
Labels
Devices may be arbitrarily labeled. Such labels can describe
physical layout -- cabinet, networking, power, etc -- along with
logical usage -- compute service, etc. Labels are not in a
hierarchy. (But should they be?)
Some systems like Kubernetes use a key/value scheme for
labels. This can be readily supported by the convention
``key:value`` for the label name.
A **host** is a concrete subclass of device, and corresponds to the
equivalent Ansible concept: a host is directly configurable by an
Ansible playbook over SSH. Hosts have associated SSH keys for such
access.
**Principals** interact (read and/or write) with inventory about
devices. Governance for this interaction is mediated by RBAC and
logged with respect to **change records** (including the reason for
the change). Third party governance systems like OneOps can further
structure these interactions. There are two classes of principals:
Workflows
Capture audits about inventory; which are in turn used for any
remediation. The pattern of usage is bottom-up.
Workflows are pluggable. They can be refined to a number of
levels: Ansible, OpenStack Ansible, a specific workflow for
managing patch levels with OSA (TODO does that actually make
sense for OSA?).
Note that a workflow can be run any number of times, against
different subsets of inventory. Example: migrate a specific
cabinet, as specified by a label. Think of this distinction as
being like an Ansible playbook vs its play.
Because workflows also know about version-controlled config,
perhaps they can be used for queries as well. (TODO Ansible has
some limited ways of determining such variables; it's possible OSA
might develop this further as well in terms of role-based scheme.)
Users
Configure and query inventory. The pattern of usage is top-down,
whether that's configuring a specific label or drilling down from
a given cell.
Users also can run workflows. This capability implies that
workflows can be linked to roles; and that permissions include
being the ability to run workflows.
Principals
Inventory interactions can be optionally logged. For example, if
inventory is configured to use MySQL with InnoDB as its backing store,
then all changes can be captured in the write-ahead log and reliably
streamed to Kafka for analysis.
Associated with each region, cell, label, and device are
**variables**. Here are some aspects of variables:
Description
Variables are used to describe a device with config, auditing
(possibly a subset of discovered facts), and other
information. However variables do not store logging, metric, or
monitoring information --- because of volume, such storage is best
done in a separate database, such as timeseries DB.
Key/value pairs
Variables are made of up key/value pairs.
Key
Keys are strings; they are additionally restricted to be valid
Python identifiers. We usually refer to these as **top-level
keys**, because values can be arbitrarily complex JSON values.
Such keys, and their prefixes, also serve as roles in Craton's
implementation of RBAC. Keys are in a single namespace that does
not differentiate between config or audit variables.
Value
Values are of JSON type, and can store arbitrary data as such,
including binary data via base64 encoding. Workflows define these
specifics.
Scope resolution
Variables use hierarchical scope to resolve for a specific device,
using the following ordering:
1. Region
2. Cell
3. Label; if a device has multiple labels, the labels are sorted
alphanumerically
4. Device
Such resolution overrides at the lowest defined level, which
allows for variables to describe a device with the "broadest
possible brush". Overrides do not merge values, even if the value
has keys embedded in it.
In general, config variables should be set at the highest
possible level; whereas audit data should be bottom up from
device.
Metadata
Variables are also associated with the actor that wrote
them, along with a record of change, including a note describing
the change.
It may be desirable to track other metadata about a variable: is
this intended for config, vs discovered from an audit? But note
this might be just a question of which actor wrote this variable:
was it a user? (Config.) Or was it a workflow? (Audit/remediation,
possibly further identified by workflow metadata.)
Implementation
--------------
Craton's inventory is modeled using Python objects, which in turn has
a concrete reference implementation using SQLAlchemy:
.. image:: img/schema.svg
:width: 660px
:align: center
:alt: ER model
TODO(jimbaker): implementation of the inventory concepts is a work in
progress, however, the above schema represents the current
implementation. Notably missing are principals, including workflows
and users, which will be added in the next phase of work.

297
doc/source/img/schema.svg Normal file
View File

@@ -0,0 +1,297 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.38.0 (20140413.2041)
-->
<!-- Title: G Pages: 1 -->
<svg width="957pt" height="720pt"
viewBox="0.00 0.00 957.33 720.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 716)">
<title>G</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-716 953.333,-716 953.333,4 -4,4"/>
<!-- AccessSecret -->
<g id="node1" class="node"><title>AccessSecret</title>
<polygon fill="none" stroke="black" points="45.8406,-328 45.8406,-342 134.841,-342 134.841,-328 45.8406,-328"/>
<text text-anchor="start" x="59.7742" y="-332" font-family="Bitstream-Vera Sans" font-size="10.00">AccessSecret</text>
<polygon fill="none" stroke="black" points="45.8406,-292 45.8406,-328 134.841,-328 134.841,-292 45.8406,-292"/>
<text text-anchor="start" x="47.8406" y="-319.6" font-family="Bitstream-Vera Sans" font-size="8.00">+created_at : DateTime</text>
<text text-anchor="start" x="47.8406" y="-311.6" font-family="Bitstream-Vera Sans" font-size="8.00">+updated_at : DateTime</text>
<text text-anchor="start" x="47.8406" y="-303.6" font-family="Bitstream-Vera Sans" font-size="8.00">+id : Integer</text>
<text text-anchor="start" x="47.8406" y="-295.6" font-family="Bitstream-Vera Sans" font-size="8.00">+cert : Text</text>
</g>
<!-- Host -->
<g id="node6" class="node"><title>Host</title>
<polygon fill="none" stroke="black" points="142.841,-206 142.841,-220 250.841,-220 250.841,-206 142.841,-206"/>
<text text-anchor="start" x="186.56" y="-210" font-family="Bitstream-Vera Sans" font-size="10.00">Host</text>
<polygon fill="none" stroke="black" points="142.841,-106 142.841,-206 250.841,-206 250.841,-106 142.841,-106"/>
<text text-anchor="start" x="144.841" y="-197.6" font-family="Bitstream-Vera Sans" font-size="8.00">+created_at : DateTime</text>
<text text-anchor="start" x="144.841" y="-189.6" font-family="Bitstream-Vera Sans" font-size="8.00">+updated_at : DateTime</text>
<text text-anchor="start" x="144.841" y="-181.6" font-family="Bitstream-Vera Sans" font-size="8.00">+id : Integer</text>
<text text-anchor="start" x="144.841" y="-173.6" font-family="Bitstream-Vera Sans" font-size="8.00">+type : String</text>
<text text-anchor="start" x="144.841" y="-165.6" font-family="Bitstream-Vera Sans" font-size="8.00">+name : String</text>
<text text-anchor="start" x="144.841" y="-157.6" font-family="Bitstream-Vera Sans" font-size="8.00">+region_id : Integer</text>
<text text-anchor="start" x="144.841" y="-149.6" font-family="Bitstream-Vera Sans" font-size="8.00">+cell_id : Integer</text>
<text text-anchor="start" x="144.841" y="-141.6" font-family="Bitstream-Vera Sans" font-size="8.00">+project_id : Integer</text>
<text text-anchor="start" x="144.841" y="-133.6" font-family="Bitstream-Vera Sans" font-size="8.00">+ip_address : IPAddressType</text>
<text text-anchor="start" x="144.841" y="-125.6" font-family="Bitstream-Vera Sans" font-size="8.00">+active : Boolean</text>
<text text-anchor="start" x="144.841" y="-117.6" font-family="Bitstream-Vera Sans" font-size="8.00">+note : Text</text>
<text text-anchor="start" x="144.841" y="-109.6" font-family="Bitstream-Vera Sans" font-size="8.00">+access_secret_id : Integer</text>
</g>
<!-- AccessSecret&#45;&gt;Host -->
<g id="edge22" class="edge"><title>AccessSecret&#45;&gt;Host</title>
<path fill="none" stroke="black" d="M104.328,-287.766C114.286,-272.141 128.03,-251.985 141.903,-232.495"/>
<polygon fill="black" stroke="black" points="147.846,-224.197 145.682,-234.947 144.935,-228.262 142.023,-232.327 142.023,-232.327 142.023,-232.327 144.935,-228.262 138.365,-229.707 147.846,-224.197 147.846,-224.197"/>
<text text-anchor="middle" x="135.102" y="-225.597" font-family="Bitstream-Vera Sans" font-size="7.00">+hosts *</text>
</g>
<!-- Cell -->
<g id="node2" class="node"><title>Cell</title>
<polygon fill="none" stroke="black" points="293.841,-474 293.841,-488 382.841,-488 382.841,-474 293.841,-474"/>
<text text-anchor="start" x="329.727" y="-478" font-family="Bitstream-Vera Sans" font-size="10.00">Cell</text>
<polygon fill="none" stroke="black" points="293.841,-414 293.841,-474 382.841,-474 382.841,-414 293.841,-414"/>
<text text-anchor="start" x="295.841" y="-465.6" font-family="Bitstream-Vera Sans" font-size="8.00">+created_at : DateTime</text>
<text text-anchor="start" x="295.841" y="-457.6" font-family="Bitstream-Vera Sans" font-size="8.00">+updated_at : DateTime</text>
<text text-anchor="start" x="295.841" y="-449.6" font-family="Bitstream-Vera Sans" font-size="8.00">+id : Integer</text>
<text text-anchor="start" x="295.841" y="-441.6" font-family="Bitstream-Vera Sans" font-size="8.00">+region_id : Integer</text>
<text text-anchor="start" x="295.841" y="-433.6" font-family="Bitstream-Vera Sans" font-size="8.00">+project_id : Integer</text>
<text text-anchor="start" x="295.841" y="-425.6" font-family="Bitstream-Vera Sans" font-size="8.00">+name : String</text>
<text text-anchor="start" x="295.841" y="-417.6" font-family="Bitstream-Vera Sans" font-size="8.00">+note : Text</text>
</g>
<!-- CellVariable -->
<g id="node3" class="node"><title>CellVariable</title>
<polygon fill="none" stroke="black" points="257.841,-332 257.841,-346 346.841,-346 346.841,-332 257.841,-332"/>
<text text-anchor="start" x="275.751" y="-336" font-family="Bitstream-Vera Sans" font-size="10.00">CellVariable</text>
<polygon fill="none" stroke="black" points="257.841,-288 257.841,-332 346.841,-332 346.841,-288 257.841,-288"/>
<text text-anchor="start" x="259.841" y="-323.6" font-family="Bitstream-Vera Sans" font-size="8.00">+created_at : DateTime</text>
<text text-anchor="start" x="259.841" y="-315.6" font-family="Bitstream-Vera Sans" font-size="8.00">+updated_at : DateTime</text>
<text text-anchor="start" x="259.841" y="-307.6" font-family="Bitstream-Vera Sans" font-size="8.00">+parent_id : Integer</text>
<text text-anchor="start" x="259.841" y="-299.6" font-family="Bitstream-Vera Sans" font-size="8.00">+key : String</text>
<text text-anchor="start" x="259.841" y="-291.6" font-family="Bitstream-Vera Sans" font-size="8.00">+value : JSONType</text>
</g>
<!-- Cell&#45;&gt;CellVariable -->
<g id="edge8" class="edge"><title>Cell&#45;&gt;CellVariable</title>
<path fill="none" stroke="black" d="M326.844,-409.678C322.569,-394.005 317.671,-376.044 313.349,-360.196"/>
<polygon fill="black" stroke="black" points="310.604,-350.133 317.577,-358.596 311.92,-354.956 313.235,-359.78 313.235,-359.78 313.235,-359.78 311.92,-354.956 308.894,-360.964 310.604,-350.133 310.604,-350.133"/>
<text text-anchor="middle" x="290.272" y="-351.533" font-family="Bitstream-Vera Sans" font-size="7.00">+_variables *</text>
</g>
<!-- Device -->
<g id="node4" class="node"><title>Device</title>
<polygon fill="none" stroke="black" points="476.841,-356 476.841,-370 584.841,-370 584.841,-356 476.841,-356"/>
<text text-anchor="start" x="515.557" y="-360" font-family="Bitstream-Vera Sans" font-size="10.00">Device</text>
<polygon fill="none" stroke="black" points="476.841,-264 476.841,-356 584.841,-356 584.841,-264 476.841,-264"/>
<text text-anchor="start" x="478.841" y="-347.6" font-family="Bitstream-Vera Sans" font-size="8.00">+created_at : DateTime</text>
<text text-anchor="start" x="478.841" y="-339.6" font-family="Bitstream-Vera Sans" font-size="8.00">+updated_at : DateTime</text>
<text text-anchor="start" x="478.841" y="-331.6" font-family="Bitstream-Vera Sans" font-size="8.00">+id : Integer</text>
<text text-anchor="start" x="478.841" y="-323.6" font-family="Bitstream-Vera Sans" font-size="8.00">+type : String</text>
<text text-anchor="start" x="478.841" y="-315.6" font-family="Bitstream-Vera Sans" font-size="8.00">+name : String</text>
<text text-anchor="start" x="478.841" y="-307.6" font-family="Bitstream-Vera Sans" font-size="8.00">+region_id : Integer</text>
<text text-anchor="start" x="478.841" y="-299.6" font-family="Bitstream-Vera Sans" font-size="8.00">+cell_id : Integer</text>
<text text-anchor="start" x="478.841" y="-291.6" font-family="Bitstream-Vera Sans" font-size="8.00">+project_id : Integer</text>
<text text-anchor="start" x="478.841" y="-283.6" font-family="Bitstream-Vera Sans" font-size="8.00">+ip_address : IPAddressType</text>
<text text-anchor="start" x="478.841" y="-275.6" font-family="Bitstream-Vera Sans" font-size="8.00">+active : Boolean</text>
<text text-anchor="start" x="478.841" y="-267.6" font-family="Bitstream-Vera Sans" font-size="8.00">+note : Text</text>
</g>
<!-- Cell&#45;&gt;Device -->
<g id="edge2" class="edge"><title>Cell&#45;&gt;Device</title>
<path fill="none" stroke="black" d="M390.266,-409.858C411.796,-394.726 437.025,-377.44 460.138,-361.976"/>
<polygon fill="black" stroke="black" points="468.547,-356.368 462.724,-365.66 464.388,-359.142 460.228,-361.916 460.228,-361.916 460.228,-361.916 464.388,-359.142 457.731,-358.172 468.547,-356.368 468.547,-356.368"/>
<text text-anchor="middle" x="452.302" y="-357.768" font-family="Bitstream-Vera Sans" font-size="7.00">+devices *</text>
</g>
<!-- Project -->
<g id="node9" class="node"><title>Project</title>
<polygon fill="none" stroke="black" points="525.841,-694 525.841,-708 614.841,-708 614.841,-694 525.841,-694"/>
<text text-anchor="start" x="554.779" y="-698" font-family="Bitstream-Vera Sans" font-size="10.00">Project</text>
<polygon fill="none" stroke="black" points="525.841,-658 525.841,-694 614.841,-694 614.841,-658 525.841,-658"/>
<text text-anchor="start" x="527.841" y="-685.6" font-family="Bitstream-Vera Sans" font-size="8.00">+created_at : DateTime</text>
<text text-anchor="start" x="527.841" y="-677.6" font-family="Bitstream-Vera Sans" font-size="8.00">+updated_at : DateTime</text>
<text text-anchor="start" x="527.841" y="-669.6" font-family="Bitstream-Vera Sans" font-size="8.00">+id : Integer</text>
<text text-anchor="start" x="527.841" y="-661.6" font-family="Bitstream-Vera Sans" font-size="8.00">+name : String</text>
</g>
<!-- Cell&#45;&gt;Project -->
<g id="edge21" class="edge"><title>Cell&#45;&gt;Project</title>
<path fill="none" stroke="black" d="M371.259,-492.314C397.782,-527.996 436.881,-580.134 478.841,-618 490.891,-628.875 504.991,-639.115 518.476,-648.083"/>
<polygon fill="black" stroke="black" points="527.222,-653.786 516.387,-652.093 523.034,-651.054 518.845,-648.323 518.845,-648.323 518.845,-648.323 523.034,-651.054 521.303,-644.554 527.222,-653.786 527.222,-653.786"/>
<text text-anchor="middle" x="511.754" y="-648.186" font-family="Bitstream-Vera Sans" font-size="7.00">+project 1</text>
</g>
<!-- Region -->
<g id="node10" class="node"><title>Region</title>
<polygon fill="none" stroke="black" points="603.841,-592 603.841,-606 692.841,-606 692.841,-592 603.841,-592"/>
<text text-anchor="start" x="632.496" y="-596" font-family="Bitstream-Vera Sans" font-size="10.00">Region</text>
<polygon fill="none" stroke="black" points="603.841,-540 603.841,-592 692.841,-592 692.841,-540 603.841,-540"/>
<text text-anchor="start" x="605.841" y="-583.6" font-family="Bitstream-Vera Sans" font-size="8.00">+created_at : DateTime</text>
<text text-anchor="start" x="605.841" y="-575.6" font-family="Bitstream-Vera Sans" font-size="8.00">+updated_at : DateTime</text>
<text text-anchor="start" x="605.841" y="-567.6" font-family="Bitstream-Vera Sans" font-size="8.00">+id : Integer</text>
<text text-anchor="start" x="605.841" y="-559.6" font-family="Bitstream-Vera Sans" font-size="8.00">+project_id : Integer</text>
<text text-anchor="start" x="605.841" y="-551.6" font-family="Bitstream-Vera Sans" font-size="8.00">+name : String</text>
<text text-anchor="start" x="605.841" y="-543.6" font-family="Bitstream-Vera Sans" font-size="8.00">+note : Text</text>
</g>
<!-- Cell&#45;&gt;Region -->
<g id="edge17" class="edge"><title>Cell&#45;&gt;Region</title>
<path fill="none" stroke="black" d="M390.378,-470.392C444.505,-490.55 528.689,-523.052 585.986,-545.855"/>
<polygon fill="black" stroke="black" points="595.295,-549.57 584.34,-550.043 590.651,-547.717 586.007,-545.864 586.007,-545.864 586.007,-545.864 590.651,-547.717 587.675,-541.684 595.295,-549.57 595.295,-549.57"/>
<text text-anchor="middle" x="580.603" y="-550.97" font-family="Bitstream-Vera Sans" font-size="7.00">+region 1</text>
</g>
<!-- Device&#45;&gt;Cell -->
<g id="edge3" class="edge"><title>Device&#45;&gt;Cell</title>
<path fill="none" stroke="black" d="M468.774,-364.89C446.374,-380.506 421.145,-397.685 398.834,-412.495"/>
<polygon fill="black" stroke="black" points="390.439,-418.046 396.298,-408.777 394.609,-415.289 398.78,-412.531 398.78,-412.531 398.78,-412.531 394.609,-415.289 401.262,-416.284 390.439,-418.046 390.439,-418.046"/>
<text text-anchor="middle" x="404.545" y="-419.446" font-family="Bitstream-Vera Sans" font-size="7.00">+cell 0..1</text>
</g>
<!-- DeviceVariable -->
<g id="node5" class="node"><title>DeviceVariable</title>
<polygon fill="none" stroke="black" points="697.841,-178 697.841,-192 786.841,-192 786.841,-178 697.841,-178"/>
<text text-anchor="start" x="709.081" y="-182" font-family="Bitstream-Vera Sans" font-size="10.00">DeviceVariable</text>
<polygon fill="none" stroke="black" points="697.841,-134 697.841,-178 786.841,-178 786.841,-134 697.841,-134"/>
<text text-anchor="start" x="699.841" y="-169.6" font-family="Bitstream-Vera Sans" font-size="8.00">+created_at : DateTime</text>
<text text-anchor="start" x="699.841" y="-161.6" font-family="Bitstream-Vera Sans" font-size="8.00">+updated_at : DateTime</text>
<text text-anchor="start" x="699.841" y="-153.6" font-family="Bitstream-Vera Sans" font-size="8.00">+parent_id : Integer</text>
<text text-anchor="start" x="699.841" y="-145.6" font-family="Bitstream-Vera Sans" font-size="8.00">+key : String</text>
<text text-anchor="start" x="699.841" y="-137.6" font-family="Bitstream-Vera Sans" font-size="8.00">+value : JSONType</text>
</g>
<!-- Device&#45;&gt;DeviceVariable -->
<g id="edge19" class="edge"><title>Device&#45;&gt;DeviceVariable</title>
<path fill="none" stroke="black" d="M593.006,-271.217C623.432,-249.299 659.679,-223.188 688.784,-202.221"/>
<polygon fill="black" stroke="black" points="697.182,-196.171 691.698,-205.668 693.125,-199.094 689.068,-202.017 689.068,-202.017 689.068,-202.017 693.125,-199.094 686.437,-198.365 697.182,-196.171 697.182,-196.171"/>
<text text-anchor="middle" x="676.85" y="-197.571" font-family="Bitstream-Vera Sans" font-size="7.00">+_variables *</text>
</g>
<!-- Device&#45;&gt;Host -->
<g id="edge1" class="edge"><title>Device&#45;&gt;Host</title>
<path fill="none" stroke="black" d="M468.801,-287.766C408.998,-260.55 318.996,-219.592 259.114,-192.34"/>
</g>
<!-- Label -->
<g id="node7" class="node"><title>Label</title>
<polygon fill="none" stroke="black" points="486.841,-174 486.841,-188 575.841,-188 575.841,-174 486.841,-174"/>
<text text-anchor="start" x="519.107" y="-178" font-family="Bitstream-Vera Sans" font-size="10.00">Label</text>
<polygon fill="none" stroke="black" points="486.841,-138 486.841,-174 575.841,-174 575.841,-138 486.841,-138"/>
<text text-anchor="start" x="488.841" y="-165.6" font-family="Bitstream-Vera Sans" font-size="8.00">+created_at : DateTime</text>
<text text-anchor="start" x="488.841" y="-157.6" font-family="Bitstream-Vera Sans" font-size="8.00">+updated_at : DateTime</text>
<text text-anchor="start" x="488.841" y="-149.6" font-family="Bitstream-Vera Sans" font-size="8.00">+id : Integer</text>
<text text-anchor="start" x="488.841" y="-141.6" font-family="Bitstream-Vera Sans" font-size="8.00">+label : String</text>
</g>
<!-- Device&#45;&gt;Label -->
<g id="edge20" class="edge"><title>Device&#45;&gt;Label</title>
<path fill="none" stroke="black" d="M524.299,-259.833C523.89,-240.784 524.083,-219.947 524.878,-202.583"/>
<polygon fill="black" stroke="black" points="525.45,-192.267 529.389,-202.501 525.173,-197.259 524.896,-202.252 524.896,-202.252 524.896,-202.252 525.173,-197.259 520.403,-202.002 525.45,-192.267 525.45,-192.267"/>
<text text-anchor="middle" x="511.927" y="-193.667" font-family="Bitstream-Vera Sans" font-size="7.00">+labels *</text>
</g>
<!-- Device&#45;&gt;Project -->
<g id="edge12" class="edge"><title>Device&#45;&gt;Project</title>
<path fill="none" stroke="black" d="M541.915,-374.29C552.415,-449.067 566.125,-579.197 570.072,-643.614"/>
<polygon fill="black" stroke="black" points="570.638,-653.74 565.587,-644.007 570.359,-648.748 570.08,-643.756 570.08,-643.756 570.08,-643.756 570.359,-648.748 574.573,-643.505 570.638,-653.74 570.638,-653.74"/>
<text text-anchor="middle" x="555.17" y="-648.14" font-family="Bitstream-Vera Sans" font-size="7.00">+project 1</text>
</g>
<!-- Device&#45;&gt;Region -->
<g id="edge15" class="edge"><title>Device&#45;&gt;Region</title>
<path fill="none" stroke="black" d="M562.399,-374.112C585.019,-419.95 614.351,-483.914 632.235,-526.58"/>
<polygon fill="black" stroke="black" points="636.088,-535.878 628.103,-528.363 634.174,-531.259 632.26,-526.64 632.26,-526.64 632.26,-526.64 634.174,-531.259 636.417,-524.917 636.088,-535.878 636.088,-535.878"/>
<text text-anchor="middle" x="621.396" y="-530.278" font-family="Bitstream-Vera Sans" font-size="7.00">+region 1</text>
</g>
<!-- Host&#45;&gt;AccessSecret -->
<g id="edge14" class="edge"><title>Host&#45;&gt;AccessSecret</title>
<path fill="none" stroke="black" d="M161.084,-224.197C148.248,-242.878 133.909,-262.891 121.437,-279.448"/>
<polygon fill="black" stroke="black" points="115.104,-287.766 117.582,-277.084 118.133,-283.788 121.162,-279.81 121.162,-279.81 121.162,-279.81 118.133,-283.788 124.742,-282.536 115.104,-287.766 115.104,-287.766"/>
<text text-anchor="middle" x="83.8787" y="-282.166" font-family="Bitstream-Vera Sans" font-size="7.00">+access_secret 0..1</text>
</g>
<!-- Label&#45;&gt;Device -->
<g id="edge7" class="edge"><title>Label&#45;&gt;Device</title>
<path fill="none" stroke="black" d="M536.231,-192.267C537.31,-208.482 537.747,-229.566 537.541,-249.723"/>
<polygon fill="black" stroke="black" points="537.382,-259.833 533.04,-249.763 537.461,-254.833 537.54,-249.834 537.54,-249.834 537.54,-249.834 537.461,-254.833 542.039,-249.905 537.382,-259.833 537.382,-259.833"/>
<text text-anchor="middle" x="521.136" y="-254.233" font-family="Bitstream-Vera Sans" font-size="7.00">+devices *</text>
</g>
<!-- LabelVariable -->
<g id="node8" class="node"><title>LabelVariable</title>
<polygon fill="none" stroke="black" points="486.841,-48 486.841,-62 575.841,-62 575.841,-48 486.841,-48"/>
<text text-anchor="start" x="501.131" y="-52" font-family="Bitstream-Vera Sans" font-size="10.00">LabelVariable</text>
<polygon fill="none" stroke="black" points="486.841,-4 486.841,-48 575.841,-48 575.841,-4 486.841,-4"/>
<text text-anchor="start" x="488.841" y="-39.6" font-family="Bitstream-Vera Sans" font-size="8.00">+created_at : DateTime</text>
<text text-anchor="start" x="488.841" y="-31.6" font-family="Bitstream-Vera Sans" font-size="8.00">+updated_at : DateTime</text>
<text text-anchor="start" x="488.841" y="-23.6" font-family="Bitstream-Vera Sans" font-size="8.00">+parent_id : Integer</text>
<text text-anchor="start" x="488.841" y="-15.6" font-family="Bitstream-Vera Sans" font-size="8.00">+key : String</text>
<text text-anchor="start" x="488.841" y="-7.6" font-family="Bitstream-Vera Sans" font-size="8.00">+value : JSONType</text>
</g>
<!-- Label&#45;&gt;LabelVariable -->
<g id="edge5" class="edge"><title>Label&#45;&gt;LabelVariable</title>
<path fill="none" stroke="black" d="M530.841,-133.91C530.841,-117.021 530.841,-95.1983 530.841,-76.2588"/>
<polygon fill="black" stroke="black" points="530.841,-66.083 535.341,-76.083 530.841,-71.083 530.841,-76.083 530.841,-76.083 530.841,-76.083 530.841,-71.083 526.341,-76.0831 530.841,-66.083 530.841,-66.083"/>
<text text-anchor="middle" x="510.509" y="-67.483" font-family="Bitstream-Vera Sans" font-size="7.00">+_variables *</text>
</g>
<!-- Project&#45;&gt;Cell -->
<g id="edge16" class="edge"><title>Project&#45;&gt;Cell</title>
<path fill="none" stroke="black" d="M517.243,-657.967C498.314,-647.016 477.597,-633.121 460.841,-618 422.159,-583.093 385.909,-536.055 362.741,-500.964"/>
<polygon fill="black" stroke="black" points="357.139,-492.314 366.352,-498.262 359.857,-496.511 362.575,-500.708 362.575,-500.708 362.575,-500.708 359.857,-496.511 358.798,-503.154 357.139,-492.314 357.139,-492.314"/>
<text text-anchor="middle" x="345.759" y="-493.714" font-family="Bitstream-Vera Sans" font-size="7.00">+cells *</text>
</g>
<!-- Project&#45;&gt;Device -->
<g id="edge13" class="edge"><title>Project&#45;&gt;Device</title>
<path fill="none" stroke="black" d="M562.987,-653.74C553.536,-596.313 538.919,-464.629 532.594,-384.305"/>
<polygon fill="black" stroke="black" points="531.829,-374.29 537.077,-383.918 532.209,-379.275 532.59,-384.261 532.59,-384.261 532.59,-384.261 532.209,-379.275 528.103,-384.604 531.829,-374.29 531.829,-374.29"/>
<text text-anchor="middle" x="515.583" y="-375.69" font-family="Bitstream-Vera Sans" font-size="7.00">+devices *</text>
</g>
<!-- Project&#45;&gt;Region -->
<g id="edge23" class="edge"><title>Project&#45;&gt;Region</title>
<path fill="none" stroke="black" d="M584.125,-653.906C591.297,-643.039 600.255,-630.337 609.193,-618.301"/>
<polygon fill="black" stroke="black" points="615.356,-610.104 612.943,-620.802 612.351,-614.101 609.346,-618.097 609.346,-618.097 609.346,-618.097 612.351,-614.101 605.75,-615.393 615.356,-610.104 615.356,-610.104"/>
<text text-anchor="middle" x="599.498" y="-611.504" font-family="Bitstream-Vera Sans" font-size="7.00">+regions *</text>
</g>
<!-- User -->
<g id="node12" class="node"><title>User</title>
<polygon fill="none" stroke="black" points="814.841,-600 814.841,-614 903.841,-614 903.841,-600 814.841,-600"/>
<text text-anchor="start" x="848.784" y="-604" font-family="Bitstream-Vera Sans" font-size="10.00">User</text>
<polygon fill="none" stroke="black" points="814.841,-532 814.841,-600 903.841,-600 903.841,-532 814.841,-532"/>
<text text-anchor="start" x="816.841" y="-591.6" font-family="Bitstream-Vera Sans" font-size="8.00">+created_at : DateTime</text>
<text text-anchor="start" x="816.841" y="-583.6" font-family="Bitstream-Vera Sans" font-size="8.00">+updated_at : DateTime</text>
<text text-anchor="start" x="816.841" y="-575.6" font-family="Bitstream-Vera Sans" font-size="8.00">+id : Integer</text>
<text text-anchor="start" x="816.841" y="-567.6" font-family="Bitstream-Vera Sans" font-size="8.00">+project_id : Integer</text>
<text text-anchor="start" x="816.841" y="-559.6" font-family="Bitstream-Vera Sans" font-size="8.00">+username : String</text>
<text text-anchor="start" x="816.841" y="-551.6" font-family="Bitstream-Vera Sans" font-size="8.00">+api_key : String</text>
<text text-anchor="start" x="816.841" y="-543.6" font-family="Bitstream-Vera Sans" font-size="8.00">+is_admin : Boolean</text>
<text text-anchor="start" x="816.841" y="-535.6" font-family="Bitstream-Vera Sans" font-size="8.00">+roles : JSONType</text>
</g>
<!-- Project&#45;&gt;User -->
<g id="edge18" class="edge"><title>Project&#45;&gt;User</title>
<path fill="none" stroke="black" d="M622.439,-660.275C671.547,-641.312 744.746,-613.964 796.707,-595.168"/>
<polygon fill="black" stroke="black" points="806.151,-591.762 798.271,-599.388 801.447,-593.459 796.744,-595.155 796.744,-595.155 796.744,-595.155 801.447,-593.459 795.217,-590.922 806.151,-591.762 806.151,-591.762"/>
<text text-anchor="middle" x="793.214" y="-593.162" font-family="Bitstream-Vera Sans" font-size="7.00">+users *</text>
</g>
<!-- Region&#45;&gt;Cell -->
<g id="edge4" class="edge"><title>Region&#45;&gt;Cell</title>
<path fill="none" stroke="black" d="M595.046,-553.513C540.96,-533.366 457.056,-500.972 399.852,-478.207"/>
<polygon fill="black" stroke="black" points="390.557,-474.499 401.513,-474.025 395.201,-476.352 399.845,-478.204 399.845,-478.204 399.845,-478.204 395.201,-476.352 398.177,-482.384 390.557,-474.499 390.557,-474.499"/>
<text text-anchor="middle" x="401.937" y="-475.899" font-family="Bitstream-Vera Sans" font-size="7.00">+cells *</text>
</g>
<!-- Region&#45;&gt;Device -->
<g id="edge10" class="edge"><title>Region&#45;&gt;Device</title>
<path fill="none" stroke="black" d="M626.315,-535.878C606.064,-496.33 576.105,-432.092 555.061,-383.591"/>
<polygon fill="black" stroke="black" points="550.976,-374.112 559.066,-381.515 552.955,-378.704 554.934,-383.296 554.934,-383.296 554.934,-383.296 552.955,-378.704 550.801,-385.077 550.976,-374.112 550.976,-374.112"/>
<text text-anchor="middle" x="567.222" y="-375.512" font-family="Bitstream-Vera Sans" font-size="7.00">+devices *</text>
</g>
<!-- Region&#45;&gt;Project -->
<g id="edge11" class="edge"><title>Region&#45;&gt;Project</title>
<path fill="none" stroke="black" d="M628.174,-610.104C620.332,-621.59 611.159,-634.293 602.409,-645.786"/>
<polygon fill="black" stroke="black" points="596.136,-653.906 598.688,-643.241 599.193,-649.949 602.249,-645.992 602.249,-645.992 602.249,-645.992 599.193,-649.949 605.811,-648.743 596.136,-653.906 596.136,-653.906"/>
<text text-anchor="middle" x="611.604" y="-648.306" font-family="Bitstream-Vera Sans" font-size="7.00">+project 1</text>
</g>
<!-- RegionVariable -->
<g id="node11" class="node"><title>RegionVariable</title>
<polygon fill="none" stroke="black" points="699.841,-466 699.841,-480 788.841,-480 788.841,-466 699.841,-466"/>
<text text-anchor="start" x="710.52" y="-470" font-family="Bitstream-Vera Sans" font-size="10.00">RegionVariable</text>
<polygon fill="none" stroke="black" points="699.841,-422 699.841,-466 788.841,-466 788.841,-422 699.841,-422"/>
<text text-anchor="start" x="701.841" y="-457.6" font-family="Bitstream-Vera Sans" font-size="8.00">+created_at : DateTime</text>
<text text-anchor="start" x="701.841" y="-449.6" font-family="Bitstream-Vera Sans" font-size="8.00">+updated_at : DateTime</text>
<text text-anchor="start" x="701.841" y="-441.6" font-family="Bitstream-Vera Sans" font-size="8.00">+parent_id : Integer</text>
<text text-anchor="start" x="701.841" y="-433.6" font-family="Bitstream-Vera Sans" font-size="8.00">+key : String</text>
<text text-anchor="start" x="701.841" y="-425.6" font-family="Bitstream-Vera Sans" font-size="8.00">+value : JSONType</text>
</g>
<!-- Region&#45;&gt;RegionVariable -->
<g id="edge6" class="edge"><title>Region&#45;&gt;RegionVariable</title>
<path fill="none" stroke="black" d="M676.644,-535.996C687.632,-522.261 700.227,-506.517 711.521,-492.4"/>
<polygon fill="black" stroke="black" points="717.998,-484.303 715.265,-494.923 714.874,-488.208 711.751,-492.112 711.751,-492.112 711.751,-492.112 714.874,-488.208 708.237,-489.301 717.998,-484.303 717.998,-484.303"/>
<text text-anchor="middle" x="697.666" y="-485.703" font-family="Bitstream-Vera Sans" font-size="7.00">+_variables *</text>
</g>
<!-- User&#45;&gt;Project -->
<g id="edge9" class="edge"><title>User&#45;&gt;Project</title>
<path fill="none" stroke="black" d="M805.98,-595.827C756.825,-614.803 683.699,-642.123 631.822,-660.887"/>
<polygon fill="black" stroke="black" points="622.393,-664.287 630.273,-656.661 627.096,-662.591 631.8,-660.895 631.8,-660.895 631.8,-660.895 627.096,-662.591 633.327,-665.128 622.393,-664.287 622.393,-664.287"/>
<text text-anchor="middle" x="637.861" y="-665.687" font-family="Bitstream-Vera Sans" font-size="7.00">+project 1</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 27 KiB