using System; using System.Collections.Generic; using System.Xml.Serialization; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Runtime.InteropServices; using Renci.SshNet; using StackExchange.Redis; using StackExchange.Redis.Extensions.Core; using StackExchange.Redis.Extensions.Core.Abstractions; using StackExchange.Redis.Extensions.Core.Configuration; using StackExchange.Redis.Extensions.Core.Implementations; namespace ServerCore; // HANDOVER: AWS S3 SDK Wrapper 클래스 이다. // S3 저장소에 관리할 Bucket 정보를 관리해준다. public abstract class RedisConnectorBase { private readonly string m_connection_string = string.Empty; private readonly RedisConfiguration m_redis_config; private ConnectionMultiplexer? m_connector; private SshClient? m_ssh_tuneling; private ForwardedPortLocal? m_forwarded_port_local; public RedisConnectorBase(string connectionString) { m_redis_config = new RedisConfiguration() { ConnectionString = connectionString, }; m_connection_string = connectionString; } public async Task tryConnectAsync() { if (false == isConfigureConnectionInfo()) { return false; } try { if (false == isConnected()) { ConnectionMultiplexer.SetFeatureFlag("preventthreadtheft", true); var config_options = ConfigurationOptions.Parse(m_connection_string); var connection = await ConnectionMultiplexer.ConnectAsync(config_options); if (null == connection) { var err_msg = $"Failed to connectAsync RedisServer !!! : ConnectString:{m_connection_string}"; throw new Exception(err_msg); } m_connector = connection; connection.ConnectionFailed += onConnectfailed; connection.ErrorMessage += onError; connection.InternalError += onInternalError; connection.ConnectionRestored += onConnectionRestored; connection.ConfigurationChanged += onConfigurationChanged; connection.ConfigurationChangedBroadcast += onMasterSlaveChanged; } } catch(Exception e) { var err_msg = $"Exception !!!, Failed to perform in tryConnectAsync() !!! : exception:{e}, connectionString:{m_connection_string} - {toBasicString()}"; Log.getLogger().error(err_msg); return false; } return true; } protected void setConnectionMultiplexer(ConnectionMultiplexer connector) => m_connector = connector; public bool tryConnect() { if (false == isConfigureConnectionInfo()) { return false; } try { if (false == isConnected()) { var config_options = ConfigurationOptions.Parse(m_connection_string); var connection = ConnectionMultiplexer.Connect(config_options); if (null == connection) { var err_msg = $"Failed to connect RedisServer !!! : ConnectString:{m_connection_string}"; throw new Exception(err_msg); } m_connector = connection; connection.ConnectionFailed += onConnectfailed; connection.ErrorMessage += onError; connection.InternalError += onInternalError; connection.ConnectionRestored += onConnectionRestored; connection.ConfigurationChanged += onConfigurationChanged; connection.ConfigurationChangedBroadcast += onMasterSlaveChanged; } } catch (Exception e) { var err_msg = $"Exception !!!, try connectAsync failed !!! : Exception:{e} - {toBasicString()}"; Log.getLogger().error(err_msg); return false; } return true; } //========================================================================================= // sshHost : ec2-54-69-223-245.us-west-2.compute.amazonaws.com // SSH 서버 (Jump Host or Bastion) // sshPort : 2211 // SSH 포트 // sshUsername : "ubuntu" // SSH 사용자 이름 // privateKeyPath : @"D:\SVN\Caliverse\Metaverse\trunk\Caliverse\Server\Security\SSH-Keys\pem\USWest2-KeyPair.pem" // Private Key 보안 파일 참조 경로 // localPort : 16379 // 로컬에서 사용할 포트 // redisHost : "clustercfg.metaverse-qa-cluster.ocif0u.usw2.cache.amazonaws.com" // Redis 클러스터 호스트 // redisPort : 6379 // Redis 사용 포트 // redisPassword : "wiUaVvNwX4PhBj&8" //========================================================================================= public bool startTuneling( string sshHost, int sshPort, string sshUserName, string privateKeyPath , int localPort, string redisHost, int redisPort, string redisPassword ) { // SSH Private Key 설정 PrivateKeyFile privateKey = new PrivateKeyFile(privateKeyPath); var keyFiles = new[] { privateKey }; try { var ssh_client = new SshClient(sshHost, sshPort, sshUserName, keyFiles); // SSH 연결 시도 ssh_client.Connect(); // 포트 포워딩 설정 (로컬 포트 -> 원격 Redis 호스트) var portForward = new ForwardedPortLocal("127.0.0.1", (uint)localPort, redisHost, (uint)redisPort); ssh_client.AddForwardedPort(portForward); portForward.Start(); m_forwarded_port_local = portForward; m_ssh_tuneling = ssh_client; } catch(Exception e) { var err_msg = $"Exception !!!, Failed to function in startTuneling() !!! : Exception:{e}"; Log.getLogger().error(err_msg); return false; } return true; } public void stopTuneling() { if (null != m_forwarded_port_local) { m_forwarded_port_local.Stop(); m_forwarded_port_local = null; } if(null != m_ssh_tuneling) { // SSH 연결 종료 m_ssh_tuneling.Disconnect(); m_ssh_tuneling = null; } } protected bool isConfigureConnectionInfo() { if ( 0 >= m_connection_string.Length ) { var err_msg = $"Invalid Configure of RedisConnector, Empty m_connectionString !!! - {toBasicString()}"; Log.getLogger().error(err_msg); return false; } return true; } public bool isConnected() { if( null == m_connector || false == m_connector.IsConnected) { return false; } return true; } public IDatabase? getDatabase() { if(false == isConnected()) { return null; } return m_connector?.GetDatabase(); } public ConnectionMultiplexer? getConnectionMultiplexer() => m_connector; #region event protected virtual void onConnectionRestored(object? sender, ConnectionFailedEventArgs e) { Log.getLogger().warn($"Redis connection restored !!! : {e} - {toBasicString()}"); } protected void onConnectfailed(object? sender, ConnectionFailedEventArgs e) { Log.getLogger().error($"Redis connect failed !!! : ConnectionFailed:{e.Exception} - {toBasicString()}"); } protected void onError(object? sender, RedisErrorEventArgs e) { Log.getLogger().error($"Redis connection restored !!! : RedisError:{e} - {toBasicString()}"); } protected void onInternalError(object? sender, InternalErrorEventArgs e) { Log.getLogger().error($"Redis internal error !!! : InternalError:{e} - {toBasicString()}"); } protected void onConfigurationChanged(object? sender, EndPointEventArgs e) { Log.getLogger().warn($"Redis configuration changed !!! : EndPoint:{e} - {toBasicString()}"); } protected void onMasterSlaveChanged(object? sender, EndPointEventArgs e) { Log.getLogger().warn($"Redis changed master-slave !!! : EndPoint:{e.EndPoint} - {toBasicString()}"); } #endregion event public string toBasicString() { return $"RedisConnector - Class:{this.getTypeName()}, ConnectString:{m_connection_string}"; } }