Skip to content

Commit

Permalink
Merge pull request #3232 from andfasano/validate-hosts
Browse files Browse the repository at this point in the history
baremetal: platform host validations
  • Loading branch information
openshift-merge-robot authored Mar 20, 2020
2 parents 1121d3f + 508e7e4 commit d3acf80
Show file tree
Hide file tree
Showing 55 changed files with 10,254 additions and 7 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ require (
github.com/frankban/quicktest v1.7.2 // indirect
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
github.com/go-logr/zapr v0.1.1 // indirect
github.com/go-playground/validator/v10 v10.2.0
github.com/golang/mock v1.3.1
github.com/google/go-cmp v0.3.2-0.20191028172631-481baca67f93 // indirect
github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible // indirect
Expand Down
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,14 @@ github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+
github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
github.com/go-ozzo/ozzo-validation v3.5.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
Expand Down Expand Up @@ -1504,6 +1512,8 @@ github.com/kubernetes-sigs/kube-storage-version-migrator v0.0.0-20191127225502-5
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
Expand Down
10 changes: 5 additions & 5 deletions pkg/types/baremetal/platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ import (

// BMC stores the information about a baremetal host's management controller.
type BMC struct {
Username string `json:"username"`
Password string `json:"password"`
Address string `json:"address"`
Username string `json:"username" validate:"required"`
Password string `json:"password" validate:"required"`
Address string `json:"address" validate:"required,uniqueField"`
DisableCertificateVerification bool `json:"disableCertificateVerification"`
}

// Host stores all the configuration data for a baremetal host.
type Host struct {
Name string `json:"name,omitempty"`
Name string `json:"name,omitempty" validate:"required,uniqueField"`
BMC BMC `json:"bmc"`
Role string `json:"role"`
BootMACAddress string `json:"bootMACAddress"`
BootMACAddress string `json:"bootMACAddress" validate:"required,uniqueField"`
HardwareProfile string `json:"hardwareProfile"`
}

Expand Down
58 changes: 57 additions & 1 deletion pkg/types/baremetal/validation/platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import (
"fmt"
"net"
"net/url"
"reflect"
"strings"

"github.com/go-playground/validator/v10"

"github.com/openshift/installer/pkg/types"
"github.com/openshift/installer/pkg/types/baremetal"
"github.com/openshift/installer/pkg/validate"
"k8s.io/apimachinery/pkg/util/validation/field"
"strings"
)

// dynamicValidator is a function that validates certain fields in the platform.
Expand Down Expand Up @@ -61,6 +64,57 @@ func validateOSImageURI(uri string) error {
return nil
}

// validateHosts checks that hosts have all required fields set with appropriate values
func validateHosts(hosts []*baremetal.Host, fldPath *field.Path) field.ErrorList {
hostErrs := field.ErrorList{}

values := make(map[string]map[interface{}]struct{})

//Initialize a new validator and register a custom validation rule for the tag `uniqueField`
validate := validator.New()
validate.RegisterValidation("uniqueField", func(fl validator.FieldLevel) bool {
valueFound := false
fieldName := fl.Parent().Type().Name() + "." + fl.FieldName()
fieldValue := fl.Field().Interface()

if fl.Field().Type().Comparable() {
if _, present := values[fieldName]; !present {
values[fieldName] = make(map[interface{}]struct{})
}

fieldValues := values[fieldName]
if _, valueFound = fieldValues[fieldValue]; !valueFound {
fieldValues[fieldValue] = struct{}{}
}
} else {
panic(fmt.Sprintf("Cannot apply validation rule 'uniqueField' on field %s", fl.FieldName()))
}

return !valueFound
})

//Apply validations and translate errors
fldPath = fldPath.Child("hosts")

for idx, host := range hosts {
err := validate.Struct(host)
if err != nil {
hostType := reflect.TypeOf(hosts).Elem().Elem().Name()
for _, err := range err.(validator.ValidationErrors) {
childName := fldPath.Index(idx).Child(err.Namespace()[len(hostType)+1:])
switch err.Tag() {
case "required":
hostErrs = append(hostErrs, field.Required(childName, "missing "+err.Field()))
case "uniqueField":
hostErrs = append(hostErrs, field.Duplicate(childName, err.Value()))
}
}
}
}

return hostErrs
}

// ValidatePlatform checks that the specified platform is valid.
func ValidatePlatform(p *baremetal.Platform, n *types.Networking, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
Expand Down Expand Up @@ -156,6 +210,8 @@ func ValidatePlatform(p *baremetal.Platform, n *types.Networking, fldPath *field
}
}

allErrs = append(allErrs, validateHosts(p.Hosts, fldPath)...)

for _, validator := range dynamicValidators {
allErrs = append(allErrs, validator(p, fldPath)...)
}
Expand Down
248 changes: 247 additions & 1 deletion pkg/types/baremetal/validation/platform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -481,11 +481,257 @@ func TestValidatePlatform(t *testing.T) {
network: network,
expected: "Invalid value: \"192.168.128.1\": \"192.168.128.1\" is not in the provisioning network",
},
{
name: "duplicate_bmc_address",
platform: &baremetal.Platform{
APIVIP: "192.168.111.2",
DNSVIP: "192.168.111.3",
IngressVIP: "192.168.111.4",
Hosts: []*baremetal.Host{
{
Name: "host1",
BootMACAddress: "CA:FE:CA:FE:00:00",
BMC: baremetal.BMC{
Username: "root",
Password: "password",
Address: "ipmi://192.168.111.1",
},
},
{
Name: "host2",
BootMACAddress: "CA:FE:CA:FE:00:01",
BMC: baremetal.BMC{
Username: "root",
Password: "password",
Address: "ipmi://192.168.111.1",
},
},
},
LibvirtURI: "qemu://system",
ProvisioningNetworkCIDR: ipnet.MustParseCIDR("172.22.0.0/24"),
ClusterProvisioningIP: "172.22.0.3",
BootstrapProvisioningIP: "172.22.0.2",
ExternalBridge: "br0",
ProvisioningBridge: "br1",
ProvisioningNetworkInterface: "ens3",
},
network: network,
expected: "baremetal.hosts\\[1\\].BMC.Address: Duplicate value: \"ipmi://192.168.111.1\"",
},
{
name: "bmc_address_required",
platform: &baremetal.Platform{
APIVIP: "192.168.111.2",
DNSVIP: "192.168.111.3",
IngressVIP: "192.168.111.4",
Hosts: []*baremetal.Host{
{
Name: "host1",
BootMACAddress: "CA:FE:CA:FE:00:00",
BMC: baremetal.BMC{
Username: "root",
Password: "password",
},
},
},
LibvirtURI: "qemu://system",
ProvisioningNetworkCIDR: ipnet.MustParseCIDR("172.22.0.0/24"),
ClusterProvisioningIP: "172.22.0.3",
BootstrapProvisioningIP: "172.22.0.2",
ExternalBridge: "br0",
ProvisioningBridge: "br1",
ProvisioningNetworkInterface: "ens3",
},
network: network,
expected: "baremetal.hosts\\[0\\].BMC.Address: Required value: missing Address",
},
{
name: "bmc_username_required",
platform: &baremetal.Platform{
APIVIP: "192.168.111.2",
DNSVIP: "192.168.111.3",
IngressVIP: "192.168.111.4",
Hosts: []*baremetal.Host{
{
Name: "host1",
BootMACAddress: "CA:FE:CA:FE:00:00",
BMC: baremetal.BMC{
Password: "password",
Address: "ipmi://192.168.111.1",
},
},
},
LibvirtURI: "qemu://system",
ProvisioningNetworkCIDR: ipnet.MustParseCIDR("172.22.0.0/24"),
ClusterProvisioningIP: "172.22.0.3",
BootstrapProvisioningIP: "172.22.0.2",
ExternalBridge: "br0",
ProvisioningBridge: "br1",
ProvisioningNetworkInterface: "ens3",
},
network: network,
expected: "baremetal.hosts\\[0\\].BMC.Username: Required value: missing Username",
},
{
name: "bmc_password_required",
platform: &baremetal.Platform{
APIVIP: "192.168.111.2",
DNSVIP: "192.168.111.3",
IngressVIP: "192.168.111.4",
Hosts: []*baremetal.Host{
{
Name: "host1",
BootMACAddress: "CA:FE:CA:FE:00:00",
BMC: baremetal.BMC{
Username: "root",
Address: "ipmi://192.168.111.1",
},
},
},
LibvirtURI: "qemu://system",
ProvisioningNetworkCIDR: ipnet.MustParseCIDR("172.22.0.0/24"),
ClusterProvisioningIP: "172.22.0.3",
BootstrapProvisioningIP: "172.22.0.2",
ExternalBridge: "br0",
ProvisioningBridge: "br1",
ProvisioningNetworkInterface: "ens3",
},
network: network,
expected: "baremetal.hosts\\[0\\].BMC.Password: Required value: missing Password",
},
{
name: "duplicate_host_name",
platform: &baremetal.Platform{
APIVIP: "192.168.111.2",
DNSVIP: "192.168.111.3",
IngressVIP: "192.168.111.4",
Hosts: []*baremetal.Host{
{
Name: "host1",
BootMACAddress: "CA:FE:CA:FE:00:00",
BMC: baremetal.BMC{
Username: "root",
Password: "password",
Address: "ipmi://192.168.111.1",
},
},
{
Name: "host1",
BootMACAddress: "CA:FE:CA:FE:00:01",
BMC: baremetal.BMC{
Username: "root",
Password: "password",
Address: "ipmi://192.168.111.2",
},
},
},
LibvirtURI: "qemu://system",
ProvisioningNetworkCIDR: ipnet.MustParseCIDR("172.22.0.0/24"),
ClusterProvisioningIP: "172.22.0.3",
BootstrapProvisioningIP: "172.22.0.2",
ExternalBridge: "br0",
ProvisioningBridge: "br1",
ProvisioningNetworkInterface: "ens3",
},
network: network,
expected: "baremetal.hosts\\[1\\].Name: Duplicate value: \"host1\"",
},
{
name: "duplicate_host_mac",
platform: &baremetal.Platform{
APIVIP: "192.168.111.2",
DNSVIP: "192.168.111.3",
IngressVIP: "192.168.111.4",
Hosts: []*baremetal.Host{
{
Name: "host1",
BootMACAddress: "CA:FE:CA:FE:CA:FE",
BMC: baremetal.BMC{
Username: "root",
Password: "password",
Address: "ipmi://192.168.111.1",
},
},
{
Name: "host2",
BootMACAddress: "CA:FE:CA:FE:CA:FE",
BMC: baremetal.BMC{
Username: "root",
Password: "password",
Address: "ipmi://192.168.111.2",
},
},
},
LibvirtURI: "qemu://system",
ProvisioningNetworkCIDR: ipnet.MustParseCIDR("172.22.0.0/24"),
ClusterProvisioningIP: "172.22.0.3",
BootstrapProvisioningIP: "172.22.0.2",
ExternalBridge: "br0",
ProvisioningBridge: "br1",
ProvisioningNetworkInterface: "ens3",
},
network: network,
expected: "baremetal.hosts\\[1\\].BootMACAddress: Duplicate value: \"CA:FE:CA:FE:CA:FE\"",
},
{
name: "missing_name",
platform: &baremetal.Platform{
APIVIP: "192.168.111.2",
DNSVIP: "192.168.111.3",
IngressVIP: "192.168.111.4",
Hosts: []*baremetal.Host{
{
BootMACAddress: "CA:FE:CA:FE:CA:FE",
BMC: baremetal.BMC{
Username: "root",
Password: "password",
Address: "ipmi://192.168.111.1",
},
},
},
LibvirtURI: "qemu://system",
ProvisioningNetworkCIDR: ipnet.MustParseCIDR("172.22.0.0/24"),
ClusterProvisioningIP: "172.22.0.3",
BootstrapProvisioningIP: "172.22.0.2",
ExternalBridge: "br0",
ProvisioningBridge: "br1",
ProvisioningNetworkInterface: "ens3",
},
network: network,
expected: "baremetal.hosts\\[0\\].Name: Required value: missing Name",
},
{
name: "missing_mac",
platform: &baremetal.Platform{
APIVIP: "192.168.111.2",
DNSVIP: "192.168.111.3",
IngressVIP: "192.168.111.4",
Hosts: []*baremetal.Host{
{
Name: "host1",
BMC: baremetal.BMC{
Username: "root",
Password: "password",
Address: "ipmi://192.168.111.1",
},
},
},
LibvirtURI: "qemu://system",
ProvisioningNetworkCIDR: ipnet.MustParseCIDR("172.22.0.0/24"),
ClusterProvisioningIP: "172.22.0.3",
BootstrapProvisioningIP: "172.22.0.2",
ExternalBridge: "br0",
ProvisioningBridge: "br1",
ProvisioningNetworkInterface: "ens3",
},
network: network,
expected: "baremetal.hosts\\[0\\].BootMACAddress: Required value: missing BootMACAddress",
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
err := ValidatePlatform(tc.platform, tc.network, field.NewPath("test-path")).ToAggregate()
err := ValidatePlatform(tc.platform, tc.network, field.NewPath("baremetal")).ToAggregate()
if tc.expected == "" {
assert.NoError(t, err)
} else {
Expand Down
Loading

0 comments on commit d3acf80

Please sign in to comment.