1c52aaa92828b6ecc18dd5800fe6650b91daf696 max Fri Sep 18 07:22:10 2026 -0700 hgSession: the new Sessions page also lists the sessions saved on our other servers #Preview2 week - bugs introduced now will need a build patch to fix Every geo mirror node keeps its own namedSessionDb, so a session saved on genome-euro is invisible on genome.ucsc.edu and people do not find it again. The table now holds the sessions from every node, with a new Server column saying where each one lives, and the session name links to the server that holds it. Two new hgSession actions, both answered in main() before the cart is built, so that a request from another node leaves no cart, userDb or sessionDb row behind: hgS_doSessionListJson returns this server's sessions for the user named by the login cookie, as JSON. This is what the other nodes call. hgS_doMirrorSessions asks every other gbNode for that list and returns what came back. It sends all of the requests before reading any of the answers, so the wait is the slowest node rather than the sum of them. The fetch goes through our own server rather than from the browser to the other nodes, which keeps cross-origin requests out of it: apache handles the CORS headers differently on every node, and the two mirrors are administered by Bielefeld and RIKEN, while CGI code reaches them with the release. The user's login cookies, and only those, are passed on (wikiLinkLoginCookieHeader), which works because the nodes share a cookie salt. The page never waits for any of this: the table is drawn from this server's own sessions and the rows from the other servers are merged in as they arrive, keeping the current sort, search and page. A node that does not answer, times out, or runs a release without the endpoint is named in a line under the toolbar instead, and costs nothing else. Sessions from another server are shown but not managed here: no Share, Edit, Overwrite or Delete, no bulk-select checkbox, and the Update now shortcut keeps ignoring them. Table rows are keyed by a new uid rather than by the session name, since the same name can now be in the table twice, once per server. geoMirrorThisNode() and geoMirrorOtherNodes() read the node list from the hgcentral gbNode table, so nothing new has to be configured: browser.node already says which node a server is. diff --git src/hg/lib/geoMirror.c src/hg/lib/geoMirror.c index 3a905321e17..595a01b75ea 100644 --- src/hg/lib/geoMirror.c +++ src/hg/lib/geoMirror.c @@ -1,280 +1,322 @@ /* geoMirror - support geographic based mirroring (e.g. euro and asia nodes) */ /* Copyright (C) 2014 The Regents of the University of California * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */ #include "common.h" #include "geoMirror.h" #include "hgConfig.h" #include "internet.h" /* geographic server (mirror) support Customize 'Mirrors' drop-down menu based on current server. If the server is a UCSC-sponsored site (USA/Ca or European), show this in drop-down menu with a checkmark, and allow user to change server. Based on design notes here: http://genomewiki.ucsc.edu/genecats/index.php/Euronode NOTE: Uses hgcentral.gbNode table to populate menu items and locate redirects. This implementation uses the spec from above wiki page: browser.node=1 -> US server (genome.ucsc.edu) browser.node=2 -> European server (genome-euro.ucsc.edu) browser.node=3 -> Asian server (genome-asia.ucsc.edu) For Testing, use browser.geoSuffix: e.g. browser.geoSuffix=Test will cause it to use in hgcentral the tables gbNodeTest and geoIpNodeTest instead. See the genomewiki link above for more documentation on testing. */ boolean geoMirrorEnabled() { // return TRUE if this site has geographic mirroring turned on return geoMirrorNode() != NULL; } char *geoMirrorNode() { // return which geo mirror node this is (or NULL if geo mirroring is turned off) return cfgOption("browser.node"); } int geoMirrorDefaultNode4(struct sqlConnection *centralConn, char *ipStr) // return default node for given IP { char query[1024]; bits32 ip = 0; int defaultNode = 1; if (isIpv6Address(ipStr)) // ipv6 which we do not handle yet. TODO return defaultNode; // at least tolerate IPV6 remote addr internetDottedQuadToIp(ipStr, &ip); char *geoSuffix = cfgOptionDefault("browser.geoSuffix",""); // We (sort-of) assume no overlaps in geoIpNode table, so we can use limit 1 to make query very efficient; // we do accomodate a range that is completely contained in another (to accomodate the hgroaming entry for testing); // this is accomplished by "<= ipEnd" in the sql query. // TODO The hgroaming thing is probably obsolete and testing is done with browser.geoSuffix= instead. // If so, we may wish to remove the loop below since that was added by Larry and reformulate // it as it was originally done by Galt. However it does not seem to affect performance so we can leave it for now. sqlSafef(query, sizeof query, "select ipStart, ipEnd, node from geoIpNode%s where %u >= ipStart and %u <= ipEnd order by ipStart desc limit 1" , geoSuffix, ip, ip); char **row; struct sqlResult *sr = sqlGetResult(centralConn, query); if ((row = sqlNextRow(sr)) != NULL) { uint ipStart = sqlUnsigned(row[0]); uint ipEnd = sqlUnsigned(row[1]); if (ipStart <= ip && ipEnd >= ip) { defaultNode = sqlSigned(row[2]); } } sqlFreeResult(&sr); return defaultNode; } int geoMirrorDefaultNode6(struct sqlConnection *centralConn, char *ipStr) // return default node for given IP { char query[1024]; char newIpStr[NI_MAXHOST]; struct in6_addr ip; ZeroVar(&ip); char ipHex[33]; int defaultNode = 1; if (isIpv6Address(ipStr)) safef(newIpStr, sizeof newIpStr, "%s", ipStr); else if (isIpv4Address(ipStr)) safef(newIpStr, sizeof newIpStr, "%s%s", IPV4MAPPED_PREFIX, ipStr); // "::ffff:" else { warn("Unexpected strange ip address string: %s", ipStr); return defaultNode; } if (!internetIpStringToIp6(newIpStr, &ip)) errAbort("internetIpStringToIp6 failed for %s", ipStr); ip6AddrToHexStr(&ip, ipHex, sizeof ipHex); char *geoSuffix = cfgOptionDefault("browser.geoSuffix",""); // We (sort-of) assume no overlaps in geoIpNode table, so we can use limit 1 to make query very efficient; // we do accomodate a range that is completely contained in another (to accomodate the hgroaming entry for testing); // this is accomplished by "<= ipEnd" in the sql query. // TODO The hgroaming thing is probably obsolete and testing is done with browser.geoSuffix= instead. // If so, we may wish to remove the loop below since that was added by Larry and reformulate // it as it was originally done by Galt. However it does not seem to affect performance so we can leave it for now. sqlSafef(query, sizeof query, "select ipStart, ipEnd, node from geoIpNode6%s where unhex('%s') >= ipStart and unhex('%s') <= ipEnd order by ipStart desc limit 1" , geoSuffix, ipHex, ipHex); char **row; struct sqlResult *sr = sqlGetResult(centralConn, query); if ((row = sqlNextRow(sr)) != NULL) { struct in6_addr ipStart; ip6AddrCopy((struct in6_addr *)row[0], &ipStart); struct in6_addr ipEnd; ip6AddrCopy((struct in6_addr *)row[1], &ipEnd ); if ( (ip6AddrCmpBits(&ipStart, &ip) <= 0) && (ip6AddrCmpBits(&ipEnd , &ip) >= 0) ) { defaultNode = sqlSigned(row[2]); } } sqlFreeResult(&sr); return defaultNode; } int geoMirrorDefaultNode(struct sqlConnection *centralConn, char *ipStr) // return default node for given IP { char *geoSuffix = cfgOptionDefault("browser.geoSuffix",""); char fullTableName[1024]; safef(fullTableName, sizeof fullTableName, "%s%s", "geoIpNode6", geoSuffix); int defaultNode = 1; if (sqlTableExists(centralConn, fullTableName)) { defaultNode = geoMirrorDefaultNode6(centralConn, ipStr); } else { defaultNode = geoMirrorDefaultNode4(centralConn, ipStr); } return defaultNode; } char *geoMirrorCountry6(struct sqlConnection *centralConn, char *ipStr) /* Return 2 letter country code for given IP. user has already checked table geoIpCountry6 exists. * Return error string otherwise. Free the response string. */ { char query[1024]; char newIpStr[NI_MAXHOST]; struct in6_addr ip; ZeroVar(&ip); char ipHex[33]; char response[256]; safef(response, sizeof response, "not found"); if (isIpv6Address(ipStr)) safef(newIpStr, sizeof newIpStr, "%s", ipStr); else if (isIpv4Address(ipStr)) safef(newIpStr, sizeof newIpStr, "%s%s", IPV4MAPPED_PREFIX, ipStr); // "::ffff:" else { warn("Unexpected strange ip address string: %s", ipStr); safef(response, sizeof response, "Unexpected strange ip address string: %s", ipStr); return cloneString(response); } if (!internetIpStringToIp6(newIpStr, &ip)) errAbort("internetIpStringToIp6 failed for %s", ipStr); ip6AddrToHexStr(&ip, ipHex, sizeof ipHex); char *geoSuffix = cfgOptionDefault("browser.geoSuffix",""); // We (sort-of) assume no overlaps in geoIpCountry6 table, so we can use limit 1 to make query very efficient; // we do accomodate a range that is completely contained in another (to accomodate the hgroaming entry for testing); // this is accomplished by "<= ipEnd" in the sql query. // TODO The hgroaming thing is probably obsolete and testing is done with browser.geoSuffix= instead. // If so, we may wish to remove the loop below since that was added by Larry and reformulate // it as it was originally done by Galt. However it does not seem to affect performance so we can leave it for now. sqlSafef(query, sizeof query, "select ipStart, ipEnd, countryId from geoIpCountry6%s where unhex('%s') >= ipStart and unhex('%s') <= ipEnd order by ipStart desc limit 1" , geoSuffix, ipHex, ipHex); char **row; struct sqlResult *sr = sqlGetResult(centralConn, query); if ((row = sqlNextRow(sr)) != NULL) { struct in6_addr ipStart; ip6AddrCopy((struct in6_addr *)row[0], &ipStart); struct in6_addr ipEnd; ip6AddrCopy((struct in6_addr *)row[1], &ipEnd ); if ( (ip6AddrCmpBits(&ipStart, &ip) <= 0) && (ip6AddrCmpBits(&ipEnd , &ip) >= 0) ) { safef(response, sizeof response, "%s", row[2]); } } sqlFreeResult(&sr); return cloneString(response); } +static struct slPair *geoMirrorNodeList(boolean wantSelf) +/* Return gbNode as pairs of name=shortLabel, val=domain, ordered by node: either every node but + * this one (wantSelf FALSE) or only this one (wantSelf TRUE). */ +{ +if (!geoMirrorEnabled()) + return NULL; +char *myNode = geoMirrorNode(); +char *geoSuffix = cfgOptionDefault("browser.geoSuffix",""); +char query[256]; +sqlSafef(query, sizeof query, "SELECT node, domain, shortLabel from gbNode%s order by node", + geoSuffix); +struct sqlConnection *conn = hConnectCentral(); +struct sqlResult *sr = sqlGetResult(conn, query); +struct slPair *nodes = NULL; +char **row = NULL; +while ((row = sqlNextRow(sr)) != NULL) + { + if (sameString(row[0], myNode) != wantSelf) + continue; + slPairAdd(&nodes, row[2], cloneString(row[1])); + } +sqlFreeResult(&sr); +hDisconnectCentral(&conn); +slReverse(&nodes); +return nodes; +} + +struct slPair *geoMirrorThisNode() +/* Return this node (browser.node) as a single pair of name=shortLabel, val=domain, or NULL when + * geo mirroring is off or gbNode has no row for it. slPairFreeValsAndList when done. */ +{ +return geoMirrorNodeList(TRUE); +} + +struct slPair *geoMirrorOtherNodes() +/* Return the other geo mirror nodes, as pairs of name=shortLabel, val=domain, ordered by node. + * The node this CGI is running on (browser.node) is left out. Returns NULL when geo mirroring + * is off or this is the only node. slPairFreeValsAndList when done. */ +{ +return geoMirrorNodeList(FALSE); +} + char *geoMirrorMenu() /* Create customized geoMirror menu string for substitution of into * in htdocs/inc/globalNavBar.inc * Reads hgcentral geo tables and hg.conf settings. * Free the returned string when done. */ { struct dyString *dy = dyStringNew(0); // by default replacment is just an empty string. if (geoMirrorEnabled()) { dyStringAppend(dy, "

  • \n"); // Geo mirror functionality (e.g. in nav bar) char *myNode = geoMirrorNode(); /* hgcentral.gbNode table so UI can share w/ browser GEO mirror redirect code */ char **row = NULL; struct sqlConnection *conn = hConnectCentral(); // after hClade since it access hgcentral too // get the gbNode table char *geoSuffix = cfgOptionDefault("browser.geoSuffix",""); char query[256]; sqlSafef(query, sizeof query, "SELECT node, domain, shortLabel from gbNode%s order by node", geoSuffix); struct sqlResult *sr = sqlGetResult(conn, query); while ((row = sqlNextRow(sr)) != NULL) { char *node = row[0]; char *domain = row[1]; char *shortLabel = row[2]; dyStringPrintf(dy, "
  • \n"); dyStringAppend(dy, "\"X\"\n"); if (!sameString(node, myNode)) dyStringPrintf(dy, "", domain); dyStringPrintf(dy, "%s", shortLabel); if (!sameString(node, myNode)) dyStringAppend(dy, ""); dyStringAppend(dy, "
  • \n"); } sqlFreeResult(&sr); hDisconnectCentral(&conn); } return dyStringCannibalize(&dy); }