From f0f7ce313f38a7ae841328b4173324e0e9f7cf29 Mon Sep 17 00:00:00 2001 From: Chris Marget Date: Wed, 23 Oct 2024 22:51:33 -0400 Subject: [PATCH] iotas -> enums; raw/polish -> marshal/unmarshal --- .../client_rendered_diff_integration_test.go | 3 +- apstra/enum/enums.go | 32 + apstra/enum/generated_enums.go | 84 ++ apstra/helpers.go | 48 ++ apstra/helpers_test.go | 2 +- apstra/two_stage_l3_clos_client.go | 32 +- ...ty_template_assignment_integration_test.go | 4 +- apstra/two_stage_l3_clos_policies_test.go | 4 +- apstra/two_stage_l3_clos_virtual_networks.go | 793 +++++------------- ..._clos_virtual_networks_integration_test.go | 48 +- ...tage_l3_clos_virtual_networks_unit_test.go | 124 ++- apstra/types.go | 2 - 12 files changed, 531 insertions(+), 645 deletions(-) diff --git a/apstra/client_rendered_diff_integration_test.go b/apstra/client_rendered_diff_integration_test.go index 90a8364..1b32bfd 100644 --- a/apstra/client_rendered_diff_integration_test.go +++ b/apstra/client_rendered_diff_integration_test.go @@ -15,6 +15,7 @@ import ( "sync" "testing" + "github.com/Juniper/apstra-go-sdk/apstra/enum" "github.com/stretchr/testify/require" ) @@ -97,7 +98,7 @@ func TestGetNodeRenderedDiff(t *testing.T) { Label: randString(6, "hex"), SecurityZoneId: szId, VnBindings: vnBindings, - VnType: VnTypeVxlan, + VnType: enum.VnTypeVxlan, }) require.NoError(t, err) t.Logf(vnId.String()) diff --git a/apstra/enum/enums.go b/apstra/enum/enums.go index ebe5459..4b66425 100644 --- a/apstra/enum/enums.go +++ b/apstra/enum/enums.go @@ -195,3 +195,35 @@ var ( RenderedConfigTypeStaging = RenderedConfigType{Value: "staging"} RenderedConfigTypeDeployed = RenderedConfigType{Value: "deployed"} ) + +type SviIpv4Mode oenum.Member[string] + +var ( + SviIpv4ModeDisabled = SviIpv4Mode{Value: "disabled"} + SviIpv4ModeEnabled = SviIpv4Mode{Value: "enabled"} + SviIpv4ModeForced = SviIpv4Mode{Value: "forced"} +) + +type SviIpv6Mode oenum.Member[string] + +var ( + SviIpv6ModeDisabled = SviIpv6Mode{Value: "disabled"} + SviIpv6ModeEnabled = SviIpv6Mode{Value: "enabled"} + SviIpv6ModeForced = SviIpv6Mode{Value: "forced"} + SviIpv6ModeLinkLocal = SviIpv6Mode{Value: "link_local"} +) + +type DhcpServiceMode oenum.Member[string] + +var ( + DhcpServiceModeDisabled = DhcpServiceMode{Value: "dhcpServiceDisabled"} + DhcpServiceModeEnabled = DhcpServiceMode{Value: "dhcpServiceEnabled"} +) + +type VnType oenum.Member[string] + +var ( + VnTypeExternal = VnType{Value: "disabled"} + VnTypeVlan = VnType{Value: "vlan"} + VnTypeVxlan = VnType{Value: "vxlan"} +) diff --git a/apstra/enum/generated_enums.go b/apstra/enum/generated_enums.go index ba615e4..60d3439 100644 --- a/apstra/enum/generated_enums.go +++ b/apstra/enum/generated_enums.go @@ -50,6 +50,20 @@ func (o *DeviceProfileType) FromString(s string) error { return nil } +var _ enum = (*DhcpServiceMode)(nil) + +func (o DhcpServiceMode) String() string { + return o.Value +} + +func (o *DhcpServiceMode) FromString(s string) error { + if DhcpServiceModes.Parse(s) == nil { + return newEnumParseError(o, s) + } + o.Value = s + return nil +} + var _ enum = (*FFResourceType)(nil) func (o FFResourceType) String() string { @@ -260,6 +274,34 @@ func (o *StorageSchemaPath) FromString(s string) error { return nil } +var _ enum = (*SviIpv4Mode)(nil) + +func (o SviIpv4Mode) String() string { + return o.Value +} + +func (o *SviIpv4Mode) FromString(s string) error { + if SviIpv4Modes.Parse(s) == nil { + return newEnumParseError(o, s) + } + o.Value = s + return nil +} + +var _ enum = (*SviIpv6Mode)(nil) + +func (o SviIpv6Mode) String() string { + return o.Value +} + +func (o *SviIpv6Mode) FromString(s string) error { + if SviIpv6Modes.Parse(s) == nil { + return newEnumParseError(o, s) + } + o.Value = s + return nil +} + var _ enum = (*TcpStateQualifier)(nil) func (o TcpStateQualifier) String() string { @@ -274,6 +316,20 @@ func (o *TcpStateQualifier) FromString(s string) error { return nil } +var _ enum = (*VnType)(nil) + +func (o VnType) String() string { + return o.Value +} + +func (o *VnType) FromString(s string) error { + if VnTypes.Parse(s) == nil { + return newEnumParseError(o, s) + } + o.Value = s + return nil +} + var ( _ enum = new(ApiFeature) ApiFeatures = oenum.New( @@ -300,6 +356,12 @@ var ( DeviceProfileTypeMonolithic, ) + _ enum = new(DhcpServiceMode) + DhcpServiceModes = oenum.New( + DhcpServiceModeDisabled, + DhcpServiceModeEnabled, + ) + _ enum = new(FFResourceType) FFResourceTypes = oenum.New( FFResourceTypeAsn, @@ -434,8 +496,30 @@ var ( StorageSchemaPathXcvr, ) + _ enum = new(SviIpv4Mode) + SviIpv4Modes = oenum.New( + SviIpv4ModeDisabled, + SviIpv4ModeEnabled, + SviIpv4ModeForced, + ) + + _ enum = new(SviIpv6Mode) + SviIpv6Modes = oenum.New( + SviIpv6ModeDisabled, + SviIpv6ModeEnabled, + SviIpv6ModeForced, + SviIpv6ModeLinkLocal, + ) + _ enum = new(TcpStateQualifier) TcpStateQualifiers = oenum.New( TcpStateQualifierEstablished, ) + + _ enum = new(VnType) + VnTypes = oenum.New( + VnTypeExternal, + VnTypeVlan, + VnTypeVxlan, + ) ) diff --git a/apstra/helpers.go b/apstra/helpers.go index da9568c..3ebfabe 100644 --- a/apstra/helpers.go +++ b/apstra/helpers.go @@ -116,3 +116,51 @@ func isv6(ip net.IP) bool { } return 16 == len(ip.To16()) } + +// ipFromString is an improvemen ton calling net.ParseIP() directly because it +// handles empty strings gracefully (returns nil net.IP) and because it returns +// errors in case of un-parseable input strings. +func ipFromString(s string) (net.IP, error) { + if s == "" { + return nil, nil + } + + ip := net.ParseIP(s) + if ip == nil { + return nil, fmt.Errorf("cannot parse IP %q", s) + } + + return ip, nil +} + +// ipNetFromString is an improvement on calling net.ParseCIDR() directly because +// it handles empty strings gracefully (returns nil pointer) and because it +// returns a net.IPNet with the actual IP address, rather than the base address. +func ipNetFromString(s string) (*net.IPNet, error) { + if s == "" { + return nil, nil + } + + ip, ipNet, err := net.ParseCIDR(s) + if err != nil { + return nil, fmt.Errorf("while parsing CIDR string %q - %w", s, err) + } + ipNet.IP = ip + + return ipNet, nil +} + +// macFromString is an improvement on calling net.ParseMAC() directly because it +// handles empty strings gracefully (returns nil net.HardwareAddr) +func macFromString(s string) (net.HardwareAddr, error) { + if s == "" { + return nil, nil + } + + mac, err := net.ParseMAC(s) + if err != nil { + return nil, fmt.Errorf("cannot parse hardware address %q", s) + } + + return mac, nil +} diff --git a/apstra/helpers_test.go b/apstra/helpers_test.go index 63d9e70..4d29df2 100644 --- a/apstra/helpers_test.go +++ b/apstra/helpers_test.go @@ -846,7 +846,7 @@ func testVirtualNetwork(t testing.TB, ctx context.Context, bp *TwoStageL3ClosCli SecurityZoneId: szId, VirtualGatewayIpv4Enabled: true, VnBindings: vnBindings, - VnType: VnTypeVxlan, + VnType: enum.VnTypeVxlan, }) require.NoError(t, err) diff --git a/apstra/two_stage_l3_clos_client.go b/apstra/two_stage_l3_clos_client.go index 281d5f5..657b5b3 100644 --- a/apstra/two_stage_l3_clos_client.go +++ b/apstra/two_stage_l3_clos_client.go @@ -344,7 +344,7 @@ func (o *TwoStageL3ClosClient) DeletePolicyRuleById(ctx context.Context, policyI // CreateVirtualNetwork creates a new virtual network according to the supplied VirtualNetworkData func (o *TwoStageL3ClosClient) CreateVirtualNetwork(ctx context.Context, in *VirtualNetworkData) (ObjectId, error) { - return o.createVirtualNetwork(ctx, in.raw()) + return o.createVirtualNetwork(ctx, in) } // ListAllVirtualNetworkIds returns []ObjectId representing virtual networks configured in the blueprint @@ -354,20 +354,12 @@ func (o *TwoStageL3ClosClient) ListAllVirtualNetworkIds(ctx context.Context) ([] // GetVirtualNetwork returns *VirtualNetwork representing the given vnId func (o *TwoStageL3ClosClient) GetVirtualNetwork(ctx context.Context, vnId ObjectId) (*VirtualNetwork, error) { - raw, err := o.getVirtualNetwork(ctx, vnId) - if err != nil { - return nil, err - } - return raw.polish() + return o.getVirtualNetwork(ctx, vnId) } // GetVirtualNetworkByName returns *VirtualNetwork representing the given VN name func (o *TwoStageL3ClosClient) GetVirtualNetworkByName(ctx context.Context, name string) (*VirtualNetwork, error) { - raw, err := o.getVirtualNetworkByName(ctx, name) - if err != nil { - return nil, err - } - return raw.polish() + return o.getVirtualNetworkByName(ctx, name) } // GetAllVirtualNetworks return map[ObjectId]VirtualNetwork representing all @@ -375,27 +367,13 @@ func (o *TwoStageL3ClosClient) GetVirtualNetworkByName(ctx context.Context, name // RETURN the SVI information, so each map entry will have a nil slice at it's // Data.SviIps struct element. func (o *TwoStageL3ClosClient) GetAllVirtualNetworks(ctx context.Context) (map[ObjectId]VirtualNetwork, error) { - rawMap, err := o.getAllVirtualNetworks(ctx) - if err != nil { - return nil, err - } - - result := make(map[ObjectId]VirtualNetwork, len(rawMap)) - for id, raw := range rawMap { - polished, err := raw.polish() - if err != nil { - return nil, err - } - result[id] = *polished - } - - return result, nil + return o.getAllVirtualNetworks(ctx) } // UpdateVirtualNetwork updates the virtual network specified by ID using the // VirtualNetworkData and HTTP method PUT. func (o *TwoStageL3ClosClient) UpdateVirtualNetwork(ctx context.Context, id ObjectId, in *VirtualNetworkData) error { - return o.updateVirtualNetwork(ctx, id, in.raw()) + return o.updateVirtualNetwork(ctx, id, in) } // DeleteVirtualNetwork deletes the virtual network specified by id from the diff --git a/apstra/two_stage_l3_clos_connectivity_template_assignment_integration_test.go b/apstra/two_stage_l3_clos_connectivity_template_assignment_integration_test.go index f29b722..077423a 100644 --- a/apstra/two_stage_l3_clos_connectivity_template_assignment_integration_test.go +++ b/apstra/two_stage_l3_clos_connectivity_template_assignment_integration_test.go @@ -11,6 +11,8 @@ import ( "context" "log" "testing" + + "github.com/Juniper/apstra-go-sdk/apstra/enum" ) func compareConnectivityTemplateAssignments(a, b map[ObjectId]bool, applicationPointId ObjectId, t *testing.T) { @@ -100,7 +102,7 @@ func TestAssignClearCtToInterface(t *testing.T) { Label: randString(6, "hex"), SecurityZoneId: szId, VnBindings: bindings, - VnType: VnTypeVxlan, + VnType: enum.VnTypeVxlan, }) if err != nil { t.Fatal(err) diff --git a/apstra/two_stage_l3_clos_policies_test.go b/apstra/two_stage_l3_clos_policies_test.go index 6c8aff0..6c816b7 100644 --- a/apstra/two_stage_l3_clos_policies_test.go +++ b/apstra/two_stage_l3_clos_policies_test.go @@ -278,7 +278,7 @@ func TestCreateDatacenterPolicy(t *testing.T) { Label: "vn_" + strconv.Itoa(i), SecurityZoneId: szId, VnBindings: bindings, - VnType: VnTypeVxlan, + VnType: enum.VnTypeVxlan, }) if err != nil { t.Fatal(err) @@ -399,7 +399,7 @@ func TestAddDeletePolicyRule(t *testing.T) { Label: "vn_" + strconv.Itoa(i), SecurityZoneId: szId, VnBindings: bindings, - VnType: VnTypeVxlan, + VnType: enum.VnTypeVxlan, }) if err != nil { t.Fatal(err) diff --git a/apstra/two_stage_l3_clos_virtual_networks.go b/apstra/two_stage_l3_clos_virtual_networks.go index bd1ea5c..04e2930 100644 --- a/apstra/two_stage_l3_clos_virtual_networks.go +++ b/apstra/two_stage_l3_clos_virtual_networks.go @@ -6,10 +6,13 @@ package apstra import ( "context" + "encoding/json" "fmt" "net" "net/http" "strconv" + + "github.com/Juniper/apstra-go-sdk/apstra/enum" ) const ( @@ -18,319 +21,31 @@ const ( // apiUrlVirtualNetworkById apiUrlVirtualNetworks = apiUrlBlueprintById + apiUrlPathDelim + "virtual-networks" apiUrlVirtualNetworkById = apiUrlVirtualNetworks + apiUrlPathDelim + "%s" - - dhcpServiceDisabled = dhcpServiceMode("dhcpServiceDisabled") - dhcpServiceEnabled = dhcpServiceMode("dhcpServiceEnabled") ) type ( DhcpServiceEnabled bool - dhcpServiceMode string -) - -func (o DhcpServiceEnabled) raw() dhcpServiceMode { - if o { - return dhcpServiceEnabled - } - return dhcpServiceDisabled -} - -func (o dhcpServiceMode) polish() DhcpServiceEnabled { - return o == dhcpServiceEnabled -} - -//const ( -// l3ConnectivityEnabled = l3ConnectivityMode("l3Enabled") -// l3ConnectivityDisabled = l3ConnectivityMode("l3Disabled") -//) -// -//type L3ConnectivityMode bool -//type l3ConnectivityMode string -// -//func (o L3ConnectivityMode) raw() l3ConnectivityMode { -// if o { -// return l3ConnectivityEnabled -// } -// return l3ConnectivityDisabled -//} -// -//func (o l3ConnectivityMode) polish() L3ConnectivityMode { -// return o == l3ConnectivityEnabled -//} - -type ( - SviIpRequirement int - sviIpRequirement string -) - -const ( - SviIpRequirementNone = SviIpRequirement(iota) - SviIpRequirementOptional - SviIpRequirementForbidden - SviIpRequirementMandatory - SviIpRequirementIntentionConflict - SviIpRequirementUnknown = "SVI IP requirement mode '%s' unknown" - - sviIpRequirementNone = sviIpRequirement("") - sviIpRequirementOptional = sviIpRequirement("optional") - sviIpRequirementForbidden = sviIpRequirement("forbidden") - sviIpRequirementMandatory = sviIpRequirement("mandatory") - sviIpRequirementIntentionConflict = sviIpRequirement("intention_conflict") - sviIpRequirementUnknown = "SVI IP requirement mode %d unknown" -) - -func (o SviIpRequirement) String() string { - return string(o.raw()) -} - -func (o SviIpRequirement) int() int { - return int(o) -} - -func (o SviIpRequirement) raw() sviIpRequirement { - switch o { - case SviIpRequirementNone: - return sviIpRequirementNone - case SviIpRequirementOptional: - return sviIpRequirementOptional - case SviIpRequirementForbidden: - return sviIpRequirementForbidden - case SviIpRequirementMandatory: - return sviIpRequirementMandatory - case SviIpRequirementIntentionConflict: - return sviIpRequirementIntentionConflict - default: - return sviIpRequirement(fmt.Sprintf(sviIpRequirementUnknown, o)) - } -} - -func (o sviIpRequirement) string() string { - return string(o) -} - -func (o sviIpRequirement) parse() (int, error) { - switch o { - case sviIpRequirementNone: - return int(SviIpRequirementNone), nil - case sviIpRequirementOptional: - return int(SviIpRequirementOptional), nil - case sviIpRequirementForbidden: - return int(SviIpRequirementForbidden), nil - case sviIpRequirementMandatory: - return int(SviIpRequirementMandatory), nil - case sviIpRequirementIntentionConflict: - return int(SviIpRequirementIntentionConflict), nil - default: - return 0, fmt.Errorf(SviIpRequirementUnknown, o) - } -} - -type ( - Ipv4Mode int - ipv4Mode string -) - -const ( - Ipv4ModeNone = Ipv4Mode(iota) - Ipv4ModeDisabled - Ipv4ModeEnabled - Ipv4ModeForced - Ipv4ModeUnknown = "unknown IPv4 mode '%s'" - - ipv4ModeNone = ipv4Mode("") - ipv4ModeDisabled = ipv4Mode("disabled") - ipv4ModeEnabled = ipv4Mode("enabled") - ipv4ModeForced = ipv4Mode("forced") - ipv4ModeUnknown = "unknown IPv4 mode %d" -) - -func (o Ipv4Mode) String() string { - return string(o.raw()) -} - -func (o Ipv4Mode) int() int { - return int(o) -} - -func (o Ipv4Mode) raw() ipv4Mode { - switch o { - case Ipv4ModeNone: - return ipv4ModeNone - case Ipv4ModeDisabled: - return ipv4ModeDisabled - case Ipv4ModeEnabled: - return ipv4ModeEnabled - case Ipv4ModeForced: - return ipv4ModeForced - default: - return ipv4Mode(fmt.Sprintf(ipv4ModeUnknown, o)) - } -} - -func (o ipv4Mode) string() string { - return string(o) -} - -func (o ipv4Mode) parse() (int, error) { - switch o { - case ipv4ModeNone: - return int(Ipv4ModeNone), nil - case ipv4ModeDisabled: - return int(Ipv4ModeDisabled), nil - case ipv4ModeEnabled: - return int(Ipv4ModeEnabled), nil - case ipv4ModeForced: - return int(Ipv4ModeForced), nil - default: - return 0, fmt.Errorf(Ipv4ModeUnknown, o) - } -} - -type ( - Ipv6Mode int - ipv6Mode string ) -const ( - Ipv6ModeNone = Ipv6Mode(iota) - Ipv6ModeDisabled - Ipv6ModeEnabled - Ipv6ModeForced - Ipv6ModeLinkLocal - Ipv6ModeUnknown = "unknown IPv6 mode '%s'" - - ipv6ModeNone = ipv6Mode("") - ipv6ModeDisabled = ipv6Mode("disabled") - ipv6ModeEnabled = ipv6Mode("enabled") - ipv6ModeForced = ipv6Mode("forced") - ipv6ModeLinkLocal = ipv6Mode("link_local") - ipv6ModeUnknown = "unknown IPv6 mode %d" -) - -func (o Ipv6Mode) String() string { - return string(o.raw()) -} - -func (o Ipv6Mode) int() int { - return int(o) -} +func (o *DhcpServiceEnabled) FromString(s string) error { + var dsm enum.DhcpServiceMode -func (o Ipv6Mode) raw() ipv6Mode { - switch o { - case Ipv6ModeNone: - return ipv6ModeNone - case Ipv6ModeDisabled: - return ipv6ModeDisabled - case Ipv6ModeEnabled: - return ipv6ModeEnabled - case Ipv6ModeLinkLocal: - return ipv6ModeLinkLocal - case Ipv6ModeForced: - return ipv6ModeForced - default: - return ipv6Mode(fmt.Sprintf(ipv6ModeUnknown, o)) - } -} - -func (o ipv6Mode) string() string { - return string(o) -} - -func (o ipv6Mode) parse() (int, error) { - switch o { - case ipv6ModeNone: - return int(Ipv6ModeNone), nil - case ipv6ModeDisabled: - return int(Ipv6ModeDisabled), nil - case ipv6ModeEnabled: - return int(Ipv6ModeEnabled), nil - case ipv6ModeLinkLocal: - return int(Ipv6ModeLinkLocal), nil - case ipv6ModeForced: - return int(Ipv6ModeForced), nil - default: - return 0, fmt.Errorf(Ipv6ModeUnknown, o) - } -} - -type ( - VnType int - vnType string -) - -const ( - VnTypeExternal = VnType(iota) - VnTypeVlan - VnTypeVxlan - VnTypeUnknown = "unknown VN type '%s'" - - vnTypeExternal = vnType("external") - vnTypeVlan = vnType("vlan") - vnTypeVxlan = vnType("vxlan") - vnTypeUnknown = "unknown VN type '%d'" -) - -func (o VnType) String() string { - return string(o.raw()) -} - -func (o VnType) int() int { - return int(o) -} - -func (o *VnType) FromString(s string) error { - i, err := vnType(s).parse() + err := dsm.FromString(s) if err != nil { - return err + return fmt.Errorf("while parsing dhcp service mode - %w", err) } - *o = VnType(i) - return nil -} -func (o VnType) raw() vnType { - switch o { - case VnTypeExternal: - return vnTypeExternal - case VnTypeVlan: - return vnTypeVlan - case VnTypeVxlan: - return vnTypeVxlan - default: - return vnType(fmt.Sprintf(vnTypeUnknown, o)) - } -} + *o = dsm == enum.DhcpServiceModeEnabled -func (o vnType) string() string { - return string(o) + return nil } -func (o vnType) parse() (int, error) { - switch o { - case vnTypeExternal: - return int(VnTypeExternal), nil - case vnTypeVlan: - return int(VnTypeVlan), nil - case vnTypeVxlan: - return int(VnTypeVxlan), nil - default: - return 0, fmt.Errorf(VnTypeUnknown, o) +func (o DhcpServiceEnabled) String() string { + if o { + return enum.DhcpServiceModeEnabled.String() } -} -// AllVirtualNetworkTypes returns the []VnType representing -// each supported VnType -func AllVirtualNetworkTypes() []VnType { - i := 0 - var result []VnType - for { - var os VnType - err := os.FromString(VnType(i).String()) - if err != nil { - return result[:i] - } - result = append(result, os) - i++ - } + return enum.DhcpServiceModeDisabled.String() } type ( @@ -421,317 +136,282 @@ func (o systemRole) parse() (int, error) { } } +var ( + _ json.Marshaler = (*SviIp)(nil) + _ json.Unmarshaler = (*SviIp)(nil) +) + type SviIp struct { - SystemId ObjectId `json:"system_id"` - Ipv4Addr net.IP `json:"ipv4_addr"` - Ipv4Mode Ipv4Mode `json:"ipv4_mode"` - Ipv4Requirement SviIpRequirement `json:"ipv4_requirement"` - Ipv6Addr net.IP `json:"ipv6_addr"` - Ipv6Mode Ipv6Mode `json:"ipv6_mode"` - Ipv6Requirement SviIpRequirement `json:"ipv6_requirement"` + SystemId ObjectId + Ipv4Addr *net.IPNet + Ipv4Mode enum.SviIpv4Mode + Ipv6Addr *net.IPNet + Ipv6Mode enum.SviIpv6Mode } -func (o *SviIp) raw() *rawSviIp { - var ipv4Addr, ipv6Addr string - if len(o.Ipv4Addr) != 0 { - ipv4Addr = o.Ipv4Addr.String() +func (o *SviIp) MarshalJSON() ([]byte, error) { + var raw struct { + Ipv4Addr string `json:"ipv4_addr,omitempty"` + Ipv4Mode string `json:"ipv4_mode,omitempty"` + Ipv6Addr string `json:"ipv6_addr,omitempty"` + Ipv6Mode string `json:"ipv6_mode,omitempty"` + SystemId ObjectId `json:"system_id"` } - if len(o.Ipv6Addr) != 0 { - ipv6Addr = o.Ipv6Addr.String() + + if o.Ipv4Addr != nil { + raw.Ipv4Addr = o.Ipv4Addr.String() } - return &rawSviIp{ - SystemId: o.SystemId, - Ipv4Addr: ipv4Addr, - Ipv4Mode: o.Ipv4Mode.raw(), - Ipv4Requirement: o.Ipv4Requirement.raw(), - Ipv6Addr: ipv6Addr, - Ipv6Mode: o.Ipv6Mode.raw(), - Ipv6Requirement: o.Ipv6Requirement.raw(), + raw.Ipv4Mode = o.Ipv4Mode.String() + + if o.Ipv6Addr != nil { + raw.Ipv6Addr = o.Ipv6Addr.String() } -} + raw.Ipv6Mode = o.Ipv6Mode.String() + + raw.SystemId = o.SystemId -type rawSviIp struct { - Ipv4Addr string `json:"ipv4_addr,omitempty"` - Ipv4Mode ipv4Mode `json:"ipv4_mode,omitempty"` - Ipv4Requirement sviIpRequirement `json:"ipv4_requirement,omitempty"` // not present in swagger example, not present in GET - Ipv6Addr string `json:"ipv6_addr,omitempty"` - Ipv6Mode ipv6Mode `json:"ipv6_mode,omitempty"` - Ipv6Requirement sviIpRequirement `json:"ipv6_requirement,omitempty"` // not present in swagger example, not present in GET - SystemId ObjectId `json:"system_id"` + return json.Marshal(raw) } -func (o *rawSviIp) parse() (*SviIp, error) { - var ipv4Addr, ipv6Addr net.IP - var err error +func (o *SviIp) UnmarshalJSON(bytes []byte) error { + var raw struct { + Ipv4Addr string `json:"ipv4_addr"` + Ipv4Mode string `json:"ipv4_mode"` + Ipv6Addr string `json:"ipv6_addr"` + Ipv6Mode string `json:"ipv6_mode"` + SystemId ObjectId `json:"system_id"` + } - if o.Ipv4Addr != "" { - ipv4Addr, _, err = net.ParseCIDR(o.Ipv4Addr) - if err != nil { - return nil, err - } + err := json.Unmarshal(bytes, &raw) + if err != nil { + return fmt.Errorf("while unmarshaling an SviIp - %w", err) } - if o.Ipv6Addr != "" { - ipv6Addr, _, err = net.ParseCIDR(o.Ipv6Addr) + o.Ipv4Addr = nil + if raw.Ipv4Addr != "" { + var ip net.IP + ip, o.Ipv4Addr, err = net.ParseCIDR(raw.Ipv4Addr) if err != nil { - return nil, err + return fmt.Errorf("while parsing SviIp.Ipv4Addr - %w", err) } + o.Ipv4Addr.IP = ip } - ipv4mode, err := o.Ipv4Mode.parse() + err = o.Ipv4Mode.FromString(raw.Ipv4Mode) if err != nil { - return nil, err - } - ipv6mode, err := o.Ipv6Mode.parse() - if err != nil { - return nil, err + return fmt.Errorf("while parsing SviIp.Ipv4Mode - %w", err) } - ipv4Requirement, err := o.Ipv4Requirement.parse() - if err != nil { - return nil, err + o.Ipv6Addr = nil + if raw.Ipv6Addr != "" { + var ip net.IP + ip, o.Ipv6Addr, err = net.ParseCIDR(raw.Ipv6Addr) + if err != nil { + return fmt.Errorf("while parsing SviIp.Ipv6Addr - %w", err) + } + o.Ipv6Addr.IP = ip } - ipv6Requirement, err := o.Ipv6Requirement.parse() + + err = o.Ipv6Mode.FromString(raw.Ipv6Mode) if err != nil { - return nil, err + return fmt.Errorf("while parsing SviIp.Ipv6Mode - %w", err) } - return &SviIp{ - SystemId: o.SystemId, - Ipv4Addr: ipv4Addr, - Ipv4Mode: Ipv4Mode(ipv4mode), - Ipv4Requirement: SviIpRequirement(ipv4Requirement), - Ipv6Addr: ipv6Addr, - Ipv6Mode: Ipv6Mode(ipv6mode), - Ipv6Requirement: SviIpRequirement(ipv6Requirement), - }, nil + o.SystemId = raw.SystemId + + return nil } type VnBinding struct { - // AccessSwitches []interface `json:"access_switches"` AccessSwitchNodeIds []ObjectId `json:"access_switch_node_ids"` - // Role string `json:"role"` // so far: "leaf", possibly graphdb "role" element - SystemId ObjectId `json:"system_id"` // graphdb node id of a leaf (so far) switch - VlanId *Vlan `json:"vlan_id"` // optional (auto-assign) - // Selected bool `json:"selected?"` - // Tags []interface `json:"tags"` //sent as empty string by 4.1.2 web UI, not seen in 4.1.0 or 4.1.1 - - //PodData struct { - // Description interface{} `json:"description"` - // GlobalCatalogId interface{} `json:"global_catalog_id"` - // Label string `json:"label"` - // Position int `json:"position"` - // Type string `json:"type"` - // Id string `json:"id"` - //} `json:"pod-data"` + SystemId ObjectId `json:"system_id"` // graphdb node id of a leaf (so far) switch + VlanId *Vlan `json:"vlan_id"` // optional (auto-assign) } +var _ json.Unmarshaler = (*VirtualNetwork)(nil) + type VirtualNetwork struct { Id ObjectId Data *VirtualNetworkData } -type Endpoint struct { - InterfaceId ObjectId `json:"interface_id"` - TagType string `json:"tag_type"` - Label string `json:"label"` -} +func (o *VirtualNetwork) UnmarshalJSON(bytes []byte) error { + var raw struct { + Id ObjectId `json:"id"` + DhcpService string `json:"dhcp_service"` + Ipv4Enabled bool `json:"ipv4_enabled"` + Ipv4Subnet string `json:"ipv4_subnet"` + Ipv6Enabled bool `json:"ipv6_enabled"` + Ipv6Subnet string `json:"ipv6_subnet"` + L3Mtu *int `json:"l3_mtu"` + Label string `json:"label"` + ReservedVlanId *Vlan `json:"reserved_vlan_id"` + RouteTarget string `json:"route_target"` + RtPolicy *RtPolicy `json:"rt_policy"` + SecurityZoneId ObjectId `json:"security_zone_id"` + SviIps []SviIp `json:"svi_ips"` + VirtualGatewayIpv4 string `json:"virtual_gateway_ipv4"` + VirtualGatewayIpv6 string `json:"virtual_gateway_ipv6"` + VirtualGatewayIpv4Enabled bool `json:"virtual_gateway_ipv4_enabled"` + VirtualGatewayIpv6Enabled bool `json:"virtual_gateway_ipv6_enabled"` + VnBindings []VnBinding `json:"bound_to"` + VnId string `json:"vn_id"` + VnType string `json:"vn_type"` + VirtualMac string `json:"virtual_mac"` + } + + err := json.Unmarshal(bytes, &raw) + if err != nil { + return fmt.Errorf("while unmarshaling raw API response - %w", err) + } -type VirtualNetworkData struct { - DhcpService DhcpServiceEnabled - Ipv4Enabled bool - Ipv4Subnet *net.IPNet - Ipv6Enabled bool - Ipv6Subnet *net.IPNet - L3Mtu *int - Label string - ReservedVlanId *Vlan - RouteTarget string - RtPolicy *RtPolicy - SecurityZoneId ObjectId - SviIps []SviIp - VirtualGatewayIpv4 net.IP - VirtualGatewayIpv6 net.IP - VirtualGatewayIpv4Enabled bool - VirtualGatewayIpv6Enabled bool - VnBindings []VnBinding - VnId *VNI - VnType VnType - VirtualMac net.HardwareAddr -} + o.Id = raw.Id + o.Data = new(VirtualNetworkData) -func (o *VirtualNetworkData) raw() *rawVirtualNetwork { - var ipv4Subnet, ipv6Subnet string - if o.Ipv4Subnet != nil { - ipv4Subnet = o.Ipv4Subnet.String() + err = o.Data.DhcpService.FromString(raw.DhcpService) + if err != nil { + return fmt.Errorf("while unmarshaling dhcp_service %q - %w", raw.DhcpService, err) } - if o.Ipv6Subnet != nil { - ipv6Subnet = o.Ipv6Subnet.String() + + o.Data.Ipv4Enabled = raw.Ipv4Enabled + o.Data.Ipv4Subnet, err = ipNetFromString(raw.Ipv4Subnet) + if err != nil { + return fmt.Errorf("while parsing virtual network data ipv4_subnet %q - %w", raw.Ipv4Subnet, err) } - var l3Mtu *int - if o.L3Mtu != nil { - mtu := *o.L3Mtu - l3Mtu = &mtu + o.Data.Ipv6Enabled = raw.Ipv6Enabled + o.Data.Ipv6Subnet, err = ipNetFromString(raw.Ipv6Subnet) + if err != nil { + return fmt.Errorf("while parsing virtual network data ipv6_subnet %q - %w", raw.Ipv6Subnet, err) } - sviIps := make([]rawSviIp, len(o.SviIps)) - for i := range o.SviIps { - sviIps[i] = *o.SviIps[i].raw() + o.Data.L3Mtu = raw.L3Mtu + o.Data.Label = raw.Label + o.Data.ReservedVlanId = raw.ReservedVlanId + o.Data.RouteTarget = raw.RouteTarget + o.Data.RtPolicy = raw.RtPolicy + o.Data.SecurityZoneId = raw.SecurityZoneId + o.Data.SviIps = raw.SviIps + + o.Data.VirtualGatewayIpv4, err = ipFromString(raw.VirtualGatewayIpv4) + if err != nil { + return fmt.Errorf("while parsing virtual network data virtual_gateway_ipv4 %q - %w", raw.VirtualGatewayIpv4, err) } - var virtualGatewayIpv4, virtualGatewayIpv6 string - if len(o.VirtualGatewayIpv4.To4()) == net.IPv4len { - virtualGatewayIpv4 = o.VirtualGatewayIpv4.String() + o.Data.VirtualGatewayIpv6, err = ipFromString(raw.VirtualGatewayIpv6) + if err != nil { + return fmt.Errorf("while parsing virtual network data virtual_gateway_ipv6 %q - %w", raw.VirtualGatewayIpv6, err) } - if len(o.VirtualGatewayIpv6) == net.IPv6len { - virtualGatewayIpv6 = o.VirtualGatewayIpv6.String() + + o.Data.VirtualGatewayIpv4Enabled = raw.VirtualGatewayIpv4Enabled + o.Data.VirtualGatewayIpv6Enabled = raw.VirtualGatewayIpv6Enabled + o.Data.VnBindings = raw.VnBindings + + err = o.Data.VnType.FromString(raw.VnType) + if err != nil { + return fmt.Errorf("while parsing virtual network data vn_type %q - %w", raw.VnType, err) } - var vnId string - if o.VnId != nil { - vnId = strconv.Itoa(int(*o.VnId)) + o.Data.VirtualMac, err = macFromString(raw.VirtualMac) + if err != nil { + return fmt.Errorf("while parsing virtual network data virtual_mac %q - %w", raw.VirtualMac, err) } - return &rawVirtualNetwork{ - DhcpService: o.DhcpService.raw(), + return nil +} + +type Endpoint struct { + InterfaceId ObjectId `json:"interface_id"` + TagType string `json:"tag_type"` + Label string `json:"label"` +} + +var _ json.Marshaler = (*VirtualNetworkData)(nil) + +type VirtualNetworkData struct { + DhcpService DhcpServiceEnabled `json:"dhcp_service"` + Ipv4Enabled bool `json:"ipv4_enabled"` + Ipv4Subnet *net.IPNet `json:"ipv4_subnet,omitempty"` + Ipv6Enabled bool `json:"ipv6_enabled"` + Ipv6Subnet *net.IPNet `json:"ipv6_subnet,omitempty"` + L3Mtu *int `json:"l3_mtu,omitempty"` + Label string `json:"label"` + ReservedVlanId *Vlan `json:"reserved_vlan_id,omitempty"` + RouteTarget string `json:"route_target,omitempty"` + RtPolicy *RtPolicy `json:"rt_policy"` + SecurityZoneId ObjectId `json:"security_zone_id,omitempty"` + SviIps []SviIp `json:"svi_ips"` + VirtualGatewayIpv4 net.IP `json:"virtual_gateway_ipv4,omitempty"` + VirtualGatewayIpv6 net.IP `json:"virtual_gateway_ipv6,omitempty"` + VirtualGatewayIpv4Enabled bool `json:"virtual_gateway_ipv4_enabled"` + VirtualGatewayIpv6Enabled bool `json:"virtual_gateway_ipv6_enabled"` + VnBindings []VnBinding `json:"bound_to"` + VnId *VNI `json:"vn_id,omitempty"` // VNI as a string, null when unset + VnType enum.VnType `json:"vn_type"` + VirtualMac net.HardwareAddr `json:"virtual_mac,omitempty"` +} + +func (o VirtualNetworkData) MarshalJSON() ([]byte, error) { + raw := struct { + DhcpService string `json:"dhcp_service"` + Ipv4Enabled bool `json:"ipv4_enabled"` + Ipv4Subnet string `json:"ipv4_subnet,omitempty"` + Ipv6Enabled bool `json:"ipv6_enabled"` + Ipv6Subnet string `json:"ipv6_subnet,omitempty"` + L3Mtu *int `json:"l3_mtu,omitempty"` + Label string `json:"label"` + ReservedVlanId *Vlan `json:"reserved_vlan_id,omitempty"` + RouteTarget string `json:"route_target,omitempty"` + RtPolicy *RtPolicy `json:"rt_policy"` + SecurityZoneId ObjectId `json:"security_zone_id,omitempty"` + SviIps []SviIp `json:"svi_ips"` + VirtualGatewayIpv4 string `json:"virtual_gateway_ipv4,omitempty"` + VirtualGatewayIpv6 string `json:"virtual_gateway_ipv6,omitempty"` + VirtualGatewayIpv4Enabled bool `json:"virtual_gateway_ipv4_enabled"` + VirtualGatewayIpv6Enabled bool `json:"virtual_gateway_ipv6_enabled"` + VnBindings []VnBinding `json:"bound_to"` + VnId string `json:"vn_id,omitempty"` + VnType string `json:"vn_type"` + VirtualMac string `json:"virtual_mac,omitempty"` + }{ Ipv4Enabled: o.Ipv4Enabled, - Ipv4Subnet: ipv4Subnet, Ipv6Enabled: o.Ipv6Enabled, - Ipv6Subnet: ipv6Subnet, - L3Mtu: l3Mtu, + L3Mtu: o.L3Mtu, Label: o.Label, ReservedVlanId: o.ReservedVlanId, RouteTarget: o.RouteTarget, RtPolicy: o.RtPolicy, SecurityZoneId: o.SecurityZoneId, - SviIps: sviIps, - VirtualGatewayIpv4: virtualGatewayIpv4, - VirtualGatewayIpv6: virtualGatewayIpv6, + SviIps: o.SviIps, VirtualGatewayIpv4Enabled: o.VirtualGatewayIpv4Enabled, VirtualGatewayIpv6Enabled: o.VirtualGatewayIpv6Enabled, VnBindings: o.VnBindings, - VnId: vnId, - VnType: o.VnType.raw(), - VirtualMac: o.VirtualMac.String(), - } -} - -type rawVirtualNetwork struct { - Id ObjectId `json:"id,omitempty"` - DhcpService dhcpServiceMode `json:"dhcp_service"` - Ipv4Enabled bool `json:"ipv4_enabled"` - Ipv4Subnet string `json:"ipv4_subnet,omitempty"` - Ipv6Enabled bool `json:"ipv6_enabled"` - Ipv6Subnet string `json:"ipv6_subnet,omitempty"` - L3Mtu *int `json:"l3_mtu,omitempty"` - Label string `json:"label"` - ReservedVlanId *Vlan `json:"reserved_vlan_id,omitempty"` - RouteTarget string `json:"route_target,omitempty"` // not mentioned in swagger, seen in 4.1.1: "10000:1" - RtPolicy *RtPolicy `json:"rt_policy"` - SecurityZoneId ObjectId `json:"security_zone_id,omitempty"` - SviIps []rawSviIp `json:"svi_ips"` - VirtualGatewayIpv4 string `json:"virtual_gateway_ipv4,omitempty"` - VirtualGatewayIpv6 string `json:"virtual_gateway_ipv6,omitempty"` - VirtualGatewayIpv4Enabled bool `json:"virtual_gateway_ipv4_enabled"` - VirtualGatewayIpv6Enabled bool `json:"virtual_gateway_ipv6_enabled"` - VnBindings []VnBinding `json:"bound_to"` - VnId string `json:"vn_id,omitempty"` // VNI as a string, null when unset - VnType vnType `json:"vn_type"` - VirtualMac string `json:"virtual_mac,omitempty"` - // CreatePolicyTagged bool `json:"create_policy_tagged"` - // CreatePolicyUntagged bool `json:"create_policy_untagged"` - // DefaultEndpointTagTypes interface{} `json:"default_endpoint_tag_types"` // what is this? not present in 4.1.1 api response - // Description string `json:"description"` // not used in the web UI - // FloatingIps []interface{} `json:"floating_ips"` // seen in 4.1.1 api response - // ForceMoveUntaggedEndpoints bool `json:"force_move_untagged_endpoints"` // not used in post/get with web UI - // L3Connectivity *l3ConnectivityMode `json:"l3_connectivity,omitempty"` // does not appear in 4.1.2 swagger - // VniIds []interface{} `json:"vni_ids,omitempty"` // unknown, sent by web UI as empty list - // Endpoints []interface{} `json:"endpoints"` // unknown, maybe relates to servers, etc? -} - -func (o rawVirtualNetwork) polish() (*VirtualNetwork, error) { - var err error - - var ipv4Subnet *net.IPNet - if o.Ipv4Subnet != "" { - _, ipv4Subnet, err = net.ParseCIDR(o.Ipv4Subnet) - if err != nil { - return nil, err - } - } - - var ipv6Subnet *net.IPNet - if o.Ipv6Subnet != "" { - _, ipv6Subnet, err = net.ParseCIDR(o.Ipv6Subnet) - if err != nil { - return nil, err - } } - var l3mtu *int - if o.L3Mtu != nil { - mtu := *o.L3Mtu - l3mtu = &mtu + raw.DhcpService = o.DhcpService.String() + if o.Ipv4Subnet != nil && o.Ipv4Subnet.IP != nil { // todo: handle zero/removal of existing IP address value + raw.Ipv4Subnet = o.Ipv4Subnet.String() } - - sviIps := make([]SviIp, len(o.SviIps)) - for i, sviIp := range o.SviIps { - SviIp, err := sviIp.parse() - if err != nil { - return nil, err - } - sviIps[i] = *SviIp + if o.Ipv6Subnet != nil && o.Ipv6Subnet.IP != nil { // todo: handle zero/removal of existing IP address value + raw.Ipv6Subnet = o.Ipv6Subnet.String() } - - var vnId *VNI - if o.VnId != "" { - vniUint64, err := strconv.ParseUint(o.VnId, 10, 32) - if err != nil { - return nil, fmt.Errorf("error parsing VNID from string %q - %w", o.VnId, err) - } - vni := VNI(uint32(vniUint64)) - vnId = &vni + if len(o.VirtualGatewayIpv4.To4()) == net.IPv4len { // todo: handle zero/removal o fexisting IP address value + raw.VirtualGatewayIpv4 = o.VirtualGatewayIpv4.String() } - - vntype, err := o.VnType.parse() - if err != nil { - return nil, err + if len(o.VirtualGatewayIpv6) == net.IPv6len { // todo: handle zero/removal o fexisting IP address value + raw.VirtualGatewayIpv6 = o.VirtualGatewayIpv6.String() } - - var virtualMac net.HardwareAddr - if o.VirtualMac != "" { - virtualMac, err = net.ParseMAC(o.VirtualMac) + if o.VnId != nil { + err := o.VnId.validate() if err != nil { - return nil, fmt.Errorf("error parsing mac address %q - %w", o.VirtualMac, err) + return nil, fmt.Errorf("while validating VNI value %d - %w", *o.VnId, err) } + raw.VnId = strconv.Itoa(int(*o.VnId)) } + raw.VnType = o.VnType.String() + raw.VirtualMac = o.VirtualMac.String() - return &VirtualNetwork{ - Id: o.Id, - Data: &VirtualNetworkData{ - DhcpService: o.DhcpService.polish(), - Ipv4Enabled: o.Ipv4Enabled, - Ipv4Subnet: ipv4Subnet, - Ipv6Enabled: o.Ipv6Enabled, - Ipv6Subnet: ipv6Subnet, - L3Mtu: l3mtu, - Label: o.Label, - ReservedVlanId: o.ReservedVlanId, - RouteTarget: o.RouteTarget, - RtPolicy: o.RtPolicy, - SecurityZoneId: o.SecurityZoneId, - SviIps: sviIps, - VirtualGatewayIpv4: net.ParseIP(o.VirtualGatewayIpv4), - VirtualGatewayIpv6: net.ParseIP(o.VirtualGatewayIpv6), - VirtualGatewayIpv4Enabled: o.VirtualGatewayIpv4Enabled, - VirtualGatewayIpv6Enabled: o.VirtualGatewayIpv6Enabled, - VnBindings: o.VnBindings, - VnId: vnId, - VnType: VnType(vntype), - VirtualMac: virtualMac, - }, - }, nil + return json.Marshal(raw) } func (o *TwoStageL3ClosClient) listAllVirtualNetworkIds(ctx context.Context) ([]ObjectId, error) { @@ -741,7 +421,7 @@ func (o *TwoStageL3ClosClient) listAllVirtualNetworkIds(ctx context.Context) ([] } response := &struct { - VirtualNetworks map[ObjectId]rawVirtualNetwork `json:"virtual_networks"` + VirtualNetworks map[ObjectId]VirtualNetwork `json:"virtual_networks"` }{} err = o.client.talkToApstra(ctx, &talkToApstraIn{ @@ -762,37 +442,37 @@ func (o *TwoStageL3ClosClient) listAllVirtualNetworkIds(ctx context.Context) ([] return result, nil } -func (o *TwoStageL3ClosClient) getVirtualNetwork(ctx context.Context, vnId ObjectId) (*rawVirtualNetwork, error) { +func (o *TwoStageL3ClosClient) getVirtualNetwork(ctx context.Context, vnId ObjectId) (*VirtualNetwork, error) { apstraUrl, err := o.urlWithParam(fmt.Sprintf(apiUrlVirtualNetworkById, o.blueprintId, vnId)) if err != nil { return nil, err } - response := &rawVirtualNetwork{} + response := VirtualNetwork{} err = o.client.talkToApstra(ctx, &talkToApstraIn{ method: http.MethodGet, url: apstraUrl, - apiResponse: response, + apiResponse: &response, unsynchronized: true, }) if err != nil { return nil, convertTtaeToAceWherePossible(err) } - return response, nil + return &response, nil } -func (o *TwoStageL3ClosClient) getVirtualNetworkByName(ctx context.Context, name string) (*rawVirtualNetwork, error) { +func (o *TwoStageL3ClosClient) getVirtualNetworkByName(ctx context.Context, name string) (*VirtualNetwork, error) { rawVns, err := o.getAllVirtualNetworks(ctx) if err != nil { return nil, err } var found int - var rawVn rawVirtualNetwork + var rawVn VirtualNetwork for i := range rawVns { - if rawVns[i].Label == name { + if rawVns[i].Data.Label == name { found++ rawVn = rawVns[i] } @@ -815,9 +495,9 @@ func (o *TwoStageL3ClosClient) getVirtualNetworkByName(ctx context.Context, name } } -func (o *TwoStageL3ClosClient) getAllVirtualNetworks(ctx context.Context) (map[ObjectId]rawVirtualNetwork, error) { +func (o *TwoStageL3ClosClient) getAllVirtualNetworks(ctx context.Context) (map[ObjectId]VirtualNetwork, error) { var response struct { - VirtualNetworks map[ObjectId]rawVirtualNetwork `json:"virtual_networks"` + VirtualNetworks map[ObjectId]VirtualNetwork `json:"virtual_networks"` } err := o.client.talkToApstra(ctx, &talkToApstraIn{ @@ -832,10 +512,7 @@ func (o *TwoStageL3ClosClient) getAllVirtualNetworks(ctx context.Context) (map[O return response.VirtualNetworks, nil } -func (o *TwoStageL3ClosClient) createVirtualNetwork(ctx context.Context, cfg *rawVirtualNetwork) (ObjectId, error) { - if cfg.Id != "" { - return "", fmt.Errorf("refusing to create virtual network using input data with a populated ID field") - } +func (o *TwoStageL3ClosClient) createVirtualNetwork(ctx context.Context, cfg *VirtualNetworkData) (ObjectId, error) { response := &objectIdResponse{} err := o.client.talkToApstra(ctx, &talkToApstraIn{ method: http.MethodPost, @@ -850,11 +527,7 @@ func (o *TwoStageL3ClosClient) createVirtualNetwork(ctx context.Context, cfg *ra return response.Id, nil } -func (o *TwoStageL3ClosClient) updateVirtualNetwork(ctx context.Context, id ObjectId, cfg *rawVirtualNetwork) error { - if cfg.Id != "" { - return fmt.Errorf("refusing to update virtual network using input data with a populated ID field") - } - +func (o *TwoStageL3ClosClient) updateVirtualNetwork(ctx context.Context, id ObjectId, cfg *VirtualNetworkData) error { err := o.client.talkToApstra(ctx, &talkToApstraIn{ method: http.MethodPut, urlStr: fmt.Sprintf(apiUrlVirtualNetworkById, o.blueprintId, id), diff --git a/apstra/two_stage_l3_clos_virtual_networks_integration_test.go b/apstra/two_stage_l3_clos_virtual_networks_integration_test.go index b3f222e..ca944a1 100644 --- a/apstra/two_stage_l3_clos_virtual_networks_integration_test.go +++ b/apstra/two_stage_l3_clos_virtual_networks_integration_test.go @@ -3,7 +3,6 @@ // SPDX-License-Identifier: Apache-2.0 //go:build integration -// +build integration package apstra @@ -13,6 +12,9 @@ import ( "log" "math/rand" "testing" + + "github.com/Juniper/apstra-go-sdk/apstra/enum" + "github.com/stretchr/testify/require" ) func compareRtPolicy(t *testing.T, a, b *RtPolicy) { @@ -27,32 +29,20 @@ func compareRtPolicy(t *testing.T, a, b *RtPolicy) { } func comapareSviIps(t *testing.T, a, b SviIp) { - if a.SystemId != b.SystemId { - t.Fatalf("SystemId mismatch: %q vs. %q", a.SystemId, b.SystemId) - } - - if !a.Ipv4Addr.Equal(b.Ipv4Addr) { - t.Fatalf("Ipv4Addr mismatch: %q vs. %q", a.Ipv4Addr.String(), b.Ipv4Addr.String()) - } - - if a.Ipv4Mode != b.Ipv4Mode { - t.Fatalf("Ipv4Mode mismatch: %q vs. %q", a.Ipv4Mode.String(), b.Ipv4Mode.String()) - } - - if a.Ipv4Requirement != b.Ipv4Requirement { - t.Fatalf("Ipv4Requirement mismatch: %q vs. %q", a.Ipv4Requirement.String(), b.Ipv4Requirement.String()) - } - - if !a.Ipv6Addr.Equal(b.Ipv6Addr) { - t.Fatalf("Ipv6Addr mismatch: %q vs. %q", a.Ipv6Addr.String(), b.Ipv6Addr.String()) - } + require.Equal(t, a.SystemId, b.SystemId) - if a.Ipv6Mode != b.Ipv6Mode { - t.Fatalf("Ipv6Mode mismatch: %q vs. %q", a.Ipv6Mode.String(), b.Ipv6Mode.String()) + require.Equal(t, a.Ipv4Mode, b.Ipv4Mode) + if a.Ipv4Addr != nil || b.Ipv4Addr != nil { + require.NotNil(t, a.Ipv4Addr) + require.NotNil(t, b.Ipv4Addr) + require.Equal(t, a.Ipv4Addr.String(), b.Ipv4Addr.String()) } - if a.Ipv6Requirement != b.Ipv6Requirement { - t.Fatalf("Ipv6Requirement mismatch: %q vs. %q", a.Ipv6Requirement.String(), b.Ipv6Requirement.String()) + require.Equal(t, a.Ipv6Mode, b.Ipv6Mode) + if a.Ipv6Addr != nil || b.Ipv6Addr != nil { + require.NotNil(t, a.Ipv6Addr) + require.NotNil(t, b.Ipv6Addr) + require.Equal(t, a.Ipv6Addr.String(), b.Ipv6Addr.String()) } } @@ -94,7 +84,7 @@ func compareVnBindingSlices(t *testing.T, a, b []VnBinding, strict bool) { func compareVirtualNetworkData(t *testing.T, a, b *VirtualNetworkData, strict bool) { if a.DhcpService != b.DhcpService { - t.Fatalf("DhcpService mismatch: %q vs. %q", a.DhcpService.raw(), b.DhcpService.raw()) + t.Fatalf("DhcpService mismatch: %q vs. %q", a.DhcpService.String(), b.DhcpService.String()) } if a.Ipv4Enabled != b.Ipv4Enabled { @@ -174,7 +164,7 @@ func compareVirtualNetworkData(t *testing.T, a, b *VirtualNetworkData, strict bo } if a.VnType != b.VnType { - t.Fatalf("VnType mismatch: %q vs. %q", a.VnType.String(), b.VnType.String()) + t.Fatalf("VnType mismatch: %q vs. %q", a.VnType, b.VnType) } if a.VirtualMac.String() != b.VirtualMac.String() { @@ -239,8 +229,8 @@ func TestCreateUpdateDeleteVirtualNetwork(t *testing.T) { leafId := ObjectId(result.Items[i].System.SystemId) sviIps[i] = SviIp{ SystemId: leafId, - Ipv4Mode: Ipv4ModeEnabled, - Ipv6Mode: Ipv6ModeDisabled, + Ipv4Mode: enum.SviIpv4ModeEnabled, + Ipv6Mode: enum.SviIpv6ModeDisabled, } vnBindings[i] = VnBinding{ SystemId: leafId, @@ -257,7 +247,7 @@ func TestCreateUpdateDeleteVirtualNetwork(t *testing.T) { SviIps: sviIps[:1], VirtualGatewayIpv4Enabled: true, VnBindings: vnBindings[:1], - VnType: VnTypeVxlan, + VnType: enum.VnTypeVxlan, } log.Printf("testing CreateVirtualNetwork() against %s %s (%s)", client.clientType, clientName, client.client.ApiVersion()) diff --git a/apstra/two_stage_l3_clos_virtual_networks_unit_test.go b/apstra/two_stage_l3_clos_virtual_networks_unit_test.go index b0295c0..cb28024 100644 --- a/apstra/two_stage_l3_clos_virtual_networks_unit_test.go +++ b/apstra/two_stage_l3_clos_virtual_networks_unit_test.go @@ -3,12 +3,16 @@ // SPDX-License-Identifier: Apache-2.0 //go:build integration -// +build integration package apstra import ( + "encoding/json" + "net" "testing" + + "github.com/Juniper/apstra-go-sdk/apstra/enum" + "github.com/stretchr/testify/require" ) func TestTwoStageL3ClosVirtualNetworkStrings(t *testing.T) { @@ -28,27 +32,6 @@ func TestTwoStageL3ClosVirtualNetworkStrings(t *testing.T) { stringType apiIotaString } testData := []stringTestData{ - {stringVal: "", intType: SviIpRequirementNone, stringType: sviIpRequirementNone}, - {stringVal: "optional", intType: SviIpRequirementOptional, stringType: sviIpRequirementOptional}, - {stringVal: "forbidden", intType: SviIpRequirementForbidden, stringType: sviIpRequirementForbidden}, - {stringVal: "mandatory", intType: SviIpRequirementMandatory, stringType: sviIpRequirementMandatory}, - {stringVal: "intention_conflict", intType: SviIpRequirementIntentionConflict, stringType: sviIpRequirementIntentionConflict}, - - {stringVal: "", intType: Ipv4ModeNone, stringType: ipv4ModeNone}, - {stringVal: "disabled", intType: Ipv4ModeDisabled, stringType: ipv4ModeDisabled}, - {stringVal: "enabled", intType: Ipv4ModeEnabled, stringType: ipv4ModeEnabled}, - {stringVal: "forced", intType: Ipv4ModeForced, stringType: ipv4ModeForced}, - - {stringVal: "", intType: Ipv6ModeNone, stringType: ipv6ModeNone}, - {stringVal: "disabled", intType: Ipv6ModeDisabled, stringType: ipv6ModeDisabled}, - {stringVal: "enabled", intType: Ipv6ModeEnabled, stringType: ipv6ModeEnabled}, - {stringVal: "forced", intType: Ipv6ModeForced, stringType: ipv6ModeForced}, - {stringVal: "link_local", intType: Ipv6ModeLinkLocal, stringType: ipv6ModeLinkLocal}, - - {stringVal: "vlan", intType: VnTypeVlan, stringType: vnTypeVlan}, - {stringVal: "vxlan", intType: VnTypeVxlan, stringType: vnTypeVxlan}, - {stringVal: "external", intType: VnTypeExternal, stringType: vnTypeExternal}, - {stringVal: "", intType: SystemRoleNone, stringType: systemRoleNone}, {stringVal: "access", intType: SystemRoleAccess, stringType: systemRoleAccess}, {stringVal: "leaf", intType: SystemRoleLeaf, stringType: systemRoleLeaf}, @@ -70,3 +53,100 @@ func TestTwoStageL3ClosVirtualNetworkStrings(t *testing.T) { } } } + +func TestVirtualNetworkDataMarshalJson(t *testing.T) { + mustParseIpNet := func(t testing.TB, s string) *net.IPNet { + t.Helper() + result, err := ipNetFromString(s) + require.NoError(t, err) + return result + } + + mustParseIp := func(t testing.TB, s string) net.IP { + t.Helper() + result, err := ipFromString(s) + require.NoError(t, err) + return result + } + + type testCase struct { + d VirtualNetworkData + e string + } + + testCases := map[string]testCase{ + "a": { + d: VirtualNetworkData{ + Ipv4Enabled: true, + Ipv4Subnet: mustParseIpNet(t, "192.0.2.0/24"), + Ipv6Enabled: true, + Ipv6Subnet: mustParseIpNet(t, "3fff::/64"), + L3Mtu: toPtr(9000), + Label: "a", + SecurityZoneId: "dtUF3UAr4Cqfuoy6iII", + SviIps: []SviIp{ + { + SystemId: "UJoJhK-jXJkc5Mtarc8", + Ipv4Addr: mustParseIpNet(t, "192.0.2.2/24"), + Ipv4Mode: enum.SviIpv4ModeEnabled, + Ipv6Addr: mustParseIpNet(t, "3fff::2/64"), + Ipv6Mode: enum.SviIpv6ModeEnabled, + }, + }, + VirtualGatewayIpv4: mustParseIp(t, "192.0.2.1"), + VirtualGatewayIpv6: mustParseIp(t, "3fff::1"), + VirtualGatewayIpv4Enabled: true, + VirtualGatewayIpv6Enabled: true, + VnBindings: []VnBinding{ + { + AccessSwitchNodeIds: []ObjectId{}, + SystemId: "UJoJhK-jXJkc5Mtarc8", + VlanId: toPtr(Vlan(3)), + }, + }, + VnType: enum.VnTypeVxlan, + }, + e: `{ + "dhcp_service": "dhcpServiceDisabled", + "ipv4_enabled": true, + "ipv4_subnet": "192.0.2.0/24", + "ipv6_enabled": true, + "ipv6_subnet": "3fff::/64", + "l3_mtu": 9000, + "label": "a", + "rt_policy": null, + "security_zone_id": "dtUF3UAr4Cqfuoy6iII", + "svi_ips": [ + { + "ipv4_addr": "192.0.2.2/24", + "ipv4_mode": "enabled", + "ipv6_addr": "3fff::2/64", + "ipv6_mode": "enabled", + "system_id": "UJoJhK-jXJkc5Mtarc8" + } + ], + "virtual_gateway_ipv4": "192.0.2.1", + "virtual_gateway_ipv6": "3fff::1", + "virtual_gateway_ipv6_enabled": true, + "virtual_gateway_ipv4_enabled": true, + "bound_to": [ + { + "access_switch_node_ids": [], + "system_id": "UJoJhK-jXJkc5Mtarc8", + "vlan_id": 3 + } + ], + "vn_type": "vxlan" + }`, + }, + } + + for tName, tCase := range testCases { + t.Run(tName, func(t *testing.T) { + t.Parallel() + a, err := json.Marshal(tCase.d) + require.NoError(t, err) + require.JSONEq(t, tCase.e, string(a)) + }) + } +} diff --git a/apstra/types.go b/apstra/types.go index 3c8e41f..99347a8 100644 --- a/apstra/types.go +++ b/apstra/types.go @@ -16,7 +16,6 @@ const ( type Vlan uint16 -//lint:ignore U1000 keep for future use func (o Vlan) validate() error { if o < vlanMin || o > vlanMax { return fmt.Errorf("VLAN %d out of range", o) @@ -26,7 +25,6 @@ func (o Vlan) validate() error { type VNI uint32 -//lint:ignore U1000 keep for future use func (o VNI) validate() error { if o < vniMin || o > vniMax { return fmt.Errorf("VNI %d out of range", o)