// // DomainServer.cpp // hifi // // Created by Stephen Birarda on 9/26/13. // Copyright (c) 2013 HighFidelity, Inc. All rights reserved. // #include #include #include #include #include #include #include #include #include #include #include "DomainServer.h" const int RESTART_HOLD_TIME_MSECS = 5 * 1000; const char* VOXEL_SERVER_CONFIG = "voxelServerConfig"; const char* PARTICLE_SERVER_CONFIG = "particleServerConfig"; const char* METAVOXEL_SERVER_CONFIG = "metavoxelServerConfig"; void signalhandler(int sig){ if (sig == SIGINT) { qApp->quit(); } } const quint16 DOMAIN_SERVER_HTTP_PORT = 8080; DomainServer::DomainServer(int argc, char* argv[]) : QCoreApplication(argc, argv), _HTTPManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this), _assignmentQueueMutex(), _assignmentQueue(), _staticAssignmentFile(QString("%1/config.ds").arg(QCoreApplication::applicationDirPath())), _staticAssignmentFileData(NULL), _voxelServerConfig(NULL), _metavoxelServerConfig(NULL), _hasCompletedRestartHold(false) { signal(SIGINT, signalhandler); const char CUSTOM_PORT_OPTION[] = "-p"; const char* customPortString = getCmdOption(argc, (const char**) argv, CUSTOM_PORT_OPTION); unsigned short domainServerPort = customPortString ? atoi(customPortString) : DEFAULT_DOMAIN_SERVER_PORT; const char CONFIG_FILE_OPTION[] = "-c"; const char* configFilePath = getCmdOption(argc, (const char**) argv, CONFIG_FILE_OPTION); if (!readConfigFile(configFilePath)) { QByteArray voxelConfigOption = QString("--%1").arg(VOXEL_SERVER_CONFIG).toLocal8Bit(); _voxelServerConfig = getCmdOption(argc, (const char**) argv, voxelConfigOption.constData()); QByteArray particleConfigOption = QString("--%1").arg(PARTICLE_SERVER_CONFIG).toLocal8Bit(); _particleServerConfig = getCmdOption(argc, (const char**) argv, particleConfigOption.constData()); QByteArray metavoxelConfigOption = QString("--%1").arg(METAVOXEL_SERVER_CONFIG).toLocal8Bit(); _metavoxelServerConfig = getCmdOption(argc, (const char**) argv, metavoxelConfigOption.constData()); } NodeList* nodeList = NodeList::createInstance(NODE_TYPE_DOMAIN, domainServerPort); connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), this, SLOT(nodeKilled(SharedNodePointer))); if (!_staticAssignmentFile.exists() || _voxelServerConfig) { if (_voxelServerConfig) { // we have a new VS config, clear the existing file to start fresh _staticAssignmentFile.remove(); } prepopulateStaticAssignmentFile(); } _staticAssignmentFile.open(QIODevice::ReadWrite); _staticAssignmentFileData = _staticAssignmentFile.map(0, _staticAssignmentFile.size()); _staticAssignments = (Assignment*) _staticAssignmentFileData; QTimer* silentNodeTimer = new QTimer(this); connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000); connect(&nodeList->getNodeSocket(), SIGNAL(readyRead()), SLOT(readAvailableDatagrams())); // fire a single shot timer to add static assignments back into the queue after a restart QTimer::singleShot(RESTART_HOLD_TIME_MSECS, this, SLOT(addStaticAssignmentsBackToQueueAfterRestart())); connect(this, SIGNAL(aboutToQuit()), SLOT(cleanup())); } void DomainServer::readAvailableDatagrams() { NodeList* nodeList = NodeList::getInstance(); HifiSockAddr senderSockAddr, nodePublicAddress, nodeLocalAddress; static unsigned char packetData[MAX_PACKET_SIZE]; static unsigned char broadcastPacket[MAX_PACKET_SIZE]; static unsigned char* currentBufferPos; static unsigned char* startPointer; int receivedBytes = 0; while (nodeList->getNodeSocket().hasPendingDatagrams()) { if ((receivedBytes = nodeList->getNodeSocket().readDatagram((char*) packetData, MAX_PACKET_SIZE, senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer())) && packetVersionMatch((unsigned char*) packetData)) { if (packetData[0] == PACKET_TYPE_DOMAIN_REPORT_FOR_DUTY || packetData[0] == PACKET_TYPE_DOMAIN_LIST_REQUEST) { // this is an RFD or domain list request packet, and there is a version match int numBytesSenderHeader = numBytesForPacketHeader((unsigned char*) packetData); NODE_TYPE nodeType = *(packetData + numBytesSenderHeader); int packetIndex = numBytesSenderHeader + sizeof(NODE_TYPE); QUuid nodeUUID = QUuid::fromRfc4122(QByteArray(((char*) packetData + packetIndex), NUM_BYTES_RFC4122_UUID)); packetIndex += NUM_BYTES_RFC4122_UUID; int numBytesPrivateSocket = HifiSockAddr::unpackSockAddr(packetData + packetIndex, nodePublicAddress); packetIndex += numBytesPrivateSocket; if (nodePublicAddress.getAddress().isNull()) { // this node wants to use us its STUN server // so set the node public address to whatever we perceive the public address to be // if the sender is on our box then leave its public address to 0 so that // other users attempt to reach it on the same address they have for the domain-server if (senderSockAddr.getAddress().isLoopback()) { nodePublicAddress.setAddress(QHostAddress()); } else { nodePublicAddress.setAddress(senderSockAddr.getAddress()); } } int numBytesPublicSocket = HifiSockAddr::unpackSockAddr(packetData + packetIndex, nodeLocalAddress); packetIndex += numBytesPublicSocket; const char STATICALLY_ASSIGNED_NODES[] = { NODE_TYPE_AUDIO_MIXER, NODE_TYPE_AVATAR_MIXER, NODE_TYPE_VOXEL_SERVER, NODE_TYPE_METAVOXEL_SERVER }; Assignment* matchingStaticAssignment = NULL; if (memchr(STATICALLY_ASSIGNED_NODES, nodeType, sizeof(STATICALLY_ASSIGNED_NODES)) == NULL || ((matchingStaticAssignment = matchingStaticAssignmentForCheckIn(nodeUUID, nodeType)) || checkInWithUUIDMatchesExistingNode(nodePublicAddress, nodeLocalAddress, nodeUUID))) { SharedNodePointer checkInNode = nodeList->addOrUpdateNode(nodeUUID, nodeType, nodePublicAddress, nodeLocalAddress); if (matchingStaticAssignment) { // this was a newly added node with a matching static assignment if (_hasCompletedRestartHold) { // remove the matching assignment from the assignment queue so we don't take the next check in removeAssignmentFromQueue(matchingStaticAssignment); } // set the linked data for this node to a copy of the matching assignment // so we can re-queue it should the node die Assignment* nodeCopyOfMatchingAssignment = new Assignment(*matchingStaticAssignment); checkInNode->setLinkedData(nodeCopyOfMatchingAssignment); } int numHeaderBytes = populateTypeAndVersion(broadcastPacket, PACKET_TYPE_DOMAIN); currentBufferPos = broadcastPacket + numHeaderBytes; startPointer = currentBufferPos; unsigned char* nodeTypesOfInterest = packetData + packetIndex + sizeof(unsigned char); int numInterestTypes = *(nodeTypesOfInterest - 1); if (numInterestTypes > 0) { // if the node has sent no types of interest, assume they want nothing but their own ID back foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { if (node->getUUID() != nodeUUID && memchr(nodeTypesOfInterest, node->getType(), numInterestTypes)) { // don't send avatar nodes to other avatars, that will come from avatar mixer if (nodeType != NODE_TYPE_AGENT || node->getType() != NODE_TYPE_AGENT) { currentBufferPos = addNodeToBroadcastPacket(currentBufferPos, node.data()); } } } } // update last receive to now uint64_t timeNow = usecTimestampNow(); checkInNode->setLastHeardMicrostamp(timeNow); // send the constructed list back to this node nodeList->getNodeSocket().writeDatagram((char*) broadcastPacket, (currentBufferPos - startPointer) + numHeaderBytes, senderSockAddr.getAddress(), senderSockAddr.getPort()); } } else if (packetData[0] == PACKET_TYPE_REQUEST_ASSIGNMENT) { if (_assignmentQueue.size() > 0) { // construct the requested assignment from the packet data Assignment requestAssignment(packetData, receivedBytes); qDebug("Received a request for assignment type %i from %s.", requestAssignment.getType(), qPrintable(senderSockAddr.getAddress().toString())); Assignment* assignmentToDeploy = deployableAssignmentForRequest(requestAssignment); if (assignmentToDeploy) { // give this assignment out, either the type matches or the requestor said they will take any int numHeaderBytes = populateTypeAndVersion(broadcastPacket, PACKET_TYPE_CREATE_ASSIGNMENT); int numAssignmentBytes = assignmentToDeploy->packToBuffer(broadcastPacket + numHeaderBytes); nodeList->getNodeSocket().writeDatagram((char*) broadcastPacket, numHeaderBytes + numAssignmentBytes, senderSockAddr.getAddress(), senderSockAddr.getPort()); if (assignmentToDeploy->getNumberOfInstances() == 0) { // there are no more instances of this script to send out, delete it delete assignmentToDeploy; } } } else { qDebug() << "Received an invalid assignment request from" << senderSockAddr.getAddress(); } } } } } QJsonObject jsonForSocket(const HifiSockAddr& socket) { QJsonObject socketJSON; socketJSON["ip"] = socket.getAddress().toString(); socketJSON["port"] = ntohs(socket.getPort()); return socketJSON; } const char JSON_KEY_TYPE[] = "type"; const char JSON_KEY_PUBLIC_SOCKET[] = "public"; const char JSON_KEY_LOCAL_SOCKET[] = "local"; const char JSON_KEY_POOL[] = "pool"; QJsonObject jsonObjectForNode(Node* node) { QJsonObject nodeJson; // re-format the type name so it matches the target name QString nodeTypeName(node->getTypeName()); nodeTypeName = nodeTypeName.toLower(); nodeTypeName.replace(' ', '-'); // add the node type nodeJson[JSON_KEY_TYPE] = nodeTypeName; // add the node socket information nodeJson[JSON_KEY_PUBLIC_SOCKET] = jsonForSocket(node->getPublicSocket()); nodeJson[JSON_KEY_LOCAL_SOCKET] = jsonForSocket(node->getLocalSocket()); // if the node has pool information, add it if (node->getLinkedData() && ((Assignment*) node->getLinkedData())->hasPool()) { nodeJson[JSON_KEY_POOL] = QString(((Assignment*) node->getLinkedData())->getPool()); } return nodeJson; } // Attempts to read configuration from specified path // returns true on success, false otherwise bool DomainServer::readConfigFile(const char* path) { if (!path) { // config file not specified return false; } if (!QFile::exists(path)) { qWarning("Specified configuration file does not exist!\n"); return false; } QFile configFile(path); if (!configFile.open(QIODevice::ReadOnly | QIODevice::Text)) { qWarning("Can't open specified configuration file!\n"); return false; } else { qDebug("Reading configuration from %s\n", path); } QTextStream configStream(&configFile); QByteArray configStringByteArray = configStream.readAll().toUtf8(); QJsonObject configDocObject = QJsonDocument::fromJson(configStringByteArray).object(); configFile.close(); QString voxelServerConfig = readServerAssignmentConfig(configDocObject, VOXEL_SERVER_CONFIG); _voxelServerConfig = new char[strlen(voxelServerConfig.toLocal8Bit().constData()) +1]; _voxelServerConfig = strcpy((char *) _voxelServerConfig, voxelServerConfig.toLocal8Bit().constData() + '\0'); QString particleServerConfig = readServerAssignmentConfig(configDocObject, PARTICLE_SERVER_CONFIG); _particleServerConfig = new char[strlen(particleServerConfig.toLocal8Bit().constData()) +1]; _particleServerConfig = strcpy((char *) _particleServerConfig, particleServerConfig.toLocal8Bit().constData() + '\0'); QString metavoxelServerConfig = readServerAssignmentConfig(configDocObject, METAVOXEL_SERVER_CONFIG); _metavoxelServerConfig = new char[strlen(metavoxelServerConfig.toLocal8Bit().constData()) +1]; _metavoxelServerConfig = strcpy((char *) _metavoxelServerConfig, metavoxelServerConfig.toLocal8Bit().constData() + '\0'); return true; } // find assignment configurations on the specified node name and json object // returns a string in the form of its equivalent cmd line params QString DomainServer::readServerAssignmentConfig(QJsonObject jsonObject, const char* nodeName) { QJsonArray nodeArray = jsonObject[nodeName].toArray(); QStringList serverConfig; foreach (const QJsonValue & childValue, nodeArray) { QString cmdParams; QJsonObject childObject = childValue.toObject(); QStringList keys = childObject.keys(); for (int i = 0; i < keys.size(); i++) { QString key = keys[i]; QString value = childObject[key].toString(); // both cmd line params and json keys are the same cmdParams += QString("--%1 %2 ").arg(key, value); } serverConfig << cmdParams; } // according to split() calls from DomainServer::prepopulateStaticAssignmentFile // we shold simply join them with semicolons return serverConfig.join(';'); } bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QString& path) { const QString JSON_MIME_TYPE = "application/json"; const QString URI_ASSIGNMENT = "/assignment"; const QString URI_NODE = "/node"; if (connection->requestOperation() == QNetworkAccessManager::GetOperation) { if (path == "/assignments.json") { // user is asking for json list of assignments // setup the JSON QJsonObject assignmentJSON; QJsonObject assignedNodesJSON; // enumerate the NodeList to find the assigned nodes foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { if (node->getLinkedData()) { // add the node using the UUID as the key QString uuidString = uuidStringWithoutCurlyBraces(node->getUUID()); assignedNodesJSON[uuidString] = jsonObjectForNode(node.data()); } } assignmentJSON["fulfilled"] = assignedNodesJSON; QJsonObject queuedAssignmentsJSON; // add the queued but unfilled assignments to the json std::deque::iterator assignment = _assignmentQueue.begin(); while (assignment != _assignmentQueue.end()) { QJsonObject queuedAssignmentJSON; QString uuidString = uuidStringWithoutCurlyBraces((*assignment)->getUUID()); queuedAssignmentJSON[JSON_KEY_TYPE] = QString((*assignment)->getTypeName()); // if the assignment has a pool, add it if ((*assignment)->hasPool()) { queuedAssignmentJSON[JSON_KEY_POOL] = QString((*assignment)->getPool()); } // add this queued assignment to the JSON queuedAssignmentsJSON[uuidString] = queuedAssignmentJSON; // push forward the iterator to check the next assignment assignment++; } assignmentJSON["queued"] = queuedAssignmentsJSON; // print out the created JSON QJsonDocument assignmentDocument(assignmentJSON); connection->respond(HTTPConnection::StatusCode200, assignmentDocument.toJson(), qPrintable(JSON_MIME_TYPE)); // we've processed this request return true; } else if (path == "/nodes.json") { // setup the JSON QJsonObject rootJSON; QJsonObject nodesJSON; // enumerate the NodeList to find the assigned nodes NodeList* nodeList = NodeList::getInstance(); foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { // add the node using the UUID as the key QString uuidString = uuidStringWithoutCurlyBraces(node->getUUID()); nodesJSON[uuidString] = jsonObjectForNode(node.data()); } rootJSON["nodes"] = nodesJSON; // print out the created JSON QJsonDocument nodesDocument(rootJSON); // send the response connection->respond(HTTPConnection::StatusCode200, nodesDocument.toJson(), qPrintable(JSON_MIME_TYPE)); } } else if (connection->requestOperation() == QNetworkAccessManager::PostOperation) { if (path == URI_ASSIGNMENT) { // this is a script upload - ask the HTTPConnection to parse the form data QList formData = connection->parseFormData(); // create an assignment for this saved script, for now make it local only Assignment* scriptAssignment = new Assignment(Assignment::CreateCommand, Assignment::AgentType, NULL, Assignment::LocalLocation); // check how many instances of this assignment the user wants by checking the ASSIGNMENT-INSTANCES header const QString ASSIGNMENT_INSTANCES_HEADER = "ASSIGNMENT-INSTANCES"; QByteArray assignmentInstancesValue = connection->requestHeaders().value(ASSIGNMENT_INSTANCES_HEADER.toLocal8Bit()); if (!assignmentInstancesValue.isEmpty()) { // the user has requested a specific number of instances // so set that on the created assignment int numInstances = assignmentInstancesValue.toInt(); if (numInstances > 0) { qDebug() << numInstances; scriptAssignment->setNumberOfInstances(numInstances); } } const char ASSIGNMENT_SCRIPT_HOST_LOCATION[] = "resources/web/assignment"; QString newPath(ASSIGNMENT_SCRIPT_HOST_LOCATION); newPath += "/"; // append the UUID for this script as the new filename, remove the curly braces newPath += uuidStringWithoutCurlyBraces(scriptAssignment->getUUID()); // create a file with the GUID of the assignment in the script host locaiton QFile scriptFile(newPath); scriptFile.open(QIODevice::WriteOnly); scriptFile.write(formData[0].second); qDebug("Saved a script for assignment at %s", qPrintable(newPath)); // respond with a 200 code for successful upload connection->respond(HTTPConnection::StatusCode200); // add the script assigment to the assignment queue // lock the assignment queue mutex since we're operating on a different thread than DS main _assignmentQueueMutex.lock(); _assignmentQueue.push_back(scriptAssignment); _assignmentQueueMutex.unlock(); } } else if (connection->requestOperation() == QNetworkAccessManager::DeleteOperation) { if (path.startsWith(URI_NODE)) { // this is a request to DELETE a node by UUID // pull the UUID from the url QUuid deleteUUID = QUuid(path.mid(URI_NODE.size() + sizeof('/'))); if (!deleteUUID.isNull()) { SharedNodePointer nodeToKill = NodeList::getInstance()->nodeWithUUID(deleteUUID); if (nodeToKill) { // start with a 200 response connection->respond(HTTPConnection::StatusCode200); // we have a valid UUID and node - kill the node that has this assignment QMetaObject::invokeMethod(NodeList::getInstance(), "killNodeWithUUID", Q_ARG(const QUuid&, deleteUUID)); // successfully processed request return true; } } // bad request, couldn't pull a node ID connection->respond(HTTPConnection::StatusCode400); return true; } } // didn't process the request, let the HTTPManager try and handle return false; } void DomainServer::addReleasedAssignmentBackToQueue(Assignment* releasedAssignment) { qDebug() << "Adding assignment" << *releasedAssignment << " back to queue."; // find this assignment in the static file for (int i = 0; i < MAX_STATIC_ASSIGNMENT_FILE_ASSIGNMENTS; i++) { if (_staticAssignments[i].getUUID() == releasedAssignment->getUUID()) { // reset the UUID on the static assignment _staticAssignments[i].resetUUID(); // put this assignment back in the queue so it goes out _assignmentQueueMutex.lock(); _assignmentQueue.push_back(&_staticAssignments[i]); _assignmentQueueMutex.unlock(); } else if (_staticAssignments[i].getUUID().isNull()) { // we are at the blank part of the static assignments - break out break; } } } void DomainServer::nodeKilled(SharedNodePointer node) { // if this node has linked data it was from an assignment if (node->getLinkedData()) { Assignment* nodeAssignment = (Assignment*) node->getLinkedData(); addReleasedAssignmentBackToQueue(nodeAssignment); } } unsigned char* DomainServer::addNodeToBroadcastPacket(unsigned char* currentPosition, Node* nodeToAdd) { *currentPosition++ = nodeToAdd->getType(); QByteArray rfcUUID = nodeToAdd->getUUID().toRfc4122(); memcpy(currentPosition, rfcUUID.constData(), rfcUUID.size()); currentPosition += rfcUUID.size(); currentPosition += HifiSockAddr::packSockAddr(currentPosition, nodeToAdd->getPublicSocket()); currentPosition += HifiSockAddr::packSockAddr(currentPosition, nodeToAdd->getLocalSocket()); // return the new unsigned char * for broadcast packet return currentPosition; } void DomainServer::prepopulateStaticAssignmentFile() { int numFreshStaticAssignments = 0; // write a fresh static assignment array to file Assignment freshStaticAssignments[MAX_STATIC_ASSIGNMENT_FILE_ASSIGNMENTS]; // pre-populate the first static assignment list with assignments for root AuM, AvM, VS freshStaticAssignments[numFreshStaticAssignments++] = Assignment(Assignment::CreateCommand, Assignment::AudioMixerType); freshStaticAssignments[numFreshStaticAssignments++] = Assignment(Assignment::CreateCommand, Assignment::AvatarMixerType); // Handle Domain/Voxel Server configuration command line arguments if (_voxelServerConfig) { qDebug("Reading Voxel Server Configuration."); qDebug() << "config: " << _voxelServerConfig; QString multiConfig((const char*) _voxelServerConfig); QStringList multiConfigList = multiConfig.split(";"); // read each config to a payload for a VS assignment for (int i = 0; i < multiConfigList.size(); i++) { QString config = multiConfigList.at(i); qDebug("config[%d]=%s", i, config.toLocal8Bit().constData()); // Now, parse the config to check for a pool const char ASSIGNMENT_CONFIG_POOL_OPTION[] = "--pool"; QString assignmentPool; int poolIndex = config.indexOf(ASSIGNMENT_CONFIG_POOL_OPTION); if (poolIndex >= 0) { int spaceBeforePoolIndex = config.indexOf(' ', poolIndex); int spaceAfterPoolIndex = config.indexOf(' ', spaceBeforePoolIndex); assignmentPool = config.mid(spaceBeforePoolIndex + 1, spaceAfterPoolIndex); qDebug() << "The pool for this voxel-assignment is" << assignmentPool; } Assignment voxelServerAssignment(Assignment::CreateCommand, Assignment::VoxelServerType, (assignmentPool.isEmpty() ? NULL : assignmentPool.toLocal8Bit().constData())); int payloadLength = config.length() + sizeof(char); voxelServerAssignment.setPayload((uchar*)config.toLocal8Bit().constData(), payloadLength); freshStaticAssignments[numFreshStaticAssignments++] = voxelServerAssignment; } } else { Assignment rootVoxelServerAssignment(Assignment::CreateCommand, Assignment::VoxelServerType); freshStaticAssignments[numFreshStaticAssignments++] = rootVoxelServerAssignment; } // Handle Domain/Particle Server configuration command line arguments if (_particleServerConfig) { qDebug("Reading Particle Server Configuration."); qDebug() << "config: " << _particleServerConfig; QString multiConfig((const char*) _particleServerConfig); QStringList multiConfigList = multiConfig.split(";"); // read each config to a payload for a VS assignment for (int i = 0; i < multiConfigList.size(); i++) { QString config = multiConfigList.at(i); qDebug("config[%d]=%s", i, config.toLocal8Bit().constData()); // Now, parse the config to check for a pool const char ASSIGNMENT_CONFIG_POOL_OPTION[] = "--pool"; QString assignmentPool; int poolIndex = config.indexOf(ASSIGNMENT_CONFIG_POOL_OPTION); if (poolIndex >= 0) { int spaceBeforePoolIndex = config.indexOf(' ', poolIndex); int spaceAfterPoolIndex = config.indexOf(' ', spaceBeforePoolIndex); assignmentPool = config.mid(spaceBeforePoolIndex + 1, spaceAfterPoolIndex); qDebug() << "The pool for this particle-assignment is" << assignmentPool; } Assignment particleServerAssignment(Assignment::CreateCommand, Assignment::ParticleServerType, (assignmentPool.isEmpty() ? NULL : assignmentPool.toLocal8Bit().constData())); int payloadLength = config.length() + sizeof(char); particleServerAssignment.setPayload((uchar*)config.toLocal8Bit().constData(), payloadLength); freshStaticAssignments[numFreshStaticAssignments++] = particleServerAssignment; } } else { Assignment rootParticleServerAssignment(Assignment::CreateCommand, Assignment::ParticleServerType); freshStaticAssignments[numFreshStaticAssignments++] = rootParticleServerAssignment; } // handle metavoxel configuration command line argument Assignment& metavoxelAssignment = (freshStaticAssignments[numFreshStaticAssignments++] = Assignment(Assignment::CreateCommand, Assignment::MetavoxelServerType)); if (_metavoxelServerConfig) { metavoxelAssignment.setPayload((const unsigned char*)_metavoxelServerConfig, strlen(_metavoxelServerConfig)); } qDebug() << "Adding" << numFreshStaticAssignments << "static assignments to fresh file."; _staticAssignmentFile.open(QIODevice::WriteOnly); _staticAssignmentFile.write((char*) &freshStaticAssignments, sizeof(freshStaticAssignments)); _staticAssignmentFile.resize(MAX_STATIC_ASSIGNMENT_FILE_ASSIGNMENTS * sizeof(Assignment)); _staticAssignmentFile.close(); } Assignment* DomainServer::matchingStaticAssignmentForCheckIn(const QUuid& checkInUUID, NODE_TYPE nodeType) { // pull the UUID passed with the check in if (_hasCompletedRestartHold) { _assignmentQueueMutex.lock(); // iterate the assignment queue to check for a match std::deque::iterator assignment = _assignmentQueue.begin(); while (assignment != _assignmentQueue.end()) { if ((*assignment)->getUUID() == checkInUUID) { // return the matched assignment _assignmentQueueMutex.unlock(); return *assignment; } else { // no match, push deque iterator forwards assignment++; } } _assignmentQueueMutex.unlock(); } else { for (int i = 0; i < MAX_STATIC_ASSIGNMENT_FILE_ASSIGNMENTS; i++) { if (_staticAssignments[i].getUUID() == checkInUUID) { // return matched assignment return &_staticAssignments[i]; } else if (_staticAssignments[i].getUUID().isNull()) { // end of static assignments, no match - return NULL return NULL; } } } return NULL; } Assignment* DomainServer::deployableAssignmentForRequest(Assignment& requestAssignment) { _assignmentQueueMutex.lock(); // this is an unassigned client talking to us directly for an assignment // go through our queue and see if there are any assignments to give out std::deque::iterator assignment = _assignmentQueue.begin(); while (assignment != _assignmentQueue.end()) { bool requestIsAllTypes = requestAssignment.getType() == Assignment::AllTypes; bool assignmentTypesMatch = (*assignment)->getType() == requestAssignment.getType(); bool nietherHasPool = !(*assignment)->hasPool() && !requestAssignment.hasPool(); bool assignmentPoolsMatch = memcmp((*assignment)->getPool(), requestAssignment.getPool(), MAX_ASSIGNMENT_POOL_BYTES) == 0; if ((requestIsAllTypes || assignmentTypesMatch) && (nietherHasPool || assignmentPoolsMatch)) { Assignment* deployableAssignment = *assignment; if ((*assignment)->getType() == Assignment::AgentType) { // if there is more than one instance to send out, simply decrease the number of instances if ((*assignment)->getNumberOfInstances() == 1) { _assignmentQueue.erase(assignment); } deployableAssignment->decrementNumberOfInstances(); } else { // remove the assignment from the queue _assignmentQueue.erase(assignment); // until we get a check-in from that GUID // put assignment back in queue but stick it at the back so the others have a chance to go out _assignmentQueue.push_back(deployableAssignment); } // stop looping, we've handed out an assignment _assignmentQueueMutex.unlock(); return deployableAssignment; } else { // push forward the iterator to check the next assignment assignment++; } } _assignmentQueueMutex.unlock(); return NULL; } void DomainServer::removeAssignmentFromQueue(Assignment* removableAssignment) { _assignmentQueueMutex.lock(); std::deque::iterator assignment = _assignmentQueue.begin(); while (assignment != _assignmentQueue.end()) { if ((*assignment)->getUUID() == removableAssignment->getUUID()) { _assignmentQueue.erase(assignment); break; } else { // push forward the iterator to check the next assignment assignment++; } } _assignmentQueueMutex.unlock(); } bool DomainServer::checkInWithUUIDMatchesExistingNode(const HifiSockAddr& nodePublicSocket, const HifiSockAddr& nodeLocalSocket, const QUuid& checkInUUID) { NodeList* nodeList = NodeList::getInstance(); foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { if (node->getLinkedData() && nodePublicSocket == node->getPublicSocket() && nodeLocalSocket == node->getLocalSocket() && node->getUUID() == checkInUUID) { // this is a matching existing node if the public socket, local socket, and UUID match return true; } } return false; } void DomainServer::addStaticAssignmentsBackToQueueAfterRestart() { _hasCompletedRestartHold = true; // if the domain-server has just restarted, // check if there are static assignments in the file that we need to // throw into the assignment queue // pull anything in the static assignment file that isn't spoken for and add to the assignment queue for (int i = 0; i < MAX_STATIC_ASSIGNMENT_FILE_ASSIGNMENTS; i++) { if (_staticAssignments[i].getUUID().isNull()) { // reached the end of static assignments, bail break; } bool foundMatchingAssignment = false; NodeList* nodeList = NodeList::getInstance(); // enumerate the nodes and check if there is one with an attached assignment with matching UUID foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { if (node->getLinkedData()) { Assignment* linkedAssignment = (Assignment*) node->getLinkedData(); if (linkedAssignment->getUUID() == _staticAssignments[i].getUUID()) { foundMatchingAssignment = true; break; } } } if (!foundMatchingAssignment) { // this assignment has not been fulfilled - reset the UUID and add it to the assignment queue _staticAssignments[i].resetUUID(); qDebug() << "Adding static assignment to queue -" << _staticAssignments[i]; _assignmentQueueMutex.lock(); _assignmentQueue.push_back(&_staticAssignments[i]); _assignmentQueueMutex.unlock(); } } } void DomainServer::cleanup() { _staticAssignmentFile.unmap(_staticAssignmentFileData); _staticAssignmentFile.close(); }