Skip to content

Commit

Permalink
Merge pull request #20 from hawkv6/config-validation
Browse files Browse the repository at this point in the history
Config validation
  • Loading branch information
jklaiber authored Nov 10, 2023
2 parents 23f6e6a + bab10e4 commit f5e176e
Show file tree
Hide file tree
Showing 12 changed files with 223 additions and 23 deletions.
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.21

require (
github.com/cilium/ebpf v0.11.0
github.com/go-playground/validator v9.31.0+incompatible
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.7.0
github.com/spf13/viper v1.16.0
Expand All @@ -15,9 +16,12 @@ require (

require (
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
Expand All @@ -31,6 +35,7 @@ require (
golang.org/x/net v0.17.0 // indirect
golang.org/x/text v0.13.0 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
11 changes: 11 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbS
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp4Mit+3FDh548oRqwVgNsHA=
github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
Expand Down Expand Up @@ -144,6 +150,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
Expand Down Expand Up @@ -182,6 +190,7 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
Expand Down Expand Up @@ -500,6 +509,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
Expand Down
17 changes: 11 additions & 6 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,31 @@ package config
import (
"fmt"

"github.com/hawkv6/hawkwing/pkg/logging"
"github.com/spf13/viper"
)

const Subsystem = "go-config"

var (
// Use \ as key delimiter because we have . in the key
viperInstance = viper.NewWithOptions(viper.KeyDelimiter("\\"))
Params Config
log = logging.DefaultLogger.WithField("subsystem", Subsystem)
)

type HawkEyeConfig struct {
Hostname string `mapstructure:"hostname"`
Port int `mapstructure:"port"`
// TODO: validate the format of the hostname
Hostname string `mapstructure:"hostname" validate:"required"`
Port int `mapstructure:"port" validate:"required,gt=0,lt=65535"`
}

type Intent struct {
Intent string `mapstructure:"intent"`
Port int `mapstructure:"port"`
MinValue int `mapstructure:"min_value"`
MaxValue int `mapstructure:"max_value"`
Functions []string `mapstructure:"functions"`
FlexAlgoNr int `mapstructure:"flex_algo_number"`
Sid []string `mapstructure:"sid"`
}

type Application struct {
Expand All @@ -40,8 +43,8 @@ type ServiceConfig struct {
}

type Config struct {
HawkEye HawkEyeConfig
Services map[string]ServiceConfig
HawkEye HawkEyeConfig `validate:"required,dive,required"`
Services map[string]ServiceConfig `validate:"required,dive,required"`
}

func init() {
Expand All @@ -60,6 +63,8 @@ func Parse() error {
return fmt.Errorf("failed to parse config: %v", err)
}

Validate()

return nil
}

Expand Down
8 changes: 8 additions & 0 deletions internal/config/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package config

type validationError struct {
Namespace string `json:"namespace"`
Field string `json:"field"`
ActualValue string `json:"actual_value"`
Message string `json:"message"`
}
47 changes: 47 additions & 0 deletions internal/config/validate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package config

import (
"encoding/json"
"fmt"
"reflect"

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

var validate *validator.Validate

func Validate() {
validate = validator.New()

validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
return fld.Tag.Get("mapstructure")
})

validate.RegisterStructValidation(ServiceConfigValidation, ServiceConfig{})
validate.RegisterStructValidation(ApplicationValidation, Application{})
validate.RegisterStructValidation(IntentValidation, Intent{})

err := validate.Struct(Params)
if err != nil {
if _, ok := err.(*validator.InvalidValidationError); ok {
log.Info(err)
}

for _, err := range err.(validator.ValidationErrors) {
e := validationError{
Namespace: err.Namespace(),
Field: err.Field(),
ActualValue: fmt.Sprintf("%v", err.Value()),
Message: err.Tag(),
}

indent, err := json.MarshalIndent(e, "", " ")
if err != nil {
log.Fatalf("failed to validate config: %v", err)
}
fmt.Println(string(indent))
log.Fatalf("failed to validate config with errors above")
}
}

}
54 changes: 54 additions & 0 deletions internal/config/validate_application.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package config

import (
"net"

"github.com/go-playground/validator"
"github.com/hawkv6/hawkwing/pkg/types"
)

func ApplicationValidation(sl validator.StructLevel) {
application := sl.Current().Interface().(Application)

if application.Port > 65535 || application.Port < 0 {
sl.ReportError(application.Port, "port", "", "port must be between 0 and 65535", "")
}

if application.Sid != nil {
for _, sid := range application.Sid {
ip := net.ParseIP(sid)
if ip == nil || ip.To4() != nil {
sl.ReportError(sid, "sid", "", "sid must be a valid ipv6 address", "")
}
}
}

if application.Sid == nil && application.Intents == nil {
sl.ReportError(application.Sid, "sid", "", "sid or intents must be specified", "")
}

for k, intent := range application.Intents {
intentType, err := types.ParseIntentType(intent.Intent)
if err != nil {
sl.ReportError(intent.Intent, "intent", "", "invalid intent", "")
}

// sfc intent must be the first intent in the list
if intentType == types.IntentTypeSfc {
if k != 0 {
sl.ReportError(intent.Intent, "intent", "", "sfc intent must be the first intent in the list", "")
}

if application.Sid == nil {
sl.ReportError(intent.Intent, "sid", "", "sid is required as backup path when using an sfc intent", "")
}
}

// flex-algo intent must be the first intent in the list if there is no sfc intent
if intentType == types.IntentTypeFlexAlgo {
if k >= 1 && application.Intents[0].Intent != types.IntentTypeSfc.String() {
sl.ReportError(intent.Intent, "intent", "", "flex-algo intent must be the first intent in the list", "")
}
}
}
}
37 changes: 37 additions & 0 deletions internal/config/validate_intent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package config

import (
"github.com/go-playground/validator"
"github.com/hawkv6/hawkwing/pkg/types"
)

func IntentValidation(sl validator.StructLevel) {
intent := sl.Current().Interface().(Intent)

intentType, err := types.ParseIntentType(intent.Intent)
if err != nil {
sl.ReportError(intent.Intent, "intent", "", "invalid intent", "")
}

if intentType == types.IntentTypeFlexAlgo || intentType == types.IntentTypeSfc {
if intent.MinValue > 0 {
sl.ReportError(intent.MinValue, "min_value", "", "min_value and max_value are not allowed for flex-algo and sfc intents", "")
}
if intent.MaxValue > 0 {
sl.ReportError(intent.MaxValue, "max_value", "", "min_value and max_value are not allowed for flex-algo and sfc intents", "")
}
}

if intentType == types.IntentTypeFlexAlgo {
if intent.FlexAlgoNr == 0 {
sl.ReportError(intent.FlexAlgoNr, "flex_algo_number", "", "flex_algo_number is required when using an flex_algo intent", "")
}
}

if intentType == types.IntentTypeSfc {
if intent.Functions == nil {
sl.ReportError(intent.Functions, "functions", "", "functions is required when using an sfc intent", "")
}
}

}
29 changes: 29 additions & 0 deletions internal/config/validate_service_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package config

import (
"net"

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

func ServiceConfigValidation(sl validator.StructLevel) {
serviceCfg := sl.Current().Interface().(ServiceConfig)

if len(serviceCfg.DomainName) == 0 && len(serviceCfg.Ipv6Addresses) == 0 {
sl.ReportError(serviceCfg.DomainName, "domain_name", "", "domain_name or ipv6_addresses is required", "")
sl.ReportError(serviceCfg.Ipv6Addresses, "ipv6_addresses", "", "domain_name or ipv6_addresses is required", "")
}

if serviceCfg.Ipv6Addresses != nil {
for _, ipv6 := range serviceCfg.Ipv6Addresses {
ip := net.ParseIP(ipv6)
if ip == nil || ip.To4() != nil {
sl.ReportError(ipv6, "ipv6_addresses", "", "ipv6_addresses must be valid ipv6 addresses", "")
}
}
}

if len(serviceCfg.Applications) == 0 {
sl.ReportError(serviceCfg.Applications, "applications", "", "at least one application must be specified", "")
}
}
19 changes: 10 additions & 9 deletions pkg/entities/intent.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,49 +4,50 @@ import (
"log"

"github.com/hawkv6/hawkwing/internal/config"
"github.com/hawkv6/hawkwing/pkg/types"
)

type IntentValue struct {
IntentValueType IntentValueType
IntentValueType types.IntentValueType
NumberValue int32
StringValue string
}

func CreateIntentValueForIntent(intent config.Intent) []IntentValue {
intentValues := make([]IntentValue, 0)
if intent.Intent == IntentTypeSfc.String() {
if intent.Intent == types.IntentTypeSfc.String() {
for _, function := range intent.Functions {
intentValues = append(intentValues, IntentValue{
IntentValueType: IntentValueTypeSFC,
IntentValueType: types.IntentValueTypeSFC,
StringValue: function,
})
}
return intentValues
}
if intent.Intent == IntentTypeFlexAlgo.String() {
if intent.Intent == types.IntentTypeFlexAlgo.String() {
intentValues = append(intentValues, IntentValue{
IntentValueType: IntentValueTypeFlexAlgoNr,
IntentValueType: types.IntentValueTypeFlexAlgoNr,
NumberValue: int32(intent.FlexAlgoNr),
})
return intentValues
}
if intent.MinValue != 0 {
intentValues = append(intentValues, IntentValue{
IntentValueType: IntentValueTypeMinValue,
IntentValueType: types.IntentValueTypeMinValue,
NumberValue: int32(intent.MinValue),
})
}
if intent.MaxValue != 0 {
intentValues = append(intentValues, IntentValue{
IntentValueType: IntentValueTypeMaxValue,
IntentValueType: types.IntentValueTypeMaxValue,
NumberValue: int32(intent.MaxValue),
})
}
return intentValues
}

type Intent struct {
IntentType IntentType
IntentType types.IntentType
IntentValues []IntentValue
}

Expand All @@ -56,7 +57,7 @@ func CreateIntentsForServiceApplication(serviceKey string, applicationPort int)
for _, application := range serviceCfg.Applications {
if application.Port == applicationPort {
for _, intent := range application.Intents {
intentType, err := ParseIntentType(intent.Intent)
intentType, err := types.ParseIntentType(intent.Intent)
if err != nil {
log.Fatalf("failed to parse intent type %s: %v", intent.Intent, err)
}
Expand Down
9 changes: 6 additions & 3 deletions pkg/entities/path_result.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package entities

import "github.com/hawkv6/hawkwing/pkg/api"
import (
"github.com/hawkv6/hawkwing/pkg/api"
"github.com/hawkv6/hawkwing/pkg/types"
)

type PathResult struct {
Ipv6DestinationAddress string
Expand Down Expand Up @@ -45,13 +48,13 @@ func UnmarshalPathResult(pr *api.PathResult) *PathResult {
intentValues := make([]IntentValue, 0, len(intent.Values))
for _, val := range intent.Values {
intentValues = append(intentValues, IntentValue{
IntentValueType: IntentValueType(val.Type),
IntentValueType: types.IntentValueType(val.Type),
NumberValue: *val.NumberValue,
StringValue: *val.StringValue,
})
}
intents = append(intents, Intent{
IntentType: IntentType(intent.Type),
IntentType: types.IntentType(intent.Type),
IntentValues: intentValues,
})
}
Expand Down
Loading

0 comments on commit f5e176e

Please sign in to comment.