2022-09-05 03:06:08 -04:00
/ *
Copyright ( c ) Edgeless Systems GmbH
SPDX - License - Identifier : AGPL - 3.0 - only
* /
2022-03-22 11:03:15 -04:00
package config
import (
2022-08-02 06:24:55 -04:00
"reflect"
2022-03-22 11:03:15 -04:00
"testing"
2022-09-21 07:47:57 -04:00
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config/instancetypes"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
2022-09-07 05:53:44 -04:00
"github.com/go-playground/locales/en"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
2022-03-22 11:03:15 -04:00
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
2022-06-30 09:24:36 -04:00
"go.uber.org/goleak"
2022-03-22 11:03:15 -04:00
)
2022-06-30 09:24:36 -04:00
func TestMain ( m * testing . M ) {
2022-10-28 11:57:24 -04:00
goleak . VerifyTestMain ( m ,
// https://github.com/google/go-sev-guest/issues/23
goleak . IgnoreTopFunction ( "github.com/golang/glog.(*loggingT).flushDaemon" ) ,
)
2022-06-30 09:24:36 -04:00
}
2022-03-22 11:03:15 -04:00
func TestDefaultConfig ( t * testing . T ) {
assert := assert . New ( t )
def := Default ( )
assert . NotNil ( def )
}
func TestFromFile ( t * testing . T ) {
testCases := map [ string ] struct {
2022-05-16 12:54:25 -04:00
config * Config
configName string
wantResult * Config
wantErr bool
2022-03-22 11:03:15 -04:00
} {
2022-05-16 12:54:25 -04:00
"default config from default file" : {
config : Default ( ) ,
configName : constants . ConfigFilename ,
wantResult : Default ( ) ,
} ,
"default config from different path" : {
config : Default ( ) ,
configName : "other-config.yaml" ,
wantResult : Default ( ) ,
2022-03-22 11:03:15 -04:00
} ,
2022-05-16 12:54:25 -04:00
"default config when path empty" : {
config : nil ,
configName : "" ,
2022-08-08 05:04:17 -04:00
wantErr : true ,
2022-03-22 11:03:15 -04:00
} ,
2022-05-16 12:54:25 -04:00
"err when path not exist" : {
config : nil ,
configName : "wrong-name.yaml" ,
wantErr : true ,
} ,
"custom config from default file" : {
config : & Config {
2022-09-15 10:51:07 -04:00
Version : Version1 ,
2022-05-16 12:54:25 -04:00
} ,
configName : constants . ConfigFilename ,
wantResult : & Config {
2022-09-15 10:51:07 -04:00
Version : Version1 ,
2022-05-16 12:54:25 -04:00
} ,
} ,
"modify default config" : {
config : func ( ) * Config {
conf := Default ( )
conf . Provider . GCP . Region = "eu-north1"
conf . Provider . GCP . Zone = "eu-north1-a"
return conf
} ( ) ,
configName : constants . ConfigFilename ,
wantResult : func ( ) * Config {
conf := Default ( )
conf . Provider . GCP . Region = "eu-north1"
conf . Provider . GCP . Zone = "eu-north1-a"
return conf
} ( ) ,
2022-03-22 11:03:15 -04:00
} ,
}
for name , tc := range testCases {
t . Run ( name , func ( t * testing . T ) {
assert := assert . New ( t )
require := require . New ( t )
fileHandler := file . NewHandler ( afero . NewMemMapFs ( ) )
2022-05-16 12:54:25 -04:00
if tc . config != nil {
require . NoError ( fileHandler . WriteYAML ( tc . configName , tc . config , file . OptNone ) )
}
2022-03-22 11:03:15 -04:00
result , err := FromFile ( fileHandler , tc . configName )
if tc . wantErr {
assert . Error ( err )
} else {
require . NoError ( err )
2022-05-16 12:54:25 -04:00
assert . Equal ( tc . wantResult , result )
2022-03-22 11:03:15 -04:00
}
} )
}
}
2022-05-18 05:39:14 -04:00
2022-05-23 09:01:39 -04:00
func TestValidate ( t * testing . T ) {
2022-10-21 06:24:18 -04:00
const defaultMsgCount = 20 // expect this number of error messages by default because user-specific values are not set and multiple providers are defined by default
2022-09-11 10:09:05 -04:00
2022-05-23 09:01:39 -04:00
testCases := map [ string ] struct {
cnf * Config
wantMsgCount int
} {
"default config is valid" : {
cnf : Default ( ) ,
2022-08-25 08:06:29 -04:00
wantMsgCount : defaultMsgCount ,
2022-05-23 09:01:39 -04:00
} ,
"config with 1 error" : {
cnf : func ( ) * Config {
cnf := Default ( )
cnf . Version = "v0"
return cnf
} ( ) ,
2022-08-25 08:06:29 -04:00
wantMsgCount : defaultMsgCount + 1 ,
2022-05-23 09:01:39 -04:00
} ,
"config with 2 errors" : {
cnf : func ( ) * Config {
cnf := Default ( )
cnf . Version = "v0"
cnf . StateDiskSizeGB = - 1
return cnf
} ( ) ,
2022-08-25 08:06:29 -04:00
wantMsgCount : defaultMsgCount + 2 ,
2022-05-23 09:01:39 -04:00
} ,
}
for name , tc := range testCases {
t . Run ( name , func ( t * testing . T ) {
assert := assert . New ( t )
require := require . New ( t )
msgs , err := tc . cnf . Validate ( )
require . NoError ( err )
assert . Len ( msgs , tc . wantMsgCount )
} )
}
}
func TestHasProvider ( t * testing . T ) {
assert := assert . New ( t )
assert . False ( ( & Config { } ) . HasProvider ( cloudprovider . Unknown ) )
assert . False ( ( & Config { } ) . HasProvider ( cloudprovider . Azure ) )
assert . False ( ( & Config { } ) . HasProvider ( cloudprovider . GCP ) )
assert . False ( ( & Config { } ) . HasProvider ( cloudprovider . QEMU ) )
assert . False ( Default ( ) . HasProvider ( cloudprovider . Unknown ) )
assert . True ( Default ( ) . HasProvider ( cloudprovider . Azure ) )
assert . True ( Default ( ) . HasProvider ( cloudprovider . GCP ) )
cnfWithAzure := Config { Provider : ProviderConfig { Azure : & AzureConfig { } } }
assert . False ( cnfWithAzure . HasProvider ( cloudprovider . Unknown ) )
assert . True ( cnfWithAzure . HasProvider ( cloudprovider . Azure ) )
assert . False ( cnfWithAzure . HasProvider ( cloudprovider . GCP ) )
}
2022-08-01 03:37:05 -04:00
func TestImage ( t * testing . T ) {
testCases := map [ string ] struct {
cfg * Config
wantImage string
} {
2022-10-21 06:24:18 -04:00
"default aws" : {
cfg : func ( ) * Config { c := Default ( ) ; c . RemoveProviderExcept ( cloudprovider . AWS ) ; return c } ( ) ,
wantImage : Default ( ) . Provider . AWS . Image ,
} ,
2022-08-01 03:37:05 -04:00
"default azure" : {
cfg : func ( ) * Config { c := Default ( ) ; c . RemoveProviderExcept ( cloudprovider . Azure ) ; return c } ( ) ,
wantImage : Default ( ) . Provider . Azure . Image ,
} ,
"default gcp" : {
cfg : func ( ) * Config { c := Default ( ) ; c . RemoveProviderExcept ( cloudprovider . GCP ) ; return c } ( ) ,
wantImage : Default ( ) . Provider . GCP . Image ,
} ,
"default qemu" : {
cfg : func ( ) * Config { c := Default ( ) ; c . RemoveProviderExcept ( cloudprovider . QEMU ) ; return c } ( ) ,
wantImage : "" ,
} ,
}
for name , tc := range testCases {
t . Run ( name , func ( t * testing . T ) {
assert := assert . New ( t )
image := tc . cfg . Image ( )
assert . Equal ( tc . wantImage , image )
} )
}
}
2022-05-18 05:39:14 -04:00
func TestConfigRemoveProviderExcept ( t * testing . T ) {
testCases := map [ string ] struct {
removeExcept cloudprovider . Provider
2022-10-21 06:24:18 -04:00
wantAWS * AWSConfig
2022-05-18 05:39:14 -04:00
wantAzure * AzureConfig
wantGCP * GCPConfig
wantQEMU * QEMUConfig
} {
2022-10-21 06:24:18 -04:00
"except aws" : {
removeExcept : cloudprovider . AWS ,
wantAWS : Default ( ) . Provider . AWS ,
} ,
2022-05-18 05:39:14 -04:00
"except azure" : {
removeExcept : cloudprovider . Azure ,
wantAzure : Default ( ) . Provider . Azure ,
} ,
"except gcp" : {
removeExcept : cloudprovider . GCP ,
wantGCP : Default ( ) . Provider . GCP ,
} ,
"except qemu" : {
removeExcept : cloudprovider . QEMU ,
wantQEMU : Default ( ) . Provider . QEMU ,
} ,
"unknown provider" : {
removeExcept : cloudprovider . Unknown ,
2022-10-21 06:24:18 -04:00
wantAWS : Default ( ) . Provider . AWS ,
2022-05-18 05:39:14 -04:00
wantAzure : Default ( ) . Provider . Azure ,
wantGCP : Default ( ) . Provider . GCP ,
wantQEMU : Default ( ) . Provider . QEMU ,
} ,
}
for name , tc := range testCases {
t . Run ( name , func ( t * testing . T ) {
assert := assert . New ( t )
conf := Default ( )
conf . RemoveProviderExcept ( tc . removeExcept )
2022-10-21 06:24:18 -04:00
assert . Equal ( tc . wantAWS , conf . Provider . AWS )
2022-05-18 05:39:14 -04:00
assert . Equal ( tc . wantAzure , conf . Provider . Azure )
assert . Equal ( tc . wantGCP , conf . Provider . GCP )
assert . Equal ( tc . wantQEMU , conf . Provider . QEMU )
} )
}
}
2022-05-18 12:10:57 -04:00
func TestConfigGeneratedDocsFresh ( t * testing . T ) {
assert := assert . New ( t )
2022-08-02 06:24:55 -04:00
updateMsg := "remember to re-generate config docs! 🔨"
assert . Len ( ConfigDoc . Fields , reflect . ValueOf ( Config { } ) . NumField ( ) , updateMsg )
2022-08-29 10:49:44 -04:00
assert . Len ( UpgradeConfigDoc . Fields , reflect . ValueOf ( UpgradeConfig { } ) . NumField ( ) , updateMsg )
2022-08-02 06:24:55 -04:00
assert . Len ( UserKeyDoc . Fields , reflect . ValueOf ( UserKey { } ) . NumField ( ) , updateMsg )
assert . Len ( ProviderConfigDoc . Fields , reflect . ValueOf ( ProviderConfig { } ) . NumField ( ) , updateMsg )
assert . Len ( AzureConfigDoc . Fields , reflect . ValueOf ( AzureConfig { } ) . NumField ( ) , updateMsg )
assert . Len ( GCPConfigDoc . Fields , reflect . ValueOf ( GCPConfig { } ) . NumField ( ) , updateMsg )
assert . Len ( QEMUConfigDoc . Fields , reflect . ValueOf ( QEMUConfig { } ) . NumField ( ) , updateMsg )
2022-05-18 12:10:57 -04:00
}
2022-08-01 03:37:05 -04:00
func TestConfig_UpdateMeasurements ( t * testing . T ) {
assert := assert . New ( t )
newMeasurements := Measurements {
1 : [ ] byte { 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 } ,
2 : [ ] byte { 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 } ,
3 : [ ] byte { 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 } ,
}
2022-10-21 06:24:18 -04:00
{ // AWS
conf := Default ( )
conf . RemoveProviderExcept ( cloudprovider . AWS )
for k := range conf . Provider . AWS . Measurements {
delete ( conf . Provider . AWS . Measurements , k )
}
conf . UpdateMeasurements ( newMeasurements )
assert . Equal ( newMeasurements , conf . Provider . AWS . Measurements )
}
2022-08-01 03:37:05 -04:00
{ // Azure
conf := Default ( )
conf . RemoveProviderExcept ( cloudprovider . Azure )
for k := range conf . Provider . Azure . Measurements {
delete ( conf . Provider . Azure . Measurements , k )
}
conf . UpdateMeasurements ( newMeasurements )
assert . Equal ( newMeasurements , conf . Provider . Azure . Measurements )
}
{ // GCP
conf := Default ( )
conf . RemoveProviderExcept ( cloudprovider . GCP )
for k := range conf . Provider . GCP . Measurements {
delete ( conf . Provider . GCP . Measurements , k )
}
conf . UpdateMeasurements ( newMeasurements )
assert . Equal ( newMeasurements , conf . Provider . GCP . Measurements )
}
{ // QEMU
conf := Default ( )
conf . RemoveProviderExcept ( cloudprovider . QEMU )
for k := range conf . Provider . QEMU . Measurements {
delete ( conf . Provider . QEMU . Measurements , k )
}
conf . UpdateMeasurements ( newMeasurements )
assert . Equal ( newMeasurements , conf . Provider . QEMU . Measurements )
}
}
2022-08-16 09:53:54 -04:00
func TestConfig_IsImageDebug ( t * testing . T ) {
testCases := map [ string ] struct {
conf * Config
want bool
} {
2022-10-21 06:24:18 -04:00
// TODO: Add AWS when we know the format of published images & debug images
2022-08-16 09:53:54 -04:00
"gcp release" : {
conf : func ( ) * Config {
conf := Default ( )
conf . RemoveProviderExcept ( cloudprovider . GCP )
conf . Provider . GCP . Image = "projects/constellation-images/global/images/constellation-v1-3-0"
return conf
} ( ) ,
want : false ,
} ,
"gcp debug" : {
conf : func ( ) * Config {
conf := Default ( )
conf . RemoveProviderExcept ( cloudprovider . GCP )
conf . Provider . GCP . Image = "projects/constellation-images/global/images/constellation-20220812102023"
return conf
} ( ) ,
want : true ,
} ,
"azure release" : {
conf : func ( ) * Config {
conf := Default ( )
conf . RemoveProviderExcept ( cloudprovider . Azure )
2022-09-01 06:58:31 -04:00
conf . Provider . Azure . Image = "/CommunityGalleries/ConstellationCVM-b3782fa0-0df7-4f2f-963e-fc7fc42663df/Images/constellation/Versions/0.0.1"
2022-08-16 09:53:54 -04:00
return conf
} ( ) ,
want : false ,
} ,
"azure debug" : {
conf : func ( ) * Config {
conf := Default ( )
conf . RemoveProviderExcept ( cloudprovider . Azure )
conf . Provider . Azure . Image = "/subscriptions/0d202bbb-4fa7-4af8-8125-58c269a05435/resourceGroups/constellation-images/providers/Microsoft.Compute/galleries/Constellation_Debug/images/v1.4.0/versions/2022.0805.151600"
return conf
} ( ) ,
want : true ,
} ,
"empty config" : {
conf : & Config { } ,
want : false ,
} ,
}
for name , tc := range testCases {
t . Run ( name , func ( t * testing . T ) {
assert := assert . New ( t )
2022-09-06 07:06:09 -04:00
assert . Equal ( tc . want , tc . conf . IsDebugImage ( ) )
2022-08-16 09:53:54 -04:00
} )
}
}
2022-08-31 11:35:33 -04:00
func TestValidInstanceTypeForProvider ( t * testing . T ) {
testCases := map [ string ] struct {
provider cloudprovider . Provider
instanceTypes [ ] string
nonCVMsAllowed bool
expectedResult bool
} {
"empty all" : {
provider : cloudprovider . Unknown ,
instanceTypes : [ ] string { } ,
expectedResult : false ,
} ,
2022-10-21 06:24:18 -04:00
"empty aws" : {
provider : cloudprovider . AWS ,
instanceTypes : [ ] string { } ,
expectedResult : false ,
} ,
2022-08-31 11:35:33 -04:00
"empty azure only CVMs" : {
provider : cloudprovider . Azure ,
instanceTypes : [ ] string { } ,
expectedResult : false ,
} ,
"empty azure with non-CVMs" : {
provider : cloudprovider . Azure ,
instanceTypes : [ ] string { } ,
nonCVMsAllowed : true ,
expectedResult : false ,
} ,
"empty gcp" : {
provider : cloudprovider . GCP ,
instanceTypes : [ ] string { } ,
expectedResult : false ,
} ,
"azure only CVMs" : {
provider : cloudprovider . Azure ,
instanceTypes : instancetypes . AzureCVMInstanceTypes ,
expectedResult : true ,
} ,
"azure CVMs but CVMs disabled" : {
provider : cloudprovider . Azure ,
instanceTypes : instancetypes . AzureCVMInstanceTypes ,
nonCVMsAllowed : true ,
expectedResult : false ,
} ,
"azure trusted launch VMs with CVMs enabled" : {
provider : cloudprovider . Azure ,
instanceTypes : instancetypes . AzureTrustedLaunchInstanceTypes ,
expectedResult : false ,
} ,
2022-10-21 06:24:18 -04:00
"azure trusted launch VMs with CVMs disabled" : {
2022-08-31 11:35:33 -04:00
provider : cloudprovider . Azure ,
instanceTypes : instancetypes . AzureTrustedLaunchInstanceTypes ,
nonCVMsAllowed : true ,
expectedResult : true ,
} ,
"gcp" : {
provider : cloudprovider . GCP ,
instanceTypes : instancetypes . GCPInstanceTypes ,
expectedResult : true ,
} ,
"put gcp when azure is set" : {
provider : cloudprovider . Azure ,
instanceTypes : instancetypes . GCPInstanceTypes ,
expectedResult : false ,
} ,
"put gcp when azure is set with CVMs disabled" : {
provider : cloudprovider . Azure ,
instanceTypes : instancetypes . GCPInstanceTypes ,
nonCVMsAllowed : true ,
expectedResult : false ,
} ,
"put azure when gcp is set" : {
provider : cloudprovider . GCP ,
instanceTypes : instancetypes . AzureCVMInstanceTypes ,
expectedResult : false ,
} ,
"put azure when gcp is set with CVMs disabled" : {
provider : cloudprovider . GCP ,
instanceTypes : instancetypes . AzureTrustedLaunchInstanceTypes ,
nonCVMsAllowed : true ,
expectedResult : false ,
} ,
2022-10-21 06:24:18 -04:00
// Testing every possible instance type for AWS is not feasible, so we just test a few based on known supported / unsupported families
// Also serves as a test for checkIfInstanceInValidAWSFamilys
"aws two valid instances" : {
provider : cloudprovider . AWS ,
instanceTypes : [ ] string { "c5.xlarge" , "c5a.2xlarge" , "c5a.16xlarge" , "u-12tb1.112xlarge" } ,
expectedResult : true ,
} ,
"aws one valid instance one with too little vCPUs" : {
provider : cloudprovider . AWS ,
instanceTypes : [ ] string { "c5.medium" } ,
expectedResult : false ,
} ,
"aws graviton sub-family unsupported" : {
provider : cloudprovider . AWS ,
instanceTypes : [ ] string { "m6g.xlarge" , "r6g.2xlarge" , "x2gd.xlarge" , "g5g.8xlarge" } ,
expectedResult : false ,
} ,
"aws combined two valid instances as one string" : {
provider : cloudprovider . AWS ,
instanceTypes : [ ] string { "c5.xlarge, c5a.2xlarge" } ,
expectedResult : false ,
} ,
2022-08-31 11:35:33 -04:00
}
for name , tc := range testCases {
t . Run ( name , func ( t * testing . T ) {
assert := assert . New ( t )
for _ , instanceType := range tc . instanceTypes {
assert . Equal ( tc . expectedResult , validInstanceTypeForProvider ( instanceType , tc . nonCVMsAllowed , tc . provider ) , instanceType )
}
} )
}
}
2022-09-05 10:53:37 -04:00
func TestIsDebugCluster ( t * testing . T ) {
testCases := map [ string ] struct {
config * Config
prepareConfig func ( * Config )
expectedResult bool
} {
"empty config" : {
config : & Config { } ,
expectedResult : false ,
} ,
"default config" : {
config : Default ( ) ,
expectedResult : false ,
} ,
"enabled" : {
config : Default ( ) ,
prepareConfig : func ( conf * Config ) {
* conf . DebugCluster = true
} ,
expectedResult : true ,
} ,
}
for name , tc := range testCases {
t . Run ( name , func ( t * testing . T ) {
assert := assert . New ( t )
if tc . prepareConfig != nil {
tc . prepareConfig ( tc . config )
}
assert . Equal ( tc . expectedResult , tc . config . IsDebugCluster ( ) )
} )
}
}
2022-09-07 05:53:44 -04:00
func TestValidateProvider ( t * testing . T ) {
testCases := map [ string ] struct {
provider ProviderConfig
wantErr bool
expectedErrorTag string
} {
"empty, should trigger no provider error" : {
provider : ProviderConfig { } ,
wantErr : true ,
expectedErrorTag : "no_provider" ,
} ,
"azure only, should be okay" : {
provider : ProviderConfig {
Azure : & AzureConfig { } ,
} ,
wantErr : false ,
} ,
"gcp only, should be okay" : {
provider : ProviderConfig {
GCP : & GCPConfig { } ,
} ,
wantErr : false ,
} ,
"qemu only, should be okay" : {
provider : ProviderConfig {
QEMU : & QEMUConfig { } ,
} ,
wantErr : false ,
} ,
"azure and gcp, should trigger multiple provider error" : {
provider : ProviderConfig {
Azure : & AzureConfig { } ,
GCP : & GCPConfig { } ,
} ,
wantErr : true ,
expectedErrorTag : "more_than_one_provider" ,
} ,
}
for name , tc := range testCases {
t . Run ( name , func ( t * testing . T ) {
assert := assert . New ( t )
require := require . New ( t )
v := validator . New ( )
trans := ut . New ( en . New ( ) ) . GetFallback ( )
conf := Default ( )
conf . Provider = tc . provider
v . RegisterStructValidation ( validateProvider , ProviderConfig { } )
err := v . StructPartial ( tc . provider )
// Register provider validation error types.
// Make sure the tags and expected strings below are in sync with the actual implementation.
require . NoError ( v . RegisterTranslation ( "no_provider" , trans , registerNoProviderError , translateNoProviderError ) )
require . NoError ( v . RegisterTranslation ( "more_than_one_provider" , trans , registerMoreThanOneProviderError , conf . translateMoreThanOneProviderError ) )
// Continue if no error is expected.
if ! tc . wantErr {
assert . NoError ( err )
return
}
// Validate if the error was identified correctly.
require . NotNil ( err )
assert . Error ( err )
assert . Contains ( err . Error ( ) , tc . expectedErrorTag )
// Check if error translation works correctly.
validationErr := err . ( validator . ValidationErrors )
translatedErr := validationErr . Translate ( trans )
// The translator does not seem to export a list of available translations or for a specific field.
// So we need to hardcode expected strings. Needs to be in sync with implementation.
switch tc . expectedErrorTag {
case "no_provider" :
assert . Contains ( translatedErr [ "ProviderConfig.Provider" ] , "No provider has been defined" )
case "more_than_one_provider" :
assert . Contains ( translatedErr [ "ProviderConfig.Provider" ] , "Only one provider can be defined" )
}
} )
}
}