#include "ShmManager.h"

#include "Logger.h"
#include "ProcessConfig.h"

#include <string.h>
#include <math.h>



// 생성자
CShmManager::CShmManager( struct RcInfo * p )
	: m_pShm( p )
{
	
	
}

// 소멸자
CShmManager::~CShmManager()
{

}

// 멤버 변수인 공유메모리에 대한 포인터가 유효하지 여부를 판단하기 위한 함수.
bool CShmManager::IsValid() const
{
	if( m_pShm != NULL )
		return true;
	else
		return false;
}

// 공유메모리 전체 영역을 초기화하기 위한 함수.
void CShmManager::ClearAll() const
{
	if( m_pShm != NULL )
	{
		memset( m_pShm, 0x00, sizeof( struct RcInfo ) );

		// 각 Client Slot 의 서비스 상태 정보는 SERVICE_STAT_NOT_DEFINE 로 설정 처리
		for( int i = 0; i < MAX_CLIENT_COUNT; i++ )
		{
			m_pShm->client[i].service.service_stat = SERVICE_STAT_NOT_DEFINE;
		}

		// client index 정보는 -1 로 초기화.
		m_pShm->client_max_index = -1;
	}
}


// 공유메모리 상의 Client index 을 할당
// - 전달 받은 IP 정보를 기반으로 이미 존재하는 경우.. 해당 index 반환.
// - 존재하지 않는 경우.. 적합한 index 정보를 할당하여 전달 처리.
// return 오류 발생시 -1, 그 외 client index
// 본 함수는 업무 flow 상 동시 call을 하지 않아.. lock 을 사용하지 않음.
// - 만약 동시 call 할 경우.. lock 기능 추가 필요.
int CShmManager::AllocateClientIndex( const char * pClientAddress ) const
{
	// 기존에 이미 할당된 slot 이 존재하는지 확인.
	int i;
	int nMaxIndex = m_pShm->client_max_index;
	for( i = 0 ; i <= nMaxIndex; i++ )
	{
		// 해당 slot 이 사용 중이고...
		if( m_pShm->client[i].use == 1 )		
		{
			// client IP 정보가 동일한 경우...
			if( strcmp( pClientAddress, m_pShm->client[i].client_ip ) == 0 )
			{
				return i;
			}
		}
	}

	// 할당된 slot 이 존재하지 않는 경우....
	// 모든 slot 을 대상으로 할당 가능한 slot 이 존재하는지 확인 후
	// slot 할당 및 index 정보 업데이트 처리.
	int index = -1;
	for( index = 0; index < MAX_CLIENT_COUNT; index++ )
	{
		// 해당 slot 이 미사용 중인 경우...
		if( m_pShm->client[index].use == 0 )
		{
			// 초기화...
			memset( &( m_pShm->client[index] ), 0x00, sizeof( struct ClientInfo ) );
			m_pShm->client[index].service.service_stat = SERVICE_STAT_NOT_DEFINE;
			
			// 사용 여부 설정.
			m_pShm->client[index].use = 1;

			// IP 정보 복사
			memcpy( m_pShm->client[index].client_ip, pClientAddress, IP_ADDR_LENGTH );

			// 최대 client index 조정.
			if( index > m_pShm->client_max_index )
			{
				m_pShm->client_max_index = index;
			}

			return index;
		}
	}

	// 적합한 index 를 찾지 못한 경우...
	return -1;
}

// 공유메모리 상의 Client index 조회
// - 전달 받은 IP 정보를 기반으로 이미 존재하는 경우.. 해당 index 반환.
// - 존재하지 않을 경우.. -1 반환.
// return 오류 발생시 -1, 그 외 client index
// 본 함수는 업무 flow 상 동시 call을 하지 않아.. lock 을 사용하지 않음.
// - 만약 동시 call 할 경우.. lock 기능 추가 필요.
int CShmManager::FindClientIndex( const char * pClientAddress ) const
{
	// 기존에 이미 할당된 slot 이 존재하는지 확인.
	int i;
	int nMaxIndex = m_pShm->client_max_index;
	for( i = 0; i <= nMaxIndex; i++ )
	{
		// 해당 slot 이 사용 중이고...
		if( m_pShm->client[i].use == 1 )
		{
			// client IP 정보가 동일한 경우...
			if( strcmp( pClientAddress, m_pShm->client[i].client_ip ) == 0 )
			{
				return i;
			}
		}
	}
		
	// 해당 index 를 찾지 못한 경우...
	return -1;
}


// 공유메모리 상의 Client index 해제 처리.
void CShmManager::ReleaseClientIndex( int index ) const
{
	// 잘못된 index 정보인 경우.. 그냥 무시
	if( index < 0 || index >= MAX_CLIENT_COUNT )
		return;

	// 정상 index 인 경우...
	m_pShm->client[index].use = 0;
	
	// 최대 client index 조정.
	if( m_pShm->client_max_index == index )
	{
		UpdateClientMaxIndex();		
	}

	return;
}

// 공유메모리 상의 Client Max index 를 다시 조사하여 설정 처리.
void CShmManager::UpdateClientMaxIndex() const
{
	for( int i = (MAX_CLIENT_COUNT - 1); i >= 0; --i )
	{
		// 해당 slot 이 미사용 중인 경우...
		if( m_pShm->client[i].use == 1 )
		{
			m_pShm->client_max_index = i;
			return;
		}
	}

	// 만약 유효한 slot 이 없는 경우....
	// client index 정보는 -1 로 초기화.
	m_pShm->client_max_index = -1;

	return;
}



// 공유메모리 상의 Client index 에 해당하는 client 영역을 반환.
struct ClientInfo * CShmManager::GetClientPtr( int index ) const
{
	// 잘못된 index 정보인 경우.. 그냥 무시
	if( index < 0 || index >= MAX_CLIENT_COUNT )
		return NULL;

	return &(m_pShm->client[index]);	
}



// vmasd 에서 받은 정보를 공유메모리에 저장 처리
// - 실제 오류 상황인 경우에만.. false 반환.
// - 그외 이미 존재하는 상황 등인 경우에는 정상 처리된 것으로 반환 처리한다.
// return false 
bool CShmManager::SetVmasdData( struct vmasd_broadcast_message_body * pBody ) const
{	
	int index = -1;

	// 기존에 이미 할당된 slot 이 존재하는지 확인.
	int nMaxIndex = m_pShm->client_max_index;
	for( int i = 0; i <= nMaxIndex; i++ )
	{
		// 해당 slot 이 이미 사용 중이고...
		if( m_pShm->client[i].use == 1 && m_pShm->client[i].valid == 1 )
		{
			// csagnetd 의 경우. client ip 를 비교했지만...
			// vmasd 는 hostname 을 비교해야 함.
			// - csagentd 는 공인, 사설망을 사용할 수 있어..  사설망만을 사용하는 vmasd 와 동시 동작시 ip 가 다를 수 있다.

			// hostname 이 동일한 경우...
			if( strcmp( pBody->hostname, m_pShm->client[i].hostname ) == 0 )
			{
				// client_app 이 vmasd 인 경우...
				if( m_pShm->client[i].client_app == 1 )
				{
					index = i;
					break;
				}
				else
				{
					// csagentd 정보와 충돌인 경우... csagentd 의 정보를 우선 사용토록 처리한다.
					_LOG( LINF, "vmasd shm update skip. csagentd data exist. [%s][%s]", pBody->domain, pBody->hostname );
					return true;
				}
			}
		}
	}

	// 기존에 할당된 slot 이 존재하지 않는 경우....
	// 모든 slot 을 대상으로 할당 가능한 slot 이 존재하는지 확인 후
	// slot 할당 및 index 정보 업데이트 처리.
	if( index == -1 )
	{
		for( int j = 0; j < MAX_CLIENT_COUNT; j++ )
		{
			// 해당 slot 이 미사용 중인 경우...
			if( m_pShm->client[j].use == 0 )
			{
				index = j;

				// 초기화...
				memset( &( m_pShm->client[j] ), 0x00, sizeof( struct ClientInfo ) );
				m_pShm->client[j].service.service_stat = SERVICE_STAT_NOT_DEFINE;

				// 사용 여부 설정.
				m_pShm->client[j].use = 1;

				// IP 정보 복사
				strcpy( m_pShm->client[j].client_ip, pBody->domain );
				
				// hostname 정보 복사.
				strcpy( m_pShm->client[j].hostname, pBody->hostname );

				// client_app 을 vmasd 로 설정.
				m_pShm->client[j].client_app = 1;
				
				// 최대 client index 조정.
				if( j > m_pShm->client_max_index )
				{
					m_pShm->client_max_index = j;
				}

				break;
			}
		}
	}

	if( index == -1 )
	{
		LOG( LERR, "vmasd shm update fail. shm allocate fail.[%s][%s]", pBody->domain, pBody->hostname );
		return false;
	}
	else
	{
		struct ClientInfo * pClient = &( m_pShm->client[index] );
				
		// Body 정보를 저장 처리.

		// vmasd storage 용량 정보 처리 관련...
		pClient->system.storage_total = pBody->total;
		pClient->system.storage_avail = pBody->free;
		if( pClient->system.storage_avail == 0 )
		{
			if( pClient->system.storage_total == 0 )
			{
				// 1. total = 0, free = 0 인 경우
				// - 공인 IP 정보 추출 실패, 공인 IP 를 통한 Apache 접속 테스트 실패, DNS resolve 체크 실패시...
				// - 서비스 불가 (SERVICE_STAT_DISABLE) 상태로 처리.				
				pClient->service.service_stat = SERVICE_STAT_DISABLE;

				// storage 최대 사용량 정보는 0 으로 설정 처리.
				pClient->system.storage_max_usage = 0;
			}
			else
			{
				// 2. total = 정상, free = 0 인 경우
				// - free 용량이 total 보다 큰 경우
				// - Read Only (SERVICE_STAT_RONLY) 상태로 처리한다.
				pClient->service.service_stat = SERVICE_STAT_RONLY;

				// storage 최대 사용량 정보는 100 % 으로 설정 처리.
				pClient->system.storage_max_usage = 100;
			}
		}
		else
		{
			// free 용량 정보는 정상...
			if( pClient->system.storage_total == 0 )
			{
				// total 정보가 0 인 경우...
				// - 추출 관련 오류 발생 상황으로 판단되며...
				// - Read Only (SERVICE_STAT_RONLY) 상태로 처리한다.
				pClient->service.service_stat = SERVICE_STAT_RONLY;

				// storage 최대 사용량 정보는 100 % 으로 설정 처리.
				pClient->system.storage_max_usage = 100;
			}
			else
			{
				// total, free 정보가 모두 정상인 경우...

				// 평균 사용량 정보를 추출한다.
				int usage = (int)ceil( (double)( pClient->system.storage_total - pClient->system.storage_avail ) / pClient->system.storage_total  * 100.0 );

				// 원래는 최대 storage 사용량 의미이지만.. vmasd 는 평균 사용량으로 밖에 처리할 수 없음.
				pClient->system.storage_max_usage = usage;

				// pBody->manual_disable
				// 2 : fsck_ufs 프로세스가 동작 중인 경우..             --> SERVICE_STAT_RONLY 로 설정.
				// 1 : /user/service/vmasd.disable 파일이 존재할 경우..
				//     .disable 설정이 되지 않은 유효 Mount 중.. 가장 큰 Free Disk 용량(f_bfree) 이 0 인 경우...  --> SERVICE_STAT_RONLY 로 설정.
				// 0 : 그 외..

				if( pBody->manual_disable == 0 )
				{
					// 정상인 상태에서...
					// storeage 사용량이 conf 에 정의된 설정값 이상인 경우... 강제로 read-only 로 설정 처리한다.
					if( usage >= CProcessConfig::GetInstance()->GetStorageMaxUsageToDisable() )
					{
						_LOG( LINF, "vmasd client[%s] hostname[%s] storage max usage over [%d/%d]. set read only stat."
							  , pBody->domain, pBody->hostname, usage, CProcessConfig::GetInstance()->GetStorageMaxUsageToDisable() );

						pClient->service.service_stat = SERVICE_STAT_RONLY;
					}
					else
						pClient->service.service_stat = SERVICE_STAT_OK;
				}
				else
				{
					pClient->service.service_stat = SERVICE_STAT_RONLY;
				}
			}
		}
		

		// vmasd 는 평균 busy 율 사용,  csagentd 는 Max busy 율 사용.
		// 따라서 값의 의미가 정확히 일치하지는 않지만.. 그래도 저장 처리한다.
		pClient->system.disk_max_busy = pBody->disk_percent;

		// vmasd 는 마지막 disk 의 queue length. csagent 는 disk 의 최대 queue length
		// 따라서 값의 의미가 정확히 일치하지는 않지만.. 그래도 저장 처리한다.
		pClient->system.disk_max_queue = pBody->disk_backlog;
				
		pClient->system.tcp_80           = pBody->connection_total;
		pClient->system.tcp_80_establish = pBody->connection_established;
		
		pClient->system.network_packet_in   = pBody->traffic_ip;
		pClient->system.network_packet_out  = pBody->traffic_op;
		pClient->system.network_traffic_in  = pBody->traffic_ib;
		pClient->system.network_traffic_out = pBody->traffic_ob;
		

		// 현재 시간 정보 추출 및 업데이트
		time_t currentTime = time( 0 );
		
		struct timespec currentClock;
		memset( &currentClock, 0x00, sizeof( struct timespec ) );
		clock_gettime( CLOCK_MONOTONIC, &currentClock );
		
		pClient->service.update_time           = currentTime;
		pClient->service.update_time_clock_sec = currentClock.tv_sec;
		pClient->system.update_time            = currentTime;
		
		// Data 유효 여부 설정.
		pClient->valid = 1;
		
		return true;
	}	
}

// Alive (상태가 OK, Read-only) 인 FHS 목록 정보 조회 요청 
// param vecResult [out] 추출된 alive fhs 목록을 저장하기 위한 vector 참조.
// return 목록 갯수
int CShmManager::GetAliveFhsList( std::vector<struct FhsServiceStatInfo> & vecResult ) const
{
	int nMaxIndex = m_pShm->client_max_index;
	int nResult = 0;
	
	struct FhsServiceStatInfo stFhs;
	memset( &stFhs, 0x00, sizeof( struct FhsServiceStatInfo ) );

	// 결과를 저장할 vector 는 초기화 하지 않음.
	// - 이미 다른 목적으로 data 를 저장하여 전달할 수 있으므로.. 초기화하지 않음.

	// Max index 까지 루프를 돌면서... 검색
	for( int i = 0; i <= nMaxIndex; i++ )
	{
		// slot 할당되어 있고.. data 가 유효한 경우...
		if( m_pShm->client[i].use == 1 && m_pShm->client[i].valid == 1 )
		{
			// 서비스 상태가 OK, read only 인 경우...
			if( m_pShm->client[i].service.service_stat == SERVICE_STAT_OK
				|| m_pShm->client[i].service.service_stat == SERVICE_STAT_RONLY )
			{
				// update 처리가 오래된 garbage 데이터는 control 모듈에서 정리 처리하므로...
				// 여기서는 체크하지 않는다.

				// hostname 정보가 유효한지 확인 후...
				if( strlen( m_pShm->client[i].hostname ) > 0 )
				{
					// hostname 정보 복사.
					strncpy( stFhs.hostname, m_pShm->client[i].hostname, HOST_NAME_LENGTH );

					// 서비스 상태 정보 설정.
					stFhs.service_stat = m_pShm->client[i].service.service_stat;

					// vector 에 결과 저장
					vecResult.push_back( stFhs );
					
					nResult += 1;
				}
			}
		}
	}

	return nResult;
}


// 공유메모리에 저장된 client 정보 중 갱신되지 않는 오래된 client 정보를 정리한다.
// param expireTime [in] 공유메모리 update_time_clock_sec 정보와 expire 처리 여부를 비교하기 위한 기준값.
// return expire 처리된 client 수.
int CShmManager::ExpireClient( time_t expireTime ) const
{
	int nExpireCount = 0;

	// Max Index 에 오류가 있을 수도 있으므로..... 전체 slot 을 모두 검사한다.
	for( int i = 0; i < MAX_CLIENT_COUNT; i++ )
	{
		// 해당 slot 이 사용 중이고.. data 정보가 유효한 경우...
		if( m_pShm->client[i].use == 1 && m_pShm->client[i].valid == 1 && m_pShm->client[i].service.update_time_clock_sec != 0 )
		{
			// 최종 update clock 정보와 전달받은 expire 정보를 비교...
			if( m_pShm->client[i].service.update_time_clock_sec < expireTime )
			{
				// 전달받은 expire 시간보다 오래된 client 인 경우....
				// - 로깅 후 expire 처리한다.

				struct tm timeLast;
				localtime_r( &(m_pShm->client[i].service.update_time), &timeLast );

				_LOG( LWAR, "client[%s] stat[%d] app[%d] expired. last update time[%04d-%02d-%02d %02d:%02d:%02d]"
					  , m_pShm->client[i].hostname, m_pShm->client[i].service.service_stat, m_pShm->client[i].client_app
					  , timeLast.tm_year + 1900, timeLast.tm_mon + 1, timeLast.tm_mday
					  , timeLast.tm_hour, timeLast.tm_min, timeLast.tm_sec
					  );

				// 해제 처리 시작
				// vmasd 인 경우.. valid, use 까지 reset 처리.
				// csagent 인 경우.. valid 만 reset 처리 ( 이래야 세션이 살아 있는 중에 재할당을 방지하고.. 세션이 끊어지면서.. use 를 0으로 처리함)
				if( m_pShm->client[i].client_app == 1 )
				{
					// vmasd 인 경우.. 
					m_pShm->client[i].valid = 0;
					m_pShm->client[i].use = 0;
				}
				else
				{
					// csagent 인 경우...
					m_pShm->client[i].valid = 0;
				}
				
				// count 증가 처리
				++nExpireCount;
			}
		}
	}
	
	// expire 처리된 client 가 존재하는 경우.... Max Index 재설정 처리.
	if( nExpireCount > 0 )
	{
		UpdateClientMaxIndex();
	}
	
	return nExpireCount;
}

// GMS 로 전송할 RC 상태 정보 요청 
// param vecResult [out] 추출된 목록을 저장하기 위한 vector 참조.
// return 목록 갯수
int CShmManager::GetGmsSendList( std::vector<struct PacketDataGms> & vecResult ) const
{

	struct PacketDataGms stSystem;
	int nResult   = 0;
	int nMaxIndex = m_pShm->client_max_index;

	// 결과를 저장할 vector 는 초기화 하지 않음.
	// - 이미 다른 목적으로 data 를 저장하여 전달할 수 있으므로.. 초기화하지 않음.

	// Max index 까지 루프를 돌면서... 검색
	for( int i = 0; i <= nMaxIndex; i++ )
	{
		// slot 할당되어 있고.. data 가 유효한 경우...
		if( m_pShm->client[i].use == 1 && m_pShm->client[i].valid == 1 )
		{
			// 임시 객체 초기화.
			memset( &stSystem, 0x00, sizeof( struct PacketDataGms ) );
			
			// 저장할 정보 변환 처리.
			strncpy( stSystem.domain, m_pShm->client[i].hostname, 511 );
			
			stSystem.total = m_pShm->client[i].system.storage_total;
			stSystem.free  = m_pShm->client[i].system.storage_avail;

			// 정상 상태인 경우.. system 업데이트 시간 사용..
			// ronly, disable, out 등의 상황인 경우.. service 업데이트 시간 사용.
			if( m_pShm->client[i].service.service_stat == SERVICE_STAT_OK )
			{
				stSystem.manual_disable = 0;
				stSystem.stamp = m_pShm->client[i].system.update_time;
			}
			else
			{
				stSystem.stamp = m_pShm->client[i].service.update_time;

				if( m_pShm->client[i].service.service_stat == SERVICE_STAT_RONLY )
				{
					stSystem.manual_disable = 1;
				}
				else if( m_pShm->client[i].service.service_stat == SERVICE_STAT_OUT)
				{
					stSystem.manual_disable = 3;
				}
				else
				{
					stSystem.manual_disable = 2;					
				}
			}
						
			// (disk_backlog/10) + (connection_established/50) 의 계산 값... ( vmasd 소스 참고)
			stSystem.upload_level = ( m_pShm->client[i].system.disk_max_queue ) / 10 + (m_pShm->client[i].system.tcp_80_establish) / 50;
			
			stSystem.disk_backlog = (int)( m_pShm->client[i].system.disk_max_queue );
			stSystem.disk_percent = m_pShm->client[i].system.disk_max_busy;
			
			stSystem.connection_established = (int)( m_pShm->client[i].system.tcp_80_establish );
			stSystem.connection_total       = (int)( m_pShm->client[i].system.tcp_80 );
			
			stSystem.traffic_ip = (uint32_t)( m_pShm->client[i].system.network_packet_in );
			stSystem.traffic_ib = (uint32_t)( m_pShm->client[i].system.network_traffic_in );
			stSystem.traffic_op = (uint32_t)( m_pShm->client[i].system.network_packet_out );
			stSystem.traffic_ob = (uint32_t)( m_pShm->client[i].system.network_traffic_out );
			
			strncpy( stSystem.hostname, m_pShm->client[i].hostname, 63 );
			
			// vector 에 결과 저장
			vecResult.push_back( stSystem );

			// result count 증가.
			nResult += 1;
		}
	}

	return nResult;
}



// Top FHS 정보 추출
// param vecResult [out] 추출된 목록을 저장하기 위한 vector 참조.
// param dDiskMaxBusyWeight [in] score 항목 계산시 disk_max_busy 정보에 대한 가중치값.
// return 목록 갯수
int CShmManager::GetWritableFhsList( std::vector<struct WritableFhsInfo> & vecResult, double dDiskMaxBusyWeight ) const
{
	struct WritableFhsInfo stWritableFhs;
	
	int nResult = 0;
	int nMaxIndex = m_pShm->client_max_index;

	// 결과를 저장할 vector 는 초기화 하지 않음.
	// - 이미 다른 목적으로 data 를 저장하여 전달할 수 있으므로.. 초기화하지 않음.
	
	// FHS 의 시스템 정보 업데이트가 오래된 경우.. 
	// - GarbageClean Thread 가 지정된 시간( 20 sec ) 를 초과한 경우.. expire 처리하지만...
	// - 오류가 발생할 수 있고... FHS 상태가 이상할 경우.. 해당 장비는 선택하지 않는 것이 좋으므로...
	// - 업데이트가 비교적 최근에 발생한 FHS 정보만을 사용토록 한다.
	// - vmasd 는 6 sec 마다... csagentd 는 4(service), 10(system) sec 마다 정보 전송...
	// - 따라서.. 8 sec 초과된 정보는 사용하지 않도록 한다.
	// - local time 은 시간 동기화 영향 받으므로... update_time_clock_sec 항목을 사용하여 판단한다.
	time_t    expireTime;
	struct timespec currentClock;
	memset( &currentClock, 0x00, sizeof( struct timespec ) );
	clock_gettime( CLOCK_MONOTONIC, &currentClock );

	expireTime = currentClock.tv_sec - 8;


	// Max index 까지 루프를 돌면서... 검색
	for( int i = 0; i <= nMaxIndex; i++ )
	{
		// slot 할당되어 있고.. data 가 유효하고.. 
		// write 가능 상태인 경우.. 
		// system 상태 정보가 정상인 경우...( system 정보가 늦게 수신되므로.. 이를 반영 )
		if( m_pShm->client[i].use == 1
			&& m_pShm->client[i].valid == 1
			&& m_pShm->client[i].service.service_stat == SERVICE_STAT_OK 
			&& m_pShm->client[i].system.update_time != 0 )
		{
			// 최종 update clock 정보와 전달받은 expire 정보를 비교...
			if( m_pShm->client[i].service.update_time_clock_sec >= expireTime )
			{
				// expire 시간보다 오래되지 않은 최근 업데이트된 FHS 인 경우..

				// 임시 객체 초기화.
				memset( &stWritableFhs, 0x00, sizeof( struct WritableFhsInfo ) );

				// hostname 저장.
				strncpy( stWritableFhs.hostname, m_pShm->client[i].hostname, HOST_NAME_LENGTH );

				// write score 계산 처리.
				// - storage_max_usage 항목과
				// - disk_max_busy * 전달받은 가중치 값의 합.
				stWritableFhs.score = (double)( m_pShm->client[i].system.storage_max_usage ) 
					                  + ( ( m_pShm->client[i].system.disk_max_busy ) * dDiskMaxBusyWeight );

				// vector 에 결과 저장
				vecResult.push_back( stWritableFhs );

				// result count 증가.
				nResult += 1;
			}
		}
	}

	return nResult;
}
