Naming
The Vanadium naming system enables discovery of devices, regardless of their physical locations - with or without an internet connection.
Object names
Vanadium names, usually abbreviated to "names" refer to objects.
Objects implement RPC methods. In other words, methods are invoked on object names. The basic primitive is thus:
name.method(args) -> results
For example, if the name /host:8080/a/y/foo.jpg
represents a JPEG file, then
/host:8080/a/y/foo.jpg.Get()
will return the contents of that file.
Object names are hierarchical consisting of components separated by slashes (/). Glob patterns, as on Unix, are restricted to matching name components.
Object names are resolved to obtain an object address. The underlying RPC protocol uses object addresses to establish communication with the process containing the named object prior to invoking methods on the object. The service that implements name resolution consists of servers, called mount tables, and a client library called the namespace library.
Mount tables and namespaces
Mount tables are similar to DNS servers and the namespace library to the
DNS resolver. Similar to DNS and the Unix filesystem, mount
tables may be arranged in layered hierarchies. The resolution process
implemented by the namespace library may iteratively communicate with multiple
mount tables to resolve a single name. This is illustrated in the diagram
below, which shows 6 mount tables. ns1.v.io:8101
is the root and
mount table a
, mount table b
and mount table c
are mounted in it as a
,
b
, c
. Similarly, mount table y
and mount table z
are mounted in
mount table a
as y
and z
. To resolve the name a/y
, the mount tables on
ns1.v.io:8101
and mount table a
must be consulted. To resolve the name
a/y/foo/bar
, mount table a/y
must also be consulted, since it serves
mounts made below y
.
The first element of a name that begins with /
points to the mount table at
which to begin the resolution. For example, the name /ns1.v.io:8101/a
starts
resolution with the mount table ns1.v.io:8101
. These names are called rooted
because they need no additional state to perform the resolution, they stand by
themselves.
Names that don't begin with a /
begin the resolution at a default (or
current) mount table set in the process' namespace library. We
call those names relative because they need the state of the namespace
library to determine how they are resolved.
This is illustrated below, where process Client 1
has a namespace
that is relative to ns1.v.io:8101
, its root, and hence can resolve
a
, b
, c
, a/y
, and a/z
.
In contrast, process Client 2
with ns2.v.io:8102
as its root can
resolve only y
and z
.
The advantage of using a relative namespace is that it can define a context. For example, for debugging or testing one would set up the namespace to contain the emulated environment. It is similarly possible to provide context of nearby devices, or one local to a single machine.
Rooted names can be used to specify an arbitrary server where name resolution
will begin, thus overriding the root of the namespace asked to perform the
resolution. This would allow Client 2
to refer to a
as /ns1.v.io:8101/a
.
The element immediately after the leading slash must be an address, either in
dnsname:port
, ipv6:port
, or ipv4:port
format, or in the Vanadium
endpoint format that encodes more detailed information about the
server supporting the object (such as protocol versions or a globally unique
id). In all cases they provide a starting point in the forest of
namespaces. The object addressed by that element can be another mount table or
the terminal server.
In essence, both rooted and relative names are the same. They represent a walk through a namespace that consists of a directed cyclic graph of mount tables ending in a server of the object we're trying to get at. The only difference with relative names is that we're assuming a "current directory" to start from as opposed to providing it in the name.
User-supplied server code that implements RPCs also appears as names in mount
tables. In the diagram above Server 1
and Server 2
provide a server called
srv
that can be accessed as /ns1.v.io:8101/srv
, which will
resolve to the server hosted by Server 1
and /ns1.v.io:8101/a/srv
,
which will resolve to the server hosted by Server 2
. The mount table at
ns1.v.io:8101
contains the entry for Server 1
(as srv
) and the
mount table at mount table a
contains the entry for
Server 2
(also called srv
). Invoking /ns1.v.io:8101/srv.Get()
will result in Server 1
serving that method, whereas
/ns1.v.io:8101/a/srv.Get()
will be served by Server 2
.
The name resolution process is iterative. For Client 1
to resolve a/y/bar
,
it will first ask ns1.v.io:8101
to resolve a/y/bar
, root
will reply with mount table a
's address and y/bar
as the "unresolved"
portion of the name. Client 1
will then ask mount table a
to resolve
y/bar
, and so on.
If the resolution reaches a leaf server (i.e. one that isn't
a mount table such as Serve 1
or Server 2
) and there
are still remaining components to the name, those
"unresolved" components will be passed to the leaf server along with the operation.
Thus the call /ns1.v.io:8101/srv/foo/bar.Get()
will result in Server 1
receiving
the RPC foo/bar.Get()
.
It is possible to create cycles by creating mount tables that mount themselves through other mount tables. Cycles are handled by limiting the number of iterations the resolution algorithm will execute. That is, regardless of whether cycles are present, the resolution algorithm will bound the number of iterations it executes.
Mount entries
Our examples above have shown mount tables pointing to other mount tables or to leaf servers but we haven't said what those pointers are. They are a set of equivalent rooted names. They either specify different addresses for the same server reachable via different networks (for example IP, IPv6, and Bluetooth), or they specify addresses for servers that are themselves equivalent either because they are stateless or because they transparently synchronize their state. Thus, we can send an RPC to whichever one is available and that we can reach.
Normally these rooted names point to the root of the server's namespace and that
is what we have shown in our examples. However, you can also mount objects
further inside the server's namespace. In the example above, we could have
mounted Server 1
s /foo/bar
onto the name /ns1.v.io:8101/quux
. In that
case both /ns1.v.io:8101/srv/foo/bar
and /ns1.v.io:8101/quux
would resolve
to the same object. You can use this to create aliases or nicknames for objects,
or to create namespaces that represent some context (like all left handed green
eyed poker players) without creating a specific server to provice such a
grouping.