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#//
... View more