Files
SLikeNet/Samples/AutopatcherServer_SelfScaling/main.cpp
2025-11-24 14:19:51 +05:30

1413 lines
49 KiB
C++

/*
* Original work: Copyright (c) 2014, Oculus VR, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* RakNet License.txt file in the licenses directory of this source tree. An additional grant
* of patent rights can be found in the RakNet Patents.txt file in the same directory.
*
*
* Modified work: Copyright (c) 2016-2020, SLikeSoft UG (haftungsbeschränkt)
*
* This source code was modified by SLikeSoft. Modifications are licensed under the MIT-style
* license found in the license.txt file in the root directory of this source tree.
*/
// How to patch
// 1. Create a new server instance using the most recent image (optional)
// 2. Connect to the new (or any existing) instance through remote desktop. Immediately set max concurrent users to 0. Wait for users to finish patching, if any started.
// 3. Update the patch
// 4. Image the server
// 5. Wait for the image to complete (press l to check, or use Rackspace control panel. Takes like half an hour).
// 6. When the image completes, set the image spawn from the 'l' option to the image that was created in step 4
// 6. Send terminate command. Servers running the old patch will shutdown automatically.
// 9. Set max concurrent users back above 0.
// 10. Optional: Manually call SpawnServers() if load is immediately anticipated
// Common includes
#include <stdio.h>
#include <stdlib.h>
#include <limits> // used for std::numeric_limits
#include "slikenet/Kbhit.h"
#include "slikenet/GetTime.h"
#include "slikenet/peerinterface.h"
#include "slikenet/MessageIdentifiers.h"
#include "slikenet/BitStream.h"
#include "slikenet/StringCompressor.h"
#include "slikenet/FileListTransfer.h"
#include "slikenet/FileList.h" // FLP_Printf
#include "slikenet/PacketizedTCP.h"
#include "slikenet/Gets.h"
#include "CloudServerHelper.h"
#include "slikenet/FullyConnectedMesh2.h"
#include "slikenet/TwoWayAuthentication.h"
#include "slikenet/CloudClient.h"
#include "slikenet/DynDNS.h"
#include "slikenet/peerinterface.h"
#include "slikenet/sleep.h"
#include "slikenet/ConnectionGraph2.h"
#include "CloudServerHelper.h"
#include "slikenet/HTTPConnection2.h"
#include "Rackspace2.h"
#include "slikenet/GetTime.h"
#include "slikenet/linux_adapter.h"
#include "slikenet/osx_adapter.h"
// See http://www.digip.org/jansson/doc/2.4/
// This is used to make it easier to parse the JSON returned from the master server
#include "jansson.h"
// Server only includes
#include "AutopatcherServer.h"
// Replace this repository with your own implementation if you don't want to use PostgreSQL
#include "AutopatcherPostgreRepository.h"
#ifdef _WIN32
#include "slikenet/WindowsIncludes.h" // Sleep
#else
#include <unistd.h> // usleep
#endif
#define LISTEN_PORT_TCP_PATCHER 60000
using namespace SLNet;
static const SLNet::Time LOAD_CHECK_INTERVAL=1000*60*5;
static const SLNet::Time LOAD_CHECK_INTERVAL_AFTER_SPAWN=1000*60*60;
char databasePassword[128];
int workerThreadCount;
int sqlConnectionObjectCount;
int allowDownloadingUnmodifiedFiles;
unsigned short autopatcherLoad=0;
SLNet::RakPeerInterface *g_rakPeer;
SLNet::AutopatcherServer *autopatcherServer;
enum AppState
{
AP_RUNNING,
AP_TERMINATE_WHEN_ZERO_USERS_REACHED,
AP_TERMINATE_IF_NOT_DNS_HOST,
AP_TERMINATING,
AP_TERMINATED,
} appState;
SLNet::Time timeSinceZeroUsers;
struct CloudServerHelper_RackspaceCloudDNS : public CloudServerHelper, public Rackspace2EventCallback
{
public:
enum CSHState
{
AUTHENTICATING,
AUTHENTICATED,
CANNOT_FIND_PATCHER_DOMAIN,
CANNOT_FIND_PATCHER_RECORD,
GETTING_SERVERS,
GOT_MY_SERVER,
CANNOT_FIND_MY_SERVER,
GETTING_DOMAINS,
GOT_DOMAIN,
GETTING_RECORDS,
GOT_RECORDS,
} cshState;
CloudServerHelper_RackspaceCloudDNS() {
rackspace2= SLNet::OP_NEW<Rackspace2>(_FILE_AND_LINE_);
memset(&thisServerDetail, 0, sizeof(ServerDetail));
}
~CloudServerHelper_RackspaceCloudDNS() {
SLNet::OP_DELETE(rackspace2,_FILE_AND_LINE_);
}
virtual bool Update(void) {
UpdateTCP();
return true;
}
void CopyServerDetails(json_t *arrayElement, const char *publicIPV4)
{
strcpy_s(thisServerDetail.publicIPV4, publicIPV4);
strcpy_s(thisServerDetail.id, json_string_value(json_object_get(arrayElement, "id")));
json_t *privateAddressesArray = json_object_get(json_object_get(arrayElement, "addresses"),"private");
size_t privateAddressesArraySize = json_array_size(privateAddressesArray);
for (size_t l=0; l < privateAddressesArraySize; l++)
{
json_t *privateAddressElement = json_array_get(privateAddressesArray, l);
if (json_integer_value(json_object_get(privateAddressElement, "version"))==4)
{
strcpy_s(thisServerDetail.privateIPV4,json_string_value(json_object_get(privateAddressElement, "addr")));
break;
}
}
strcpy_s(thisServerDetail.flavorId,json_string_value(json_object_get(json_object_get(arrayElement, "flavor"),"id")));
strcpy_s(thisServerDetail.hostId,json_string_value(json_object_get(arrayElement, "hostId")));
strcpy_s(thisServerDetail.id,json_string_value(json_object_get(arrayElement, "id")));
strcpy_s(thisServerDetail.imageId,json_string_value(json_object_get(json_object_get(arrayElement, "image"),"id")));
strcpy_s(thisServerDetail.name,json_string_value(json_object_get(arrayElement, "name")));
strcpy_s(spawningImageId, thisServerDetail.imageId);
strcpy_s(spawningFlavorId, thisServerDetail.flavorId);
printf("Using image id %s for new servers\n", spawningImageId);
}
virtual void OnTCPFailure(void){ printf("--ERROR--: OnTCPFailure()\n"); }
virtual void OnTransmissionFailed(HTTPConnection2 *httpConnection2, RakString postStr, RakString authURLDomain)
{
printf("--ERROR--: OnTransmissionFailed().\npostStr=%s\nauthURLDomain=%s\n", postStr.C_String(), authURLDomain.C_String());
RakSleep(30000);
printf("Retrying...\n");
httpConnection2->TransmitRequest(postStr,authURLDomain, 443, true);
}
virtual void OnEmptyResponse(RakString stringTransmitted)
{
printf("--ERROR--: OnEmptyResponse(). stringTransmitted=%s", stringTransmitted.C_String());
}
virtual void OnMessage(const char *message, RakString responseReceived, RakString stringTransmitted, ptrdiff_t contentOffset)
{
// unused parameters
(void)responseReceived;
(void)contentOffset;
printf("--WARNING--: OnMessage(). message=%s\nstringTransmitted=%s", message, stringTransmitted.C_String());
}
virtual void OnResponse(Rackspace2ResponseCode r2rc, RakString responseReceived, ptrdiff_t contentOffset)
{
if (r2rc==R2RC_AUTHENTICATED)
{
printf("Authenticated with Rackspace\nX-Auth-Token: %s\n", rackspace2->GetAuthToken());
cshState=AUTHENTICATED;
json_error_t error;
json_t *root = json_loads(strstr(responseReceived.C_String() + contentOffset, "{"), JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK, &error);
json_t *accessJson = json_object_get(root, "access");
json_t *userJson = json_object_get(accessJson, "user");
json_t *regionJson = json_object_get(userJson, "RAX-AUTH:defaultRegion");
const char *userRegionStr = json_string_value(regionJson);
json_t *serviceCatalogArray = json_object_get(accessJson, "serviceCatalog");
size_t arraySize = json_array_size(serviceCatalogArray);
size_t i;
printf("Services:\n");
for (i=0; i < arraySize; i++)
{
json_t *arrayElement = json_array_get(serviceCatalogArray, i);
json_t *elementNameJson = json_object_get(arrayElement, "name");
const char *elementNameStr = json_string_value(elementNameJson);
json_t *elementTypeJson = json_object_get(arrayElement, "type");
const char *elementTypeStr = json_string_value(elementTypeJson);
printf("%i. name=%s type=%s ", i+1, elementNameStr, elementTypeStr);
json_t *endpointArray = json_object_get(arrayElement, "endpoints");
size_t endpointArraySize = json_array_size(endpointArray);
size_t j;
char *myPublicURLStr=0;
for (j=0; j < endpointArraySize; j++)
{
json_t *endpointArrayElement = json_array_get(endpointArray, j);
json_t *publicURLJson = json_object_get(endpointArrayElement, "publicURL");
const char *publicURLStr = json_string_value(publicURLJson);
regionJson = json_object_get(endpointArrayElement, "region");
if (regionJson)
{
const char *regionStr = json_string_value(regionJson);
if (strcmp(regionStr, userRegionStr)==0)
{
printf("My URL=%s", publicURLStr);
myPublicURLStr=(char*) publicURLStr;
break;
}
}
else
{
printf("My URL=%s", publicURLStr);
myPublicURLStr=(char*) publicURLStr;
break;
}
}
if (j==endpointArraySize)
printf("My URL=<unknown>");
if (myPublicURLStr)
{
if (strcmp(elementNameStr, "cloudDNS")==0)
strcpy_s(rackspaceDNSURL, myPublicURLStr);
else if (strcmp(elementNameStr, "cloudServersOpenStack")==0)
strcpy_s(rackspaceServersURL, myPublicURLStr);
}
printf("\n");
}
if (appState==AP_TERMINATING || appState==AP_TERMINATED ||
appState==AP_TERMINATE_WHEN_ZERO_USERS_REACHED || appState==AP_TERMINATE_IF_NOT_DNS_HOST)
Terminate();
json_decref(root);
}
else if (r2rc==R2RC_GOT_SERVERS)
{
json_error_t error;
json_t *root = json_loads(strstr(responseReceived.C_String() + contentOffset, "{"), JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK, &error);
json_t *serversArray = json_object_get(root, "servers");
size_t arraySize = json_array_size(serversArray);
size_t i;
for (i=0; i < arraySize && cshState==GETTING_SERVERS; i++)
{
json_t *arrayElement = json_array_get(serversArray, i);
//json_t *publicAddressesArray = json_object_get(json_object_get(arrayElement, "addresses"),"public");
//size_t publicAddressesArraySize = json_array_size(publicAddressesArray);
//for (size_t j=0; j < publicAddressesArraySize; j++)
//{
for (unsigned k=0; k < g_rakPeer->GetNumberOfAddresses(); k++)
{
if (strcmp(g_rakPeer->GetLocalIP(k), json_string_value(json_object_get(arrayElement, "accessIPv4")))==0)
{
CopyServerDetails(arrayElement, g_rakPeer->GetLocalIP(k));
cshState=GOT_MY_SERVER;
break;
}
}
//}
}
if (cshState==GETTING_SERVERS)
{
// printf("Could not find a server with this IP address\n");
// printf("Continue anyway (y/n)?\n");
// if (_getch()=='y')
// {
for (unsigned k=0; k < g_rakPeer->GetNumberOfAddresses(); k++)
{
SystemAddress sa = g_rakPeer->GetInternalID(UNASSIGNED_SYSTEM_ADDRESS,k);
if (sa.IsLANAddress()==false)
{
json_t *arrayElement = json_array_get(serversArray, 0);
CopyServerDetails(arrayElement, g_rakPeer->GetLocalIP(0));
cshState=GOT_MY_SERVER;;
break;
}
}
// }
// else
// cshState=CANNOT_FIND_MY_SERVER;
}
json_decref(root);
}
else if (r2rc==R2RC_GOT_IMAGES)
{
json_error_t error;
json_t *root = json_loads(strstr(responseReceived.C_String() + contentOffset, "{"), JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK, &error);
json_t *domainsArray = json_object_get(root, "images");
size_t arraySize = json_array_size(domainsArray);
size_t i;
printf("Got %i images:\n", arraySize);
for (i=0; i < arraySize; i++)
{
json_t *arrayElement = json_array_get(domainsArray, i);
printf("%i. name=%s ", i+1, json_string_value(json_object_get(arrayElement, "name")));
printf("progress=%" JSON_INTEGER_FORMAT " ", json_integer_value(json_object_get(arrayElement, "progress")));
printf("status=%s ", json_string_value(json_object_get(arrayElement, "status")));
printf("created=%s ", json_string_value(json_object_get(arrayElement, "created")));
printf("id=%s\n", json_string_value(json_object_get(arrayElement, "id")));
}
printf("\n");
if (arraySize>0)
{
printf("Select image to spawn for new servers?: ");
char str[32];
Gets(str, sizeof(str));
if (str[0])
{
int idx = atoi(str);
if (idx>=1 && static_cast<unsigned>(idx) <= arraySize)
{
json_t *arrayElement = json_array_get(domainsArray, idx-1);
strcpy_s(spawningImageId, json_string_value(json_object_get(arrayElement, "id")));
// Use spawningFlavorId of whatever it was when the server was started, I don't care about flavor
printf("Using image id %s for new servers\n", spawningImageId);
}
}
else
printf("Aborted.\n");
}
json_decref(root);
}
else if (r2rc==R2RC_GOT_RECORDS)
{
json_error_t error;
json_t *root = json_loads(strstr(responseReceived.C_String() + contentOffset, "{"), JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK, &error);
json_t *domainsArray = json_object_get(root, "records");
size_t arraySize = json_array_size(domainsArray);
size_t i;
for (i=0; i < arraySize; i++)
{
json_t *arrayElement = json_array_get(domainsArray, i);
json_t *elementNameJson = json_object_get(arrayElement, "name");
const char *elementNameStr = json_string_value(elementNameJson);
if (strcmp(elementNameStr, patcherHostSubdomainURL)==0)
{
arrayElement = json_array_get(domainsArray, i);
json_t *elementIPJson = json_object_get(arrayElement, "data");
strcpy_s(dnsHostIP, json_string_value(elementIPJson));
json_t *elementIDJson = json_object_get(arrayElement, "id");
strcpy_s(patchHostRecordID,json_string_value(elementIDJson));
timeOfLastDNSHostCheck= SLNet::GetTime();
if (appState==AP_TERMINATE_IF_NOT_DNS_HOST)
{
if (strcmp(dnsHostIP, g_rakPeer->GetLocalIP(0))==0)
{
// We are dns host. Just keep running
appState=AP_RUNNING;
}
else
{
if (timeSinceZeroUsers!=0)
Terminate();
else
appState=AP_TERMINATE_WHEN_ZERO_USERS_REACHED;
}
}
cshState=GOT_RECORDS;
break;
}
}
if (i==arraySize)
{
printf("Can't find %s in record list\n", patcherHostSubdomainURL);
cshState=CANNOT_FIND_PATCHER_RECORD;
}
json_decref(root);
}
else if (r2rc==R2RC_GOT_DOMAINS)
{
json_error_t error;
json_t *root = json_loads(strstr(responseReceived.C_String() + contentOffset, "{"), JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK, &error);
json_t *domainsArray = json_object_get(root, "domains");
size_t arraySize = json_array_size(domainsArray);
size_t i;
for (i=0; i < arraySize; i++)
{
json_t *arrayElement = json_array_get(domainsArray, i);
json_t *elementNameJson = json_object_get(arrayElement, "name");
const char *elementNameStr = json_string_value(elementNameJson);
if (strcmp(elementNameStr, "raknetpatcher.com")==0)
{
// Primary domain
json_t *idNameJson = json_object_get(arrayElement, "id");
raknetPatcherDomainId = (int) json_integer_value(idNameJson);
RakString url("%s/domains/%i/records", rackspaceDNSURL, raknetPatcherDomainId );
rackspace2->AddOperation(url,Rackspace2::OT_GET,0, true);
cshState=GETTING_RECORDS;
break;
}
}
json_decref(root);
if (i==arraySize)
{
printf("Can't find raknetpatcher.com in domain list\n");
cshState=CANNOT_FIND_PATCHER_DOMAIN;
}
}
else
{
if (r2rc==R2RC_UNAUTHORIZED)
{
printf("Unauthorized\n");
// Try to reauthenticate
rackspace2->Authenticate(rackspaceAuthenticationURL, rackspaceCloudUsername, apiAccessKey);
}
else
{
printf("General response failure: %s\n", responseReceived.C_String());
}
}
}
virtual bool UpdateTCP() {
rackspace2->Update();
return false;
}
void Terminate(void)
{
appState=AP_TERMINATING;
// http://docs.rackspace.com/servers/api/v2/cs-devguide/content/Delete_Server-d1e2883.html
RakString url("%s/servers/%s", rackspaceServersURL, thisServerDetail.id);
rackspace2->AddOperation(url,Rackspace2::OT_DELETE,0, true);
}
void GetCustomImages(void)
{
// curl -k https://ord.servers.api.rackspacecloud.com/v2/570016/images?type=SNAPSHOT --header "X-Auth-Token: 97d8335b-7b9e-4a74-96a1-2a3a043c1000" --header "Content-Type: application/json"
// List images
// http://docs.rackspace.com/servers/api/v2/cs-devguide/content/List_Images-d1e4435.html
RakString url("%s/images/detail?type=SNAPSHOT", rackspaceServersURL);
rackspace2->AddOperation(url,Rackspace2::OT_GET,0, true);
}
void ImageThisServer(void)
{
// Create image
// date
// http://docs.rackspace.com/servers/api/v2/cs-devguide/content/Create_Image-d1e4655.html
time_t aclock;
time( &aclock ); // Get time in seconds
tm newtime;
localtime_s( &newtime, &aclock ); // Convert time to struct tm form
char text[1024];
sprintf_s(text, "%s_%s", patcherHostSubdomainURL, asctime( &newtime ));
RakString url("%s/servers/%s/action", rackspaceServersURL, thisServerDetail.id);
json_t *jsonObjectRoot = json_object();
json_t *jsonObjectCreateImage = json_object();
json_t *jsonMetaData = json_object();
json_object_set(jsonObjectCreateImage, "name", json_string(text));
json_object_set(jsonObjectRoot, "createImage", jsonObjectCreateImage);
json_object_set(jsonObjectCreateImage, "metadata", jsonMetaData);
json_object_set(jsonMetaData, "ImageType", json_string("snapshot"));
printf("Imaging server with name %s\n", text);
rackspace2->AddOperation(url,Rackspace2::OT_POST,jsonObjectRoot, true);
}
void SpawnServers(unsigned count)
{
if (count==0)
return;
RakAssert(count <= 4);
// http://docs.rackspace.com/servers/api/v2/cs-devguide/content/CreateServers.html
RakString url("%s/servers", rackspaceServersURL);
time_t aclock;
time( &aclock ); // Get time in seconds
tm newtime;
localtime_s( &newtime, &aclock ); // Convert time to struct tm form
char text[1024];
for (unsigned int i=0; i < count; i++)
{
json_t *jsonObjectRoot = json_object();
json_t *jsonObjectServer = json_object();
json_object_set(jsonObjectRoot, "server", jsonObjectServer);
sprintf_s(text, "%s_%s_%u", patcherHostSubdomainURL, asctime( &newtime ), i);
json_object_set(jsonObjectServer, "name", json_string(text));
json_object_set(jsonObjectServer, "imageRef", json_string(spawningImageId));
json_object_set(jsonObjectServer, "flavorRef", json_string(spawningFlavorId));
rackspace2->AddOperation(url,Rackspace2::OT_POST,jsonObjectRoot, true);
}
}
void GetThisServer(void)
{
// http://docs.rackspace.com/servers/api/v2/cs-devguide/content/List_Servers-d1e2078.html
cshState=GETTING_SERVERS;
RakString url("%s/servers/detail?status=ACTIVE", rackspaceServersURL);
//RakString url("%s/servers/detail", rackspaceServersURL);
rackspace2->AddOperation(url,Rackspace2::OT_GET,0, true);
}
void GetDomainRecords(void) {
cshState=GETTING_DOMAINS;
RakString url("%s/domains/?name=%s", rackspaceDNSURL, "raknetpatcher.com");
rackspace2->AddOperation(url,Rackspace2::OT_GET,0, true);
}
bool AuthenticateWithRackspaceBlocking(void) {
rackspace2->SetEventCallback(this);
rackspace2->Authenticate(rackspaceAuthenticationURL, rackspaceCloudUsername, apiAccessKey);
cshState=AUTHENTICATING;
while (cshState==AUTHENTICATING)
{
UpdateTCP();
RakSleep(30);
}
if (cshState!=AUTHENTICATED)
return false;
GetThisServer();
while (cshState==GETTING_SERVERS)
{
UpdateTCP();
RakSleep(30);
}
if (cshState!=GOT_MY_SERVER)
{
return false;
}
// http://docs.rackspace.com/cdns/api/v1.0/cdns-devguide/content/list_domains.html
GetDomainRecords();
while (cshState==GETTING_DOMAINS || cshState==GETTING_RECORDS)
{
UpdateTCP();
RakSleep(30);
}
if (cshState!=GOT_RECORDS)
return false;
return true;
}
void UpdateHostIPAsynch(SystemAddress sa) {
if (appState!=AP_RUNNING)
return;
// RakString url("%s/domains/domainId/records/recordId");
// rackspace2->AddOperation()
// http://docs.rackspace.com/cdns/api/v1.0/cdns-devguide/content/Modify_Records-d1e5033.html
//rackspace2;
RakString url("%s/domains/%i/records/%s", rackspaceDNSURL, raknetPatcherDomainId, patchHostRecordID);
json_t *jsonObject = json_object();
json_object_set(jsonObject, "data", json_string(sa.ToString(false)));
printf("Setting DNS to %s\n", sa.ToString(false));
// TESTING HACK
// json_object_set(jsonObject, "data", json_string("198.61.202.20"));
json_object_set(jsonObject, "name", json_string(patcherHostSubdomainURL));
rackspace2->AddOperation(url,Rackspace2::OT_PUT,jsonObject, true);
}
virtual void PrintHelp(void)
{
printf("Distributed authenticated CloudServer using DNS based host migration.\n");
printf("Parameter1: serverToServerPassword\n");
printf("Parameter2: serverPort\n");
printf("Parameter3: allowedIncomingConnections\n");
printf("Parameter4: allowedOutgoingConnections\n");
printf("Parameter5: patcherHostRecordURL\n");
printf("Parameter6: patcherHostDomainURL\n");
printf("Parameter7: rackspaceAuthenticationURL\n");
printf("Parameter8: rackspaceCloudUsername\n");
printf("Parameter9: apiAccessKey\n");
printf("Parameter10: databasePassword\n");
printf("Parameter11: workerThreadCount\n");
printf("Parameter12: sqlConnectionObjectCount\n");
printf("Parameter13: allowDownloadingUnmodifiedFiles\n");
printf("Example:\n");
printf("AutopatcherServer_SelfScaling s2sPassword 60000 1024 128 game1.mycompanypatcher.com mycompanypatcher.com https://identity.api.rackspacecloud.com/v2.0 rackspaceUsername rackspacePw dbPassword 3 6 1");
}
virtual bool ParseCommandLineParameters(int argc, char **argv) {
if (argc!=14)
PrintHelp();
serverToServerPassword=argv[1];
const int intServerPort = atoi(argv[2]);
if ((intServerPort < 0) || (intServerPort > std::numeric_limits<unsigned short>::max())) {
printf("Specified server port %d is outside valid bounds [0, %u]", intServerPort, std::numeric_limits<unsigned short>::max());
return false;
}
rakPeerPort = static_cast<unsigned short>(intServerPort);
int intConnections = atoi(argv[3]);
if ((intConnections < 0) || (intConnections > std::numeric_limits<unsigned short>::max())) {
printf("Specified allowed incoming connections %d is outside valid bounds [0, %u]", intConnections, std::numeric_limits<unsigned short>::max());
return false;
}
allowedIncomingConnections = static_cast<unsigned short>(intConnections);
intConnections = atoi(argv[4]);
if ((intConnections < 0) || (intConnections > std::numeric_limits<unsigned short>::max())) {
printf("Specified allowed outgoing connections %d is outside valid bounds [0, %u]", intConnections, std::numeric_limits<unsigned short>::max());
return false;
}
allowedOutgoingConnections = static_cast<unsigned short>(intConnections);
strcpy_s(patcherHostSubdomainURL, argv[5]);
strcpy_s(patcherHostDomainURL, argv[6]);
strcpy_s(rackspaceAuthenticationURL, argv[7]);
strcpy_s(rackspaceCloudUsername, argv[8]);
strcpy_s(apiAccessKey, argv[9]);
strcpy_s(databasePassword, argv[10]);
workerThreadCount=atoi(argv[11]);
sqlConnectionObjectCount=atoi(argv[12]);
allowDownloadingUnmodifiedFiles=atoi(argv[13]);
/*
2 worker threads, 4 mb read: 5 minutes, 55 seconds
4 worker threads, 4 mb read: 5 minutes, 7 seconds
8 worker threads, 4 mb read: 4 minutes, 48 seconds
2 worker threads, 2 mb read: 8 minutes, 31 seconds
8 worker threads, 2 mb read: 4 minutes, 30 seconds, but one failed to start
4 worker threads, 8 mb read: 4 minutes, 55 seconds
4 worker threads, 16 mb read: 4 minutes, 48 seconds
3 worker threads, 16 mb read: 4 minutes, 19 seconds
2 worker threads, 16 mb read: 5 minutes, 18 seconds
3 worker threads, 32 mb read: 4 minutes, 18 seconds
*/
return true;
}
// Call when you get ID_FCM2_NEW_HOST
virtual void OnFCMNewHost(Packet *packet, RakPeerInterface *rakPeer) {
RakAssert(packet->data[0]==ID_FCM2_NEW_HOST);
SLNet::BitStream bsIn(packet->data, packet->length, false);
bsIn.IgnoreBytes(sizeof(MessageID));
RakNetGUID oldHost;
bsIn.Read(oldHost);
RakNetGUID newHost = packet->guid;
if (newHost==rakPeer->GetMyGUID() && oldHost!=newHost)
{
for (unsigned int i=0; i < rakPeer->GetNumberOfAddresses(); i++)
{
SystemAddress sa = rakPeer->GetInternalID(UNASSIGNED_SYSTEM_ADDRESS,i);
if (sa.IsLANAddress()==false)
{
printf("Assuming host. Updating DNS\n");
UpdateHostIPAsynch(sa);
break;
}
}
}
}
const char *GetCloudHostAddress(void) const {return dnsHostIP;}
SLNet::Time GetTimeOfLastDNSHostCheck(void) {return timeOfLastDNSHostCheck;}
void SetTimeOfLastDNSHostCheck(SLNet::Time t) {timeOfLastDNSHostCheck=t;}
virtual int OnJoinCloudResult(
Packet *packet,
SLNet::RakPeerInterface *rakPeer,
SLNet::CloudServer *cloudServer,
SLNet::CloudClient *cloudClient,
SLNet::FullyConnectedMesh2 *fullyConnectedMesh2,
SLNet::TwoWayAuthentication *twoWayAuthentication,
SLNet::ConnectionGraph2 *connectionGraph2,
const char *rakPeerIpOrDomain,
char myPublicIP[32]
) {
if (packet->data[0]==ID_CONNECTION_ATTEMPT_FAILED)
{
for (unsigned int i=0; i < rakPeer->GetNumberOfAddresses(); i++)
{
SystemAddress sa = rakPeer->GetInternalID(UNASSIGNED_SYSTEM_ADDRESS,i);
if (sa.IsLANAddress()==false)
{
printf("Failed connection. Changing DNS to point to this system.\n");
UpdateHostIPAsynch(sa);
break;
}
}
}
else if (packet->data[0]==ID_ALREADY_CONNECTED && packet->systemAddress.IsLANAddress())
{
for (unsigned int i=0; i < rakPeer->GetNumberOfAddresses(); i++)
{
SystemAddress sa = rakPeer->GetInternalID(UNASSIGNED_SYSTEM_ADDRESS,i);
if (sa.IsLANAddress()==false)
{
printf("Connected to self-LAN address. Changing DNS to point to this system.\n");
UpdateHostIPAsynch(sa);
return CloudServerHelper::OnJoinCloudResult(packet, rakPeer, cloudServer, cloudClient, fullyConnectedMesh2, twoWayAuthentication, connectionGraph2, sa.ToString(false), myPublicIP);
}
}
}
return CloudServerHelper::OnJoinCloudResult(packet, rakPeer, cloudServer, cloudClient, fullyConnectedMesh2, twoWayAuthentication, connectionGraph2, rakPeerIpOrDomain, myPublicIP);
}
virtual void OnConnectionCountChange(RakPeerInterface *rakPeer, CloudClient *cloudClient)
{
SLNet::BitStream bs;
CloudKey cloudKey("CloudConnCount",0);
bs.Write(autopatcherLoad);
cloudClient->Post(&cloudKey, bs.GetData(), bs.GetNumberOfBytesUsed(), rakPeer->GetMyGUID());
}
char patcherHostSubdomainURL[128];
char patcherHostDomainURL[128];
char dnsHostIP[128];
char rackspaceAuthenticationURL[128];
char rackspaceDNSURL[128];
char rackspaceServersURL[128];
char rackspaceCloudUsername[128];
char apiAccessKey[128];
Rackspace2 *rackspace2;
char patchHostRecordID[128];
protected:
int raknetPatcherDomainId;
SLNet::Time timeOfLastDNSHostCheck;
struct ServerDetail
{
char publicIPV4[32];
char privateIPV4[32];
char flavorId[128];
char hostId[128];
char id[128];
char imageId[128];
char name[128];
} thisServerDetail;
char spawningImageId[128];
char spawningFlavorId[128];
} *cloudServerHelper;
class AutopatcherLoadNotifier : public SLNet::AutopatcherServerLoadNotifier
{
void AutopatcherLoadNotifier::OnQueueUpdate(SystemAddress remoteSystem,
AutopatcherServerLoadNotifier::RequestType requestType,
AutopatcherServerLoadNotifier::QueueOperation queueOperation,
AutopatcherServerLoadNotifier::AutopatcherState *autopatcherState)
{
// unused parameters
(void)remoteSystem;
(void)requestType;
(void)queueOperation;
// #med - consider changing autopatcherLoad to unsigned int
autopatcherLoad=static_cast<unsigned short>(autopatcherState->requestsQueued+autopatcherState->requestsWorking);
//AutopatcherServerLoadNotifier_Printf::OnQueueUpdate(remoteSystem, requestType, queueOperation, autopatcherState);
}
void AutopatcherLoadNotifier::OnGetChangelistCompleted(
SystemAddress remoteSystem,
AutopatcherServerLoadNotifier::GetChangelistResult getChangelistResult,
AutopatcherServerLoadNotifier::AutopatcherState *autopatcherState)
{
// unused parameters
(void)remoteSystem;
(void)getChangelistResult;
autopatcherLoad=static_cast<unsigned short>(autopatcherState->requestsQueued+autopatcherState->requestsWorking);
//AutopatcherServerLoadNotifier_Printf::OnGetChangelistCompleted(remoteSystem, getChangelistResult, autopatcherState);
}
void AutopatcherLoadNotifier::OnGetPatchCompleted(SystemAddress remoteSystem,
AutopatcherServerLoadNotifier::PatchResult patchResult,
AutopatcherServerLoadNotifier::AutopatcherState *autopatcherState)
{
// unused parameters
(void)remoteSystem;
(void)patchResult;
autopatcherLoad=static_cast<unsigned short>(autopatcherState->requestsQueued+autopatcherState->requestsWorking);
//AutopatcherServerLoadNotifier_Printf::OnGetPatchCompleted(remoteSystem, patchResult, autopatcherState);
}
};
void SetMaxConcurrentUsers(int i)
{
autopatcherServer->SetMaxConurrentUsers(i);
}
//char WORKING_DIRECTORY[MAX_PATH];
//char PATH_TO_XDELTA_EXE[MAX_PATH];
// The default AutopatcherPostgreRepository2 uses bsdiff which takes too much memory for large files.
// I override MakePatch to use XDelta in this case
class AutopatcherPostgreRepository2_WithXDelta : public SLNet::AutopatcherPostgreRepository2
{
int MakePatch(const char *oldFile, const char *newFile, char **patch, unsigned int *patchLength, int *patchAlgorithm)
{
FILE *fpOld;
fopen_s(&fpOld, oldFile, "rb");
fseek(fpOld, 0, SEEK_END);
int contentLengthOld = ftell(fpOld);
FILE *fpNew;
fopen_s(&fpNew, newFile, "rb");
fseek(fpNew, 0, SEEK_END);
int contentLengthNew = ftell(fpNew);
char WORKING_DIRECTORY[MAX_PATH];
GetTempPathA(MAX_PATH, WORKING_DIRECTORY);
if (WORKING_DIRECTORY[strlen(WORKING_DIRECTORY)-1]=='\\' || WORKING_DIRECTORY[strlen(WORKING_DIRECTORY)-1]=='/')
WORKING_DIRECTORY[strlen(WORKING_DIRECTORY)-1]=0;
const char *PATH_TO_XDELTA_EXE = "c:/xdelta3-3.0.6-win32.exe";
bool shortContent = contentLengthOld < 33554432 && contentLengthNew < 33554432;
if (shortContent || PATH_TO_XDELTA_EXE[0]==0)
{
if (shortContent==false)
{
printf("Warning: PATH_TO_XDELTA_EXE needed but not set.\ncontentLengthOld=%i\ncontentLengthNew=%i\n", contentLengthOld, contentLengthNew );
}
// Use bsdiff, which does a good job but takes a lot of memory based on the size of the file
*patchAlgorithm=0;
bool b = MakePatchBSDiff(fpOld, contentLengthOld, fpNew, contentLengthNew, patch, patchLength);
fclose(fpOld);
fclose(fpNew);
return b == false ? -1 : 0;
}
else
{
*patchAlgorithm=1;
fclose(fpOld);
fclose(fpNew);
char buff[128];
SLNet::TimeUS time = SLNet::GetTimeUS();
#if defined(_WIN32)
sprintf_s(buff, "%I64u", time);
#else
sprintf_s(buff, "%llu", (long long unsigned int) time);
#endif
// Invoke xdelta
// See https://code.google.com/p/xdelta/wiki/CommandLineSyntax
char commandLine[512];
_snprintf(commandLine, sizeof(commandLine)-1, "-f -s %s %s patchServer_%s.tmp", oldFile, newFile, buff);
commandLine[511]=0;
SHELLEXECUTEINFOA shellExecuteInfo;
shellExecuteInfo.cbSize = sizeof(SHELLEXECUTEINFOA);
shellExecuteInfo.fMask = SEE_MASK_NOASYNC | SEE_MASK_NO_CONSOLE;
shellExecuteInfo.hwnd = nullptr;
shellExecuteInfo.lpVerb = "open";
shellExecuteInfo.lpFile = PATH_TO_XDELTA_EXE;
shellExecuteInfo.lpParameters = commandLine;
shellExecuteInfo.lpDirectory = WORKING_DIRECTORY;
shellExecuteInfo.nShow = SW_SHOWNORMAL;
shellExecuteInfo.hInstApp = nullptr;
ShellExecuteExA(&shellExecuteInfo);
// ShellExecute is blocking, but if it writes a file to disk that file is not always immediately accessible after it returns. And this only happens in release, and only when not running in the debugger
//ShellExecute(nullptr, "open", PATH_TO_XDELTA_EXE, commandLine, WORKING_DIRECTORY, SW_SHOWNORMAL);
char pathToPatch[MAX_PATH];
sprintf_s(pathToPatch, "%s/patchServer_%s.tmp", WORKING_DIRECTORY, buff);
FILE *fpPatch;
errno_t error = fopen_s(&fpPatch, pathToPatch, "r+b");
SLNet::TimeUS stopWaiting = time + 60000000 * 5;
while (error!=0 && SLNet::GetTimeUS() < stopWaiting)
{
RakSleep(1000);
error = fopen_s(&fpPatch, pathToPatch, "r+b");
}
if (error!=0)
{
char buff2[1024];
strerror_s(buff2, errno);
printf("\nERROR: Could not open %s.\nerr=%i (%s)\narguments=%s\n", pathToPatch, errno, buff2, commandLine);
return 1;
}
fseek(fpPatch, 0, SEEK_END);
*patchLength = ftell(fpPatch);
fseek(fpPatch, 0, SEEK_SET);
*patch = (char*) rakMalloc_Ex(*patchLength, _FILE_AND_LINE_);
fread(*patch, 1, *patchLength, fpPatch);
fclose(fpPatch);
int unlinkRes = _unlink(pathToPatch);
while (unlinkRes!=0 && SLNet::GetTimeUS() < stopWaiting)
{
RakSleep(1000);
unlinkRes = _unlink(pathToPatch);
}
if (unlinkRes!=0) {
char buff2[1024];
strerror_s(buff2, errno);
printf("\nWARNING: unlink %s failed.\nerr=%i (%s)\n", pathToPatch, errno, buff2);
}
return 0;
}
}
};
int main(int argc, char **argv)
{
#if OPEN_SSL_CLIENT_SUPPORT==0
#error RakNet must be compiled with OPEN_SSL_CLIENT_SUPPORT=1 to connect to Rackspace
return 0;
#endif
cloudServerHelper = SLNet::OP_NEW<CloudServerHelper_RackspaceCloudDNS>(_FILE_AND_LINE_);
if (!cloudServerHelper->ParseCommandLineParameters(argc, argv))
{
return 1;
}
g_rakPeer = SLNet::RakPeerInterface::GetInstance();
SLNet::Time timeForNextLoadCheck= SLNet::GetTime()+LOAD_CHECK_INTERVAL;
if (!cloudServerHelper->AuthenticateWithRackspaceBlocking())
{
printf("Exiting due to failed startup with Rackspace\n");
return 1;
}
// This does not work unless it comes after AuthenticateWithRackspaceBlocking
SLNet::PacketizedTCP packetizedTCP;
if (packetizedTCP.Start(LISTEN_PORT_TCP_PATCHER,cloudServerHelper->allowedIncomingConnections)==false)
{
printf("Failed to start TCP. Is the port already in use?");
return 1;
}
appState=AP_RUNNING;
timeSinceZeroUsers= SLNet::GetTime();
printf("Server starting... ");
autopatcherServer = SLNet::OP_NEW<AutopatcherServer>(_FILE_AND_LINE_);
// SLNet::FLP_Printf progressIndicator;
SLNet::FileListTransfer fileListTransfer;
AutopatcherPostgreRepository2_WithXDelta *connectionObject;
connectionObject = SLNet::OP_NEW_ARRAY<AutopatcherPostgreRepository2_WithXDelta>(sqlConnectionObjectCount, _FILE_AND_LINE_);
// SLNet::AutopatcherRepositoryInterface **connectionObjectAddresses[sqlConnectionObjectCount];
AutopatcherRepositoryInterface **connectionObjectAddresses = SLNet::OP_NEW_ARRAY<AutopatcherRepositoryInterface *>(sqlConnectionObjectCount, _FILE_AND_LINE_);
for (int i=0; i < sqlConnectionObjectCount; i++)
connectionObjectAddresses[i]=&connectionObject[i];
autopatcherServer->SetFileListTransferPlugin(&fileListTransfer);
// PostgreSQL is fast, so this may not be necessary, or could use fewer threads
// This is used to read increments of large files concurrently, thereby serving users downloads as other users read from the DB
fileListTransfer.StartIncrementalReadThreads(sqlConnectionObjectCount);
autopatcherServer->SetMaxConurrentUsers(workerThreadCount); // More users than this get queued up
AutopatcherLoadNotifier autopatcherLoadNotifier;
autopatcherServer->SetLoadManagementCallback(&autopatcherLoadNotifier);
packetizedTCP.AttachPlugin(autopatcherServer);
packetizedTCP.AttachPlugin(&fileListTransfer);
autopatcherServer->SetAllowDownloadOfOriginalUnmodifiedFiles(allowDownloadingUnmodifiedFiles!=0);
// ---- PLUGINS -----
// Used to load balance clients, allow for client to client discovery
SLNet::CloudServer cloudServer;
// Used to update the local cloudServer
SLNet::CloudClient cloudClient;
// Used to determine the host of the server fully connected mesh, as well as to connect servers automatically
SLNet::FullyConnectedMesh2 fullyConnectedMesh2;
// Used for servers to verify each other - otherwise any system could pose as a server
// Could also be used to verify and restrict clients if paired with the MessageFilter plugin
SLNet::TwoWayAuthentication twoWayAuthentication;
// Used to tell servers about each other
SLNet::ConnectionGraph2 connectionGraph2;
g_rakPeer->AttachPlugin(&cloudServer);
g_rakPeer->AttachPlugin(&cloudClient);
g_rakPeer->AttachPlugin(&fullyConnectedMesh2);
g_rakPeer->AttachPlugin(&twoWayAuthentication);
g_rakPeer->AttachPlugin(&connectionGraph2);
if (!cloudServerHelper->StartRakPeer(g_rakPeer))
return 1;
SLNet::CloudServerHelperFilter sampleFilter; // Keeps clients from updating stuff to the server they are not supposed to
sampleFilter.serverGuid= g_rakPeer->GetMyGUID();
cloudServerHelper->SetupPlugins(&cloudServer, &sampleFilter, &cloudClient, &fullyConnectedMesh2, &twoWayAuthentication,&connectionGraph2, cloudServerHelper->serverToServerPassword);
int ret;
do
{
ret = cloudServerHelper->JoinCloud(g_rakPeer, &cloudServer, &cloudClient, &fullyConnectedMesh2, &twoWayAuthentication, &connectionGraph2, cloudServerHelper->GetCloudHostAddress());
} while (ret==2);
if (ret==1)
return 1;
printf("started.\n");
printf("Enter database password:\n");
char connectionString[256];
char username[256];
bool connectionSuccess=false;
while (connectionSuccess==false)
{
strcpy_s(username, "postgres");
strcpy_s(connectionString, "user=");
strcat_s(connectionString, username);
strcat_s(connectionString, " password=");
strcat_s(connectionString, databasePassword);
int conIdx;
for (conIdx=0; conIdx < sqlConnectionObjectCount; conIdx++)
{
if (connectionObject[conIdx].Connect(connectionString)==false)
{
printf("Database connection failed.\n");
printf("Try a different password? (y/n)\n");
if (_getch()!='y')
{
return 1;
}
else
{
connectionSuccess=false;
printf("Enter password: ");
Gets(databasePassword, sizeof(databasePassword));
break;
}
}
}
if (conIdx==sqlConnectionObjectCount)
connectionSuccess=true;
}
printf("Database connection suceeded.\n");
printf("Starting threads\n");
// 4 Worker threads, which is CPU intensive
// A greater number of SQL connections, which read files incrementally for large downloads
autopatcherServer->StartThreads(workerThreadCount,sqlConnectionObjectCount, connectionObjectAddresses);
autopatcherServer->CacheMostRecentPatch(0);
printf("System ready for connections\n");
printf("(D)rop database\n(C)reate database.\n(U)pdate revision.\n(S)pawn a clone of this server\n(M)ax concurrent users (this server only)\n(I)mage the current state of this server\n(L)ist images\n(T)erminate cloud\n(Q)uit\n");
int ch;
SLNet::Packet *p;
while (appState!=AP_TERMINATED)
{
SLNet::SystemAddress notificationAddress;
notificationAddress=packetizedTCP.HasCompletedConnectionAttempt();
// if (notificationAddress!=SLNet::UNASSIGNED_SYSTEM_ADDRESS)
// printf("ID_CONNECTION_REQUEST_ACCEPTED\n");
notificationAddress=packetizedTCP.HasNewIncomingConnection();
// if (notificationAddress!=SLNet::UNASSIGNED_SYSTEM_ADDRESS)
// printf("ID_NEW_INCOMING_CONNECTION\n");
notificationAddress=packetizedTCP.HasLostConnection();
// if (notificationAddress!=SLNet::UNASSIGNED_SYSTEM_ADDRESS)
// printf("ID_CONNECTION_LOST\n");
p=packetizedTCP.Receive();
while (p)
{
packetizedTCP.DeallocatePacket(p);
p=packetizedTCP.Receive();
}
if (autopatcherLoad==0)
{
if (appState==AP_TERMINATE_WHEN_ZERO_USERS_REACHED)
{
cloudServerHelper->Terminate();
}
if (timeSinceZeroUsers==0)
timeSinceZeroUsers= SLNet::GetTime();
}
else
{
timeSinceZeroUsers=0;
}
p= g_rakPeer->Receive();
while (p)
{
/*
if (p->data[0]==ID_NEW_INCOMING_CONNECTION)
printf("ID_NEW_INCOMING_CONNECTION (TCP) from %s\n", p->systemAddress.ToString(true));
else if (p->data[0]==ID_DISCONNECTION_NOTIFICATION)
printf("ID_DISCONNECTION_NOTIFICATION (TCP) from %s\n", p->systemAddress.ToString(true));
else if (p->data[0]==ID_CONNECTION_LOST)
printf("ID_CONNECTION_LOST (TCP) from %s\n", p->systemAddress.ToString(true));
else
*/
if (p->data[0]==ID_FCM2_NEW_HOST)
{
if (appState==AP_RUNNING)
{
SLNet::BitStream bs(p->data,p->length,false);
bs.IgnoreBytes(1);
RakNetGUID oldHost;
bs.Read(oldHost);
if (p->guid== g_rakPeer->GetMyGUID())
{
if (oldHost!=UNASSIGNED_RAKNET_GUID)
{
printf("ID_FCM2_NEW_HOST: Taking over as host from the old host.\n");
}
else
{
// Room not hosted if we become host the first time since this was done in CreateRoom() already
printf("ID_FCM2_NEW_HOST: We have become host for the first time.\n");
}
}
timeForNextLoadCheck= SLNet::GetTime()+LOAD_CHECK_INTERVAL;
}
}
else if (p->data[0]==ID_USER_PACKET_ENUM)
{
SLNet::BitStream bsIn(p->data, p->length, false);
bsIn.IgnoreBytes((sizeof(MessageID)));
RakString incomingPw;
bsIn.Read(incomingPw);
if (incomingPw==cloudServerHelper->serverToServerPassword)
{
SetMaxConcurrentUsers(0);
if (appState==AP_RUNNING)
{
if (timeSinceZeroUsers!=0)
cloudServerHelper->Terminate();
else
appState=AP_TERMINATE_WHEN_ZERO_USERS_REACHED;
}
// Disconnect from the cloud, and do not accept new connections from users
// The autopatcher will still run since it is on TCP
g_rakPeer->Shutdown(1000);
}
}
else if (p->data[0]==ID_CLOUD_GET_RESPONSE)
{
SLNet::CloudQueryResult cloudQueryResult;
cloudClient.OnGetReponse(&cloudQueryResult, p);
unsigned int rowIndex;
const bool wasCallToGetServers=cloudQueryResult.cloudQuery.keys[0].primaryKey=="CloudConnCount";
RakAssert(wasCallToGetServers);
/*
if (wasCallToGetServers)
printf("Downloaded server list. %i servers.\n", cloudQueryResult.rowsReturned.Size());
*/
SLNet::SystemAddress lowestConnectionAddress;
int totalConnections=0;
for (rowIndex=0; rowIndex < cloudQueryResult.rowsReturned.Size(); rowIndex++)
{
SLNet::CloudQueryRow *row = cloudQueryResult.rowsReturned[rowIndex];
unsigned short connCount;
SLNet::BitStream bsIn(row->data, row->length, false);
bsIn.Read(connCount);
printf("%i. Server found at %s with %i connections\n", rowIndex+1, row->serverSystemAddress.ToString(true), connCount);
totalConnections+=connCount;
}
const int MAX_SERVERS_EVER=32;
int available = cloudQueryResult.rowsReturned.Size() * sqlConnectionObjectCount - totalConnections;
if (available < 0 &&
cloudQueryResult.rowsReturned.Size() < MAX_SERVERS_EVER // no more than MAX_SERVERS_EVER servers ever, to control costs
)
{
int newServersNeeded = -available / sqlConnectionObjectCount;
if (newServersNeeded > 4)
newServersNeeded = 4; // Do not start more than 4 servers at a time
if (cloudQueryResult.rowsReturned.Size() + newServersNeeded > MAX_SERVERS_EVER)
{
newServersNeeded = MAX_SERVERS_EVER - cloudQueryResult.rowsReturned.Size();
}
if (newServersNeeded>0)
cloudServerHelper->SpawnServers(static_cast<unsigned>(newServersNeeded));
}
timeForNextLoadCheck = SLNet::GetTime() + LOAD_CHECK_INTERVAL_AFTER_SPAWN;
cloudClient.DeallocateWithDefaultAllocator(&cloudQueryResult);
}
cloudServerHelper->OnPacket(p, g_rakPeer, &cloudClient, &cloudServer, &fullyConnectedMesh2, &twoWayAuthentication, &connectionGraph2);
g_rakPeer->DeallocatePacket(p);
p=g_rakPeer->Receive();
}
if (timeSinceZeroUsers!=0 && appState==AP_RUNNING && autopatcherServer->GetMaxConurrentUsers()>0)
{
// No users currently connected
SLNet::Time curTime = SLNet::GetTime();
SLNet::Time diff = curTime - timeSinceZeroUsers;
if (diff > 0)
{
if (fullyConnectedMesh2.IsHostSystem()==false)
{
// 2. Shutdown automatically if no users for 12 hours, except if we are the host
if (diff > 1000 * 60 * 60 * 12)
cloudServerHelper->Terminate();
}
else
{
// We are host according to RakPeer. But no users for 24 hours
// Verify we are the host according to Rackspace DNS records. If not, shutdown
if (diff > 1000 * 60 * 60 * 24)
{
// No users for 24 hours, verify if we are the host according to Rackspace DNS records. If not, shutdown
SLNet::Time diff2;
diff2 = curTime - cloudServerHelper->GetTimeOfLastDNSHostCheck();
if (diff2 > 1000 * 60 * 60 * 24)
{
appState=AP_TERMINATE_IF_NOT_DNS_HOST;
cloudServerHelper->SetTimeOfLastDNSHostCheck(curTime);
cloudServerHelper->GetDomainRecords();
}
}
}
}
}
cloudServerHelper->Update();
// 4. If we are the host, every 1 hour check load among all servers (including self). It takes like 30 minutes to build a new server.
if (fullyConnectedMesh2.IsHostSystem() && autopatcherServer->GetMaxConurrentUsers()>0)
{
SLNet::Time curTime = SLNet::GetTime();
if (curTime > timeForNextLoadCheck)
{
timeForNextLoadCheck = curTime + LOAD_CHECK_INTERVAL;
// If the load exceeds such that >=1 new fully loaded server is needed, add that many servers
// See ID_CLOUD_GET_RESPONSE
SLNet::CloudQuery cloudQuery;
cloudQuery.keys.Push(SLNet::CloudKey("CloudConnCount",0),_FILE_AND_LINE_); // CloudConnCount is defined at the top of CloudServerHelper.cpp
cloudQuery.subscribeToResults=false;
cloudClient.Get(&cloudQuery, g_rakPeer->GetMyGUID());
}
}
if (_kbhit())
{
ch=_getch();
if (ch=='q')
break;
else if (ch=='c')
{
if (connectionObject[0].CreateAutopatcherTables()==false)
printf("%s", connectionObject[0].GetLastError());
else
printf("Created tables.\n");
if (connectionObject[0].AddApplication(cloudServerHelper->patcherHostSubdomainURL, username)==false)
printf("%s", connectionObject[0].GetLastError());
else
printf("Added application\n");
}
else if (ch=='d')
{
if (connectionObject[0].DestroyAutopatcherTables()==false)
printf("%s", connectionObject[0].GetLastError());
}
else if (ch=='i')
{
printf("Image this server?\nWill clone this server so that future servers made from this image will use its current state.\nPress 'y' to image.\n");
if (_getch()=='y')
{
cloudServerHelper->ImageThisServer();
}
}
else if (ch=='s')
{
printf("How many servers to spawn? (0-4) ");
char str[32];
Gets(str, sizeof(str));
int num = atoi(str);
if (num>0)
cloudServerHelper->SpawnServers(static_cast<unsigned>(num));
}
else if (ch=='m')
{
char maxConcurrent[64];
printf("Enter new allowed max concurrent users: ");
Gets(maxConcurrent, sizeof(maxConcurrent));
if (maxConcurrent[0])
SetMaxConcurrentUsers(atoi(maxConcurrent));
}
else if (ch=='t')
{
printf("Setting max concurrent users to 0 on all servers\n");
SetMaxConcurrentUsers(0);
BitStream bsOut;
bsOut.Write((MessageID)ID_USER_PACKET_ENUM);
bsOut.Write(cloudServerHelper->serverToServerPassword);
DataStructures::List<RakNetGUID> remoteServersOut;
cloudServer.GetRemoteServers(remoteServersOut);
for (unsigned int i=0; i < remoteServersOut.Size(); i++)
{
g_rakPeer->Send(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, remoteServersOut[i], false);
g_rakPeer->CloseConnection(remoteServersOut[i], true);
}
printf("Remote servers will shutdown when all users are done\n");
printf("Disconnected from remote servers\n");
for (unsigned int i=0; i < g_rakPeer->GetNumberOfAddresses(); i++)
{
SystemAddress sa = g_rakPeer->GetInternalID(UNASSIGNED_SYSTEM_ADDRESS,i);
if (sa.IsLANAddress()==false)
{
printf("Updating host DNS entry to this server\n");
cloudServerHelper->UpdateHostIPAsynch(sa);
break;
}
}
}
else if (ch=='l')
{
cloudServerHelper->GetCustomImages();
}
else if (ch=='u')
{
// printf("Enter application name: ");
char appName[512];
strcpy_s(appName, cloudServerHelper->patcherHostSubdomainURL);
// Gets(appName,sizeof(appName));
// if (appName[0]==0)
// strcpy_s(appName, "TestApp");
printf("Enter application directory: ");
char appDir[512];
Gets(appDir,sizeof(appDir));
if (appDir[0]==0)
strcpy_s(appDir, "D:/temp");
if (connectionObject[0].UpdateApplicationFiles(appName, appDir, username, 0)==false)
{
printf("%s", connectionObject[0].GetLastError());
}
else
{
printf("Update success.\n");
autopatcherServer->CacheMostRecentPatch(appName);
}
}
}
RakSleep(30);
}
SLNet::OP_DELETE_ARRAY(connectionObject, _FILE_AND_LINE_);
SLNet::OP_DELETE_ARRAY(connectionObjectAddresses, _FILE_AND_LINE_);
packetizedTCP.Stop();
SLNet::RakPeerInterface::DestroyInstance(g_rakPeer);
SLNet::OP_DELETE(cloudServerHelper, _FILE_AND_LINE_);
return 0;
}