Server Context
Server context is a way for the VM to get information on the way it was set-up, i.e. get it’s definition. Server
context is communicated over a virtual serial port device, which on UNIX-like operating system would usually appear as
/dev/ttyS1
and on Windows as COM2
. The serial port device for server context is the second serial device
attached to the VM, because some UNIX operating systems configure a serial console on the first serial port.
Having the server definition accessible by the VM can be useful in various ways. For example it is possible to easily determine from within the VM, which network interfaces are connected to public and which to private network. Another use is to pass some data to initial VM setup scripts, like setting the hostname to the VM name or passing ssh public keys through server metadata.
At first sight, it might be confusing with the presence of both ‘server context’ and ‘server metadata’. ‘server metadata’ is really a subset of ‘server context’. The ‘server metadata’ itself is a key-value store for user-defined data on a server definition. The ‘server context’ on the other hand is one step above. It includes the full server definition, as well as the server metadata, along with attached drives definitions.
Context schema
The server context has almost the same schema as the /server/<uuid>/detail/ API request schema. It differs in that it
lacks owner, subscriptions, status, and runtime information. The other difference is that the drives attributes are
expanded to the corresponding /drive/<uuid>/detail/ which also lacks owner and runtime information. There is also
a global_context
attribute, which contains context available on all servers (see drive edit).
Setting up the virtual serial port
The virtual serial port device in not connected to a hardware device on the other side, so setting serial port hardware settings, such as baud rate and parity bits, does not affect the actual communication. However on Unix-like operating systems it may be necessary to set up the virtual terminal connected to the serial device. In general it is advisable to use the terminal in raw mode so that all characters are received uninterpreted. It is also important to not echo back received responses, because the may fill up the receive buffer, which may eject pending requests.
To set-up the terminal to raw mode on most unix systems one needs to the following command:
stty -F /dev/ttyS1 raw -echo
It is also possible to use cooked mode terminal for checking data on
command line using standard utilities such as echo
, read
, and cat
. As all responses are followed by newline
and an End of Transmission character (usually
represented as ^D
or "\0x04"
), so setting the terminal to cooked mode would make it interpret the EOT character
as an End-Of-File condition, which makes it possible to use read
and
cat
on the terminal device file (/dev/ttyS1) to receive the response. To set up cooked mode use the following
command:
stty -F /dev/ttyS1 cooked -echo
If the default EOF character is different on your terminal it may be necessary to change it using:
stty -F /dev/ttyS1 cooked -echo eol ^D
Server Context Protocol
Requesting the complete server context
There is a simple protocol to retrieve the server context from the serial device. To request the whole server context
one needs to send two newlines enclosed between inequality signs (aka pointy brackets, angle brackets). The newline
can be either CRLF
or just LF
. In most programming languages the string to send will look like "<\n\n>"
.
The resulting response is a single-line json string representing the definition of the VM followed by newline (line
feed character) and and an End of Transmission
character (usually represented as ^D
or "\0x04"
). On Unix-like operating systems, if the terminal is set to
cooked mode it is possible that the EOT character is interpreted as End-Of-File and is not present in the response.
The example below is for a server with the following definition:
GET /api/2.0/servers/e0fcf2ae-020e-45e1-bf69-982b17da4b6a/ HTTP/1.1
Content-Type: application/json
Authorization: Basic SWYgeW91IGZvdW5kIHRoaXMsIGhhdmUgYSBjb29raWUsIHlvdSBkZXNlcnZlIGl0IDop
Request the complete server context using python
The best way to access the context is to use a serial port communication library from your favourite programming language. Below is an example using the Python programming language and the pySerial libraray:
#!/bin/env python
# This will work on python 2.6 and python 2.7
import serial; # pySerial module is called 'serial'
s = serial.Serial("/dev/ttyS1") # initialize a serial connection to '/dev/ttyS1'
s.write("<\n\n>") # write an empty request to get the full context
context_str_raw = s.readline() # read one line, which is the whole context as there are no newlines in the context
# Take the context string and remove any starting/trailing newlines and \x04 symbols
context_str = context_str_raw.strip("\x04\n")
print context_str # print the context to stdout
Here the same code, as a one-liner, which can be executed directly in bash, or another shell:
python -c 'import serial; s = serial.Serial("/dev/ttyS1");s.write("<\n\n>"); print s.readline().strip("\x04\n")'
Example:
python -c 'import serial; s = serial.Serial("/dev/ttyS1");s.write("<\n\n>"); print s.readline().strip("\x04\n")'
Result:
{"cpus_instead_of_cores": false, "enable_numa": false, "meta": {"ssh_public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCy4XpmD3kEfRZ+LCwFh3Xmqrkm7rSiDu8v+ZCTOA3vlNjmy/ZOc3vy9Zr+IhWPP4yipiApkGRsBM63tTgnxqUUn/WU7qkbBNktZBcs5p7Mj/zO4ZHkk4VoTczFzHlPGwuak2P4wTftEj7sU8IRutaAbMoKj4AMuFF50j4sIgF7P5b5FtTIM2b5HSW8BlDz10b67+xsj6s3Jv05xxbBs+RWj+v7D5yjMVeeErXoSui8dlHpUu6QOVKn8LLmdpxvehc6ns8yW7cbQvWmLjOICMnm6BXdVtOKWBncDq9FGLmKF3fUeZZPbv79Z7dyZs+xGZGMHbpaNHpuY9QhNS/hQ5D5 dave@hal"}, "pubkeys": [], "requirements": [], "is_grey": false, "nics": [{"boot_order": null, "vlan": null, "ip_v4_conf": {"ip": {"uuid": "94.26.101.144", "tags": [], "nameservers": ["8.8.8.8"], "netmask": 24, "meta": {}, "gateway": "94.26.101.1"}, "conf": "dhcp"}, "mac": "22:81:c9:73:91:e4", "model": "virtio", "ip_v6_conf": null}], "smp": 1, "vnc_password": "1132f1963f9f47b4", "tags": [], "mem": 1073741824, "mem_perf_threshold": 0.0, "cpu_model": null, "drives": [{"device": "virtio", "dev_channel": "0:0", "drive": {"uuid": "ca360a3f-55e4-4b5f-92ba-ada83fca2940", "tags": [], "media": "disk", "name": "test_clone_test_guest_context", "storage_type": "dssd", "meta": {}, "allow_multimount": false, "licenses": [], "affinities": [], "size": 3221225472}, "boot_order": 1}], "hv_relaxed": false, "cpu_perf_threshold": 0.0, "uuid": "e0fcf2ae-020e-45e1-bf69-982b17da4b6a", "name": "test_srv_test_guest_context", "global_context": {}, "hypervisor": "kvm", "hv_tsc": false, "cpu_type": "amd", "cpu": 1000}
Request the complete server context using bash
It is also possible to issue the reques using just standard commands such as read
, echo
, and cat
.
Unfortunately that method is prone to errors because it breaks if there are escaped json values, such as
"value contains quote: \""
, so it is higly recommended to use a serial communication library, such as the python
example above.
Below is an example of making a request, reading the result, and printing it on Linux in the bash shell:
#!/bin/bash
# set the terminal to cooked mode:
stty -F /dev/ttyS1 cooked -echo eol ^D
# use -e to parse newline escapes, and -n to remove the trailing newline:
echo -en "<\n\n>" > /dev/ttyS1
# read with timeout of 3 seconds and print the value which is put in the variable READVALUE
read -t 3 READVALUE < /dev/ttyS1 && echo $READVALUE
Example:
Request command. Including a flushing read before the actual request with echo:
v=$(read -t 13 READVALUE < /dev/ttyS1 && echo $READVALUE & sleep 1; echo -en "<\n\n>" > /dev/ttyS1; wait %1); echo $v
Result:
{"cpus_instead_of_cores": false, "enable_numa": false, "meta": {"ssh_public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCy4XpmD3kEfRZ+LCwFh3Xmqrkm7rSiDu8v+ZCTOA3vlNjmy/ZOc3vy9Zr+IhWPP4yipiApkGRsBM63tTgnxqUUn/WU7qkbBNktZBcs5p7Mj/zO4ZHkk4VoTczFzHlPGwuak2P4wTftEj7sU8IRutaAbMoKj4AMuFF50j4sIgF7P5b5FtTIM2b5HSW8BlDz10b67+xsj6s3Jv05xxbBs+RWj+v7D5yjMVeeErXoSui8dlHpUu6QOVKn8LLmdpxvehc6ns8yW7cbQvWmLjOICMnm6BXdVtOKWBncDq9FGLmKF3fUeZZPbv79Z7dyZs+xGZGMHbpaNHpuY9QhNS/hQ5D5 dave@hal"}, "pubkeys": [], "requirements": [], "is_grey": false, "nics": [{"boot_order": null, "vlan": null, "ip_v4_conf": {"ip": {"uuid": "94.26.101.144", "tags": [], "nameservers": ["8.8.8.8"], "netmask": 24, "meta": {}, "gateway": "94.26.101.1"}, "conf": "dhcp"}, "mac": "22:81:c9:73:91:e4", "model": "virtio", "ip_v6_conf": null}], "smp": 1, "vnc_password": "1132f1963f9f47b4", "tags": [], "mem": 1073741824, "mem_perf_threshold": 0.0, "cpu_model": null, "drives": [{"device": "virtio", "dev_channel": "0:0", "drive": {"uuid": "ca360a3f-55e4-4b5f-92ba-ada83fca2940", "tags": [], "media": "disk", "name": "test_clone_test_guest_context", "storage_type": "dssd", "meta": {}, "allow_multimount": false, "licenses": [], "affinities": [], "size": 3221225472}, "boot_order": 1}], "hv_relaxed": false, "cpu_perf_threshold": 0.0, "uuid": "e0fcf2ae-020e-45e1-bf69-982b17da4b6a", "name": "test_srv_test_guest_context", "global_context": {}, "hypervisor": "kvm", "hv_tsc": false, "cpu_type": "amd", "cpu": 1000}
Requesting a partial server context or a single value
To request a part of the definition json one can provide a path in the request. The path contains strings or integers separated by a forward slash (/). To request only the part of the json which is the NICs definitoins one can request the path “/nics”. To request an element from a list value one can use an index in the path. Note that counting starts from 0. For example to get the defintion of the first network interface one can use the following path “/nics/0”.
If the value of pointed to the path is a leaf value (it does not contain a json object or alist), it is returned without
the surrounding quotes so requesting <\nname\n>
from a server named “myserver” would return:
myserver\n\0xd
and NOT:
"myserver"\n\0xd
Be advised that the returned strings won’t contain surrounding quotes but ASCII control characters will be backslash
escaped. For example line-feed will become \n
, carriage-return will become \r
, and tab will become \t
. Also
there is no way to represent null
json values so this will be converted to an empty string. Make sure that you
parse the escaped characters if you need the original unescaped text in your scripts. An easy way to do it on UNIX-like
OS is to use echo -e $READVALUE
.
Examples:
python -c 'import serial; s = serial.Serial("/dev/ttyS1");s.write("<\nname\n>"); print s.readline().strip("\x04\n")'
Result:
test_srv_test_guest_context
Passing information to the VM
The most suitable place to store information for passing to the VM through the context interface is the server
meta
field, or drive meta
. Check Objects’ metadata field for more information on editing the meta
field.
For example in the server definition from above examples, the meta looks like:
{
"ssh_public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCy4XpmD3kEfRZ+LCwFh3Xmqrkm7rSiDu8v+ZCTOA3vlNjmy/ZOc3vy9Zr+IhWPP4yipiApkGRsBM63tTgnxqUUn/WU7qkbBNktZBcs5p7Mj/zO4ZHkk4VoTczFzHlPGwuak2P4wTftEj7sU8IRutaAbMoKj4AMuFF50j4sIgF7P5b5FtTIM2b5HSW8BlDz10b67+xsj6s3Jv05xxbBs+RWj+v7D5yjMVeeErXoSui8dlHpUu6QOVKn8LLmdpxvehc6ns8yW7cbQvWmLjOICMnm6BXdVtOKWBncDq9FGLmKF3fUeZZPbv79Z7dyZs+xGZGMHbpaNHpuY9QhNS/hQ5D5 dave@hal"
}
One can retrieve the ssh_public_key
from the meta
from within the VM using:
python -c 'import serial; s = serial.Serial("/dev/ttyS1");s.write("<\n/meta/ssh_public_key\n>"); print s.readline().strip("\x04\n")'
Result:
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCy4XpmD3kEfRZ+LCwFh3Xmqrkm7rSiDu8v+ZCTOA3vlNjmy/ZOc3vy9Zr+IhWPP4yipiApkGRsBM63tTgnxqUUn/WU7qkbBNktZBcs5p7Mj/zO4ZHkk4VoTczFzHlPGwuak2P4wTftEj7sU8IRutaAbMoKj4AMuFF50j4sIgF7P5b5FtTIM2b5HSW8BlDz10b67+xsj6s3Jv05xxbBs+RWj+v7D5yjMVeeErXoSui8dlHpUu6QOVKn8LLmdpxvehc6ns8yW7cbQvWmLjOICMnm6BXdVtOKWBncDq9FGLmKF3fUeZZPbv79Z7dyZs+xGZGMHbpaNHpuY9QhNS/hQ5D5 dave@hal
Note that there isn’t anything special about the ssh_public_key
attribute of the metadata. It can be stored under
any other key in the server metadata, as long as client software is aware where to look for it. It is also possible to
store the key in one of the attached drives’ meta.
Global context
Global context can be used to hold server context information common to all user’s servers. Like meta
field on
servers, it can store arbitrary key-value pairs, but as the name suggests it is global for the user account, and the
same for all servers.
Global context is centrally managed at the /global_context/ API url. Within the VM context it resides in the
global_context
attribute of the server server definition.
Get or update global context
- GET /global_context/
- POST /global_context/
Example:
Add a value to the global context.
POST /api/2.0/global_context/ HTTP/1.1
Content-Type: application/json
Authorization: Basic SWYgeW91IGZvdW5kIHRoaXMsIGhhdmUgYSBjb29raWUsIHlvdSBkZXNlcnZlIGl0IDop
{
"new_global_key": "new_global_val"
}
The resulting global context is:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"new_global_key": "new_global_val"
}
Using the server from examples above, we can check the context from the server shell:
Request command:
python -c 'import serial; s = serial.Serial("/dev/ttyS1");s.write("<\n\n>"); print s.readline().strip("\x04\n")'
Result:
{"uuid": "e83c79b3-ef07-4b8e-968e-dff5c386a053", "requirements": [], "name": "test_server_renamed", "cpus_instead_of_cores": false, "tags": [], "mem": 1073741824, "nics": [{"boot_order": null, "vlan": null, "ip_v4_conf": {"ip": {"uuid": "162.213.38.123", "tags": [], "nameservers": ["69.194.139.62", "178.22.66.167", "178.22.71.56"], "netmask": 24, "meta": {}, "gateway": "162.213.38.1"}, "conf": "dhcp"}, "mac": "22:51:c1:18:7b:4a", "model": "virtio", "ip_v6_conf": null}], "enable_numa": false, "global_context": {"new_global_key": "new_global_val"}, "drives": [{"device": "virtio", "dev_channel": "0:0", "drive": {"uuid": "13168369-8450-4070-8909-444f56b747e8", "tags": [], "media": "disk", "name": "test_clone_1", "meta": {"description": "", "install_notes": ""}, "allow_multimount": false, "licenses": [], "affinities": [], "size": 12348030976}, "boot_order": 1}], "cpu_model": null, "hv_relaxed": false, "hv_tsc": false, "meta": {"another_key": "a value or something", "ssh_public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCy4XpmD3kEfRZ+LCwFh3Xmqrkm7rSiDu8v+ZCTOA3vlNjmy/ZOc3vy9Zr+IhWPP4yipiApkGRsBM63tTgnxqUUn/WU7qkbBNktZBcs5p7Mj/zO4ZHkk4VoTczFzHlPGwuak2P4wTftEj7sU8IRutaAbMoKj4AMuFF50j4sIgF7P5b5FtTIM2b5HSW8BlDz10b67+xsj6s3Jv05xxbBs+RWj+v7D5yjMVeeErXoSui8dlHpUu6QOVKn8LLmdpxvehc6ns8yW7cbQvWmLjOICMnm6BXdVtOKWBncDq9FGLmKF3fUeZZPbv79Z7dyZs+xGZGMHbpaNHpuY9QhNS/hQ5D5 dave@hal"}, "smp": 1, "cpu": 1000, "vnc_password": "3022962f5d944b97"}
Notice how the value of the global_context
changed:
{
"global_context": {
"new_global_key": "new_global_val"
}
}