From 48c5c781ab5bf0bb48a4dd1a31f02983f32e8240 Mon Sep 17 00:00:00 2001 From: "Wickham.Andrew.J" Date: Thu, 1 Oct 2020 20:44:18 -0400 Subject: [PATCH 1/6] Initial add of SnapshotIsolation support on SqlDatabase resource --- .../DSC_SqlDatabase/DSC_SqlDatabase.psm1 | 44 ++++++++++++++++++- .../DSC_SqlDatabase.schema.mof | 1 + .../en-US/DSC_SqlDatabase.strings.psd1 | 3 ++ .../DSC_SqlDatabase.Integration.Tests.ps1 | 3 ++ tests/Integration/DSC_SqlDatabase.config.ps1 | 2 + 5 files changed, 51 insertions(+), 2 deletions(-) diff --git a/source/DSCResources/DSC_SqlDatabase/DSC_SqlDatabase.psm1 b/source/DSCResources/DSC_SqlDatabase/DSC_SqlDatabase.psm1 index 2a15bdced..61c4ae2a5 100644 --- a/source/DSCResources/DSC_SqlDatabase/DSC_SqlDatabase.psm1 +++ b/source/DSCResources/DSC_SqlDatabase/DSC_SqlDatabase.psm1 @@ -74,6 +74,7 @@ function Get-TargetResource CompatibilityLevel = $null RecoveryModel = $null OwnerName = $null + SnapshotIsolation = $null } $sqlServerObject = Connect-SQL -ServerName $ServerName -InstanceName $InstanceName @@ -90,6 +91,7 @@ function Get-TargetResource $returnValue['CompatibilityLevel'] = $sqlDatabaseObject.CompatibilityLevel $returnValue['RecoveryModel'] = $sqlDatabaseObject.RecoveryModel $returnValue['OwnerName'] = $sqlDatabaseObject.Owner + $returnValue['SnapshotIsolation'] = $sqlDatabaseObject.SnapshotIsolationState -eq 'Enabled' Write-Verbose -Message ( $script:localizedData.DatabasePresent -f $Name @@ -137,6 +139,9 @@ function Get-TargetResource .PARAMETER OwnerName Specifies the name of the login that should be the owner of the database. + + .PARAMETER SnapshotIsolation + Specifics whether snapshot isolation should be enabled for the new database. #> function Set-TargetResource { @@ -181,7 +186,11 @@ function Set-TargetResource [Parameter()] [System.String] - $OwnerName + $OwnerName, + + [Parameter()] + [System.Boolean] + $SnapshotIsolation ) $sqlServerObject = Connect-SQL -ServerName $ServerName -InstanceName $InstanceName @@ -275,6 +284,24 @@ function Set-TargetResource $wasUpdate = $true } + if ($PSBoundParameters.ContainsKey('SnapshotIsolation')) + { + Write-Verbose -Message ( + $script:localizedData.UpdatingSnapshotIsolation -f $SnapshotIsolation + ) + + try + { + $sqlDatabaseObject.SetSnapshotIsolation($SnapshotIsolation) + } + catch + { + $errorMessage = $script:localizedData.FailedToUpdateSnapshotIsolation -f $SnapshotIsolation, $Name + + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + } + } + try { if ($wasUpdate) @@ -438,7 +465,11 @@ function Test-TargetResource [Parameter()] [System.String] - $OwnerName + $OwnerName, + + [Parameter()] + [System.Boolean] + $SnapshotIsolation ) Write-Verbose -Message ( @@ -516,6 +547,15 @@ function Test-TargetResource $isDatabaseInDesiredState = $false } + + if ($PSBoundParameters.ContainsKey('SnapshotIsolation') -and ($getTargetResourceResult.SnapshotIsolationState -eq 'Enabled') -ne $SnapshotIsolation) + { + Write-Verbose -Message ( + $script:localizedData.SnapshotIsolationWrong -f $Name, ($getTargetResourceResult.SnapshotIsolationState -eq 'Enabled'), $SnapshotIsolation + ) + + $isDatabaseInDesiredState = $false + } } } } diff --git a/source/DSCResources/DSC_SqlDatabase/DSC_SqlDatabase.schema.mof b/source/DSCResources/DSC_SqlDatabase/DSC_SqlDatabase.schema.mof index 787d01618..e9f49deed 100644 --- a/source/DSCResources/DSC_SqlDatabase/DSC_SqlDatabase.schema.mof +++ b/source/DSCResources/DSC_SqlDatabase/DSC_SqlDatabase.schema.mof @@ -9,4 +9,5 @@ class DSC_SqlDatabase : OMI_BaseResource [Write, Description("Specifies the version of the _SQL Database Compatibility Level_ to use for the specified database."), ValueMap{"Version80","Version90","Version100","Version110","Version120","Version130","Version140","Version150"}, Values{"Version80","Version90","Version100","Version110","Version120","Version130","Version140","Version150"}] String CompatibilityLevel; [Write, Description("The recovery model for the specified database."), ValueMap{"Simple","Full","BulkLogged"}, Values{"Simple","Full","BulkLogged"}] String RecoveryModel; [Write, Description("Specifies the name of the login that should be the owner of the database.")] String OwnerName; + [Write, Description("Enables snapshot isolation for the specified database.")] Boolean SnapshotIsolation; }; diff --git a/source/DSCResources/DSC_SqlDatabase/en-US/DSC_SqlDatabase.strings.psd1 b/source/DSCResources/DSC_SqlDatabase/en-US/DSC_SqlDatabase.strings.psd1 index 0ab2d79f8..89b93a775 100644 --- a/source/DSCResources/DSC_SqlDatabase/en-US/DSC_SqlDatabase.strings.psd1 +++ b/source/DSCResources/DSC_SqlDatabase/en-US/DSC_SqlDatabase.strings.psd1 @@ -22,4 +22,7 @@ ConvertFrom-StringData @' OwnerNameWrong = The database '{0}' exist and has the owner '{1}', but expected it to have the owner '{2}'. UpdatingOwner = Changing the database owner to '{0}'. FailedToUpdateOwner = Failed changing to owner to '{0}' for the database '{1}'. + UpdatingSnapshotIsolation = Updating snapshot isolation to '{0}'. + FailedToUpdateSnapshotIsolation = Failed changing snapshot isolation to '{0}' for the database '{1}'. + SnapshotIsolationWrong = The database '{0}' exists and has snapshot isolation set to '{1}', but expected it to have snapshot isolation set to '{2}'. '@ diff --git a/tests/Integration/DSC_SqlDatabase.Integration.Tests.ps1 b/tests/Integration/DSC_SqlDatabase.Integration.Tests.ps1 index a606a5f91..8f1793cde 100644 --- a/tests/Integration/DSC_SqlDatabase.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlDatabase.Integration.Tests.ps1 @@ -80,6 +80,7 @@ try $resourceCurrentState.Collation | Should -Be 'Finnish_Swedish_CI_AS' $resourceCurrentState.RecoveryModel | Should -Be 'Full' $resourceCurrentState.OwnerName | Should -Be ('{0}\SqlAdmin' -f $env:COMPUTERNAME) + $resourceCurrentState.SnapshotIsolation | Should -Be $true } It 'Should return $true when Test-DscConfiguration is run' { @@ -280,6 +281,7 @@ try $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName $resourceCurrentState.OwnerName | Should -Be $ConfigurationData.AllNodes.OwnerName + $resourceCurrentState.SnapshotIsolation | Should -Be $ConfigurationData.AllNodes.SnapshotIsolation } It 'Should return $true when Test-DscConfiguration is run' { @@ -333,6 +335,7 @@ try $resourceCurrentState.OwnerName | Should -BeNullOrEmpty $resourceCurrentState.RecoveryModel | Should -BeNullOrEmpty $resourceCurrentState.CompatibilityLevel | Should -BeNullOrEmpty + $resourceCurrentState.SnapshotIsolation | Should -BeNullOrEmpty } It 'Should return $true when Test-DscConfiguration is run' { diff --git a/tests/Integration/DSC_SqlDatabase.config.ps1 b/tests/Integration/DSC_SqlDatabase.config.ps1 index 77d455b47..05353cb26 100644 --- a/tests/Integration/DSC_SqlDatabase.config.ps1 +++ b/tests/Integration/DSC_SqlDatabase.config.ps1 @@ -35,6 +35,7 @@ else CompatibilityLevel = 'Version120' RecoveryModel = 'Simple' OwnerName = 'sa' + SnapshotIsolation = $true } ) } @@ -156,6 +157,7 @@ Configuration DSC_SqlDatabase_AddDatabase5_Config InstanceName = $Node.InstanceName Name = $Node.DatabaseName5 OwnerName = $Node.OwnerName + SnapshotIsolation = $Node.SnapshotIsolation PsDscRunAsCredential = New-Object ` -TypeName System.Management.Automation.PSCredential ` From d87879fd1d6932cc956adcef6e8601abda23f4cb Mon Sep 17 00:00:00 2001 From: "Wickham.Andrew.J" Date: Thu, 1 Oct 2020 21:11:07 -0400 Subject: [PATCH 2/6] Calling wrong property --- source/DSCResources/DSC_SqlDatabase/DSC_SqlDatabase.psm1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/DSCResources/DSC_SqlDatabase/DSC_SqlDatabase.psm1 b/source/DSCResources/DSC_SqlDatabase/DSC_SqlDatabase.psm1 index 61c4ae2a5..a4f3f43a2 100644 --- a/source/DSCResources/DSC_SqlDatabase/DSC_SqlDatabase.psm1 +++ b/source/DSCResources/DSC_SqlDatabase/DSC_SqlDatabase.psm1 @@ -548,10 +548,10 @@ function Test-TargetResource $isDatabaseInDesiredState = $false } - if ($PSBoundParameters.ContainsKey('SnapshotIsolation') -and ($getTargetResourceResult.SnapshotIsolationState -eq 'Enabled') -ne $SnapshotIsolation) + if ($PSBoundParameters.ContainsKey('SnapshotIsolation') -and $getTargetResourceResult.SnapshotIsolationState -ne $SnapshotIsolation) { Write-Verbose -Message ( - $script:localizedData.SnapshotIsolationWrong -f $Name, ($getTargetResourceResult.SnapshotIsolationState -eq 'Enabled'), $SnapshotIsolation + $script:localizedData.SnapshotIsolationWrong -f $Name, $getTargetResourceResult.SnapshotIsolationState, $SnapshotIsolation ) $isDatabaseInDesiredState = $false From eacaf334db593465800b1e94a4968330b5f4938f Mon Sep 17 00:00:00 2001 From: "Wickham.Andrew.J" Date: Fri, 2 Oct 2020 15:12:25 -0400 Subject: [PATCH 3/6] Redoing tests to have dedicated SnapshotIsolation database (Database 6) --- .../DSC_SqlDatabase.Integration.Tests.ps1 | 50 ++++++++++++++++++- tests/Integration/DSC_SqlDatabase.config.ps1 | 25 ++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/tests/Integration/DSC_SqlDatabase.Integration.Tests.ps1 b/tests/Integration/DSC_SqlDatabase.Integration.Tests.ps1 index 8f1793cde..1dc79309e 100644 --- a/tests/Integration/DSC_SqlDatabase.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlDatabase.Integration.Tests.ps1 @@ -80,7 +80,6 @@ try $resourceCurrentState.Collation | Should -Be 'Finnish_Swedish_CI_AS' $resourceCurrentState.RecoveryModel | Should -Be 'Full' $resourceCurrentState.OwnerName | Should -Be ('{0}\SqlAdmin' -f $env:COMPUTERNAME) - $resourceCurrentState.SnapshotIsolation | Should -Be $true } It 'Should return $true when Test-DscConfiguration is run' { @@ -281,6 +280,55 @@ try $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName $resourceCurrentState.OwnerName | Should -Be $ConfigurationData.AllNodes.OwnerName + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + $configurationName = "$($script:dscResourceName)_AddDatabase6_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.Ensure | Should -Be 'Present' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.DatabaseName5 + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName $resourceCurrentState.SnapshotIsolation | Should -Be $ConfigurationData.AllNodes.SnapshotIsolation } diff --git a/tests/Integration/DSC_SqlDatabase.config.ps1 b/tests/Integration/DSC_SqlDatabase.config.ps1 index 05353cb26..55b681d7a 100644 --- a/tests/Integration/DSC_SqlDatabase.config.ps1 +++ b/tests/Integration/DSC_SqlDatabase.config.ps1 @@ -30,6 +30,7 @@ else DatabaseName3 = 'Database3' DatabaseName4 = 'Database4' DatabaseName5 = 'Database5' + DatabaseName6 = 'Database6' Collation = 'SQL_Latin1_General_Pref_CP850_CI_AS' CompatibilityLevel = 'Version120' @@ -157,6 +158,30 @@ Configuration DSC_SqlDatabase_AddDatabase5_Config InstanceName = $Node.InstanceName Name = $Node.DatabaseName5 OwnerName = $Node.OwnerName + + PsDscRunAsCredential = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @($Node.Username, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) + } + } +} + +<# + .SYNOPSIS + Creates a database with a specific snapshot isolation. +#> +Configuration DSC_SqlDatabase_AddDatabase6_Config +{ + Import-DscResource -ModuleName 'SqlServerDsc' + + node $AllNodes.NodeName + { + SqlDatabase 'Integration_Test' + { + Ensure = 'Present' + ServerName = $Node.ServerName + InstanceName = $Node.InstanceName + Name = $Node.DatabaseName6 SnapshotIsolation = $Node.SnapshotIsolation PsDscRunAsCredential = New-Object ` From 7c27a5081a9419b735b1dcd81666ef0183c5007b Mon Sep 17 00:00:00 2001 From: "Wickham.Andrew.J" Date: Fri, 2 Oct 2020 20:01:41 -0400 Subject: [PATCH 4/6] Modify changelog to include snapshot isolation --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 011522ecb..66c99d256 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - SqlReplication - The resource are now using the helper function `Get-SqlInstanceMajorVersion` ([issue #1408](https://github.com/dsccommunity/SqlServerDsc/issues/1408)). +- SqlDatabase + - Support enabling or disabling snapshot isolation + ([issue #845](https://github.com/dsccommunity/SqlServerDsc/issues/845)). ## [14.2.1] - 2020-08-14 From fc77b47a864aa842bd4e97d2c883221a31a7fe5e Mon Sep 17 00:00:00 2001 From: "Wickham.Andrew.J" Date: Sat, 3 Oct 2020 11:40:29 -0400 Subject: [PATCH 5/6] Fix Database6 config to expect database6 name --- tests/Integration/DSC_SqlDatabase.Integration.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Integration/DSC_SqlDatabase.Integration.Tests.ps1 b/tests/Integration/DSC_SqlDatabase.Integration.Tests.ps1 index 1dc79309e..2f1415307 100644 --- a/tests/Integration/DSC_SqlDatabase.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlDatabase.Integration.Tests.ps1 @@ -326,7 +326,7 @@ try } $resourceCurrentState.Ensure | Should -Be 'Present' - $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.DatabaseName5 + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.DatabaseName6 $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName $resourceCurrentState.SnapshotIsolation | Should -Be $ConfigurationData.AllNodes.SnapshotIsolation From d8f1e285fa85f62955369f64ccb6ef30bf8cb69e Mon Sep 17 00:00:00 2001 From: "Wickham.Andrew.J" Date: Sat, 3 Oct 2020 15:09:27 -0400 Subject: [PATCH 6/6] Was looking at wrong property on $getTargetResourceResult --- source/DSCResources/DSC_SqlDatabase/DSC_SqlDatabase.psm1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/DSCResources/DSC_SqlDatabase/DSC_SqlDatabase.psm1 b/source/DSCResources/DSC_SqlDatabase/DSC_SqlDatabase.psm1 index a4f3f43a2..1f1cf804b 100644 --- a/source/DSCResources/DSC_SqlDatabase/DSC_SqlDatabase.psm1 +++ b/source/DSCResources/DSC_SqlDatabase/DSC_SqlDatabase.psm1 @@ -548,10 +548,10 @@ function Test-TargetResource $isDatabaseInDesiredState = $false } - if ($PSBoundParameters.ContainsKey('SnapshotIsolation') -and $getTargetResourceResult.SnapshotIsolationState -ne $SnapshotIsolation) + if ($PSBoundParameters.ContainsKey('SnapshotIsolation') -and $getTargetResourceResult.SnapshotIsolation -ne $SnapshotIsolation) { Write-Verbose -Message ( - $script:localizedData.SnapshotIsolationWrong -f $Name, $getTargetResourceResult.SnapshotIsolationState, $SnapshotIsolation + $script:localizedData.SnapshotIsolationWrong -f $Name, $getTargetResourceResult.SnapshotIsolation, $SnapshotIsolation ) $isDatabaseInDesiredState = $false