by Glynn Foster
Learn how to use the administrative interfaces provided by the Oracle Solaris Remote Administration Daemon to perform local and remote configuration of a mix of technologies, including Oracle Solaris Zones, the ZFS file system, and the Service Management Facility.
Introduction
One of the exciting technologies that has been included in Oracle Solaris 11 is the Remote Administration Daemon (also known as RAD). RAD provides a set of programmatic interfaces to allow administrators to manage Oracle Solaris 11 subsystems such as Oracle Solaris Zones, the ZFS file system, the Service Management Facility (SMF), and more. RAD is also intended for developers as a complete development framework for creating custom interfaces that manage subsystems.
Why RAD?
There’s no doubt that the rise of virtualization in the data center has led to increased agility and efficiency, allowing administrators to consolidate the physical real estate they need to manage and improve time to deployment for applications. At the same time, this has caused an explosion in easy-to-create virtual environments with little perceived cost. Unfortunately this increased use of virtualization has also led to increased management and administration costs.
As the next generation data center moves towards the cloud, it’s important to be able to curb these costs and manage environments effectively at scale through automation. And for this, tooling is hugely important. This is where RAD fits in—a programmatic interface to Oracle Solaris technologies that can be consumed directly by administrators and, crucially, third-party management applications.
What Is RAD?
RAD is an integrated technology included in Oracle Solaris 11 that exposes a set of programmatic interfaces through a set of modules in a unified way to Oracle Solaris technologies. The client-side language bindings currently supported are C, Java, Python, and REST APIs over HTTP. Administrators and developers can use these client bindings to connect locally or remotely to systems, to view, and to manage system configuration much like the existing Oracle Solaris command-line interfaces. SMF starts the RAD daemon running as the root user, with three service instances being responsible for managing connections to RAD.
- The svc:/system/rad:local service instance manages local connections through UNIX sockets.
- The svc:/system/rad:remote service instance manages secure remote connections using TLS. The ability to accept remote connections is disabled by default.
- Finally, the svc:/system/rad:local-http service instance provides access for local connections over HTTP.
When a request comes in and authentication has been established, the primary RAD daemon will spawn a slave daemon running at the same privilege level as the authenticated user. It then proxies the requests through to this slave daemon, executing the appropriate APIs with the authenticated user’s privileges and audit context. RAD supports authentication through PAM, getpeerucred(3C), and GSSAPI for environments that have been configured to use Kerberos.
All RAD APIs are versioned allowing clients to define which version they would like to interact with (and, thus, being able to simultaneously support newer interfaces without breaking older ones). An interface provides a definition of how a client can interact with a system through a set of exposed methods, attributes, and events using a well-defined namespace. The various Oracle Solaris subsystems implement these interfaces and usually map to common administrative tasks for that particular subsystem.
An interface may be shared across several technology areas where it makes sense. As we will see when we start to go through the examples below, it’s simply a case of connecting to RAD, obtaining references to interface instances (either by creating new references or looking up existing ones), and interacting with these references via their properties, methods, and events.
Some of the modules provided now in Oracle Solaris 11 are for SMF, Oracle Solaris Zones, Elastic Virtual Switch, Image Packaging System (IPS), user management, KStats, and ZFS. Further modules are expected in future Oracle Solaris releases.
Figure 1: A high-level architecture of RAD
Getting Started with RAD
In this article, we will explore how to consume RAD and the available RAD modules for Oracle Solaris technologies using a very simple set of examples. To start, we will examine a single example using each of the different available language bindings—Python, C, Java, and the REST API. Once completed, we will then continue to explore examples for other RAD modules using Python for convenience.
Example 1: Querying Oracle Solaris Zones
In our first example, we will use the Oracle Solaris Zones RAD module to query a system locally for zones. We will use the module to retrieve some basic information about the zone including its name, state, whether it’s set to automatically boot on system startup, whether it has any memory capping and, finally, what brand it is (either an Oracle Solaris non-global zone or an Oracle Solaris Kernel Zone).
Python Bindings
We will start with Python. The current Oracle Solaris RAD modules are using Python 2.6, so we need to make sure any scripts that we write include this version in the shell interpreter.
The first thing that we will need to do is to import two Python libraries: rad.connect to be able to set up the connection with the RAD daemon and com.oracle.solaris.rad.zonemgr_1 to provide access to a variety of classes that provide a set of interfaces to configure and administer Oracle Solaris Zones. All RAD modules currently are at version 1. We achieve this using the following code:
import rad.connect as radc
import rad.bindings.com.oracle.solaris.rad.zonemgr_1 as zonemgr
The RadConnection class offers a number of different ways to communicate with the RAD daemon. In our case, we will simply set up a connection over UNIX sockets. To do this, we will create a string that will hold our RAD URI, and then call the connect() method on it and store the returned RadConnection object. We will see in future examples how different URIs can be used to connect remotely to systems.
uri = radc.RadURI(“unix:///”)
rc = uri.connect()
Once we have successfully created a connection, we can use it to query the system. We will use our connection handle to list all objects that implement the Zone() interface as follows:
zones = rc.list_objects(zonemgr.Zone())
The list_objects() method will return us a list of object names of type ADRName (this corresponds to how RAD internally maps between Python and the interfaces that use the Abstract Data Representation [ADR] definition language). We can now loop through these names to get the underlying objects that are associated to those names:
for name in zones:
zone = rc.get\_object(name)
Finally, for each zone that we find, let’s query the configuration to see whether the zone is set to boot automatically, and whether it has any resource management associated with it. We will use the getResourceProperties() method to pull the autoboot property within the global resource scope using the Resource class. Within the zone configuration, resource scoping allows properties to be logically grouped together—you can see this when using zonecfg(1M) to add an automatic VNIC (anet), for example.
We will also use the getResources() method to determine whether the capped-memory resource has been declared on a given zone and set an appropriate variable; all Oracle Solaris Kernel Zones will have this resource by default.
autoboot_prop = zone.getResourceProperties(zonemgr.Resource('global'),
\['autoboot'\])
if len(zone.getResources(zonemgr.Resource('capped-memory'))) == 0:
capping\_prop = 'no'
else:
capping\_prop = 'yes'
Let’s quickly summarize our full Python script:
#!/usr/bin/python2.6
import rad.connect as radc
import rad.bindings.com.oracle.solaris.rad.zonemgr_1 as zonemgr
# Connect to RAD through local UNIX socket
uri = radc.RadURI("unix:///")
rc = uri.connect()
# List the Zone objects
zones = rc.list_objects(zonemgr.Zone())
# Print out our headings
print "%-16s %-11s %-11s %-10s %-6s" % ("NAME", "STATUS", "AUTOBOOT",
"CAPPED", "BRAND")
# Iterate over the Zone objects. Retrieve the autoboot property and
# check to see whether a capped-memory resource is set.
for name in zones:
zone = rc.get\_object(name)
autoboot\_prop = zone.getResourceProperties(zonemgr.Resource('global'),
\['autoboot'\])
if len(zone.getResources(zonemgr.Resource('capped-memory'))) == 0:
capping\_prop = 'no'
else:
capping\_prop = 'yes'
# Print out the results
print "%-16s %-11s %-11s %-10s %-6s" % (zone.name, zone.state,
autoboot\_prop\[0\].value,
capping\_prop, zone.brand)
# Close the RAD connection
rc.close()
As you’ll see, we’ve purposely ignored basic error handling for simplicity, but it’s important to add that for completeness. Once we have saved the script, we can go ahead and run it on the command line:
# ./zones.py
NAME STATUS AUTOBOOT CAPPED BRAND
zone1 running false no solaris
zone2 installed true no solaris
zone3 configured false yes solaris-kz
One handy tip is that the Python RAD bindings provide very helpful inline documentation if you prototype using IPython. For example, to view the docs for RAD connections, you might do the following:
# ipython-2.6
Python 2.6.8 (unknown, May 26 2015, 00:32:22) [C]
Type "copyright", "credits" or "license" for more information.
IPython 0.10 -- An enhanced Interactive Python.
? -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help -> Python's own help system.
object? -> Details about 'object'. ?object also works, ?? prints more.
In [1]: import rad.connect as radc
In [2]: help radc
Help on module rad.connect in rad:
NAME
rad.connect - connect - RAD connection oriented utilities
FILE
/usr/lib/python2.6/vendor-packages/rad/connect.py
MODULE DOCS
http://docs.python.org/library/rad.connect
DESCRIPTION
This module facilitates communication with RAD instances via a number of
alternative transports. The RadConnection class acts as the primary interface
and offers a connection to a RAD instance which may be used to:
- locate objects
- invoke object methods
- read/write object properties
- subscribe/unsubscribe to object events
CLASSES
\_\_builtin\_\_.object
ProcessPseudoSocket
RadConnection
RadEvent
RadURI
rad.client.RADStruct(rad.client.RADObject)
RadUpdateParameter
...
C Bindings
Unsurprisingly, writing a RAD client using the C bindings is a little more complex, but the same basics apply—connect to RAD, list the zone objects that RAD knows about, and iterate over the array that we get back. We have a little more work to do, particularly in terms of memory management, but essentially we get a similar result at the end.
#include <stdio.h>
#include <rad/adr.h>
#include <rad/radclient.h>
#include <rad/client/1/zonemgr.h>
int main(int argc, char *argv[])
{
rc\_uri\_t \*uri;
rc\_err\_t status;
int name\_count, i;
adr\_name\_t \*\*name\_list;
/\* Connect to RAD through local UNIX socket \*/
uri = rc\_alloc\_uri("unix:///", RCS\_UNIX);
rc\_conn\_t \*conn = rc\_connect\_uri(uri, RCC\_NONE);
rc\_free\_uri(uri);
/\* List the list of Zone ADR names and return result into an array \*/
zonemgr\_Zone\_\_rad\_list(conn, B\_TRUE, NS\_GLOB,
&name\_list, &name\_count, 0);
/\* Print out our headings \*/
printf("%-16s %-11s %-11s %-10s %-6s\\n", "NAME", "STATUS",
"AUTOBOOT", "CAPPED",
"BRAND");
/\* Iterate over the Zone objects. Retrieve the autoboot property and
\* check to see whether a capped-memory resource is set.
\*/
for (i = 0; i \< name\_count; i++) {
/\* Define Resource structures that can be used to look up
\* the configuration for the global and capped-memory
\* scope.
\*/
zonemgr\_Resource\_t global = { .zr\_type = "global"};
zonemgr\_Resource\_t capped = { .zr\_type = "capped-memory"};
zonemgr\_Property\_t \*result;
zonemgr\_Result\_t \*error;
rc\_instance\_t \*zone\_inst;
char \*zone\_name, \*zone\_brand, \*zone\_state;
const char \*autoboot\_prop, \*capped\_prop;
int property\_count, j;
/\* Lookup the instance from the ADR name \*/
rc\_lookup(conn, name\_list\[i\], NULL, B\_TRUE, &zone\_inst);
zonemgr\_Zone\_get\_name(zone\_inst, &zone\_name);
zonemgr\_Zone\_get\_brand(zone\_inst, &zone\_brand);
zonemgr\_Zone\_get\_state(zone\_inst, &zone\_state);
zonemgr\_Zone\_getResourceProperties(zone\_inst, &global, NULL,
0, &result, &property\_count,
&error);
/\* We need to iterate over all the properties to find autoboot \*/
for (j = 0; j \< property\_count; j++)
if (strncmp(result\[j\].zp\_name, "autoboot", strlen("autoboot")) == 0)
autoboot\_prop = strdup(result\[j\].zp\_value);
zonemgr\_Result\_free(error);
zonemgr\_Property\_array\_free(result, property\_count);
zonemgr\_Zone\_getResourceProperties(zone\_inst, &capped, NULL,
0, &result, &property\_count, &error);
/\* Check if there is a capped-memory resource \*/
if (error && (int) error->zr\_code == ZEC\_RESOURCE\_NOT\_FOUND)
capped\_prop = "no";
else
capped\_prop = "yes";
zonemgr\_Result\_free(error);
zonemgr\_Property\_array\_free(result, property\_count);
printf("%-16s %-11s %-11s %-10s %-6s\\n", zone\_name, zone\_state,
autoboot\_prop, capped\_prop,
zone\_brand);
free(zone\_name);
free(zone\_state);
free(zone\_brand);
}
rc\_disconnect(conn);
}
As in the Python example, there’s a lot more error checking that we should be doing; for example, checking to see if we made a valid RAD connection. However, we can now quickly compile this code and run it to verify that it’s returning the same information as before:
# cc -o zones zones.c -lradclient -ladr -lzonemgr_client \
-L /usr/lib/rad/client/c/ -R /usr/lib/rad/client/c/
# **export LD_LIBRARY_PATH=/usr/lib/rad/client/c/
**# **./zones
**
NAME STATUS AUTOBOOT CAPPED BRAND
zone1 running false no solaris
zone2 installed true no solaris
zone3 configured false yes solaris-kz
To use other RAD C client bindings, the best place to look is in the header files included in /usr/include/rad/client/1/. It also probably helps to look at the Python documentation to see the typical interaction that you should expect with these interfaces.
Java Bindings
Writing a Java client ends up being a little less cumbersome than using C, but again, it follows similar principles.
import java.util.*;
import java.io.IOException;
import com.oracle.solaris.rad.client.*;
import com.oracle.solaris.rad.connect.*;
import com.oracle.solaris.rad.zonemgr.*;
class RadClient
{
public static void main(String\[\] args) {
Connection conn;
URIConnection uri;
ZoneInfo zi;
try {
// First we open a RAD connection on a UNIX socket
uri = new URIConnection("unix:///");
conn = uri.connect(null);
// Print out our header
System.out.format("%-16s %-11s %-11s %-10s %-6s\\n", "NAME", "STATUS",
"AUTOBOOT", "CAPPED", "BRAND");
// Iterate over all the Zone objects we can find
for (ADRName name: conn.listObjects(new Zone())) {
Zone zone = conn.getObject(name);
String capped = "no";
String autoboot = "false";
// Create resource filters for the global and capped-memory scopes
Resource global\_filter = new Resource("global", null, null);
Resource capped\_filter = new Resource("capped-memory", null, null);
List\<Property> props = zone.getResourceProperties(global\_filter, null);
try {
List\<Property> capped\_props = zone.getResourceProperties(capped\_filter, null);
if (!capped\_props.isEmpty())
capped = "yes";
} catch (RadException e) {}
for (Property prop: props) {
if (prop.getName().equals("autoboot"))
autoboot = prop.getValue();
}
System.out.format("%-16s %-11s %-11s %-10s %-6s\\n", zone.getname(),
zone.getstate(),
autoboot, capped,
zone.getbrand());
}
} catch (IOException e) {}
}
}
As soon as we’ve written the Java code, we can compile it through to a Java class file using the javac compiler, as follows:
# export CLASSPATH=/usr/lib/rad/java/rad.jar:/usr/lib/rad/java/zonemgr.jar:/root
# javac RadClient.java
# java RadClient
NAME STATUS AUTOBOOT CAPPED BRAND
zone1 running false no solaris
zone2 installed true no solaris
zone3 configured false yes solaris-kz
To use other RAD Java client bindings, one way of looking to see what is available is by copying one of the JAR files locally, unpacking it, and checking the classes with javap, for example:
# cp /usr/lib/rad/java/rad.jar .
# /usr/jdk/instances/jdk1.7.0/bin/jar xf rad.jar
# cd com/oracle/solaris/rad/connect/
# /usr/jdk/instances/jdk1.7.0/bin/javap URIConnection.class
Compiled from "URIConnection.java"
public class com.oracle.solaris.rad.connect.URIConnection {
public static final java.lang.String SCHEME_UNIX;
public static final java.lang.String SCHEME_RAD;
public static final java.lang.String SCHEME_RADS;
public static final java.lang.String SCHEME_SSH;
public static final java.util.Set<java.lang.String> DEFAULT_SCHEMES;
public static final java.util.Set<java.lang.String> CREDENTIAL_CLASSES;
public com.oracle.solaris.rad.connect.URIConnection(java.lang.String) throws
java.io.IOException;
public com.oracle.solaris.rad.connect.URIConnection(java.lang.String,
java.util.Set<java.lang.String>) throws java.io.IOException;
public com.oracle.solaris.rad.connect.URIConnection(java.lang.String,
java.util.Set<java.lang.String>, java.util.Set<java.lang.String>) throws
java.io.IOException;
public void addCertFile(java.lang.String);
public void rmCertFile(java.lang.String);
public com.oracle.solaris.rad.connect.Connection
connect(com.oracle.solaris.rad.connect.Credentials) throws java.io.IOException;
public void processPAMAuth(com.oracle.solaris.rad.connect.PAMCredentials,
com.oracle.solaris.rad.connect.Connection) throws java.io.IOException;
public java.lang.String getAuth();
public java.lang.String getCredClass();
public void setCredClass(java.lang.String) throws java.io.IOException;
public java.lang.String getHost();
public java.lang.String getPath();
public int getPort();
public java.lang.String getSrc();
public java.lang.String getScheme();
public java.util.Set<java.lang.String> getSchemes();
public java.lang.String getUser();
public java.lang.String toString();
static {};
}
This details all the methods associated for a particular class.
REST Bindings
From Oracle Solaris 11.3 onwards, a new REST interface to RAD has been added. REST APIs are an increasingly popular way of interacting with system services across the network over both HTTP and HTTPS using an encoding payload such as JSON or XML. Oracle Solaris 11.3 includes a new SMF service instance, svc:/system/rad:local-http, that is responsible for facilitating RAD communication from HTTP clients. Given that HTTP connections are not encrypted, the default configuration allows only for accepting connections from a local host on a UNIX socket. Administrators can choose to accept connections over a public port if desired, and secure transport is expected to be provided in a future release.
A RESTful architecture uses resources, identified by a URI, as the primary interface through which to manipulate data, or it uses call methods to operate on that data. Resources can be accessed individually or as a collection of member resources. The RAD REST functionality supports HTTP GET, POST, PUT, and DELETE requests.
As before, we’ll first need to set up the RAD connection. For this, we’ll send a POST request to the /api/com.oracle.solaris.rad.authentication/1.0/Session API and we’ll also provide some credentials included in a JSON file, as follows:
{
"username": "root",
"password": "solaris11",
"scheme": "pam",
"preserve": true,
"timeout": -1
}
From the code above, you can see that we’ve provided both a username and a password, that we want to authenticate using PAM over a UNIX socket located at /system/volatile/rad/radsocket-http, and that we’d like to preserve this connection and reconnect to it later (using a default timeout of 60 minutes, which is indicated by the -1 argument). Once we have saved this code in a file called body.json, we can make the POST request using curl, saving the authentication token to a file called cookie.txt:
# curl -H "Content-type: application/json" -X POST --data-binary @body.json \
localhost/api/com.oracle.solaris.rad.authentication/1.0/Session \
--unix-socket /system/volatile/rad/radsocket-http -v -c cookie.txt -b cookie.txt
* Trying /system/volatile/rad/radsocket-http...
* Connected to localhost (/system/volatile/rad/radsocket-http) port 80 (#0)
> POST /api/com.oracle.solaris.rad.authentication/1.0/Session HTTP/1.1
> User-Agent: curl/7.40.0
> Host: localhost
> Accept: */*
> Cookie: _rad_instance=1792; _rad_token=0f96e2ed-af68-47a7-91aa-9fe80239c661
> Content-type: application/json
> Content-Length: 103
>
* upload completely sent off: 103 out of 103 bytes
< HTTP/1.1 201 Created
< Connection: Keep-Alive
< Content-Length: 164
< Expires: 0
< Pragma: no-cache
< Cache-Control: no-cache, no-store, must-revalidate
< Location: /api/com.oracle.solaris.rad.authentication/1.0/Session/_rad_reference/2048
* Replaced cookie _rad_instance="2048" for domain localhost, path /api, expire 1435280743
< Set-Cookie: _rad_instance=2048; Path=/api; Max-Age=3600; HttpOnly
* Replaced cookie _rad_token="d5009d3f-35d5-45d1-919a-bcacc468da84" for domain localhost, path /api, expire 1435280743
< Set-Cookie: _rad_token=d5009d3f-35d5-45d1-919a-bcacc468da84; Path=/api; Max-Age=3600; HttpOnly
< Date: Fri, 26 Jun 2015 00:05:43 GMT
<
{
"status": "success",
"payload": {
"href":
"/api/com.oracle.solaris.rad.authentication/1.0/Session/_rad_reference/2048"
}
* Connection #0 to host localhost left intact
# cat cookie.txt
# Netscape HTTP Cookie File
# http://curl.haxx.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
#HttpOnly_localhost FALSE /api FALSE 1435280743 _rad_instance 2048
#HttpOnly_localhost FALSE /api FALSE 1435280743 _rad_token d5009d3f-35d5-45d1-919a-bcacc468da84
Now that we have successfully obtained the token, we can go ahead and start using the zone RAD module. For this we’ll use the /api/com.oracle.solaris.rad.zonemgr/1.0/Zone API. Let’s first do a GET request, using our cookie, and see what we get back:
# curl -H "Content-type: application/json" -X GET \
localhost/api/com.oracle.solaris.rad.zonemgr/1.0/Zone \
--unix-socket /system/volatile/rad/radsocket-http -b cookie.txt
{
"status": "success",
"payload": \[
{
"href": "api/com.oracle.solaris.rad.zonemgr/1.2/Zone/zone1"
},
{
"href": "api/com.oracle.solaris.rad.zonemgr/1.2/Zone/zone2"
},
{
"href": "api/com.oracle.solaris.rad.zonemgr/1.2/Zone/zone3"
}
\]
}
The resulting payload returned is very similar to the previous examples. However, instead of returning a list of ADR names for the zone instances it has found, we get back a URI instead. We can now use this URIs to reference individual zones. Let’s look at the properties of zone1, as follows:
# curl -H "Content-type: application/json" -X GET \
localhost/api/com.oracle.solaris.rad.zonemgr/1.2/Zone/zone1?_rad_detail=true \
--unix-socket /system/volatile/rad/radsocket-http -b cookie.txt
{
"status": "success",
"payload": {
"href": "api/com.oracle.solaris.rad.zonemgr/1.2/Zone/zone1",
"Zone": {
"auxstate": \[\],
"brand": "solaris",
"id": 2,
"uuid": "321b2c4f-ca4f-4b93-a0e0-e183b86f982f",
"name": "zone1",
"state": "running"
}
}
}
From the code above, you can see we’ve taken the URI provided and also added a query parameter, _rad_detail, to provide more information about this zone. We can see we’ve identified the zone name, brand, and state.
To look at more properties of the zone, we will need to call a method on this interface. This is not considered a traditional RESTful operation, but many REST-based APIs use method invocation. To use method calls, we will need to include _rad_method within the URI to distinguish between the RAD instance and the method name. We will also provide empty data using the following JSON file, meaning that we would like all properties of the zone to be returned:
{
}
The following lists all the properties of the zone:
# curl -H "Content-type: application/json" -X PUT --data @zones.json \
localhost/api/com.oracle.solaris.rad.zonemgr/1.2/Zone/zone1/_rad_method/get