/**
 * @file peerpull_migration.c
 *
 * @brief PeerPull migration algorithm.
 *
 * This is an implementation of the PeerPull service placement algorithm
 * proposed in:
 * Hongzhou Liu, Tom Roeder, Kevin Walsh, Rimon Barr, and Emin Guen Sirer.
 * Design and Implementation of a Single System Image Operating System for Ad
 * Hoc Networks. In Proceedings of the Third International Conference on Mobile
 * Systems, Applications, and Services (MobiSys '05), Seattle, Washington, USA,
 * June 2005.
 *
 * At the end of an epoch, this algorithm migrates the service to the client
 * that caused most traffic.
 *
 * Copyright 2007-2008, Freie Universitaet Berlin. All rights reserved.
 * Please refer to the COPYING file for detailed licensing information.
 *
 * @author Georg Wittenburg
 *
 * $Id: peerpull_migration.c 2685 2008-06-25 16:38:56Z wittenbu $
 */

#include "../../types.h"
#include "../../vars.h"
#include "../../interface.h"
#include "../../interface_internal.h"
#include "../../util/debug.h"
#include "../../util/list.h"
#include "../../util/callback.h"
#include "../../util/net.h"
#include "../../service/service.h"
#include "../../service/service_types.h"
#include "../placement.h"
#include "../placement_types.h"
#include "../placement_vars.h"
#include "../placement_types.h"
#include "peerpull_migration.h"

/**
 * @brief Epoch of the PeerPull algorithm in seconds.
 */
#define PLACEMENT_PEERPULL_EPOCH	600

/**
 * @brief Service migration callback.
 *
 * @param parameter Migration result.
 */
static void peerpull_migration_callback(callback_parameter_t parameter) {
	SERVICE_migration_result_t* result = (SERVICE_migration_result_t*) parameter;

	switch(result->error_code) {
	case C_ERROR_OK:
		C_DEBUG(C_DEBUG_LEVEL_NOTE, "PLACEMENT: Migration of service \"%s\" was successful.\n", result->service_id);
		break;
	case C_ERROR_PARAMETER:
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "PLACEMENT: Migration of service \"%s\" failed due to an invalid parameter.\n", result->service_id);
		break;
	case C_ERROR_TIMEOUT:
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "PLACEMENT: Migration of service \"%s\" timed out.\n", result->service_id);
		break;
	case C_ERROR_BUSY:
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "PLACEMENT: Migration of service \"%s\" failed because system is busy.\n", result->service_id);
		break;
	case C_ERROR_IN_PROGRESS:
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "PLACEMENT: Migration of service \"%s\" failed due to an invalid parameter.\n", result->service_id);
		break;
	case C_ERROR_COMM_ERROR:
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "PLACEMENT: Migration of service \"%s\" failed due to communication errors.\n", result->service_id);
		break;
	case C_ERROR_REMOTE_BUSY:
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "PLACEMENT: Migration of service \"%s\" failed because the remote system refused.\n", result->service_id);
		break;
	default:
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "PLACEMENT: Migration callback with unknown error code 0x%X.\n", result->error_code);
		abort();
	}
	FREE(result);
}

/**
 * @brief PeerPull migration algorithm.
 *
 * @param parameter Pointer to service for which to run this algorithm.
 */
static void peerpull_migration_run(callback_parameter_t parameter) {
	SERVICE_service_t* service = (SERVICE_service_t*) parameter;
	unsigned int max_service_requests_traffic = 0;
	node_t* node_with_max_service_request_traffic = NULL;
	unsigned int i;

	// Check whether the service is still running on this node.
	if(SERVICE_get_active_service(service->id) == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "PLACEMENT: Trying to run migration algorithm for service \"%s\", but service is not active.\n", service->id);
		return;
	} else if(service->placement_data->service_request_count == 0) {
		C_DEBUG(C_DEBUG_LEVEL_WARN, "PLACEMENT: Trying to run migration algorithm for service \"%s\", but no service requests have been received.\n", service->id);
		return;
	}

	// Look for node that caused most of traffic to this node.
	for(i = 0; i < list_size(service->placement_data->node_stats); i++) {
		PLACEMENT_node_stats_t* node_stats = (PLACEMENT_node_stats_t*) list_get(service->placement_data->node_stats, i);
		if(node_stats->service_requests_traffic > max_service_requests_traffic) {
			max_service_requests_traffic = node_stats->service_requests_traffic;
			node_with_max_service_request_traffic = &node_stats->node;
		}
	}

	// This basically can't happen but it can't hurt to check.
	if(node_with_max_service_request_traffic == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "PLACEMENT: %d service request(s) for service \"%s\" but no traffic.\n", service->placement_data->service_request_count, service->id);
		abort();
	}

	if(node_with_max_service_request_traffic->address != C_LOCAL_ADDR) {
		SERVICE_migration_result_t* result;
		C_DEBUG(C_DEBUG_LEVEL_NOTE, "PLACEMENT: Migrating service \"%s\" to node %s (%d bytes after %d total requests)...\n", service->id, addr_to_str(node_with_max_service_request_traffic->address), max_service_requests_traffic, service->placement_data->service_request_count);

		service->placement_data->active = false;
		MALLOC(result, SERVICE_migration_result_t);
		SERVICE_migrate_service(service->id, node_with_max_service_request_traffic->address, peerpull_migration_callback, result);
	} else {
		C_DEBUG(C_DEBUG_LEVEL_NOTE, "PLACEMENT: Service \"%s\" is placed optimally on the current node (%d bytes after %d total requests)...\n", service->id, addr_to_str(node_with_max_service_request_traffic->address), max_service_requests_traffic, service->placement_data->service_request_count);
		service->placement_data->callback_id = callback_add(peerpull_migration_run, service, PLACEMENT_PEERPULL_EPOCH);
	}
}

/**
 * @brief Start PeerPull migration algorithm.
 *
 * @param service Pointer to service for which to start this algorithm.
 */
void peerpull_migration_start(SERVICE_service_t* service) {
	service->placement_data->active = true;
	service->placement_data->callback_id = callback_add(peerpull_migration_run, service, PLACEMENT_PEERPULL_EPOCH);
}

/**
 * @brief Stop PeerPull migration algorithm.
 *
 * @param service Pointer to service for which to stop this algorithm.
 */
void peerpull_migration_stop(SERVICE_service_t* service) {
	if(service->placement_data->active) {
		service->placement_data->active = false;
		callback_cancel(service->placement_data->callback_id);
	}
}

/**
 * @brief Trigger PeerPull migration algorithm.
 *
 * @param service Pointer to service for which to trigger this algorithm.
 */
void peerpull_migration_trigger(SERVICE_service_t* service) {
	// Nothing to do.
}
