go.ligato.io/vpp-agent/v3@v3.5.0/client/dynamic_config_test.go (about) 1 // Copyright (c) 2020 Pantheon.tech 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at: 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package client_test 16 17 import ( 18 "bytes" 19 "encoding/json" 20 "testing" 21 22 yaml2 "github.com/ghodss/yaml" 23 "github.com/go-errors/errors" 24 "github.com/goccy/go-yaml" 25 . "github.com/onsi/gomega" 26 "google.golang.org/protobuf/encoding/protojson" 27 "google.golang.org/protobuf/proto" 28 29 "go.ligato.io/vpp-agent/v3/client" 30 "go.ligato.io/vpp-agent/v3/pkg/models" 31 testmodel "go.ligato.io/vpp-agent/v3/pkg/models/testdata/proto" 32 "go.ligato.io/vpp-agent/v3/proto/ligato/configurator" 33 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp" 34 interfaces "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" 35 vpp_srv6 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/srv6" 36 ) 37 38 // TestYamlCompatibility test dynamically generated all-in-one configuration proto message to be compatible 39 // with its hardcoded counterpart(configurator.Config). The compatibility refers to the ability to use the same 40 // yaml config file to set the configuration. 41 func TestYamlCompatibility(t *testing.T) { 42 RegisterTestingT(t) 43 44 // fill hardcoded Config with configuration 45 ifaces := []*interfaces.Interface{memIFRed, memIFBlack, loop1, vppTap1} 46 config := &configurator.Config{ 47 VppConfig: &vpp.ConfigData{ 48 Interfaces: ifaces, 49 Srv6Global: srv6Global, 50 }, 51 } 52 // TODO add more configuration to hardcoded version of configuration so it can cover all configuration 53 // possibilities 54 55 // create construction input for dynamic config from locally registered models (only with class "config") 56 // (for remote models use generic client's KnownModels, example of this is in agentctl yaml config 57 // update (commands.runConfigUpdate)) 58 var knownModels []*models.ModelInfo 59 for _, model := range models.RegisteredModels() { 60 if model.Spec().Class == "config" { 61 knownModels = append(knownModels, &models.ModelInfo{ 62 ModelDetail: model.ModelDetail(), 63 MessageDescriptor: model.NewInstance().ProtoReflect().Descriptor(), 64 }) 65 } 66 } 67 68 // create dynamic config 69 // Note: for revealing dynamic structure use fmt.Println(client.ExportDynamicConfigStructure(dynConfig)) 70 dynConfig, err := client.NewDynamicConfig(knownModels) 71 Expect(err).ShouldNot(HaveOccurred(), "can't create dynamic config") 72 73 // Hardcoded Config filled with data -> YAML -> JSON -> load into empty dynamic Config -> YAML 74 yamlFromHardcodedConfig, err := toYAML(config) // should be the same output as agentctl config get 75 Expect(err).ShouldNot(HaveOccurred(), "can't export hardcoded config as yaml (initial export)") 76 bj, err := yaml2.YAMLToJSON([]byte(yamlFromHardcodedConfig)) 77 Expect(err).ShouldNot(HaveOccurred(), "can't convert yaml (from hardcoded config) to json") 78 Expect(protojson.Unmarshal(bj, dynConfig)).To(Succeed(), 79 "can't marshal json data (from hardcoded config) to dynamic config") 80 yamlFromDynConfig, err := toYAML(dynConfig) 81 Expect(err).ShouldNot(HaveOccurred(), "can't export hardcoded config as yaml") 82 83 // final compare of YAML from hardcoded and dynamic config 84 Expect(yamlFromDynConfig).To(BeEquivalentTo(yamlFromHardcodedConfig)) 85 } 86 87 // TestDynamicConfigWithThirdPartyModel tests whether 3rd party model (= model not in hardcoded configurator.Config) 88 // data can be loaded into dynamic config from yaml form 89 func TestDynamicConfigWithThirdPartyModel(t *testing.T) { 90 RegisterTestingT(t) 91 yaml := `modelConfig: 92 Basic_list: 93 - name: testName1 94 - name: testName2 95 ` 96 models.Register(&testmodel.Basic{}, models.Spec{ 97 Module: "model", 98 Type: "basic", 99 Version: "v1", 100 }, models.WithNameTemplate("{{.Name}}")) // contains Name template => as repeated field in dynamic config 101 // Note: no Name template (and no GetName() in generated proto message) => as optional field in 102 // dynamic config (without "_list" suffix and list to single reference in yaml) 103 104 // create construction input for dynamic config 105 var knownModels []*models.ModelInfo 106 for _, model := range models.RegisteredModels() { 107 if model.Spec().Class == "config" && model.Spec().Module == "model" { 108 knownModels = append(knownModels, &models.ModelInfo{ 109 ModelDetail: model.ModelDetail(), 110 MessageDescriptor: model.NewInstance().ProtoReflect().Descriptor(), 111 }) 112 } 113 } 114 115 // create dynamic config 116 // Note: for revealing dynamic sctructure use fmt.Println(client.ExportDynamicConfigStructure(dynConfig)) 117 dynConfig, err := client.NewDynamicConfig(knownModels) 118 Expect(err).ShouldNot(HaveOccurred(), "can't create dynamic config") 119 120 // test loading of 3rd party model data into dynamic config 121 bj2, err := yaml2.YAMLToJSON([]byte(yaml)) 122 Expect(err).ShouldNot(HaveOccurred(), "can't convert yaml to json") 123 Expect(protojson.Unmarshal(bj2, dynConfig)).To(Succeed(), 124 "can't marshal json data to dynamic config") 125 } 126 127 func toYAML(data interface{}) (string, error) { 128 out, err := encodeJson(data, "") 129 if err != nil { 130 return "", errors.Errorf("can't encode to JSON due to: %v", err) 131 } 132 bb, err := jsonToYaml(out) 133 if err != nil { 134 return "", errors.Errorf("can't convert json to yaml due to: %v", err) 135 } 136 return string(bb), nil 137 } 138 139 func encodeJson(data interface{}, ident string) ([]byte, error) { 140 if msg, ok := data.(proto.Message); ok { 141 m := protojson.MarshalOptions{ 142 Indent: ident, 143 } 144 b, err := m.Marshal(msg) 145 if err != nil { 146 return nil, errors.Errorf("can't marshal proto message to json due to: %v", err) 147 } 148 return b, nil 149 } 150 var b bytes.Buffer 151 encoder := json.NewEncoder(&b) 152 encoder.SetIndent("", ident) 153 if err := encoder.Encode(data); err != nil { 154 return nil, errors.Errorf("can't marshal data to json due to: %v", err) 155 } 156 return b.Bytes(), nil 157 } 158 159 func jsonToYaml(j []byte) ([]byte, error) { 160 var jsonObj interface{} 161 err := yaml.UnmarshalWithOptions(j, &jsonObj, yaml.UseOrderedMap()) 162 if err != nil { 163 return nil, err 164 } 165 return yaml.Marshal(jsonObj) 166 } 167 168 // test configuration 169 var ( 170 memIFRed = &interfaces.Interface{ 171 Name: "red", 172 Type: interfaces.Interface_MEMIF, 173 IpAddresses: []string{"100.0.0.1/24"}, 174 Mtu: 9200, 175 Enabled: true, 176 Link: &interfaces.Interface_Memif{ 177 Memif: &interfaces.MemifLink{ 178 Id: 1, 179 Master: false, 180 SocketFilename: "/var/run/memif_k8s-master.sock", 181 }, 182 }, 183 } 184 memIFBlack = &interfaces.Interface{ 185 Name: "black", 186 Type: interfaces.Interface_MEMIF, 187 IpAddresses: []string{"192.168.20.1/24"}, 188 Mtu: 9200, 189 Enabled: true, 190 Link: &interfaces.Interface_Memif{ 191 Memif: &interfaces.MemifLink{ 192 Id: 2, 193 Master: false, 194 SocketFilename: "/var/run/memif_k8s-master.sock", 195 }, 196 }, 197 } 198 loop1 = &interfaces.Interface{ 199 Name: "loop-test-1", 200 Type: interfaces.Interface_SOFTWARE_LOOPBACK, 201 Enabled: true, 202 Mtu: 1500, 203 IpAddresses: []string{"10.10.1.1/24"}, 204 } 205 vppTap1 = &interfaces.Interface{ 206 Name: "vpp-tap1", 207 Type: interfaces.Interface_TAP, 208 Enabled: true, 209 IpAddresses: []string{"10.10.10.1/24"}, 210 Link: &interfaces.Interface_Tap{ 211 Tap: &interfaces.TapLink{ 212 Version: 2, 213 ToMicroservice: "test-microservice1", 214 }, 215 }, 216 } 217 srv6Global = &vpp_srv6.SRv6Global{ 218 EncapSourceAddress: "10.1.1.1", 219 } 220 )