Forum Stats

  • 3,875,125 Users
  • 2,266,808 Discussions
  • 7,912,088 Comments

Discussions

Getting Started with the Remote Administration Daemon on Oracle Solaris 11

steph-choyer-Oracle
steph-choyer-Oracle Member Posts: 101 Red Ribbon
edited Dec 1, 2016 4:10AM in Solaris 11

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.

image001.png

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