Skip to content

Commit

Permalink
Merge pull request #62 from hannesmann/input-validation
Browse files Browse the repository at this point in the history
Validation for input JSON file in application generator
  • Loading branch information
alekodu authored Jun 22, 2023
2 parents daddd27 + 981c6b2 commit f2d2da3
Show file tree
Hide file tree
Showing 7 changed files with 286 additions and 37 deletions.
6 changes: 5 additions & 1 deletion generator/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ go 1.17

require (
github.com/spf13/cobra v1.2.1
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
gopkg.in/yaml.v3 v3.0.1
k8s.io/apimachinery v0.27.3
)

require (
github.com/gogo/protobuf v1.3.2 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect
)
9 changes: 9 additions & 0 deletions generator/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9
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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
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=
Expand Down Expand Up @@ -550,6 +551,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/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
Expand All @@ -558,13 +561,19 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/apimachinery v0.27.3 h1:Ubye8oBufD04l9QnNtW05idcOe9Z3GQN8+7PqmuVcUM=
k8s.io/apimachinery v0.27.3/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E=
k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY=
k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
45 changes: 16 additions & 29 deletions generator/src/pkg/generate/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ 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
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,
Expand Down Expand Up @@ -54,12 +54,23 @@ func Parse(configFilename string) (model.FileConfig, []string) {
configFileByteValue, _ := ioutil.ReadAll(configFile)

if err != nil {
fmt.Println(err)
panic(err)
}

loaded_config := s.CreateFileConfig()
err = json.Unmarshal(configFileByteValue, &loaded_config)

if err != nil {
panic(err)
}

ApplyDefaults(&loaded_config)
err = ValidateFileConfig(&loaded_config)

if err != nil {
panic(err)
}

json.Unmarshal(configFileByteValue, &loaded_config)
for i := 0; i < len(loaded_config.Services); i++ {
services = append(services, loaded_config.Services[i].Name)
for j := 0; j < len(loaded_config.Services[i].Clusters); j++ {
Expand Down Expand Up @@ -99,37 +110,12 @@ func CreateK8sYaml(config model.FileConfig, clusters []string) {
proto_temp_filled := proto_temp_filled_byte.String()
for i := 0; i < len(config.Services); i++ {
serv := config.Services[i].Name
resources := s.CreateInputResources()
resources = config.Services[i].Resources
protocol := config.Services[i].Endpoints[0].Protocol

if resources.Limits.Cpu == "" {
resources.Limits.Cpu = s.LimitsCPUDefault
}
if resources.Limits.Memory == "" {
resources.Limits.Memory = s.LimitsMemoryDefault
}
if resources.Requests.Cpu == "" {
resources.Requests.Cpu = s.RequestsCPUDefault
}
if resources.Requests.Memory == "" {
resources.Requests.Memory = s.RequestsMemoryDefault
}

readinessProbe := config.Services[i].ReadinessProbe
if readinessProbe == 0 {
readinessProbe = s.SvcReadinessProbeDefault
}

resources := config.Services[i].Resources
processes := config.Services[i].Processes
if processes == 0 {
processes = s.SvcProcessesDefault
}

threads := config.Services[i].Threads
if threads == 0 {
processes = s.SvcThreadsDefault
}

logging := config.Settings.Logging
cm_data := s.CreateConfigMap(processes, threads, logging, config.Services[i].Endpoints)
Expand All @@ -143,6 +129,7 @@ func CreateK8sYaml(config model.FileConfig, clusters []string) {
directory := config.Services[i].Clusters[j].Cluster
annotations := config.Services[i].Clusters[j].Annotations
replicas := config.Services[i].Clusters[j].Replicas

directory_path := fmt.Sprintf(path+"/%s", directory)
c_id := config.Services[i].Clusters[j].Cluster
nodeAffinity := config.Services[i].Clusters[j].Node
Expand Down
244 changes: 244 additions & 0 deletions generator/src/pkg/generate/validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
/*
Copyright 2023 Telefonaktiebolaget LM Ericsson AB
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 generate

import (
"application-generator/src/pkg/model"
s "application-generator/src/pkg/service"

"errors"
"fmt"

"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/util/validation"
)

// Occurrences returns the number of occurrences of every value in a slice of strings
func Occurrences(strSlice []string) map[string]int {
occurrences := make(map[string]int)
for _, entry := range strSlice {
occurrences[entry]++
}
return occurrences
}

// Validates service and endpoint names in JSON config
func ValidateNames(config *model.FileConfig) error {
serviceNames := []string{}

// Validate service names (RFC 1035 DNS Label)
for _, service := range config.Services {
errs := validation.IsDNS1035Label(service.Name)

// There can be several conformance errors but only one is returned by this function
// If the user fixes one error, the next error will be shown when running the generator again
if len(errs) > 0 {
return fmt.Errorf("Service '%s' has invalid name: %s", service.Name, errs[0])
}

serviceNames = append(serviceNames, service.Name)
}

serviceNameOccurrences := Occurrences(serviceNames)

for _, service := range config.Services {
// Duplicate name found
if serviceNameOccurrences[service.Name] > 1 {
return fmt.Errorf("Duplicate service name '%s'", service.Name)
}

endpointNames := []string{}

// Validate endpoint names (RFC 1123 DNS Subdomain)
for _, endpoint := range service.Endpoints {
errs := validation.IsDNS1123Subdomain(endpoint.Name)

if len(errs) > 0 {
return fmt.Errorf("Endpoint '%s' has invalid name: %s", endpoint.Name, errs[0])
}

if endpoint.NetworkComplexity != nil {
for _, calledService := range endpoint.NetworkComplexity.CalledServices {
errs = validation.IsDNS1035Label(calledService.Service)

if len(errs) > 0 {
return fmt.Errorf("Call from endpoint '%s' to invalid service '%s': %s", endpoint.Name, calledService.Service, errs[0])
}

errs = validation.IsDNS1123Subdomain(calledService.Endpoint)

if len(errs) > 0 {
return fmt.Errorf("Call from endpoint '%s' to invalid endpoint '%s': %s", endpoint.Name, calledService.Endpoint, errs[0])
}
}
}

endpointNames = append(endpointNames, endpoint.Name)
}

endpointNameOccurrences := Occurrences(serviceNames)

for _, endpoint := range service.Endpoints {
// Duplicate name found
if endpointNameOccurrences[endpoint.Name] > 1 {
return fmt.Errorf("Duplicate endpoint '%s' in service '%s'", endpoint.Name, service.Name)
}
}
}

return nil
}

// Validates resource limits in input JSON
func ValidateResources(config *model.FileConfig) error {
for _, service := range config.Services {
limits := []string{
service.Resources.Limits.Cpu,
service.Resources.Limits.Memory,
service.Resources.Requests.Cpu,
service.Resources.Requests.Memory,
}

for _, limit := range limits {
quantity, err := resource.ParseQuantity(limit)

if err != nil {
return fmt.Errorf("Invalid resource allocation '%s': %s", limit, err)
}

// TODO: Max limits
if quantity.Sign() != 1 {
return fmt.Errorf("Resource allocation '%s' too low", limit)
}
}
}

return nil
}

// Validate that protocols are set in both endpoint definition and call
func ValidateProtocols(service *model.Service, endpoint *model.Endpoint) error {
validProtocols := map[string]bool{"http": true, "grpc": true}

if !validProtocols[endpoint.Protocol] {
return fmt.Errorf("Endpoint '%s' in service '%s' has invalid protocol '%s'",
endpoint.Name, service.Name, endpoint.Protocol)
}

if endpoint.NetworkComplexity != nil {
for _, calledService := range endpoint.NetworkComplexity.CalledServices {
if !validProtocols[calledService.Protocol] {
return fmt.Errorf("Call to endpoint '%s' from endpoint '%s' has invalid protocol '%s'",
calledService.Endpoint, endpoint.Name, calledService.Protocol)
}
}
}

return nil
}

// Validate that input JSON contains required parameters
func ValidateRequiredParameters(config *model.FileConfig) error {
if len(config.Services) == 0 {
return errors.New("At least one service is required")
}

for _, service := range config.Services {
if len(service.Clusters) == 0 {
return fmt.Errorf("Service '%s' needs to be deployed on at least one cluster", service.Name)
}

if len(service.Endpoints) == 0 {
return fmt.Errorf("At least one endpoint is required in service '%s'", service.Name)
} else {
for _, endpoint := range service.Endpoints {
err := ValidateProtocols(&service, &endpoint)

if err != nil {
return err
}
}
}
}

return nil
}

// Validates an input JSON config provided by the user
func ValidateFileConfig(config *model.FileConfig) error {
if err := ValidateRequiredParameters(config); err != nil {
return err
}

if err := ValidateNames(config); err != nil {
return err
}

if err := ValidateResources(config); err != nil {
return err
}

return nil
}

// Applies default values to input JSON
func ApplyDefaults(config *model.FileConfig) {
for i, _ := range config.Services {
service := &config.Services[i]

if service.Resources.Limits.Cpu == "" {
service.Resources.Limits.Cpu = s.LimitsCPUDefault
}
if service.Resources.Requests.Cpu == "" {
service.Resources.Requests.Cpu = s.RequestsCPUDefault
}
if service.Resources.Limits.Memory == "" {
service.Resources.Limits.Memory = s.LimitsMemoryDefault
}
if service.Resources.Requests.Memory == "" {
service.Resources.Requests.Memory = s.RequestsMemoryDefault
}

if service.Processes <= 0 {
service.Processes = s.SvcProcessesDefault
}
if service.Threads <= 0 {
service.Threads = s.SvcThreadsDefault
}

if service.ReadinessProbe <= 0 {
service.ReadinessProbe = s.SvcReadinessProbeDefault
}

for j, _ := range service.Clusters {
cluster := &service.Clusters[j]

if cluster.Namespace == "" {
cluster.Namespace = s.ClusterNamespaceDefault
}
}

for k, _ := range service.Endpoints {
endpoint := &service.Endpoints[k]

if endpoint.NetworkComplexity != nil && endpoint.NetworkComplexity.CalledServices == nil {
// json.Marshal returns null for a nil slice
endpoint.NetworkComplexity.CalledServices = []model.CalledService{}
}
}
}
}
2 changes: 1 addition & 1 deletion generator/src/pkg/model/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type DeploymentInstance struct {
Cluster string `yaml:"version"`
} `yaml:"matchLabels"`
} `yaml:"selector"`
Replicas int `yaml:"replicas"`
Replicas int `yaml:"replicas,omitempty"`
Template struct {
Metadata struct {
Labels struct {
Expand Down
Loading

0 comments on commit f2d2da3

Please sign in to comment.