github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/vm_unit_test.go (about) 1 //go:build vm || unit || ALL 2 3 /* 4 * Copyright 2019 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. 5 */ 6 7 package govcd 8 9 import ( 10 "reflect" 11 "testing" 12 13 "github.com/vmware/go-vcloud-director/v2/types/v56" 14 ) 15 16 func init() { 17 testingTags["unit"] = "vm_unit_test.go" 18 } 19 20 // Test_updateNicParameters_multinic is meant to check functionality of a complicated 21 // code structure used in vm.ChangeNetworkConfig which is abstracted into 22 // vm.updateNicParameters() method so that it does not contain any API calls, but 23 // only adjust the object which is meant to be sent to API. Initially we hit a bug 24 // which occurred only when API returned NICs in random order. 25 func Test_VMupdateNicParameters_multiNIC(t *testing.T) { 26 27 // Mock VM struct 28 c := Client{} 29 vm := NewVM(&c) 30 31 // Sample config which is rendered by .tf schema parsed 32 tfCfg := []map[string]interface{}{ 33 map[string]interface{}{ 34 "network_name": "multinic-net", 35 "ip_allocation_mode": "POOL", 36 "ip": "", 37 "is_primary": false, 38 }, 39 map[string]interface{}{ 40 "network_name": "multinic-net", 41 "ip_allocation_mode": "DHCP", 42 "ip": "", 43 "is_primary": true, 44 }, 45 map[string]interface{}{ 46 "ip_allocation_mode": "NONE", 47 }, 48 map[string]interface{}{ 49 "network_name": "multinic-net2", 50 "ip_allocation_mode": "MANUAL", 51 "ip": "1.1.1.1", 52 "is_primary": false, 53 }, 54 } 55 56 // A sample NetworkConnectionSection object simulating API returning ordered list 57 vcdConfig := types.NetworkConnectionSection{ 58 PrimaryNetworkConnectionIndex: 0, 59 NetworkConnection: []*types.NetworkConnection{ 60 &types.NetworkConnection{ 61 Network: "multinic-net", 62 NetworkConnectionIndex: 0, 63 IPAddress: "", 64 IsConnected: true, 65 MACAddress: "00:00:00:00:00:00", 66 IPAddressAllocationMode: "POOL", 67 NetworkAdapterType: "VMXNET3", 68 }, 69 &types.NetworkConnection{ 70 Network: "multinic-net", 71 NetworkConnectionIndex: 1, 72 IPAddress: "", 73 IsConnected: true, 74 MACAddress: "00:00:00:00:00:01", 75 IPAddressAllocationMode: "POOL", 76 NetworkAdapterType: "VMXNET3", 77 }, 78 &types.NetworkConnection{ 79 Network: "multinic-net", 80 NetworkConnectionIndex: 2, 81 IPAddress: "", 82 IsConnected: true, 83 MACAddress: "00:00:00:00:00:02", 84 IPAddressAllocationMode: "POOL", 85 NetworkAdapterType: "VMXNET3", 86 }, 87 &types.NetworkConnection{ 88 Network: "multinic-net", 89 NetworkConnectionIndex: 3, 90 IPAddress: "", 91 IsConnected: true, 92 MACAddress: "00:00:00:00:00:03", 93 IPAddressAllocationMode: "POOL", 94 NetworkAdapterType: "VMXNET3", 95 }, 96 }, 97 } 98 99 // NIC configuration when API returns an ordered list 100 vcdCfg := &vcdConfig 101 err := vm.updateNicParameters(tfCfg, vcdCfg) 102 if err != nil { 103 t.Error(err) 104 } 105 106 // Test NIC updates when API returns an unordered list 107 // Swap two &types.NetworkConnection so that it is not ordered correctly 108 vcdConfig2 := vcdConfig 109 vcdConfig2.NetworkConnection[2], vcdConfig2.NetworkConnection[0] = vcdConfig2.NetworkConnection[0], vcdConfig2.NetworkConnection[2] 110 vcdCfg2 := &vcdConfig2 111 err = vm.updateNicParameters(tfCfg, vcdCfg2) 112 if err != nil { 113 t.Error(err) 114 } 115 116 var tableTests = []struct { 117 title string 118 tfConfig []map[string]interface{} 119 vcdConfig *types.NetworkConnectionSection 120 }{ 121 {"Ordered NICs list", tfCfg, vcdCfg}, 122 {"Unordered NIC list", tfCfg, vcdCfg2}, 123 } 124 125 for _, tableTest := range tableTests { 126 t.Run(tableTest.title, func(t *testing.T) { 127 // Check that primary interface is reset to 1 as hardcoded in tfCfg "is_primary" parameter 128 if vcdCfg.PrimaryNetworkConnectionIndex != 1 { 129 t.Errorf("PrimaryNetworkConnectionIndex expected: 1, got: %d", vcdCfg.PrimaryNetworkConnectionIndex) 130 } 131 132 for loopIndex := range tableTest.vcdConfig.NetworkConnection { 133 vcdNic := tableTest.vcdConfig.NetworkConnection[loopIndex] 134 vcdNicSlot := vcdNic.NetworkConnectionIndex 135 tfNic := tableTest.tfConfig[vcdNicSlot] 136 137 if vcdNic.IPAddressAllocationMode != tfNic["ip_allocation_mode"].(string) { 138 t.Errorf("IPAddressAllocationMode expected: %s, got: %s", tfNic["ip_allocation_mode"].(string), vcdNic.IPAddressAllocationMode) 139 } 140 141 if vcdNic.IPAddressAllocationMode != tfNic["ip_allocation_mode"].(string) { 142 t.Errorf("IPAddressAllocationMode expected: %s, got: %s", tfNic["ip_allocation_mode"].(string), vcdNic.IPAddressAllocationMode) 143 } 144 145 if vcdNic.IsConnected != true { 146 t.Errorf("IsConnected expected: true, got: %t", vcdNic.IsConnected) 147 } 148 149 if vcdNic.NeedsCustomization != true { 150 t.Errorf("NeedsCustomization expected: true, got: %t", vcdNic.NeedsCustomization) 151 } 152 153 if vcdNic.IPAddressAllocationMode != types.IPAllocationModeNone { 154 if vcdNic.Network != tfNic["network_name"].(string) { 155 t.Errorf("Network expected: %s, got: %s", tfNic["network_name"].(string), vcdNic.Network) 156 } 157 } else { 158 if vcdNic.Network != "none" { 159 t.Errorf("Network expected: none, got: %s", vcdNic.Network) 160 } 161 } 162 } 163 }) 164 } 165 } 166 167 // // Test_updateNicParameters_singleNIC is meant to check functionality when single NIC 168 // // is being configured and meant to check functionality so that the function is able 169 // // to cover legacy scenarios when Terraform provider was able to create single IP only. 170 // // TODO v3.0 this test should become irrelevant once `ip` and `network_name` parameters are removed. 171 func Test_VMupdateNicParameters_singleNIC(t *testing.T) { 172 // Mock VM struct 173 c := Client{} 174 vm := NewVM(&c) 175 176 tfCfgDHCP := []map[string]interface{}{ 177 map[string]interface{}{ 178 "network_name": "multinic-net", 179 "ip": "dhcp", 180 }, 181 } 182 183 tfCfgAllocated := []map[string]interface{}{ 184 map[string]interface{}{ 185 "network_name": "multinic-net", 186 "ip": "allocated", 187 }, 188 } 189 190 tfCfgNone := []map[string]interface{}{ 191 map[string]interface{}{ 192 "network_name": "multinic-net", 193 "ip": "none", 194 }, 195 } 196 197 tfCfgManual := []map[string]interface{}{ 198 map[string]interface{}{ 199 "network_name": "multinic-net", 200 "ip": "1.1.1.1", 201 }, 202 } 203 204 tfCfgInvalidIp := []map[string]interface{}{ 205 map[string]interface{}{ 206 "network_name": "multinic-net", 207 "ip": "invalidIp", 208 }, 209 } 210 211 tfCfgNoNetworkName := []map[string]interface{}{ 212 map[string]interface{}{ 213 "ip": "invalidIp", 214 }, 215 } 216 217 vcdConfig := types.NetworkConnectionSection{ 218 PrimaryNetworkConnectionIndex: 1, 219 NetworkConnection: []*types.NetworkConnection{ 220 &types.NetworkConnection{ 221 Network: "singlenic-net", 222 NetworkConnectionIndex: 0, 223 IPAddress: "", 224 IsConnected: true, 225 MACAddress: "00:00:00:00:00:00", 226 IPAddressAllocationMode: "POOL", 227 NetworkAdapterType: "VMXNET3", 228 }, 229 }, 230 } 231 232 var tableTests = []struct { 233 title string 234 tfConfig []map[string]interface{} 235 expectedIPAddressAllocationMode string 236 expectedIPAddress string 237 mustNotError bool 238 }{ 239 {"IPAllocationModeDHCP", tfCfgDHCP, types.IPAllocationModeDHCP, "Any", true}, 240 {"IPAllocationModePool", tfCfgAllocated, types.IPAllocationModePool, "Any", true}, 241 {"IPAllocationModeNone", tfCfgNone, types.IPAllocationModeNone, "Any", true}, 242 {"IPAllocationModeManual", tfCfgManual, types.IPAllocationModeManual, tfCfgManual[0]["ip"].(string), true}, 243 {"IPAllocationModeDHCPInvalidIP", tfCfgInvalidIp, types.IPAllocationModeDHCP, "Any", true}, 244 {"ErrNoNetworkName", tfCfgNoNetworkName, types.IPAllocationModeDHCP, "Any", false}, 245 } 246 247 for _, tableTest := range tableTests { 248 249 t.Run(tableTest.title, func(t *testing.T) { 250 vcdCfg := &vcdConfig 251 err := vm.updateNicParameters(tableTest.tfConfig, vcdCfg) // Execute parsing procedure 252 253 // if we got an error which was expected abandon the subtest 254 if err != nil && tableTest.mustNotError { 255 t.Errorf("unexpected error got: %s", err) 256 return 257 } 258 259 if vcdCfg.PrimaryNetworkConnectionIndex != 0 { 260 t.Errorf("PrimaryNetworkConnectionIndex expected: 0, got: %d", vcdCfg.PrimaryNetworkConnectionIndex) 261 } 262 263 if vcdCfg.NetworkConnection[0].IPAddressAllocationMode != tableTest.expectedIPAddressAllocationMode { 264 t.Errorf("IPAddressAllocationMode expected: %s, got: %s", tableTest.expectedIPAddressAllocationMode, vcdCfg.NetworkConnection[0].IPAddressAllocationMode) 265 } 266 267 if vcdCfg.NetworkConnection[0].IPAddress != tableTest.expectedIPAddress { 268 t.Errorf("IPAddress expected: %s, got: %s", tableTest.expectedIPAddress, vcdCfg.NetworkConnection[0].IPAddress) 269 } 270 }) 271 272 } 273 } 274 275 // TestProductSectionList_SortByPropertyKeyName validates that a 276 // SortByPropertyKeyName() works on ProductSectionList and can handle empty properties as well as 277 // sort correctly 278 func TestProductSectionList_SortByPropertyKeyName(t *testing.T) { 279 sliceProductSection := &types.ProductSectionList{ 280 ProductSection: &types.ProductSection{}, 281 } 282 283 emptyProductSection := &types.ProductSectionList{ 284 ProductSection: &types.ProductSection{ 285 Info: "Custom properties", 286 }, 287 } 288 289 // unordered list for test 290 sortOrder := &types.ProductSectionList{ 291 ProductSection: &types.ProductSection{ 292 Info: "Custom properties", 293 Property: []*types.Property{ 294 &types.Property{ 295 UserConfigurable: false, 296 Key: "sys_owner", 297 Label: "sys_owner_label", 298 Type: "string", 299 DefaultValue: "sys_owner_default", 300 Value: &types.Value{Value: "test"}, 301 }, 302 &types.Property{ 303 UserConfigurable: true, 304 Key: "asset_tag", 305 Label: "asset_tag_label", 306 Type: "string", 307 DefaultValue: "asset_tag_default", 308 Value: &types.Value{Value: "xxxyyy"}, 309 }, 310 &types.Property{ 311 UserConfigurable: true, 312 Key: "guestinfo.config.bootstrap.ip", 313 Label: "guestinfo.config.bootstrap.ip_label", 314 Type: "string", 315 DefaultValue: "default_ip", 316 Value: &types.Value{Value: "192.168.12.180"}, 317 }, 318 }, 319 }, 320 } 321 // correct state after ordering 322 expectedSortedOrder := &types.ProductSectionList{ 323 ProductSection: &types.ProductSection{ 324 Info: "Custom properties", 325 Property: []*types.Property{ 326 &types.Property{ 327 UserConfigurable: true, 328 Key: "asset_tag", 329 Label: "asset_tag_label", 330 Type: "string", 331 DefaultValue: "asset_tag_default", 332 Value: &types.Value{Value: "xxxyyy"}, 333 }, 334 &types.Property{ 335 UserConfigurable: true, 336 Key: "guestinfo.config.bootstrap.ip", 337 Label: "guestinfo.config.bootstrap.ip_label", 338 Type: "string", 339 DefaultValue: "default_ip", 340 Value: &types.Value{Value: "192.168.12.180"}, 341 }, 342 &types.Property{ 343 UserConfigurable: false, 344 Key: "sys_owner", 345 Label: "sys_owner_label", 346 Type: "string", 347 DefaultValue: "sys_owner_default", 348 Value: &types.Value{Value: "test"}, 349 }, 350 }, 351 }, 352 } 353 354 tests := []struct { 355 name string 356 setValue *types.ProductSectionList 357 expectedValue *types.ProductSectionList 358 }{ 359 {name: "Empty", setValue: emptyProductSection, expectedValue: emptyProductSection}, 360 {name: "Slice", setValue: sliceProductSection, expectedValue: sliceProductSection}, 361 {name: "SortOrder", setValue: sortOrder, expectedValue: expectedSortedOrder}, 362 } 363 for _, tt := range tests { 364 t.Run(tt.name, func(t *testing.T) { 365 p := tt.setValue 366 p.SortByPropertyKeyName() 367 368 if !reflect.DeepEqual(p, tt.expectedValue) { 369 t.Errorf("Objects were not deeply equal: \n%#+v\n, got:\n %#+v\n", tt.expectedValue, p) 370 } 371 372 }) 373 } 374 }