# Copyright 2022 PerfKitBenchmarker Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Responsible for IAAS relational database provisioning.

This class is responsible to provide helper methods for IAAS relational
database.
"""

import ntpath
import os

from absl import flags
from perfkitbenchmarker import data
from perfkitbenchmarker import db_util
from perfkitbenchmarker import errors
from perfkitbenchmarker import iaas_relational_db
from perfkitbenchmarker import os_types
from perfkitbenchmarker import relational_db
from perfkitbenchmarker import sql_engine_utils
from perfkitbenchmarker import virtual_machine
from perfkitbenchmarker import vm_util

CONTROLLER_SCRIPT_PATH = (
    "relational_db_configs/sqlserver_ha_configs/controller.ps1"
)

FCI_CLUSTER_SCRIPT_PATH = (
    "relational_db_configs/sqlserver_ha_configs/fci_cluster.ps1"
)

FLAGS = flags.FLAGS

IS_READY_TIMEOUT = 600  # 10 minutes
MSSQL_PID = "developer"  # Edition of SQL server on Linux

DEFAULT_ENGINE_VERSION = sql_engine_utils.SQLSERVER_ENTERPRISE


class SQLServerIAASRelationalDb(iaas_relational_db.IAASRelationalDb):
  """Object representing a IAAS relational database Service."""
  TEMPDB_DISK_LETTER = "T"

  ENGINE = sql_engine_utils.SQLSERVER

  def __init__(self, relational_db_spec):
    """Initialize the SQLServer IAAS relational database object.

    Args:
      relational_db_spec: spec of the managed database.
    """
    super().__init__(relational_db_spec)
    self.ConfirmFlags()

  @property
  def replica_vms(self):
    """Server VM of replicas for hosting a managed database.

    Raises:
      RelationalDbPropertyNotSetError: if the server_vm is missing.

    Returns:
      The replica vms.
    """
    if not hasattr(self, "_replica_vms"):
      raise relational_db.RelationalDbPropertyNotSetError(
          "replica_vms is not set"
      )
    return self._replica_vms

  @replica_vms.setter
  def replica_vms(self, replica_vms):
    self._replica_vms = replica_vms

  @property
  def controller_vm(self):
    """Server VM of replicas for hosting a managed database.

    Raises:
      RelationalDbPropertyNotSetError: if the server_vm is missing.

    Returns:
      The replica vms.
    """
    if not hasattr(self, "_controller_vm"):
      raise relational_db.RelationalDbPropertyNotSetError(
          "controller_vm is not set"
      )
    return self._controller_vm

  @controller_vm.setter
  def controller_vm(self, controller_vm):
    self._controller_vm = controller_vm

  def ConfirmFlags(self):
    """Confirm flags to make sure all the requirements are met."""
    # If high availability mode, set high availability type if needed
    if self.spec.high_availability and self.spec.high_availability_type is None:
      self.spec.high_availability_type = "FCIS2D"

  def SetVms(self, vm_groups):
    super().SetVms(vm_groups)
    if "servers" in vm_groups:
      self.server_vm = vm_groups["servers"][0]

    if self.spec.high_availability:
      assert len(vm_groups["servers_replicas"]) >= 1
      self.replica_vms = vm_groups["servers_replicas"]
      self.controller_vm = vm_groups["controller"][0]

  def MoveSQLServerTempDb(self):
    """Moves the SQL Server temporary database to LocalSSD."""
    vms = [self.server_vm]
    if self.spec.high_availability_type == "AOAG" and self.replica_vms:
      vms.extend(self.replica_vms)

    # Moves the SQL Server temporary database to LocalSSD.
    for vm in vms:
      stdout, _ = vm.RemoteCommand(
          "Get-PSDrive -PSProvider FileSystem | Select Name"
      )

      drive_list = [
          str(drive.strip().replace("\r", ""))
          for drive in stdout.split("\n")
          if drive
      ]

      if self.TEMPDB_DISK_LETTER in drive_list:
        stdout, _ = vm.RemoteCommand(
            'sqlcmd -h -1 -Q "SET NOCOUNT '
            " ON; select f.name + CASE WHEN "
            "f.type = 1 THEN '.ldf' "
            "ELSE '.mdf' END "
            "FROM sys.master_files "
            "f WHERE f.database_id"
            " = DB_ID('tempdb');\""
        )
        tmp_db_files_list = [
            str(tmp_file.strip().replace("\r", ""))
            for tmp_file in stdout.split("\n")
            if tmp_file
        ]

        for tmp_db_file in tmp_db_files_list:
          tmp_db_name = tmp_db_file.split(".")[0]
          vm.RemoteCommand(
              'sqlcmd -Q "ALTER DATABASE tempdb '
              "MODIFY FILE (NAME = [{}], "
              "FILENAME = '{}:\\TEMPDB\\{}');\"".format(
                  tmp_db_name, self.TEMPDB_DISK_LETTER, tmp_db_file
              )
          )

        vm.RemoteCommand("Restart-Service MSSQLSERVER -Force")

  def _SetupWindowsUnamangedDatabase(self):
    self.spec.database_username = "sa"
    self.spec.database_password = db_util.GenerateRandomDbPassword()

    if self.spec.high_availability:
      if (self.spec.high_availability_type == "FCIMW"
          or self.spec.high_availability_type == "FCIS2D"):
        self.ConfigureSQLServerHaFci()
      elif self.spec.high_availability_type == "AOAG":
        self.ConfigureSQLServerHaAoag()
        ConfigureSQLServer(
            self.server_vm,
            self.spec.database_username,
            self.spec.database_password)
        ConfigureSQLServer(
            self.replica_vms[0],
            self.spec.database_username,
            self.spec.database_password)
        self.MoveSQLServerTempDb()
        self.EnsurePrimaryReplica()
    else:
      ConfigureSQLServer(
          self.server_vm,
          self.spec.database_username,
          self.spec.database_password)
      self.MoveSQLServerTempDb()

  def _SetEndpoint(self):
    """Set the DB endpoint for this instance during _PostCreate."""
    super()._SetEndpoint()
    if self.spec.high_availability:
      perf_domain = "perf" + FLAGS.run_uri[:6]
      self.endpoint = "fcidnn.{}.local".format(perf_domain)
    else:
      self.endpoint = self.server_vm.internal_ip

  def _SetupLinuxUnmanagedDatabase(self):
    if self.spec.engine_version == "2022":
      mssql_name = "mssql2022"
    elif self.spec.engine_version == "2019":
      mssql_name = "mssql2019"
    else:
      raise NotImplementedError(
          "Invalid database engine version: {}. "
          "Only 2019 and 2022 are supported.".format(self.spec.engine_version)
      )

    self.spec.database_username = "sa"
    self.spec.database_password = vm_util.GenerateRandomWindowsPassword(
        password_length=vm_util.PASSWORD_LENGTH, special_chars="*!@#+"
    )

    self.server_vm.Install(mssql_name)
    self.server_vm.RemoteCommand(
        "sudo MSSQL_SA_PASSWORD={} "
        "MSSQL_PID={} /opt/mssql/bin/mssql-conf -n "
        "setup accept-eula".format(self.spec.database_password, MSSQL_PID)
    )

    self.server_vm.RemoteCommand("sudo systemctl restart mssql-server")

    self.server_vm.RemoteCommand("sudo mkdir -p /scratch/mssqldata")
    self.server_vm.RemoteCommand("sudo mkdir -p /scratch/mssqllog")
    self.server_vm.RemoteCommand("sudo mkdir -p /scratch/mssqltemp")

    self.server_vm.RemoteCommand("sudo chown mssql /scratch/mssqldata")
    self.server_vm.RemoteCommand("sudo chown mssql /scratch/mssqllog")
    self.server_vm.RemoteCommand("sudo chown mssql /scratch/mssqltemp")

    self.server_vm.RemoteCommand("sudo chgrp mssql /scratch/mssqldata")
    self.server_vm.RemoteCommand("sudo chgrp mssql /scratch/mssqllog")
    self.server_vm.RemoteCommand("sudo chgrp mssql /scratch/mssqltemp")

    self.server_vm.RemoteCommand(
        "sudo /opt/mssql/bin/mssql-conf set "
        "filelocation.defaultdatadir "
        "/scratch/mssqldata"
    )
    self.server_vm.RemoteCommand(
        "sudo /opt/mssql/bin/mssql-conf set "
        "filelocation.defaultlogdir "
        "/scratch/mssqllog"
    )
    # Enabling FUA I/O subsystem capability per MSFT
    # https://learn.microsoft.com/en-us/sql/linux/sql-server-linux-performance-best-practices?view=sql-server-ver16
    self.server_vm.RemoteCommand(
        "sudo /opt/mssql/bin/mssql-conf set "
        "control.alternatewritethrough 0"
    )
    self.server_vm.RemoteCommand(
        "sudo /opt/mssql/bin/mssql-conf set "
        "control.writethrough 1"
    )
    self.server_vm.RemoteCommand(
        "sudo /opt/mssql/bin/mssql-conf set "
        "traceflag.traceflag 3979"
    )

    if self.server_vm.OS_TYPE == os_types.DEFAULT:
      self.server_vm.Install("mssql_tools")

    self.MoveSQLServerTempDBLinux()
    self.server_vm.RemoteCommand("sudo systemctl restart mssql-server")

    if self.server_vm.OS_TYPE == os_types.RHEL8:
      _TuneForSQL(self.server_vm)

  def MoveSQLServerTempDBLinux(self):
    vm = self.server_vm
    stdout, _ = vm.RemoteCommand(
        "/opt/mssql-tools/bin/sqlcmd -C "
        "-S localhost -U sa -P \'{}\' -h -1 -Q  \"SET NOCOUNT ON; "
        "SELECT f.name + SUBSTRING(f.physical_name, "
        "CHARINDEX('.', f.physical_name), LEN(f.physical_name) -1) "
        "FROM sys.master_files f "
        "WHERE f.database_id = DB_ID('tempdb');\""
        .format(self.spec.database_password)
        )

    tmp_db_files_list = [
        str(tmp_file.strip().replace("\r", ""))
        for tmp_file in stdout.split("\n")
        if tmp_file
    ]

    for tmp_db_file in tmp_db_files_list:
      tmp_db_name = tmp_db_file.split(".")[0]
      vm.RemoteCommand(
          "/opt/mssql-tools/bin/sqlcmd -C "
          "-S localhost -U sa -P \'{}\' -h -1 -Q "
          "\"ALTER DATABASE tempdb "
          "MODIFY FILE (NAME = [{}], "
          "FILENAME = '/scratch/mssqltemp/{}');\""
          .format(self.spec.database_password, tmp_db_name, tmp_db_file))

  @staticmethod
  def GetDefaultEngineVersion(engine):
    """Returns the default version of a given database engine.

    Args:
      engine (string): type of database (my_sql or postgres).

    Returns:
      (string): Default version for the given database engine.
    """
    return DEFAULT_ENGINE_VERSION

  def ConfigureSQLServerHaFci(self):
    """Create SQL server HA deployment for performance testing."""
    server_vm = self.server_vm
    client_vm = self.client_vm
    controller_vm = self.controller_vm
    replica_vms = self.replica_vms

    win_password = vm_util.GenerateRandomWindowsPassword(
        vm_util.PASSWORD_LENGTH, "*!@#%^+="
    )

    ip_address = self.CreateIpReservation()
    perf_domain = "perf" + FLAGS.run_uri[:6]

    # Install and configure AD components.
    self.PushAndRunPowershellScript(
        controller_vm, "setup_domain_controller.ps1",
        [win_password, perf_domain, self.spec.cloud])
    controller_vm.Reboot()
    self.PushAndRunPowershellScript(
        controller_vm, "add_user_to_domain_groups.ps1",
        [win_password, perf_domain])
    # Remove volumes and partitions created on the attached disks.
    # Disks will be initialized by S2D.
    self.PushAndRunPowershellScript(
        server_vm, "clean_disks.ps1")
    server_vm.Reboot()
    self.PushAndRunPowershellScript(
        server_vm, "set_dns_join_domain.ps1",
        [controller_vm.internal_ip, win_password, perf_domain])
    server_vm.Reboot()

    self.PushAndRunPowershellScript(
        replica_vms[0], "clean_disks.ps1")
    replica_vms[0].Reboot()

    self.PushAndRunPowershellScript(
        replica_vms[0], "set_dns_join_domain.ps1",
        [controller_vm.internal_ip, win_password, perf_domain])
    replica_vms[0].Reboot()

    self.PushAndRunPowershellScript(
        client_vm, "set_dns.ps1", [controller_vm.internal_ip])

    # Install all components needed to create and configure failover cluster.
    self.PushAndRunPowershellScript(
        controller_vm, "install_cluster_components.ps1")
    controller_vm.Reboot()
    self.PushAndRunPowershellScript(
        server_vm, "install_cluster_components.ps1")
    server_vm.Reboot()
    self.PushAndRunPowershellScript(
        replica_vms[0], "install_cluster_components.ps1")
    replica_vms[0].Reboot()

    # Setup cluster witness.
    self.PushAndRunPowershellScript(
        controller_vm, "setup_witness.ps1")
    self.PushAndRunPowershellScript(
        server_vm, "setup_fci_cluster.ps1",
        [replica_vms[0].hostname, win_password, perf_domain])

    # Ensure all nodes in the cluster have access to the witness share
    self.PushAndRunPowershellScript(
        controller_vm, "grant_witness_access.ps1")

    # Uninstall existing SQL server.
    # FCI cluster requires different installation to what comes with the image.
    self.PushAndRunPowershellScript(
        server_vm, "uninstall_sql_server.ps1")
    server_vm.Reboot()

    self.PushAndRunPowershellScript(
        replica_vms[0], "uninstall_sql_server.ps1")
    replica_vms[0].Reboot()
    server_vm.Reboot()

    if self.spec.high_availability_type == "FCIMW":
      # Configure MW cluster disks.
      self.PushAndRunPowershellScript(
          server_vm, "setup_mw_volume.ps1",
          [win_password, replica_vms[0].hostname, perf_domain])
    else:
      # Configure S2D pool and volumes.
      # All available storage (both PD and local SSD) will be used
      self.PushAndRunPowershellScript(
          server_vm, "setup_s2d_volumes.ps1",
          [win_password, replica_vms[0].hostname, perf_domain])

    # install SQL server into newly created cluster
    self.PushAndRunPowershellScript(
        server_vm, "setup_sql_server_first_node.ps1",
        [ip_address, win_password, perf_domain]
    )
    self.PushAndRunPowershellScript(
        replica_vms[0], "add_sql_server_second_node.ps1",
        [ip_address, win_password, perf_domain],
    )

    # Install SQL server updates.
    # Installation media present on the system is very outdated and it
    # does not support DNN connection for SQL.

    # Fetch the SQL server update link from MS site.
    sql_srv_vms = [server_vm, replica_vms[0]]
    sql_srv_updates_windows = {
        "2022": "KB5032679",
        "2019": "KB5031908",
        "2017": "KB5016884",
        "2016": "KB5029186",
        "2014": "KB5029185",
    }

    # for each database vm, fetch sql server update link and execute update

    for sql_srv_vm in sql_srv_vms:
      sql_server_version = self.GetSQLServerVersion(sql_srv_vm)
      if (sql_srv_updates_windows.get(sql_server_version) is not None):
        kb_number = sql_srv_updates_windows[sql_server_version]
        if (kb_number is not None and kb_number):
          self.PushAndRunPowershellScript(
              sql_srv_vm, "update_sql_server.ps1", [kb_number]
          )
          sql_srv_vm.Reboot()
    self.PushAndRunPowershellScript(
        server_vm, "check_sql_role_status_after_upgrade.ps1")

    # Update variables user for connection to SQL server.
    self.spec.database_password = win_password
    self.spec.endpoint = "fcidnn.{}.local".format(perf_domain)
    self.ReleaseIpReservation()

  def CreateIpReservation(self):
    """Create IP reservation for SQL server."""
    raise NotImplementedError("CreateIpReservation not implemented.")

  def ReleaseIpReservation(self):
    """Release IP reservation for SQL server."""
    raise NotImplementedError("ReleaseIpReservation not implemented.")

  def EnsurePrimaryReplica(self):
    """Move server_vm as primary node in AOAG."""
    # Set first server vm as primary if necessary
    out, _ = self.server_vm.RemoteCommand(
        """sqlcmd -Q \"
        SELECT
          role_desc
        FROM
          sys.dm_hadr_availability_replica_states ars
          JOIN sys.availability_groups ag ON ars.group_id = ag.group_id
        WHERE
          ag.name = '{}'
          AND is_local = 1;\"
        """.format(sql_engine_utils.SQLSERVER_AOAG_NAME))
    if "PRIMARY" not in out:
      self.server_vm.RemoteCommand(
          'sqlcmd -Q "ALTER AVAILABILITY GROUP [{}] FAILOVER"'.format(
              sql_engine_utils.SQLSERVER_AOAG_NAME))

  def ConfigureSQLServerHaAoag(self):
    """Create SQL server HA deployment for performance testing."""
    server_vm = self.server_vm
    client_vm = self.client_vm
    controller_vm = self.controller_vm
    replica_vms = self.replica_vms

    perf_domain = "perf" + FLAGS.run_uri[:6]
    win_password = vm_util.GenerateRandomWindowsPassword(
        vm_util.PASSWORD_LENGTH, "*!@#%^+=")

    # Install and configure AD components.
    self.PushAndRunPowershellScript(
        controller_vm, "setup_domain_controller.ps1",
        [win_password, perf_domain, self.spec.cloud])
    controller_vm.Reboot()
    self.PushAndRunPowershellScript(
        controller_vm, "add_user_to_domain_groups.ps1",
        [win_password, perf_domain])

    self.PushAndRunPowershellScript(
        server_vm, "set_dns_join_domain.ps1",
        [controller_vm.internal_ip, win_password, perf_domain])
    server_vm.Reboot()

    self.PushAndRunPowershellScript(
        replica_vms[0], "set_dns_join_domain.ps1",
        [controller_vm.internal_ip, win_password, perf_domain])
    replica_vms[0].Reboot()

    self.PushAndRunPowershellScript(
        client_vm, "set_dns.ps1", [controller_vm.internal_ip])

    # Install all components needed to create and configure failover cluster.
    self.PushAndRunPowershellScript(
        controller_vm, "install_cluster_components.ps1")
    controller_vm.Reboot()
    self.PushAndRunPowershellScript(
        server_vm, "install_cluster_components.ps1")
    server_vm.Reboot()
    self.PushAndRunPowershellScript(
        replica_vms[0], "install_cluster_components.ps1")
    replica_vms[0].Reboot()

    # Setup cluster witness.
    self.PushAndRunPowershellScript(
        controller_vm, "setup_witness.ps1")

    self.PushAndRunPowershellScript(
        server_vm, "setup_fci_cluster.ps1",
        [replica_vms[0].hostname, win_password, perf_domain])

    # Ensure all nodes in the cluster have access to the witness share
    self.PushAndRunPowershellScript(
        controller_vm, "grant_witness_access.ps1")

    retry_count = 0
    while retry_count < 3:
      try:
        server_vm.RemoteCommand(
            f"Enable-SqlAlwaysOn -ServerInstance {server_vm.name} -Force")
        break
      except errors.VirtualMachine.RemoteCommandError as e:
        retry_count += 1
        if retry_count >= 3:
          raise e

    retry_count = 0
    while retry_count < 3:
      try:
        replica_vms[0].RemoteCommand(
            f"Enable-SqlAlwaysOn -ServerInstance {replica_vms[0].name} -Force")
        break
      except errors.VirtualMachine.RemoteCommandError as e:
        retry_count += 1
        if retry_count >= 3:
          raise e

    # Create folder structure and dummy DB database for AOAG creation
    server_vm.RemoteCommand(r"mkdir F:\DATA; mkdir F:\Logs; mkdir F:\Backup")
    replica_vms[0].RemoteCommand(
        r"mkdir F:\DATA; mkdir F:\Logs; mkdir F:\Backup")
    self.PushAndRunPowershellScript(
        server_vm, "check_sql_server_status.ps1")
    server_vm.RemoteCommand("""sqlcmd -Q \"
        USE [master]
        GO
        ALTER LOGIN [sa] ENABLE
        GO
        CREATE DATABASE [{0}] ON PRIMARY (
          NAME = [{0}],
          FILENAME='F:\\Data\\{0}.mdf',
          SIZE = 256MB,
          MAXSIZE = UNLIMITED,
          FILEGROWTH = 256MB)
        LOG ON (
          NAME = '{0}_log',
          FILENAME='F:\\Logs\\{0}.ldf',
          SIZE = 256MB,
          MAXSIZE = UNLIMITED,
          FILEGROWTH = 256MB)
        GO
        USE [{0}]
        GO
        EXEC sp_changedbowner 'sa'
        GO
        USE [master]
        GO
        ALTER DATABASE [{0}] SET RECOVERY FULL
        GO
        BACKUP DATABASE [{0}] TO DISK = 'F:\\Backup\\{0}.bak'
        GO\"
        """.format(sql_engine_utils.SQLSERVER_AOAG_DB_NAME))

    # Change log on for MSSQLSERVICE to perflab\adminuser
    # Default PowerShell version doesn't have Set-Service -Credential option
    server_vm.RemoteCommand(
        'sc.exe config MSSQLSERVER obj= "{0}\\pkbadminuser" password='
        f' "{win_password}" type= own'.format(perf_domain)
    )
    replica_vms[0].RemoteCommand(
        'sc.exe config MSSQLSERVER obj= "{0}\\pkbadminuser" password='
        f' "{win_password}" type= own'.format(perf_domain)
    )

    # create AOAG
    # running all the AOAG query from SQL server errors with Login
    # failed to for user 'NT AUTHORITY\ANONYMOUS LOGON double
    self.PushAndRunPowershellScript(server_vm, "check_sql_server_status.ps1")
    server_vm.RemoteCommand(
        """sqlcmd -Q \"--- YOU MUST EXECUTE THE FOLLOWING SCRIPT IN SQLCMD MODE.
        USE [master]
        GO

        CREATE ENDPOINT [Hadr_endpoint]
          AS TCP (LISTENER_PORT = 5022)
            FOR DATA_MIRRORING (ROLE = ALL, ENCRYPTION = REQUIRED ALGORITHM AES)
        GO

        IF (SELECT state FROM sys.endpoints WHERE name = N'Hadr_endpoint') <> 0
        BEGIN
            ALTER ENDPOINT [Hadr_endpoint] STATE = STARTED
        END
        GO

        CREATE LOGIN [{0}\\pkbadminuser] FROM WINDOWS WITH DEFAULT_DATABASE=[master]
        GO
        ALTER SERVER ROLE [sysadmin] ADD MEMBER [{0}\\pkbadminuser]
        GO

        GRANT CONNECT ON ENDPOINT::[Hadr_endpoint] TO [{0}\\pkbadminuser]
        GO

        IF EXISTS(SELECT * FROM sys.server_event_sessions WHERE name='AlwaysOn_health')
        BEGIN
            ALTER EVENT SESSION [AlwaysOn_health] ON SERVER WITH (STARTUP_STATE=ON);
        END
        IF NOT EXISTS(SELECT * FROM sys.dm_xe_sessions WHERE name='AlwaysOn_health')
        BEGIN
            ALTER EVENT SESSION [AlwaysOn_health] ON SERVER STATE=START;
        END
        GO\"
        """.format(perf_domain)
        )
    self.PushAndRunPowershellScript(
        replica_vms[0], "check_sql_server_status.ps1")
    replica_vms[0].RemoteCommand(
        """sqlcmd -Q \"--- YOU MUST EXECUTE THE FOLLOWING SCRIPT IN SQLCMD MODE.
        USE [master]
        GO

        ALTER LOGIN [sa] ENABLE
        GO

        CREATE ENDPOINT [Hadr_endpoint]
            AS TCP (LISTENER_PORT = 5022)
        FOR DATA_MIRRORING (ROLE = ALL, ENCRYPTION = REQUIRED ALGORITHM AES)
        GO

        IF (SELECT state FROM sys.endpoints WHERE name = N'Hadr_endpoint') <> 0
        BEGIN
            ALTER ENDPOINT [Hadr_endpoint] STATE = STARTED
        END
        GO

        CREATE LOGIN [{0}\\pkbadminuser] FROM WINDOWS WITH DEFAULT_DATABASE=[master]
        GO
        ALTER SERVER ROLE [sysadmin] ADD MEMBER [{0}\\pkbadminuser]
        GO

        GRANT CONNECT ON ENDPOINT::[Hadr_endpoint] TO [{0}\\pkbadminuser]
        GO

        IF EXISTS(SELECT * FROM sys.server_event_sessions WHERE name='AlwaysOn_health')
        BEGIN
            ALTER EVENT SESSION [AlwaysOn_health] ON SERVER WITH (STARTUP_STATE=ON);
        END
        IF NOT EXISTS(SELECT * FROM sys.dm_xe_sessions WHERE name='AlwaysOn_health')
        BEGIN
            ALTER EVENT SESSION [AlwaysOn_health] ON SERVER STATE=START;
        END
        GO\"
        """.format(perf_domain)
        )
    self.PushAndRunPowershellScript(server_vm, "check_sql_server_status.ps1")
    server_vm.RemoteCommand(
        """sqlcmd -Q \"--- YOU MUST EXECUTE THE FOLLOWING SCRIPT IN SQLCMD MODE.
        USE [master]
        GO

        CREATE AVAILABILITY GROUP [{3}]
        WITH (AUTOMATED_BACKUP_PREFERENCE = SECONDARY,
        DB_FAILOVER = OFF,
        DTC_SUPPORT = NONE,
        REQUIRED_SYNCHRONIZED_SECONDARIES_TO_COMMIT = 0)
        FOR DATABASE [{2}]

        REPLICA ON N'{0}' WITH (ENDPOINT_URL = N'TCP://{0}.{4}.local:5022', FAILOVER_MODE = AUTOMATIC, AVAILABILITY_MODE = SYNCHRONOUS_COMMIT, BACKUP_PRIORITY = 50, SEEDING_MODE = AUTOMATIC, SECONDARY_ROLE(ALLOW_CONNECTIONS = NO)),
            N'{1}' WITH (ENDPOINT_URL = N'TCP://{1}.{4}.local:5022', FAILOVER_MODE = AUTOMATIC, AVAILABILITY_MODE = SYNCHRONOUS_COMMIT, BACKUP_PRIORITY = 50, SEEDING_MODE = AUTOMATIC, SECONDARY_ROLE(ALLOW_CONNECTIONS = NO));
        GO\"
        """.format(self.server_vm.name,
                   self.replica_vms[0].name,
                   sql_engine_utils.SQLSERVER_AOAG_DB_NAME,
                   sql_engine_utils.SQLSERVER_AOAG_NAME,
                   perf_domain))
    self.PushAndRunPowershellScript(
        replica_vms[0], "check_sql_server_status.ps1")
    replica_vms[0].RemoteCommand(
        """sqlcmd -Q \"--- YOU MUST EXECUTE THE FOLLOWING SCRIPT IN SQLCMD MODE.
        ALTER AVAILABILITY GROUP [{0}] JOIN;
        GO

        ALTER AVAILABILITY GROUP [{0}] GRANT CREATE ANY DATABASE;
        GO\"
        """.format(sql_engine_utils.SQLSERVER_AOAG_NAME))

    # Restart SQL Service for AOAG replication to begin
    server_vm.RemoteCommand("Restart-Service MSSQLSERVER -Force")
    replica_vms[0].RemoteCommand("Restart-Service MSSQLSERVER -Force")

    self.PushAndRunPowershellScript(server_vm, "check_sql_server_status.ps1")
    # Add DNN listener
    self.PushAndRunPowershellScript(
        server_vm,
        "add_dnn_listener.ps1",
        [sql_engine_utils.SQLSERVER_AOAG_NAME, "fcidnn", "1533"])

    self.PushAndRunPowershellScript(server_vm, "check_sql_server_status.ps1")
    server_vm.RemoteCommand(
        """sqlcmd -Q \"
        ALTER AVAILABILITY GROUP [{1}] REMOVE DATABASE [{0}];
        DROP DATABASE [{0}];\"
        """.format(sql_engine_utils.SQLSERVER_AOAG_DB_NAME,
                   sql_engine_utils.SQLSERVER_AOAG_NAME))

    # Update variables user for connection to SQL server.
    self.spec.database_password = win_password
    self.spec.endpoint = "fcidnn.{}.local".format(perf_domain)
    self.port = 1533

  def PushAndRunPowershellScript(
      self,
      vm: virtual_machine.VirtualMachine,
      script_name: str,
      cmd_parameters: list[str] | None = None,
      source_path: str = "relational_db_configs/sqlserver_ha_configs/",
  ) -> tuple[str, str]:
    """Pushes a powershell script to VM and run it.

    Args:
      vm: vm where script will be uploaded and executed
      script_name: name of the script to upload and execute
      cmd_parameters: optional command parameters
      source_path: script source location

    Returns:
      command execution output.
    """
    if cmd_parameters is None:
      cmd_parameters = []
    script_path_on_vm = ntpath.join(vm.temp_dir, script_name)  # pytype: disable=attribute-error
    script_path = data.ResourcePath(os.path.join(source_path, script_name))
    vm.PushFile(script_path, script_path_on_vm)

    return vm.RemoteCommand("powershell {} {}"
                            .format(script_path_on_vm,
                                    " ".join(cmd_parameters)))

  def RestartDatabase(self):
    """Restarts all the database services in the benchmark."""
    vms = [self.server_vm]
    if self.spec.high_availability and self.replica_vms:
      vms.append(self.replica_vms)
    for vm in vms:
      if vm.BASE_OS_TYPE == os_types.WINDOWS:
        vm.RemoteCommand("Restart-Service MSSQLSERVER -Force")
      else:
        vm.RemoteCommand("sudo systemctl restart mssql-server")

  def GetSQLServerVersion(
      self,
      vm: virtual_machine.VirtualMachine
  ) -> str:
    """Fetch SQL Server Version of the vm.

    Args:
      vm: vm

    Returns:
      SQL Server Version
    """

    return_value: str = None
    sql_srv_version_prefix = "sqlserver_"
    sql_srv_supported_versions = [
        "2022",
        "2019",
        "2017",
        "2016",
        "2014",
        "2012"
    ]

    for version in sql_srv_supported_versions:
      if vm.OS_TYPE.find(sql_srv_version_prefix+version) > 0:
        return_value = version
        break
    return return_value


def ConfigureSQLServer(vm, username: str, password: str):
  """Update the username and password on a SQL Server."""
  vm.RemoteCommand(f'sqlcmd -Q "ALTER LOGIN {username} ENABLE;"')
  vm.RemoteCommand(
      f"sqlcmd -Q \"ALTER LOGIN sa WITH PASSWORD = '{password}' ;\""
  )
  vm.RemoteCommand(
      "sqlcmd -Q \"EXEC xp_instance_regwrite N'HKEY_LOCAL_MACHINE', "
      "N'Software\\Microsoft\\MSSQLServer\\MSSQLServer', "
      "N'LoginMode', REG_DWORD, 2\""
  )
  vm.RemoteCommand(
      "sqlcmd -Q \"EXEC xp_instance_regwrite N'HKEY_LOCAL_MACHINE', "
      "N'Software\\Microsoft\\MSSQLServer\\MSSQLServer', "
      "N'BackupDirectory', REG_SZ, N'C:\\scratch'\""
  )

  vm.RemoteCommand(
      "sqlcmd -Q \"EXEC xp_instance_regwrite N'HKEY_LOCAL_MACHINE', "
      "N'Software\\Microsoft\\MSSQLServer\\MSSQLServer', "
      f"N'DefaultData', REG_SZ, N'{vm.assigned_disk_letter}:\\'\""
  )
  vm.RemoteCommand(
      "sqlcmd -Q \"EXEC xp_instance_regwrite N'HKEY_LOCAL_MACHINE', "
      "N'Software\\Microsoft\\MSSQLServer\\MSSQLServer', "
      f"N'DefaultLog', REG_SZ, N'{vm.assigned_disk_letter}:\\'\""
  )
  vm.RemoteCommand("net stop SQLSERVERAGENT /y", ignore_failure=True)
  vm.RemoteCommand("net stop mssqlserver /y")
  vm.RemoteCommand("net start mssqlserver")
  vm.RemoteCommand("net start SQLSERVERAGENT", ignore_failure=True)


def _TuneForSQL(vm):
  """Set TuneD settings specific to SQL Server on RedHat."""
  tune_settings = (
      "# A TuneD configuration for SQL Server on Linux \n"
      "[main] \n"
      "summary=Optimize for Microsoft SQL Server \n"
      "include=throughput-performance \n\n"
      "[cpu] \n"
      "force_latency=5\n\n"
      "[vm] \n"
      "# For multi-instance SQL deployments use 'madvise' instead of 'always'\n"
      "transparent_hugepages=always \n\n"
      "[sysctl]\n"
      "vm.swappiness = 1\n"
      "vm.dirty_background_ratio = 3\n"
      "vm.dirty_ratio = 80\n"
      "vm.dirty_expire_centisecs = 500\n"
      "vm.dirty_writeback_centisecs = 100\n"
      "vm.max_map_count=1600000\n"
      "net.core.rmem_default = 262144\n"
      "net.core.rmem_max = 4194304\n"
      "net.core.wmem_default = 262144\n"
      "net.core.wmem_max = 1048576\n"
      "kernel.numa_balancing=0\n\n"
      "[scheduler]\n"
      "sched_latency_ns=60000000\n"
      "sched_migration_cost_ns=500000\n"
      "sched_min_granularity_ns=15000000\n"
      "sched_wakeup_granularity_ns=2000000\n"
      "\n"
  )
  vm.RemoteCommand("sudo mkdir -p /usr/lib/tuned/mssql")
  vm.RemoteCommand(
      'echo "{}" | sudo tee /usr/lib/tuned/mssql/tuned.conf'.format(
          tune_settings
      )
  )

  vm.RemoteCommand("sudo chmod +x /usr/lib/tuned/mssql/tuned.conf")
  vm.RemoteCommand("sudo tuned-adm profile mssql")
  vm.RemoteCommand("sudo tuned-adm active")
  vm.Reboot()


def ConfigureSQLServerLinux(vm, username: str, password: str):
  """Update the username and password on a SQL Server."""
  vm.RemoteCommand(
      f'/opt/mssql-tools/bin/sqlcmd -C -Q "ALTER LOGIN {username} ENABLE;"'
  )
  vm.RemoteCommand(
      "/opt/mssql-tools/bin/sqlcmd -C -Q "
      f"\"ALTER LOGIN sa WITH PASSWORD = '{password}';\""
  )
  vm.RemoteCommand("sudo systemctl restart mssql-server")
