cancel
Showing results for 
Search instead for 
Did you mean: 

Pulse Secure vADC

Sort by:
This article discusses how to use Stingray's RESTful Control API with TrafficScript, Stingray's scripting language.  The RESTful API can be accessed in a TrafficScript rule using the http.request.get(), http.request.put() and http.request.delete() functions, but to make programming easier I have taken advantage of the support in TrafficScript for subroutine libraries to create a TrafficScript REST client that contains subroutines that be can be used by TrafficScript rules and that abstract many of the RESTful API programming details.   Resources   The RESTful API gives you access to the Stingray Configuration, presented in the form of resources.  The format of the data exchanged using the Stingray RESTful API will depend on the type of resource being accessed:   Data for Configuration Resources, such as Virtual Servers and Pools are exchanged in JSON format using the MIME type “application/json”, so when getting data on a resource with a GET request the data will be returned in JSON format and must be deserialized or decoded into a TrafficScript data structure.  When adding or changing a resource with a PUT request the data must be serialized or encoded from a TrafficScript data structure into JSON format. Files, such as rules and those in the extra directory are exchanged in raw format using the MIME type “application/octet-stream”.   Working with JSON and TrafficScript   TrafficScript provides functions for JSON serializing and deserializing.  To take a TrafficScript data structure and serialize it into JSON format, use json.serialize() and to deserialize a JSON formatted string into a TrafficScript structure, use json.deserialize().   Working with the RESTful API and TrafficScript   To make the programming easier, the program examples that follow utilize the stmrestclient REST client. To use stmrestclient in a TrafficScript rule you include it with the import statement:   import stmrestclient;   The hostname of the STM to access, the base URI path and the userid and password are defined in stmrestclient and can be changed there if needed.  The default is for the hostname to be the same as the STM on which the rule is running and the userid and password are both "admin".  There are three subroutines for use in rules:   stmRestGet(resourcepath, type) stmRestPut(resourcepath, type, data) stmRestDelete(resourcepath)   resourcepath identifies the part of the URI path to the resource that needs to be added to the base URI path.  The base URI path is "/api/tm/1.0/config/active/". So for example, to get a list of pools, resourcepath would be set to "pools" or to work with the pool "testpool", resourcepath would be set to "pools/testpool", resulting in a URI path of "/api/tm/1.0/config/active/pools" and "/api/tm/1.0/config/active/pools/testpool" respectively.   type identifies the format of the data and should be set to "json", "file" or "any".  These will be translated to “application/json”, “application/octet-stream” and “application/json, application/octet-stream” respectively.   data should be a TrafficScript hash for configurations resources or a string for files.   Each subroutine returns the following hash:   rc: Return code.  1=OK, 0=Error info: "OK" if OK, and an error message if there was an error data: The data from the response.  A hash containing one element, "children" for lists or "properties" for configuration data. For files it is the file. content-type: The Content-Type header of the response, either “application/json” or “application/octet-stream” status: The HTTP response code   Data Structures   Responses that return a JSON formatted string will be deseriailized by the stmrestclient subroutines into a hash that always contains one element. The key to this element will be:   "children" for lists of resources.  The value will be an array with each element in the array being a hash with the key, "name", set to the name of the resource and the key, "href", set to the URI of the resource. "properties" for configuration resources.  The value will be a hash with each key value pair being a section of properties with the key being set to the name of the section and the value being a hash containing the configuration values as key/value pairs.  Configuration values can be scalars, arrays or hashes.   Please see Feature Brief: Stingray's RESTful Control API for examples of these data structures and tools such as the Chrome REST Console can be used to see what the actual data looks like.   Example   The following rule will get a list of all pools and create a webpage listing them:   import stmrestclient; $html = "Pools: \n"; $response = stmrestclient.stmRestclient("pools", "json"); if ($response["rc"]) { $pools = $response["data"]["children"]; foreach ($pool in $pools) { $html = $html . $pool["name"] . " "; } } else { $html = $html . "There was an error getting the pool list: " . $response['info']; } http.sendResponse("200 OK", "text/html", $html, "");   stmrestclient   The TrafficScript code for the STM REST client, stmrestclient, has been attached to this article.   Read More   The REST API Guide in the Stingray Product Documentation Feature Brief: Stingray's RESTful Control API Collected Tech Tips: Using the RESTful Control API
View full article
This article describes how to integrate SLF4J logging into the the Stingray event log when using Stingray Java extensions that make use of libraries that use the Simple Logging Facade for Java (SLF4J).  The standard SLF4J package includes various jar files that can be used to control logging, such as slf4j-nop-1.7.5.jar that suppresses all messages and slf4j-simple-1.7.5.jar that will output all messages to the Stingray Event Log.  By uploading one and only one of these files, you can control whether or not messages are output to the Stingray Event Log.  The issue with using slf4j-simple-1.7.5.jar is that it is unaware of Stingray's log levels and logging infrastructure so all messages show up as warnings:     To address this we need to create our own StingrayLogger class that can map log messages to the Stingray log levels. The implementation in this article is based off of slf4j-simple.  To create a StingrayLogger class we also need StingrayLoggerBinder, StingrayMarkerBinder, StingrayMDCBinder and StingrayLoggerFactory classes.  The source files and jar file are attached to this article.  In order to compile these classes yourself, you will need to obtain zeus.java.jar from $ZEUSHOME/zxtm/lib on your Stingray Traffic Manager and slf4j-api-1.7.5.jar.  These class files should be in a jar under org.slf4j.impl.     The system property, org.slf4j.stingrayLogger.logLevel, specifies what level of logging to output to the Stingray event log.  Any message at that level or higher will be output, any below that level will be suppressed. The value of this property should be one of: "trace", "debug", "info", "warn", or "error". If not specified it defaults to "warn".  Stingray doesn't have trace or debug log levels, so any messages at those levels will be mapped to info.   An example of a Java Extension using the StingrayLogger class is described in WURFL - Updated
View full article
This is an update to the "Simply WURFL" article written in 2009.  Since that time, some of the underlying libraries have changed, especially with regards to logging.   What is WURFL?  WURFL stands for Wireless Universal Resource FiLe.  From the WURFL webpage: "WURFL is a Device Description Repository (DDR), i.e. a framework that enables applications to map HTTP requests to a description of the capability of the mobile device that requests the page."  WURFL is licensed and maintained by ScientiaMobile, Inc. and includes a Java API so it can be used with the Stingray Traffic Manager.  It is up to the user to make sure they comply with the ScientiaMobile WURFL license.  You do not need to know Java to get these examples working since all the necessary class and jar files are either attached to this article or available at the links below, but if you do want to modify the source code, the source files are also attached.  To get started with WURFL you first need to ensure your Stingray Traffic Manager has working Java support and then download the following items.   wurfl Java API : This code has been tested with version 1.4.4.3.  You will need wurfl-1.4.4.3.jar. wurfl: The WURFL respository.  wurfl.zip which can be used as is or extracted as wurfl.xml.  This code has been tested with version 2.3.4. commons-collections: Java interfaces, implementations and utilities.  This code has been tested with version 3.2.1.  You will need commons-collections-3.2.1.jar. commons-lang: Helper utilities.  This code has been tested with version 3.1.  You will need commons-lang3-3.1.jar. sl4j: Logging framework.  This code has been tested with version 1.7.5.  You will need slf4j-api-1.7.5.jar and slf4j-noop-1.7.5.jar or slf4j-simple-1.7.5.jar depending on what you want to see with regards to logging messages.  slf4j-noop-1.7.5.jar will cause all messages to be suppressed while slf4j-simple-1.7.5.jar will cause all messages to appear in the Stingray event log as warnings.   Upload the files specified using the Calalogs > Java UI page on your Stingray Traffic Manager. Now you're all set to experiment with the following examples.   The first sample servlet is a very simple use-case of WURFL, useful as a base for your own servlet or for debugging. The intention is for it to introduce the WURFL API as it fits within the framework of Stingray Java Extensions. WURFL is typically configured using the ConfigListener model, but Stingray doesn't go as far as implementing all the nuts and bolts required by full web applications. Our WURFL servlet must perform the required initialization itself, so an init method has been implemented that sets up a WURFLManager. As much work as possible is done at servlet initialization time. Then all the doGet method needs to do is check the request against the pre-initialised WURFLManager.   The source code and the compiled class for this example, StingrayWURFLInfoServlet are attached to this article.  To compile the code yourself on the Stingray instance, after uploading the specified jar files, you can upload the source file as well and then from the $ZEUSHOME/zxtm/conf/jars directory execute the following command:   javac -cp "$ZEUSHOME/zxtm/lib/*:$ZEUSHOME/zxtm/conf/jars/* StingrayWURFLInfoServlet.java   To compile it on a different machine, you will need the following jar files that were uploaded to Stingray: commons-collections-3.2.1.jar, commons-lang3.3.1.jar, wurfl-1.4.4.3.jar as well as servlet.jar and zxtm-servlet.jar from Stingray.  These are available via links in the Stingray UI under Catalogs > Java.  Please see the Stingray Java Development Guide for more information.   To get the servlet working follow these steps:   Upload the StingrayWURFLInfoServlet.class file using Stingray Catalogs > Java UI page, leave the "Automatically create TrafficScript rule" checkbox ticked If you want devices that are not included in the standard WURFL repository, like desktop browsers, to be detected properly, a patches file can be created and uploaded to Stingray using the Catalogs > Java page.   If this file is uploaded, click on the StingrayWURFLInfoServlet link on the UI page and add the parameter "wurfl_patches" with the value of the name of the patches file, e.g., "web_browsers_patch.xml".  For details on creating a patches file see the ScientiaMobile website. Set up a Virtual Server for testing.  The pool can be set to "discard" since StingrayWURFLInfoServlet will create a response. Associate the auto-generated StingrayWURFLInfoServlet rule with the test Virtual Server as a request rule. Visit the virtual server from different browsers on different devices.   The result should be a page showing some general information about your browser at the top followed by the full table of WURFL capabilities and their values. The following screenshots show the top part of the output using a iPhone and a desktop browser:   Safari on iPhone:     Firefox on Windows:   This is fine as a demo but not particularly useful for a real world application. What we really want is a module that can export capabilities so that they can be used by TrafficScript and other extensions. We also don't want to specifically be a doGet processor, so we'll modify the servlet along the lines described in the article: Writing TrafficScript functions in Java   in Java*</a>   There are a lot of capabilities covered by WURFL, the tables in the above screenshots go on for several pages - more than 500 capabilities in all. So we'll make it possible for TrafficScript to specify what capability fields it wants.   This gives us a servlet that could be used from TrafficScript using a rule such as the following example.  This rule extracts a few values and caches them in the Global Associative Array using the user-agent as the key so that subsequent requests for the same user-agent don't require another look up.   sub checkWURFL() { $ua = http.getHeader( "User-Agent" ); if (!string.length($ua)) return; $markup = data.get("WURFL" . $ua . "preferred_markup"); $datarate = data.get("WURFL" . $ua . "max_data_rate"); $brand = data.get("WURFL" . $ua . "brand_name"); $cookie = data.get("WURFL" . $ua . "cookie_support"); $os = data.get("WURFL" . $ua . "device_os"); $osVersion = data.get("WURFL" . $ua . "device_os_version"); $isWireless = data.get("WURFL" . $ua . "is_wireless_device"); if (string.length($markup)) { log.info("Returning cached values for User-Agent: " . $ua . ", datarate: " . $datarate . ", markup: " . $markup . ", brand: " . $brand . ", cookies: " . $cookie . ", os: " . $os . ", version: " . $osVersion . ", iswireless: " . $isWireless); $1 = $markup; $2 = $datarate; $3 = $brand; $4 = $cookie; $5 = $os; $6 = $osVersion; $7 = $isWireLess; return; } # no cached values for the UA, so run it through WURFL java.run("StingrayWURFLServlet", "max_data_rate", "preferred_markup", "brand_name", "cookie_support", "device_os", "device_os_version", "is_wireless_device"); $markup = connection.data.get("preferred_markup"); $datarate = connection.data.get("max_data_rate"); $brand = connection.data.get("brand_name"); $cookie = connection.data.get("cookie_support"); $os = connection.data.get("device_os"); $osVersion = connection.data.get("device_os_version"); $isWireless = connection.data.get("is_wireless_device"); data.set("WURFL" . $ua . "preferred_markup", $markup); data.set("WURFL" . $ua . "max_data_rate", $datarate); data.set("WURFL" . $ua . "max_data_rate", $datarate); data.set("WURFL" . $ua . "brand_name", $brand); data.set("WURFL" . $ua . "cookie_support", $cookie); data.set("WURFL" . $ua . "device_os", $os); data.set("WURFL" . $ua . "device_os_version", $osVersion); data.set("WURFL" . $ua . "is_wireless_device", $isWireless); log.info("Returning fresh WURFL values for User-Agent: " . $ua . ", datarate: " . $datarate . ", markup: " . $markup . ", brand: " . $brand . ", cookies: " . $cookie . ", os: " . $os . ", version: " . $osVersion . ", iswireless:" . $isWireless); $1 = $markup; $2 = $datarate; $3 = $brand; $4 = $cookie; $5 = $os; $6 = $osVersion; $7 = $isWireless; return; } # simple case to test the checkWURFL function checkWURFL(); $html = "Max Data Rate: " . $2 . "kbps Preferred Markup: " . $1 . " Brand: " . $3 . " OS: " . $5 . " Version: " . $6 . " Cookie Support: " . $4 . " Is Wireless Device: " . $7; http.sendResponse("200", "text/html", $html, "");   The corresponding StingrayWURFLServlet source and compiled class file and slf4j-stingray.jar file are attached to this article.  The slf4j-stingray.jar file includes an implementation to properly direct slf4j messages to the Stingray Event Log. The logging code is documented here: slf4j Logging and Stingray Java Extensions . The slf4j-noop-1.7.5.jar or slf4j-simple-1.7.5.jar file uploaded for the previous example should be deleted.  To control what level of logging to output to the Stingray Event Log, a parameter, "log_level" can be set to a value (case insensitive) of "debug", "info", "warn" or "error".  If no value is set, the default is "warn".   Rather than the doGet method implemented in the StingrayWURFLInfoServlet we now have a simpler service method.   public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String[] args = (String[])request.getAttribute( "args" ); if (args == null || args.length == 0) { throw new ServletException("error: no arguments supplied"); } Device device = manager.getDeviceForRequest(request); Map capabilities = device.getCapabilities(); for (int i = 0; i < args.length; ++i) { String cap = (String)capabilities.get(args[i]); if (cap == null) { Logger.warn("Java Servlet StingrayWURFLServlet: No capability found matching: " + args[i]); } else { ((ZXTMServletRequest)request).setConnectionData( args[i], cap ); } } }   There is much more you could do with the StingrayWURFLServlet Java Extension provided in this article. Think of it as a starting point for developing your own solutions to improve the web browsing experience of your mobile users.   A few examples:   The max_data_rate value retrieved above could be used to reduce image quality or size for people with low bandwidth devices. This would result in a snappier web browsing experience for these people as there would be less data for them to retrieve over their slow links. The preferred_markup value can be used to direct clients to different backend pools based on whether they can handle XHTML, or should be served WML. The streaming_flv value can be checked to see if the device has Flash video support and can thus be sent to your full bells-and-whistles website. A scaled down version could be made for those that only have Flash Lite, which is specified by the value of flash_lite_version. Devices that don't support flash at all (such as the iPhone) can be sent to a plain HTML version of the site, or WML as in the previous bullet point.   Speaking of the iPhone, it doesn't have Flash but its browser does have excellent AJAX support. You know your site is being visited by an iPhone user using the normal iPhone web browser when model_name is "iPhone" and mobile_browser is "Safari". If there are important differences between iPhone OS releases you can also check model_extra_info ordevice_os_version for this detail. For AJAX in general there are a whole set of specific properties: ajax_manipulate_css ajax_manipulate_dom ajax_support_event_listener ajax_support_events   Up to date documentation on all the WURFL capabilities can be found on the WURFL website.   <a title=h   Any of these values can also be passed on to your backend nodes of course. You could add special headers containing the values, a cookie, or a URL argument. You could also cache browser capabilities uniquely to each device with cookies or another method of session tracking, rather than cache the capabilities based solely on the user agent. Then you could offer users the ability to override special mobile device modes.   We would love to hear your ideas and learn how we can help you in this exciting area - the opportunities are practically limitless.
View full article
The 'draining' property of a node is configured per-pool.  If the node is used in several pools and you want to prepare it to take out of service, you need to edit each pool to set it to be draining. With the Stingray UI, you can use the 'Drain a Node' and 'Undrain a Node' wizards.  These checks all pools and ensures that the node is updated in all of them. This Python script does the same thing: #!/usr/bin/python import requests import json import sys def usage():   print "Syntax: %s drain|undrain nodename:[port]" % sys.argv[0]   sys.exit( -1 ) if len( sys.argv ) != 3:   usage() action = sys.argv[1] target = sys.argv[2] if action == "drain":   print "Draining node " + target + ":" elif action == "undrain":   print "Stopping draining node " + target + ":" else:   usage() url = ' https://stingray:9070/api/tm/1.0/config/active/pools/'; client = requests.Session() client.auth = ('admin', 'admin') client.verify = 0 response = client.get(url) pools = json.loads(response.content) def nodeInList( node, list ):   for n in list:     if n.startswith( node ):       return n   return '' for pool in pools['children']:   print "  checking pool "+pool['name']   response = client.get(url+pool['name'])   data = json.loads(response.content)   matchednode = nodeInList( target, data['properties']['basic']['nodes'] )   if matchednode == '':     continue   if action == 'drain' and nodeInList( matchednode, data['properties']['basic']['draining'] ) == '':     data['properties']['basic']['draining'].append( matchednode )     print "    draining node %s in pool %s" % ( matchednode, pool['name'] )     client.put( url+pool['name'], data = json.dumps( data ), headers = {'content-type': 'application/json'} )   if action == 'undrain' and nodeInList( matchednode, data['properties']['basic']['draining'] ) != '':     data['properties']['basic']['draining'].remove( matchednode )     print "    undraining node %s in pool %s" % ( matchednode, pool['name'] )     client.put( url+pool['name'], data = json.dumps( data ), headers = {'content-type': 'application/json'} ) Use it as follows: $ ./setDraining.py drain splash.riverbed.com Draining node splash.riverbed.com:   checking pool web site pool   checking pool Forward Proxy Pool   checking pool splash     draining node splash.riverbed.com:443 in pool splash   checking pool SSH   checking pool Internal HTTPS loopback   checking pool WebSocket test   checking pool splash proxy     draining node splash.riverbed.com:443 in pool splash proxy $ ./setDraining.py undrain splash.riverbed.com Stopping draining node splash.riverbed.com:   checking pool web site pool   checking pool Forward Proxy Pool   checking pool splash     undraining node splash.riverbed.com:443 in pool splash   checking pool SSH   checking pool Internal HTTPS loopback   checking pool WebSocket test   checking pool splash proxy     undraining node splash.riverbed.com:443 in pool splash proxy
View full article
Following is a library that I am working on that has one simple design goal: Make it easier to do authentication overlay with Stingray.   I want to have the ability to deploy a configuration that uses a single line to input an authentication element (basic auth or forms based) that takes the name of an authenticator, and uses a simple list to define what resources are protected and which groups can access them.   Below is the beginning of this library.  Once we have better code revision handling in splash (hint hint Owen Garrett!! ) I will move it to something more re-usable.  Until then, here it is.   As always, comments, suggestions, flames or gifts of mutton and mead most welcome...   The way I want to call it is like this:   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import lib_auth_overlay as aaa;   # Here we challenge for user/pass $userpasswd = aaa.promptAuth401();   # extract the entered username / password into variables for clarity $username = $userpasswd [0]; $password = $userpasswd [1];   # Here we authenticate check that the user is a member of the listed group # We are using the "user_ldap" authenticator that I set up against my laptop.snrkl.org # AD domain controller. $authResult = aaa.doAuthAndCheckGroup( "user_ldap" , $username , $password , "CN=staff,CN=Users,DC=laptop,DC=snrkl,DC=org" );   # for convienience we will tell the user the result of their Auth in an http response aaa.doHtmlResponse.200( "Auth Result:" . $authResult );   here is the lib_auth_overlay that is referenced in the above element.  Please note the promptAuthHttpForm() sub routine is not yet finished...   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 sub doHtmlResponse.200 ( $message ){       http.sendResponse(          "200 OK" ,          "text/html" ,          $message ,          ""          ); }   sub challengeBasicAuth( $errorMessage , $realm ){       http.sendResponse(          "401 Access Denied" ,          "text/html" ,          $errorMessage ,          "WWW-Authenticate: Basic realm=\"" . $realm . "\"" );   }   sub basicAuthExtractUserPass( $ah ){ #// $ah is $authHeader,          $enc = string.skip( $ah , 6 );          $up = string. split (string.base64decode( $enc ), ":" );          return $up ;       }   sub doAuthAndGetGroups ( $authenticator , $u , $p ){       $auth = auth.query( $authenticator , $u , $p );       if ( $auth [ 'Error' ] ) {          log .error( "Error with authenticator " . $authenticator . ": " . $auth [ 'Error' ] );          return "Authentication Error" ;       } else if ( ! $auth [ 'OK' ] ) { #//Auth is not OK          # Unauthorised          log . warn ( "Access Denied - invalid username or password for user: \"" . $u . "\"" );          return "Access Denied - invalid username or password" ;       } else if ( $auth [ 'OK' ] ){          log .info( "Authenticated \"" . $u . "\" successfully at " . sys. localtime . format ( "%a, %d %b %Y %T EST" ));          return $auth [ 'memberOf' ];       }   }   sub doAuthAndCheckGroup ( $authenticator , $u , $p , $g ){       $auth = auth.query( $authenticator , $u , $p );       if ( $auth [ 'Error' ] ) {          log .error( "Error with authenticator \"" . $authenticator . "\": " . $auth [ 'Error' ] );          return "Authentication Error" ;       } else if ( ! $auth [ 'OK' ] ) { #//Auth is not OK          # Unauthorised          log . warn ( "Access Denied - invalid username or password for user: \"" . $u . "\"" );          return "Access Denied - invalid username or password" ;       } else if ( $auth [ 'OK' ] ){          log .info( "Authenticated \"" . $u . "\" successfully at " . sys. localtime . format ( "%a, %d %b %Y %T EST" ));          if ( lang.isArray( $auth [ 'memberOf' ])){ #//More than one group returned             foreach ( $group in $auth [ 'memberOf' ]){                if ( $group == $g ) {                   log .info( "User \"" . $u . "\" permitted access at " . sys. localtime . format ( "%a, %d %b %Y %T EST" ));                   return "PASS" ;                   break;                } else {                   log . warn ( "User \"" . $u . "\" denied access - not a member of \"" . $g . "\" at " . sys. localtime . format ( "%a, %d %b %Y %T EST" ));                }           }             #// If we get to here, we have exhausted list of groups with no match             return "FAIL" ;            } else { #// This means that only one group is returned             $group = $auth [ 'memberOf' ];                if ( $group == $g ) {                   log .info( "User \"" . $u . "\" permitted access " . sys. localtime . format ( "%a, %d %b %Y %T EST" ));                   return "PASS" ;                   break;                } else {                   log . warn ( "User \"" . $u . "\" denied access - not a member of \"" . $g . "\" at " . sys. localtime . format ( "%a, %d %b %Y %T EST" ));                   return "FAIL" ;                }        }     } }   sub promptAuth401(){       if (!http.getHeader( "Authorization" )) { #// no Authorization header present, lets challenge for credentials          challengeBasicAuth( "Error Message" , "Realm" );       } else {          $authHeader = http.getHeader( "Authorization" );          $up = basicAuthExtractUserPass( $authHeader );            return $up ;     } }   sub promptAuthHttpForm(){       $response = "<html> <head>Authenticate me...</head> <form action=/login method=POST> <input name=user required> <input name=realm type=hidden value=stingray> <input name=pass type=password required> <button>Log In</button> </form> </html>" ;       doHtmlResponse.200( $response ); }  
View full article
If you wish to use Stingray to inspect or modify the headers or body of SMTP messages, you need to write some TrafficScript to do so.   Your TrafficScript code will run on a Server First protocol, and you will need to detect the start and end of each SMTP message (request and response) so that your code is executed correctly and you don't stall the connection by waiting for client (or server) data that will never arrive.  This article explains how to do this, and presents an example TrafficScript rule that can be used as the basis of more advanced SMTP rules.   Background - salient details of SMTP   SMTP is a server first TCP protocol, usually run on port 25. Lines are terminated by carriage return and line feed (CRLF). A typical SMTP conversation proceeds as follows:       Client                                  Server   1. Opens connection to server                                           2. Server sends banner   3. sends EHLO ...                                           4. Server acknowledges/rejects   # Header processing   5. sends MAIL FROM: someone@example.com                                           6. Server acknowledges/rejects   7. sends RCPT TO: other@example.com                                           8. Server acknowledges/rejects   9. sends DATA                                           10. Server acknowledges   10. Client sends body terminated by '.' on new line                                           11. Server acknowledges   (Client optionally sends another email, by returning to step 5., otherwise sends "QUIT" and server closes connection)   It's important to note the following:   The protocol is server first (use the server_first banner setting in Stingray), so a proxy must send the server's banner before waiting for a request from the client. Each request or response is terminated by CRLF. However, once the server has acknowledged the client's "DATA" request, the client continues to send CRLF terminated lines, without waiting for any response from the server, until it sends the final '.' Real world clients may not wait for a response from the server before sending the next header line. It's quite usual to see a client send all the headers in the packet it sends at step 5. The proxy must handle this situation as well as the conventional alternating request/response sequence of events.   Synchronizing SMTP with TrafficScript   To handle SMTP, we need a TrafficScript rule that creates a state-machine with two states:   State 1. Reading Headers: Read the request a line at a time, sending each line on to the server. If the request is "DATA", then transition to state 2.   State 2. Reading Body: Read the request a line at a time, but don't send it to the server straight away. Instead buffer the request until the entire body has been received. Once the whole body has been received, send the body and transition to state 1, ready to receive another email or another command, such as QUIT.   Since our TrafficScript rule is going to be executed every time a new line of data is received from the client, not just once per email sent, we need to keep track of the state between invocations of the rule. TrafficScript provides connection.data.set() and connection.data.get() to set flags that will let us keep track of which state we are in. As their names suggest, these commands allow you to set and read variables that persist as long as the TCP connection they are associated with.   Multiple lines might arrive in the same packet from the client, even though we only wish to pass them to the server one at a time. We therefore need to indicate to Stingray how to split the data into individual lines. We can do this using request.endsWith() , which takes a single argument, a regular expression that defines the end of the current request. Any data that comes after this will be kept for the next time the request rule is run.   Putting this together, the following is a request rule that will synchronise SMTP. There are three places in it from which you could hook in code to inspect/modify the headers and body on the way through.   # SMTP Synchronizer - Stingray Request Rule if (connection.data.get("BODY_COMPLETE")) { # We've finished one email, so reset the state flags, in case # another email is on its way. connection.data.set ("DATA_RECEIVED", 0); connection.data.set ("BODY_COMPLETE", 0); } if (! connection.data.get("DATA_RECEIVED")) { # State 1: Reading Headers # We haven't seen "DATA" yet, so process headers, line by line # Read the next line of the request into $request $request = request.endsWith("\r\n"); if (string.regexmatch($request, "^DATA\r\n")) { # DATA seen - set the DATA_RECEIVED flag to switch into # the Reading Body state connection.data.set("DATA_RECEIVED", 1); } #-------------------------------------------------- # INSPECTION/MODIFICATION HOOK # USE $request TO MODIFY/INSPECT HEADERS HERE #-------------------------------------------------- } else { # State 2: Reading Body # Keep reading the body until we see the end of it $line = ""; $next = 0; # The regular expression in the next line matches the # "end of body" indicator (a full stop on a line on its own) while ( ! string.regexmatch($line, "^\\.\r\n")) { $line = request.getline("\r\n", $next); $next = $1; #---------------------------------------------------- # INSPECTION/MODIFICATION HOOK # USE $line TO INSPECT/MODIFY BODY LINE BY LINE HERE #---------------------------------------------------- $request = $request . $line; } #-------------------------------------------------- # INSPECTION/MODIFICATION HOOK # USE $request to INSPECT/MODIFY WHOLE BODY HERE #-------------------------------------------------- # We've read the whole body, so set the BODY_COMPLETE flag connection.data.set("BODY_COMPLETE", 1); } # Send the request on to the back-end server. # At this point $request will contain either a single header line, or # the entire body, depending on which state we are in. request.set($request);   You should note the difference in behavior of request.endsWith() and request.getline() . Whilst they both return some data from the request, request.endsWith() tells Stingray that this is the end of this request, and no more data should be read from the client until after the request has been sent to the server and a response received. request.getline() , on the other hand, merely reads some data from the client without ending the request, and so can be called multiple times within the same invocation of a request rule. If no data is available when request.getline() is called, processing will pause until the client sends some. We can thus use it to read and buffer the body. If we tried to use request.endsWith() instead, then the connection would stall. Stingray would read the first line of the body, send it to the server, then pause waiting for a response from the server, which would never come.   The condensed version   If you don't want to perform line by line inspection or modification of the body, you can condense the above rule into the more elegant one below. This uses request.endswith() to read both the headers and the body, with only a single connection.data variable that both tracks the state and supplies the appropriate regular expression to request.endswith() .   # SMTP Synchronizer Condensed- ZXTM Request Rule # State is tracked in the connection.data variable "STATE", which is # set to one of the following two constants. The constants themselves # are the regular expressions supplied as arguments to # request.endsWith() to define the end of a request in each state. $STATE_HEADERS = "\r\n"; $STATE_BODY = "\r\n\\.\r\n"; # Initialise STATE if this is the first request on a new connection if (! connection.data.get("STATE")) { connection.data.set("STATE", $STATE_HEADERS); } $request = request.endsWith(connection.data.get("STATE")); #### STATE SWITCHING # We read the whole body in one go, so if we've got to here, and the # state is STATE_BODY, then switch back to reading headers. if (connection.data.get("STATE") == $STATE_BODY) { connection.data.set("STATE", $STATE_HEADERS); } else if (string.regexmatch($request, "^DATA\r\n")) { # Whereas if we were reading headers, and see DATA, we should # prepare ourselves for the body connection.data.set("STATE", $STATE_BODY); } #### END STATE SWITCHING #---------------------------------------------------------------------- # INSPECTION/MODIFICATION HOOK # USE $request to INSPECT/MODIFY WHOLE BODY OR INDIVIDUAL HEADERS HERE #---------------------------------------------------------------------- request.set($request);   For more details and examples of this synchronization technique, you can refer to the following resources:   Feature Brief: Server First, Client First and Generic Streaming Protocols Building a load-balancing MySQL proxy with TrafficScript
View full article
This question cropped up on our discussion forum: Re: How to use RESTful Control API with Python? - enable, disable, or drain node: How can I list the nodes that are draining in a Stingray configuration, much like the 'Draining' page in the UI.   You can get the list of nodes that are draining per-pool from /api/tm/1.0/config/active/pools/poolname; look at the properties->basic->draining value.  There's no single action to get a list of all of the nodes that are draining in all pools.  The UI reads each pool one at a time and merges the 'draining' lists from each; you will need to do the same.   The following Python code illustrates how you could do this:   #!/usr/bin/python import requests import json url = 'https://stingray:9070/api/tm/1.0/config/active/pools/'; client = requests.Session() client.auth = ('admin', 'admin') client.verify = 0 response = client.get(url) pools = json.loads(response.content) draining = [] for node in pools['children']: response = client.get(url+node['name']) data = json.loads(response.content) draining += data['properties']['basic']['draining'] print "Draining Nodes: " + ", ".join( set( draining ))   Read more   HowTo: Drain a node in multiple pools (Python REST API example)
View full article
Have you ever wanted to make use of LDAPs StartTLS functionality, or to restrict the search filters and attributes which external users can use when querying your directory service? How about simply rejecting or closing LDAP connections at will?   Although Stingray does not include a built-in LDAP protocol parser, you can use TrafficScript LDAP Library 1.1 to provide the TrafficScript functionality to decode and build up LDAP packets.  Then with a little TrafficScript logic, you can implement some sophisticated traffic management policies for LDAP traffic.   Example Uses     Restrict LDAP Operations   The script below will read in a packet from the client using getPacket(). The getPacket() function returns a hash containing the current request in a hash along with the protocolOp (Protocol Operation), Message ID (all LDAP packets have an ID), and some other data. Once we have the packet and we know what the protocolOp is we can decide what to do with it.   import libLdap.rts as ldap; $packet = ldap.getPacket(); $op = ldap.getOp($packet); $ip = request.getRemoteIP(); if( !string.ipmaskmatch( $ip,"10.0.0.0/8" )) { # Non LAN clients can only BIND, UNBIND, and SEARCH if( ( $op != “BindRequest” ) && ( $op != “SearchRequest” ) && ( $op != “UnbindRequest” )) { # Send back a Notice of Disconnection and close the socket ldap.close(); } }   In this case, we simply check if the client is on the LAN (in the 10.0.0.0/8 subnet), and if it is not, then we restrict the types of requests they can make to binding, searching, and unbinding from the server.   Please Note: It's important to remember to set LDAP TrafficScript rules you create to run on "every" request, not just "once" (unless you're only interested in the Bind of course).   Chose an LDAP server based on bindDN   Here's another scenario for you, this time we have multiple LDAP servers sitting on the network, and we want to direct users to a server based on their binding credentials. If the BindDN contains “dc=nbttech,dc=com” then we will use the nbttech ldap Servers. Alternatively if the BindDN contains “dc=riverbed,dc=com” then we will use the Riverbed ldap servers. Anonymous binds will be rejected.   import libLDAP.rts as ldap; $packet = ldap.getPacket(); $op = ldap.getOp( $packet ); if( $op == "BindRequest" ) { $details = ldap.getBindDetails( $packet ); if( $details["bindDN"] == “” ) { # Anonymous bind. I don't think so! ldap.rejectAnonBind( $packet["messageID"] ); } elseif( string.endswith( $details["bindDN"], “dc=nbttech,dc=com” )){ pool.use(“nbttech-ldap”); } elseif( string.endswith( $details["bindDN"], “dc=riverbed,dc=com” )){ pool.use(“riverbed-ldap”); } else { # Unknown bind domain. Log a warning and reject with invalid Credentials (49) log.warn( “UnknownBind:“. $details["bindDN"] ); ldap.rejectBind( $packet["messageID"], 49 ); } }   The getBindDetails() function also returns details of the authentication method (SIMPLE/SASL), the LDAP version, and the authentication data itself.   Override Attributes and filters   Another thing you may wish to do is to limit which attributes are sent out from the LDAP server, and possibly which parts of the LDAP tree are searchable. The libLdap library can be used to set attributes and filters on incoming requests to restrict which information the LDAP server returns.   In the TrafficScript below, we are going to override everything (mwah ha ha ha)....   First the script will detect the BindRequest and use connection.data.set() to store the bindDN in memory, If the bind is anonymous we will close the connection. If we then get a SearchRequest from the user we will retrieve the bindDN and limit the query base object to match the “dc” or “ou” used in the bind. We will also limit the attributes to “cn”, “uid”, and “gid”. Set the filter to match the bind users common name, and finally Set the search scope to a single level.   You are obviously unlikely to limit all of those things at once or all the time, but this sample script lets you see how they all work:   import libLDAP.rts as ldap; $packet = ldap.getPacket(); $op = ldap.getOp( $packet ); if( $op == "BindRequest" ){ $details = ldap.getBindDetails( $packet ); if( $details["bindDN"] == "") { ldap.rejectAnonBind( $packet["messageID"] ); } else { connection.data.set( "bindDN", $details["bindDN"] ); } } elseif ( $op == "SearchRequest" ) { $details = ldap.getSearchDetails( $packet ); $bindDN = connection.data.get( "bindDN" ); if( string.regexMatch( $bindDN, "(cn=.*?),(dc|ou=.*)" )) { # Set the Base object to be in the same domain as the binder $details[ "baseObject" ] = $2; # restrict the attributes returned to cn, uid and gid ldap.setSearchAttributes( $details, "cn uid gid" ); # Override the user supplied filter too. They can only search themselves. ldap.setSearchFilter( $details, "(". $1 .")" ); # Change the scope... why not ;-) $details[ "scope" ]=1; # Commit the changes request.set( ldap.updateSearch( $packet, $details )); }else{ # Oh dear failed to process search... Lets reject it ;-) ldap.rejectSearch( $packet["messageID"], "Failed to nobble search. So Denied. Sorry!" ); } }   StartTLS   At the time of writing, Stingray doesn't support StartTLS for LDAP out of the box, but with this TrafficScript you can implement it yourself quite simply. You will need to create two LDAP services, one plain ldap, and the other a ldaps service with SSL Offload enabled. Then you need a loop back pool to link the plain ldap service with the ldaps service.   Once you have that set up, you just need the script:   import libLDAP.rts as ldap; if( connection.data.get( "TLS" )) { # the connection is already in TLS mode, stop processing break; } $packet = ldap.getPacket(); $op = ldap.getOp( $packet ); if( $op == "ExtendedRequest" ) { if( ldap.isStartTLS( $packet )) { $ip = request.getRemoteIP(); # Only Accept StartTLS requests if the clients are not local if( !string.ipmaskmatch( $ip, "10.0.0.0/8" )) { # Set the TLS flag on the connection connection.data.set( "TLS", "yes" ); # Send the TSL acceptance packet ldap.acceptStartTLS( $packet ); # Use the ldaps loopback pool, and we're done :-) pool.use( "ldap-loop" ); } else { # LAN clients have TLS rejected and can carry on in clear text ldap.rejectStartTLS( $packet, "Use Plain Text please" ); } } }   The first thing this script does is check to see if a TLS flag has been set on the connection. If it exists then startTLS has already happened and we should exit, the packet is almost certainly encrypted. If the TLS flag is not set, we continue. The code checks for the startTLS command, and if the user is a remote user it sends back an accept, sets the TLS flag on the connection, and selects the loop back pool. Further LDAP processing can be performed in the LDAPS Virtual Server. If however we find the user is in the 10/8 subnet we reject the startTLS command and make them continue in plain text.   Use LDAP Simple Authentication for other Services   The libLDAPauth.rts library is a small library which uses libLDAP to verify user/passwords against an ldap server. At present it only does a simple BindRequest.  You may want to look at the built-in 'auth.query()' TrafficScript function now; that makes this legacy library somewhat redundant.   The simplest way to use is by using the checkLdapAuth() function. This function returns the LDAP result code, which should be 0 if the authentication was successful.   import libLDAPauth.rts as la; $auth = la.checkLdapAuth( "10.4.5.1", "389", "cn=user,dc=znbttech,dc=com", "password” ); if ( $auth == 0 ) { # success log.info( "UserAuthenticated" ); } else { # Failed log.warn( "User failed authentication:" . $auth); }
View full article
This article uses the libDNS.rts trafficscript library as described in libDNS.rts: Interrogating and managing DNS traffic in Stingray.   In this example, we intercept DNS requests and respond directly for known A records.   The request rule   import libDNS.rts as dns; # Map domain names to lists of IP addresses they should resolve to $ipAddresses = [ "dev1.ha.company.internal." => [ "10.1.1.1", "10.2.1.1" ], "dev2.ha.company.internal." => [ "10.1.1.2", "10.2.1.2" ] ]; $packet = dns.convertRawDataToObject( request.get(), "udp" ); # Ignore unparsable packets and query responses to avoid # attacks like the one described in CVE-2004-0789. if( hash.count( $packet ) == 0 || $packet["qr"] == "1" ) { break; } $host = $packet["question"]["host"]; if( hash.contains( $ipAddresses, $host )) { foreach( $ip in $ipAddresses[$host] ) { $packet = dns.addResponse($packet, "answer", $host, $ip, "A", "IN", "60", []); } $packet["aa"] = "1"; # Make the answer authorative } else { $packet["rcode"] = "0011"; # Set NXDOMAIN error } $packet["qr"] = "1"; # Changes the packet to a response $packet["ra"] = "1"; # Pretend that we support recursion request.sendResponse( dns.convertObjectToRawData($packet, "udp"));
View full article
 This article uses the libDNS.rts trafficscript library as described in libDNS.rts: Interrogating and managing DNS traffic in Stingray.   In this example, we intercept DNS requests. If the client is seeking to resolve www.site.com and they are based in the UK, then we respond directly with a CNAME response, directing them to resolve www.site.co.uk instead.   Request rule   import libDNS.rts as dns; $request = request.get(); $packet = dns.convertRawDataToObject($request, "udp"); # Ignore unparsable packets and query responses to avoid # attacks like the one described in CVE-2004-0789. if( hash.count( $packet ) == 0 || $packet["qr"] == "1" ) { break; } $host = dns.getQuestion( $packet )["host"]; $country = geo.getCountry( request.getRemoteIP() ); if( $host == "www.site.com." && $country == "GB" ) { $packet = dns.addResponse($packet, "answer", "www.site.com", "www.site.co.uk.", "CNAME", "IN", "60", []); $packet["qr"] = 1; request.sendResponse( dns.convertObjectToRawData($packet, "udp")); }
View full article
This article uses the libDNS.rts trafficscript library as described in libDNS.rts: Interrogating and managing DNS traffic in Stingray.   In this example, we inspect DNS requests.  If the client is seeking to resolve www.site.*, we rewrite the request to resolve www.site.com instead before passing the DNS request on to the real DNS server.   We also need to patch up the DNS response to restore the correct value for the 'question'.   Request rule   import libDNS.rts as dns; $data = request.get(); # Create a data structure holding the contents of the DNS request $packet = dns.convertRawDataToObject( $data, "udp" ); # Get the Question section from the request $question = dns.getQuestion( $packet ); $host = $question["host"]; if( string.wildmatch( $host, "www.site.*" ) && $host != "www.site.com" ) { # We'll rewrite the question, We also need to store the original value so that we can restore it in the response connection.data.set( "question", $question ); $packet = dns.setQuestion( $packet, "www.site.com", $question["type"], $question["class"] ); request.set( dns.convertObjectToRawData( $packet, "udp" )); }   Response rule   import libDNS.rts as dns; $question = connection.data.get( "question" ); if( !$question ) break; # We don't need to patch up the response $data = response.get(); $packet = dns.convertRawDataToObject( $data, "udp" ); $packet = dns.setQuestion($packet, $question["host"], $question["type"], $question["class"] ); # Set the name of each Answer RR to what was asked for in the question $i = 0; while( $i < $packet["answercount"] ) { $packet["answer"][$i++]["name"] = $question["host"]; } # Convert the data structure back into raw data and set it as the response response.set( dns.convertObjectToRawData( $packet, "udp" ));
View full article
This page indexes some useful libraries and add-ons for Stingray Traffic Manager.   Tools   ZeusBench is a simple and accurate benchmarking tool that is bundled with Stingray Traffic Manager: Introducing Zeusbench Puppet Deployment: you can deploy Stingray software using Puppet: Stingray Traffic Manager Puppet Module: Reference Guide Stingray Kernel Modules: the optional Stingray Kernel Modules are used by Stingray on Linux hosts for low-level networking operations: Stingray Kernel Modules for Linux Software   TrafficScript Libraries   libTable.rts: The Table library provide a simple interface to read data from external tables: libTable.rts: Interrogating tables of data in TrafficScript libWebSockets.rts: The WebSockets library allows you to manage HTTP and WebSockets traffic published on the same endpoint: libWebSockets.rts: Managing WebSockets traffic with Stingray Traffic Manager libDNS.rts: The DNS library allows you to interrogate and manage DNS traffic: libDNS.rts: Interrogating and managing DNS traffic in Stingray libLDAP.rts: The LDAP library provides the interface to interogate and modify LDAP requests and responses while they are managed by Stingray: libLDAP.rts: a TrafficScript LDAP Library libHMAC.rts: The HMAC library implements the HMAC-SHA1 and HMAC-MD5 hash algorithms: libHMAC.rts: Calculating HMAC hashes in TrafficScript libSet.rts: The Sets library builds on trafficscript primitives to implement Set datatypes: libSet.rts: Sets in TrafficScript   API Libraries   PyRunner.jar lets you run Python code directly from TrafficScript: PyRunner.jar: Running Python code in Stingray Traffic Manager Stingray-API.jar provides a Java interface to Stingray's Control API: Stingray-API.jar: A Java Interface Library for Stingray's SOAP Control API
View full article
The following code uses Stingray's RESTful API to a file to the extra directory.   The code is written in Perl.  This program adds the file 'validserialnumbers' to the extra directory and if the file already exists it will be overwrite it.  If this is not the desired behavior, code can be added to to check for the existence of the file as was done in the addpool example.   addextrafile.pl   #!/usr/bin/perl use REST::Client; use MIME::Base64; #Since Stingray is using a self-signed certificate we don't need to verify it $ENV{'PERL_LWP_SSL_VERIFY_HOSTNAME'} = 0; my $fileName = 'validserialnumbers'; my $url = "/api/tm/1.0/config/active/extra/$fileName"; my $validSerialNumbers = <<END; 123456 234567 345678 END # Set up the connection my $client = REST::Client->new(); $client->setHost("https://stingray.example.com:9070"); $client->addHeader("Authorization", "Basic " . encode_base64("admin:admin")); # For files, the MIME type is octet-stream $client->addHeader("Content-Type", "application/octet-stream"); $client->PUT($url, $validSerialNumbers); # If the file already exists, it will be replaced with this version and 204 will be returned # otherwise 201 will be returned. if ($client->responseCode() == 201 || $client->responseCode() == 204) { print "File $fileName added"; } else { print "Error adding file $fileName. Status: " . $client->responseCode() . " URL: $url"; }   Running the example   This code was tested with Perl 5.14.2 and version 249 of the REST::Client module.   Run the Perl script as follows:   $ addextrafile.pl File validserialnumbers added   Notes   Since this is a file and not a configuration resource, JSON will not be used and the MIME type will be "application/octet-stream".  Another difference when dealing with files is how Stingray handles adding a file that already exists.  If the file already exists, Stingray will overwrite the it and return a HTTP status code of 204.  If the file doesn't already exist, the HTTP status code will be a 201.   Read More   Stingray REST API Guide in the Stingray Product Documentation Feature Brief: Stingray's RESTful Control API Tech Tip: Using Stingray's RESTful Control API Tech Tip: Using the RESTful Control API with Perl Collected Tech Tips: Using the RESTful Control API
View full article
The following code uses Stingray's RESTful API to delete a pool.  The code is written in Perl.  This program deletes the "pltest" pool created by the addpool.pl example.  To delete a resource you do a HTTP DELETE on the URI for the resource.  If the delete is successful a 204 HTTP status code will be returned.   deletepool.pl   #!/usr/bin/perl use REST::Client; use MIME::Base64; use JSON; # Since Stingray is using a self-signed certificate we don't need to verify it $ENV{'PERL_LWP_SSL_VERIFY_HOSTNAME'} = 0; my $poolName = 'pltest'; my $url = "/api/tm/1.0/config/active/pools/$poolName"; # Set up the connection my $client = REST::Client->new(); $client->setHost("https://stingray.example.com:9070"); $client->addHeader("Authorization", "Basic " . encode_base64("admin:admin")); #First see if the pool already exists $client->DELETE($url); if ($client->responseCode == 204) { print "Pool $poolName deleted"; } elsif ($client->responseCode == 404) { print "Pool $poolName not found"; } else { print "Error deleting pool $poolName. Status: " . $client->responseCode . " URL: $url"; }   Running the example   This code was tested with Perl 5.14.2 and version 249 of the REST::Client module.   Run the Perl script as follows:   $ delelepool.pl Pool pltest deleted   Read More   Stingray REST API Guide in the Stingray Product Documentation Feature Brief: Stingray's RESTful Control API Tech Tip: Using Stingray's RESTful Control API Tech Tip: Using the RESTful Control API with Perl Collected Tech Tips: Using the RESTful Control API
View full article
The following code uses Stingray's RESTful API to add a pool.   The code is written in Perl. This program creates a new pool, "pltest", first doing a GET to make sure the pool doesn't already exist, and if the pool doesn't exist, the pool is created by doing a PUT with just the minimum data needed to create a pool.  In this case the program creates a properties hash with just one node.  All other values will get default values when Stingray creates the pool.   addpool.pl   #!/usr/bin/perl use REST::Client; use MIME::Base64; use JSON; # Since Stingray is using a self-signed certificate we don't need to verify it $ENV{'PERL_LWP_SSL_VERIFY_HOSTNAME'} = 0; my $poolName = 'pltest'; my %pool = (properties => {basic => {nodes => [ '192.168.168.135:80']}}); my $url = "/api/tm/1.0/config/active/pools/$poolName"; # Set up the connection my $client = REST::Client->new(); $client->setHost("https://stingray.example.com:9070"); $client->addHeader("Authorization", "Basic " . encode_base64("admin:admin")); #First see if the pool already exists $client->GET($url); if ($client->responseCode == 404) { $client->addHeader("Content-Type", "application/json"); $client->PUT($url, encode_json(\%pool)); my $poolConfig = decode_json $client->responseContent(); if ($client->responseCode() == 201) { # When creating a new resource we expect to get a 201 print "Pool $poolName added"; } else { print "Error adding pool. status=" . $client->responseCode() . " Id=" . $vsConfig->{error_id} . ": " . $vsConfig->{error_text} . "\n"; } } else { if ($client->responseCode() == 200) { print "Pool $poolName already exists"; } else { print "Error getting pool config. status=" . $client->responseCode() . " Id=" . $vsConfig->{error_id} . ": " . $vsConfig->{error_text} . "\n"; } }   Running the example   This code was tested with Perl 5.14.2 and version 249 of the REST::Client module.   Run the Perl script as follows:   $ addpool.pl Pool pltest added   Notes   The only difference between doing a PUT to change a resource and a PUT to add a resource is the HTTP status code returned.  When changing a resource 200 is the expected status code and when adding a resource, 201 is the expected status code.   Read More   Stingray REST API Guide in the Stingray Product Documentation Feature Brief: Stingray's RESTful Control API Tech Tip: Using Stingray's RESTful Control API Tech Tip: Using the RESTful Control API with Perl Collected Tech Tips: Using the RESTful Control API
View full article
The following code uses Stingray's RESTful API to enable or disabled a specific Virtual Server.   The code is written in Perl.  This program checks to see if the Virtual Server "test vs" is enabled and if it is, it disables it and if it is disabled, it enables it.  A GET is done to retrieve the configuration data for the Virtual Server and the "enabled" value in the "basic" properties section is checked.  This is a boolean value, so if it is true it is set to false and if it is false it is set to true. The changed data is then sent to the server using a PUT.   startstopvs.pl   #!/usr/bin/perl use REST::Client; use MIME::Base64; use JSON; use URI::Escape; # Since Stingray is using a self-signed certificate we don't need to verify it $ENV{'PERL_LWP_SSL_VERIFY_HOSTNAME'} = 0; my $vs = "test vs"; # Because there is a space in the virtual serve name it must be escaped my $url = "/api/tm/1.0/config/active/vservers/" . uri_escape($vs); # Set up the connection my $client = REST::Client->new(); $client->setHost("https://stingray.example.com:9070"); $client->addHeader("Authorization", "Basic " . encode_base64("admin:admin")); # Get configuration data for the virtual server $client->GET($url); # Decode the json response. The result will be a hash my $vsConfig = decode_json $client->responseContent(); if ($client->responseCode() == 200) { if ($vsConfig->{properties}->{basic}->{enabled}) { # The virtual server is enabled, disable it. We only need to send the data that we # are changing so create a new hash with just this data. %newVSConfig = (properties => { basic => { enabled => JSON::false}}); print "$vs is Enabled. Disable it.\n"; } else { # The virtual server is disabled, enable it. %newVSConfig = (properties => { basic => { enabled => JSON::true}}); print "$vs is Diabled. Enable it.\n"; } $client->addHeader("Content-Type", "application/json"); $client->PUT($url, encode_json(\%newVSConfig)); $vsConfig = decode_json $client->responseContent(); if ($client->responseCode() != 200) { print "Error putting virtual server config. status=" . $client->responseCode() . " Id=" . $vsConfig->{error_id} . ": " . $vsConfig->{error_text} . "\n"; } } else { print "Error getting pool config. status=" . $client->responseCode() . " Id=" . $vsConfig->{error_id} . ": " . $vsConfig->{error_text} . "\n"; }   Running the example   This code was tested with Perl 5.14.2 and version 249 of the REST::Client module.   Run the Perl script as follows:   $ startstopvs.pl test vs is enabled. Disable it.   Notes   This program it is sending only the 'enabled' value to the server by creating a new hash with just this value in the 'basic' properties section.  Alternatively, the entire Virtual Server configuration could have been returned to the server with just the enabled value changed.  Sending just the data that has changed reduces the chances of overwriting another user's changes if multiple programs are concurrently accessing the RESTful API.   Read More   Stingray REST API Guide in the Stingray Product Documentation Feature Brief: Stingray's RESTful Control API Tech Tip: Using Stingray's RESTful Control API Tech Tip: Using the RESTful Control API with Perl Collected Tech Tips: Using the RESTful Control API
View full article
The following code uses Stingray's RESTful API to list all the pools defined for a cluster and for each pool it lists the nodes defined for that pool, including draining and disabled nodes. The code is written in Perl. This example builds on the previous listpools.pl example. This program does a GET request for the list of pool and then while looping through the list of pools, a GET is done for each pool to retrieve the configuration parameters for that pool.   listpoolnodes.pl   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 #!/usr/bin/perl     use REST::Client;   use MIME::Base64;   use JSON;     print "Pools:\n\n" ;     # Since Stingray is using a self-signed certificate we don't need to verify it   $ENV { 'PERL_LWP_SSL_VERIFY_HOSTNAME' } = 0;     my $url = "/api/tm/1.0/config/active/pools" ;   # Set up the connection   my $client = REST::Client->new();   $client ->setHost( "https://stingray.example.com:9070" );   $client ->addHeader( "Authorization" , "Basic " . encode_base64( "admin:admin" ));     # Request a list of pools   $client ->GET( $url );     # Decode the json response   my $response = decode_json( $client ->responseContent());   if ( $client ->responseCode() == 200) {        # Obtain a reference to the children array        my $poolArrayRef = $response ->{children};        foreach my $pool (@ $poolArrayRef ) {            my $poolName = $pool ->{name};            $client ->GET( "$url/$poolName" );            my $poolConfig = decode_json $client ->responseContent();            if ( $client ->responseCode() == 200) {                my $nodes = $poolConfig ->{properties}->{basic}->{nodes};                my $draining = $poolConfig ->{properties}->{basic}->{draining};                my $disabled = $poolConfig ->{properties}->{basic}->{disabled};                print "Pool: $poolName\n" ;                print "    Nodes: " ;                foreach my $node (@ $nodes ) {                    print "$node " ;                }                print "\n" ;                if ( scalar (@ $draining ) gt 0) {                    print "    Draining Nodes: " ;                    foreach my $node (@ $draining ) {                        print "$node " ;                    }                    print "\n" ;                }                if ( scalar (@ $disabled ) gt 0) {                    print "    Diabled Nodes: " ;                    foreach my $node (@ $disabled ) {                        print "$node " ;                    }                    print "\n" ;                }                print "\n" ;            } else {                print "Error getting pool config: status=" . $client ->responseCode() . " Id=" . $poolConfig ->{error_id} . ": " . $poolConfig ->{error_text} . "\n"            }        }   } else {        print "Error getting list of pools: status=" . $client ->responseCode() . " Id=" . $response ->{error_id} . ": " . $response ->{error_text} . "\n" ;   }   Running the example   This code was tested with Perl 5.14.2 and version 249 of the REST::Client module.   Run the Perl script as follows:   $ listpoolnodes.pl Pools:   Pool1     Nodes:  192.168.1.100 192.168.1.101     Draining:  192.168.1.101     Disabled:  192.168.1.102   Pool2     Nodes:  192.168.1.103 192.168.1.104 >   Read More   Stingray REST API Guide in the Stingray Product Documentation Feature Brief: Stingray's RESTful Control API Tech Tip: Using Stingray's RESTful Control API Tech Tip: Using the RESTful Control API with Perl Collected Tech Tips: Using the RESTful Control API
View full article
The following code uses Stingray's RESTful API to list all the pools defined on a cluster. The code is written in Perl. This example has more extensive comments then the following examples and most of these are applicable to all the examples. This program does a single GET request for the list of pools and then loops through that list, each element of which is a hash, and then outputs the pool name.   listpools.pl   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 #!/usr/bin/perl     use REST::Client;   use MIME::Base64;   use JSON;     print "Pools:\n" ;     # Since Stingray is using a self-signed certificate we don't need to verify it   $ENV { 'PERL_LWP_SSL_VERIFY_HOSTNAME' } = 0;     # Set up the connection   my $client = REST::Client->new();   $client ->setHost( "https://stingray.example.com:9070" );   # Setup the basic authorization header with the encoded Userid and Password.   # These need to match a UserId and Password for a Stingray user   $client ->addHeader( "Authorization" , "Basic " . encode_base64( "admin:admin" ));     # Do the HTTP GET to get the lists of pools   $client ->GET( "/api/tm/1.0/config/active/pools" );     # Deserialize the JSON response into a hash   my $response = decode_json( $client ->responseContent());   if ( $client ->responseCode() == 200) {        # Obtain a reference to the children array        my $poolArrayRef = $response ->{children};        foreach my $pool (@ $poolArrayRef ) {            print $pool ->{name} . "\n" ;        }   } else {        # We weren't able to connect to the Stingray or there was a problem with the request.        # The most likely reasons for this are:        # - the hostname of the Stingray instance is incorrect        # - this client doesn't have network access to the Stingray instance or port 9070        # - the RESTful API is disabled        # - the RESTful API is using using a different port        # - the URL is incorrect        print "Error: status=" . $client ->responseCode() . " Id=" . $response ->{error_id} . ": " . $response ->{error_text} . "\n" ;   }   Running the example   This code was tested with Perl 5.14.2 and version 249 of the REST::Client module.   Run the Perl script as follows:   $ listpoolnodes.pl Pools:   Pool11     Pool2       Read More   Stingray REST API Guide in the Stingray Product Documentation Feature Brief: Stingray's RESTful Control API Tech Tip: Using Stingray's RESTful Control API Tech Tip: Using the RESTful Control API with Perl Collected Tech Tips: Using the RESTful Control API
View full article
The following code uses Stingray's RESTful API to a file to the extra directory.   The code is written in Ruby.  This program adds the file 'validserialnumbers' to the extra directory and if the file already exists it will be overwrite it.  If this is not the desired behavior, code can be added to to check for the existence of the file as was done in the addpool example.   addextrafile.rb   require 'rest_client' require 'base64' require 'json' fileName = 'validserialnumbers' url = 'https://stingray.example.com:9070/api/tm/1.0/config/active/extra/' + fileName auth = 'Basic ' + Base64.encode64('admin:admin') validSerialNumbers = < 'application/octet-stream', :authorization => auth}) # If the file already exists, it will be replaced with this version and 204 will be returned # otherwise 201 will be returned. if response.code == 201 || response.code == 204 puts "File #{fileName} added" else puts "Bad status code #{response.code} when adding file #{fileName}" end rescue => e puts "Error: URL=#{url} Error: #{e.message}" end   Running the example   This code was tested with Ruby 1.9.3 and version 1.6.7 of the rest-client module.   Run the Ruby script as follows:   $ addextrafile.rb File added   Notes   Since this is a file and not a configuration resource, JSON will not be used and the MIME type will be "application/octet-stream".  Another difference when dealing with files is how Stingray handles adding a file that already exists.  If the file already exists, Stingray will overwrite the it and return a HTTP status code of 204.  If the file doesn't already exist, the HTTP status code will be a 201.   Read More   Stingray REST API Guide in the Stingray Product Documentation Feature Brief: Stingray's RESTful Control API Tech Tip: Using Stingray's RESTful Control API Tech Tip: Using the RESTful Control API with Perl Collected Tech Tips: Using the RESTful Control API
View full article
The following code uses Stingray's RESTful API to delete a pool.  The code is written in Ruby.  This program deletes the "rbtest" pool created by the addpool.rb example.  To delete a resource you do a HTTP DELETE on the URI for the resource.  If the delete is successful a 204 HTTP status code will be returned.   deletepool.rb   require 'rest_client' require 'base64' require 'json' poolName = 'rbtest' url = 'https://stingray.example.com:9070/api/tm/1.0/config/active/pools/' + poolName auth = 'Basic ' + Base64.encode64('admin:admin') begin # Try to delete the pool. If it exists and is deleted we will get a 204. If it doesn't # exist we will get a 404, which will causue a resource.not_found exception response = RestClient.delete(url, {:authorization => auth}) if response.code == 204 puts "Pool #{poolName} deleted" else puts "Bad status code #{response.code} when deleting pool" end rescue => e # If a 404 is returned then e.response will be a json object, otherwise it may not be if defined?(e.response) error = JSON.parse(e.response) if error['error_id'] == 'resource.not_found' puts "Pool #{poolName} not found" else puts "Error: URL=#{url} Error: #{e.message}" end else puts "Error: URL=#{url} Error: #{e.message}" end end   Running the example   This code was tested with Ruby 1.9.3 and version 1.6.7 of the rest-client module.   Run the Ruby script as follows:   $ delelepool.rb Pool rbtest deleted   Read More   Stingray REST API Guide in the Stingray Product Documentation Feature Brief: Stingray's RESTful Control API Tech Tip: Using Stingray's RESTful Control API Tech Tip: Using the RESTful Control API with Perl Collected Tech Tips: Using the RESTful Control API
View full article