The following is a Traffic Script Rule for load-balancing VMware View Servers in an AlwaysOn Architecture serving all VMware clients.
This Rule is used both as a Request and a Response rule. More details on the usage and configurations of the Brocade Virtual Traffic Manager can be found in the Deployment Guide (link below).
This link is used as a reference in the Brocade Virtual Traffic Manager and VMware Horizon View Servers Deployment Guide
#// TS Rule for LB VMWare View
#// Set the Debug Level (possible values: 0,1,2,3) #// 0 = Logging Off #// 1 = Informational logging #// 2 = Full Debug #// 3 = Full Debug INCLUDING PASSWORDS - USE WITH EXTREME CARE $debug = 2;
#// Rule to Direct Traffic Based on AD Group Membership #// Please declare the names of the pools you have configured, and ensure #// that the trafficscript!variable_pool_use Global setting is set to 'yes'
#// What is the name of your VTM AD Authenticator #// This can be setup under "Catalogs > Authenticators" in the Traffic Manager GUI. $authenticator = "AD_AUTHENTICATOR";
#// What are the names of your pools to differentiate between Site A and Site B? #// These are setup under "Services > Pools" in the Traffic Manager GUI. $view_siteA_pool = "SITE-A-POOL"; $view_siteB_pool = "SITE-B-POOL";
#// Following are some View Specific variables that we need. #// 'server' is the name of your view Connection Server #// 'dns' is the DNS search suffix (single suffix only) used in your setup #// 'domain' is the NT4 style name of your active directory #// 'guid' is the GUID of the view connection server, and can be found by checking #// 'HKLM\SOFTWARE\VMware, Inc.\VMware VDM\Node Manager\ConnectionServer Cluster GUID' with regedit.exe #// on your connection server. $view_info = [ "guid" => "648a7ef8-dcba-abcd-efgh-58973ad94085", "server" => "rvbdvTMs1", "dns" => "brocade.local", "domain" => "brocade" ];
#// Here we need to know about the names of the AD Groups to map to each Site $siteA_AD_Groupname = "brcd_DC0"; $siteB_AD_Groupname = "brcd_DC1";
#// Here we need to know about the IP addresses of your SITEB Traffic Manager. #// Site B VTM IP addresses (up to two supported. If your setup requires more, please #// Contact your Brocade sales representative to arrange for Brocade Professional #// Services to provide a quote for customising this deployment guide.
$site_B_VTM1_IP = "10.10.10.6"; $site_B_VTM2_IP = "";
#// Is this a request or response rule? $rulestate = rule.getstate();
#// What is the Client's IP address - useful for logging: $client_ip = request.getRemoteIP();
if ($rulestate == "REQUEST" ){ if( $debug > 0 ) {log.info("#*#* Start Reqeust Section *#*#");} #//We need to extract the tunnelID for persistence purposes then break out of the rule to let the tunnel through $path = http.getPath(); if (string.startswith($path, "/ice/tunnel")){ $tunnelID = http.getQueryString(); if ($tunnelID){ if ($debug > 0){log.info("Request from client: " . $client_ip . " Using tunnelID:". $tunnelID . " as persistence value");} connection.setPersistenceKey( $tunnelID ); $choose_pool = data.get( $tunnelID ); if( $debug > 0 ) { log.info( "Matching this to pool: ".$choose_pool ); } if( $choose_pool ) { pool.select( $choose_pool ); } } else { log.error("Request from client: " . $client_ip . " Found no tunnelID:". $tunnelID . " in request for /ice/tunnel: This shouldn't happen, contact support."); } if ($debug > 1){log.info("Request from client: " . $client_ip . " Request is for /ice/tunnel - exiting rule to let it through");} #//Bypass Script if the path is for "/ice/tunnel" to let the desktop session start break; }
$JSESSIONID = http.getCookie("JSESSIONID"); if($JSESSIONID){ if ($debug > 0) {log.info("Request from client: " . $client_ip . " JSESSIONID=" .$JSESSIONID." found in the request...");} connection.setPersistenceKey($JSESSIONID); $choose_pool = data.get( $JSESSIONID ); if( $debug > 0 ) { log.info( "Matching this to pool: ".$choose_pool ); } if( $choose_pool ) { pool.select( $choose_pool ); } } else { if ($debug > 0) {log.info("Request from client: " . $client_ip . " No JSESSIONID found in the request...");} }
#//Bypass Script if the path is for / as it could be GLB health monitor if( $path == "/") { break;}
#// Collect the HTTP request headers and body $req_headers = http.getRequest(); if(!http.getHeader("Expect")=="100-continue"){ $req_body = http.getBody(); } if ($debug > 1){log.info("Request from client: " . $client_ip . " HTTP Request is:\n" . $req_headers);} if ($debug > 1){log.info("Request from client: " . $client_ip . " HTTP BODY is:\n" . $req_body);}
#// Reset flags to the needed defaults. $username = ''; $password = '';
#// Inspect the request and see if it is something we are interested in: #// syntax is xml.xpath.matchNodeSet( doc, nspacemap, query ) $is_xml = false; if (!string.regexmatch($req_body, '\<\?xml',"i")){ #//Document is _not_ an XML if ($debug > 1){log.info("Request from client: " . $client_ip . " Request is NOT an XML document - exiting");} $is_xml = false; break; } else { #//Document is an XML Doc $is_xml = true; if ($debug > 0){log.info("Request from client: " . $client_ip . " Request is an XML document");} }
#// test to see if we have been sent a "<get-configuration>" request $get_configuration = xml.xpath.matchNodeCount( $req_body, "", "//broker/get-configuration" ); if ($debug > 0){log.info("Request from client: " . $client_ip . " get-config is:" . $get_configuration);}
#// test to see if we have been sent a "<get-tunnel-connection>" request $get_tunnel_connection = xml.xpath.matchNodeCount( $req_body, "", "//broker/get-tunnel-connection" ); if ($debug > 0){log.warn("Request from client: " . $client_ip . " get-tunnel-connection is:" . $get_tunnel_connection);}
#// test to see if we have been sent a "<do-submit-authentication>" request $do_submit_authentication = xml.xpath.matchNodeCount( $req_body, "", "//broker/do-submit-authentication" ); if ($debug > 0){log.info("Request from client: " . $client_ip . " do-submit-authentication is:" . $do_submit_authentication);}
#// test to see if we have been sent a "<do-logout>" request $do_logout = xml.xpath.matchNodeCount( $req_body, "", "//broker/do-logout" ); if ($debug > 0){log.info("Request from client: " . $client_ip . " do-logout is:" . $do_logout);}
#// test to see if we have been sent a "<get-tunnel-connection>" request if ($get_tunnel_connection == 1 && $is_xml == true){ if( $debug > 0 ){ log.info( "Request from client: " . $client_ip . " <get-tunnel-connection> identified from: " . $client_ip ); } connection.data.set("connection_state", "get-tunnel-connection"); } #// If we have a <get-configuration> query, we will send the first response and exit if ($get_configuration == 1 && $is_xml == true){ if( $debug > 0 ){ log.info( "Request from client: " . $client_ip . " <get-configuration> response - Sending first_response to client: " . $client_ip ); } sendFirstResponse($view_info, $debug); break; } #// If we have been sent authentication credentials, we will go to work if ($do_submit_authentication == 1 && $is_xml == true){ if( $debug > 0 ){ log.info( "Request from client: " . $client_ip . " <do-submit-authentication> identified from: " . $client_ip ); }
$xml_user_credentials_data = xml.xpath.matchNodeSet($req_body,"","//broker/do-submit-authentication/screen/params/param/values/value/text()"); $xml_user_credentials_fieldnames = xml.xpath.matchNodeSet($req_body,"","//broker/do-submit-authentication/screen/params/param/name/text()");
#// we check that $xml_user_credentials_fieldnames contains "username, domain, password": if(string.regexmatch( $xml_user_credentials_fieldnames, "username, domain, password")){ if ($debug > 0){log.info("Request from client: " . $client_ip . " <do-submit-authentication>: extracted username, domain, password fields in submitted request.");} if ($debug > 1){log.info("Request from client: " . $client_ip . " <do-submit-authentication> extracted XML Fields: " .$xml_user_credentials_fieldnames);} if ($debug > 2){log.info("Request from client: " . $client_ip . " <do-submit-authentication> extracted XML Values: " .$xml_user_credentials_data);}
#//lets extract the username and password: $credentials = string.split($xml_user_credentials_data,","); $username = $credentials[0]; $password = $credentials[2]; #// Currently we don't need the domain name, so we won't extract it, but it is here for future use if needed #//$cred_domain = $credentials[1];
$auth = auth.query( $authenticator, $username, $password ); #// We should check to ensure auth.query returned successfully: #/// If $auth returns 'Error' then it means something is wrong with the authenticator and the admin needs to investigate if( $auth['Error'] ) { log.error( "Request from client: " . $client_ip ." Error with authenticator " . $authenticator . ": " . $auth['Error'] ); }
#// Lets extract the list of groups the user is a 'memberOf' $groups = $auth['memberOf']; #// If there is only one group, "$auth['memberOf']" will return a string, #// not an array, so we need to force the $groups value to be an array $group_isArray = lang.isarray($groups); if ($group_isArray != true){ if ($debug > 1){log.info("Connection From: " . $client_ip .": $auth['memberOf'] returned a single group, forcing $group to be an array");} $groups = lang.toArray($groups); }
if ($debug > 1){log.info("Request from client: " . $client_ip ." Full Auth Info: " . lang.dump($auth));} if ($debug > 1){log.info("Request from client: " . $client_ip ." Group Info: " . lang.dump($groups));}
#// Map Site B users to the Site B pool of servers foreach ( $group in $groups){ if( $debug > 0 ) {log.info("$group is" . lang.dump($group));}
if( string.contains( $group, $siteB_AD_Groupname ) ){ if( $debug > 0) { log.info( "Request from client: " . $client_ip ." User: ".$username." member of SiteB Users group" );} pool.select( $view_siteB_pool ); break; } else{ if( $debug > 0) { log.info( "Request from client: " . $client_ip ." User: ".$username." is NOT a member of SiteB Users group" );}
} } #// Map Site A users to the Site A pool of servers foreach ( $group in $groups){ if( string.contains( $group, $siteA_AD_Groupname ) ) { if( $debug > 0 ) { log.info( "Request from client: " . $client_ip ." User: ".$username." member of Default SiteA Users group" ) ;} pool.select( $view_siteA_pool ); break; } else{ if( $debug > 0) { log.info( "Request from client: " . $client_ip ." User: ".$username." is NOT a member of SiteA Users group" );}
} } } } #//end do-submit-authentication
if( $debug > 0 ) {log.info("#*#* End Reqeust Section *#*#");} } #// End of REQUEST Section
sub sendFirstResponse( $info, $debug ) { #//$first_response = '<?xml version="1.0"?><broker version="7.0"><configuration><result>ok</result><broker-guid>'.$info["guid"].'</broker-guid><broker-service-principal><type>kerberos</type><name>'.$info["server"].'[email protected]'.$info["dns"].'</name></broker-service-principal><authentication><screen><name>windows-password</name><params><param><name>domain</name><values><value>'.$info["domain"].'</value></values></param></params></screen></authentication></configuration></broker>'; $first_response = '<?xml version="1.0"?><broker version="7.0"><set-locale><result>ok</result></set-locale><configuration><result>ok</result><broker-guid>'.$info["guid"].'</broker-guid><broker-service-principal><type>kerberos</type><name>'.$info["server"].'[email protected]'.$info["dns"].'</name></broker-service-principal><authentication><screen><name>windows-password</name><params><param><name>domain</name><values><value>'.$info["domain"].'</value></values></param></params></screen></authentication></configuration></broker>'; if( $debug > 1 ){ log.info( "first_response data:\n" .$first_response); } http.sendResponse( "200 OK", "text/xml;charset=UTF-8", $first_response, "XFF: VTM_SiteA" ); } ###// AK: should be == if ($rulestate == "RESPONSE" ){ if( $debug > 0 ) {log.info("#*#* Start Response Section *#*#");} $debug_node = connection.getNode(); $debug_pool = connection.getPool(); $debug_http_response_code=http.getResponseCode();
$resp_headers = http.getResponseHeaders(); if ($debug > 1){log.info("Response to: " . $client_ip . " HTTP Response Headers are:" . lang.dump($resp_headers));}
#//$resp_body = http.getResponseBody( 96 ); #// What is the nature of the response? $content_type = http.getResponseHeader( "Content-Type" ); if ($debug > 1) { log.info("Content type of response is" . $content_type ); } if( string.startsWith( $content_type, "text/xml" ) ) { #// TODO: What if the response doesn't contain a </broker> tag? $resp_body = http.stream.readResponse( 4096, "</broker>" ); #// Limit was arbitrarily chosen } else { if( $debug > 0 ) {log.warn( "This response was not XML - not extracting content for logging." );} }
#// ASSUMPTION: Any XML response we care about (i.e. the one with the session ID in it) is less than 4096 bytes. #// If it's longer, then we'll just stream it to the client. if( string.length( $content_type ) < 4096 ) {
if ($debug > 1) { log.info( "Grabbed response body:\n" . $resp_body ); }
if( $debug > 0 ) {log.info("RESPONSE: connection was sent to node:" . $debug_node);} if( $debug > 0 ) {log.info("RESPONSE: connection was sent to pool:" . $debug_pool);} if( $debug > 0 ) {log.info("RESPONSE: Server Responded with code:" . $debug_http_response_code);} $response_cookies = http.getResponseCookies();
if($response_cookies){ if($response_cookies["JSESSIONID"]){ $JSESSIONID = $response_cookies["JSESSIONID"]; #// need to bypasS for <set-locale> message if (string.regexmatch($resp_body, '\<\?xml',"i")){ if( $debug > 0 ) {log.info("#### PARSING XML RESPONSE ###");} $set_locale = xml.xpath.matchNodeCount($resp_body,"","//broker/set-locale/result"); if( $debug > 0 ) {log.info("#### SETLOCALE IS: ". $set_locale . " ###");} if (!$set_locale){ connection.setPersistenceKey( $JSESSIONID ); data.set( $JSESSIONID, $debug_pool ); if ($debug > 0) {log.info("Response Rule - Request from client: " . $client_ip . " Response set JSESSIONID=" . $JSESSIONID .". Extracting and using for persistence key");} } else { if ($debug > 0) {log.info("Response Rule - Request from client: " . $client_ip . " Response set JSESSIONID=" . $JSESSIONID .". _NOT_ extracting and using for persistence key");} } } else { if( $debug > 0 ) {log.warn( "Response data did not seem to be XML." );} } } } else { if ($debug > 0) {log.info("Response Rule - Request from client: " . $client_ip . " No JSESSIONID found in response...");} }
if (connection.data.get("connection_state") == "get-tunnel-connection"){ if ($debug > 0){log.info("Response to: " . $client_ip . " HTTP Response Headers are:" . lang.dump($resp_headers));} if ($debug > 0){log.info("Response to: " . $client_ip . " HTTP Response BODY is:" . $resp_body);}
#// Inspect the response and see if it is something we are interested in: #// syntax is xml.xpath.matchNodeSet( doc, nspacemap, query ) if (string.regexmatch($resp_body, '\<\?xml',"i")){ #//Document is an XML Doc if ($debug > 0){log.info("Request from client: " . $client_ip . " Response to <get-tunnel-connection> is an XML document");} $tunnelID = xml.xpath.matchNodeSet($resp_body,"","//broker/tunnel-connection/connection-id/text()"); if ($tunnelID){ if ($debug > 0) {log.info("Response Rule - Request from client: " . $client_ip . " Response set tunnelID=" . $tunnelID .". Extracting and using for persistence key");} connection.setPersistenceKey( $tunnelID ); data.set( $tunnelID, $debug_pool ); } } else { lang.warn( "Didn't think response body was XML." ); } } #// End of connection_state == get-tunnel-connection
if ($debug > 1){log.info("Response to: " . $client_ip . " HTTP Response Headers are:" . lang.dump($resp_headers));} if ($debug > 1){log.info("Response to: " . $client_ip . " HTTP Response BODY is:" . $resp_body);} }
if( $resp_body ) { http.stream.startResponse( http.getResponseCode(), $content_type, "" ); http.stream.writeResponse( $resp_body ); http.stream.continueFromBackend(); }
if( $debug > 0 ) {log.info("#*#* End Response Section *#*#");} } ##/// End of RESPONSE Section#// |