From 45c50a6632f09a797795164b005baab078658616 Mon Sep 17 00:00:00 2001 From: Allan Liu Date: Tue, 26 Jul 2016 13:25:58 -0400 Subject: [PATCH] dcim{,_related-connections}: added support for endpoint --- AUTHORS | 2 + client_test.go | 76 ++++++++++++++++ dcim.go | 8 ++ dcim_devices.go | 51 +++++++++++ dcim_devices_test.go | 57 ++++++++++++ dcim_related-connections.go | 102 +++++++++++++++++++++ dcim_related-connections_test.go | 152 +++++++++++++++++++++++++++++++ 7 files changed, 448 insertions(+) create mode 100644 dcim_devices.go create mode 100644 dcim_devices_test.go create mode 100644 dcim_related-connections.go create mode 100644 dcim_related-connections_test.go diff --git a/AUTHORS b/AUTHORS index 32386240be..9032852adf 100644 --- a/AUTHORS +++ b/AUTHORS @@ -6,5 +6,7 @@ Original Author --------------- Matt Layher + Contributors ------------ +Allan Liu diff --git a/client_test.go b/client_test.go index 03f13d6f8a..01502c4770 100644 --- a/client_test.go +++ b/client_test.go @@ -179,6 +179,15 @@ func testDeviceIdentifier(n int) *DeviceIdentifier { } } +func testDeviceTypeIdentifier(n int) *DeviceTypeIdentifier { + return &DeviceTypeIdentifier{ + ID: n, + Manufacturer: testSimpleIdentifier(n), + Model: fmt.Sprintf("device model %d", n), + Slug: fmt.Sprintf("devicetype%d", n), + } +} + func testInterfaceIdentifier(n int) *InterfaceIdentifier { return &InterfaceIdentifier{ ID: n, @@ -255,6 +264,65 @@ func testPrefix(family Family, n int) *Prefix { } } +func testRackIdentifier(n int) *RackIdentifier { + return &RackIdentifier{ + ID: n, + Name: fmt.Sprintf("rack %d", n), + FacilityID: fmt.Sprintf("facility%d", n), + DisplayName: fmt.Sprintf("Rack %d", n), + } +} + +func testRCConsolePortIdentifier(n int) *RCConsolePortIdentifier { + return &RCConsolePortIdentifier{ + ConsoleServer: fmt.Sprintf("console server %d", n), + Name: fmt.Sprintf("rc console port %d", n), + Port: fmt.Sprintf("port %d", n), + } +} + +func testRCInterfaceIdentifier(n int) *RCInterfaceIdentifier { + return &RCInterfaceIdentifier{ + Device: fmt.Sprintf("device %d", n), + Interface: fmt.Sprintf("interface %d", n), + Name: fmt.Sprintf("rc interface %d", n), + } +} + +func testRCPowerPortIdentifier(n int) *RCPowerPortIdentifier { + return &RCPowerPortIdentifier{ + PDU: fmt.Sprintf("pdu %d", n), + Name: fmt.Sprintf("rc power port %d", n), + Outlet: fmt.Sprintf("outlet %d", n), + } +} + +func testRelatedConnection(n int) *RelatedConnection { + return &RelatedConnection{ + Device: &Device{ + ID: n, + Name: fmt.Sprintf("device %d", n), + DisplayName: fmt.Sprintf("Device %d", n), + DeviceType: testDeviceTypeIdentifier(n), + DeviceRole: testSimpleIdentifier(n), + Platform: testSimpleIdentifier(n), + Serial: fmt.Sprintf("relatedconnection%d", n), + Rack: testRackIdentifier(n), + Position: n, + Face: n, + ParentDevice: testDeviceIdentifier(n), + Status: true, + PrimaryIP: testIPAddressIdentifier(FamilyIPv4, n), + PrimaryIP4: testIPAddressIdentifier(FamilyIPv4, n), + PrimaryIP6: testIPAddressIdentifier(FamilyIPv6, n), + Comments: "", + }, + ConsolePorts: []*RCConsolePortIdentifier{testRCConsolePortIdentifier(n)}, + Interfaces: []*RCInterfaceIdentifier{testRCInterfaceIdentifier(n)}, + PowerPorts: []*RCPowerPortIdentifier{testRCPowerPortIdentifier(n)}, + } +} + func testRIR(n int) *RIR { return &RIR{ ID: n, @@ -288,6 +356,14 @@ func testRoleIdentifier(n int) *RoleIdentifier { } } +func testSimpleIdentifier(n int) *SimpleIdentifier { + return &SimpleIdentifier{ + ID: n, + Name: fmt.Sprintf("simple %d", n), + Slug: fmt.Sprintf("simple%d", n), + } +} + func testSite(n int) *Site { return &Site{ ID: n, diff --git a/dcim.go b/dcim.go index cbd0e5cdc6..520a5b3138 100644 --- a/dcim.go +++ b/dcim.go @@ -39,3 +39,11 @@ type DeviceIdentifier struct { ID int `json:"id"` Name string `json:"name"` } + +// SimpleIdentifier represents a simple object that consists of only an ID, +// name, and slug. +type SimpleIdentifier struct { + ID int `json:"id"` + Name string `json:"name"` + Slug string `json:"slug"` +} diff --git a/dcim_devices.go b/dcim_devices.go new file mode 100644 index 0000000000..8a25054d48 --- /dev/null +++ b/dcim_devices.go @@ -0,0 +1,51 @@ +// Copyright 2016 The go-netbox Authors. +// +// 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 netbox + +// Device is a network device. +type Device struct { + ID int `json:"id"` + Name string `json:"name"` + DisplayName string `json:"display_name"` + DeviceType *DeviceTypeIdentifier `json:"device_type"` + DeviceRole *SimpleIdentifier `json:"device_role"` + Platform *SimpleIdentifier `json:"platform"` + Serial string `json:"serial"` + Rack *RackIdentifier `json:"rack"` + Position int `json:"position"` + Face int `json:"face"` + ParentDevice *DeviceIdentifier `json:"parent_device"` + Status bool `json:"status"` + PrimaryIP *IPAddressIdentifier `json:"primary_ip"` + PrimaryIP4 *IPAddressIdentifier `json:"primary_ip4"` + PrimaryIP6 *IPAddressIdentifier `json:"primary_ip6"` + Comments string `json:"comments"` +} + +// A DeviceTypeIdentifier indicates the device type of a network device. +type DeviceTypeIdentifier struct { + ID int `json:"id"` + Manufacturer *SimpleIdentifier `json:"manufacturer"` + Model string `json:"model"` + Slug string `json:"slug"` +} + +// RackIdentifier represents a server rack. +type RackIdentifier struct { + ID int `json:"id"` + Name string `json:"name"` + FacilityID string `json:"facility_id"` + DisplayName string `json:"display_name"` +} diff --git a/dcim_devices_test.go b/dcim_devices_test.go new file mode 100644 index 0000000000..df3dc6bbd5 --- /dev/null +++ b/dcim_devices_test.go @@ -0,0 +1,57 @@ +// Copyright 2016 The go-netbox Authors. +// +// 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 netbox + +import "reflect" + +func deviceEqual(a, b *Device) bool { + var obsAB = []struct { + a interface{} + b interface{} + }{ + {a: a.DeviceType, b: b.DeviceType}, + {a: a.DeviceRole, b: b.DeviceRole}, + {a: a.Platform, b: b.Platform}, + {a: a.Rack, b: b.Rack}, + {a: a.ParentDevice, b: b.ParentDevice}, + {a: a.ID, b: b.ID}, + {a: a.Name, b: b.Name}, + {a: a.DisplayName, b: b.DisplayName}, + {a: a.Serial, b: b.Serial}, + {a: a.Position, b: b.Position}, + {a: a.Face, b: b.Face}, + {a: a.Status, b: b.Status}, + {a: a.Comments, b: b.Comments}, + {a: a.PrimaryIP, b: b.PrimaryIP}, + {a: a.PrimaryIP4, b: b.PrimaryIP4}, + {a: a.PrimaryIP6, b: b.PrimaryIP6}, + } + for _, o := range obsAB { + + switch o.a.(type) { + case *IPAddressIdentifier: + i, j := o.a.(*IPAddressIdentifier), o.b.(*IPAddressIdentifier) + if ok := ipAddressIdentifiersEqual(*i, *j); !ok { + return false + } + default: + if !reflect.DeepEqual(o.a, o.b) { + return false + } + } + } + + return true +} diff --git a/dcim_related-connections.go b/dcim_related-connections.go new file mode 100644 index 0000000000..fcd425d395 --- /dev/null +++ b/dcim_related-connections.go @@ -0,0 +1,102 @@ +// Copyright 2016 The go-netbox Authors. +// +// 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 netbox + +import ( + "errors" + "net/http" + "net/url" +) + +// RelatedConnection represents components that have a related peer-device and +// peer-interface. +type RelatedConnection struct { + Device *Device `json:"device"` + ConsolePorts []*RCConsolePortIdentifier `json:"console-ports"` + Interfaces []*RCInterfaceIdentifier `json:"interfaces"` + PowerPorts []*RCPowerPortIdentifier `json:"power-ports"` +} + +// RCConsolePortIdentifier represents a reduced version of a console port. +type RCConsolePortIdentifier struct { + ConsoleServer string `json:"console-server"` + Name string `json:"name"` + Port string `json:"port"` +} + +// RCInterfaceIdentifier represents a reduced version of a device interface. +type RCInterfaceIdentifier struct { + Device string `json:"device"` + Interface string `json:"interface"` + Name string `json:"name"` +} + +// RCPowerPortIdentifier represents a reduced version of a single power port. +type RCPowerPortIdentifier struct { + PDU string `json:"pdu"` + Name string `json:"name"` + Outlet string `json:"outlet"` +} + +// GetRelatedConnections retrieves a RelatedConnection object from NetBox. +func (s *DCIMService) GetRelatedConnections( + peerDevice, peerInterface string, +) (*RelatedConnection, error) { + req, err := s.c.newRequest( + http.MethodGet, + "/api/dcim/related-connections/", + &relatedConnectionsOptions{ + peerDevice, + peerInterface, + }, + ) + if err != nil { + return nil, err + } + + rc := new(RelatedConnection) + err = s.c.do(req, rc) + if err != nil { + return nil, err + } + return rc, nil +} + +// relatedConnectionsOptions is used as an argument for +// Client.DCIM.GetRelatedConnections. +type relatedConnectionsOptions struct { + PeerDevice string + PeerInterface string +} + +func (o *relatedConnectionsOptions) values() (url.Values, error) { + err := errors.New( + "must provide non-zero values for both peer-device and peer-interface", + ) + if o == nil { + return nil, err + } + + if o.PeerDevice == "" || o.PeerInterface == "" { + return nil, err + } + v := url.Values{} + + v.Set("peer-device", o.PeerDevice) + + v.Set("peer-interface", o.PeerInterface) + + return v, nil +} diff --git a/dcim_related-connections_test.go b/dcim_related-connections_test.go new file mode 100644 index 0000000000..c107acba60 --- /dev/null +++ b/dcim_related-connections_test.go @@ -0,0 +1,152 @@ +// Copyright 2016 The go-netbox Authors. +// +// 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 netbox + +import ( + "net/http" + "net/url" + "reflect" + "testing" +) + +func TestGetRelatedConnection(t *testing.T) { + want := testRelatedConnection(1) + + c, done := testClient( + t, + testHandler(t, http.MethodGet, "/api/dcim/related-connections/", want), + ) + defer done() + + got, err := c.DCIM.GetRelatedConnections("device1", "interface1") + if err != nil { + t.Fatalf("unexpected error from Client.DCIM.GetRelatedConnections: %v", err) + } + + if !rcEqual(want, got) { + t.Fatalf( + "unexpected related-connections payload:\n- want: %v\n- got: %v", + *want, + *got, + ) + } +} + +func TestGetRelatedConnectionWithOptions(t *testing.T) { + var happyPathTests = []struct { + desc string + o *relatedConnectionsOptions + want url.Values + }{ + { + desc: "normal string length", + o: &relatedConnectionsOptions{ + PeerDevice: "test-abc_def-0123456789", + PeerInterface: "test-zyx-wvu-987654321", + }, + want: url.Values{ + "peer-device": []string{"test-abc_def-0123456789"}, + "peer-interface": []string{"test-zyx-wvu-987654321"}, + }, + }, + { + desc: "super long string", + o: &relatedConnectionsOptions{ + PeerDevice: "test-abc_def-01234567890000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + PeerInterface: "test-zyx-wvu-98765432100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + want: url.Values{ + "peer-device": []string{"test-abc_def-01234567890000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}, + "peer-interface": []string{"test-zyx-wvu-98765432100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}, + }, + }, + { + desc: "super short string", + o: &relatedConnectionsOptions{ + PeerDevice: "a", + PeerInterface: "z", + }, + want: url.Values{ + "peer-device": []string{"a"}, + "peer-interface": []string{"z"}, + }, + }, + } + for i, test := range happyPathTests { + t.Logf("[%02d] happy path test %q", i, test.desc) + + got, err := test.o.values() + if err != nil { + t.Fatalf("unexpected Values error: %v", err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Fatalf( + "unexpected url.Values map:\n- want: %v\n- got: %v", + test.want, + got, + ) + } + } + + var negativeTests = []struct { + desc string + o *relatedConnectionsOptions + }{ + { + desc: "only peer-device", + o: &relatedConnectionsOptions{ + PeerDevice: "test-abc_def-0123456789", + }, + }, + { + desc: "only peer-interface", + o: &relatedConnectionsOptions{ + PeerInterface: "test-zyx-wvu-987654321", + }, + }, + { + desc: "no peer-device or peer-interface", + o: &relatedConnectionsOptions{}, + }, + } + for i, test := range negativeTests { + t.Logf("[%02d] negative path test %q", i, test.desc) + + if _, err := test.o.values(); err == nil { + t.Fatal("expected Error but got nil") + } + } +} + +func rcEqual(a, b *RelatedConnection) bool { + if ok := deviceEqual(a.Device, b.Device); !ok { + return false + } + + if !reflect.DeepEqual(a.ConsolePorts, b.ConsolePorts) { + return false + } + + if !reflect.DeepEqual(a.Interfaces, b.Interfaces) { + return false + } + + if !reflect.DeepEqual(a.PowerPorts, b.PowerPorts) { + return false + } + + return true +}