Skip to content

Commit

Permalink
chore(azure): Adding a verifyAccountHealth configuration (spinnaker#6296
Browse files Browse the repository at this point in the history
)

* chore(azure): Adding a verifyAccountHealth configuration

* test(azure): add single test for AzureHealthIndicator

---------

Co-authored-by: Edgar Garcia <[email protected]>
  • Loading branch information
christosarvanitis and edgarulg authored Oct 29, 2024
1 parent aab2a60 commit 0be9ea8
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ class AzureResourceManagerClient extends AzureBaseClient {
} catch (Exception e) {
// Something went wrong. log the exception
log.error("Unable to register Azure Provider: ${namespace}", e)
throw e
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ package com.netflix.spinnaker.clouddriver.azure.config
import com.netflix.spinnaker.clouddriver.azure.resources.vmimage.model.AzureCustomImageStorage
import com.netflix.spinnaker.clouddriver.azure.resources.vmimage.model.AzureVMImage
import com.netflix.spinnaker.fiat.model.resources.Permissions
import groovy.transform.Canonical
import groovy.transform.ToString
import org.springframework.boot.context.properties.NestedConfigurationProperty

class AzureConfigurationProperties {

Expand All @@ -42,4 +44,16 @@ class AzureConfigurationProperties {
}

List<ManagedAccount> accounts = []
/**
* health check related config settings
*/
@Canonical
static class HealthConfig {
/**
* flag to toggle verifying account health check. by default, account health check is enabled.
*/
boolean verifyAccountHealth = true
}
@NestedConfigurationProperty
final HealthConfig health = new HealthConfig()
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.netflix.spinnaker.clouddriver.azure.health

import com.netflix.spinnaker.clouddriver.azure.config.AzureConfigurationProperties
import com.netflix.spinnaker.clouddriver.azure.security.AzureNamedAccountCredentials
import com.netflix.spinnaker.clouddriver.security.AccountCredentialsProvider
import groovy.transform.CompileStatic
Expand All @@ -41,6 +42,9 @@ class AzureHealthIndicator implements HealthIndicator {
@Autowired
AccountCredentialsProvider accountCredentialsProvider

@Autowired
AzureConfigurationProperties azureConfigurationProperties

private final AtomicReference<Exception> lastException = new AtomicReference<>(null)

@Override
Expand All @@ -57,6 +61,8 @@ class AzureHealthIndicator implements HealthIndicator {
@Scheduled(fixedDelay = 300000L)
void checkHealth() {
try {
if (azureConfigurationProperties.getHealth().getVerifyAccountHealth()) {
LOG.info("azure.health.verifyAccountHealth flag is enabled - verifying connection to the Azure accounts")
Set<AzureNamedAccountCredentials> azureCredentialsSet = accountCredentialsProvider.all.findAll {
it instanceof AzureNamedAccountCredentials
} as Set<AzureNamedAccountCredentials>
Expand All @@ -73,7 +79,9 @@ class AzureHealthIndicator implements HealthIndicator {
throw new AzureIOException(e)
}
}

} else {
LOG.info("azure.health.verifyAccountHealth flag is disabled - Not verifying connection to the Azure accounts");
}
lastException.set(null)
} catch (Exception ex) {
LOG.warn "Unhealthy", ex
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ import com.netflix.spinnaker.clouddriver.azure.client.AzureComputeClient
import com.netflix.spinnaker.clouddriver.azure.client.AzureNetworkClient
import com.netflix.spinnaker.clouddriver.azure.client.AzureResourceManagerClient
import com.netflix.spinnaker.clouddriver.azure.client.AzureStorageClient
import groovy.util.logging.Slf4j

@Slf4j
class AzureCredentials {

final String tenantId
Expand Down Expand Up @@ -63,7 +65,12 @@ class AzureCredentials {

storageClient = new AzureStorageClient(this.subscriptionId, token, azureProfile)

registerProviders()
try {
registerProviders()
} catch (Exception e) {
log.error("Failed to register providers with AzureResourceManagerClient", e)
throw e
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* Copyright 2024 Harness, Inc.
*
* 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.
*
*/

package com.netflix.spinnaker.clouddriver.azure.health

import com.netflix.spinnaker.clouddriver.azure.config.AzureConfigurationProperties
import com.netflix.spinnaker.clouddriver.azure.client.AzureResourceManagerClient
import com.netflix.spinnaker.clouddriver.azure.security.AzureCredentials
import com.netflix.spinnaker.clouddriver.azure.security.AzureNamedAccountCredentials
import com.netflix.spinnaker.clouddriver.security.AccountCredentialsProvider
import org.springframework.boot.actuate.health.Status
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll

class AzureHealthIndicatorSpec extends Specification {

@Shared
AccountCredentialsProvider accountCredentialsProvider

private void setupMocks(def mockResourceManager, def mockCredentials, def mockAccountCredentials) {
def credentialsField = AzureNamedAccountCredentials.getDeclaredField("credentials")
credentialsField.accessible = true
credentialsField.set(mockAccountCredentials, mockCredentials)

def resourceManagerField = AzureCredentials.getDeclaredField("resourceManagerClient")
resourceManagerField.accessible = true
resourceManagerField.set(mockCredentials, mockResourceManager)

accountCredentialsProvider = Mock(AccountCredentialsProvider)
accountCredentialsProvider.all >> [mockAccountCredentials]
}

@Unroll
def "health succeeds when azure is reachable"() {
setup:
def mockResourceManager = Mock(AzureResourceManagerClient)
def mockCredentials = Mock(AzureCredentials)
def mockAccountCredentials = Mock(AzureNamedAccountCredentials)
setupMocks(mockResourceManager, mockCredentials, mockAccountCredentials)

def indicator = new AzureHealthIndicator(azureConfigurationProperties: new AzureConfigurationProperties())
indicator.accountCredentialsProvider = accountCredentialsProvider

when:
indicator.checkHealth()
def health = indicator.health()

then:
health.status == Status.UP
health.details.isEmpty()
}

@Unroll
def "health fails when azure is unreachable - verifyAccountHealth:true"() {
setup:
def mockResourceManager = Mock(AzureResourceManagerClient)
def mockCredentials = Mock(AzureCredentials)
def mockAccountCredentials = Mock(AzureNamedAccountCredentials)
setupMocks(mockResourceManager, mockCredentials, mockAccountCredentials)

def indicator = new AzureHealthIndicator(azureConfigurationProperties: new AzureConfigurationProperties())
indicator.accountCredentialsProvider = accountCredentialsProvider

when:
mockResourceManager.healthCheck() >> { throw new IOException("Azure is unreachable") }
indicator.checkHealth()
indicator.health()

then:
thrown(AzureHealthIndicator.AzureIOException)
}

@Unroll
def "health fails when no azure credentials are found - verifyAccountHealth:true"() {
setup:
accountCredentialsProvider = Mock(AccountCredentialsProvider)
accountCredentialsProvider.all >> []

def indicator = new AzureHealthIndicator(azureConfigurationProperties: new AzureConfigurationProperties())
indicator.accountCredentialsProvider = accountCredentialsProvider

when:
indicator.checkHealth()
indicator.health()

then:
thrown(AzureHealthIndicator.AzureCredentialsNotFoundException)
}

@Unroll
def "health succeeds when verifyAccountHealth flag is disabled"() {
setup:
def azureConfigProps = new AzureConfigurationProperties()
azureConfigProps.health.verifyAccountHealth = false

def indicator = new AzureHealthIndicator(azureConfigurationProperties: azureConfigProps)
indicator.accountCredentialsProvider = accountCredentialsProvider

when:
indicator.checkHealth()
def health = indicator.health()

then:
health.status == Status.UP
health.details.isEmpty()
}

@Unroll
def "health succeeds when azure is unreachable - verifyAccountHealth:false"() {
setup:
def mockResourceManager = Mock(AzureResourceManagerClient)
def mockCredentials = Mock(AzureCredentials)
def mockAccountCredentials = Mock(AzureNamedAccountCredentials)
setupMocks(mockResourceManager, mockCredentials, mockAccountCredentials)

def azureConfigProps = new AzureConfigurationProperties()
azureConfigProps.health.verifyAccountHealth = false
def indicator = new AzureHealthIndicator(azureConfigurationProperties: azureConfigProps)
indicator.accountCredentialsProvider = accountCredentialsProvider

when:
mockResourceManager.healthCheck() >> { throw new IOException("Azure is unreachable") }
indicator.checkHealth()
def health = indicator.health()

then:
health.status == Status.UP
health.details.isEmpty()
}
}

0 comments on commit 0be9ea8

Please sign in to comment.