/// <summary> /// Ensure that an externally-facing Service resource does not exist for the specified database server. /// </summary> public async Task EnsureExternalServiceAbsent() { RequireCurrentState(); ServiceV1 existingExternalService = await FindExternalService(); if (existingExternalService != null) { Log.LogInformation("Deleting external service {ServiceName} for server {ServerId}...", existingExternalService.Metadata.Name, State.Id ); StatusV1 result = await KubeClient.ServicesV1().Delete( name: existingExternalService.Metadata.Name, kubeNamespace: KubeOptions.KubeNamespace ); if (result.Status != "Success" && result.Reason != "NotFound") { Log.LogError("Failed to delete external service {ServiceName} for server {ServerId} (Message:{FailureMessage}, Reason:{FailureReason}).", existingExternalService.Metadata.Name, State.Id, result.Message, result.Reason ); } Log.LogInformation("Deleted external service {ServiceName} for server {ServerId}.", existingExternalService.Metadata.Name, State.Id ); } }
/// <summary> /// Ensure that an externally-facing Service resource exists for the specified database server. /// </summary> /// <returns> /// A <see cref="Task"/> representing the operation. /// </returns> public async Task EnsureExternalServicePresent() { RequireCurrentState(); ServiceV1 existingExternalService = await FindExternalService(); if (existingExternalService == null) { Log.LogInformation("Creating external service for server {ServerId}...", State.Id ); ServiceV1 createdService = await KubeClient.ServicesV1().Create( KubeResources.ExternalService(State, kubeNamespace: KubeOptions.KubeNamespace ) ); Log.LogInformation("Successfully created external service {ServiceName} for server {ServerId}.", createdService.Metadata.Name, State.Id ); } else { Log.LogInformation("Found existing external service {ServiceName} for server {ServerId}.", existingExternalService.Metadata.Name, State.Id ); } }
/// <summary> /// Find the server's associated externally-facing Service (if it exists). /// </summary> /// <returns> /// The Service, or <c>null</c> if it was not found. /// </returns> public async Task <ServiceV1> FindExternalService() { RequireCurrentState(); List <ServiceV1> matchingServices = await KubeClient.ServicesV1().List( labelSelector: $"cloud.dimensiondata.daas.server-id = {State.Id},cloud.dimensiondata.daas.service-type = external", kubeNamespace: KubeOptions.KubeNamespace ); if (matchingServices.Count == 0) { return(null); } return(matchingServices[matchingServices.Count - 1]); }
/// <summary> /// Get the public TCP port number on which the database server is accessible. /// </summary> /// <param name="server"> /// A <see cref="DatabaseServer"/> describing the server. /// </param> /// <param name="kubeNamespace"> /// An optional target Kubernetes namespace. /// </param> /// <returns> /// The port, or <c>null</c> if the externally-facing service for the server cannot be found. /// </returns> public async Task <int?> GetPublicPort() { RequireCurrentState(); List <ServiceV1> matchingServices = await KubeClient.ServicesV1().List( labelSelector: $"cloud.dimensiondata.daas.server-id = {State.Id}, cloud.dimensiondata.daas.service-type = external", kubeNamespace: KubeOptions.KubeNamespace ); if (matchingServices.Count == 0) { return(null); } ServiceV1 externalService = matchingServices[matchingServices.Count - 1]; return(externalService.Spec.Ports[0].NodePort); }
/// <summary> /// Determine the connection string for the specified server. /// </summary> /// <param name="serverId"> /// The Id of the target server. /// </param> /// <returns> /// The base UR. /// </returns> async Task <Uri> GetServerBaseAddress(string serverId) { if (String.IsNullOrWhiteSpace(serverId)) { throw new ArgumentException("Argument cannot be null, empty, or entirely composed of whitespace: 'serverId'.", nameof(serverId)); } Log.LogInformation("Determining connection string for server {ServerId}...", serverId ); DatabaseServer targetServer = await GetServer(serverId); List <ServiceV1> matchingServices = await KubeClient.ServicesV1().List( labelSelector: $"cloud.dimensiondata.daas.server-id = {serverId},cloud.dimensiondata.daas.service-type = internal", kubeNamespace: KubeOptions.KubeNamespace ); if (matchingServices.Count == 0) { Log.LogWarning("Cannot determine connection string for server {ServerId} (server's associated Kubernetes Service not found).", serverId ); throw RespondWith(NotFound(new { Reason = "EndPointNotFound", Id = serverId, EntityType = "DatabaseServer", Message = $"Cannot determine base address for server '{targetServer.Id}'." })); } ServiceV1 serverService = matchingServices[matchingServices.Count - 1]; string serverFQDN = $"{serverService.Metadata.Name}.{serverService.Metadata.Namespace}.svc.cluster.local"; int serverPort = serverService.Spec.Ports[0].Port; Log.LogInformation("Database proxy will connect to RavenDB server '{ServerFQDN}' on {ServerPort}.", serverFQDN, serverPort); return(new Uri($"http://{serverFQDN}:{serverPort}")); }
/// <summary> /// Determine the connection string for the specified <see cref="SqlRequest"/>. /// </summary> /// <param name="request"> /// The <see cref="SqlRequest"/> being executed. /// </param> /// <returns> /// The connection string. /// </returns> async Task <string> GetConnectionString(SqlRequest request) { if (request == null) { throw new ArgumentNullException(nameof(request)); } Log.LogInformation("Determining connection string for database {DatabaseId} in server {ServerId}...", request.DatabaseId, request.ServerId ); DatabaseServer targetServer = await DocumentSession.LoadAsync <DatabaseServer>(request.ServerId); if (targetServer == null) { Log.LogWarning("Cannot determine connection string for database {DatabaseId} in server {ServerId} (server not found).", request.DatabaseId, request.ServerId ); throw RespondWith(Ok(new SqlResult { ResultCode = -1, Errors = { new SqlError { Kind = SqlErrorKind.Infrastructure, Message = $"Unable to determine connection settings for database {request.DatabaseId} in server {request.ServerId} (server not found)." } } })); } List <ServiceV1> matchingServices = await KubeClient.ServicesV1().List( labelSelector: $"cloud.dimensiondata.daas.server-id = {targetServer.Id},cloud.dimensiondata.daas.service-type = internal", kubeNamespace: KubeOptions.KubeNamespace ); if (matchingServices.Count == 0) { Log.LogWarning("Cannot determine connection string for database {DatabaseId} in server {ServerId} (server's associated Kubernetes Service not found).", request.DatabaseId, request.ServerId ); throw RespondWith(Ok(new SqlResult { ResultCode = -1, Errors = { new SqlError { Kind = SqlErrorKind.Infrastructure, Message = $"Unable to determine connection settings for database {request.DatabaseId} in server {request.ServerId} (server's associated Kubernetes Service not found)." } } })); } ServiceV1 serverService = matchingServices[matchingServices.Count - 1]; (string serverFQDN, int?serverPort) = serverService.GetHostAndPort(portName: "sql-server"); if (serverPort == null) { Log.LogWarning("Cannot determine connection string for database {DatabaseId} in server {ServerId} (cannot find the port named 'sql-server' on server's associated Kubernetes Service).", request.DatabaseId, request.ServerId ); throw RespondWith(Ok(new SqlResult { ResultCode = -1, Errors = { new SqlError { Kind = SqlErrorKind.Infrastructure, Message = $"Unable to determine connection settings for database {request.DatabaseId} in server {request.ServerId} (cannot find the port named 'sql-server' on server's associated Kubernetes Service)." } } })); } Log.LogInformation("Database proxy will connect to SQL Server '{ServerFQDN}' on {ServerPort}.", serverFQDN, serverPort); var connectionStringBuilder = new SqlClient.SqlConnectionStringBuilder { DataSource = $"tcp:{serverFQDN},{serverPort}", }; var serverSettings = targetServer.GetSettings <SqlServerSettings>(); if (request.DatabaseId != MasterDatabaseId) { DatabaseInstance targetDatabase = await DocumentSession.LoadAsync <DatabaseInstance>(request.DatabaseId); if (targetDatabase == null) { Log.LogWarning("Cannot determine connection string for database {DatabaseId} in server {ServerId} (database not found).", request.DatabaseId, request.ServerId ); throw RespondWith(Ok(new SqlResult { ResultCode = -1, Errors = { new SqlError { Kind = SqlErrorKind.Infrastructure, Message = $"Unable to determine connection settings for database {request.DatabaseId} in server {request.ServerId} (database not found)." } } })); } connectionStringBuilder.InitialCatalog = targetDatabase.Name; if (request.ExecuteAsAdminUser) { connectionStringBuilder.UserID = "sa"; connectionStringBuilder.Password = serverSettings.AdminPassword; } else { connectionStringBuilder.UserID = targetDatabase.DatabaseUser; connectionStringBuilder.Password = targetDatabase.DatabasePassword; } } else { connectionStringBuilder.InitialCatalog = "master"; connectionStringBuilder.UserID = "sa"; connectionStringBuilder.Password = serverSettings.AdminPassword; } Log.LogInformation("Successfully determined connection string for database {DatabaseId} ({DatabaseName}) in server {ServerId} ({ServerSqlName}).", request.DatabaseId, connectionStringBuilder.InitialCatalog, request.ServerId, connectionStringBuilder.DataSource ); return(connectionStringBuilder.ConnectionString); }